1
0
Fork 0
mirror of https://github.com/immich-app/immich.git synced 2024-12-28 22:51:59 +00:00

Optimize android's Gradle settings and clean up mobile source code (#240)

* optimize android side gradle settings

* android minsdk back to 21

* remove unused package, update linter and fix lint error
This commit is contained in:
xpwmaosldk 2022-06-22 14:23:35 +09:00 committed by GitHub
parent 63bebd92e0
commit caaa474c23
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
34 changed files with 524 additions and 333 deletions

View file

@ -1,10 +1,45 @@
# This file tracks properties of this Flutter project. # This file tracks properties of this Flutter project.
# Used by Flutter tool to assess capabilities and perform upgrades etc. # Used by Flutter tool to assess capabilities and perform upgrades etc.
# #
# This file should be version controlled and should not be manually edited. # This file should be version controlled.
version: version:
revision: 77d935af4db863f6abd0b9c31c7e6df2a13de57b revision: cd41fdd495f6944ecd3506c21e94c6567b073278
channel: stable channel: stable
project_type: app project_type: app
# Tracks metadata for the flutter migrate command
migration:
platforms:
- platform: root
create_revision: cd41fdd495f6944ecd3506c21e94c6567b073278
base_revision: cd41fdd495f6944ecd3506c21e94c6567b073278
- platform: android
create_revision: cd41fdd495f6944ecd3506c21e94c6567b073278
base_revision: cd41fdd495f6944ecd3506c21e94c6567b073278
- platform: ios
create_revision: cd41fdd495f6944ecd3506c21e94c6567b073278
base_revision: cd41fdd495f6944ecd3506c21e94c6567b073278
- platform: linux
create_revision: cd41fdd495f6944ecd3506c21e94c6567b073278
base_revision: cd41fdd495f6944ecd3506c21e94c6567b073278
- platform: macos
create_revision: cd41fdd495f6944ecd3506c21e94c6567b073278
base_revision: cd41fdd495f6944ecd3506c21e94c6567b073278
- platform: web
create_revision: cd41fdd495f6944ecd3506c21e94c6567b073278
base_revision: cd41fdd495f6944ecd3506c21e94c6567b073278
- platform: windows
create_revision: cd41fdd495f6944ecd3506c21e94c6567b073278
base_revision: cd41fdd495f6944ecd3506c21e94c6567b073278
# User provided section
# List of Local paths (relative to this file) that should be
# ignored by the migrate tool.
#
# Files that are not part of the templates will be ignored by default.
unmanaged_files:
- 'lib/main.dart'
- 'ios/Runner.xcodeproj/project.pbxproj'

View file

@ -24,6 +24,7 @@ linter:
rules: rules:
# avoid_print: false # Uncomment to disable the `avoid_print` rule # avoid_print: false # Uncomment to disable the `avoid_print` rule
# prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule
use_build_context_synchronously: false
# Additional information about this file can be found at # Additional information about this file can be found at
# https://dart.dev/guides/language/analysis-options # https://dart.dev/guides/language/analysis-options

View file

@ -81,5 +81,4 @@ flutter {
dependencies { dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
implementation 'com.android.support:multidex:1.0.3'
} }

View file

@ -1,25 +0,0 @@
// Generated file.
//
// If you wish to remove Flutter's multidex support, delete this entire file.
//
// Modifications to this file should be done in a copy under a different name
// as this file may be regenerated.
package io.flutter.app;
import android.app.Application;
import android.content.Context;
import androidx.annotation.CallSuper;
import androidx.multidex.MultiDex;
/**
* Extension of {@link android.app.Application}, adding multidex support.
*/
public class FlutterMultiDexApplication extends Application {
@Override
@CallSuper
protected void attachBaseContext(Context base) {
super.attachBaseContext(base);
MultiDex.install(this);
}
}

View file

@ -6,7 +6,7 @@ buildscript {
} }
dependencies { dependencies {
classpath 'com.android.tools.build:gradle:4.1.0' classpath 'com.android.tools.build:gradle:7.1.2'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
} }
} }

View file

@ -3,5 +3,5 @@ distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-6.7-all.zip distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-all.zip
distributionSha256Sum=0080de8491f0918e4f529a6db6820fa0b9e818ee2386117f4394f95feb1d5583 distributionSha256Sum=cd5c2958a107ee7f0722004a12d0f8559b4564c34daad7df06cffd4d12a426d0

View file

