mirror of
https://github.com/immich-app/immich.git
synced 2024-11-24 08:52:28 +02: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:
parent
63bebd92e0
commit
caaa474c23
@ -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'
|
||||||
|
@ -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
|
||||||
|
@ -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'
|
|
||||||
}
|
}
|
||||||
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
@ -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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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
|
@ -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(),
|
||||||
|
@ -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} "),
|
||||||
|
@ -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 }
|
||||||
|
@ -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,
|
||||||
|
@ -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);
|
||||||
});
|
});
|
||||||
|
@ -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"),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
|
@ -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),
|
||||||
)),
|
)),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -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),
|
||||||
|
@ -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()
|
||||||
|
@ -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],
|
||||||
|
@ -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,
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -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 {
|
||||||
|
@ -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';
|
||||||
|
@ -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(),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -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);
|
||||||
});
|
});
|
||||||
|
@ -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(
|
||||||
|
@ -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(
|
||||||
|
@ -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(
|
||||||
|
@ -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(),
|
||||||
|
@ -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() {
|
||||||
|
@ -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)));
|
||||||
});
|
});
|
||||||
|
@ -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());
|
||||||
|
@ -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);
|
||||||
});
|
});
|
||||||
|
@ -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();
|
||||||
|
@ -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');
|
||||||
|
|
||||||
|
@ -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';
|
||||||
|
|
||||||
|
@ -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:
|
||||||
|
@ -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
|
||||||
|
Loading…
Reference in New Issue
Block a user