mirror of
https://github.com/immich-app/immich.git
synced 2024-12-23 02:06:15 +02:00
Fix backup not resuming after closed and reopen (#266)
* Fixed app not resuming backup after closing and reopening the app * Fixed cosmetic effect of backup button doesn't change state right away after pressing start backup * Fixed grammar * Fixed deep copy problem that cause incorrect asset count when backing up * Format code
This commit is contained in:
parent
d02b97e1c1
commit
40a8115101
@ -28,22 +28,26 @@ class ImageViewerPageState {
|
|||||||
|
|
||||||
factory ImageViewerPageState.fromMap(Map<String, dynamic> map) {
|
factory ImageViewerPageState.fromMap(Map<String, dynamic> map) {
|
||||||
return ImageViewerPageState(
|
return ImageViewerPageState(
|
||||||
downloadAssetStatus: DownloadAssetStatus.values[map['downloadAssetStatus'] ?? 0],
|
downloadAssetStatus:
|
||||||
|
DownloadAssetStatus.values[map['downloadAssetStatus'] ?? 0],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
String toJson() => json.encode(toMap());
|
String toJson() => json.encode(toMap());
|
||||||
|
|
||||||
factory ImageViewerPageState.fromJson(String source) => ImageViewerPageState.fromMap(json.decode(source));
|
factory ImageViewerPageState.fromJson(String source) =>
|
||||||
|
ImageViewerPageState.fromMap(json.decode(source));
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() => 'ImageViewerPageState(downloadAssetStatus: $downloadAssetStatus)';
|
String toString() =>
|
||||||
|
'ImageViewerPageState(downloadAssetStatus: $downloadAssetStatus)';
|
||||||
|
|
||||||
@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 ImageViewerPageState && other.downloadAssetStatus == downloadAssetStatus;
|
return other is ImageViewerPageState &&
|
||||||
|
other.downloadAssetStatus == downloadAssetStatus;
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -9,7 +9,9 @@ import 'package:immich_mobile/shared/ui/immich_toast.dart';
|
|||||||
class ImageViewerStateNotifier extends StateNotifier<ImageViewerPageState> {
|
class ImageViewerStateNotifier extends StateNotifier<ImageViewerPageState> {
|
||||||
final ImageViewerService _imageViewerService = ImageViewerService();
|
final ImageViewerService _imageViewerService = ImageViewerService();
|
||||||
|
|
||||||
ImageViewerStateNotifier() : super(ImageViewerPageState(downloadAssetStatus: DownloadAssetStatus.idle));
|
ImageViewerStateNotifier()
|
||||||
|
: super(ImageViewerPageState(
|
||||||
|
downloadAssetStatus: DownloadAssetStatus.idle));
|
||||||
|
|
||||||
void downloadAsset(ImmichAsset asset, BuildContext context) async {
|
void downloadAsset(ImmichAsset asset, BuildContext context) async {
|
||||||
state = state.copyWith(downloadAssetStatus: DownloadAssetStatus.loading);
|
state = state.copyWith(downloadAssetStatus: DownloadAssetStatus.loading);
|
||||||
@ -40,4 +42,5 @@ class ImageViewerStateNotifier extends StateNotifier<ImageViewerPageState> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
final imageViewerStateProvider =
|
final imageViewerStateProvider =
|
||||||
StateNotifierProvider<ImageViewerStateNotifier, ImageViewerPageState>(((ref) => ImageViewerStateNotifier()));
|
StateNotifierProvider<ImageViewerStateNotifier, ImageViewerPageState>(
|
||||||
|
((ref) => ImageViewerStateNotifier()));
|
||||||
|
@ -15,12 +15,14 @@ class ImageViewerService {
|
|||||||
try {
|
try {
|
||||||
String fileName = p.basename(asset.originalPath);
|
String fileName = p.basename(asset.originalPath);
|
||||||
var savedEndpoint = Hive.box(userInfoBox).get(serverEndpointKey);
|
var savedEndpoint = Hive.box(userInfoBox).get(serverEndpointKey);
|
||||||
Uri filePath =
|
Uri filePath = Uri.parse(
|
||||||
Uri.parse("$savedEndpoint/asset/download?aid=${asset.deviceAssetId}&did=${asset.deviceId}&isThumb=false");
|
"$savedEndpoint/asset/download?aid=${asset.deviceAssetId}&did=${asset.deviceId}&isThumb=false");
|
||||||
|
|
||||||
var res = await http.get(
|
var res = await http.get(
|
||||||
filePath,
|
filePath,
|
||||||
headers: {"Authorization": "Bearer ${Hive.box(userInfoBox).get(accessTokenKey)}"},
|
headers: {
|
||||||
|
"Authorization": "Bearer ${Hive.box(userInfoBox).get(accessTokenKey)}"
|
||||||
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
final AssetEntity? entity;
|
final AssetEntity? entity;
|
||||||
|
@ -21,13 +21,16 @@ class AvailableAlbum {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() => 'AvailableAlbum(albumEntity: $albumEntity, thumbnailData: $thumbnailData)';
|
String toString() =>
|
||||||
|
'AvailableAlbum(albumEntity: $albumEntity, thumbnailData: $thumbnailData)';
|
||||||
|
|
||||||
@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 AvailableAlbum && other.albumEntity == albumEntity && other.thumbnailData == thumbnailData;
|
return other is AvailableAlbum &&
|
||||||
|
other.albumEntity == albumEntity &&
|
||||||
|
other.thumbnailData == thumbnailData;
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -10,7 +10,7 @@ enum BackUpProgressEnum { idle, inProgress, done }
|
|||||||
class BackUpState extends Equatable {
|
class BackUpState extends Equatable {
|
||||||
// enum
|
// enum
|
||||||
final BackUpProgressEnum backupProgress;
|
final BackUpProgressEnum backupProgress;
|
||||||
final List<String> allAssetOnDatabase;
|
final List<String> allAssetsInDatabase;
|
||||||
final double progressInPercentage;
|
final double progressInPercentage;
|
||||||
final CancellationToken cancelToken;
|
final CancellationToken cancelToken;
|
||||||
final ServerInfo serverInfo;
|
final ServerInfo serverInfo;
|
||||||
@ -28,7 +28,7 @@ class BackUpState extends Equatable {
|
|||||||
|
|
||||||
const BackUpState({
|
const BackUpState({
|
||||||
required this.backupProgress,
|
required this.backupProgress,
|
||||||
required this.allAssetOnDatabase,
|
required this.allAssetsInDatabase,
|
||||||
required this.progressInPercentage,
|
required this.progressInPercentage,
|
||||||
required this.cancelToken,
|
required this.cancelToken,
|
||||||
required this.serverInfo,
|
required this.serverInfo,
|
||||||
@ -41,7 +41,7 @@ class BackUpState extends Equatable {
|
|||||||
|
|
||||||
BackUpState copyWith({
|
BackUpState copyWith({
|
||||||
BackUpProgressEnum? backupProgress,
|
BackUpProgressEnum? backupProgress,
|
||||||
List<String>? allAssetOnDatabase,
|
List<String>? allAssetsInDatabase,
|
||||||
double? progressInPercentage,
|
double? progressInPercentage,
|
||||||
CancellationToken? cancelToken,
|
CancellationToken? cancelToken,
|
||||||
ServerInfo? serverInfo,
|
ServerInfo? serverInfo,
|
||||||
@ -53,7 +53,7 @@ class BackUpState extends Equatable {
|
|||||||
}) {
|
}) {
|
||||||
return BackUpState(
|
return BackUpState(
|
||||||
backupProgress: backupProgress ?? this.backupProgress,
|
backupProgress: backupProgress ?? this.backupProgress,
|
||||||
allAssetOnDatabase: allAssetOnDatabase ?? this.allAssetOnDatabase,
|
allAssetsInDatabase: allAssetsInDatabase ?? this.allAssetsInDatabase,
|
||||||
progressInPercentage: progressInPercentage ?? this.progressInPercentage,
|
progressInPercentage: progressInPercentage ?? this.progressInPercentage,
|
||||||
cancelToken: cancelToken ?? this.cancelToken,
|
cancelToken: cancelToken ?? this.cancelToken,
|
||||||
serverInfo: serverInfo ?? this.serverInfo,
|
serverInfo: serverInfo ?? this.serverInfo,
|
||||||
@ -61,20 +61,21 @@ class BackUpState extends Equatable {
|
|||||||
selectedBackupAlbums: selectedBackupAlbums ?? this.selectedBackupAlbums,
|
selectedBackupAlbums: selectedBackupAlbums ?? this.selectedBackupAlbums,
|
||||||
excludedBackupAlbums: excludedBackupAlbums ?? this.excludedBackupAlbums,
|
excludedBackupAlbums: excludedBackupAlbums ?? this.excludedBackupAlbums,
|
||||||
allUniqueAssets: allUniqueAssets ?? this.allUniqueAssets,
|
allUniqueAssets: allUniqueAssets ?? this.allUniqueAssets,
|
||||||
selectedAlbumsBackupAssetsIds: selectedAlbumsBackupAssetsIds ?? this.selectedAlbumsBackupAssetsIds,
|
selectedAlbumsBackupAssetsIds:
|
||||||
|
selectedAlbumsBackupAssetsIds ?? this.selectedAlbumsBackupAssetsIds,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
return 'BackUpState(backupProgress: $backupProgress, allAssetOnDatabase: $allAssetOnDatabase, progressInPercentage: $progressInPercentage, cancelToken: $cancelToken, serverInfo: $serverInfo, availableAlbums: $availableAlbums, selectedBackupAlbums: $selectedBackupAlbums, excludedBackupAlbums: $excludedBackupAlbums, allUniqueAssets: $allUniqueAssets, selectedAlbumsBackupAssetsIds: $selectedAlbumsBackupAssetsIds)';
|
return 'BackUpState(backupProgress: $backupProgress, allAssetsInDatabase: $allAssetsInDatabase, progressInPercentage: $progressInPercentage, cancelToken: $cancelToken, serverInfo: $serverInfo, availableAlbums: $availableAlbums, selectedBackupAlbums: $selectedBackupAlbums, excludedBackupAlbums: $excludedBackupAlbums, allUniqueAssets: $allUniqueAssets, selectedAlbumsBackupAssetsIds: $selectedAlbumsBackupAssetsIds)';
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<Object> get props {
|
List<Object> get props {
|
||||||
return [
|
return [
|
||||||
backupProgress,
|
backupProgress,
|
||||||
allAssetOnDatabase,
|
allAssetsInDatabase,
|
||||||
progressInPercentage,
|
progressInPercentage,
|
||||||
cancelToken,
|
cancelToken,
|
||||||
serverInfo,
|
serverInfo,
|
||||||
|
@ -19,7 +19,8 @@ class HiveBackupAlbums {
|
|||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() => 'HiveBackupAlbums(selectedAlbumIds: $selectedAlbumIds, excludedAlbumsIds: $excludedAlbumsIds)';
|
String toString() =>
|
||||||
|
'HiveBackupAlbums(selectedAlbumIds: $selectedAlbumIds, excludedAlbumsIds: $excludedAlbumsIds)';
|
||||||
|
|
||||||
HiveBackupAlbums copyWith({
|
HiveBackupAlbums copyWith({
|
||||||
List<String>? selectedAlbumIds,
|
List<String>? selectedAlbumIds,
|
||||||
@ -49,7 +50,8 @@ class HiveBackupAlbums {
|
|||||||
|
|
||||||
String toJson() => json.encode(toMap());
|
String toJson() => json.encode(toMap());
|
||||||
|
|
||||||
factory HiveBackupAlbums.fromJson(String source) => HiveBackupAlbums.fromMap(json.decode(source));
|
factory HiveBackupAlbums.fromJson(String source) =>
|
||||||
|
HiveBackupAlbums.fromMap(json.decode(source));
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool operator ==(Object other) {
|
bool operator ==(Object other) {
|
||||||
|
@ -18,7 +18,7 @@ class BackupNotifier extends StateNotifier<BackUpState> {
|
|||||||
: super(
|
: super(
|
||||||
BackUpState(
|
BackUpState(
|
||||||
backupProgress: BackUpProgressEnum.idle,
|
backupProgress: BackUpProgressEnum.idle,
|
||||||
allAssetOnDatabase: const [],
|
allAssetsInDatabase: const [],
|
||||||
progressInPercentage: 0,
|
progressInPercentage: 0,
|
||||||
cancelToken: CancellationToken(),
|
cancelToken: CancellationToken(),
|
||||||
serverInfo: ServerInfo(
|
serverInfo: ServerInfo(
|
||||||
@ -36,7 +36,9 @@ class BackupNotifier extends StateNotifier<BackUpState> {
|
|||||||
allUniqueAssets: const {},
|
allUniqueAssets: const {},
|
||||||
selectedAlbumsBackupAssetsIds: const {},
|
selectedAlbumsBackupAssetsIds: const {},
|
||||||
),
|
),
|
||||||
);
|
) {
|
||||||
|
getBackupInfo();
|
||||||
|
}
|
||||||
|
|
||||||
final BackupService _backupService;
|
final BackupService _backupService;
|
||||||
final ServerInfoService _serverInfoService;
|
final ServerInfoService _serverInfoService;
|
||||||
@ -93,7 +95,7 @@ class BackupNotifier extends StateNotifier<BackUpState> {
|
|||||||
/// If this is the first time performing backup - set the default selected album to be
|
/// If this is the first time performing backup - set the default selected album to be
|
||||||
/// the one that has all assets (Recent on Android, Recents on iOS)
|
/// the one that has all assets (Recent on Android, Recents on iOS)
|
||||||
///
|
///
|
||||||
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(
|
List<AssetPathEntity> albums = await PhotoManager.getAssetPathList(
|
||||||
@ -177,7 +179,7 @@ class BackupNotifier extends StateNotifier<BackUpState> {
|
|||||||
/// Find the assets that are not overlapping between the two sets
|
/// Find the assets that are not overlapping between the two sets
|
||||||
/// Those assets are unique and are used as the total assets
|
/// Those assets are unique and are used as the total assets
|
||||||
///
|
///
|
||||||
void _updateBackupAssetCount() async {
|
Future<void> _updateBackupAssetCount() async {
|
||||||
Set<AssetEntity> assetsFromSelectedAlbums = {};
|
Set<AssetEntity> assetsFromSelectedAlbums = {};
|
||||||
Set<AssetEntity> assetsFromExcludedAlbums = {};
|
Set<AssetEntity> assetsFromExcludedAlbums = {};
|
||||||
|
|
||||||
@ -195,27 +197,27 @@ class BackupNotifier extends StateNotifier<BackUpState> {
|
|||||||
|
|
||||||
Set<AssetEntity> allUniqueAssets =
|
Set<AssetEntity> allUniqueAssets =
|
||||||
assetsFromSelectedAlbums.difference(assetsFromExcludedAlbums);
|
assetsFromSelectedAlbums.difference(assetsFromExcludedAlbums);
|
||||||
List<String> allAssetOnDatabase =
|
List<String> allAssetsInDatabase =
|
||||||
await _backupService.getDeviceBackupAsset();
|
await _backupService.getDeviceBackupAsset();
|
||||||
|
|
||||||
// Find asset that were backup from selected albums
|
// Find asset that were backup from selected albums
|
||||||
Set<String> selectedAlbumsBackupAssets =
|
Set<String> selectedAlbumsBackupAssets =
|
||||||
Set.from(allUniqueAssets.map((e) => e.id));
|
Set.from(allUniqueAssets.map((e) => e.id));
|
||||||
selectedAlbumsBackupAssets
|
selectedAlbumsBackupAssets
|
||||||
.removeWhere((assetId) => !allAssetOnDatabase.contains(assetId));
|
.removeWhere((assetId) => !allAssetsInDatabase.contains(assetId));
|
||||||
|
|
||||||
if (allUniqueAssets.isEmpty) {
|
if (allUniqueAssets.isEmpty) {
|
||||||
debugPrint("No Asset On Device");
|
debugPrint("No Asset On Device");
|
||||||
state = state.copyWith(
|
state = state.copyWith(
|
||||||
backupProgress: BackUpProgressEnum.idle,
|
backupProgress: BackUpProgressEnum.idle,
|
||||||
allAssetOnDatabase: allAssetOnDatabase,
|
allAssetsInDatabase: allAssetsInDatabase,
|
||||||
allUniqueAssets: {},
|
allUniqueAssets: {},
|
||||||
selectedAlbumsBackupAssetsIds: selectedAlbumsBackupAssets,
|
selectedAlbumsBackupAssetsIds: selectedAlbumsBackupAssets,
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
} else {
|
} else {
|
||||||
state = state.copyWith(
|
state = state.copyWith(
|
||||||
allAssetOnDatabase: allAssetOnDatabase,
|
allAssetsInDatabase: allAssetsInDatabase,
|
||||||
allUniqueAssets: allUniqueAssets,
|
allUniqueAssets: allUniqueAssets,
|
||||||
selectedAlbumsBackupAssetsIds: selectedAlbumsBackupAssets,
|
selectedAlbumsBackupAssetsIds: selectedAlbumsBackupAssets,
|
||||||
);
|
);
|
||||||
@ -223,6 +225,8 @@ class BackupNotifier extends StateNotifier<BackUpState> {
|
|||||||
|
|
||||||
// Save to persistent storage
|
// Save to persistent storage
|
||||||
_updatePersistentAlbumsSelection();
|
_updatePersistentAlbumsSelection();
|
||||||
|
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
///
|
///
|
||||||
@ -230,10 +234,10 @@ class BackupNotifier extends StateNotifier<BackUpState> {
|
|||||||
/// which albums are selected or excluded
|
/// which albums are selected or excluded
|
||||||
/// and then update the UI according to those information
|
/// and then update the UI according to those information
|
||||||
///
|
///
|
||||||
void getBackupInfo() async {
|
Future<void> getBackupInfo() async {
|
||||||
await getBackupAlbumsInfo();
|
await _getBackupAlbumsInfo();
|
||||||
_updateServerInfo();
|
await _updateServerInfo();
|
||||||
_updateBackupAssetCount();
|
await _updateBackupAssetCount();
|
||||||
}
|
}
|
||||||
|
|
||||||
///
|
///
|
||||||
@ -256,11 +260,10 @@ class BackupNotifier extends StateNotifier<BackUpState> {
|
|||||||
/// Invoke backup process
|
/// Invoke backup process
|
||||||
///
|
///
|
||||||
void startBackupProcess() async {
|
void startBackupProcess() async {
|
||||||
_updateServerInfo();
|
|
||||||
_updateBackupAssetCount();
|
|
||||||
|
|
||||||
state = state.copyWith(backupProgress: BackUpProgressEnum.inProgress);
|
state = state.copyWith(backupProgress: BackUpProgressEnum.inProgress);
|
||||||
|
|
||||||
|
await getBackupInfo();
|
||||||
|
|
||||||
var authResult = await PhotoManager.requestPermissionExtend();
|
var authResult = await PhotoManager.requestPermissionExtend();
|
||||||
if (authResult.isAuth) {
|
if (authResult.isAuth) {
|
||||||
await PhotoManager.clearFileCache();
|
await PhotoManager.clearFileCache();
|
||||||
@ -271,10 +274,10 @@ class BackupNotifier extends StateNotifier<BackUpState> {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Set<AssetEntity> assetsWillBeBackup = state.allUniqueAssets;
|
Set<AssetEntity> assetsWillBeBackup = Set.from(state.allUniqueAssets);
|
||||||
|
|
||||||
// Remove item that has already been backed up
|
// Remove item that has already been backed up
|
||||||
for (var assetId in state.allAssetOnDatabase) {
|
for (var assetId in state.allAssetsInDatabase) {
|
||||||
assetsWillBeBackup.removeWhere((e) => e.id == assetId);
|
assetsWillBeBackup.removeWhere((e) => e.id == assetId);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -301,8 +304,8 @@ class BackupNotifier extends StateNotifier<BackUpState> {
|
|||||||
state = state.copyWith(selectedAlbumsBackupAssetsIds: {
|
state = state.copyWith(selectedAlbumsBackupAssetsIds: {
|
||||||
...state.selectedAlbumsBackupAssetsIds,
|
...state.selectedAlbumsBackupAssetsIds,
|
||||||
deviceAssetId
|
deviceAssetId
|
||||||
}, allAssetOnDatabase: [
|
}, allAssetsInDatabase: [
|
||||||
...state.allAssetOnDatabase,
|
...state.allAssetsInDatabase,
|
||||||
deviceAssetId
|
deviceAssetId
|
||||||
]);
|
]);
|
||||||
|
|
||||||
@ -321,7 +324,7 @@ class BackupNotifier extends StateNotifier<BackUpState> {
|
|||||||
progressInPercentage: (sent.toDouble() / total.toDouble() * 100));
|
progressInPercentage: (sent.toDouble() / total.toDouble() * 100));
|
||||||
}
|
}
|
||||||
|
|
||||||
void _updateServerInfo() async {
|
Future<void> _updateServerInfo() async {
|
||||||
var serverInfo = await _serverInfoService.getServerInfo();
|
var serverInfo = await _serverInfoService.getServerInfo();
|
||||||
|
|
||||||
// Update server info
|
// Update server info
|
||||||
|
@ -14,16 +14,22 @@ class AlbumInfoCard extends HookConsumerWidget {
|
|||||||
final Uint8List? imageData;
|
final Uint8List? imageData;
|
||||||
final AssetPathEntity albumInfo;
|
final AssetPathEntity albumInfo;
|
||||||
|
|
||||||
const AlbumInfoCard({Key? key, this.imageData, required this.albumInfo}) : super(key: key);
|
const AlbumInfoCard({Key? key, this.imageData, required this.albumInfo})
|
||||||
|
: super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
final bool isSelected = ref.watch(backupProvider).selectedBackupAlbums.contains(albumInfo);
|
final bool isSelected =
|
||||||
final bool isExcluded = ref.watch(backupProvider).excludedBackupAlbums.contains(albumInfo);
|
ref.watch(backupProvider).selectedBackupAlbums.contains(albumInfo);
|
||||||
|
final bool isExcluded =
|
||||||
|
ref.watch(backupProvider).excludedBackupAlbums.contains(albumInfo);
|
||||||
|
|
||||||
ColorFilter selectedFilter = ColorFilter.mode(Theme.of(context).primaryColor.withAlpha(100), BlendMode.darken);
|
ColorFilter selectedFilter = ColorFilter.mode(
|
||||||
ColorFilter excludedFilter = ColorFilter.mode(Colors.red.withAlpha(75), BlendMode.darken);
|
Theme.of(context).primaryColor.withAlpha(100), BlendMode.darken);
|
||||||
ColorFilter unselectedFilter = const ColorFilter.mode(Colors.black, BlendMode.color);
|
ColorFilter excludedFilter =
|
||||||
|
ColorFilter.mode(Colors.red.withAlpha(75), BlendMode.darken);
|
||||||
|
ColorFilter unselectedFilter =
|
||||||
|
const ColorFilter.mode(Colors.black, BlendMode.color);
|
||||||
|
|
||||||
_buildSelectedTextBox() {
|
_buildSelectedTextBox() {
|
||||||
if (isSelected) {
|
if (isSelected) {
|
||||||
@ -32,7 +38,8 @@ class AlbumInfoCard extends HookConsumerWidget {
|
|||||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(5)),
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(5)),
|
||||||
label: const Text(
|
label: const Text(
|
||||||
"INCLUDED",
|
"INCLUDED",
|
||||||
style: TextStyle(fontSize: 10, color: Colors.white, fontWeight: FontWeight.bold),
|
style: TextStyle(
|
||||||
|
fontSize: 10, color: Colors.white, fontWeight: FontWeight.bold),
|
||||||
),
|
),
|
||||||
backgroundColor: Theme.of(context).primaryColor,
|
backgroundColor: Theme.of(context).primaryColor,
|
||||||
);
|
);
|
||||||
@ -42,7 +49,8 @@ class AlbumInfoCard extends HookConsumerWidget {
|
|||||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(5)),
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(5)),
|
||||||
label: const Text(
|
label: const Text(
|
||||||
"EXCLUDED",
|
"EXCLUDED",
|
||||||
style: TextStyle(fontSize: 10, color: Colors.white, fontWeight: FontWeight.bold),
|
style: TextStyle(
|
||||||
|
fontSize: 10, color: Colors.white, fontWeight: FontWeight.bold),
|
||||||
),
|
),
|
||||||
backgroundColor: Colors.red[300],
|
backgroundColor: Colors.red[300],
|
||||||
);
|
);
|
||||||
@ -85,10 +93,15 @@ class AlbumInfoCard extends HookConsumerWidget {
|
|||||||
HapticFeedback.selectionClick();
|
HapticFeedback.selectionClick();
|
||||||
|
|
||||||
if (isExcluded) {
|
if (isExcluded) {
|
||||||
ref.watch(backupProvider.notifier).removeExcludedAlbumForBackup(albumInfo);
|
ref
|
||||||
|
.watch(backupProvider.notifier)
|
||||||
|
.removeExcludedAlbumForBackup(albumInfo);
|
||||||
} else {
|
} else {
|
||||||
if (ref.watch(backupProvider).selectedBackupAlbums.length == 1 &&
|
if (ref.watch(backupProvider).selectedBackupAlbums.length == 1 &&
|
||||||
ref.watch(backupProvider).selectedBackupAlbums.contains(albumInfo)) {
|
ref
|
||||||
|
.watch(backupProvider)
|
||||||
|
.selectedBackupAlbums
|
||||||
|
.contains(albumInfo)) {
|
||||||
ImmichToast.show(
|
ImmichToast.show(
|
||||||
context: context,
|
context: context,
|
||||||
msg: "Cannot exclude the only album",
|
msg: "Cannot exclude the only album",
|
||||||
@ -98,7 +111,9 @@ class AlbumInfoCard extends HookConsumerWidget {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
ref.watch(backupProvider.notifier).addExcludedAlbumForBackup(albumInfo);
|
ref
|
||||||
|
.watch(backupProvider.notifier)
|
||||||
|
.addExcludedAlbumForBackup(albumInfo);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
child: Card(
|
child: Card(
|
||||||
@ -121,12 +136,16 @@ class AlbumInfoCard extends HookConsumerWidget {
|
|||||||
width: 200,
|
width: 200,
|
||||||
height: 200,
|
height: 200,
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
borderRadius: const BorderRadius.only(topLeft: Radius.circular(12), topRight: Radius.circular(12)),
|
borderRadius: const BorderRadius.only(
|
||||||
|
topLeft: Radius.circular(12),
|
||||||
|
topRight: Radius.circular(12)),
|
||||||
image: DecorationImage(
|
image: DecorationImage(
|
||||||
colorFilter: _buildImageFilter(),
|
colorFilter: _buildImageFilter(),
|
||||||
image: imageData != null
|
image: imageData != null
|
||||||
? MemoryImage(imageData!)
|
? MemoryImage(imageData!)
|
||||||
: const AssetImage('assets/immich-logo-no-outline.png') as ImageProvider,
|
: const AssetImage(
|
||||||
|
'assets/immich-logo-no-outline.png')
|
||||||
|
as ImageProvider,
|
||||||
fit: BoxFit.cover,
|
fit: BoxFit.cover,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -150,13 +169,17 @@ class AlbumInfoCard extends HookConsumerWidget {
|
|||||||
Text(
|
Text(
|
||||||
albumInfo.name,
|
albumInfo.name,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 14, color: Theme.of(context).primaryColor, fontWeight: FontWeight.bold),
|
fontSize: 14,
|
||||||
|
color: Theme.of(context).primaryColor,
|
||||||
|
fontWeight: FontWeight.bold),
|
||||||
),
|
),
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.only(top: 2.0),
|
padding: const EdgeInsets.only(top: 2.0),
|
||||||
child: Text(
|
child: Text(
|
||||||
albumInfo.assetCount.toString() + (albumInfo.isAll ? " (ALL)" : ""),
|
albumInfo.assetCount.toString() +
|
||||||
style: TextStyle(fontSize: 12, color: Colors.grey[600]),
|
(albumInfo.isAll ? " (ALL)" : ""),
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 12, color: Colors.grey[600]),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
@ -165,7 +188,8 @@ class AlbumInfoCard extends HookConsumerWidget {
|
|||||||
),
|
),
|
||||||
IconButton(
|
IconButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
AutoRouter.of(context).push(AlbumPreviewRoute(album: albumInfo));
|
AutoRouter.of(context)
|
||||||
|
.push(AlbumPreviewRoute(album: albumInfo));
|
||||||
},
|
},
|
||||||
icon: Icon(
|
icon: Icon(
|
||||||
Icons.image_outlined,
|
Icons.image_outlined,
|
||||||
|
@ -4,7 +4,12 @@ class BackupInfoCard extends StatelessWidget {
|
|||||||
final String title;
|
final String title;
|
||||||
final String subtitle;
|
final String subtitle;
|
||||||
final String info;
|
final String info;
|
||||||
const BackupInfoCard({Key? key, required this.title, required this.subtitle, required this.info}) : super(key: key);
|
const BackupInfoCard(
|
||||||
|
{Key? key,
|
||||||
|
required this.title,
|
||||||
|
required this.subtitle,
|
||||||
|
required this.info})
|
||||||
|
: super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
@ -16,7 +16,8 @@ class AlbumPreviewPage extends HookConsumerWidget {
|
|||||||
final assets = useState<List<AssetEntity>>([]);
|
final assets = useState<List<AssetEntity>>([]);
|
||||||
|
|
||||||
_getAssetsInAlbum() async {
|
_getAssetsInAlbum() async {
|
||||||
assets.value = await album.getAssetListRange(start: 0, end: album.assetCount);
|
assets.value =
|
||||||
|
await album.getAssetListRange(start: 0, end: album.assetCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
useEffect(() {
|
useEffect(() {
|
||||||
@ -37,7 +38,10 @@ class AlbumPreviewPage extends HookConsumerWidget {
|
|||||||
padding: const EdgeInsets.only(top: 4.0),
|
padding: const EdgeInsets.only(top: 4.0),
|
||||||
child: Text(
|
child: Text(
|
||||||
"ID ${album.id}",
|
"ID ${album.id}",
|
||||||
style: TextStyle(fontSize: 10, color: Colors.grey[600], fontWeight: FontWeight.bold),
|
style: TextStyle(
|
||||||
|
fontSize: 10,
|
||||||
|
color: Colors.grey[600],
|
||||||
|
fontWeight: FontWeight.bold),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@ -55,8 +59,9 @@ class AlbumPreviewPage extends HookConsumerWidget {
|
|||||||
),
|
),
|
||||||
itemCount: assets.value.length,
|
itemCount: assets.value.length,
|
||||||
itemBuilder: (context, index) {
|
itemBuilder: (context, index) {
|
||||||
Future<Uint8List?> thumbData =
|
Future<Uint8List?> thumbData = assets.value[index]
|
||||||
assets.value[index].thumbnailDataWithSize(const ThumbnailSize(200, 200), quality: 50);
|
.thumbnailDataWithSize(const ThumbnailSize(200, 200),
|
||||||
|
quality: 50);
|
||||||
|
|
||||||
return FutureBuilder<Uint8List?>(
|
return FutureBuilder<Uint8List?>(
|
||||||
future: thumbData,
|
future: thumbData,
|
||||||
|
@ -17,7 +17,7 @@ class BackupAlbumSelectionPage extends HookConsumerWidget {
|
|||||||
final excludedBackupAlbums = ref.watch(backupProvider).excludedBackupAlbums;
|
final excludedBackupAlbums = ref.watch(backupProvider).excludedBackupAlbums;
|
||||||
|
|
||||||
useEffect(() {
|
useEffect(() {
|
||||||
ref.read(backupProvider.notifier).getBackupAlbumsInfo();
|
ref.read(backupProvider.notifier).getBackupInfo();
|
||||||
return null;
|
return null;
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
@ -37,8 +37,12 @@ class BackupAlbumSelectionPage extends HookConsumerWidget {
|
|||||||
itemBuilder: ((context, index) {
|
itemBuilder: ((context, index) {
|
||||||
var thumbnailData = availableAlbums[index].thumbnailData;
|
var thumbnailData = availableAlbums[index].thumbnailData;
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: index == 0 ? const EdgeInsets.only(left: 16.00) : const EdgeInsets.all(0),
|
padding: index == 0
|
||||||
child: AlbumInfoCard(imageData: thumbnailData, albumInfo: availableAlbums[index].albumEntity),
|
? const EdgeInsets.only(left: 16.00)
|
||||||
|
: const EdgeInsets.all(0),
|
||||||
|
child: AlbumInfoCard(
|
||||||
|
imageData: thumbnailData,
|
||||||
|
albumInfo: availableAlbums[index].albumEntity),
|
||||||
);
|
);
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
@ -67,10 +71,14 @@ class BackupAlbumSelectionPage extends HookConsumerWidget {
|
|||||||
onTap: removeSelection,
|
onTap: removeSelection,
|
||||||
child: Chip(
|
child: Chip(
|
||||||
visualDensity: VisualDensity.compact,
|
visualDensity: VisualDensity.compact,
|
||||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(5)),
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(5)),
|
||||||
label: Text(
|
label: Text(
|
||||||
album.name,
|
album.name,
|
||||||
style: const TextStyle(fontSize: 10, color: Colors.white, fontWeight: FontWeight.bold),
|
style: const TextStyle(
|
||||||
|
fontSize: 10,
|
||||||
|
color: Colors.white,
|
||||||
|
fontWeight: FontWeight.bold),
|
||||||
),
|
),
|
||||||
backgroundColor: Theme.of(context).primaryColor,
|
backgroundColor: Theme.of(context).primaryColor,
|
||||||
deleteIconColor: Colors.white,
|
deleteIconColor: Colors.white,
|
||||||
@ -88,7 +96,9 @@ class BackupAlbumSelectionPage extends HookConsumerWidget {
|
|||||||
_buildExcludedAlbumNameChip() {
|
_buildExcludedAlbumNameChip() {
|
||||||
return excludedBackupAlbums.map((album) {
|
return excludedBackupAlbums.map((album) {
|
||||||
void removeSelection() {
|
void removeSelection() {
|
||||||
ref.watch(backupProvider.notifier).removeExcludedAlbumForBackup(album);
|
ref
|
||||||
|
.watch(backupProvider.notifier)
|
||||||
|
.removeExcludedAlbumForBackup(album);
|
||||||
}
|
}
|
||||||
|
|
||||||
return GestureDetector(
|
return GestureDetector(
|
||||||
@ -97,10 +107,14 @@ class BackupAlbumSelectionPage extends HookConsumerWidget {
|
|||||||
padding: const EdgeInsets.only(right: 8.0),
|
padding: const EdgeInsets.only(right: 8.0),
|
||||||
child: Chip(
|
child: Chip(
|
||||||
visualDensity: VisualDensity.compact,
|
visualDensity: VisualDensity.compact,
|
||||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(5)),
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(5)),
|
||||||
label: Text(
|
label: Text(
|
||||||
album.name,
|
album.name,
|
||||||
style: const TextStyle(fontSize: 10, color: Colors.white, fontWeight: FontWeight.bold),
|
style: const TextStyle(
|
||||||
|
fontSize: 10,
|
||||||
|
color: Colors.white,
|
||||||
|
fontWeight: FontWeight.bold),
|
||||||
),
|
),
|
||||||
backgroundColor: Colors.red[300],
|
backgroundColor: Colors.red[300],
|
||||||
deleteIconColor: Colors.white,
|
deleteIconColor: Colors.white,
|
||||||
@ -142,7 +156,10 @@ class BackupAlbumSelectionPage extends HookConsumerWidget {
|
|||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 16.0),
|
padding: const EdgeInsets.symmetric(horizontal: 16.0),
|
||||||
child: Wrap(
|
child: Wrap(
|
||||||
children: [..._buildSelectedAlbumNameChip(), ..._buildExcludedAlbumNameChip()],
|
children: [
|
||||||
|
..._buildSelectedAlbumNameChip(),
|
||||||
|
..._buildExcludedAlbumNameChip()
|
||||||
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
||||||
@ -165,10 +182,17 @@ class BackupAlbumSelectionPage extends HookConsumerWidget {
|
|||||||
visualDensity: VisualDensity.compact,
|
visualDensity: VisualDensity.compact,
|
||||||
title: Text(
|
title: Text(
|
||||||
"Total unique assets",
|
"Total unique assets",
|
||||||
style: TextStyle(fontWeight: FontWeight.bold, fontSize: 14, color: Colors.grey[700]),
|
style: TextStyle(
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
fontSize: 14,
|
||||||
|
color: Colors.grey[700]),
|
||||||
),
|
),
|
||||||
trailing: Text(
|
trailing: Text(
|
||||||
ref.watch(backupProvider).allUniqueAssets.length.toString(),
|
ref
|
||||||
|
.watch(backupProvider)
|
||||||
|
.allUniqueAssets
|
||||||
|
.length
|
||||||
|
.toString(),
|
||||||
style: const TextStyle(fontWeight: FontWeight.bold),
|
style: const TextStyle(fontWeight: FontWeight.bold),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -206,7 +230,8 @@ class BackupAlbumSelectionPage extends HookConsumerWidget {
|
|||||||
context: context,
|
context: context,
|
||||||
builder: (BuildContext context) {
|
builder: (BuildContext context) {
|
||||||
return AlertDialog(
|
return AlertDialog(
|
||||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(12)),
|
||||||
elevation: 5,
|
elevation: 5,
|
||||||
title: Text(
|
title: Text(
|
||||||
'Selection Info',
|
'Selection Info',
|
||||||
@ -221,7 +246,8 @@ class BackupAlbumSelectionPage extends HookConsumerWidget {
|
|||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
'Assets can scatter across multiple albums. Thus, albums can be included or excluded during the backup process.',
|
'Assets can scatter across multiple albums. Thus, albums can be included or excluded during the backup process.',
|
||||||
style: TextStyle(fontSize: 14, color: Colors.grey[700]),
|
style: TextStyle(
|
||||||
|
fontSize: 14, color: Colors.grey[700]),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
@ -26,7 +26,7 @@ class BackupControllerPage extends HookConsumerWidget {
|
|||||||
|
|
||||||
useEffect(() {
|
useEffect(() {
|
||||||
if (backupState.backupProgress != BackUpProgressEnum.inProgress) {
|
if (backupState.backupProgress != BackUpProgressEnum.inProgress) {
|
||||||
ref.read(backupProvider.notifier).getBackupInfo();
|
ref.watch(backupProvider.notifier).getBackupInfo();
|
||||||
}
|
}
|
||||||
|
|
||||||
ref
|
ref
|
||||||
@ -112,13 +112,15 @@ class BackupControllerPage extends HookConsumerWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
isAutoBackup
|
if (isAutoBackup) {
|
||||||
? ref
|
ref
|
||||||
.watch(authenticationProvider.notifier)
|
.read(authenticationProvider.notifier)
|
||||||
.setAutoBackup(false)
|
.setAutoBackup(false);
|
||||||
: ref
|
} else {
|
||||||
.watch(authenticationProvider.notifier)
|
ref
|
||||||
.setAutoBackup(true);
|
.read(authenticationProvider.notifier)
|
||||||
|
.setAutoBackup(true);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
child: Text("Turn $backupBtnText Backup",
|
child: Text("Turn $backupBtnText Backup",
|
||||||
style: const TextStyle(fontWeight: FontWeight.bold)),
|
style: const TextStyle(fontWeight: FontWeight.bold)),
|
||||||
@ -212,7 +214,7 @@ class BackupControllerPage extends HookConsumerWidget {
|
|||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
const Text(
|
const Text(
|
||||||
"Albums to be backup",
|
"Albums to be backed up",
|
||||||
style: TextStyle(color: Color(0xFF808080), fontSize: 12),
|
style: TextStyle(color: Color(0xFF808080), fontSize: 12),
|
||||||
),
|
),
|
||||||
_buildSelectedAlbumName(),
|
_buildSelectedAlbumName(),
|
||||||
@ -282,14 +284,12 @@ class BackupControllerPage extends HookConsumerWidget {
|
|||||||
),
|
),
|
||||||
BackupInfoCard(
|
BackupInfoCard(
|
||||||
title: "Backup",
|
title: "Backup",
|
||||||
subtitle:
|
subtitle: "Backed up photos and videos",
|
||||||
"Photos and videos from selected albums that are backup",
|
|
||||||
info: "${backupState.selectedAlbumsBackupAssetsIds.length}",
|
info: "${backupState.selectedAlbumsBackupAssetsIds.length}",
|
||||||
),
|
),
|
||||||
BackupInfoCard(
|
BackupInfoCard(
|
||||||
title: "Remainder",
|
title: "Remainder",
|
||||||
subtitle:
|
subtitle: "Remaining photos and albums to back up from selection",
|
||||||
"Photos and videos that has not been backing up from selected albums",
|
|
||||||
info:
|
info:
|
||||||
"${backupState.allUniqueAssets.length - backupState.selectedAlbumsBackupAssetsIds.length}",
|
"${backupState.allUniqueAssets.length - backupState.selectedAlbumsBackupAssetsIds.length}",
|
||||||
),
|
),
|
||||||
|
@ -35,7 +35,8 @@ class DeleteAssetResponse {
|
|||||||
|
|
||||||
String toJson() => json.encode(toMap());
|
String toJson() => json.encode(toMap());
|
||||||
|
|
||||||
factory DeleteAssetResponse.fromJson(String source) => DeleteAssetResponse.fromMap(json.decode(source));
|
factory DeleteAssetResponse.fromJson(String source) =>
|
||||||
|
DeleteAssetResponse.fromMap(json.decode(source));
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() => 'DeleteAssetResponse(id: $id, status: $status)';
|
String toString() => 'DeleteAssetResponse(id: $id, status: $status)';
|
||||||
@ -44,7 +45,9 @@ class DeleteAssetResponse {
|
|||||||
bool operator ==(Object other) {
|
bool operator ==(Object other) {
|
||||||
if (identical(this, other)) return true;
|
if (identical(this, other)) return true;
|
||||||
|
|
||||||
return other is DeleteAssetResponse && other.id == id && other.status == status;
|
return other is DeleteAssetResponse &&
|
||||||
|
other.id == id &&
|
||||||
|
other.status == status;
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -31,13 +31,15 @@ class ImmichAssetGroupByDate {
|
|||||||
factory ImmichAssetGroupByDate.fromMap(Map<String, dynamic> map) {
|
factory ImmichAssetGroupByDate.fromMap(Map<String, dynamic> map) {
|
||||||
return ImmichAssetGroupByDate(
|
return ImmichAssetGroupByDate(
|
||||||
date: map['date'] ?? '',
|
date: map['date'] ?? '',
|
||||||
assets: List<ImmichAsset>.from(map['assets']?.map((x) => ImmichAsset.fromMap(x))),
|
assets: List<ImmichAsset>.from(
|
||||||
|
map['assets']?.map((x) => ImmichAsset.fromMap(x))),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
String toJson() => json.encode(toMap());
|
String toJson() => json.encode(toMap());
|
||||||
|
|
||||||
factory ImmichAssetGroupByDate.fromJson(String source) => ImmichAssetGroupByDate.fromMap(json.decode(source));
|
factory ImmichAssetGroupByDate.fromJson(String source) =>
|
||||||
|
ImmichAssetGroupByDate.fromMap(json.decode(source));
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() => 'ImmichAssetGroupByDate(date: $date, assets: $assets)';
|
String toString() => 'ImmichAssetGroupByDate(date: $date, assets: $assets)';
|
||||||
@ -46,7 +48,9 @@ class ImmichAssetGroupByDate {
|
|||||||
bool operator ==(Object other) {
|
bool operator ==(Object other) {
|
||||||
if (identical(this, other)) return true;
|
if (identical(this, other)) return true;
|
||||||
|
|
||||||
return other is ImmichAssetGroupByDate && other.date == date && listEquals(other.assets, assets);
|
return other is ImmichAssetGroupByDate &&
|
||||||
|
other.date == date &&
|
||||||
|
listEquals(other.assets, assets);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -86,17 +90,20 @@ class GetAllAssetResponse {
|
|||||||
factory GetAllAssetResponse.fromMap(Map<String, dynamic> map) {
|
factory GetAllAssetResponse.fromMap(Map<String, dynamic> map) {
|
||||||
return GetAllAssetResponse(
|
return GetAllAssetResponse(
|
||||||
count: map['count']?.toInt() ?? 0,
|
count: map['count']?.toInt() ?? 0,
|
||||||
data: List<ImmichAssetGroupByDate>.from(map['data']?.map((x) => ImmichAssetGroupByDate.fromMap(x))),
|
data: List<ImmichAssetGroupByDate>.from(
|
||||||
|
map['data']?.map((x) => ImmichAssetGroupByDate.fromMap(x))),
|
||||||
nextPageKey: map['nextPageKey'] ?? '',
|
nextPageKey: map['nextPageKey'] ?? '',
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
String toJson() => json.encode(toMap());
|
String toJson() => json.encode(toMap());
|
||||||
|
|
||||||
factory GetAllAssetResponse.fromJson(String source) => GetAllAssetResponse.fromMap(json.decode(source));
|
factory GetAllAssetResponse.fromJson(String source) =>
|
||||||
|
GetAllAssetResponse.fromMap(json.decode(source));
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() => 'GetAllAssetResponse(count: $count, data: $data, nextPageKey: $nextPageKey)';
|
String toString() =>
|
||||||
|
'GetAllAssetResponse(count: $count, data: $data, nextPageKey: $nextPageKey)';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool operator ==(Object other) {
|
bool operator ==(Object other) {
|
||||||
|
@ -37,14 +37,16 @@ class HomePageState {
|
|||||||
factory HomePageState.fromMap(Map<String, dynamic> map) {
|
factory HomePageState.fromMap(Map<String, dynamic> map) {
|
||||||
return HomePageState(
|
return HomePageState(
|
||||||
isMultiSelectEnable: map['isMultiSelectEnable'] ?? false,
|
isMultiSelectEnable: map['isMultiSelectEnable'] ?? false,
|
||||||
selectedItems: Set<ImmichAsset>.from(map['selectedItems']?.map((x) => ImmichAsset.fromMap(x))),
|
selectedItems: Set<ImmichAsset>.from(
|
||||||
|
map['selectedItems']?.map((x) => ImmichAsset.fromMap(x))),
|
||||||
selectedDateGroup: Set<String>.from(map['selectedDateGroup']),
|
selectedDateGroup: Set<String>.from(map['selectedDateGroup']),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
String toJson() => json.encode(toMap());
|
String toJson() => json.encode(toMap());
|
||||||
|
|
||||||
factory HomePageState.fromJson(String source) => HomePageState.fromMap(json.decode(source));
|
factory HomePageState.fromJson(String source) =>
|
||||||
|
HomePageState.fromMap(json.decode(source));
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() =>
|
String toString() =>
|
||||||
@ -62,5 +64,8 @@ class HomePageState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
int get hashCode => isMultiSelectEnable.hashCode ^ selectedItems.hashCode ^ selectedDateGroup.hashCode;
|
int get hashCode =>
|
||||||
|
isMultiSelectEnable.hashCode ^
|
||||||
|
selectedItems.hashCode ^
|
||||||
|
selectedDateGroup.hashCode;
|
||||||
}
|
}
|
||||||
|
@ -13,7 +13,8 @@ class HomePageStateNotifier extends StateNotifier<HomePageState> {
|
|||||||
);
|
);
|
||||||
|
|
||||||
void addSelectedDateGroup(String dateGroupTitle) {
|
void addSelectedDateGroup(String dateGroupTitle) {
|
||||||
state = state.copyWith(selectedDateGroup: {...state.selectedDateGroup, dateGroupTitle});
|
state = state.copyWith(
|
||||||
|
selectedDateGroup: {...state.selectedDateGroup, dateGroupTitle});
|
||||||
}
|
}
|
||||||
|
|
||||||
void removeSelectedDateGroup(String dateGroupTitle) {
|
void removeSelectedDateGroup(String dateGroupTitle) {
|
||||||
@ -25,11 +26,13 @@ class HomePageStateNotifier extends StateNotifier<HomePageState> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void enableMultiSelect(Set<ImmichAsset> selectedItems) {
|
void enableMultiSelect(Set<ImmichAsset> selectedItems) {
|
||||||
state = state.copyWith(isMultiSelectEnable: true, selectedItems: selectedItems);
|
state =
|
||||||
|
state.copyWith(isMultiSelectEnable: true, selectedItems: selectedItems);
|
||||||
}
|
}
|
||||||
|
|
||||||
void disableMultiSelect() {
|
void disableMultiSelect() {
|
||||||
state = state.copyWith(isMultiSelectEnable: false, selectedItems: {}, selectedDateGroup: {});
|
state = state.copyWith(
|
||||||
|
isMultiSelectEnable: false, selectedItems: {}, selectedDateGroup: {});
|
||||||
}
|
}
|
||||||
|
|
||||||
void addSingleSelectedItem(ImmichAsset asset) {
|
void addSingleSelectedItem(ImmichAsset asset) {
|
||||||
@ -60,4 +63,5 @@ class HomePageStateNotifier extends StateNotifier<HomePageState> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
final homePageStateProvider =
|
final homePageStateProvider =
|
||||||
StateNotifierProvider<HomePageStateNotifier, HomePageState>(((ref) => HomePageStateNotifier()));
|
StateNotifierProvider<HomePageStateNotifier, HomePageState>(
|
||||||
|
((ref) => HomePageStateNotifier()));
|
||||||
|
@ -13,7 +13,8 @@ class ControlBottomAppBar extends StatelessWidget {
|
|||||||
width: MediaQuery.of(context).size.width,
|
width: MediaQuery.of(context).size.width,
|
||||||
height: MediaQuery.of(context).size.height * 0.15,
|
height: MediaQuery.of(context).size.height * 0.15,
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
borderRadius: const BorderRadius.only(topLeft: Radius.circular(15), topRight: Radius.circular(15)),
|
borderRadius: const BorderRadius.only(
|
||||||
|
topLeft: Radius.circular(15), topRight: Radius.circular(15)),
|
||||||
color: Colors.grey[300]?.withOpacity(0.98),
|
color: Colors.grey[300]?.withOpacity(0.98),
|
||||||
),
|
),
|
||||||
child: Column(
|
child: Column(
|
||||||
@ -46,7 +47,11 @@ class ControlBottomAppBar extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class ControlBoxButton extends StatelessWidget {
|
class ControlBoxButton extends StatelessWidget {
|
||||||
const ControlBoxButton({Key? key, required this.label, required this.iconData, required this.onPressed})
|
const ControlBoxButton(
|
||||||
|
{Key? key,
|
||||||
|
required this.label,
|
||||||
|
required this.iconData,
|
||||||
|
required this.onPressed})
|
||||||
: super(key: key);
|
: super(key: key);
|
||||||
|
|
||||||
final String label;
|
final String label;
|
||||||
|
@ -18,9 +18,12 @@ class DailyTitleText extends ConsumerWidget {
|
|||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
var currentYear = DateTime.now().year;
|
var currentYear = DateTime.now().year;
|
||||||
var groupYear = DateTime.parse(isoDate).year;
|
var groupYear = DateTime.parse(isoDate).year;
|
||||||
var formatDateTemplate = currentYear == groupYear ? 'E, MMM dd' : 'E, MMM dd, yyyy';
|
var formatDateTemplate =
|
||||||
var dateText = DateFormat(formatDateTemplate).format(DateTime.parse(isoDate));
|
currentYear == groupYear ? 'E, MMM dd' : 'E, MMM dd, yyyy';
|
||||||
var isMultiSelectEnable = ref.watch(homePageStateProvider).isMultiSelectEnable;
|
var dateText =
|
||||||
|
DateFormat(formatDateTemplate).format(DateTime.parse(isoDate));
|
||||||
|
var isMultiSelectEnable =
|
||||||
|
ref.watch(homePageStateProvider).isMultiSelectEnable;
|
||||||
var selectedDateGroup = ref.watch(homePageStateProvider).selectedDateGroup;
|
var selectedDateGroup = ref.watch(homePageStateProvider).selectedDateGroup;
|
||||||
var selectedItems = ref.watch(homePageStateProvider).selectedItems;
|
var selectedItems = ref.watch(homePageStateProvider).selectedItems;
|
||||||
|
|
||||||
@ -35,23 +38,42 @@ class DailyTitleText extends ConsumerWidget {
|
|||||||
selectedDateGroup.contains(dateText) &&
|
selectedDateGroup.contains(dateText) &&
|
||||||
selectedItems.length != assetGroup.length) {
|
selectedItems.length != assetGroup.length) {
|
||||||
// Multi select is active - click again on the icon while it is not the only active group -> remove that group from selected group/items
|
// Multi select is active - click again on the icon while it is not the only active group -> remove that group from selected group/items
|
||||||
ref.watch(homePageStateProvider.notifier).removeSelectedDateGroup(dateText);
|
ref
|
||||||
ref.watch(homePageStateProvider.notifier).removeMultipleSelectedItem(assetGroup);
|
.watch(homePageStateProvider.notifier)
|
||||||
} else if (isMultiSelectEnable && selectedDateGroup.contains(dateText) && selectedDateGroup.length > 1) {
|
.removeSelectedDateGroup(dateText);
|
||||||
ref.watch(homePageStateProvider.notifier).removeSelectedDateGroup(dateText);
|
ref
|
||||||
ref.watch(homePageStateProvider.notifier).removeMultipleSelectedItem(assetGroup);
|
.watch(homePageStateProvider.notifier)
|
||||||
|
.removeMultipleSelectedItem(assetGroup);
|
||||||
|
} else if (isMultiSelectEnable &&
|
||||||
|
selectedDateGroup.contains(dateText) &&
|
||||||
|
selectedDateGroup.length > 1) {
|
||||||
|
ref
|
||||||
|
.watch(homePageStateProvider.notifier)
|
||||||
|
.removeSelectedDateGroup(dateText);
|
||||||
|
ref
|
||||||
|
.watch(homePageStateProvider.notifier)
|
||||||
|
.removeMultipleSelectedItem(assetGroup);
|
||||||
} else if (isMultiSelectEnable && !selectedDateGroup.contains(dateText)) {
|
} else if (isMultiSelectEnable && !selectedDateGroup.contains(dateText)) {
|
||||||
ref.watch(homePageStateProvider.notifier).addSelectedDateGroup(dateText);
|
ref
|
||||||
ref.watch(homePageStateProvider.notifier).addMultipleSelectedItems(assetGroup);
|
.watch(homePageStateProvider.notifier)
|
||||||
|
.addSelectedDateGroup(dateText);
|
||||||
|
ref
|
||||||
|
.watch(homePageStateProvider.notifier)
|
||||||
|
.addMultipleSelectedItems(assetGroup);
|
||||||
} else {
|
} else {
|
||||||
ref.watch(homePageStateProvider.notifier).enableMultiSelect(assetGroup.toSet());
|
ref
|
||||||
ref.watch(homePageStateProvider.notifier).addSelectedDateGroup(dateText);
|
.watch(homePageStateProvider.notifier)
|
||||||
|
.enableMultiSelect(assetGroup.toSet());
|
||||||
|
ref
|
||||||
|
.watch(homePageStateProvider.notifier)
|
||||||
|
.addSelectedDateGroup(dateText);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return SliverToBoxAdapter(
|
return SliverToBoxAdapter(
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.only(top: 29.0, bottom: 29.0, left: 12.0, right: 12.0),
|
padding: const EdgeInsets.only(
|
||||||
|
top: 29.0, bottom: 29.0, left: 12.0, right: 12.0),
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
|
@ -14,7 +14,8 @@ class DeleteDialog extends ConsumerWidget {
|
|||||||
backgroundColor: Colors.grey[200],
|
backgroundColor: Colors.grey[200],
|
||||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)),
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)),
|
||||||
title: const Text("Delete Permanently"),
|
title: const Text("Delete Permanently"),
|
||||||
content: const Text("These items will be permanently deleted from Immich and from your device"),
|
content: const Text(
|
||||||
|
"These items will be permanently deleted from Immich and from your device"),
|
||||||
actions: [
|
actions: [
|
||||||
TextButton(
|
TextButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
@ -27,7 +28,9 @@ class DeleteDialog extends ConsumerWidget {
|
|||||||
),
|
),
|
||||||
TextButton(
|
TextButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
ref.watch(assetProvider.notifier).deleteAssets(homePageState.selectedItems);
|
ref
|
||||||
|
.watch(assetProvider.notifier)
|
||||||
|
.deleteAssets(homePageState.selectedItems);
|
||||||
ref.watch(homePageStateProvider.notifier).disableMultiSelect();
|
ref.watch(homePageStateProvider.notifier).disableMultiSelect();
|
||||||
|
|
||||||
Navigator.of(context).pop();
|
Navigator.of(context).pop();
|
||||||
|
@ -33,7 +33,10 @@ class ImageGrid extends ConsumerWidget {
|
|||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
assetGroup[index].duration.toString().substring(0, 7),
|
assetGroup[index]
|
||||||
|
.duration
|
||||||
|
.toString()
|
||||||
|
.substring(0, 7),
|
||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
color: Colors.white,
|
color: Colors.white,
|
||||||
fontSize: 10,
|
fontSize: 10,
|
||||||
|
@ -25,7 +25,8 @@ class ThumbnailImage extends HookConsumerWidget {
|
|||||||
'${box.get(serverEndpointKey)}/asset/file?aid=${asset.deviceAssetId}&did=${asset.deviceId}&isThumb=true';
|
'${box.get(serverEndpointKey)}/asset/file?aid=${asset.deviceAssetId}&did=${asset.deviceId}&isThumb=true';
|
||||||
|
|
||||||
var selectedAsset = ref.watch(homePageStateProvider).selectedItems;
|
var selectedAsset = ref.watch(homePageStateProvider).selectedItems;
|
||||||
var isMultiSelectEnable = ref.watch(homePageStateProvider).isMultiSelectEnable;
|
var isMultiSelectEnable =
|
||||||
|
ref.watch(homePageStateProvider).isMultiSelectEnable;
|
||||||
var deviceId = ref.watch(authenticationProvider).deviceId;
|
var deviceId = ref.watch(authenticationProvider).deviceId;
|
||||||
|
|
||||||
Widget _buildSelectionIcon(ImmichAsset asset) {
|
Widget _buildSelectionIcon(ImmichAsset asset) {
|
||||||
@ -45,12 +46,20 @@ class ThumbnailImage extends HookConsumerWidget {
|
|||||||
return GestureDetector(
|
return GestureDetector(
|
||||||
onTap: () {
|
onTap: () {
|
||||||
debugPrint("View ${asset.id}");
|
debugPrint("View ${asset.id}");
|
||||||
if (isMultiSelectEnable && selectedAsset.contains(asset) && selectedAsset.length == 1) {
|
if (isMultiSelectEnable &&
|
||||||
|
selectedAsset.contains(asset) &&
|
||||||
|
selectedAsset.length == 1) {
|
||||||
ref.watch(homePageStateProvider.notifier).disableMultiSelect();
|
ref.watch(homePageStateProvider.notifier).disableMultiSelect();
|
||||||
} else if (isMultiSelectEnable && selectedAsset.contains(asset) && selectedAsset.length > 1) {
|
} else if (isMultiSelectEnable &&
|
||||||
ref.watch(homePageStateProvider.notifier).removeSingleSelectedItem(asset);
|
selectedAsset.contains(asset) &&
|
||||||
|
selectedAsset.length > 1) {
|
||||||
|
ref
|
||||||
|
.watch(homePageStateProvider.notifier)
|
||||||
|
.removeSingleSelectedItem(asset);
|
||||||
} else if (isMultiSelectEnable && !selectedAsset.contains(asset)) {
|
} else if (isMultiSelectEnable && !selectedAsset.contains(asset)) {
|
||||||
ref.watch(homePageStateProvider.notifier).addSingleSelectedItem(asset);
|
ref
|
||||||
|
.watch(homePageStateProvider.notifier)
|
||||||
|
.addSingleSelectedItem(asset);
|
||||||
} else {
|
} else {
|
||||||
if (asset.type == 'IMAGE') {
|
if (asset.type == 'IMAGE') {
|
||||||
AutoRouter.of(context).push(
|
AutoRouter.of(context).push(
|
||||||
@ -65,7 +74,8 @@ class ThumbnailImage extends HookConsumerWidget {
|
|||||||
} else {
|
} else {
|
||||||
AutoRouter.of(context).push(
|
AutoRouter.of(context).push(
|
||||||
VideoViewerRoute(
|
VideoViewerRoute(
|
||||||
videoUrl: '${box.get(serverEndpointKey)}/asset/file?aid=${asset.deviceAssetId}&did=${asset.deviceId}',
|
videoUrl:
|
||||||
|
'${box.get(serverEndpointKey)}/asset/file?aid=${asset.deviceAssetId}&did=${asset.deviceId}',
|
||||||
asset: asset),
|
asset: asset),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -83,7 +93,8 @@ class ThumbnailImage extends HookConsumerWidget {
|
|||||||
Container(
|
Container(
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
border: isMultiSelectEnable && selectedAsset.contains(asset)
|
border: isMultiSelectEnable && selectedAsset.contains(asset)
|
||||||
? Border.all(color: Theme.of(context).primaryColorLight, width: 10)
|
? Border.all(
|
||||||
|
color: Theme.of(context).primaryColorLight, width: 10)
|
||||||
: const Border(),
|
: const Border(),
|
||||||
),
|
),
|
||||||
child: CachedNetworkImage(
|
child: CachedNetworkImage(
|
||||||
@ -93,11 +104,15 @@ class ThumbnailImage extends HookConsumerWidget {
|
|||||||
memCacheHeight: asset.type == 'IMAGE' ? 250 : 400,
|
memCacheHeight: asset.type == 'IMAGE' ? 250 : 400,
|
||||||
fit: BoxFit.cover,
|
fit: BoxFit.cover,
|
||||||
imageUrl: thumbnailRequestUrl,
|
imageUrl: thumbnailRequestUrl,
|
||||||
httpHeaders: {"Authorization": "Bearer ${box.get(accessTokenKey)}"},
|
httpHeaders: {
|
||||||
|
"Authorization": "Bearer ${box.get(accessTokenKey)}"
|
||||||
|
},
|
||||||
fadeInDuration: const Duration(milliseconds: 250),
|
fadeInDuration: const Duration(milliseconds: 250),
|
||||||
progressIndicatorBuilder: (context, url, downloadProgress) => Transform.scale(
|
progressIndicatorBuilder: (context, url, downloadProgress) =>
|
||||||
|
Transform.scale(
|
||||||
scale: 0.2,
|
scale: 0.2,
|
||||||
child: CircularProgressIndicator(value: downloadProgress.progress),
|
child: CircularProgressIndicator(
|
||||||
|
value: downloadProgress.progress),
|
||||||
),
|
),
|
||||||
errorWidget: (context, url, error) {
|
errorWidget: (context, url, error) {
|
||||||
return Icon(
|
return Icon(
|
||||||
@ -122,7 +137,9 @@ class ThumbnailImage extends HookConsumerWidget {
|
|||||||
right: 10,
|
right: 10,
|
||||||
bottom: 5,
|
bottom: 5,
|
||||||
child: Icon(
|
child: Icon(
|
||||||
(deviceId != asset.deviceId) ? Icons.cloud_done_outlined : Icons.photo_library_rounded,
|
(deviceId != asset.deviceId)
|
||||||
|
? Icons.cloud_done_outlined
|
||||||
|
: Icons.photo_library_rounded,
|
||||||
color: Colors.white,
|
color: Colors.white,
|
||||||
size: 18,
|
size: 18,
|
||||||
),
|
),
|
||||||
|
@ -98,7 +98,8 @@ class AuthenticationState {
|
|||||||
|
|
||||||
String toJson() => json.encode(toMap());
|
String toJson() => json.encode(toMap());
|
||||||
|
|
||||||
factory AuthenticationState.fromJson(String source) => AuthenticationState.fromMap(json.decode(source));
|
factory AuthenticationState.fromJson(String source) =>
|
||||||
|
AuthenticationState.fromMap(json.decode(source));
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool operator ==(Object other) {
|
bool operator ==(Object other) {
|
||||||
|
@ -16,5 +16,9 @@ class HiveSavedLoginInfo {
|
|||||||
@HiveField(3)
|
@HiveField(3)
|
||||||
bool isSaveLogin;
|
bool isSaveLogin;
|
||||||
|
|
||||||
HiveSavedLoginInfo({required this.email, required this.password, required this.serverUrl, required this.isSaveLogin});
|
HiveSavedLoginInfo(
|
||||||
|
{required this.email,
|
||||||
|
required this.password,
|
||||||
|
required this.serverUrl,
|
||||||
|
required this.isSaveLogin});
|
||||||
}
|
}
|
||||||
|
@ -73,7 +73,8 @@ class LogInReponse {
|
|||||||
|
|
||||||
String toJson() => json.encode(toMap());
|
String toJson() => json.encode(toMap());
|
||||||
|
|
||||||
factory LogInReponse.fromJson(String source) => LogInReponse.fromMap(json.decode(source));
|
factory LogInReponse.fromJson(String source) =>
|
||||||
|
LogInReponse.fromMap(json.decode(source));
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
|
@ -15,13 +15,17 @@ class LoginForm extends HookConsumerWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
final usernameController = useTextEditingController.fromValue(TextEditingValue.empty);
|
final usernameController =
|
||||||
final passwordController = useTextEditingController.fromValue(TextEditingValue.empty);
|
useTextEditingController.fromValue(TextEditingValue.empty);
|
||||||
final serverEndpointController = useTextEditingController(text: 'http://your-server-ip:2283');
|
final passwordController =
|
||||||
|
useTextEditingController.fromValue(TextEditingValue.empty);
|
||||||
|
final serverEndpointController =
|
||||||
|
useTextEditingController(text: 'http://your-server-ip:2283');
|
||||||
final isSaveLoginInfo = useState<bool>(false);
|
final isSaveLoginInfo = useState<bool>(false);
|
||||||
|
|
||||||
useEffect(() {
|
useEffect(() {
|
||||||
var loginInfo = Hive.box<HiveSavedLoginInfo>(hiveLoginInfoBox).get(savedLoginInfoKey);
|
var loginInfo =
|
||||||
|
Hive.box<HiveSavedLoginInfo>(hiveLoginInfoBox).get(savedLoginInfoKey);
|
||||||
|
|
||||||
if (loginInfo != null) {
|
if (loginInfo != null) {
|
||||||
usernameController.text = loginInfo.email;
|
usernameController.text = loginInfo.email;
|
||||||
@ -64,11 +68,15 @@ class LoginForm extends HookConsumerWidget {
|
|||||||
contentPadding: const EdgeInsets.symmetric(horizontal: 8),
|
contentPadding: const EdgeInsets.symmetric(horizontal: 8),
|
||||||
dense: true,
|
dense: true,
|
||||||
side: const BorderSide(color: Colors.grey, width: 1.5),
|
side: const BorderSide(color: Colors.grey, width: 1.5),
|
||||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(5)),
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(5)),
|
||||||
enableFeedback: true,
|
enableFeedback: true,
|
||||||
title: const Text(
|
title: const Text(
|
||||||
"Stay logged in",
|
"Stay logged in",
|
||||||
style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold, color: Colors.grey),
|
style: TextStyle(
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
color: Colors.grey),
|
||||||
),
|
),
|
||||||
value: isSaveLoginInfo.value,
|
value: isSaveLoginInfo.value,
|
||||||
onChanged: (switchValue) {
|
onChanged: (switchValue) {
|
||||||
@ -94,11 +102,13 @@ class LoginForm extends HookConsumerWidget {
|
|||||||
class ServerEndpointInput extends StatelessWidget {
|
class ServerEndpointInput extends StatelessWidget {
|
||||||
final TextEditingController controller;
|
final TextEditingController controller;
|
||||||
|
|
||||||
const ServerEndpointInput({Key? key, required this.controller}) : super(key: key);
|
const ServerEndpointInput({Key? key, required this.controller})
|
||||||
|
: super(key: key);
|
||||||
|
|
||||||
String? _validateInput(String? url) {
|
String? _validateInput(String? url) {
|
||||||
if (url == null) return null;
|
if (url == null) return null;
|
||||||
if (!url.startsWith(RegExp(r'https?://'))) return 'Please specify http:// or https://';
|
if (!url.startsWith(RegExp(r'https?://')))
|
||||||
|
return 'Please specify http:// or https://';
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -107,7 +117,9 @@ class ServerEndpointInput extends StatelessWidget {
|
|||||||
return TextFormField(
|
return TextFormField(
|
||||||
controller: controller,
|
controller: controller,
|
||||||
decoration: const InputDecoration(
|
decoration: const InputDecoration(
|
||||||
labelText: 'Server Endpoint URL', border: OutlineInputBorder(), hintText: 'http://your-server-ip:port'),
|
labelText: 'Server Endpoint URL',
|
||||||
|
border: OutlineInputBorder(),
|
||||||
|
hintText: 'http://your-server-ip:port'),
|
||||||
validator: _validateInput,
|
validator: _validateInput,
|
||||||
autovalidateMode: AutovalidateMode.always,
|
autovalidateMode: AutovalidateMode.always,
|
||||||
);
|
);
|
||||||
@ -131,8 +143,10 @@ class EmailInput extends StatelessWidget {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return TextFormField(
|
return TextFormField(
|
||||||
controller: controller,
|
controller: controller,
|
||||||
decoration:
|
decoration: const InputDecoration(
|
||||||
const InputDecoration(labelText: 'Email', border: OutlineInputBorder(), hintText: 'youremail@email.com'),
|
labelText: 'Email',
|
||||||
|
border: OutlineInputBorder(),
|
||||||
|
hintText: 'youremail@email.com'),
|
||||||
validator: _validateInput,
|
validator: _validateInput,
|
||||||
autovalidateMode: AutovalidateMode.always,
|
autovalidateMode: AutovalidateMode.always,
|
||||||
);
|
);
|
||||||
@ -149,7 +163,10 @@ class PasswordInput extends StatelessWidget {
|
|||||||
return TextFormField(
|
return TextFormField(
|
||||||
obscureText: true,
|
obscureText: true,
|
||||||
controller: controller,
|
controller: controller,
|
||||||
decoration: const InputDecoration(labelText: 'Password', border: OutlineInputBorder(), hintText: 'password'),
|
decoration: const InputDecoration(
|
||||||
|
labelText: 'Password',
|
||||||
|
border: OutlineInputBorder(),
|
||||||
|
hintText: 'password'),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -184,7 +201,8 @@ class LoginButton extends ConsumerWidget {
|
|||||||
|
|
||||||
var isAuthenticated = await ref
|
var isAuthenticated = await ref
|
||||||
.read(authenticationProvider.notifier)
|
.read(authenticationProvider.notifier)
|
||||||
.login(emailController.text, passwordController.text, serverEndpointController.text, isSavedLoginInfo);
|
.login(emailController.text, passwordController.text,
|
||||||
|
serverEndpointController.text, isSavedLoginInfo);
|
||||||
|
|
||||||
if (isAuthenticated) {
|
if (isAuthenticated) {
|
||||||
// Resume backup (if enable) then navigate
|
// Resume backup (if enable) then navigate
|
||||||
@ -193,7 +211,8 @@ class LoginButton extends ConsumerWidget {
|
|||||||
} else {
|
} else {
|
||||||
ImmichToast.show(
|
ImmichToast.show(
|
||||||
context: context,
|
context: context,
|
||||||
msg: "Error logging you in, check server url, email and password!",
|
msg:
|
||||||
|
"Error logging you in, check server url, email and password!",
|
||||||
toastType: ToastType.error,
|
toastType: ToastType.error,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -53,7 +53,8 @@ class CuratedLocation {
|
|||||||
|
|
||||||
String toJson() => json.encode(toMap());
|
String toJson() => json.encode(toMap());
|
||||||
|
|
||||||
factory CuratedLocation.fromJson(String source) => CuratedLocation.fromMap(json.decode(source));
|
factory CuratedLocation.fromJson(String source) =>
|
||||||
|
CuratedLocation.fromMap(json.decode(source));
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
@ -74,6 +75,10 @@ class CuratedLocation {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
int get hashCode {
|
int get hashCode {
|
||||||
return id.hashCode ^ city.hashCode ^ resizePath.hashCode ^ deviceAssetId.hashCode ^ deviceId.hashCode;
|
return id.hashCode ^
|
||||||
|
city.hashCode ^
|
||||||
|
resizePath.hashCode ^
|
||||||
|
deviceAssetId.hashCode ^
|
||||||
|
deviceId.hashCode;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -54,7 +54,8 @@ class CuratedObject {
|
|||||||
|
|
||||||
String toJson() => json.encode(toMap());
|
String toJson() => json.encode(toMap());
|
||||||
|
|
||||||
factory CuratedObject.fromJson(String source) => CuratedObject.fromMap(json.decode(source));
|
factory CuratedObject.fromJson(String source) =>
|
||||||
|
CuratedObject.fromMap(json.decode(source));
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
@ -75,6 +76,10 @@ class CuratedObject {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
int get hashCode {
|
int get hashCode {
|
||||||
return id.hashCode ^ object.hashCode ^ resizePath.hashCode ^ deviceAssetId.hashCode ^ deviceId.hashCode;
|
return id.hashCode ^
|
||||||
|
object.hashCode ^
|
||||||
|
resizePath.hashCode ^
|
||||||
|
deviceAssetId.hashCode ^
|
||||||
|
deviceId.hashCode;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -25,7 +25,8 @@ class SearchPageState {
|
|||||||
searchTerm: searchTerm ?? this.searchTerm,
|
searchTerm: searchTerm ?? this.searchTerm,
|
||||||
isSearchEnabled: isSearchEnabled ?? this.isSearchEnabled,
|
isSearchEnabled: isSearchEnabled ?? this.isSearchEnabled,
|
||||||
searchSuggestion: searchSuggestion ?? this.searchSuggestion,
|
searchSuggestion: searchSuggestion ?? this.searchSuggestion,
|
||||||
userSuggestedSearchTerms: userSuggestedSearchTerms ?? this.userSuggestedSearchTerms,
|
userSuggestedSearchTerms:
|
||||||
|
userSuggestedSearchTerms ?? this.userSuggestedSearchTerms,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -43,13 +44,15 @@ class SearchPageState {
|
|||||||
searchTerm: map['searchTerm'] ?? '',
|
searchTerm: map['searchTerm'] ?? '',
|
||||||
isSearchEnabled: map['isSearchEnabled'] ?? false,
|
isSearchEnabled: map['isSearchEnabled'] ?? false,
|
||||||
searchSuggestion: List<String>.from(map['searchSuggestion']),
|
searchSuggestion: List<String>.from(map['searchSuggestion']),
|
||||||
userSuggestedSearchTerms: List<String>.from(map['userSuggestedSearchTerms']),
|
userSuggestedSearchTerms:
|
||||||
|
List<String>.from(map['userSuggestedSearchTerms']),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
String toJson() => json.encode(toMap());
|
String toJson() => json.encode(toMap());
|
||||||
|
|
||||||
factory SearchPageState.fromJson(String source) => SearchPageState.fromMap(json.decode(source));
|
factory SearchPageState.fromJson(String source) =>
|
||||||
|
SearchPageState.fromMap(json.decode(source));
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
|
@ -44,13 +44,15 @@ class SearchResultPageState {
|
|||||||
isLoading: map['isLoading'] ?? false,
|
isLoading: map['isLoading'] ?? false,
|
||||||
isSuccess: map['isSuccess'] ?? false,
|
isSuccess: map['isSuccess'] ?? false,
|
||||||
isError: map['isError'] ?? false,
|
isError: map['isError'] ?? false,
|
||||||
searchResult: List<ImmichAsset>.from(map['searchResult']?.map((x) => ImmichAsset.fromMap(x))),
|
searchResult: List<ImmichAsset>.from(
|
||||||
|
map['searchResult']?.map((x) => ImmichAsset.fromMap(x))),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
String toJson() => json.encode(toMap());
|
String toJson() => json.encode(toMap());
|
||||||
|
|
||||||
factory SearchResultPageState.fromJson(String source) => SearchResultPageState.fromMap(json.decode(source));
|
factory SearchResultPageState.fromJson(String source) =>
|
||||||
|
SearchResultPageState.fromMap(json.decode(source));
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
@ -71,6 +73,9 @@ class SearchResultPageState {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
int get hashCode {
|
int get hashCode {
|
||||||
return isLoading.hashCode ^ isSuccess.hashCode ^ isError.hashCode ^ searchResult.hashCode;
|
return isLoading.hashCode ^
|
||||||
|
isSuccess.hashCode ^
|
||||||
|
isError.hashCode ^
|
||||||
|
searchResult.hashCode;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,7 +4,9 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
|
|||||||
import 'package:immich_mobile/modules/search/providers/search_page_state.provider.dart';
|
import 'package:immich_mobile/modules/search/providers/search_page_state.provider.dart';
|
||||||
|
|
||||||
class SearchBar extends HookConsumerWidget with PreferredSizeWidget {
|
class SearchBar extends HookConsumerWidget with PreferredSizeWidget {
|
||||||
SearchBar({Key? key, required this.searchFocusNode, required this.onSubmitted}) : super(key: key);
|
SearchBar(
|
||||||
|
{Key? key, required this.searchFocusNode, required this.onSubmitted})
|
||||||
|
: super(key: key);
|
||||||
|
|
||||||
final FocusNode searchFocusNode;
|
final FocusNode searchFocusNode;
|
||||||
final Function(String) onSubmitted;
|
final Function(String) onSubmitted;
|
||||||
|
@ -3,16 +3,20 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
|
|||||||
import 'package:immich_mobile/modules/search/providers/search_page_state.provider.dart';
|
import 'package:immich_mobile/modules/search/providers/search_page_state.provider.dart';
|
||||||
|
|
||||||
class SearchSuggestionList extends ConsumerWidget {
|
class SearchSuggestionList extends ConsumerWidget {
|
||||||
const SearchSuggestionList({Key? key, required this.onSubmitted}) : super(key: key);
|
const SearchSuggestionList({Key? key, required this.onSubmitted})
|
||||||
|
: super(key: key);
|
||||||
|
|
||||||
final Function(String) onSubmitted;
|
final Function(String) onSubmitted;
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
final searchTerm = ref.watch(searchPageStateProvider).searchTerm;
|
final searchTerm = ref.watch(searchPageStateProvider).searchTerm;
|
||||||
final searchSuggestion = ref.watch(searchPageStateProvider).searchSuggestion;
|
final searchSuggestion =
|
||||||
|
ref.watch(searchPageStateProvider).searchSuggestion;
|
||||||
|
|
||||||
return Container(
|
return Container(
|
||||||
color: searchTerm.isEmpty ? Colors.black.withOpacity(0.5) : Theme.of(context).scaffoldBackgroundColor,
|
color: searchTerm.isEmpty
|
||||||
|
? Colors.black.withOpacity(0.5)
|
||||||
|
: Theme.of(context).scaffoldBackgroundColor,
|
||||||
child: CustomScrollView(
|
child: CustomScrollView(
|
||||||
slivers: [
|
slivers: [
|
||||||
SliverFillRemaining(
|
SliverFillRemaining(
|
||||||
|
@ -5,7 +5,11 @@ import 'package:immich_mobile/constants/hive_box.dart';
|
|||||||
import 'package:immich_mobile/utils/capitalize_first_letter.dart';
|
import 'package:immich_mobile/utils/capitalize_first_letter.dart';
|
||||||
|
|
||||||
class ThumbnailWithInfo extends StatelessWidget {
|
class ThumbnailWithInfo extends StatelessWidget {
|
||||||
const ThumbnailWithInfo({Key? key, required this.textInfo, required this.imageUrl, required this.onTap})
|
const ThumbnailWithInfo(
|
||||||
|
{Key? key,
|
||||||
|
required this.textInfo,
|
||||||
|
required this.imageUrl,
|
||||||
|
required this.onTap})
|
||||||
: super(key: key);
|
: super(key: key);
|
||||||
|
|
||||||
final String textInfo;
|
final String textInfo;
|
||||||
@ -39,7 +43,9 @@ class ThumbnailWithInfo extends StatelessWidget {
|
|||||||
height: 250,
|
height: 250,
|
||||||
fit: BoxFit.cover,
|
fit: BoxFit.cover,
|
||||||
imageUrl: imageUrl,
|
imageUrl: imageUrl,
|
||||||
httpHeaders: {"Authorization": "Bearer ${box.get(accessTokenKey)}"},
|
httpHeaders: {
|
||||||
|
"Authorization": "Bearer ${box.get(accessTokenKey)}"
|
||||||
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -36,16 +36,20 @@ class AlbumViewerPageState {
|
|||||||
|
|
||||||
String toJson() => json.encode(toMap());
|
String toJson() => json.encode(toMap());
|
||||||
|
|
||||||
factory AlbumViewerPageState.fromJson(String source) => AlbumViewerPageState.fromMap(json.decode(source));
|
factory AlbumViewerPageState.fromJson(String source) =>
|
||||||
|
AlbumViewerPageState.fromMap(json.decode(source));
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() => 'AlbumViewerPageState(isEditAlbum: $isEditAlbum, editTitleText: $editTitleText)';
|
String toString() =>
|
||||||
|
'AlbumViewerPageState(isEditAlbum: $isEditAlbum, editTitleText: $editTitleText)';
|
||||||
|
|
||||||
@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 AlbumViewerPageState && other.isEditAlbum == isEditAlbum && other.editTitleText == editTitleText;
|
return other is AlbumViewerPageState &&
|
||||||
|
other.isEditAlbum == isEditAlbum &&
|
||||||
|
other.editTitleText == editTitleText;
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -22,7 +22,8 @@ class AssetSelectionPageResult {
|
|||||||
}) {
|
}) {
|
||||||
return AssetSelectionPageResult(
|
return AssetSelectionPageResult(
|
||||||
selectedNewAsset: selectedNewAsset ?? this.selectedNewAsset,
|
selectedNewAsset: selectedNewAsset ?? this.selectedNewAsset,
|
||||||
selectedAdditionalAsset: selectedAdditionalAsset ?? this.selectedAdditionalAsset,
|
selectedAdditionalAsset:
|
||||||
|
selectedAdditionalAsset ?? this.selectedAdditionalAsset,
|
||||||
isAlbumExist: isAlbumExist ?? this.isAlbumExist,
|
isAlbumExist: isAlbumExist ?? this.isAlbumExist,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -30,8 +31,12 @@ class AssetSelectionPageResult {
|
|||||||
Map<String, dynamic> toMap() {
|
Map<String, dynamic> toMap() {
|
||||||
final result = <String, dynamic>{};
|
final result = <String, dynamic>{};
|
||||||
|
|
||||||
result.addAll({'selectedNewAsset': selectedNewAsset.map((x) => x.toMap()).toList()});
|
result.addAll(
|
||||||
result.addAll({'selectedAdditionalAsset': selectedAdditionalAsset.map((x) => x.toMap()).toList()});
|
{'selectedNewAsset': selectedNewAsset.map((x) => x.toMap()).toList()});
|
||||||
|
result.addAll({
|
||||||
|
'selectedAdditionalAsset':
|
||||||
|
selectedAdditionalAsset.map((x) => x.toMap()).toList()
|
||||||
|
});
|
||||||
result.addAll({'isAlbumExist': isAlbumExist});
|
result.addAll({'isAlbumExist': isAlbumExist});
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
@ -39,16 +44,18 @@ class AssetSelectionPageResult {
|
|||||||
|
|
||||||
factory AssetSelectionPageResult.fromMap(Map<String, dynamic> map) {
|
factory AssetSelectionPageResult.fromMap(Map<String, dynamic> map) {
|
||||||
return AssetSelectionPageResult(
|
return AssetSelectionPageResult(
|
||||||
selectedNewAsset: Set<ImmichAsset>.from(map['selectedNewAsset']?.map((x) => ImmichAsset.fromMap(x))),
|
selectedNewAsset: Set<ImmichAsset>.from(
|
||||||
selectedAdditionalAsset:
|
map['selectedNewAsset']?.map((x) => ImmichAsset.fromMap(x))),
|
||||||
Set<ImmichAsset>.from(map['selectedAdditionalAsset']?.map((x) => ImmichAsset.fromMap(x))),
|
selectedAdditionalAsset: Set<ImmichAsset>.from(
|
||||||
|
map['selectedAdditionalAsset']?.map((x) => ImmichAsset.fromMap(x))),
|
||||||
isAlbumExist: map['isAlbumExist'] ?? false,
|
isAlbumExist: map['isAlbumExist'] ?? false,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
String toJson() => json.encode(toMap());
|
String toJson() => json.encode(toMap());
|
||||||
|
|
||||||
factory AssetSelectionPageResult.fromJson(String source) => AssetSelectionPageResult.fromMap(json.decode(source));
|
factory AssetSelectionPageResult.fromJson(String source) =>
|
||||||
|
AssetSelectionPageResult.fromMap(json.decode(source));
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() =>
|
String toString() =>
|
||||||
@ -66,5 +73,8 @@ class AssetSelectionPageResult {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
int get hashCode => selectedNewAsset.hashCode ^ selectedAdditionalAsset.hashCode ^ isAlbumExist.hashCode;
|
int get hashCode =>
|
||||||
|
selectedNewAsset.hashCode ^
|
||||||
|
selectedAdditionalAsset.hashCode ^
|
||||||
|
isAlbumExist.hashCode;
|
||||||
}
|
}
|
||||||
|
@ -32,9 +32,12 @@ class AssetSelectionState {
|
|||||||
}) {
|
}) {
|
||||||
return AssetSelectionState(
|
return AssetSelectionState(
|
||||||
selectedMonths: selectedMonths ?? this.selectedMonths,
|
selectedMonths: selectedMonths ?? this.selectedMonths,
|
||||||
selectedNewAssetsForAlbum: selectedNewAssetsForAlbum ?? this.selectedNewAssetsForAlbum,
|
selectedNewAssetsForAlbum:
|
||||||
selectedAdditionalAssetsForAlbum: selectedAdditionalAssetsForAlbum ?? this.selectedAdditionalAssetsForAlbum,
|
selectedNewAssetsForAlbum ?? this.selectedNewAssetsForAlbum,
|
||||||
selectedAssetsInAlbumViewer: selectedAssetsInAlbumViewer ?? this.selectedAssetsInAlbumViewer,
|
selectedAdditionalAssetsForAlbum: selectedAdditionalAssetsForAlbum ??
|
||||||
|
this.selectedAdditionalAssetsForAlbum,
|
||||||
|
selectedAssetsInAlbumViewer:
|
||||||
|
selectedAssetsInAlbumViewer ?? this.selectedAssetsInAlbumViewer,
|
||||||
isMultiselectEnable: isMultiselectEnable ?? this.isMultiselectEnable,
|
isMultiselectEnable: isMultiselectEnable ?? this.isMultiselectEnable,
|
||||||
isAlbumExist: isAlbumExist ?? this.isAlbumExist,
|
isAlbumExist: isAlbumExist ?? this.isAlbumExist,
|
||||||
);
|
);
|
||||||
@ -44,10 +47,18 @@ class AssetSelectionState {
|
|||||||
final result = <String, dynamic>{};
|
final result = <String, dynamic>{};
|
||||||
|
|
||||||
result.addAll({'selectedMonths': selectedMonths.toList()});
|
result.addAll({'selectedMonths': selectedMonths.toList()});
|
||||||
result.addAll({'selectedNewAssetsForAlbum': selectedNewAssetsForAlbum.map((x) => x.toMap()).toList()});
|
result.addAll({
|
||||||
result
|
'selectedNewAssetsForAlbum':
|
||||||
.addAll({'selectedAdditionalAssetsForAlbum': selectedAdditionalAssetsForAlbum.map((x) => x.toMap()).toList()});
|
selectedNewAssetsForAlbum.map((x) => x.toMap()).toList()
|
||||||
result.addAll({'selectedAssetsInAlbumViewer': selectedAssetsInAlbumViewer.map((x) => x.toMap()).toList()});
|
});
|
||||||
|
result.addAll({
|
||||||
|
'selectedAdditionalAssetsForAlbum':
|
||||||
|
selectedAdditionalAssetsForAlbum.map((x) => x.toMap()).toList()
|
||||||
|
});
|
||||||
|
result.addAll({
|
||||||
|
'selectedAssetsInAlbumViewer':
|
||||||
|
selectedAssetsInAlbumViewer.map((x) => x.toMap()).toList()
|
||||||
|
});
|
||||||
result.addAll({'isMultiselectEnable': isMultiselectEnable});
|
result.addAll({'isMultiselectEnable': isMultiselectEnable});
|
||||||
result.addAll({'isAlbumExist': isAlbumExist});
|
result.addAll({'isAlbumExist': isAlbumExist});
|
||||||
|
|
||||||
@ -57,12 +68,14 @@ class AssetSelectionState {
|
|||||||
factory AssetSelectionState.fromMap(Map<String, dynamic> map) {
|
factory AssetSelectionState.fromMap(Map<String, dynamic> map) {
|
||||||
return AssetSelectionState(
|
return AssetSelectionState(
|
||||||
selectedMonths: Set<String>.from(map['selectedMonths']),
|
selectedMonths: Set<String>.from(map['selectedMonths']),
|
||||||
selectedNewAssetsForAlbum:
|
selectedNewAssetsForAlbum: Set<ImmichAsset>.from(
|
||||||
Set<ImmichAsset>.from(map['selectedNewAssetsForAlbum']?.map((x) => ImmichAsset.fromMap(x))),
|
map['selectedNewAssetsForAlbum']?.map((x) => ImmichAsset.fromMap(x))),
|
||||||
selectedAdditionalAssetsForAlbum:
|
selectedAdditionalAssetsForAlbum: Set<ImmichAsset>.from(
|
||||||
Set<ImmichAsset>.from(map['selectedAdditionalAssetsForAlbum']?.map((x) => ImmichAsset.fromMap(x))),
|
map['selectedAdditionalAssetsForAlbum']
|
||||||
selectedAssetsInAlbumViewer:
|
?.map((x) => ImmichAsset.fromMap(x))),
|
||||||
Set<ImmichAsset>.from(map['selectedAssetsInAlbumViewer']?.map((x) => ImmichAsset.fromMap(x))),
|
selectedAssetsInAlbumViewer: Set<ImmichAsset>.from(
|
||||||
|
map['selectedAssetsInAlbumViewer']
|
||||||
|
?.map((x) => ImmichAsset.fromMap(x))),
|
||||||
isMultiselectEnable: map['isMultiselectEnable'] ?? false,
|
isMultiselectEnable: map['isMultiselectEnable'] ?? false,
|
||||||
isAlbumExist: map['isAlbumExist'] ?? false,
|
isAlbumExist: map['isAlbumExist'] ?? false,
|
||||||
);
|
);
|
||||||
@ -70,7 +83,8 @@ class AssetSelectionState {
|
|||||||
|
|
||||||
String toJson() => json.encode(toMap());
|
String toJson() => json.encode(toMap());
|
||||||
|
|
||||||
factory AssetSelectionState.fromJson(String source) => AssetSelectionState.fromMap(json.decode(source));
|
factory AssetSelectionState.fromJson(String source) =>
|
||||||
|
AssetSelectionState.fromMap(json.decode(source));
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
@ -85,8 +99,10 @@ class AssetSelectionState {
|
|||||||
return other is AssetSelectionState &&
|
return other is AssetSelectionState &&
|
||||||
setEquals(other.selectedMonths, selectedMonths) &&
|
setEquals(other.selectedMonths, selectedMonths) &&
|
||||||
setEquals(other.selectedNewAssetsForAlbum, selectedNewAssetsForAlbum) &&
|
setEquals(other.selectedNewAssetsForAlbum, selectedNewAssetsForAlbum) &&
|
||||||
setEquals(other.selectedAdditionalAssetsForAlbum, selectedAdditionalAssetsForAlbum) &&
|
setEquals(other.selectedAdditionalAssetsForAlbum,
|
||||||
setEquals(other.selectedAssetsInAlbumViewer, selectedAssetsInAlbumViewer) &&
|
selectedAdditionalAssetsForAlbum) &&
|
||||||
|
setEquals(
|
||||||
|
other.selectedAssetsInAlbumViewer, selectedAssetsInAlbumViewer) &&
|
||||||
other.isMultiselectEnable == isMultiselectEnable &&
|
other.isMultiselectEnable == isMultiselectEnable &&
|
||||||
other.isAlbumExist == isAlbumExist;
|
other.isAlbumExist == isAlbumExist;
|
||||||
}
|
}
|
||||||
|
@ -38,7 +38,8 @@ class SharedAlbum {
|
|||||||
ownerId: ownerId ?? this.ownerId,
|
ownerId: ownerId ?? this.ownerId,
|
||||||
albumName: albumName ?? this.albumName,
|
albumName: albumName ?? this.albumName,
|
||||||
createdAt: createdAt ?? this.createdAt,
|
createdAt: createdAt ?? this.createdAt,
|
||||||
albumThumbnailAssetId: albumThumbnailAssetId ?? this.albumThumbnailAssetId,
|
albumThumbnailAssetId:
|
||||||
|
albumThumbnailAssetId ?? this.albumThumbnailAssetId,
|
||||||
sharedUsers: sharedUsers ?? this.sharedUsers,
|
sharedUsers: sharedUsers ?? this.sharedUsers,
|
||||||
assets: assets ?? this.assets,
|
assets: assets ?? this.assets,
|
||||||
);
|
);
|
||||||
@ -69,16 +70,19 @@ class SharedAlbum {
|
|||||||
albumName: map['albumName'] ?? '',
|
albumName: map['albumName'] ?? '',
|
||||||
createdAt: map['createdAt'] ?? '',
|
createdAt: map['createdAt'] ?? '',
|
||||||
albumThumbnailAssetId: map['albumThumbnailAssetId'],
|
albumThumbnailAssetId: map['albumThumbnailAssetId'],
|
||||||
sharedUsers: List<User>.from(map['sharedUsers']?.map((x) => User.fromMap(x))),
|
sharedUsers:
|
||||||
|
List<User>.from(map['sharedUsers']?.map((x) => User.fromMap(x))),
|
||||||
assets: map['assets'] != null
|
assets: map['assets'] != null
|
||||||
? List<ImmichAsset>.from(map['assets']?.map((x) => ImmichAsset.fromMap(x)))
|
? List<ImmichAsset>.from(
|
||||||
|
map['assets']?.map((x) => ImmichAsset.fromMap(x)))
|
||||||
: null,
|
: null,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
String toJson() => json.encode(toMap());
|
String toJson() => json.encode(toMap());
|
||||||
|
|
||||||
factory SharedAlbum.fromJson(String source) => SharedAlbum.fromMap(json.decode(source));
|
factory SharedAlbum.fromJson(String source) =>
|
||||||
|
SharedAlbum.fromMap(json.decode(source));
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
|
@ -12,4 +12,5 @@ class AlbumTitleNotifier extends StateNotifier<String> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
final albumTitleProvider = StateNotifierProvider<AlbumTitleNotifier, String>((ref) => AlbumTitleNotifier());
|
final albumTitleProvider = StateNotifierProvider<AlbumTitleNotifier, String>(
|
||||||
|
(ref) => AlbumTitleNotifier());
|
||||||
|
@ -18,35 +18,48 @@ class AssetSelectionNotifier extends StateNotifier<AssetSelectionState> {
|
|||||||
state = state.copyWith(isAlbumExist: isAlbumExist);
|
state = state.copyWith(isAlbumExist: isAlbumExist);
|
||||||
}
|
}
|
||||||
|
|
||||||
void removeAssetsInMonth(String removedMonth, List<ImmichAsset> assetsInMonth) {
|
void removeAssetsInMonth(
|
||||||
|
String removedMonth, List<ImmichAsset> assetsInMonth) {
|
||||||
Set<ImmichAsset> currentAssetList = state.selectedNewAssetsForAlbum;
|
Set<ImmichAsset> currentAssetList = state.selectedNewAssetsForAlbum;
|
||||||
Set<String> currentMonthList = state.selectedMonths;
|
Set<String> currentMonthList = state.selectedMonths;
|
||||||
|
|
||||||
currentMonthList.removeWhere((selectedMonth) => selectedMonth == removedMonth);
|
currentMonthList
|
||||||
|
.removeWhere((selectedMonth) => selectedMonth == removedMonth);
|
||||||
|
|
||||||
for (ImmichAsset asset in assetsInMonth) {
|
for (ImmichAsset asset in assetsInMonth) {
|
||||||
currentAssetList.removeWhere((e) => e.id == asset.id);
|
currentAssetList.removeWhere((e) => e.id == asset.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
state = state.copyWith(selectedNewAssetsForAlbum: currentAssetList, selectedMonths: currentMonthList);
|
state = state.copyWith(
|
||||||
|
selectedNewAssetsForAlbum: currentAssetList,
|
||||||
|
selectedMonths: currentMonthList);
|
||||||
}
|
}
|
||||||
|
|
||||||
void addAdditionalAssets(List<ImmichAsset> assets) {
|
void addAdditionalAssets(List<ImmichAsset> assets) {
|
||||||
state = state.copyWith(
|
state = state.copyWith(
|
||||||
selectedAdditionalAssetsForAlbum: {...state.selectedAdditionalAssetsForAlbum, ...assets},
|
selectedAdditionalAssetsForAlbum: {
|
||||||
|
...state.selectedAdditionalAssetsForAlbum,
|
||||||
|
...assets
|
||||||
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void addAllAssetsInMonth(String month, List<ImmichAsset> assetsInMonth) {
|
void addAllAssetsInMonth(String month, List<ImmichAsset> assetsInMonth) {
|
||||||
state = state.copyWith(
|
state = state.copyWith(
|
||||||
selectedMonths: {...state.selectedMonths, month},
|
selectedMonths: {...state.selectedMonths, month},
|
||||||
selectedNewAssetsForAlbum: {...state.selectedNewAssetsForAlbum, ...assetsInMonth},
|
selectedNewAssetsForAlbum: {
|
||||||
|
...state.selectedNewAssetsForAlbum,
|
||||||
|
...assetsInMonth
|
||||||
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void addNewAssets(List<ImmichAsset> assets) {
|
void addNewAssets(List<ImmichAsset> assets) {
|
||||||
state = state.copyWith(
|
state = state.copyWith(
|
||||||
selectedNewAssetsForAlbum: {...state.selectedNewAssetsForAlbum, ...assets},
|
selectedNewAssetsForAlbum: {
|
||||||
|
...state.selectedNewAssetsForAlbum,
|
||||||
|
...assets
|
||||||
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -93,7 +106,10 @@ class AssetSelectionNotifier extends StateNotifier<AssetSelectionState> {
|
|||||||
|
|
||||||
void addAssetsInAlbumViewer(List<ImmichAsset> assets) {
|
void addAssetsInAlbumViewer(List<ImmichAsset> assets) {
|
||||||
state = state.copyWith(
|
state = state.copyWith(
|
||||||
selectedAssetsInAlbumViewer: {...state.selectedAssetsInAlbumViewer, ...assets},
|
selectedAssetsInAlbumViewer: {
|
||||||
|
...state.selectedAssetsInAlbumViewer,
|
||||||
|
...assets
|
||||||
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -108,6 +124,7 @@ class AssetSelectionNotifier extends StateNotifier<AssetSelectionState> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
final assetSelectionProvider = StateNotifierProvider<AssetSelectionNotifier, AssetSelectionState>((ref) {
|
final assetSelectionProvider =
|
||||||
|
StateNotifierProvider<AssetSelectionNotifier, AssetSelectionState>((ref) {
|
||||||
return AssetSelectionNotifier();
|
return AssetSelectionNotifier();
|
||||||
});
|
});
|
||||||
|
@ -5,7 +5,11 @@ class AlbumActionOutlinedButton extends StatelessWidget {
|
|||||||
final String labelText;
|
final String labelText;
|
||||||
final IconData iconData;
|
final IconData iconData;
|
||||||
|
|
||||||
const AlbumActionOutlinedButton({Key? key, this.onPressed, required this.labelText, required this.iconData})
|
const AlbumActionOutlinedButton(
|
||||||
|
{Key? key,
|
||||||
|
this.onPressed,
|
||||||
|
required this.labelText,
|
||||||
|
required this.iconData})
|
||||||
: super(key: key);
|
: super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -26,7 +30,8 @@ class AlbumActionOutlinedButton extends StatelessWidget {
|
|||||||
icon: Icon(iconData, size: 15),
|
icon: Icon(iconData, size: 15),
|
||||||
label: Text(
|
label: Text(
|
||||||
labelText,
|
labelText,
|
||||||
style: const TextStyle(fontSize: 12, fontWeight: FontWeight.bold, color: Colors.black87),
|
style: const TextStyle(
|
||||||
|
fontSize: 12, fontWeight: FontWeight.bold, color: Colors.black87),
|
||||||
),
|
),
|
||||||
onPressed: onPressed,
|
onPressed: onPressed,
|
||||||
),
|
),
|
||||||
|
@ -29,7 +29,8 @@ class AlbumTitleTextField extends ConsumerWidget {
|
|||||||
ref.watch(albumTitleProvider.notifier).setAlbumTitle(v);
|
ref.watch(albumTitleProvider.notifier).setAlbumTitle(v);
|
||||||
},
|
},
|
||||||
focusNode: albumTitleTextFieldFocusNode,
|
focusNode: albumTitleTextFieldFocusNode,
|
||||||
style: TextStyle(fontSize: 28, color: Colors.grey[700], fontWeight: FontWeight.bold),
|
style: TextStyle(
|
||||||
|
fontSize: 28, color: Colors.grey[700], fontWeight: FontWeight.bold),
|
||||||
controller: albumTitleController,
|
controller: albumTitleController,
|
||||||
onTap: () {
|
onTap: () {
|
||||||
isAlbumTitleTextFieldFocus.value = true;
|
isAlbumTitleTextFieldFocus.value = true;
|
||||||
|
@ -7,11 +7,14 @@ import 'package:immich_mobile/modules/sharing/providers/album_viewer.provider.da
|
|||||||
class AlbumViewerEditableTitle extends HookConsumerWidget {
|
class AlbumViewerEditableTitle extends HookConsumerWidget {
|
||||||
final SharedAlbum albumInfo;
|
final SharedAlbum albumInfo;
|
||||||
final FocusNode titleFocusNode;
|
final FocusNode titleFocusNode;
|
||||||
const AlbumViewerEditableTitle({Key? key, required this.albumInfo, required this.titleFocusNode}) : super(key: key);
|
const AlbumViewerEditableTitle(
|
||||||
|
{Key? key, required this.albumInfo, required this.titleFocusNode})
|
||||||
|
: super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
final titleTextEditController = useTextEditingController(text: albumInfo.albumName);
|
final titleTextEditController =
|
||||||
|
useTextEditingController(text: albumInfo.albumName);
|
||||||
|
|
||||||
void onFocusModeChange() {
|
void onFocusModeChange() {
|
||||||
if (!titleFocusNode.hasFocus && titleTextEditController.text.isEmpty) {
|
if (!titleFocusNode.hasFocus && titleTextEditController.text.isEmpty) {
|
||||||
@ -40,7 +43,9 @@ class AlbumViewerEditableTitle extends HookConsumerWidget {
|
|||||||
onTap: () {
|
onTap: () {
|
||||||
FocusScope.of(context).requestFocus(titleFocusNode);
|
FocusScope.of(context).requestFocus(titleFocusNode);
|
||||||
|
|
||||||
ref.watch(albumViewerProvider.notifier).setEditTitleText(albumInfo.albumName);
|
ref
|
||||||
|
.watch(albumViewerProvider.notifier)
|
||||||
|
.setEditTitleText(albumInfo.albumName);
|
||||||
ref.watch(albumViewerProvider.notifier).enableEditAlbum();
|
ref.watch(albumViewerProvider.notifier).enableEditAlbum();
|
||||||
|
|
||||||
if (titleTextEditController.text == 'Untitled') {
|
if (titleTextEditController.text == 'Untitled') {
|
||||||
|
@ -22,8 +22,10 @@ class AlbumViewerThumbnail extends HookConsumerWidget {
|
|||||||
var thumbnailRequestUrl =
|
var thumbnailRequestUrl =
|
||||||
'${box.get(serverEndpointKey)}/asset/file?aid=${asset.deviceAssetId}&did=${asset.deviceId}&isThumb=true';
|
'${box.get(serverEndpointKey)}/asset/file?aid=${asset.deviceAssetId}&did=${asset.deviceId}&isThumb=true';
|
||||||
var deviceId = ref.watch(authenticationProvider).deviceId;
|
var deviceId = ref.watch(authenticationProvider).deviceId;
|
||||||
final selectedAssetsInAlbumViewer = ref.watch(assetSelectionProvider).selectedAssetsInAlbumViewer;
|
final selectedAssetsInAlbumViewer =
|
||||||
final isMultiSelectionEnable = ref.watch(assetSelectionProvider).isMultiselectEnable;
|
ref.watch(assetSelectionProvider).selectedAssetsInAlbumViewer;
|
||||||
|
final isMultiSelectionEnable =
|
||||||
|
ref.watch(assetSelectionProvider).isMultiselectEnable;
|
||||||
|
|
||||||
_viewAsset() {
|
_viewAsset() {
|
||||||
if (asset.type == 'IMAGE') {
|
if (asset.type == 'IMAGE') {
|
||||||
@ -39,7 +41,8 @@ class AlbumViewerThumbnail extends HookConsumerWidget {
|
|||||||
} else {
|
} else {
|
||||||
AutoRouter.of(context).push(
|
AutoRouter.of(context).push(
|
||||||
VideoViewerRoute(
|
VideoViewerRoute(
|
||||||
videoUrl: '${box.get(serverEndpointKey)}/asset/file?aid=${asset.deviceAssetId}&did=${asset.deviceId}',
|
videoUrl:
|
||||||
|
'${box.get(serverEndpointKey)}/asset/file?aid=${asset.deviceAssetId}&did=${asset.deviceId}',
|
||||||
asset: asset),
|
asset: asset),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -58,7 +61,9 @@ class AlbumViewerThumbnail extends HookConsumerWidget {
|
|||||||
|
|
||||||
_enableMultiSelection() {
|
_enableMultiSelection() {
|
||||||
ref.watch(assetSelectionProvider.notifier).enableMultiselection();
|
ref.watch(assetSelectionProvider.notifier).enableMultiselection();
|
||||||
ref.watch(assetSelectionProvider.notifier).addAssetsInAlbumViewer([asset]);
|
ref
|
||||||
|
.watch(assetSelectionProvider.notifier)
|
||||||
|
.addAssetsInAlbumViewer([asset]);
|
||||||
}
|
}
|
||||||
|
|
||||||
_disableMultiSelection() {
|
_disableMultiSelection() {
|
||||||
@ -96,7 +101,9 @@ class AlbumViewerThumbnail extends HookConsumerWidget {
|
|||||||
right: 10,
|
right: 10,
|
||||||
bottom: 5,
|
bottom: 5,
|
||||||
child: Icon(
|
child: Icon(
|
||||||
(deviceId != asset.deviceId) ? Icons.cloud_done_outlined : Icons.photo_library_rounded,
|
(deviceId != asset.deviceId)
|
||||||
|
? Icons.cloud_done_outlined
|
||||||
|
: Icons.photo_library_rounded,
|
||||||
color: Colors.white,
|
color: Colors.white,
|
||||||
size: 18,
|
size: 18,
|
||||||
),
|
),
|
||||||
@ -136,7 +143,8 @@ class AlbumViewerThumbnail extends HookConsumerWidget {
|
|||||||
imageUrl: thumbnailRequestUrl,
|
imageUrl: thumbnailRequestUrl,
|
||||||
httpHeaders: {"Authorization": "Bearer ${box.get(accessTokenKey)}"},
|
httpHeaders: {"Authorization": "Bearer ${box.get(accessTokenKey)}"},
|
||||||
fadeInDuration: const Duration(milliseconds: 250),
|
fadeInDuration: const Duration(milliseconds: 250),
|
||||||
progressIndicatorBuilder: (context, url, downloadProgress) => Transform.scale(
|
progressIndicatorBuilder: (context, url, downloadProgress) =>
|
||||||
|
Transform.scale(
|
||||||
scale: 0.2,
|
scale: 0.2,
|
||||||
child: CircularProgressIndicator(value: downloadProgress.progress),
|
child: CircularProgressIndicator(value: downloadProgress.progress),
|
||||||
),
|
),
|
||||||
@ -152,13 +160,17 @@ class AlbumViewerThumbnail extends HookConsumerWidget {
|
|||||||
|
|
||||||
_handleSelectionGesture() {
|
_handleSelectionGesture() {
|
||||||
if (selectedAssetsInAlbumViewer.contains(asset)) {
|
if (selectedAssetsInAlbumViewer.contains(asset)) {
|
||||||
ref.watch(assetSelectionProvider.notifier).removeAssetsInAlbumViewer([asset]);
|
ref
|
||||||
|
.watch(assetSelectionProvider.notifier)
|
||||||
|
.removeAssetsInAlbumViewer([asset]);
|
||||||
|
|
||||||
if (selectedAssetsInAlbumViewer.isEmpty) {
|
if (selectedAssetsInAlbumViewer.isEmpty) {
|
||||||
_disableMultiSelection();
|
_disableMultiSelection();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
ref.watch(assetSelectionProvider.notifier).addAssetsInAlbumViewer([asset]);
|
ref
|
||||||
|
.watch(assetSelectionProvider.notifier)
|
||||||
|
.addAssetsInAlbumViewer([asset]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5,7 +5,8 @@ import 'package:immich_mobile/shared/models/immich_asset.model.dart';
|
|||||||
|
|
||||||
class AssetGridByMonth extends HookConsumerWidget {
|
class AssetGridByMonth extends HookConsumerWidget {
|
||||||
final List<ImmichAsset> assetGroup;
|
final List<ImmichAsset> assetGroup;
|
||||||
const AssetGridByMonth({Key? key, required this.assetGroup}) : super(key: key);
|
const AssetGridByMonth({Key? key, required this.assetGroup})
|
||||||
|
: super(key: key);
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
return SliverGrid(
|
return SliverGrid(
|
||||||
|
@ -8,12 +8,15 @@ class MonthGroupTitle extends HookConsumerWidget {
|
|||||||
final String month;
|
final String month;
|
||||||
final List<ImmichAsset> assetGroup;
|
final List<ImmichAsset> assetGroup;
|
||||||
|
|
||||||
const MonthGroupTitle({Key? key, required this.month, required this.assetGroup}) : super(key: key);
|
const MonthGroupTitle(
|
||||||
|
{Key? key, required this.month, required this.assetGroup})
|
||||||
|
: super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
final selectedDateGroup = ref.watch(assetSelectionProvider).selectedMonths;
|
final selectedDateGroup = ref.watch(assetSelectionProvider).selectedMonths;
|
||||||
final selectedAssets = ref.watch(assetSelectionProvider).selectedNewAssetsForAlbum;
|
final selectedAssets =
|
||||||
|
ref.watch(assetSelectionProvider).selectedNewAssetsForAlbum;
|
||||||
final isAlbumExist = ref.watch(assetSelectionProvider).isAlbumExist;
|
final isAlbumExist = ref.watch(assetSelectionProvider).isAlbumExist;
|
||||||
|
|
||||||
_handleTitleIconClick() {
|
_handleTitleIconClick() {
|
||||||
@ -21,10 +24,16 @@ class MonthGroupTitle extends HookConsumerWidget {
|
|||||||
|
|
||||||
if (isAlbumExist) {
|
if (isAlbumExist) {
|
||||||
if (selectedDateGroup.contains(month)) {
|
if (selectedDateGroup.contains(month)) {
|
||||||
ref.watch(assetSelectionProvider.notifier).removeAssetsInMonth(month, []);
|
ref
|
||||||
ref.watch(assetSelectionProvider.notifier).removeSelectedAdditionalAssets(assetGroup);
|
.watch(assetSelectionProvider.notifier)
|
||||||
|
.removeAssetsInMonth(month, []);
|
||||||
|
ref
|
||||||
|
.watch(assetSelectionProvider.notifier)
|
||||||
|
.removeSelectedAdditionalAssets(assetGroup);
|
||||||
} else {
|
} else {
|
||||||
ref.watch(assetSelectionProvider.notifier).addAllAssetsInMonth(month, []);
|
ref
|
||||||
|
.watch(assetSelectionProvider.notifier)
|
||||||
|
.addAllAssetsInMonth(month, []);
|
||||||
|
|
||||||
// Deep clone assetGroup
|
// Deep clone assetGroup
|
||||||
var assetGroupWithNewItems = [...assetGroup];
|
var assetGroupWithNewItems = [...assetGroup];
|
||||||
@ -33,13 +42,19 @@ class MonthGroupTitle extends HookConsumerWidget {
|
|||||||
assetGroupWithNewItems.removeWhere((a) => a.id == selectedAsset.id);
|
assetGroupWithNewItems.removeWhere((a) => a.id == selectedAsset.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
ref.watch(assetSelectionProvider.notifier).addAdditionalAssets(assetGroupWithNewItems);
|
ref
|
||||||
|
.watch(assetSelectionProvider.notifier)
|
||||||
|
.addAdditionalAssets(assetGroupWithNewItems);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (selectedDateGroup.contains(month)) {
|
if (selectedDateGroup.contains(month)) {
|
||||||
ref.watch(assetSelectionProvider.notifier).removeAssetsInMonth(month, assetGroup);
|
ref
|
||||||
|
.watch(assetSelectionProvider.notifier)
|
||||||
|
.removeAssetsInMonth(month, assetGroup);
|
||||||
} else {
|
} else {
|
||||||
ref.watch(assetSelectionProvider.notifier).addAllAssetsInMonth(month, assetGroup);
|
ref
|
||||||
|
.watch(assetSelectionProvider.notifier)
|
||||||
|
.addAllAssetsInMonth(month, assetGroup);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -59,7 +74,8 @@ class MonthGroupTitle extends HookConsumerWidget {
|
|||||||
|
|
||||||
return SliverToBoxAdapter(
|
return SliverToBoxAdapter(
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.only(top: 29.0, bottom: 29.0, left: 14.0, right: 8.0),
|
padding: const EdgeInsets.only(
|
||||||
|
top: 29.0, bottom: 29.0, left: 14.0, right: 8.0),
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
GestureDetector(
|
GestureDetector(
|
||||||
|
@ -10,7 +10,8 @@ import 'package:immich_mobile/shared/models/immich_asset.model.dart';
|
|||||||
class SelectionThumbnailImage extends HookConsumerWidget {
|
class SelectionThumbnailImage extends HookConsumerWidget {
|
||||||
final ImmichAsset asset;
|
final ImmichAsset asset;
|
||||||
|
|
||||||
const SelectionThumbnailImage({Key? key, required this.asset}) : super(key: key);
|
const SelectionThumbnailImage({Key? key, required this.asset})
|
||||||
|
: super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
@ -18,8 +19,10 @@ class SelectionThumbnailImage extends HookConsumerWidget {
|
|||||||
var box = Hive.box(userInfoBox);
|
var box = Hive.box(userInfoBox);
|
||||||
var thumbnailRequestUrl =
|
var thumbnailRequestUrl =
|
||||||
'${box.get(serverEndpointKey)}/asset/file?aid=${asset.deviceAssetId}&did=${asset.deviceId}&isThumb=true';
|
'${box.get(serverEndpointKey)}/asset/file?aid=${asset.deviceAssetId}&did=${asset.deviceId}&isThumb=true';
|
||||||
var selectedAsset = ref.watch(assetSelectionProvider).selectedNewAssetsForAlbum;
|
var selectedAsset =
|
||||||
var newAssetsForAlbum = ref.watch(assetSelectionProvider).selectedAdditionalAssetsForAlbum;
|
ref.watch(assetSelectionProvider).selectedNewAssetsForAlbum;
|
||||||
|
var newAssetsForAlbum =
|
||||||
|
ref.watch(assetSelectionProvider).selectedAdditionalAssetsForAlbum;
|
||||||
var isAlbumExist = ref.watch(assetSelectionProvider).isAlbumExist;
|
var isAlbumExist = ref.watch(assetSelectionProvider).isAlbumExist;
|
||||||
|
|
||||||
Widget _buildSelectionIcon(ImmichAsset asset) {
|
Widget _buildSelectionIcon(ImmichAsset asset) {
|
||||||
@ -72,15 +75,21 @@ class SelectionThumbnailImage extends HookConsumerWidget {
|
|||||||
// Operation for existing album
|
// Operation for existing album
|
||||||
if (!selectedAsset.contains(asset)) {
|
if (!selectedAsset.contains(asset)) {
|
||||||
if (newAssetsForAlbum.contains(asset)) {
|
if (newAssetsForAlbum.contains(asset)) {
|
||||||
ref.watch(assetSelectionProvider.notifier).removeSelectedAdditionalAssets([asset]);
|
ref
|
||||||
|
.watch(assetSelectionProvider.notifier)
|
||||||
|
.removeSelectedAdditionalAssets([asset]);
|
||||||
} else {
|
} else {
|
||||||
ref.watch(assetSelectionProvider.notifier).addAdditionalAssets([asset]);
|
ref
|
||||||
|
.watch(assetSelectionProvider.notifier)
|
||||||
|
.addAdditionalAssets([asset]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Operation for new album
|
// Operation for new album
|
||||||
if (selectedAsset.contains(asset)) {
|
if (selectedAsset.contains(asset)) {
|
||||||
ref.watch(assetSelectionProvider.notifier).removeSelectedNewAssets([asset]);
|
ref
|
||||||
|
.watch(assetSelectionProvider.notifier)
|
||||||
|
.removeSelectedNewAssets([asset]);
|
||||||
} else {
|
} else {
|
||||||
ref.watch(assetSelectionProvider.notifier).addNewAssets([asset]);
|
ref.watch(assetSelectionProvider.notifier).addNewAssets([asset]);
|
||||||
}
|
}
|
||||||
@ -97,11 +106,15 @@ class SelectionThumbnailImage extends HookConsumerWidget {
|
|||||||
memCacheHeight: asset.type == 'IMAGE' ? 150 : 150,
|
memCacheHeight: asset.type == 'IMAGE' ? 150 : 150,
|
||||||
fit: BoxFit.cover,
|
fit: BoxFit.cover,
|
||||||
imageUrl: thumbnailRequestUrl,
|
imageUrl: thumbnailRequestUrl,
|
||||||
httpHeaders: {"Authorization": "Bearer ${box.get(accessTokenKey)}"},
|
httpHeaders: {
|
||||||
|
"Authorization": "Bearer ${box.get(accessTokenKey)}"
|
||||||
|
},
|
||||||
fadeInDuration: const Duration(milliseconds: 250),
|
fadeInDuration: const Duration(milliseconds: 250),
|
||||||
progressIndicatorBuilder: (context, url, downloadProgress) => Transform.scale(
|
progressIndicatorBuilder: (context, url, downloadProgress) =>
|
||||||
|
Transform.scale(
|
||||||
scale: 0.2,
|
scale: 0.2,
|
||||||
child: CircularProgressIndicator(value: downloadProgress.progress),
|
child:
|
||||||
|
CircularProgressIndicator(value: downloadProgress.progress),
|
||||||
),
|
),
|
||||||
errorWidget: (context, url, error) {
|
errorWidget: (context, url, error) {
|
||||||
return Icon(
|
return Icon(
|
||||||
|
@ -9,7 +9,8 @@ import 'package:immich_mobile/shared/models/immich_asset.model.dart';
|
|||||||
class SharedAlbumThumbnailImage extends HookConsumerWidget {
|
class SharedAlbumThumbnailImage extends HookConsumerWidget {
|
||||||
final ImmichAsset asset;
|
final ImmichAsset asset;
|
||||||
|
|
||||||
const SharedAlbumThumbnailImage({Key? key, required this.asset}) : super(key: key);
|
const SharedAlbumThumbnailImage({Key? key, required this.asset})
|
||||||
|
: super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
@ -34,9 +35,11 @@ class SharedAlbumThumbnailImage extends HookConsumerWidget {
|
|||||||
imageUrl: thumbnailRequestUrl,
|
imageUrl: thumbnailRequestUrl,
|
||||||
httpHeaders: {"Authorization": "Bearer ${box.get(accessTokenKey)}"},
|
httpHeaders: {"Authorization": "Bearer ${box.get(accessTokenKey)}"},
|
||||||
fadeInDuration: const Duration(milliseconds: 250),
|
fadeInDuration: const Duration(milliseconds: 250),
|
||||||
progressIndicatorBuilder: (context, url, downloadProgress) => Transform.scale(
|
progressIndicatorBuilder: (context, url, downloadProgress) =>
|
||||||
|
Transform.scale(
|
||||||
scale: 0.2,
|
scale: 0.2,
|
||||||
child: CircularProgressIndicator(value: downloadProgress.progress),
|
child:
|
||||||
|
CircularProgressIndicator(value: downloadProgress.progress),
|
||||||
),
|
),
|
||||||
errorWidget: (context, url, error) {
|
errorWidget: (context, url, error) {
|
||||||
return Icon(
|
return Icon(
|
||||||
|
@ -37,11 +37,13 @@ class SharingSliverAppBar extends StatelessWidget {
|
|||||||
padding: const EdgeInsets.only(right: 4.0),
|
padding: const EdgeInsets.only(right: 4.0),
|
||||||
child: TextButton.icon(
|
child: TextButton.icon(
|
||||||
style: ButtonStyle(
|
style: ButtonStyle(
|
||||||
backgroundColor: MaterialStateProperty.all(Theme.of(context).primaryColor.withAlpha(20)),
|
backgroundColor: MaterialStateProperty.all(
|
||||||
|
Theme.of(context).primaryColor.withAlpha(20)),
|
||||||
// foregroundColor: MaterialStateProperty.all(Colors.white),
|
// foregroundColor: MaterialStateProperty.all(Colors.white),
|
||||||
),
|
),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
AutoRouter.of(context).push(const CreateSharedAlbumRoute());
|
AutoRouter.of(context)
|
||||||
|
.push(const CreateSharedAlbumRoute());
|
||||||
},
|
},
|
||||||
icon: const Icon(
|
icon: const Icon(
|
||||||
Icons.photo_album_outlined,
|
Icons.photo_album_outlined,
|
||||||
@ -49,7 +51,8 @@ class SharingSliverAppBar extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
label: const Text(
|
label: const Text(
|
||||||
"Create shared album",
|
"Create shared album",
|
||||||
style: TextStyle(fontWeight: FontWeight.bold, fontSize: 12),
|
style:
|
||||||
|
TextStyle(fontWeight: FontWeight.bold, fontSize: 12),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -59,7 +62,8 @@ class SharingSliverAppBar extends StatelessWidget {
|
|||||||
padding: const EdgeInsets.only(left: 4.0),
|
padding: const EdgeInsets.only(left: 4.0),
|
||||||
child: TextButton.icon(
|
child: TextButton.icon(
|
||||||
style: ButtonStyle(
|
style: ButtonStyle(
|
||||||
backgroundColor: MaterialStateProperty.all(Theme.of(context).primaryColor.withAlpha(20)),
|
backgroundColor: MaterialStateProperty.all(
|
||||||
|
Theme.of(context).primaryColor.withAlpha(20)),
|
||||||
// foregroundColor: MaterialStateProperty.all(Colors.white),
|
// foregroundColor: MaterialStateProperty.all(Colors.white),
|
||||||
),
|
),
|
||||||
onPressed: null,
|
onPressed: null,
|
||||||
@ -69,7 +73,8 @@ class SharingSliverAppBar extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
label: const Text(
|
label: const Text(
|
||||||
"Share with partner",
|
"Share with partner",
|
||||||
style: TextStyle(fontWeight: FontWeight.bold, fontSize: 12),
|
style:
|
||||||
|
TextStyle(fontWeight: FontWeight.bold, fontSize: 12),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -10,15 +10,18 @@ import 'package:immich_mobile/shared/ui/immich_loading_indicator.dart';
|
|||||||
class SelectAdditionalUserForSharingPage extends HookConsumerWidget {
|
class SelectAdditionalUserForSharingPage extends HookConsumerWidget {
|
||||||
final SharedAlbum albumInfo;
|
final SharedAlbum albumInfo;
|
||||||
|
|
||||||
const SelectAdditionalUserForSharingPage({Key? key, required this.albumInfo}) : super(key: key);
|
const SelectAdditionalUserForSharingPage({Key? key, required this.albumInfo})
|
||||||
|
: super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
AsyncValue<List<User>> suggestedShareUsers = ref.watch(suggestedSharedUsersProvider);
|
AsyncValue<List<User>> suggestedShareUsers =
|
||||||
|
ref.watch(suggestedSharedUsersProvider);
|
||||||
final sharedUsersList = useState<Set<User>>({});
|
final sharedUsersList = useState<Set<User>>({});
|
||||||
|
|
||||||
_addNewUsersHandler() {
|
_addNewUsersHandler() {
|
||||||
AutoRouter.of(context).pop(sharedUsersList.value.map((e) => e.id).toList());
|
AutoRouter.of(context)
|
||||||
|
.pop(sharedUsersList.value.map((e) => e.id).toList());
|
||||||
}
|
}
|
||||||
|
|
||||||
_buildTileIcon(User user) {
|
_buildTileIcon(User user) {
|
||||||
@ -32,7 +35,8 @@ class SelectAdditionalUserForSharingPage extends HookConsumerWidget {
|
|||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
return CircleAvatar(
|
return CircleAvatar(
|
||||||
backgroundImage: const AssetImage('assets/immich-logo-no-outline.png'),
|
backgroundImage:
|
||||||
|
const AssetImage('assets/immich-logo-no-outline.png'),
|
||||||
backgroundColor: Theme.of(context).primaryColor.withAlpha(50),
|
backgroundColor: Theme.of(context).primaryColor.withAlpha(50),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -49,7 +53,10 @@ class SelectAdditionalUserForSharingPage extends HookConsumerWidget {
|
|||||||
backgroundColor: Theme.of(context).primaryColor.withOpacity(0.15),
|
backgroundColor: Theme.of(context).primaryColor.withOpacity(0.15),
|
||||||
label: Text(
|
label: Text(
|
||||||
user.email,
|
user.email,
|
||||||
style: const TextStyle(fontSize: 12, color: Colors.black87, fontWeight: FontWeight.bold),
|
style: const TextStyle(
|
||||||
|
fontSize: 12,
|
||||||
|
color: Colors.black87,
|
||||||
|
fontWeight: FontWeight.bold),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -65,7 +72,10 @@ class SelectAdditionalUserForSharingPage extends HookConsumerWidget {
|
|||||||
padding: EdgeInsets.all(16.0),
|
padding: EdgeInsets.all(16.0),
|
||||||
child: Text(
|
child: Text(
|
||||||
'Suggestions',
|
'Suggestions',
|
||||||
style: TextStyle(fontSize: 14, color: Colors.grey, fontWeight: FontWeight.bold),
|
style: TextStyle(
|
||||||
|
fontSize: 14,
|
||||||
|
color: Colors.grey,
|
||||||
|
fontWeight: FontWeight.bold),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
ListView.builder(
|
ListView.builder(
|
||||||
@ -75,14 +85,20 @@ class SelectAdditionalUserForSharingPage extends HookConsumerWidget {
|
|||||||
leading: _buildTileIcon(users[index]),
|
leading: _buildTileIcon(users[index]),
|
||||||
title: Text(
|
title: Text(
|
||||||
users[index].email,
|
users[index].email,
|
||||||
style: const TextStyle(fontSize: 14, fontWeight: FontWeight.bold),
|
style: const TextStyle(
|
||||||
|
fontSize: 14, fontWeight: FontWeight.bold),
|
||||||
),
|
),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
if (sharedUsersList.value.contains(users[index])) {
|
if (sharedUsersList.value.contains(users[index])) {
|
||||||
sharedUsersList.value =
|
sharedUsersList.value = sharedUsersList.value
|
||||||
sharedUsersList.value.where((selectedUser) => selectedUser.id != users[index].id).toSet();
|
.where((selectedUser) =>
|
||||||
|
selectedUser.id != users[index].id)
|
||||||
|
.toSet();
|
||||||
} else {
|
} else {
|
||||||
sharedUsersList.value = {...sharedUsersList.value, users[index]};
|
sharedUsersList.value = {
|
||||||
|
...sharedUsersList.value,
|
||||||
|
users[index]
|
||||||
|
};
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@ -109,7 +125,8 @@ class SelectAdditionalUserForSharingPage extends HookConsumerWidget {
|
|||||||
),
|
),
|
||||||
actions: [
|
actions: [
|
||||||
TextButton(
|
TextButton(
|
||||||
onPressed: sharedUsersList.value.isEmpty ? null : _addNewUsersHandler,
|
onPressed:
|
||||||
|
sharedUsersList.value.isEmpty ? null : _addNewUsersHandler,
|
||||||
child: const Text(
|
child: const Text(
|
||||||
"Add",
|
"Add",
|
||||||
style: TextStyle(fontSize: 14, fontWeight: FontWeight.bold),
|
style: TextStyle(fontSize: 14, fontWeight: FontWeight.bold),
|
||||||
@ -120,7 +137,8 @@ class SelectAdditionalUserForSharingPage extends HookConsumerWidget {
|
|||||||
body: suggestedShareUsers.when(
|
body: suggestedShareUsers.when(
|
||||||
data: (users) {
|
data: (users) {
|
||||||
for (var sharedUsers in albumInfo.sharedUsers) {
|
for (var sharedUsers in albumInfo.sharedUsers) {
|
||||||
users.removeWhere((u) => u.id == sharedUsers.id || u.id == albumInfo.ownerId);
|
users.removeWhere(
|
||||||
|
(u) => u.id == sharedUsers.id || u.id == albumInfo.ownerId);
|
||||||
}
|
}
|
||||||
|
|
||||||
return _buildUserList(users);
|
return _buildUserList(users);
|
||||||
|
@ -29,12 +29,14 @@ class SharingPage extends HookConsumerWidget {
|
|||||||
return SliverList(
|
return SliverList(
|
||||||
delegate: SliverChildBuilderDelegate(
|
delegate: SliverChildBuilderDelegate(
|
||||||
(BuildContext context, int index) {
|
(BuildContext context, int index) {
|
||||||
String thumbnailUrl = sharedAlbums[index].albumThumbnailAssetId != null
|
String thumbnailUrl = sharedAlbums[index].albumThumbnailAssetId !=
|
||||||
|
null
|
||||||
? "$thumbnailRequestUrl/${sharedAlbums[index].albumThumbnailAssetId}"
|
? "$thumbnailRequestUrl/${sharedAlbums[index].albumThumbnailAssetId}"
|
||||||
: "https://images.unsplash.com/photo-1612178537253-bccd437b730e?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxzZWFyY2h8NXx8Ymxhbmt8ZW58MHx8MHx8&auto=format&fit=crop&w=700&q=60";
|
: "https://images.unsplash.com/photo-1612178537253-bccd437b730e?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxzZWFyY2h8NXx8Ymxhbmt8ZW58MHx8MHx8&auto=format&fit=crop&w=700&q=60";
|
||||||
|
|
||||||
return ListTile(
|
return ListTile(
|
||||||
contentPadding: const EdgeInsets.symmetric(vertical: 12, horizontal: 12),
|
contentPadding:
|
||||||
|
const EdgeInsets.symmetric(vertical: 12, horizontal: 12),
|
||||||
leading: ClipRRect(
|
leading: ClipRRect(
|
||||||
borderRadius: BorderRadius.circular(8),
|
borderRadius: BorderRadius.circular(8),
|
||||||
child: FadeInImage(
|
child: FadeInImage(
|
||||||
@ -44,7 +46,9 @@ class SharingPage extends HookConsumerWidget {
|
|||||||
placeholder: MemoryImage(kTransparentImage),
|
placeholder: MemoryImage(kTransparentImage),
|
||||||
image: NetworkImage(
|
image: NetworkImage(
|
||||||
thumbnailUrl,
|
thumbnailUrl,
|
||||||
headers: {"Authorization": "Bearer ${box.get(accessTokenKey)}"},
|
headers: {
|
||||||
|
"Authorization": "Bearer ${box.get(accessTokenKey)}"
|
||||||
|
},
|
||||||
),
|
),
|
||||||
fadeInDuration: const Duration(milliseconds: 200),
|
fadeInDuration: const Duration(milliseconds: 200),
|
||||||
fadeOutDuration: const Duration(milliseconds: 200),
|
fadeOutDuration: const Duration(milliseconds: 200),
|
||||||
@ -54,10 +58,14 @@ class SharingPage extends HookConsumerWidget {
|
|||||||
sharedAlbums[index].albumName,
|
sharedAlbums[index].albumName,
|
||||||
maxLines: 1,
|
maxLines: 1,
|
||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold, color: Colors.grey.shade800),
|
style: TextStyle(
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
color: Colors.grey.shade800),
|
||||||
),
|
),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
AutoRouter.of(context).push(AlbumViewerRoute(albumId: sharedAlbums[index].id));
|
AutoRouter.of(context)
|
||||||
|
.push(AlbumViewerRoute(albumId: sharedAlbums[index].id));
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
@ -134,7 +142,9 @@ class SharingPage extends HookConsumerWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
sharedAlbums.isNotEmpty ? _buildAlbumList() : _buildEmptyListIndication()
|
sharedAlbums.isNotEmpty
|
||||||
|
? _buildAlbumList()
|
||||||
|
: _buildEmptyListIndication()
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
@ -149,7 +149,8 @@ class ImmichExif {
|
|||||||
|
|
||||||
String toJson() => json.encode(toMap());
|
String toJson() => json.encode(toMap());
|
||||||
|
|
||||||
factory ImmichExif.fromJson(String source) => ImmichExif.fromMap(json.decode(source));
|
factory ImmichExif.fromJson(String source) =>
|
||||||
|
ImmichExif.fromMap(json.decode(source));
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
|
@ -95,7 +95,8 @@ class ImmichAsset extends Equatable {
|
|||||||
|
|
||||||
String toJson() => json.encode(toMap());
|
String toJson() => json.encode(toMap());
|
||||||
|
|
||||||
factory ImmichAsset.fromJson(String source) => ImmichAsset.fromMap(json.decode(source));
|
factory ImmichAsset.fromJson(String source) =>
|
||||||
|
ImmichAsset.fromMap(json.decode(source));
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
|
@ -85,13 +85,15 @@ class ImmichAssetWithExif {
|
|||||||
originalPath: map['originalPath'] ?? '',
|
originalPath: map['originalPath'] ?? '',
|
||||||
isFavorite: map['isFavorite'] ?? false,
|
isFavorite: map['isFavorite'] ?? false,
|
||||||
duration: map['duration'],
|
duration: map['duration'],
|
||||||
exifInfo: map['exifInfo'] != null ? ImmichExif.fromMap(map['exifInfo']) : null,
|
exifInfo:
|
||||||
|
map['exifInfo'] != null ? ImmichExif.fromMap(map['exifInfo']) : null,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
String toJson() => json.encode(toMap());
|
String toJson() => json.encode(toMap());
|
||||||
|
|
||||||
factory ImmichAssetWithExif.fromJson(String source) => ImmichAssetWithExif.fromMap(json.decode(source));
|
factory ImmichAssetWithExif.fromJson(String source) =>
|
||||||
|
ImmichAssetWithExif.fromMap(json.decode(source));
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
|
@ -34,16 +34,20 @@ class MapboxInfo {
|
|||||||
|
|
||||||
String toJson() => json.encode(toMap());
|
String toJson() => json.encode(toMap());
|
||||||
|
|
||||||
factory MapboxInfo.fromJson(String source) => MapboxInfo.fromMap(json.decode(source));
|
factory MapboxInfo.fromJson(String source) =>
|
||||||
|
MapboxInfo.fromMap(json.decode(source));
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() => 'MapboxInfo(isEnable: $isEnable, mapboxSecret: $mapboxSecret)';
|
String toString() =>
|
||||||
|
'MapboxInfo(isEnable: $isEnable, mapboxSecret: $mapboxSecret)';
|
||||||
|
|
||||||
@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 MapboxInfo && other.isEnable == isEnable && other.mapboxSecret == mapboxSecret;
|
return other is MapboxInfo &&
|
||||||
|
other.isEnable == isEnable &&
|
||||||
|
other.mapboxSecret == mapboxSecret;
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -64,7 +64,8 @@ class ServerInfo {
|
|||||||
|
|
||||||
String toJson() => json.encode(toMap());
|
String toJson() => json.encode(toMap());
|
||||||
|
|
||||||
factory ServerInfo.fromJson(String source) => ServerInfo.fromMap(json.decode(source));
|
factory ServerInfo.fromJson(String source) =>
|
||||||
|
ServerInfo.fromMap(json.decode(source));
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
|
@ -26,7 +26,8 @@ class ServerInfoState {
|
|||||||
mapboxInfo: mapboxInfo ?? this.mapboxInfo,
|
mapboxInfo: mapboxInfo ?? this.mapboxInfo,
|
||||||
serverVersion: serverVersion ?? this.serverVersion,
|
serverVersion: serverVersion ?? this.serverVersion,
|
||||||
isVersionMismatch: isVersionMismatch ?? this.isVersionMismatch,
|
isVersionMismatch: isVersionMismatch ?? this.isVersionMismatch,
|
||||||
versionMismatchErrorMessage: versionMismatchErrorMessage ?? this.versionMismatchErrorMessage,
|
versionMismatchErrorMessage:
|
||||||
|
versionMismatchErrorMessage ?? this.versionMismatchErrorMessage,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -50,7 +51,8 @@ class ServerInfoState {
|
|||||||
|
|
||||||
String toJson() => json.encode(toMap());
|
String toJson() => json.encode(toMap());
|
||||||
|
|
||||||
factory ServerInfoState.fromJson(String source) => ServerInfoState.fromMap(json.decode(source));
|
factory ServerInfoState.fromJson(String source) =>
|
||||||
|
ServerInfoState.fromMap(json.decode(source));
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
|
@ -47,7 +47,8 @@ class ServerVersion {
|
|||||||
|
|
||||||
String toJson() => json.encode(toMap());
|
String toJson() => json.encode(toMap());
|
||||||
|
|
||||||
factory ServerVersion.fromJson(String source) => ServerVersion.fromMap(json.decode(source));
|
factory ServerVersion.fromJson(String source) =>
|
||||||
|
ServerVersion.fromMap(json.decode(source));
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
|
@ -36,16 +36,20 @@ class UploadProfileImageResponse {
|
|||||||
|
|
||||||
String toJson() => json.encode(toMap());
|
String toJson() => json.encode(toMap());
|
||||||
|
|
||||||
factory UploadProfileImageResponse.fromJson(String source) => UploadProfileImageResponse.fromMap(json.decode(source));
|
factory UploadProfileImageResponse.fromJson(String source) =>
|
||||||
|
UploadProfileImageResponse.fromMap(json.decode(source));
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() => 'UploadProfileImageReponse(userId: $userId, profileImagePath: $profileImagePath)';
|
String toString() =>
|
||||||
|
'UploadProfileImageReponse(userId: $userId, profileImagePath: $profileImagePath)';
|
||||||
|
|
||||||
@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 UploadProfileImageResponse && other.userId == userId && other.profileImagePath == profileImagePath;
|
return other is UploadProfileImageResponse &&
|
||||||
|
other.userId == userId &&
|
||||||
|
other.profileImagePath == profileImagePath;
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -56,18 +56,19 @@ class User {
|
|||||||
factory User.fromJson(String source) => User.fromMap(json.decode(source));
|
factory User.fromJson(String source) => User.fromMap(json.decode(source));
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() => 'UserInfo(id: $id, email: $email, createdAt: $createdAt)';
|
String toString() =>
|
||||||
|
'UserInfo(id: $id, email: $email, createdAt: $createdAt)';
|
||||||
|
|
||||||
@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 User &&
|
return other is User &&
|
||||||
other.id == id &&
|
other.id == id &&
|
||||||
other.email == email &&
|
other.email == email &&
|
||||||
other.createdAt == createdAt &&
|
other.createdAt == createdAt &&
|
||||||
other.firstName == firstName &&
|
other.firstName == firstName &&
|
||||||
other.lastName == lastName;
|
other.lastName == lastName;
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -45,7 +45,7 @@ class AssetNotifier extends StateNotifier<List<ImmichAsset>> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await PhotoManager.editor.deleteWithIds(deleteIdList);
|
await PhotoManager.editor.deleteWithIds(deleteIdList);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
@ -29,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
|
||||||
@ -43,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");
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -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);
|
||||||
});
|
});
|
||||||
|
@ -2,7 +2,8 @@ import 'dart:math';
|
|||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
class ImmichSliverPersistentAppBarDelegate extends SliverPersistentHeaderDelegate {
|
class ImmichSliverPersistentAppBarDelegate
|
||||||
|
extends SliverPersistentHeaderDelegate {
|
||||||
final double minHeight;
|
final double minHeight;
|
||||||
final double maxHeight;
|
final double maxHeight;
|
||||||
final Widget child;
|
final Widget child;
|
||||||
@ -20,12 +21,15 @@ class ImmichSliverPersistentAppBarDelegate extends SliverPersistentHeaderDelegat
|
|||||||
double get maxExtent => max(maxHeight, minHeight);
|
double get maxExtent => max(maxHeight, minHeight);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, double shrinkOffset, bool overlapsContent) {
|
Widget build(
|
||||||
|
BuildContext context, double shrinkOffset, bool overlapsContent) {
|
||||||
return SizedBox.expand(child: child);
|
return SizedBox.expand(child: child);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool shouldRebuild(ImmichSliverPersistentAppBarDelegate oldDelegate) {
|
bool shouldRebuild(ImmichSliverPersistentAppBarDelegate oldDelegate) {
|
||||||
return maxHeight != oldDelegate.maxHeight || minHeight != oldDelegate.minHeight || child != oldDelegate.child;
|
return maxHeight != oldDelegate.maxHeight ||
|
||||||
|
minHeight != oldDelegate.minHeight ||
|
||||||
|
child != oldDelegate.child;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -15,12 +15,14 @@ class SplashScreenPage extends HookConsumerWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
HiveSavedLoginInfo? loginInfo = Hive.box<HiveSavedLoginInfo>(hiveLoginInfoBox).get(savedLoginInfoKey);
|
HiveSavedLoginInfo? loginInfo =
|
||||||
|
Hive.box<HiveSavedLoginInfo>(hiveLoginInfoBox).get(savedLoginInfoKey);
|
||||||
|
|
||||||
void performLoggingIn() async {
|
void performLoggingIn() async {
|
||||||
var isAuthenticated = await ref
|
var isAuthenticated = await ref
|
||||||
.read(authenticationProvider.notifier)
|
.read(authenticationProvider.notifier)
|
||||||
.login(loginInfo!.email, loginInfo.password, loginInfo.serverUrl, true);
|
.login(
|
||||||
|
loginInfo!.email, loginInfo.password, loginInfo.serverUrl, true);
|
||||||
|
|
||||||
if (isAuthenticated) {
|
if (isAuthenticated) {
|
||||||
// Resume backup (if enable) then navigate
|
// Resume backup (if enable) then navigate
|
||||||
|
@ -9,7 +9,8 @@ class TabControllerPage extends ConsumerWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
var isMultiSelectEnable = ref.watch(homePageStateProvider).isMultiSelectEnable;
|
var isMultiSelectEnable =
|
||||||
|
ref.watch(homePageStateProvider).isMultiSelectEnable;
|
||||||
|
|
||||||
return AutoTabsRouter(
|
return AutoTabsRouter(
|
||||||
routes: [
|
routes: [
|
||||||
@ -32,16 +33,21 @@ class TabControllerPage extends ConsumerWidget {
|
|||||||
bottomNavigationBar: isMultiSelectEnable
|
bottomNavigationBar: isMultiSelectEnable
|
||||||
? null
|
? null
|
||||||
: BottomNavigationBar(
|
: BottomNavigationBar(
|
||||||
selectedLabelStyle: const TextStyle(fontSize: 15, fontWeight: FontWeight.w600),
|
selectedLabelStyle: const TextStyle(
|
||||||
unselectedLabelStyle: const TextStyle(fontSize: 15, fontWeight: FontWeight.w600),
|
fontSize: 15, fontWeight: FontWeight.w600),
|
||||||
|
unselectedLabelStyle: const TextStyle(
|
||||||
|
fontSize: 15, fontWeight: FontWeight.w600),
|
||||||
currentIndex: tabsRouter.activeIndex,
|
currentIndex: tabsRouter.activeIndex,
|
||||||
onTap: (index) {
|
onTap: (index) {
|
||||||
tabsRouter.setActiveIndex(index);
|
tabsRouter.setActiveIndex(index);
|
||||||
},
|
},
|
||||||
items: const [
|
items: const [
|
||||||
BottomNavigationBarItem(label: 'Photos', icon: Icon(Icons.photo)),
|
BottomNavigationBarItem(
|
||||||
BottomNavigationBarItem(label: 'Search', icon: Icon(Icons.search)),
|
label: 'Photos', icon: Icon(Icons.photo)),
|
||||||
BottomNavigationBarItem(label: 'Sharing', icon: Icon(Icons.group_outlined)),
|
BottomNavigationBarItem(
|
||||||
|
label: 'Search', icon: Icon(Icons.search)),
|
||||||
|
BottomNavigationBarItem(
|
||||||
|
label: 'Sharing', icon: Icon(Icons.group_outlined)),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
Loading…
Reference in New Issue
Block a user