@ -42,10 +42,11 @@ class ImmichApp extends ConsumerStatefulWidget {
const ImmichApp({Key? key}) : super(key: key); const ImmichApp({Key? key}) : super(key: key);
@override @override
_ImmichAppState createState() => _ImmichAppState(); ImmichAppState createState() => ImmichAppState();
} }
class _ImmichAppState extends ConsumerState<ImmichApp> with WidgetsBindingObserver { class ImmichAppState extends ConsumerState<ImmichApp>
with WidgetsBindingObserver {
@override @override
void didChangeAppLifecycleState(AppLifecycleState state) { void didChangeAppLifecycleState(AppLifecycleState state) {
switch (state) { switch (state) {
@ -121,7 +122,8 @@ class _ImmichAppState extends ConsumerState<ImmichApp> with WidgetsBindingObserv
brightness: Brightness.light, brightness: Brightness.light,
primarySwatch: Colors.indigo, primarySwatch: Colors.indigo,
fontFamily: 'WorkSans', fontFamily: 'WorkSans',
snackBarTheme: const SnackBarThemeData(contentTextStyle: TextStyle(fontFamily: 'WorkSans')), snackBarTheme: const SnackBarThemeData(
contentTextStyle: TextStyle(fontFamily: 'WorkSans')),
scaffoldBackgroundColor: immichBackgroundColor, scaffoldBackgroundColor: immichBackgroundColor,
appBarTheme: const AppBarTheme( appBarTheme: const AppBarTheme(
backgroundColor: immichBackgroundColor, backgroundColor: immichBackgroundColor,
@ -132,7 +134,8 @@ class _ImmichAppState extends ConsumerState<ImmichApp> with WidgetsBindingObserv
), ),
), ),
routeInformationParser: _immichRouter.defaultRouteParser(), routeInformationParser: _immichRouter.defaultRouteParser(),
routerDelegate: _immichRouter.delegate(navigatorObservers: () => [TabNavigationObserver(ref: ref)]), routerDelegate: _immichRouter.delegate(
navigatorObservers: () => [TabNavigationObserver(ref: ref)]),
), ),
const ImmichLoadingOverlay(), const ImmichLoadingOverlay(),
const VersionAnnouncementOverlay(), const VersionAnnouncementOverlay(),

View file

@ -9,12 +9,14 @@ import 'package:latlong2/latlong.dart';
class ExifBottomSheet extends ConsumerWidget { class ExifBottomSheet extends ConsumerWidget {
final ImmichAssetWithExif assetDetail; final ImmichAssetWithExif assetDetail;
const ExifBottomSheet({Key? key, required this.assetDetail}) : super(key: key); const ExifBottomSheet({Key? key, required this.assetDetail})
: super(key: key);
@override @override
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
_buildMap() { _buildMap() {
return (assetDetail.exifInfo!.latitude != null && assetDetail.exifInfo!.longitude != null) return (assetDetail.exifInfo!.latitude != null &&
assetDetail.exifInfo!.longitude != null)
? Padding( ? Padding(
padding: const EdgeInsets.symmetric(vertical: 16.0), padding: const EdgeInsets.symmetric(vertical: 16.0),
child: Container( child: Container(
@ -25,12 +27,14 @@ class ExifBottomSheet extends ConsumerWidget {
), ),
child: FlutterMap( child: FlutterMap(
options: MapOptions( options: MapOptions(
center: LatLng(assetDetail.exifInfo!.latitude!, assetDetail.exifInfo!.longitude!), center: LatLng(assetDetail.exifInfo!.latitude!,
assetDetail.exifInfo!.longitude!),
zoom: 16.0, zoom: 16.0,
), ),
layers: [ layers: [
TileLayerOptions( TileLayerOptions(
urlTemplate: "https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", urlTemplate:
"https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png",
subdomains: ['a', 'b', 'c'], subdomains: ['a', 'b', 'c'],
attributionBuilder: (_) { attributionBuilder: (_) {
return const Text( return const Text(
@ -43,8 +47,10 @@ class ExifBottomSheet extends ConsumerWidget {
markers: [ markers: [
Marker( Marker(
anchorPos: AnchorPos.align(AnchorAlign.top), anchorPos: AnchorPos.align(AnchorAlign.top),
point: LatLng(assetDetail.exifInfo!.latitude!, assetDetail.exifInfo!.longitude!), point: LatLng(assetDetail.exifInfo!.latitude!,
builder: (ctx) => const Image(image: AssetImage('assets/location-pin.png')), assetDetail.exifInfo!.longitude!),
builder: (ctx) => const Image(
image: AssetImage('assets/location-pin.png')),
), ),
], ],
), ),
@ -56,10 +62,14 @@ class ExifBottomSheet extends ConsumerWidget {
} }
_buildLocationText() { _buildLocationText() {
return (assetDetail.exifInfo!.city != null && assetDetail.exifInfo!.state != null) return (assetDetail.exifInfo!.city != null &&
assetDetail.exifInfo!.state != null)
? Text( ? Text(
"${assetDetail.exifInfo!.city}, ${assetDetail.exifInfo!.state}", "${assetDetail.exifInfo!.city}, ${assetDetail.exifInfo!.state}",
style: TextStyle(fontSize: 12, color: Colors.grey[200], fontWeight: FontWeight.bold), style: TextStyle(
fontSize: 12,
color: Colors.grey[200],
fontWeight: FontWeight.bold),
) )
: Container(); : Container();
} }
@ -131,7 +141,8 @@ class ExifBottomSheet extends ConsumerWidget {
padding: const EdgeInsets.only(bottom: 8.0), padding: const EdgeInsets.only(bottom: 8.0),
child: Text( child: Text(
"DETAILS", "DETAILS",
style: TextStyle(fontSize: 11, color: Colors.grey[400]), style:
TextStyle(fontSize: 11, color: Colors.grey[400]),
), ),
), ),
ListTile( ListTile(
@ -158,7 +169,8 @@ class ExifBottomSheet extends ConsumerWidget {
leading: const Icon(Icons.camera), leading: const Icon(Icons.camera),
title: Text( title: Text(
"${assetDetail.exifInfo?.make} ${assetDetail.exifInfo?.model}", "${assetDetail.exifInfo?.make} ${assetDetail.exifInfo?.model}",
style: const TextStyle(fontWeight: FontWeight.bold), style: const TextStyle(
fontWeight: FontWeight.bold),
), ),
subtitle: Text( subtitle: Text(
"ƒ/${assetDetail.exifInfo?.fNumber} 1/${(1 / assetDetail.exifInfo!.exposureTime!).toStringAsFixed(0)} ${assetDetail.exifInfo?.focalLength}mm ISO${assetDetail.exifInfo?.iso} "), "ƒ/${assetDetail.exifInfo?.fNumber} 1/${(1 / assetDetail.exifInfo!.exposureTime!).toStringAsFixed(0)} ${assetDetail.exifInfo?.focalLength}mm ISO${assetDetail.exifInfo?.iso} "),

View file

@ -1,7 +1,6 @@
import 'package:cached_network_image/cached_network_image.dart'; import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter/cupertino.dart'; import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/src/widgets/framework.dart';
import 'package:photo_view/photo_view.dart'; import 'package:photo_view/photo_view.dart';
enum _RemoteImageStatus { empty, thumbnail, full } enum _RemoteImageStatus { empty, thumbnail, full }

View file

@ -1,3 +1,5 @@
import 'dart:developer';
import 'package:auto_route/auto_route.dart'; import 'package:auto_route/auto_route.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
@ -5,7 +7,10 @@ import 'package:immich_mobile/shared/models/immich_asset.model.dart';
class TopControlAppBar extends ConsumerWidget with PreferredSizeWidget { class TopControlAppBar extends ConsumerWidget with PreferredSizeWidget {
const TopControlAppBar( const TopControlAppBar(
{Key? key, required this.asset, required this.onMoreInfoPressed, required this.onDownloadPressed}) {Key? key,
required this.asset,
required this.onMoreInfoPressed,
required this.onDownloadPressed})
: super(key: key); : super(key: key);
final ImmichAsset asset; final ImmichAsset asset;
@ -42,9 +47,11 @@ class TopControlAppBar extends ConsumerWidget with PreferredSizeWidget {
iconSize: iconSize, iconSize: iconSize,
splashRadius: iconSize, splashRadius: iconSize,
onPressed: () { onPressed: () {
print("favorite"); log("favorite");
}, },
icon: asset.isFavorite ? const Icon(Icons.favorite_rounded) : const Icon(Icons.favorite_border_rounded), icon: asset.isFavorite
? const Icon(Icons.favorite_rounded)
: const Icon(Icons.favorite_border_rounded),
), ),
IconButton( IconButton(
iconSize: iconSize, iconSize: iconSize,

View file

@ -1,16 +1,15 @@
import 'package:cancellation_token_http/http.dart'; import 'package:cancellation_token_http/http.dart';
import 'package:dio/dio.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:hive_flutter/hive_flutter.dart'; import 'package:hive_flutter/hive_flutter.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/constants/hive_box.dart'; import 'package:immich_mobile/constants/hive_box.dart';
import 'package:immich_mobile/modules/backup/models/available_album.model.dart'; import 'package:immich_mobile/modules/backup/models/available_album.model.dart';
import 'package:immich_mobile/modules/backup/models/hive_backup_albums.model.dart';
import 'package:immich_mobile/modules/login/providers/authentication.provider.dart';
import 'package:immich_mobile/shared/services/server_info.service.dart';
import 'package:immich_mobile/modules/backup/models/backup_state.model.dart'; import 'package:immich_mobile/modules/backup/models/backup_state.model.dart';
import 'package:immich_mobile/shared/models/server_info.model.dart'; import 'package:immich_mobile/modules/backup/models/hive_backup_albums.model.dart';
import 'package:immich_mobile/modules/backup/services/backup.service.dart'; import 'package:immich_mobile/modules/backup/services/backup.service.dart';
import 'package:immich_mobile/modules/login/providers/authentication.provider.dart';
import 'package:immich_mobile/shared/models/server_info.model.dart';
import 'package:immich_mobile/shared/services/server_info.service.dart';
import 'package:photo_manager/photo_manager.dart'; import 'package:photo_manager/photo_manager.dart';
class BackupNotifier extends StateNotifier<BackUpState> { class BackupNotifier extends StateNotifier<BackUpState> {
@ -55,7 +54,8 @@ class BackupNotifier extends StateNotifier<BackUpState> {
removeExcludedAlbumForBackup(album); removeExcludedAlbumForBackup(album);
} }
state = state.copyWith(selectedBackupAlbums: {...state.selectedBackupAlbums, album}); state = state
.copyWith(selectedBackupAlbums: {...state.selectedBackupAlbums, album});
_updateBackupAssetCount(); _updateBackupAssetCount();
} }
@ -63,7 +63,8 @@ class BackupNotifier extends StateNotifier<BackUpState> {
if (state.selectedBackupAlbums.contains(album)) { if (state.selectedBackupAlbums.contains(album)) {
removeAlbumForBackup(album); removeAlbumForBackup(album);
} }
state = state.copyWith(excludedBackupAlbums: {...state.excludedBackupAlbums, album}); state = state
.copyWith(excludedBackupAlbums: {...state.excludedBackupAlbums, album});
_updateBackupAssetCount(); _updateBackupAssetCount();
} }
@ -94,16 +95,19 @@ class BackupNotifier extends StateNotifier<BackUpState> {
Future<void> getBackupAlbumsInfo() async { Future<void> getBackupAlbumsInfo() async {
// Get all albums on the device // Get all albums on the device
List<AvailableAlbum> availableAlbums = []; List<AvailableAlbum> availableAlbums = [];
List<AssetPathEntity> albums = await PhotoManager.getAssetPathList(hasAll: true, type: RequestType.common); List<AssetPathEntity> albums = await PhotoManager.getAssetPathList(
hasAll: true, type: RequestType.common);
for (AssetPathEntity album in albums) { for (AssetPathEntity album in albums) {
AvailableAlbum availableAlbum = AvailableAlbum(albumEntity: album); AvailableAlbum availableAlbum = AvailableAlbum(albumEntity: album);
var assetList = await album.getAssetListRange(start: 0, end: album.assetCount); var assetList =
await album.getAssetListRange(start: 0, end: album.assetCount);
if (assetList.isNotEmpty) { if (assetList.isNotEmpty) {
var thumbnailAsset = assetList.first; var thumbnailAsset = assetList.first;
var thumbnailData = await thumbnailAsset.thumbnailDataWithSize(const ThumbnailSize(512, 512)); var thumbnailData = await thumbnailAsset
.thumbnailDataWithSize(const ThumbnailSize(512, 512));
availableAlbum = availableAlbum.copyWith(thumbnailData: thumbnailData); availableAlbum = availableAlbum.copyWith(thumbnailData: thumbnailData);
} }
@ -114,7 +118,8 @@ class BackupNotifier extends StateNotifier<BackUpState> {
// Put persistent storage info into local state of the app // Put persistent storage info into local state of the app
// Get local storage on selected backup album // Get local storage on selected backup album
Box<HiveBackupAlbums> backupAlbumInfoBox = Hive.box<HiveBackupAlbums>(hiveBackupInfoBox); Box<HiveBackupAlbums> backupAlbumInfoBox =
Hive.box<HiveBackupAlbums>(hiveBackupInfoBox);
HiveBackupAlbums? backupAlbumInfo = backupAlbumInfoBox.get( HiveBackupAlbums? backupAlbumInfo = backupAlbumInfoBox.get(
backupInfoKey, backupInfoKey,
defaultValue: HiveBackupAlbums( defaultValue: HiveBackupAlbums(
@ -133,7 +138,8 @@ class BackupNotifier extends StateNotifier<BackUpState> {
debugPrint("First time backup setup recent album as default"); debugPrint("First time backup setup recent album as default");
// Get album that contains all assets // Get album that contains all assets
var list = await PhotoManager.getAssetPathList(hasAll: true, onlyAll: true, type: RequestType.common); var list = await PhotoManager.getAssetPathList(
hasAll: true, onlyAll: true, type: RequestType.common);
AssetPathEntity albumHasAllAssets = list.first; AssetPathEntity albumHasAllAssets = list.first;
backupAlbumInfoBox.put( backupAlbumInfoBox.put(
@ -151,12 +157,14 @@ class BackupNotifier extends StateNotifier<BackUpState> {
try { try {
for (var selectedAlbumId in backupAlbumInfo!.selectedAlbumIds) { for (var selectedAlbumId in backupAlbumInfo!.selectedAlbumIds) {
var albumAsset = await AssetPathEntity.fromId(selectedAlbumId); var albumAsset = await AssetPathEntity.fromId(selectedAlbumId);
state = state.copyWith(selectedBackupAlbums: {...state.selectedBackupAlbums, albumAsset}); state = state.copyWith(
selectedBackupAlbums: {...state.selectedBackupAlbums, albumAsset});
} }
for (var excludedAlbumId in backupAlbumInfo.excludedAlbumsIds) { for (var excludedAlbumId in backupAlbumInfo.excludedAlbumsIds) {
var albumAsset = await AssetPathEntity.fromId(excludedAlbumId); var albumAsset = await AssetPathEntity.fromId(excludedAlbumId);
state = state.copyWith(excludedBackupAlbums: {...state.excludedBackupAlbums, albumAsset}); state = state.copyWith(
excludedBackupAlbums: {...state.excludedBackupAlbums, albumAsset});
} }
} catch (e) { } catch (e) {
debugPrint("[ERROR] Failed to generate album from id $e"); debugPrint("[ERROR] Failed to generate album from id $e");
@ -173,21 +181,27 @@ class BackupNotifier extends StateNotifier<BackUpState> {
Set<AssetEntity> assetsFromExcludedAlbums = {}; Set<AssetEntity> assetsFromExcludedAlbums = {};
for (var album in state.selectedBackupAlbums) { for (var album in state.selectedBackupAlbums) {
var assets = await album.getAssetListRange(start: 0, end: album.assetCount); var assets =
await album.getAssetListRange(start: 0, end: album.assetCount);
assetsFromSelectedAlbums.addAll(assets); assetsFromSelectedAlbums.addAll(assets);
} }
for (var album in state.excludedBackupAlbums) { for (var album in state.excludedBackupAlbums) {
var assets = await album.getAssetListRange(start: 0, end: album.assetCount); var assets =
await album.getAssetListRange(start: 0, end: album.assetCount);
assetsFromExcludedAlbums.addAll(assets); assetsFromExcludedAlbums.addAll(assets);
} }
Set<AssetEntity> allUniqueAssets = assetsFromSelectedAlbums.difference(assetsFromExcludedAlbums); Set<AssetEntity> allUniqueAssets =
List<String> allAssetOnDatabase = await _backupService.getDeviceBackupAsset(); assetsFromSelectedAlbums.difference(assetsFromExcludedAlbums);
List<String> allAssetOnDatabase =
await _backupService.getDeviceBackupAsset();
// Find asset that were backup from selected albums // Find asset that were backup from selected albums
Set<String> selectedAlbumsBackupAssets = Set.from(allUniqueAssets.map((e) => e.id)); Set<String> selectedAlbumsBackupAssets =
selectedAlbumsBackupAssets.removeWhere((assetId) => !allAssetOnDatabase.contains(assetId)); Set.from(allUniqueAssets.map((e) => e.id));
selectedAlbumsBackupAssets
.removeWhere((assetId) => !allAssetOnDatabase.contains(assetId));
if (allUniqueAssets.isEmpty) { if (allUniqueAssets.isEmpty) {
debugPrint("No Asset On Device"); debugPrint("No Asset On Device");
@ -226,7 +240,8 @@ class BackupNotifier extends StateNotifier<BackUpState> {
/// Hive database /// Hive database
/// ///
void _updatePersistentAlbumsSelection() { void _updatePersistentAlbumsSelection() {
Box<HiveBackupAlbums> backupAlbumInfoBox = Hive.box<HiveBackupAlbums>(hiveBackupInfoBox); Box<HiveBackupAlbums> backupAlbumInfoBox =
Hive.box<HiveBackupAlbums>(hiveBackupInfoBox);
backupAlbumInfoBox.put( backupAlbumInfoBox.put(
backupInfoKey, backupInfoKey,
HiveBackupAlbums( HiveBackupAlbums(
@ -268,7 +283,8 @@ class BackupNotifier extends StateNotifier<BackUpState> {
// Perform Backup // Perform Backup
state = state.copyWith(cancelToken: CancellationToken()); state = state.copyWith(cancelToken: CancellationToken());
_backupService.backupAsset(assetsWillBeBackup, state.cancelToken, _onAssetUploaded, _onUploadProgress); _backupService.backupAsset(assetsWillBeBackup, state.cancelToken,
_onAssetUploaded, _onUploadProgress);
} else { } else {
PhotoManager.openSetting(); PhotoManager.openSetting();
} }
@ -276,23 +292,32 @@ class BackupNotifier extends StateNotifier<BackUpState> {
void cancelBackup() { void cancelBackup() {
state.cancelToken.cancel(); state.cancelToken.cancel();
state = state.copyWith(backupProgress: BackUpProgressEnum.idle, progressInPercentage: 0.0); state = state.copyWith(
backupProgress: BackUpProgressEnum.idle, progressInPercentage: 0.0);
} }
void _onAssetUploaded(String deviceAssetId, String deviceId) { void _onAssetUploaded(String deviceAssetId, String deviceId) {
state = state.copyWith( state = state.copyWith(selectedAlbumsBackupAssetsIds: {
selectedAlbumsBackupAssetsIds: {...state.selectedAlbumsBackupAssetsIds, deviceAssetId}, ...state.selectedAlbumsBackupAssetsIds,
allAssetOnDatabase: [...state.allAssetOnDatabase, deviceAssetId]); deviceAssetId
}, allAssetOnDatabase: [
...state.allAssetOnDatabase,
deviceAssetId
]);
if (state.allUniqueAssets.length - state.selectedAlbumsBackupAssetsIds.length == 0) { if (state.allUniqueAssets.length -
state = state.copyWith(backupProgress: BackUpProgressEnum.done, progressInPercentage: 0.0); state.selectedAlbumsBackupAssetsIds.length ==
0) {
state = state.copyWith(
backupProgress: BackUpProgressEnum.done, progressInPercentage: 0.0);
} }
_updateServerInfo(); _updateServerInfo();
} }
void _onUploadProgress(int sent, int total) { void _onUploadProgress(int sent, int total) {
state = state.copyWith(progressInPercentage: (sent.toDouble() / total.toDouble() * 100)); state = state.copyWith(
progressInPercentage: (sent.toDouble() / total.toDouble() * 100));
} }
void _updateServerInfo() async { void _updateServerInfo() async {
@ -326,7 +351,8 @@ class BackupNotifier extends StateNotifier<BackUpState> {
} }
// Check if this device is enable backup by the user // Check if this device is enable backup by the user
if ((authState.deviceInfo.deviceId == authState.deviceId) && authState.deviceInfo.isAutoBackup) { if ((authState.deviceInfo.deviceId == authState.deviceId) &&
authState.deviceInfo.isAutoBackup) {
// check if backup is alreayd in process - then return // check if backup is alreayd in process - then return
if (state.backupProgress == BackUpProgressEnum.inProgress) { if (state.backupProgress == BackUpProgressEnum.inProgress) {
debugPrint("[resumeBackup] Backup is already in progress - abort"); debugPrint("[resumeBackup] Backup is already in progress - abort");
@ -343,6 +369,7 @@ class BackupNotifier extends StateNotifier<BackUpState> {
} }
} }
final backupProvider = StateNotifierProvider<BackupNotifier, BackUpState>((ref) { final backupProvider =
StateNotifierProvider<BackupNotifier, BackUpState>((ref) {
return BackupNotifier(ref: ref); return BackupNotifier(ref: ref);
}); });

View file

@ -17,16 +17,21 @@ class BackupControllerPage extends HookConsumerWidget {
@override @override
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
BackUpState backupState = ref.watch(backupProvider); BackUpState backupState = ref.watch(backupProvider);
AuthenticationState _authenticationState = ref.watch(authenticationProvider); AuthenticationState authenticationState = ref.watch(authenticationProvider);
bool shouldBackup = bool shouldBackup = backupState.allUniqueAssets.length -
backupState.allUniqueAssets.length - backupState.selectedAlbumsBackupAssetsIds.length == 0 ? false : true; backupState.selectedAlbumsBackupAssetsIds.length ==
0
? false
: true;
useEffect(() { useEffect(() {
if (backupState.backupProgress != BackUpProgressEnum.inProgress) { if (backupState.backupProgress != BackUpProgressEnum.inProgress) {
ref.read(backupProvider.notifier).getBackupInfo(); ref.read(backupProvider.notifier).getBackupInfo();
} }
ref.watch(websocketProvider.notifier).stopListenToEvent('on_upload_success'); ref
.watch(websocketProvider.notifier)
.stopListenToEvent('on_upload_success');
return null; return null;
}, []); }, []);
@ -48,7 +53,8 @@ class BackupControllerPage extends HookConsumerWidget {
Padding( Padding(
padding: const EdgeInsets.only(top: 8.0), padding: const EdgeInsets.only(top: 8.0),
child: LinearPercentIndicator( child: LinearPercentIndicator(
padding: const EdgeInsets.symmetric(horizontal: 0, vertical: 0), padding:
const EdgeInsets.symmetric(horizontal: 0, vertical: 0),
barRadius: const Radius.circular(2), barRadius: const Radius.circular(2),
lineHeight: 6.0, lineHeight: 6.0,
percent: backupState.serverInfo.diskUsagePercentage / 100.0, percent: backupState.serverInfo.diskUsagePercentage / 100.0,
@ -58,7 +64,8 @@ class BackupControllerPage extends HookConsumerWidget {
), ),
Padding( Padding(
padding: const EdgeInsets.only(top: 12.0), padding: const EdgeInsets.only(top: 12.0),
child: Text('${backupState.serverInfo.diskUse} of ${backupState.serverInfo.diskSize} used'), child: Text(
'${backupState.serverInfo.diskUse} of ${backupState.serverInfo.diskSize} used'),
), ),
], ],
), ),
@ -67,9 +74,11 @@ class BackupControllerPage extends HookConsumerWidget {
} }
ListTile _buildBackupController() { ListTile _buildBackupController() {
var backUpOption = _authenticationState.deviceInfo.isAutoBackup ? "on" : "off"; var backUpOption =
var isAutoBackup = _authenticationState.deviceInfo.isAutoBackup; authenticationState.deviceInfo.isAutoBackup ? "on" : "off";
var backupBtnText = _authenticationState.deviceInfo.isAutoBackup ? "off" : "on"; var isAutoBackup = authenticationState.deviceInfo.isAutoBackup;
var backupBtnText =
authenticationState.deviceInfo.isAutoBackup ? "off" : "on";
return ListTile( return ListTile(
isThreeLine: true, isThreeLine: true,
leading: isAutoBackup leading: isAutoBackup
@ -104,10 +113,15 @@ class BackupControllerPage extends HookConsumerWidget {
), ),
onPressed: () { onPressed: () {
isAutoBackup isAutoBackup
? ref.watch(authenticationProvider.notifier).setAutoBackup(false) ? ref
: ref.watch(authenticationProvider.notifier).setAutoBackup(true); .watch(authenticationProvider.notifier)
.setAutoBackup(false)
: ref
.watch(authenticationProvider.notifier)
.setAutoBackup(true);
}, },
child: Text("Turn $backupBtnText Backup", style: const TextStyle(fontWeight: FontWeight.bold)), child: Text("Turn $backupBtnText Backup",
style: const TextStyle(fontWeight: FontWeight.bold)),
), ),
) )
], ],
@ -133,7 +147,10 @@ class BackupControllerPage extends HookConsumerWidget {
padding: const EdgeInsets.only(top: 8.0), padding: const EdgeInsets.only(top: 8.0),
child: Text( child: Text(
text.trim().substring(0, text.length - 2), text.trim().substring(0, text.length - 2),
style: TextStyle(color: Theme.of(context).primaryColor, fontSize: 12, fontWeight: FontWeight.bold), style: TextStyle(
color: Theme.of(context).primaryColor,
fontSize: 12,
fontWeight: FontWeight.bold),
), ),
); );
} else { } else {
@ -141,7 +158,10 @@ class BackupControllerPage extends HookConsumerWidget {
padding: const EdgeInsets.only(top: 8.0), padding: const EdgeInsets.only(top: 8.0),
child: Text( child: Text(
"None selected", "None selected",
style: TextStyle(color: Theme.of(context).primaryColor, fontSize: 12, fontWeight: FontWeight.bold), style: TextStyle(
color: Theme.of(context).primaryColor,
fontSize: 12,
fontWeight: FontWeight.bold),
), ),
); );
} }
@ -160,7 +180,10 @@ class BackupControllerPage extends HookConsumerWidget {
padding: const EdgeInsets.only(top: 8.0), padding: const EdgeInsets.only(top: 8.0),
child: Text( child: Text(
text.trim().substring(0, text.length - 2), text.trim().substring(0, text.length - 2),
style: TextStyle(color: Colors.red[300], fontSize: 12, fontWeight: FontWeight.bold), style: TextStyle(
color: Colors.red[300],
fontSize: 12,
fontWeight: FontWeight.bold),
), ),
); );
} else { } else {
@ -181,7 +204,8 @@ class BackupControllerPage extends HookConsumerWidget {
borderOnForeground: false, borderOnForeground: false,
child: ListTile( child: ListTile(
minVerticalPadding: 15, minVerticalPadding: 15,
title: const Text("Backup Albums", style: TextStyle(fontWeight: FontWeight.bold, fontSize: 20)), title: const Text("Backup Albums",
style: TextStyle(fontWeight: FontWeight.bold, fontSize: 20)),
subtitle: Padding( subtitle: Padding(
padding: const EdgeInsets.only(top: 8.0), padding: const EdgeInsets.only(top: 8.0),
child: Column( child: Column(
@ -258,13 +282,16 @@ class BackupControllerPage extends HookConsumerWidget {
), ),
BackupInfoCard( BackupInfoCard(
title: "Backup", title: "Backup",
subtitle: "Photos and videos from selected albums that are backup", subtitle:
"Photos and videos from selected albums that are backup",
info: "${backupState.selectedAlbumsBackupAssetsIds.length}", info: "${backupState.selectedAlbumsBackupAssetsIds.length}",
), ),
BackupInfoCard( BackupInfoCard(
title: "Remainder", title: "Remainder",
subtitle: "Photos and videos that has not been backing up from selected albums", subtitle:
info: "${backupState.allUniqueAssets.length - backupState.selectedAlbumsBackupAssetsIds.length}", "Photos and videos that has not been backing up from selected albums",
info:
"${backupState.allUniqueAssets.length - backupState.selectedAlbumsBackupAssetsIds.length}",
), ),
const Divider(), const Divider(),
_buildBackupController(), _buildBackupController(),
@ -289,29 +316,32 @@ class BackupControllerPage extends HookConsumerWidget {
Padding( Padding(
padding: const EdgeInsets.all(8.0), padding: const EdgeInsets.all(8.0),
child: Container( child: Container(
child: backupState.backupProgress == BackUpProgressEnum.inProgress child:
? ElevatedButton( backupState.backupProgress == BackUpProgressEnum.inProgress
style: ElevatedButton.styleFrom( ? ElevatedButton(
primary: Colors.red[300], style: ElevatedButton.styleFrom(
onPrimary: Colors.grey[50], primary: Colors.red[300],
), onPrimary: Colors.grey[50],
onPressed: () { ),
ref.read(backupProvider.notifier).cancelBackup(); onPressed: () {
}, ref.read(backupProvider.notifier).cancelBackup();
child: const Text("Cancel"), },
) child: const Text("Cancel"),
: ElevatedButton( )
style: ElevatedButton.styleFrom( : ElevatedButton(
primary: Theme.of(context).primaryColor, style: ElevatedButton.styleFrom(
onPrimary: Colors.grey[50], primary: Theme.of(context).primaryColor,
), onPrimary: Colors.grey[50],
onPressed: shouldBackup ),
? () { onPressed: shouldBackup
ref.read(backupProvider.notifier).startBackupProcess(); ? () {
} ref
: null, .read(backupProvider.notifier)
child: const Text("Start Backup"), .startBackupProcess();
), }
: null,
child: const Text("Start Backup"),
),
), ),
) )
], ],

View file

@ -1,6 +1,5 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/modules/home/providers/home_page_state.provider.dart';
class DisableMultiSelectButton extends ConsumerWidget { class DisableMultiSelectButton extends ConsumerWidget {
const DisableMultiSelectButton({ const DisableMultiSelectButton({
@ -36,7 +35,8 @@ class DisableMultiSelectButton extends ConsumerWidget {
icon: const Icon(Icons.close_rounded), icon: const Icon(Icons.close_rounded),
label: Text( label: Text(
selectedItemCount.toString(), selectedItemCount.toString(),
style: const TextStyle(fontWeight: FontWeight.w600, fontSize: 18), style: const TextStyle(
fontWeight: FontWeight.w600, fontSize: 18),
)), )),
), ),
), ),

View file

@ -81,7 +81,8 @@ class DraggableScrollbar extends StatefulWidget {
this.labelTextBuilder, this.labelTextBuilder,
this.labelConstraints, this.labelConstraints,
}) : assert(child.scrollDirection == Axis.vertical), }) : assert(child.scrollDirection == Axis.vertical),
scrollThumbBuilder = _thumbRRectBuilder(scrollThumbKey, alwaysVisibleScrollThumb), scrollThumbBuilder =
_thumbRRectBuilder(scrollThumbKey, alwaysVisibleScrollThumb),
super(key: key); super(key: key);
DraggableScrollbar.arrows({ DraggableScrollbar.arrows({
@ -98,7 +99,8 @@ class DraggableScrollbar extends StatefulWidget {
this.labelTextBuilder, this.labelTextBuilder,
this.labelConstraints, this.labelConstraints,
}) : assert(child.scrollDirection == Axis.vertical), }) : assert(child.scrollDirection == Axis.vertical),
scrollThumbBuilder = _thumbArrowBuilder(scrollThumbKey, alwaysVisibleScrollThumb), scrollThumbBuilder =
_thumbArrowBuilder(scrollThumbKey, alwaysVisibleScrollThumb),
super(key: key); super(key: key);
DraggableScrollbar.semicircle({ DraggableScrollbar.semicircle({
@ -115,11 +117,12 @@ class DraggableScrollbar extends StatefulWidget {
this.labelTextBuilder, this.labelTextBuilder,
this.labelConstraints, this.labelConstraints,
}) : assert(child.scrollDirection == Axis.vertical), }) : assert(child.scrollDirection == Axis.vertical),
scrollThumbBuilder = _thumbSemicircleBuilder(heightScrollThumb * 0.6, scrollThumbKey, alwaysVisibleScrollThumb), scrollThumbBuilder = _thumbSemicircleBuilder(
heightScrollThumb * 0.6, scrollThumbKey, alwaysVisibleScrollThumb),
super(key: key); super(key: key);
@override @override
_DraggableScrollbarState createState() => _DraggableScrollbarState(); DraggableScrollbarState createState() => DraggableScrollbarState();
static buildScrollThumbAndLabel( static buildScrollThumbAndLabel(
{required Widget scrollThumb, {required Widget scrollThumb,
@ -137,9 +140,9 @@ class DraggableScrollbar extends StatefulWidget {
children: [ children: [
ScrollLabel( ScrollLabel(
animation: labelAnimation, animation: labelAnimation,
child: labelText,
backgroundColor: backgroundColor, backgroundColor: backgroundColor,
constraints: labelConstraints, constraints: labelConstraints,
child: labelText,
), ),
scrollThumb, scrollThumb,
], ],
@ -154,7 +157,8 @@ class DraggableScrollbar extends StatefulWidget {
); );
} }
static ScrollThumbBuilder _thumbSemicircleBuilder(double width, Key? scrollThumbKey, bool alwaysVisibleScrollThumb) { static ScrollThumbBuilder _thumbSemicircleBuilder(
double width, Key? scrollThumbKey, bool alwaysVisibleScrollThumb) {
return ( return (
Color backgroundColor, Color backgroundColor,
Animation<double> thumbAnimation, Animation<double> thumbAnimation,
@ -168,9 +172,6 @@ class DraggableScrollbar extends StatefulWidget {
foregroundPainter: ArrowCustomPainter(Colors.white), foregroundPainter: ArrowCustomPainter(Colors.white),
child: Material( child: Material(
elevation: 4.0, elevation: 4.0,
child: Container(
constraints: BoxConstraints.tight(Size(width, height)),
),
color: backgroundColor, color: backgroundColor,
borderRadius: BorderRadius.only( borderRadius: BorderRadius.only(
topLeft: Radius.circular(height), topLeft: Radius.circular(height),
@ -178,6 +179,9 @@ class DraggableScrollbar extends StatefulWidget {
topRight: const Radius.circular(4.0), topRight: const Radius.circular(4.0),
bottomRight: const Radius.circular(4.0), bottomRight: const Radius.circular(4.0),
), ),
child: Container(
constraints: BoxConstraints.tight(Size(width, height)),
),
), ),
); );
@ -193,7 +197,8 @@ class DraggableScrollbar extends StatefulWidget {
}; };
} }
static ScrollThumbBuilder _thumbArrowBuilder(Key? scrollThumbKey, bool alwaysVisibleScrollThumb) { static ScrollThumbBuilder _thumbArrowBuilder(
Key? scrollThumbKey, bool alwaysVisibleScrollThumb) {
return ( return (
Color backgroundColor, Color backgroundColor,
Animation<double> thumbAnimation, Animation<double> thumbAnimation,
@ -203,6 +208,7 @@ class DraggableScrollbar extends StatefulWidget {
BoxConstraints? labelConstraints, BoxConstraints? labelConstraints,
}) { }) {
final scrollThumb = ClipPath( final scrollThumb = ClipPath(
clipper: ArrowClipper(),
child: Container( child: Container(
height: height, height: height,
width: 20.0, width: 20.0,
@ -213,7 +219,6 @@ class DraggableScrollbar extends StatefulWidget {
), ),
), ),
), ),
clipper: ArrowClipper(),
); );
return buildScrollThumbAndLabel( return buildScrollThumbAndLabel(
@ -228,7 +233,8 @@ class DraggableScrollbar extends StatefulWidget {
}; };
} }
static ScrollThumbBuilder _thumbRRectBuilder(Key? scrollThumbKey, bool alwaysVisibleScrollThumb) { static ScrollThumbBuilder _thumbRRectBuilder(
Key? scrollThumbKey, bool alwaysVisibleScrollThumb) {
return ( return (
Color backgroundColor, Color backgroundColor,
Animation<double> thumbAnimation, Animation<double> thumbAnimation,
@ -239,13 +245,13 @@ class DraggableScrollbar extends StatefulWidget {
}) { }) {
final scrollThumb = Material( final scrollThumb = Material(
elevation: 4.0, elevation: 4.0,
color: backgroundColor,
borderRadius: const BorderRadius.all(Radius.circular(7.0)),
child: Container( child: Container(
constraints: BoxConstraints.tight( constraints: BoxConstraints.tight(
Size(16.0, height), Size(16.0, height),
), ),
), ),
color: backgroundColor,
borderRadius: const BorderRadius.all(Radius.circular(7.0)),
); );
return buildScrollThumbAndLabel( return buildScrollThumbAndLabel(
@ -267,7 +273,8 @@ class ScrollLabel extends StatelessWidget {
final Text child; final Text child;
final BoxConstraints? constraints; final BoxConstraints? constraints;
static const BoxConstraints _defaultConstraints = BoxConstraints.tightFor(width: 72.0, height: 28.0); static const BoxConstraints _defaultConstraints =
BoxConstraints.tightFor(width: 72.0, height: 28.0);
const ScrollLabel({ const ScrollLabel({
Key? key, Key? key,
@ -298,7 +305,8 @@ class ScrollLabel extends StatelessWidget {
} }
} }
class _DraggableScrollbarState extends State<DraggableScrollbar> with TickerProviderStateMixin { class DraggableScrollbarState extends State<DraggableScrollbar>
with TickerProviderStateMixin {
late double _barOffset; late double _barOffset;
late double _viewOffset; late double _viewOffset;
late bool _isDragInProcess; late bool _isDragInProcess;
@ -345,7 +353,8 @@ class _DraggableScrollbarState extends State<DraggableScrollbar> with TickerProv
super.dispose(); super.dispose();
} }
double get barMaxScrollExtent => context.size!.height - widget.heightScrollThumb; double get barMaxScrollExtent =>
context.size!.height - widget.heightScrollThumb;
double get barMinScrollExtent => 0; double get barMinScrollExtent => 0;
@ -362,7 +371,8 @@ class _DraggableScrollbarState extends State<DraggableScrollbar> with TickerProv
); );
} }
return LayoutBuilder(builder: (BuildContext context, BoxConstraints constraints) { return LayoutBuilder(
builder: (BuildContext context, BoxConstraints constraints) {
//print("LayoutBuilder constraints=$constraints"); //print("LayoutBuilder constraints=$constraints");
return NotificationListener<ScrollNotification>( return NotificationListener<ScrollNotification>(
@ -432,7 +442,8 @@ class _DraggableScrollbarState extends State<DraggableScrollbar> with TickerProv
} }
} }
if (notification is ScrollUpdateNotification || notification is OverscrollNotification) { if (notification is ScrollUpdateNotification ||
notification is OverscrollNotification) {
if (_thumbAnimationController.status != AnimationStatus.forward) { if (_thumbAnimationController.status != AnimationStatus.forward) {
_thumbAnimationController.forward(); _thumbAnimationController.forward();
} }
@ -486,7 +497,8 @@ class _DraggableScrollbarState extends State<DraggableScrollbar> with TickerProv
_barOffset = barMaxScrollExtent; _barOffset = barMaxScrollExtent;
} }
double viewDelta = getScrollViewDelta(details.delta.dy, barMaxScrollExtent, viewMaxScrollExtent); double viewDelta = getScrollViewDelta(
details.delta.dy, barMaxScrollExtent, viewMaxScrollExtent);
_viewOffset = widget.controller.position.pixels + viewDelta; _viewOffset = widget.controller.position.pixels + viewDelta;
if (_viewOffset < widget.controller.position.minScrollExtent) { if (_viewOffset < widget.controller.position.minScrollExtent) {
@ -566,7 +578,8 @@ class ArrowClipper extends CustomClipper<Path> {
path.lineTo(startPointX + arrowWidth / 2, startPointY - arrowWidth / 2); path.lineTo(startPointX + arrowWidth / 2, startPointY - arrowWidth / 2);
path.lineTo(startPointX + arrowWidth, startPointY); path.lineTo(startPointX + arrowWidth, startPointY);
path.lineTo(startPointX + arrowWidth, startPointY + 1.0); path.lineTo(startPointX + arrowWidth, startPointY + 1.0);
path.lineTo(startPointX + arrowWidth / 2, startPointY - arrowWidth / 2 + 1.0); path.lineTo(
startPointX + arrowWidth / 2, startPointY - arrowWidth / 2 + 1.0);
path.lineTo(startPointX, startPointY + 1.0); path.lineTo(startPointX, startPointY + 1.0);
path.close(); path.close();
@ -575,7 +588,8 @@ class ArrowClipper extends CustomClipper<Path> {
path.lineTo(startPointX + arrowWidth / 2, startPointY + arrowWidth / 2); path.lineTo(startPointX + arrowWidth / 2, startPointY + arrowWidth / 2);
path.lineTo(startPointX, startPointY); path.lineTo(startPointX, startPointY);
path.lineTo(startPointX, startPointY - 1.0); path.lineTo(startPointX, startPointY - 1.0);
path.lineTo(startPointX + arrowWidth / 2, startPointY + arrowWidth / 2 - 1.0); path.lineTo(
startPointX + arrowWidth / 2, startPointY + arrowWidth / 2 - 1.0);
path.lineTo(startPointX + arrowWidth, startPointY - 1.0); path.lineTo(startPointX + arrowWidth, startPointY - 1.0);
path.close(); path.close();
@ -600,7 +614,8 @@ class SlideFadeTransition extends StatelessWidget {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return AnimatedBuilder( return AnimatedBuilder(
animation: animation, animation: animation,
builder: (context, child) => animation.value == 0.0 ? Container() : child!, builder: (context, child) =>
animation.value == 0.0 ? Container() : child!,
child: SlideTransition( child: SlideTransition(
position: Tween( position: Tween(
begin: const Offset(0.3, 0.0), begin: const Offset(0.3, 0.0),

View file

@ -20,16 +20,18 @@ class ImmichSliverAppBar extends ConsumerWidget {
@override @override
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
final BackUpState _backupState = ref.watch(backupProvider); final BackUpState backupState = ref.watch(backupProvider);
bool _isEnableAutoBackup = ref.watch(authenticationProvider).deviceInfo.isAutoBackup; bool isEnableAutoBackup =
final ServerInfoState _serverInfoState = ref.watch(serverInfoProvider); ref.watch(authenticationProvider).deviceInfo.isAutoBackup;
final ServerInfoState serverInfoState = ref.watch(serverInfoProvider);
return SliverAppBar( return SliverAppBar(
centerTitle: true, centerTitle: true,
floating: true, floating: true,
pinned: false, pinned: false,
snap: false, snap: false,
shape: const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(5))), shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(5))),
leading: Builder( leading: Builder(
builder: (BuildContext context) { builder: (BuildContext context) {
return Stack( return Stack(
@ -47,7 +49,7 @@ class ImmichSliverAppBar extends ConsumerWidget {
}, },
), ),
), ),
_serverInfoState.isVersionMismatch serverInfoState.isVersionMismatch
? Positioned( ? Positioned(
bottom: 12, bottom: 12,
right: 12, right: 12,
@ -88,7 +90,7 @@ class ImmichSliverAppBar extends ConsumerWidget {
Stack( Stack(
alignment: AlignmentDirectional.center, alignment: AlignmentDirectional.center,
children: [ children: [
_backupState.backupProgress == BackUpProgressEnum.inProgress backupState.backupProgress == BackUpProgressEnum.inProgress
? Positioned( ? Positioned(
top: 10, top: 10,
right: 12, right: 12,
@ -97,7 +99,8 @@ class ImmichSliverAppBar extends ConsumerWidget {
width: 8, width: 8,
child: CircularProgressIndicator( child: CircularProgressIndicator(
strokeWidth: 1, strokeWidth: 1,
valueColor: AlwaysStoppedAnimation<Color>(Theme.of(context).primaryColor), valueColor: AlwaysStoppedAnimation<Color>(
Theme.of(context).primaryColor),
), ),
), ),
) )
@ -105,7 +108,7 @@ class ImmichSliverAppBar extends ConsumerWidget {
IconButton( IconButton(
splashRadius: 25, splashRadius: 25,
iconSize: 30, iconSize: 30,
icon: _isEnableAutoBackup icon: isEnableAutoBackup
? const Icon(Icons.backup_rounded) ? const Icon(Icons.backup_rounded)
: Badge( : Badge(
padding: const EdgeInsets.all(4), padding: const EdgeInsets.all(4),
@ -118,20 +121,23 @@ class ImmichSliverAppBar extends ConsumerWidget {
), ),
child: const Icon(Icons.backup_rounded)), child: const Icon(Icons.backup_rounded)),
onPressed: () async { onPressed: () async {
var onPop = await AutoRouter.of(context).push(const BackupControllerRoute()); var onPop = await AutoRouter.of(context)
.push(const BackupControllerRoute());
if (onPop != null && onPop == true) { if (onPop != null && onPop == true) {
onPopBack!(); onPopBack!();
} }
}, },
), ),
_backupState.backupProgress == BackUpProgressEnum.inProgress backupState.backupProgress == BackUpProgressEnum.inProgress
? Positioned( ? Positioned(
bottom: 5, bottom: 5,
child: Text( child: Text(
(_backupState.allUniqueAssets.length - _backupState.selectedAlbumsBackupAssetsIds.length) (backupState.allUniqueAssets.length -
backupState.selectedAlbumsBackupAssetsIds.length)
.toString(), .toString(),
style: const TextStyle(fontSize: 9, fontWeight: FontWeight.bold), style: const TextStyle(
fontSize: 9, fontWeight: FontWeight.bold),
), ),
) )
: Container() : Container()

View file

@ -24,9 +24,10 @@ class ProfileDrawer extends HookConsumerWidget {
@override @override
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
String endpoint = Hive.box(userInfoBox).get(serverEndpointKey); String endpoint = Hive.box(userInfoBox).get(serverEndpointKey);
AuthenticationState _authState = ref.watch(authenticationProvider); AuthenticationState authState = ref.watch(authenticationProvider);
ServerInfoState _serverInfoState = ref.watch(serverInfoProvider); ServerInfoState serverInfoState = ref.watch(serverInfoProvider);
final uploadProfileImageStatus = ref.watch(uploadProfileImageProvider).status; final uploadProfileImageStatus =
ref.watch(uploadProfileImageProvider).status;
final appInfo = useState({}); final appInfo = useState({});
var dummmy = Random().nextInt(1024); var dummmy = Random().nextInt(1024);
@ -40,7 +41,7 @@ class ProfileDrawer extends HookConsumerWidget {
} }
_buildUserProfileImage() { _buildUserProfileImage() {
if (_authState.profileImagePath.isEmpty) { if (authState.profileImagePath.isEmpty) {
return const CircleAvatar( return const CircleAvatar(
radius: 35, radius: 35,
backgroundImage: AssetImage('assets/immich-logo-no-outline.png'), backgroundImage: AssetImage('assets/immich-logo-no-outline.png'),
@ -49,10 +50,11 @@ class ProfileDrawer extends HookConsumerWidget {
} }
if (uploadProfileImageStatus == UploadProfileStatus.idle) { if (uploadProfileImageStatus == UploadProfileStatus.idle) {
if (_authState.profileImagePath.isNotEmpty) { if (authState.profileImagePath.isNotEmpty) {
return CircleAvatar( return CircleAvatar(
radius: 35, radius: 35,
backgroundImage: NetworkImage('$endpoint/user/profile-image/${_authState.userId}?d=${dummmy++}'), backgroundImage: NetworkImage(
'$endpoint/user/profile-image/${authState.userId}?d=${dummmy++}'),
backgroundColor: Colors.transparent, backgroundColor: Colors.transparent,
); );
} else { } else {
@ -67,7 +69,8 @@ class ProfileDrawer extends HookConsumerWidget {
if (uploadProfileImageStatus == UploadProfileStatus.success) { if (uploadProfileImageStatus == UploadProfileStatus.success) {
return CircleAvatar( return CircleAvatar(
radius: 35, radius: 35,
backgroundImage: NetworkImage('$endpoint/user/profile-image/${_authState.userId}?d=${dummmy++}'), backgroundImage: NetworkImage(
'$endpoint/user/profile-image/${authState.userId}?d=${dummmy++}'),
backgroundColor: Colors.transparent, backgroundColor: Colors.transparent,
); );
} }
@ -88,15 +91,16 @@ class ProfileDrawer extends HookConsumerWidget {
} }
_pickUserProfileImage() async { _pickUserProfileImage() async {
final XFile? image = await ImagePicker().pickImage(source: ImageSource.gallery, maxHeight: 1024, maxWidth: 1024); final XFile? image = await ImagePicker().pickImage(
source: ImageSource.gallery, maxHeight: 1024, maxWidth: 1024);
if (image != null) { if (image != null) {
var success = await ref.watch(uploadProfileImageProvider.notifier).upload(image); var success =
await ref.watch(uploadProfileImageProvider.notifier).upload(image);
if (success) { if (success) {
ref ref.watch(authenticationProvider.notifier).updateUserProfileImagePath(
.watch(authenticationProvider.notifier) ref.read(uploadProfileImageProvider).profileImagePath);
.updateUserProfileImagePath(ref.read(uploadProfileImageProvider).profileImagePath);
} }
} }
} }
@ -117,7 +121,10 @@ class ProfileDrawer extends HookConsumerWidget {
DrawerHeader( DrawerHeader(
decoration: const BoxDecoration( decoration: const BoxDecoration(
gradient: LinearGradient( gradient: LinearGradient(
colors: [Color.fromARGB(255, 216, 219, 238), Color.fromARGB(255, 226, 230, 231)], colors: [
Color.fromARGB(255, 216, 219, 238),
Color.fromARGB(255, 226, 230, 231)
],
begin: Alignment.centerRight, begin: Alignment.centerRight,
end: Alignment.centerLeft, end: Alignment.centerLeft,
), ),
@ -155,7 +162,7 @@ class ProfileDrawer extends HookConsumerWidget {
], ],
), ),
Text( Text(
"${_authState.firstName} ${_authState.lastName}", "${authState.firstName} ${authState.lastName}",
style: TextStyle( style: TextStyle(
color: Theme.of(context).primaryColor, color: Theme.of(context).primaryColor,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
@ -163,7 +170,7 @@ class ProfileDrawer extends HookConsumerWidget {
), ),
), ),
Text( Text(
_authState.userEmail, authState.userEmail,
style: TextStyle(color: Colors.grey[800], fontSize: 12), style: TextStyle(color: Colors.grey[800], fontSize: 12),
) )
], ],
@ -177,10 +184,14 @@ class ProfileDrawer extends HookConsumerWidget {
), ),
title: const Text( title: const Text(
"Sign Out", "Sign Out",
style: TextStyle(color: Colors.black54, fontSize: 14, fontWeight: FontWeight.bold), style: TextStyle(
color: Colors.black54,
fontSize: 14,
fontWeight: FontWeight.bold),
), ),
onTap: () async { onTap: () async {
bool res = await ref.read(authenticationProvider.notifier).logout(); bool res =
await ref.read(authenticationProvider.notifier).logout();
if (res) { if (res) {
ref.watch(backupProvider.notifier).cancelBackup(); ref.watch(backupProvider.notifier).cancelBackup();
@ -206,19 +217,22 @@ class ProfileDrawer extends HookConsumerWidget {
), ),
), ),
child: Padding( child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 12.0, vertical: 8), padding:
const EdgeInsets.symmetric(horizontal: 12.0, vertical: 8),
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center,
children: [ children: [
Padding( Padding(
padding: const EdgeInsets.all(8.0), padding: const EdgeInsets.all(8.0),
child: Text( child: Text(
_serverInfoState.isVersionMismatch serverInfoState.isVersionMismatch
? _serverInfoState.versionMismatchErrorMessage ? serverInfoState.versionMismatchErrorMessage
: "Client and Server are up-to-date", : "Client and Server are up-to-date",
textAlign: TextAlign.center, textAlign: TextAlign.center,
style: style: TextStyle(
TextStyle(fontSize: 11, color: Theme.of(context).primaryColor, fontWeight: FontWeight.w600), fontSize: 11,
color: Theme.of(context).primaryColor,
fontWeight: FontWeight.w600),
), ),
), ),
const Divider(), const Divider(),
@ -256,7 +270,7 @@ class ProfileDrawer extends HookConsumerWidget {
), ),
), ),
Text( Text(
"${_serverInfoState.serverVersion.major}.${_serverInfoState.serverVersion.minor}.${_serverInfoState.serverVersion.patch}", "${serverInfoState.serverVersion.major}.${serverInfoState.serverVersion.minor}.${serverInfoState.serverVersion.patch}",
style: TextStyle( style: TextStyle(
fontSize: 11, fontSize: 11,
color: Colors.grey[500], color: Colors.grey[500],

View file

@ -19,10 +19,11 @@ class HomePage extends HookConsumerWidget {
@override @override
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
ScrollController _scrollController = useScrollController(); ScrollController scrollController = useScrollController();
var assetGroupByDateTime = ref.watch(assetGroupByDateTimeProvider); var assetGroupByDateTime = ref.watch(assetGroupByDateTimeProvider);
List<Widget> _imageGridGroup = []; List<Widget> imageGridGroup = [];
var isMultiSelectEnable = ref.watch(homePageStateProvider).isMultiSelectEnable; var isMultiSelectEnable =
ref.watch(homePageStateProvider).isMultiSelectEnable;
var homePageState = ref.watch(homePageStateProvider); var homePageState = ref.watch(homePageStateProvider);
useEffect(() { useEffect(() {
@ -39,7 +40,8 @@ class HomePage extends HookConsumerWidget {
_buildSelectedItemCountIndicator() { _buildSelectedItemCountIndicator() {
return isMultiSelectEnable return isMultiSelectEnable
? DisableMultiSelectButton( ? DisableMultiSelectButton(
onPressed: ref.watch(homePageStateProvider.notifier).disableMultiSelect, onPressed:
ref.watch(homePageStateProvider.notifier).disableMultiSelect,
selectedItemCount: homePageState.selectedItems.length, selectedItemCount: homePageState.selectedItems.length,
) )
: Container(); : Container();
@ -59,7 +61,7 @@ class HomePage extends HookConsumerWidget {
if (lastMonth != null) { if (lastMonth != null) {
if (currentMonth - lastMonth! != 0) { if (currentMonth - lastMonth! != 0) {
_imageGridGroup.add( imageGridGroup.add(
MonthlyTitleText( MonthlyTitleText(
isoDate: dateGroup, isoDate: dateGroup,
), ),
@ -67,14 +69,14 @@ class HomePage extends HookConsumerWidget {
} }
} }
_imageGridGroup.add( imageGridGroup.add(
DailyTitleText( DailyTitleText(
isoDate: dateGroup, isoDate: dateGroup,
assetGroup: immichAssetList, assetGroup: immichAssetList,
), ),
); );
_imageGridGroup.add( imageGridGroup.add(
ImageGrid(assetGroup: immichAssetList), ImageGrid(assetGroup: immichAssetList),
); );
@ -109,12 +111,12 @@ class HomePage extends HookConsumerWidget {
padding: const EdgeInsets.only(top: 50.0), padding: const EdgeInsets.only(top: 50.0),
child: DraggableScrollbar.semicircle( child: DraggableScrollbar.semicircle(
backgroundColor: Theme.of(context).primaryColor, backgroundColor: Theme.of(context).primaryColor,
controller: _scrollController, controller: scrollController,
heightScrollThumb: 48.0, heightScrollThumb: 48.0,
child: CustomScrollView( child: CustomScrollView(
controller: _scrollController, controller: scrollController,
slivers: [ slivers: [
..._imageGridGroup, ...imageGridGroup,
], ],
), ),
), ),

View file

@ -45,20 +45,23 @@ class SearchPageStateNotifier extends StateNotifier<SearchPageState> {
} }
void getSuggestedSearchTerms() async { void getSuggestedSearchTerms() async {
var userSuggestedSearchTerms = await _searchService.getUserSuggestedSearchTerms(); var userSuggestedSearchTerms =
await _searchService.getUserSuggestedSearchTerms();
state = state.copyWith(userSuggestedSearchTerms: userSuggestedSearchTerms); state = state.copyWith(userSuggestedSearchTerms: userSuggestedSearchTerms);
} }
} }
final searchPageStateProvider = StateNotifierProvider<SearchPageStateNotifier, SearchPageState>((ref) { final searchPageStateProvider =
StateNotifierProvider<SearchPageStateNotifier, SearchPageState>((ref) {
return SearchPageStateNotifier(); return SearchPageStateNotifier();
}); });
final getCuratedLocationProvider = FutureProvider.autoDispose<List<CuratedLocation>>((ref) async { final getCuratedLocationProvider =
final SearchService _searchService = SearchService(); FutureProvider.autoDispose<List<CuratedLocation>>((ref) async {
final SearchService searchService = SearchService();
var curatedLocation = await _searchService.getCuratedLocation(); var curatedLocation = await searchService.getCuratedLocation();
if (curatedLocation != null) { if (curatedLocation != null) {
return curatedLocation; return curatedLocation;
} else { } else {
@ -66,10 +69,11 @@ final getCuratedLocationProvider = FutureProvider.autoDispose<List<CuratedLocati
} }
}); });
final getCuratedObjectProvider = FutureProvider.autoDispose<List<CuratedObject>>((ref) async { final getCuratedObjectProvider =
final SearchService _searchService = SearchService(); FutureProvider.autoDispose<List<CuratedObject>>((ref) async {
final SearchService searchService = SearchService();
var curatedObject = await _searchService.getCuratedObjects(); var curatedObject = await searchService.getCuratedObjects();
if (curatedObject != null) { if (curatedObject != null) {
return curatedObject; return curatedObject;
} else { } else {

View file

@ -1,7 +1,6 @@
import 'package:auto_route/auto_route.dart'; import 'package:auto_route/auto_route.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:flutter_spinkit/flutter_spinkit.dart';
import 'package:hive_flutter/hive_flutter.dart'; import 'package:hive_flutter/hive_flutter.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/constants/hive_box.dart'; import 'package:immich_mobile/constants/hive_box.dart';

View file

@ -12,24 +12,27 @@ import 'package:immich_mobile/modules/search/providers/search_result_page.provid
import 'package:immich_mobile/modules/search/ui/search_suggestion_list.dart'; import 'package:immich_mobile/modules/search/ui/search_suggestion_list.dart';
class SearchResultPage extends HookConsumerWidget { class SearchResultPage extends HookConsumerWidget {
SearchResultPage({Key? key, required this.searchTerm}) : super(key: key); const SearchResultPage({Key? key, required this.searchTerm})
: super(key: key);
final String searchTerm; final String searchTerm;
late FocusNode searchFocusNode;
@override @override
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
ScrollController _scrollController = useScrollController(); ScrollController scrollController = useScrollController();
final searchTermController = useTextEditingController(text: ""); final searchTermController = useTextEditingController(text: "");
final isNewSearch = useState(false); final isNewSearch = useState(false);
final currentSearchTerm = useState(searchTerm); final currentSearchTerm = useState(searchTerm);
List<Widget> _imageGridGroup = []; final List<Widget> imageGridGroup = [];
late FocusNode searchFocusNode;
useEffect(() { useEffect(() {
searchFocusNode = FocusNode(); searchFocusNode = FocusNode();
Future.delayed(Duration.zero, () => ref.read(searchResultPageProvider.notifier).search(searchTerm)); Future.delayed(Duration.zero,
() => ref.read(searchResultPageProvider.notifier).search(searchTerm));
return () => searchFocusNode.dispose(); return () => searchFocusNode.dispose();
}, []); }, []);
@ -85,7 +88,10 @@ class SearchResultPage extends HookConsumerWidget {
children: [ children: [
Text( Text(
currentSearchTerm.value, currentSearchTerm.value,
style: TextStyle(color: Theme.of(context).primaryColor, fontSize: 13, fontWeight: FontWeight.bold), style: TextStyle(
color: Theme.of(context).primaryColor,
fontSize: 13,
fontWeight: FontWeight.bold),
maxLines: 1, maxLines: 1,
), ),
Icon( Icon(
@ -124,7 +130,7 @@ class SearchResultPage extends HookConsumerWidget {
if (lastMonth != null) { if (lastMonth != null) {
if (currentMonth - lastMonth! != 0) { if (currentMonth - lastMonth! != 0) {
_imageGridGroup.add( imageGridGroup.add(
MonthlyTitleText( MonthlyTitleText(
isoDate: dateGroup, isoDate: dateGroup,
), ),
@ -132,14 +138,14 @@ class SearchResultPage extends HookConsumerWidget {
} }
} }
_imageGridGroup.add( imageGridGroup.add(
DailyTitleText( DailyTitleText(
isoDate: dateGroup, isoDate: dateGroup,
assetGroup: immichAssetList, assetGroup: immichAssetList,
), ),
); );
_imageGridGroup.add( imageGridGroup.add(
ImageGrid(assetGroup: immichAssetList), ImageGrid(assetGroup: immichAssetList),
); );
@ -148,11 +154,11 @@ class SearchResultPage extends HookConsumerWidget {
return DraggableScrollbar.semicircle( return DraggableScrollbar.semicircle(
backgroundColor: Theme.of(context).primaryColor, backgroundColor: Theme.of(context).primaryColor,
controller: _scrollController, controller: scrollController,
heightScrollThumb: 48.0, heightScrollThumb: 48.0,
child: CustomScrollView( child: CustomScrollView(
controller: _scrollController, controller: scrollController,
slivers: [..._imageGridGroup], slivers: [...imageGridGroup],
), ),
); );
} else { } else {
@ -192,7 +198,9 @@ class SearchResultPage extends HookConsumerWidget {
child: Stack( child: Stack(
children: [ children: [
_buildSearchResult(), _buildSearchResult(),
isNewSearch.value ? SearchSuggestionList(onSubmitted: _onSearchSubmitted) : Container(), isNewSearch.value
? SearchSuggestionList(onSubmitted: _onSearchSubmitted)
: Container(),
], ],
), ),
), ),

View file

@ -8,7 +8,8 @@ class SharedAlbumNotifier extends StateNotifier<List<SharedAlbum>> {
final SharedAlbumService _sharedAlbumService = SharedAlbumService(); final SharedAlbumService _sharedAlbumService = SharedAlbumService();
getAllSharedAlbums() async { getAllSharedAlbums() async {
List<SharedAlbum> sharedAlbums = await _sharedAlbumService.getAllSharedAlbum(); List<SharedAlbum> sharedAlbums =
await _sharedAlbumService.getAllSharedAlbum();
state = sharedAlbums; state = sharedAlbums;
} }
@ -35,7 +36,8 @@ class SharedAlbumNotifier extends StateNotifier<List<SharedAlbum>> {
} }
} }
Future<bool> removeAssetFromAlbum(String albumId, List<String> assetIds) async { Future<bool> removeAssetFromAlbum(
String albumId, List<String> assetIds) async {
var res = await _sharedAlbumService.removeAssetFromAlbum(albumId, assetIds); var res = await _sharedAlbumService.removeAssetFromAlbum(albumId, assetIds);
if (res) { if (res) {
@ -46,12 +48,14 @@ class SharedAlbumNotifier extends StateNotifier<List<SharedAlbum>> {
} }
} }
final sharedAlbumProvider = StateNotifierProvider<SharedAlbumNotifier, List<SharedAlbum>>((ref) { final sharedAlbumProvider =
StateNotifierProvider<SharedAlbumNotifier, List<SharedAlbum>>((ref) {
return SharedAlbumNotifier(); return SharedAlbumNotifier();
}); });
final sharedAlbumDetailProvider = FutureProvider.autoDispose.family<SharedAlbum, String>((ref, albumId) async { final sharedAlbumDetailProvider = FutureProvider.autoDispose
final SharedAlbumService _sharedAlbumService = SharedAlbumService(); .family<SharedAlbum, String>((ref, albumId) async {
final SharedAlbumService sharedAlbumService = SharedAlbumService();
return await _sharedAlbumService.getAlbumDetail(albumId); return await sharedAlbumService.getAlbumDetail(albumId);
}); });

View file

@ -26,18 +26,22 @@ class AlbumViewerAppbar extends HookConsumerWidget with PreferredSizeWidget {
@override @override
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
final isMultiSelectionEnable = ref.watch(assetSelectionProvider).isMultiselectEnable; final isMultiSelectionEnable =
final selectedAssetsInAlbum = ref.watch(assetSelectionProvider).selectedAssetsInAlbumViewer; ref.watch(assetSelectionProvider).isMultiselectEnable;
final selectedAssetsInAlbum =
ref.watch(assetSelectionProvider).selectedAssetsInAlbumViewer;
final newAlbumTitle = ref.watch(albumViewerProvider).editTitleText; final newAlbumTitle = ref.watch(albumViewerProvider).editTitleText;
final isEditAlbum = ref.watch(albumViewerProvider).isEditAlbum; final isEditAlbum = ref.watch(albumViewerProvider).isEditAlbum;
void _onDeleteAlbumPressed(String albumId) async { void _onDeleteAlbumPressed(String albumId) async {
ImmichLoadingOverlayController.appLoader.show(); ImmichLoadingOverlayController.appLoader.show();
bool isSuccess = await ref.watch(sharedAlbumProvider.notifier).deleteAlbum(albumId); bool isSuccess =
await ref.watch(sharedAlbumProvider.notifier).deleteAlbum(albumId);
if (isSuccess) { if (isSuccess) {
AutoRouter.of(context).navigate(const TabControllerRoute(children: [SharingRoute()])); AutoRouter.of(context)
.navigate(const TabControllerRoute(children: [SharingRoute()]));
} else { } else {
ImmichToast.show( ImmichToast.show(
context: context, context: context,
@ -53,10 +57,12 @@ class AlbumViewerAppbar extends HookConsumerWidget with PreferredSizeWidget {
void _onLeaveAlbumPressed(String albumId) async { void _onLeaveAlbumPressed(String albumId) async {
ImmichLoadingOverlayController.appLoader.show(); ImmichLoadingOverlayController.appLoader.show();
bool isSuccess = await ref.watch(sharedAlbumProvider.notifier).leaveAlbum(albumId); bool isSuccess =
await ref.watch(sharedAlbumProvider.notifier).leaveAlbum(albumId);
if (isSuccess) { if (isSuccess) {
AutoRouter.of(context).navigate(const TabControllerRoute(children: [SharingRoute()])); AutoRouter.of(context)
.navigate(const TabControllerRoute(children: [SharingRoute()]));
} else { } else {
Navigator.pop(context); Navigator.pop(context);
ImmichToast.show( ImmichToast.show(
@ -73,10 +79,11 @@ class AlbumViewerAppbar extends HookConsumerWidget with PreferredSizeWidget {
void _onRemoveFromAlbumPressed(String albumId) async { void _onRemoveFromAlbumPressed(String albumId) async {
ImmichLoadingOverlayController.appLoader.show(); ImmichLoadingOverlayController.appLoader.show();
bool isSuccess = await ref.watch(sharedAlbumProvider.notifier).removeAssetFromAlbum( bool isSuccess =
albumId, await ref.watch(sharedAlbumProvider.notifier).removeAssetFromAlbum(
selectedAssetsInAlbum.map((a) => a.id).toList(), albumId,
); selectedAssetsInAlbum.map((a) => a.id).toList(),
);
if (isSuccess) { if (isSuccess) {
Navigator.pop(context); Navigator.pop(context);
@ -153,15 +160,18 @@ class AlbumViewerAppbar extends HookConsumerWidget with PreferredSizeWidget {
_buildLeadingButton() { _buildLeadingButton() {
if (isMultiSelectionEnable) { if (isMultiSelectionEnable) {
return IconButton( return IconButton(
onPressed: () => ref.watch(assetSelectionProvider.notifier).disableMultiselection(), onPressed: () => ref
.watch(assetSelectionProvider.notifier)
.disableMultiselection(),
icon: const Icon(Icons.close_rounded), icon: const Icon(Icons.close_rounded),
splashRadius: 25, splashRadius: 25,
); );
} else if (isEditAlbum) { } else if (isEditAlbum) {
return IconButton( return IconButton(
onPressed: () async { onPressed: () async {
bool isSuccess = bool isSuccess = await ref
await ref.watch(albumViewerProvider.notifier).changeAlbumTitle(albumId, userId, newAlbumTitle); .watch(albumViewerProvider.notifier)
.changeAlbumTitle(albumId, userId, newAlbumTitle);
if (!isSuccess) { if (!isSuccess) {
ImmichToast.show( ImmichToast.show(
@ -187,7 +197,9 @@ class AlbumViewerAppbar extends HookConsumerWidget with PreferredSizeWidget {
return AppBar( return AppBar(
elevation: 0, elevation: 0,
leading: _buildLeadingButton(), leading: _buildLeadingButton(),
title: isMultiSelectionEnable ? Text(selectedAssetsInAlbum.length.toString()) : Container(), title: isMultiSelectionEnable
? Text(selectedAssetsInAlbum.length.toString())
: Container(),
centerTitle: false, centerTitle: false,
actions: [ actions: [
IconButton( IconButton(

View file

@ -28,8 +28,9 @@ class AlbumViewerPage extends HookConsumerWidget {
@override @override
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
FocusNode titleFocusNode = useFocusNode(); FocusNode titleFocusNode = useFocusNode();
ScrollController _scrollController = useScrollController(); ScrollController scrollController = useScrollController();
AsyncValue<SharedAlbum> _albumInfo = ref.watch(sharedAlbumDetailProvider(albumId)); AsyncValue<SharedAlbum> albumInfo =
ref.watch(sharedAlbumDetailProvider(albumId));
final userId = ref.watch(authenticationProvider).userId; final userId = ref.watch(authenticationProvider).userId;
@ -44,16 +45,16 @@ class AlbumViewerPage extends HookConsumerWidget {
ref.watch(assetSelectionProvider.notifier).setIsAlbumExist(true); ref.watch(assetSelectionProvider.notifier).setIsAlbumExist(true);
AssetSelectionPageResult? returnPayload = AssetSelectionPageResult? returnPayload = await AutoRouter.of(context)
await AutoRouter.of(context).push<AssetSelectionPageResult?>(const AssetSelectionRoute()); .push<AssetSelectionPageResult?>(const AssetSelectionRoute());
if (returnPayload != null) { if (returnPayload != null) {
// Check if there is new assets add // Check if there is new assets add
if (returnPayload.selectedAdditionalAsset.isNotEmpty) { if (returnPayload.selectedAdditionalAsset.isNotEmpty) {
ImmichLoadingOverlayController.appLoader.show(); ImmichLoadingOverlayController.appLoader.show();
var isSuccess = var isSuccess = await SharedAlbumService().addAdditionalAssetToAlbum(
await SharedAlbumService().addAdditionalAssetToAlbum(returnPayload.selectedAdditionalAsset, albumId); returnPayload.selectedAdditionalAsset, albumId);
if (isSuccess) { if (isSuccess) {
ref.refresh(sharedAlbumDetailProvider(albumId)); ref.refresh(sharedAlbumDetailProvider(albumId));
@ -69,13 +70,15 @@ class AlbumViewerPage extends HookConsumerWidget {
} }
void _onAddUsersPressed(SharedAlbum albumInfo) async { void _onAddUsersPressed(SharedAlbum albumInfo) async {
List<String>? sharedUserIds = List<String>? sharedUserIds = await AutoRouter.of(context)
await AutoRouter.of(context).push<List<String>?>(SelectAdditionalUserForSharingRoute(albumInfo: albumInfo)); .push<List<String>?>(
SelectAdditionalUserForSharingRoute(albumInfo: albumInfo));
if (sharedUserIds != null) { if (sharedUserIds != null) {
ImmichLoadingOverlayController.appLoader.show(); ImmichLoadingOverlayController.appLoader.show();
var isSuccess = await SharedAlbumService().addAdditionalUserToAlbum(sharedUserIds, albumId); var isSuccess = await SharedAlbumService()
.addAdditionalUserToAlbum(sharedUserIds, albumId);
if (isSuccess) { if (isSuccess) {
ref.refresh(sharedAlbumDetailProvider(albumId)); ref.refresh(sharedAlbumDetailProvider(albumId));
@ -95,7 +98,9 @@ class AlbumViewerPage extends HookConsumerWidget {
) )
: Padding( : Padding(
padding: const EdgeInsets.only(left: 8.0), padding: const EdgeInsets.only(left: 8.0),
child: Text(albumInfo.albumName, style: const TextStyle(fontSize: 24, fontWeight: FontWeight.bold)), child: Text(albumInfo.albumName,
style: const TextStyle(
fontSize: 24, fontWeight: FontWeight.bold)),
), ),
); );
} }
@ -103,8 +108,10 @@ class AlbumViewerPage extends HookConsumerWidget {
Widget _buildAlbumDateRange(SharedAlbum albumInfo) { Widget _buildAlbumDateRange(SharedAlbum albumInfo) {
if (albumInfo.assets != null && albumInfo.assets!.isNotEmpty) { if (albumInfo.assets != null && albumInfo.assets!.isNotEmpty) {
String startDate = ""; String startDate = "";
DateTime parsedStartDate = DateTime.parse(albumInfo.assets!.first.createdAt); DateTime parsedStartDate =
DateTime parsedEndDate = DateTime.parse(albumInfo.assets!.last.createdAt); DateTime.parse(albumInfo.assets!.first.createdAt);
DateTime parsedEndDate =
DateTime.parse(albumInfo.assets!.last.createdAt);
if (parsedStartDate.year == parsedEndDate.year) { if (parsedStartDate.year == parsedEndDate.year) {
startDate = DateFormat('LLL d').format(parsedStartDate); startDate = DateFormat('LLL d').format(parsedStartDate);
@ -118,7 +125,8 @@ class AlbumViewerPage extends HookConsumerWidget {
padding: const EdgeInsets.only(left: 16.0, top: 8), padding: const EdgeInsets.only(left: 16.0, top: 8),
child: Text( child: Text(
"$startDate-$endDate", "$startDate-$endDate",
style: const TextStyle(fontSize: 14, fontWeight: FontWeight.bold, color: Colors.grey), style: const TextStyle(
fontSize: 14, fontWeight: FontWeight.bold, color: Colors.grey),
), ),
); );
} else { } else {
@ -147,8 +155,9 @@ class AlbumViewerPage extends HookConsumerWidget {
child: Padding( child: Padding(
padding: const EdgeInsets.all(2.0), padding: const EdgeInsets.all(2.0),
child: ClipRRect( child: ClipRRect(
child: Image.asset('assets/immich-logo-no-outline.png'),
borderRadius: BorderRadius.circular(50.0), borderRadius: BorderRadius.circular(50.0),
child:
Image.asset('assets/immich-logo-no-outline.png'),
), ),
), ),
), ),
@ -217,10 +226,10 @@ class AlbumViewerPage extends HookConsumerWidget {
}, },
child: DraggableScrollbar.semicircle( child: DraggableScrollbar.semicircle(
backgroundColor: Theme.of(context).primaryColor, backgroundColor: Theme.of(context).primaryColor,
controller: _scrollController, controller: scrollController,
heightScrollThumb: 48.0, heightScrollThumb: 48.0,
child: CustomScrollView( child: CustomScrollView(
controller: _scrollController, controller: scrollController,
slivers: [ slivers: [
_buildHeader(albumInfo), _buildHeader(albumInfo),
SliverPersistentHeader( SliverPersistentHeader(
@ -242,8 +251,9 @@ class AlbumViewerPage extends HookConsumerWidget {
} }
return Scaffold( return Scaffold(
appBar: AlbumViewerAppbar(albumInfo: _albumInfo, userId: userId, albumId: albumId), appBar: AlbumViewerAppbar(
body: _albumInfo.when( albumInfo: albumInfo, userId: userId, albumId: albumId),
body: albumInfo.when(
data: (albumInfo) => _buildBody(albumInfo), data: (albumInfo) => _buildBody(albumInfo),
error: (e, _) => Center(child: Text("Error loading album info $e")), error: (e, _) => Center(child: Text("Error loading album info $e")),
loading: () => const Center( loading: () => const Center(

View file

@ -13,13 +13,15 @@ class AssetSelectionPage extends HookConsumerWidget {
const AssetSelectionPage({Key? key}) : super(key: key); const AssetSelectionPage({Key? key}) : super(key: key);
@override @override
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
ScrollController _scrollController = useScrollController(); ScrollController scrollController = useScrollController();
var assetGroupMonthYear = ref.watch(assetGroupByMonthYearProvider); var assetGroupMonthYear = ref.watch(assetGroupByMonthYearProvider);
final selectedAssets = ref.watch(assetSelectionProvider).selectedNewAssetsForAlbum; final selectedAssets =
final newAssetsForAlbum = ref.watch(assetSelectionProvider).selectedAdditionalAssetsForAlbum; ref.watch(assetSelectionProvider).selectedNewAssetsForAlbum;
final newAssetsForAlbum =
ref.watch(assetSelectionProvider).selectedAdditionalAssetsForAlbum;
final isAlbumExist = ref.watch(assetSelectionProvider).isAlbumExist; final isAlbumExist = ref.watch(assetSelectionProvider).isAlbumExist;
List<Widget> _imageGridGroup = []; List<Widget> imageGridGroup = [];
String _buildAssetCountText() { String _buildAssetCountText() {
if (isAlbumExist) { if (isAlbumExist) {
@ -31,19 +33,20 @@ class AssetSelectionPage extends HookConsumerWidget {
Widget _buildBody() { Widget _buildBody() {
assetGroupMonthYear.forEach((monthYear, assetGroup) { assetGroupMonthYear.forEach((monthYear, assetGroup) {
_imageGridGroup.add(MonthGroupTitle(month: monthYear, assetGroup: assetGroup)); imageGridGroup
_imageGridGroup.add(AssetGridByMonth(assetGroup: assetGroup)); .add(MonthGroupTitle(month: monthYear, assetGroup: assetGroup));
imageGridGroup.add(AssetGridByMonth(assetGroup: assetGroup));
}); });
return Stack( return Stack(
children: [ children: [
DraggableScrollbar.semicircle( DraggableScrollbar.semicircle(
backgroundColor: Theme.of(context).primaryColor, backgroundColor: Theme.of(context).primaryColor,
controller: _scrollController, controller: scrollController,
heightScrollThumb: 48.0, heightScrollThumb: 48.0,
child: CustomScrollView( child: CustomScrollView(
controller: _scrollController, controller: scrollController,
slivers: [..._imageGridGroup], slivers: [...imageGridGroup],
), ),
), ),
], ],
@ -71,7 +74,8 @@ class AssetSelectionPage extends HookConsumerWidget {
), ),
centerTitle: false, centerTitle: false,
actions: [ actions: [
(!isAlbumExist && selectedAssets.isNotEmpty) || (isAlbumExist && newAssetsForAlbum.isNotEmpty) (!isAlbumExist && selectedAssets.isNotEmpty) ||
(isAlbumExist && newAssetsForAlbum.isNotEmpty)
? TextButton( ? TextButton(
onPressed: () { onPressed: () {
var payload = AssetSelectionPageResult( var payload = AssetSelectionPageResult(

View file

@ -15,11 +15,13 @@ class CreateSharedAlbumPage extends HookConsumerWidget {
@override @override
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
final albumTitleController = useTextEditingController.fromValue(TextEditingValue.empty); final albumTitleController =
useTextEditingController.fromValue(TextEditingValue.empty);
final albumTitleTextFieldFocusNode = useFocusNode(); final albumTitleTextFieldFocusNode = useFocusNode();
final isAlbumTitleTextFieldFocus = useState(false); final isAlbumTitleTextFieldFocus = useState(false);
final isAlbumTitleEmpty = useState(true); final isAlbumTitleEmpty = useState(true);
final selectedAssets = ref.watch(assetSelectionProvider).selectedNewAssetsForAlbum; final selectedAssets =
ref.watch(assetSelectionProvider).selectedNewAssetsForAlbum;
_showSelectUserPage() { _showSelectUserPage() {
AutoRouter.of(context).push(const SelectUserForSharingRoute()); AutoRouter.of(context).push(const SelectUserForSharingRoute());
@ -38,8 +40,8 @@ class CreateSharedAlbumPage extends HookConsumerWidget {
_onSelectPhotosButtonPressed() async { _onSelectPhotosButtonPressed() async {
ref.watch(assetSelectionProvider.notifier).setIsAlbumExist(false); ref.watch(assetSelectionProvider.notifier).setIsAlbumExist(false);
AssetSelectionPageResult? selectedAsset = AssetSelectionPageResult? selectedAsset = await AutoRouter.of(context)
await AutoRouter.of(context).push<AssetSelectionPageResult?>(const AssetSelectionRoute()); .push<AssetSelectionPageResult?>(const AssetSelectionRoute());
if (selectedAsset == null) { if (selectedAsset == null) {
ref.watch(assetSelectionProvider.notifier).removeAll(); ref.watch(assetSelectionProvider.notifier).removeAll();
@ -84,16 +86,22 @@ class CreateSharedAlbumPage extends HookConsumerWidget {
child: OutlinedButton.icon( child: OutlinedButton.icon(
style: OutlinedButton.styleFrom( style: OutlinedButton.styleFrom(
alignment: Alignment.centerLeft, alignment: Alignment.centerLeft,
padding: const EdgeInsets.symmetric(vertical: 22, horizontal: 16), padding:
side: const BorderSide(color: Color.fromARGB(255, 206, 206, 206)), const EdgeInsets.symmetric(vertical: 22, horizontal: 16),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(5))), side: const BorderSide(
color: Color.fromARGB(255, 206, 206, 206)),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(5))),
onPressed: _onSelectPhotosButtonPressed, onPressed: _onSelectPhotosButtonPressed,
icon: const Icon(Icons.add_rounded), icon: const Icon(Icons.add_rounded),
label: Padding( label: Padding(
padding: const EdgeInsets.only(left: 8.0), padding: const EdgeInsets.only(left: 8.0),
child: Text( child: Text(
'Select Photos', 'Select Photos',
style: TextStyle(fontSize: 16, color: Colors.grey[700], fontWeight: FontWeight.bold), style: TextStyle(
fontSize: 16,
color: Colors.grey[700],
fontWeight: FontWeight.bold),
), ),
), ),
), ),
@ -141,7 +149,8 @@ class CreateSharedAlbumPage extends HookConsumerWidget {
(BuildContext context, int index) { (BuildContext context, int index) {
return GestureDetector( return GestureDetector(
onTap: _onBackgroundTapped, onTap: _onBackgroundTapped,
child: SharedAlbumThumbnailImage(asset: selectedAssets.toList()[index]), child: SharedAlbumThumbnailImage(
asset: selectedAssets.toList()[index]),
); );
}, },
childCount: selectedAssets.length, childCount: selectedAssets.length,
@ -169,7 +178,9 @@ class CreateSharedAlbumPage extends HookConsumerWidget {
), ),
actions: [ actions: [
TextButton( TextButton(
onPressed: albumTitleController.text.isNotEmpty ? _showSelectUserPage : null, onPressed: albumTitleController.text.isNotEmpty
? _showSelectUserPage
: null,
child: const Text( child: const Text(
'Share', 'Share',
style: TextStyle( style: TextStyle(
@ -189,13 +200,13 @@ class CreateSharedAlbumPage extends HookConsumerWidget {
pinned: true, pinned: true,
floating: false, floating: false,
bottom: PreferredSize( bottom: PreferredSize(
preferredSize: const Size.fromHeight(66.0),
child: Column( child: Column(
children: [ children: [
_buildTitleInputField(), _buildTitleInputField(),
_buildControlButton(), _buildControlButton(),
], ],
), ),
preferredSize: const Size.fromHeight(66.0),
), ),
), ),
_buildTitle(), _buildTitle(),

View file

@ -1,5 +1,4 @@
import 'dart:convert'; import 'dart:convert';
import 'dart:ffi';
class DeviceInfoRemote { class DeviceInfoRemote {
final int id; final int id;
@ -66,7 +65,8 @@ class DeviceInfoRemote {
String toJson() => json.encode(toMap()); String toJson() => json.encode(toMap());
factory DeviceInfoRemote.fromJson(String source) => DeviceInfoRemote.fromMap(json.decode(source)); factory DeviceInfoRemote.fromJson(String source) =>
DeviceInfoRemote.fromMap(json.decode(source));
@override @override
String toString() { String toString() {

View file

@ -46,37 +46,45 @@ class AssetNotifier extends StateNotifier<List<ImmichAsset>> {
} }
} }
final List<String> result = await PhotoManager.editor.deleteWithIds(deleteIdList); // final List<String> result = await PhotoManager.editor.deleteWithIds(deleteIdList);
await PhotoManager.editor.deleteWithIds(deleteIdList);
// Delete asset on server // Delete asset on server
List<DeleteAssetResponse>? deleteAssetResult = await _assetService.deleteAssets(deleteAssets); List<DeleteAssetResponse>? deleteAssetResult =
await _assetService.deleteAssets(deleteAssets);
if (deleteAssetResult == null) { if (deleteAssetResult == null) {
return; return;
} }
for (var asset in deleteAssetResult) { for (var asset in deleteAssetResult) {
if (asset.status == 'success') { if (asset.status == 'success') {
state = state.where((immichAsset) => immichAsset.id != asset.id).toList(); state =
state.where((immichAsset) => immichAsset.id != asset.id).toList();
} }
} }
} }
} }
final assetProvider = StateNotifierProvider<AssetNotifier, List<ImmichAsset>>((ref) { final assetProvider =
StateNotifierProvider<AssetNotifier, List<ImmichAsset>>((ref) {
return AssetNotifier(ref); return AssetNotifier(ref);
}); });
final assetGroupByDateTimeProvider = StateProvider((ref) { final assetGroupByDateTimeProvider = StateProvider((ref) {
var assets = ref.watch(assetProvider); var assets = ref.watch(assetProvider);
assets.sortByCompare<DateTime>((e) => DateTime.parse(e.createdAt), (a, b) => b.compareTo(a)); assets.sortByCompare<DateTime>(
return assets.groupListsBy((element) => DateFormat('y-MM-dd').format(DateTime.parse(element.createdAt))); (e) => DateTime.parse(e.createdAt), (a, b) => b.compareTo(a));
return assets.groupListsBy((element) =>
DateFormat('y-MM-dd').format(DateTime.parse(element.createdAt)));
}); });
final assetGroupByMonthYearProvider = StateProvider((ref) { final assetGroupByMonthYearProvider = StateProvider((ref) {
var assets = ref.watch(assetProvider); var assets = ref.watch(assetProvider);
assets.sortByCompare<DateTime>((e) => DateTime.parse(e.createdAt), (a, b) => b.compareTo(a)); assets.sortByCompare<DateTime>(
(e) => DateTime.parse(e.createdAt), (a, b) => b.compareTo(a));
return assets.groupListsBy((element) => DateFormat('MMMM, y').format(DateTime.parse(element.createdAt))); return assets.groupListsBy((element) =>
DateFormat('MMMM, y').format(DateTime.parse(element.createdAt)));
}); });

View file

@ -34,7 +34,8 @@ class ReleaseInfoNotifier extends StateNotifier<String> {
return; return;
} }
if (latestTagVersion.isNotEmpty && localReleaseVersion != latestTagVersion) { if (latestTagVersion.isNotEmpty &&
localReleaseVersion != latestTagVersion) {
VersionAnnouncementOverlayController.appLoader.show(); VersionAnnouncementOverlayController.appLoader.show();
return; return;
} }
@ -54,4 +55,5 @@ class ReleaseInfoNotifier extends StateNotifier<String> {
} }
} }
final releaseInfoProvider = StateNotifierProvider<ReleaseInfoNotifier, String>((ref) => ReleaseInfoNotifier()); final releaseInfoProvider = StateNotifierProvider<ReleaseInfoNotifier, String>(
(ref) => ReleaseInfoNotifier());

View file

@ -3,12 +3,11 @@ import 'dart:convert';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:hive/hive.dart'; import 'package:hive/hive.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/shared/providers/asset.provider.dart';
import 'package:immich_mobile/shared/models/immich_asset.model.dart';
import 'package:socket_io_client/socket_io_client.dart';
import 'package:immich_mobile/constants/hive_box.dart'; import 'package:immich_mobile/constants/hive_box.dart';
import 'package:immich_mobile/modules/login/providers/authentication.provider.dart'; import 'package:immich_mobile/modules/login/providers/authentication.provider.dart';
import 'package:immich_mobile/shared/models/immich_asset.model.dart';
import 'package:immich_mobile/shared/providers/asset.provider.dart';
import 'package:socket_io_client/socket_io_client.dart';
class WebscoketState { class WebscoketState {
final Socket? socket; final Socket? socket;
@ -30,13 +29,16 @@ class WebscoketState {
} }
@override @override
String toString() => 'WebscoketState(socket: $socket, isConnected: $isConnected)'; String toString() =>
'WebscoketState(socket: $socket, isConnected: $isConnected)';
@override @override
bool operator ==(Object other) { bool operator ==(Object other) {
if (identical(this, other)) return true; if (identical(this, other)) return true;
return other is WebscoketState && other.socket == socket && other.isConnected == isConnected; return other is WebscoketState &&
other.socket == socket &&
other.isConnected == isConnected;
} }
@override @override
@ -44,7 +46,8 @@ class WebscoketState {
} }
class WebsocketNotifier extends StateNotifier<WebscoketState> { class WebsocketNotifier extends StateNotifier<WebscoketState> {
WebsocketNotifier(this.ref) : super(WebscoketState(socket: null, isConnected: false)) { WebsocketNotifier(this.ref)
: super(WebscoketState(socket: null, isConnected: false)) {
debugPrint("Init websocket instance"); debugPrint("Init websocket instance");
} }
@ -59,6 +62,7 @@ class WebsocketNotifier extends StateNotifier<WebscoketState> {
try { try {
debugPrint("[WEBSOCKET] Attempting to connect to ws"); debugPrint("[WEBSOCKET] Attempting to connect to ws");
// Configure socket transports must be sepecified // Configure socket transports must be sepecified
Socket socket = io( Socket socket = io(
endpoint, endpoint,
OptionBuilder() OptionBuilder()
@ -122,6 +126,7 @@ class WebsocketNotifier extends StateNotifier<WebscoketState> {
} }
} }
final websocketProvider = StateNotifierProvider<WebsocketNotifier, WebscoketState>((ref) { final websocketProvider =
StateNotifierProvider<WebsocketNotifier, WebscoketState>((ref) {
return WebsocketNotifier(ref); return WebsocketNotifier(ref);
}); });

View file

@ -1,9 +1,8 @@
import 'package:dio/dio.dart'; import 'package:dio/dio.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:immich_mobile/shared/models/mapbox_info.model.dart'; import 'package:immich_mobile/shared/models/server_info.model.dart';
import 'package:immich_mobile/shared/models/server_version.model.dart'; import 'package:immich_mobile/shared/models/server_version.model.dart';
import 'package:immich_mobile/shared/services/network.service.dart'; import 'package:immich_mobile/shared/services/network.service.dart';
import 'package:immich_mobile/shared/models/server_info.model.dart';
class ServerInfoService { class ServerInfoService {
final NetworkService _networkService = NetworkService(); final NetworkService _networkService = NetworkService();

View file

@ -12,8 +12,9 @@ class VersionAnnouncementOverlay extends HookConsumerWidget {
@override @override
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
void goToReleaseNote() async { void goToReleaseNote() async {
final Uri _url = Uri.parse('https://github.com/alextran1502/immich/releases/latest'); final Uri url =
await launchUrl(_url); Uri.parse('https://github.com/alextran1502/immich/releases/latest');
await launchUrl(url);
} }
void onAcknowledgeTapped() { void onAcknowledgeTapped() {
@ -21,7 +22,8 @@ class VersionAnnouncementOverlay extends HookConsumerWidget {
} }
return ValueListenableBuilder<bool>( return ValueListenableBuilder<bool>(
valueListenable: VersionAnnouncementOverlayController.appLoader.loaderShowingNotifier, valueListenable:
VersionAnnouncementOverlayController.appLoader.loaderShowingNotifier,
builder: (context, shouldShow, child) { builder: (context, shouldShow, child) {
if (shouldShow) { if (shouldShow) {
return Scaffold( return Scaffold(
@ -51,10 +53,14 @@ class VersionAnnouncementOverlay extends HookConsumerWidget {
child: RichText( child: RichText(
text: TextSpan( text: TextSpan(
style: const TextStyle( style: const TextStyle(
fontSize: 14, fontFamily: 'WorkSans', color: Colors.black87, height: 1.2), fontSize: 14,
fontFamily: 'WorkSans',
color: Colors.black87,
height: 1.2),
children: <TextSpan>[ children: <TextSpan>[
const TextSpan( const TextSpan(
text: 'Hi friend, there is a new release of', text:
'Hi friend, there is a new release of',
), ),
const TextSpan( const TextSpan(
text: ' Immich ', text: ' Immich ',
@ -65,14 +71,16 @@ class VersionAnnouncementOverlay extends HookConsumerWidget {
), ),
), ),
const TextSpan( const TextSpan(
text: "please take your time to visit the ", text:
"please take your time to visit the ",
), ),
TextSpan( TextSpan(
text: "release note", text: "release note",
style: const TextStyle( style: const TextStyle(
decoration: TextDecoration.underline, decoration: TextDecoration.underline,
), ),
recognizer: TapGestureRecognizer()..onTap = goToReleaseNote, recognizer: TapGestureRecognizer()
..onTap = goToReleaseNote,
), ),
const TextSpan( const TextSpan(
text: text:
@ -91,7 +99,8 @@ class VersionAnnouncementOverlay extends HookConsumerWidget {
primary: Colors.indigo, primary: Colors.indigo,
onPrimary: Colors.grey[50], onPrimary: Colors.grey[50],
elevation: 2, elevation: 2,
padding: const EdgeInsets.symmetric(vertical: 10, horizontal: 25), padding: const EdgeInsets.symmetric(
vertical: 10, horizontal: 25),
), ),
onPressed: onAcknowledgeTapped, onPressed: onAcknowledgeTapped,
child: const Text( child: const Text(
@ -119,7 +128,8 @@ class VersionAnnouncementOverlay extends HookConsumerWidget {
} }
class VersionAnnouncementOverlayController { class VersionAnnouncementOverlayController {
static final VersionAnnouncementOverlayController appLoader = VersionAnnouncementOverlayController(); static final VersionAnnouncementOverlayController appLoader =
VersionAnnouncementOverlayController();
ValueNotifier<bool> loaderShowingNotifier = ValueNotifier(false); ValueNotifier<bool> loaderShowingNotifier = ValueNotifier(false);
ValueNotifier<String> loaderTextNotifier = ValueNotifier('error message'); ValueNotifier<String> loaderTextNotifier = ValueNotifier('error message');

View file

@ -1,5 +1,4 @@
import 'package:dio/dio.dart'; import 'package:dio/dio.dart';
import 'package:flutter/material.dart';
import 'package:hive_flutter/hive_flutter.dart'; import 'package:hive_flutter/hive_flutter.dart';
import 'package:immich_mobile/constants/hive_box.dart'; import 'package:immich_mobile/constants/hive_box.dart';

View file

@ -198,7 +198,7 @@ packages:
source: hosted source: hosted
version: "4.1.0" version: "4.1.0"
collection: collection:
dependency: transitive dependency: "direct main"
description: description:
name: collection name: collection
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
@ -233,7 +233,7 @@ packages:
source: hosted source: hosted
version: "0.17.1" version: "0.17.1"
cupertino_icons: cupertino_icons:
dependency: "direct main" dependency: transitive
description: description:
name: cupertino_icons name: cupertino_icons
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
@ -334,7 +334,7 @@ packages:
name: flutter_lints name: flutter_lints
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.0.4" version: "2.0.1"
flutter_map: flutter_map:
dependency: "direct main" dependency: "direct main"
description: description:
@ -465,12 +465,12 @@ packages:
source: hosted source: hosted
version: "3.2.0" version: "3.2.0"
http_parser: http_parser:
dependency: transitive dependency: "direct main"
description: description:
name: http_parser name: http_parser
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "4.0.0" version: "4.0.1"
image: image:
dependency: transitive dependency: transitive
description: description:
@ -542,7 +542,7 @@ packages:
source: hosted source: hosted
version: "4.5.0" version: "4.5.0"
latlong2: latlong2:
dependency: transitive dependency: "direct main"
description: description:
name: latlong2 name: latlong2
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
@ -554,7 +554,7 @@ packages:
name: lints name: lints
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.0.1" version: "2.0.0"
lists: lists:
dependency: transitive dependency: transitive
description: description:
@ -668,19 +668,19 @@ packages:
source: hosted source: hosted
version: "1.0.5" version: "1.0.5"
path: path:
dependency: transitive dependency: "direct main"
description: description:
name: path name: path
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.8.1" version: "1.8.1"
path_provider: path_provider:
dependency: transitive dependency: "direct main"
description: description:
name: path_provider name: path_provider
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.0.10" version: "2.0.11"
path_provider_android: path_provider_android:
dependency: transitive dependency: transitive
description: description:
@ -861,13 +861,6 @@ packages:
description: flutter description: flutter
source: sdk source: sdk
version: "0.0.99" version: "0.0.99"
sliver_tools:
dependency: "direct main"
description:
name: sliver_tools
url: "https://pub.dartlang.org"
source: hosted
version: "0.2.6"
socket_io_client: socket_io_client:
dependency: "direct main" dependency: "direct main"
description: description:
@ -1141,13 +1134,6 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.0.10" version: "2.0.10"
visibility_detector:
dependency: "direct main"
description:
name: visibility_detector
url: "https://pub.dartlang.org"
source: hosted
version: "0.2.2"
wakelock: wakelock:
dependency: transitive dependency: transitive
description: description:

View file

@ -10,7 +10,7 @@ environment:
dependencies: dependencies:
flutter: flutter:
sdk: flutter sdk: flutter
cupertino_icons: ^1.0.2
photo_manager: ^2.0.6 photo_manager: ^2.0.6
flutter_hooks: ^0.18.0 flutter_hooks: ^0.18.0
hooks_riverpod: ^2.0.0-dev.0 hooks_riverpod: ^2.0.0-dev.0
@ -23,12 +23,10 @@ dependencies:
auto_route: ^4.0.1 auto_route: ^4.0.1
exif: ^3.1.1 exif: ^3.1.1
transparent_image: ^2.0.0 transparent_image: ^2.0.0
visibility_detector: ^0.2.2
flutter_launcher_icons: "^0.9.2" flutter_launcher_icons: "^0.9.2"
fluttertoast: ^8.0.8 fluttertoast: ^8.0.8
video_player: ^2.2.18 video_player: ^2.2.18
chewie: ^1.2.2 chewie: ^1.2.2
sliver_tools: ^0.2.5
badges: ^2.0.2 badges: ^2.0.2
photo_view: ^0.14.0 photo_view: ^0.14.0
socket_io_client: ^2.0.0-beta.4-nullsafety.0 socket_io_client: ^2.0.0-beta.4-nullsafety.0
@ -43,10 +41,17 @@ dependencies:
http: 0.13.4 http: 0.13.4
cancellation_token_http: ^1.1.0 cancellation_token_http: ^1.1.0
path: ^1.8.1
path_provider: ^2.0.11
latlong2: ^0.8.1
collection: ^1.16.0
http_parser: ^4.0.1
dev_dependencies: dev_dependencies:
flutter_test: flutter_test:
sdk: flutter sdk: flutter
flutter_lints: ^1.0.0 flutter_lints: ^2.0.1
hive_generator: ^1.1.2 hive_generator: ^1.1.2
build_runner: ^2.1.7 build_runner: ^2.1.7
auto_route_generator: ^4.0.0 auto_route_generator: ^4.0.0