mirror of
https://github.com/immich-app/immich.git
synced 2024-12-25 10:43:13 +02:00
refactor(mobile): migrate all Hive boxes to Isar database (#2036)
This commit is contained in:
parent
0616a66b05
commit
eccde8fa07
@ -2,7 +2,6 @@ import 'dart:async';
|
|||||||
|
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
import 'package:hive/hive.dart';
|
|
||||||
import 'package:immich_mobile/shared/models/store.dart';
|
import 'package:immich_mobile/shared/models/store.dart';
|
||||||
import 'package:integration_test/integration_test.dart';
|
import 'package:integration_test/integration_test.dart';
|
||||||
import 'package:isar/isar.dart';
|
import 'package:isar/isar.dart';
|
||||||
@ -35,9 +34,7 @@ class ImmichTestHelper {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static Future<void> loadApp(WidgetTester tester) async {
|
static Future<void> loadApp(WidgetTester tester) async {
|
||||||
// Clear all data from Hive
|
await EasyLocalization.ensureInitialized();
|
||||||
await Hive.deleteFromDisk();
|
|
||||||
await app.openBoxes();
|
|
||||||
// Clear all data from Isar (reuse existing instance if available)
|
// Clear all data from Isar (reuse existing instance if available)
|
||||||
final db = Isar.getInstance() ?? await app.loadDb();
|
final db = Isar.getInstance() ?? await app.loadDb();
|
||||||
await Store.clear();
|
await Store.clear();
|
||||||
@ -65,12 +62,13 @@ void immichWidgetTest(
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<void> pumpUntilFound(
|
Future<void> pumpUntilFound(
|
||||||
WidgetTester tester,
|
WidgetTester tester,
|
||||||
Finder finder, {
|
Finder finder, {
|
||||||
Duration timeout = const Duration(seconds: 120),
|
Duration timeout = const Duration(seconds: 120),
|
||||||
}) async {
|
}) async {
|
||||||
bool found = false;
|
bool found = false;
|
||||||
final timer = Timer(timeout, () => throw TimeoutException("Pump until has timed out"));
|
final timer =
|
||||||
|
Timer(timeout, () => throw TimeoutException("Pump until has timed out"));
|
||||||
while (found != true) {
|
while (found != true) {
|
||||||
await tester.pump();
|
await tester.pump();
|
||||||
found = tester.any(finder);
|
found = tester.any(finder);
|
||||||
|
@ -25,6 +25,7 @@ import 'package:immich_mobile/shared/models/album.dart';
|
|||||||
import 'package:immich_mobile/shared/models/asset.dart';
|
import 'package:immich_mobile/shared/models/asset.dart';
|
||||||
import 'package:immich_mobile/shared/models/exif_info.dart';
|
import 'package:immich_mobile/shared/models/exif_info.dart';
|
||||||
import 'package:immich_mobile/shared/models/immich_logger_message.model.dart';
|
import 'package:immich_mobile/shared/models/immich_logger_message.model.dart';
|
||||||
|
import 'package:immich_mobile/shared/models/logger_message.model.dart';
|
||||||
import 'package:immich_mobile/shared/models/store.dart';
|
import 'package:immich_mobile/shared/models/store.dart';
|
||||||
import 'package:immich_mobile/shared/models/user.dart';
|
import 'package:immich_mobile/shared/models/user.dart';
|
||||||
import 'package:immich_mobile/shared/providers/app_state.provider.dart';
|
import 'package:immich_mobile/shared/providers/app_state.provider.dart';
|
||||||
@ -42,35 +43,23 @@ import 'package:isar/isar.dart';
|
|||||||
import 'package:logging/logging.dart';
|
import 'package:logging/logging.dart';
|
||||||
import 'package:path_provider/path_provider.dart';
|
import 'package:path_provider/path_provider.dart';
|
||||||
import 'package:permission_handler/permission_handler.dart';
|
import 'package:permission_handler/permission_handler.dart';
|
||||||
import 'constants/hive_box.dart';
|
|
||||||
|
|
||||||
void main() async {
|
void main() async {
|
||||||
await initApp();
|
WidgetsFlutterBinding.ensureInitialized();
|
||||||
final db = await loadDb();
|
final db = await loadDb();
|
||||||
|
await initApp();
|
||||||
await migrateHiveToStoreIfNecessary();
|
await migrateHiveToStoreIfNecessary();
|
||||||
await migrateJsonCacheIfNecessary();
|
await migrateJsonCacheIfNecessary();
|
||||||
runApp(getMainWidget(db));
|
runApp(getMainWidget(db));
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> openBoxes() async {
|
|
||||||
await Future.wait([
|
|
||||||
Hive.openBox<ImmichLoggerMessage>(immichLoggerBox),
|
|
||||||
Hive.openBox(userInfoBox),
|
|
||||||
Hive.openBox<HiveSavedLoginInfo>(hiveLoginInfoBox),
|
|
||||||
Hive.openBox(hiveGithubReleaseInfoBox),
|
|
||||||
Hive.openBox(userSettingInfoBox),
|
|
||||||
EasyLocalization.ensureInitialized(),
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> initApp() async {
|
Future<void> initApp() async {
|
||||||
await Hive.initFlutter();
|
await Hive.initFlutter();
|
||||||
Hive.registerAdapter(HiveSavedLoginInfoAdapter());
|
Hive.registerAdapter(HiveSavedLoginInfoAdapter());
|
||||||
Hive.registerAdapter(HiveBackupAlbumsAdapter());
|
Hive.registerAdapter(HiveBackupAlbumsAdapter());
|
||||||
Hive.registerAdapter(HiveDuplicatedAssetsAdapter());
|
Hive.registerAdapter(HiveDuplicatedAssetsAdapter());
|
||||||
Hive.registerAdapter(ImmichLoggerMessageAdapter());
|
Hive.registerAdapter(ImmichLoggerMessageAdapter());
|
||||||
|
await EasyLocalization.ensureInitialized();
|
||||||
await openBoxes();
|
|
||||||
|
|
||||||
if (kReleaseMode && Platform.isAndroid) {
|
if (kReleaseMode && Platform.isAndroid) {
|
||||||
try {
|
try {
|
||||||
@ -82,7 +71,7 @@ Future<void> initApp() async {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Initialize Immich Logger Service
|
// Initialize Immich Logger Service
|
||||||
ImmichLogger().init();
|
ImmichLogger();
|
||||||
|
|
||||||
var log = Logger("ImmichErrorLogger");
|
var log = Logger("ImmichErrorLogger");
|
||||||
|
|
||||||
@ -108,6 +97,7 @@ Future<Isar> loadDb() async {
|
|||||||
UserSchema,
|
UserSchema,
|
||||||
BackupAlbumSchema,
|
BackupAlbumSchema,
|
||||||
DuplicatedAssetSchema,
|
DuplicatedAssetSchema,
|
||||||
|
LoggerMessageSchema,
|
||||||
],
|
],
|
||||||
directory: dir.path,
|
directory: dir.path,
|
||||||
maxSizeMiB: 256,
|
maxSizeMiB: 256,
|
||||||
@ -174,6 +164,7 @@ class ImmichAppState extends ConsumerState<ImmichApp>
|
|||||||
case AppLifecycleState.inactive:
|
case AppLifecycleState.inactive:
|
||||||
debugPrint("[APP STATE] inactive");
|
debugPrint("[APP STATE] inactive");
|
||||||
ref.watch(appStateProvider.notifier).state = AppStateEnum.inactive;
|
ref.watch(appStateProvider.notifier).state = AppStateEnum.inactive;
|
||||||
|
ImmichLogger().flush();
|
||||||
ref.watch(websocketProvider.notifier).disconnect();
|
ref.watch(websocketProvider.notifier).disconnect();
|
||||||
ref.watch(backupProvider.notifier).cancelBackup();
|
ref.watch(backupProvider.notifier).cancelBackup();
|
||||||
|
|
||||||
|
@ -265,7 +265,7 @@ class AlbumService {
|
|||||||
|
|
||||||
Future<bool> deleteAlbum(Album album) async {
|
Future<bool> deleteAlbum(Album album) async {
|
||||||
try {
|
try {
|
||||||
final userId = Store.get<User>(StoreKey.currentUser)!.isarId;
|
final userId = Store.get(StoreKey.currentUser).isarId;
|
||||||
if (album.owner.value?.isarId == userId) {
|
if (album.owner.value?.isarId == userId) {
|
||||||
await _apiService.albumApi.deleteAlbum(album.remoteId!);
|
await _apiService.albumApi.deleteAlbum(album.remoteId!);
|
||||||
}
|
}
|
||||||
|
@ -2,10 +2,9 @@ import 'package:auto_route/auto_route.dart';
|
|||||||
import 'package:cached_network_image/cached_network_image.dart';
|
import 'package:cached_network_image/cached_network_image.dart';
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:hive/hive.dart';
|
|
||||||
import 'package:immich_mobile/constants/hive_box.dart';
|
|
||||||
import 'package:immich_mobile/routing/router.dart';
|
import 'package:immich_mobile/routing/router.dart';
|
||||||
import 'package:immich_mobile/shared/models/album.dart';
|
import 'package:immich_mobile/shared/models/album.dart';
|
||||||
|
import 'package:immich_mobile/shared/models/store.dart';
|
||||||
import 'package:immich_mobile/utils/image_url_builder.dart';
|
import 'package:immich_mobile/utils/image_url_builder.dart';
|
||||||
import 'package:openapi/api.dart';
|
import 'package:openapi/api.dart';
|
||||||
|
|
||||||
@ -21,7 +20,6 @@ class AlbumThumbnailListTile extends StatelessWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
var box = Hive.box(userInfoBox);
|
|
||||||
var cardSize = 68.0;
|
var cardSize = 68.0;
|
||||||
var isDarkMode = Theme.of(context).brightness == Brightness.dark;
|
var isDarkMode = Theme.of(context).brightness == Brightness.dark;
|
||||||
|
|
||||||
@ -50,7 +48,9 @@ class AlbumThumbnailListTile extends StatelessWidget {
|
|||||||
album,
|
album,
|
||||||
type: ThumbnailFormat.JPEG,
|
type: ThumbnailFormat.JPEG,
|
||||||
),
|
),
|
||||||
httpHeaders: {"Authorization": "Bearer ${box.get(accessTokenKey)}"},
|
httpHeaders: {
|
||||||
|
"Authorization": "Bearer ${Store.get(StoreKey.accessToken)}"
|
||||||
|
},
|
||||||
cacheKey: getAlbumThumbNailCacheKey(album, type: ThumbnailFormat.JPEG),
|
cacheKey: getAlbumThumbNailCacheKey(album, type: ThumbnailFormat.JPEG),
|
||||||
errorWidget: (context, url, error) =>
|
errorWidget: (context, url, error) =>
|
||||||
const Icon(Icons.image_not_supported_outlined),
|
const Icon(Icons.image_not_supported_outlined),
|
||||||
|
@ -4,16 +4,15 @@ import 'package:auto_route/auto_route.dart';
|
|||||||
import 'package:cached_network_image/cached_network_image.dart';
|
import 'package:cached_network_image/cached_network_image.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
import 'package:flutter_hooks/flutter_hooks.dart' hide Store;
|
||||||
import 'package:hive/hive.dart';
|
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:immich_mobile/constants/hive_box.dart';
|
|
||||||
import 'package:immich_mobile/modules/album/ui/add_to_album_bottom_sheet.dart';
|
import 'package:immich_mobile/modules/album/ui/add_to_album_bottom_sheet.dart';
|
||||||
import 'package:immich_mobile/modules/asset_viewer/providers/image_viewer_page_state.provider.dart';
|
import 'package:immich_mobile/modules/asset_viewer/providers/image_viewer_page_state.provider.dart';
|
||||||
import 'package:immich_mobile/modules/asset_viewer/ui/exif_bottom_sheet.dart';
|
import 'package:immich_mobile/modules/asset_viewer/ui/exif_bottom_sheet.dart';
|
||||||
import 'package:immich_mobile/modules/asset_viewer/ui/top_control_app_bar.dart';
|
import 'package:immich_mobile/modules/asset_viewer/ui/top_control_app_bar.dart';
|
||||||
import 'package:immich_mobile/modules/asset_viewer/views/video_viewer_page.dart';
|
import 'package:immich_mobile/modules/asset_viewer/views/video_viewer_page.dart';
|
||||||
import 'package:immich_mobile/modules/favorite/providers/favorite_provider.dart';
|
import 'package:immich_mobile/modules/favorite/providers/favorite_provider.dart';
|
||||||
|
import 'package:immich_mobile/shared/models/store.dart';
|
||||||
import 'package:immich_mobile/shared/services/asset.service.dart';
|
import 'package:immich_mobile/shared/services/asset.service.dart';
|
||||||
import 'package:immich_mobile/modules/home/ui/delete_dialog.dart';
|
import 'package:immich_mobile/modules/home/ui/delete_dialog.dart';
|
||||||
import 'package:immich_mobile/modules/settings/providers/app_settings.provider.dart';
|
import 'package:immich_mobile/modules/settings/providers/app_settings.provider.dart';
|
||||||
@ -47,7 +46,6 @@ class GalleryViewerPage extends HookConsumerWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
final Box<dynamic> box = Hive.box(userInfoBox);
|
|
||||||
final settings = ref.watch(appSettingsServiceProvider);
|
final settings = ref.watch(appSettingsServiceProvider);
|
||||||
final isLoadPreview = useState(AppSettingsEnum.loadPreview.defaultValue);
|
final isLoadPreview = useState(AppSettingsEnum.loadPreview.defaultValue);
|
||||||
final isLoadOriginal = useState(AppSettingsEnum.loadOriginal.defaultValue);
|
final isLoadOriginal = useState(AppSettingsEnum.loadOriginal.defaultValue);
|
||||||
@ -57,7 +55,7 @@ class GalleryViewerPage extends HookConsumerWidget {
|
|||||||
final isPlayingMotionVideo = useState(false);
|
final isPlayingMotionVideo = useState(false);
|
||||||
final isPlayingVideo = useState(false);
|
final isPlayingVideo = useState(false);
|
||||||
late Offset localPosition;
|
late Offset localPosition;
|
||||||
final authToken = 'Bearer ${box.get(accessTokenKey)}';
|
final authToken = 'Bearer ${Store.get(StoreKey.accessToken)}';
|
||||||
|
|
||||||
showAppBar.addListener(() {
|
showAppBar.addListener(() {
|
||||||
// Change to and from immersive mode, hiding navigation and app bar
|
// Change to and from immersive mode, hiding navigation and app bar
|
||||||
|
@ -1,13 +1,12 @@
|
|||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:hive/hive.dart';
|
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:immich_mobile/constants/hive_box.dart';
|
|
||||||
import 'package:chewie/chewie.dart';
|
import 'package:chewie/chewie.dart';
|
||||||
import 'package:immich_mobile/modules/asset_viewer/models/image_viewer_page_state.model.dart';
|
import 'package:immich_mobile/modules/asset_viewer/models/image_viewer_page_state.model.dart';
|
||||||
import 'package:immich_mobile/modules/asset_viewer/providers/image_viewer_page_state.provider.dart';
|
import 'package:immich_mobile/modules/asset_viewer/providers/image_viewer_page_state.provider.dart';
|
||||||
import 'package:immich_mobile/shared/models/asset.dart';
|
import 'package:immich_mobile/shared/models/asset.dart';
|
||||||
|
import 'package:immich_mobile/shared/models/store.dart';
|
||||||
import 'package:immich_mobile/shared/ui/immich_loading_indicator.dart';
|
import 'package:immich_mobile/shared/ui/immich_loading_indicator.dart';
|
||||||
import 'package:photo_manager/photo_manager.dart';
|
import 'package:photo_manager/photo_manager.dart';
|
||||||
import 'package:video_player/video_player.dart';
|
import 'package:video_player/video_player.dart';
|
||||||
@ -54,17 +53,15 @@ class VideoViewerPage extends HookConsumerWidget {
|
|||||||
}
|
}
|
||||||
final downloadAssetStatus =
|
final downloadAssetStatus =
|
||||||
ref.watch(imageViewerStateProvider).downloadAssetStatus;
|
ref.watch(imageViewerStateProvider).downloadAssetStatus;
|
||||||
final box = Hive.box(userInfoBox);
|
|
||||||
final String jwtToken = box.get(accessTokenKey);
|
|
||||||
final String videoUrl = isMotionVideo
|
final String videoUrl = isMotionVideo
|
||||||
? '${box.get(serverEndpointKey)}/asset/file/${asset.livePhotoVideoId}'
|
? '${Store.get(StoreKey.serverEndpoint)}/asset/file/${asset.livePhotoVideoId}'
|
||||||
: '${box.get(serverEndpointKey)}/asset/file/${asset.remoteId}';
|
: '${Store.get(StoreKey.serverEndpoint)}/asset/file/${asset.remoteId}';
|
||||||
|
|
||||||
return Stack(
|
return Stack(
|
||||||
children: [
|
children: [
|
||||||
VideoThumbnailPlayer(
|
VideoThumbnailPlayer(
|
||||||
url: videoUrl,
|
url: videoUrl,
|
||||||
jwtToken: jwtToken,
|
jwtToken: Store.get(StoreKey.accessToken),
|
||||||
isMotionVideo: isMotionVideo,
|
isMotionVideo: isMotionVideo,
|
||||||
onVideoEnded: onVideoEnded,
|
onVideoEnded: onVideoEnded,
|
||||||
onPaused: onPaused,
|
onPaused: onPaused,
|
||||||
|
@ -8,16 +8,13 @@ import 'package:collection/collection.dart';
|
|||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:flutter/widgets.dart';
|
import 'package:flutter/widgets.dart';
|
||||||
import 'package:hive_flutter/hive_flutter.dart';
|
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:immich_mobile/constants/hive_box.dart';
|
|
||||||
import 'package:immich_mobile/main.dart';
|
import 'package:immich_mobile/main.dart';
|
||||||
import 'package:immich_mobile/modules/backup/background_service/localization.dart';
|
import 'package:immich_mobile/modules/backup/background_service/localization.dart';
|
||||||
import 'package:immich_mobile/modules/backup/models/backup_album.model.dart';
|
import 'package:immich_mobile/modules/backup/models/backup_album.model.dart';
|
||||||
import 'package:immich_mobile/modules/backup/models/current_upload_asset.model.dart';
|
import 'package:immich_mobile/modules/backup/models/current_upload_asset.model.dart';
|
||||||
import 'package:immich_mobile/modules/backup/models/error_upload_asset.model.dart';
|
import 'package:immich_mobile/modules/backup/models/error_upload_asset.model.dart';
|
||||||
import 'package:immich_mobile/modules/backup/services/backup.service.dart';
|
import 'package:immich_mobile/modules/backup/services/backup.service.dart';
|
||||||
import 'package:immich_mobile/modules/login/models/hive_saved_login_info.model.dart';
|
|
||||||
import 'package:immich_mobile/modules/settings/services/app_settings.service.dart';
|
import 'package:immich_mobile/modules/settings/services/app_settings.service.dart';
|
||||||
import 'package:immich_mobile/shared/models/store.dart';
|
import 'package:immich_mobile/shared/models/store.dart';
|
||||||
import 'package:immich_mobile/shared/services/api.service.dart';
|
import 'package:immich_mobile/shared/services/api.service.dart';
|
||||||
@ -317,7 +314,6 @@ class BackgroundService {
|
|||||||
debugPrint(error.toString());
|
debugPrint(error.toString());
|
||||||
return false;
|
return false;
|
||||||
} finally {
|
} finally {
|
||||||
await Hive.close();
|
|
||||||
releaseLock();
|
releaseLock();
|
||||||
}
|
}
|
||||||
case "systemStop":
|
case "systemStop":
|
||||||
@ -332,17 +328,9 @@ class BackgroundService {
|
|||||||
|
|
||||||
Future<bool> _onAssetsChanged() async {
|
Future<bool> _onAssetsChanged() async {
|
||||||
final Isar db = await loadDb();
|
final Isar db = await loadDb();
|
||||||
await Hive.initFlutter();
|
|
||||||
|
|
||||||
Hive.registerAdapter(HiveSavedLoginInfoAdapter());
|
|
||||||
|
|
||||||
await Future.wait([
|
|
||||||
Hive.openBox(userInfoBox),
|
|
||||||
Hive.openBox<HiveSavedLoginInfo>(hiveLoginInfoBox),
|
|
||||||
Hive.openBox(userSettingInfoBox),
|
|
||||||
]);
|
|
||||||
ApiService apiService = ApiService();
|
ApiService apiService = ApiService();
|
||||||
apiService.setAccessToken(Hive.box(userInfoBox).get(accessTokenKey));
|
apiService.setAccessToken(Store.get(StoreKey.accessToken));
|
||||||
BackupService backupService = BackupService(apiService, db);
|
BackupService backupService = BackupService(apiService, db);
|
||||||
AppSettingsService settingsService = AppSettingsService();
|
AppSettingsService settingsService = AppSettingsService();
|
||||||
|
|
||||||
@ -387,7 +375,7 @@ class BackgroundService {
|
|||||||
db.backupAlbums.deleteAllSync(toDelete);
|
db.backupAlbums.deleteAllSync(toDelete);
|
||||||
db.backupAlbums.putAllSync(toUpsert);
|
db.backupAlbums.putAllSync(toUpsert);
|
||||||
});
|
});
|
||||||
} else if (Store.get(StoreKey.backupFailedSince) == null) {
|
} else if (Store.tryGet(StoreKey.backupFailedSince) == null) {
|
||||||
Store.put(StoreKey.backupFailedSince, DateTime.now());
|
Store.put(StoreKey.backupFailedSince, DateTime.now());
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -529,7 +517,7 @@ class BackgroundService {
|
|||||||
} else if (value == 5) {
|
} else if (value == 5) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
final DateTime? failedSince = Store.get(StoreKey.backupFailedSince);
|
final DateTime? failedSince = Store.tryGet(StoreKey.backupFailedSince);
|
||||||
if (failedSince == null) {
|
if (failedSince == null) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,7 @@
|
|||||||
import 'package:cancellation_token_http/http.dart';
|
import 'package:cancellation_token_http/http.dart';
|
||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.dart';
|
||||||
import 'package:flutter/widgets.dart';
|
import 'package:flutter/widgets.dart';
|
||||||
import 'package:hive_flutter/hive_flutter.dart';
|
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:immich_mobile/constants/hive_box.dart';
|
|
||||||
import 'package:immich_mobile/modules/backup/models/available_album.model.dart';
|
import 'package:immich_mobile/modules/backup/models/available_album.model.dart';
|
||||||
import 'package:immich_mobile/modules/backup/models/backup_album.model.dart';
|
import 'package:immich_mobile/modules/backup/models/backup_album.model.dart';
|
||||||
import 'package:immich_mobile/modules/backup/models/backup_state.model.dart';
|
import 'package:immich_mobile/modules/backup/models/backup_state.model.dart';
|
||||||
@ -42,9 +40,10 @@ class BackupNotifier extends StateNotifier<BackUpState> {
|
|||||||
progressInPercentage: 0,
|
progressInPercentage: 0,
|
||||||
cancelToken: CancellationToken(),
|
cancelToken: CancellationToken(),
|
||||||
backgroundBackup: false,
|
backgroundBackup: false,
|
||||||
backupRequireWifi: true,
|
backupRequireWifi: Store.get(StoreKey.backupRequireWifi, true),
|
||||||
backupRequireCharging: false,
|
backupRequireCharging:
|
||||||
backupTriggerDelay: 5000,
|
Store.get(StoreKey.backupRequireCharging, false),
|
||||||
|
backupTriggerDelay: Store.get(StoreKey.backupTriggerDelay, 5000),
|
||||||
serverInfo: ServerInfoResponseDto(
|
serverInfo: ServerInfoResponseDto(
|
||||||
diskAvailable: "0",
|
diskAvailable: "0",
|
||||||
diskAvailableRaw: 0,
|
diskAvailableRaw: 0,
|
||||||
@ -163,14 +162,12 @@ class BackupNotifier extends StateNotifier<BackUpState> {
|
|||||||
triggerMaxDelay: state.backupTriggerDelay * 10,
|
triggerMaxDelay: state.backupTriggerDelay * 10,
|
||||||
);
|
);
|
||||||
if (success) {
|
if (success) {
|
||||||
await Future.wait([
|
await Store.put(StoreKey.backupRequireWifi, state.backupRequireWifi);
|
||||||
Store.put(StoreKey.backupRequireWifi, state.backupRequireWifi),
|
await Store.put(
|
||||||
Store.put(
|
StoreKey.backupRequireCharging,
|
||||||
StoreKey.backupRequireCharging,
|
state.backupRequireCharging,
|
||||||
state.backupRequireCharging,
|
);
|
||||||
),
|
await Store.put(StoreKey.backupTriggerDelay, state.backupTriggerDelay);
|
||||||
Store.put(StoreKey.backupTriggerDelay, state.backupTriggerDelay),
|
|
||||||
]);
|
|
||||||
} else {
|
} else {
|
||||||
state = state.copyWith(
|
state = state.copyWith(
|
||||||
backgroundBackup: wasEnabled,
|
backgroundBackup: wasEnabled,
|
||||||
@ -544,7 +541,7 @@ class BackupNotifier extends StateNotifier<BackUpState> {
|
|||||||
|
|
||||||
Future<void> _resumeBackup() async {
|
Future<void> _resumeBackup() async {
|
||||||
// Check if user is login
|
// Check if user is login
|
||||||
final accessKey = Hive.box(userInfoBox).get(accessTokenKey);
|
final accessKey = Store.tryGet(StoreKey.accessToken);
|
||||||
|
|
||||||
// User has been logged out return
|
// User has been logged out return
|
||||||
if (accessKey == null || !_authState.isAuthenticated) {
|
if (accessKey == null || !_authState.isAuthenticated) {
|
||||||
@ -603,9 +600,6 @@ class BackupNotifier extends StateNotifier<BackUpState> {
|
|||||||
backupProgress: BackUpProgressEnum.inBackground,
|
backupProgress: BackUpProgressEnum.inBackground,
|
||||||
selectedBackupAlbums: selectedAlbums,
|
selectedBackupAlbums: selectedAlbums,
|
||||||
excludedBackupAlbums: excludedAlbums,
|
excludedBackupAlbums: excludedAlbums,
|
||||||
backupRequireWifi: Store.get(StoreKey.backupRequireWifi),
|
|
||||||
backupRequireCharging: Store.get(StoreKey.backupRequireCharging),
|
|
||||||
backupTriggerDelay: Store.get(StoreKey.backupTriggerDelay),
|
|
||||||
);
|
);
|
||||||
// assumes the background service is currently running
|
// assumes the background service is currently running
|
||||||
// if true, waits until it has stopped to start the backup
|
// if true, waits until it has stopped to start the backup
|
||||||
|
@ -5,13 +5,12 @@ import 'dart:io';
|
|||||||
import 'package:cancellation_token_http/http.dart';
|
import 'package:cancellation_token_http/http.dart';
|
||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:hive/hive.dart';
|
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:immich_mobile/constants/hive_box.dart';
|
|
||||||
import 'package:immich_mobile/modules/backup/models/backup_album.model.dart';
|
import 'package:immich_mobile/modules/backup/models/backup_album.model.dart';
|
||||||
import 'package:immich_mobile/modules/backup/models/current_upload_asset.model.dart';
|
import 'package:immich_mobile/modules/backup/models/current_upload_asset.model.dart';
|
||||||
import 'package:immich_mobile/modules/backup/models/duplicated_asset.model.dart';
|
import 'package:immich_mobile/modules/backup/models/duplicated_asset.model.dart';
|
||||||
import 'package:immich_mobile/modules/backup/models/error_upload_asset.model.dart';
|
import 'package:immich_mobile/modules/backup/models/error_upload_asset.model.dart';
|
||||||
|
import 'package:immich_mobile/shared/models/store.dart';
|
||||||
import 'package:immich_mobile/shared/providers/api.provider.dart';
|
import 'package:immich_mobile/shared/providers/api.provider.dart';
|
||||||
import 'package:immich_mobile/shared/providers/db.provider.dart';
|
import 'package:immich_mobile/shared/providers/db.provider.dart';
|
||||||
import 'package:immich_mobile/shared/services/api.service.dart';
|
import 'package:immich_mobile/shared/services/api.service.dart';
|
||||||
@ -38,7 +37,7 @@ class BackupService {
|
|||||||
BackupService(this._apiService, this._db);
|
BackupService(this._apiService, this._db);
|
||||||
|
|
||||||
Future<List<String>?> getDeviceBackupAsset() async {
|
Future<List<String>?> getDeviceBackupAsset() async {
|
||||||
String deviceId = Hive.box(userInfoBox).get(deviceIdKey);
|
final String deviceId = Store.get(StoreKey.deviceId);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
return await _apiService.assetApi.getUserAssetsByDeviceId(deviceId);
|
return await _apiService.assetApi.getUserAssetsByDeviceId(deviceId);
|
||||||
@ -173,7 +172,7 @@ class BackupService {
|
|||||||
}
|
}
|
||||||
final Set<String> existing = {};
|
final Set<String> existing = {};
|
||||||
try {
|
try {
|
||||||
final String deviceId = Hive.box(userInfoBox).get(deviceIdKey);
|
final String deviceId = Store.get(StoreKey.deviceId);
|
||||||
final CheckExistingAssetsResponseDto? duplicates =
|
final CheckExistingAssetsResponseDto? duplicates =
|
||||||
await _apiService.assetApi.checkExistingAssets(
|
await _apiService.assetApi.checkExistingAssets(
|
||||||
CheckExistingAssetsDto(
|
CheckExistingAssetsDto(
|
||||||
@ -204,8 +203,8 @@ class BackupService {
|
|||||||
Function(CurrentUploadAsset) setCurrentUploadAssetCb,
|
Function(CurrentUploadAsset) setCurrentUploadAssetCb,
|
||||||
Function(ErrorUploadAsset) errorCb,
|
Function(ErrorUploadAsset) errorCb,
|
||||||
) async {
|
) async {
|
||||||
String deviceId = Hive.box(userInfoBox).get(deviceIdKey);
|
final String deviceId = Store.get(StoreKey.deviceId);
|
||||||
String savedEndpoint = Hive.box(userInfoBox).get(serverEndpointKey);
|
final String savedEndpoint = Store.get(StoreKey.serverEndpoint);
|
||||||
File? file;
|
File? file;
|
||||||
bool anyErrors = false;
|
bool anyErrors = false;
|
||||||
final List<String> duplicatedAssetIds = [];
|
final List<String> duplicatedAssetIds = [];
|
||||||
@ -236,15 +235,14 @@ class BackupService {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
var box = Hive.box(userInfoBox);
|
|
||||||
|
|
||||||
var req = MultipartRequest(
|
var req = MultipartRequest(
|
||||||
'POST',
|
'POST',
|
||||||
Uri.parse('$savedEndpoint/asset/upload'),
|
Uri.parse('$savedEndpoint/asset/upload'),
|
||||||
onProgress: ((bytes, totalBytes) =>
|
onProgress: ((bytes, totalBytes) =>
|
||||||
uploadProgressCb(bytes, totalBytes)),
|
uploadProgressCb(bytes, totalBytes)),
|
||||||
);
|
);
|
||||||
req.headers["Authorization"] = "Bearer ${box.get(accessTokenKey)}";
|
req.headers["Authorization"] =
|
||||||
|
"Bearer ${Store.get(StoreKey.accessToken)}";
|
||||||
|
|
||||||
req.fields['deviceAssetId'] = entity.id;
|
req.fields['deviceAssetId'] = entity.id;
|
||||||
req.fields['deviceId'] = deviceId;
|
req.fields['deviceId'] = deviceId;
|
||||||
|
@ -2,9 +2,7 @@ import 'dart:math';
|
|||||||
|
|
||||||
import 'package:auto_route/auto_route.dart';
|
import 'package:auto_route/auto_route.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:hive/hive.dart';
|
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:immich_mobile/constants/hive_box.dart';
|
|
||||||
import 'package:immich_mobile/modules/login/models/authentication_state.model.dart';
|
import 'package:immich_mobile/modules/login/models/authentication_state.model.dart';
|
||||||
import 'package:immich_mobile/modules/login/providers/authentication.provider.dart';
|
import 'package:immich_mobile/modules/login/providers/authentication.provider.dart';
|
||||||
|
|
||||||
@ -12,6 +10,7 @@ import 'package:immich_mobile/routing/router.dart';
|
|||||||
import 'package:immich_mobile/modules/backup/models/backup_state.model.dart';
|
import 'package:immich_mobile/modules/backup/models/backup_state.model.dart';
|
||||||
import 'package:immich_mobile/shared/models/server_info_state.model.dart';
|
import 'package:immich_mobile/shared/models/server_info_state.model.dart';
|
||||||
import 'package:immich_mobile/modules/backup/providers/backup.provider.dart';
|
import 'package:immich_mobile/modules/backup/providers/backup.provider.dart';
|
||||||
|
import 'package:immich_mobile/shared/models/store.dart';
|
||||||
import 'package:immich_mobile/shared/providers/server_info.provider.dart';
|
import 'package:immich_mobile/shared/providers/server_info.provider.dart';
|
||||||
import 'package:immich_mobile/shared/ui/transparent_image.dart';
|
import 'package:immich_mobile/shared/ui/transparent_image.dart';
|
||||||
|
|
||||||
@ -47,7 +46,7 @@ class HomePageAppBar extends ConsumerWidget with PreferredSizeWidget {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
String endpoint = Hive.box(userInfoBox).get(serverEndpointKey);
|
final String? endpoint = Store.get(StoreKey.serverEndpoint);
|
||||||
var dummy = Random().nextInt(1024);
|
var dummy = Random().nextInt(1024);
|
||||||
return InkWell(
|
return InkWell(
|
||||||
onTap: () {
|
onTap: () {
|
||||||
|
@ -1,14 +1,13 @@
|
|||||||
import 'dart:math';
|
import 'dart:math';
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
import 'package:flutter_hooks/flutter_hooks.dart' hide Store;
|
||||||
import 'package:hive_flutter/hive_flutter.dart';
|
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:image_picker/image_picker.dart';
|
import 'package:image_picker/image_picker.dart';
|
||||||
import 'package:immich_mobile/constants/hive_box.dart';
|
|
||||||
import 'package:immich_mobile/modules/home/providers/upload_profile_image.provider.dart';
|
import 'package:immich_mobile/modules/home/providers/upload_profile_image.provider.dart';
|
||||||
import 'package:immich_mobile/modules/login/models/authentication_state.model.dart';
|
import 'package:immich_mobile/modules/login/models/authentication_state.model.dart';
|
||||||
import 'package:immich_mobile/modules/login/providers/authentication.provider.dart';
|
import 'package:immich_mobile/modules/login/providers/authentication.provider.dart';
|
||||||
|
import 'package:immich_mobile/shared/models/store.dart';
|
||||||
import 'package:immich_mobile/shared/ui/immich_loading_indicator.dart';
|
import 'package:immich_mobile/shared/ui/immich_loading_indicator.dart';
|
||||||
import 'package:immich_mobile/shared/ui/transparent_image.dart';
|
import 'package:immich_mobile/shared/ui/transparent_image.dart';
|
||||||
|
|
||||||
@ -19,7 +18,7 @@ class ProfileDrawerHeader extends HookConsumerWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
String endpoint = Hive.box(userInfoBox).get(serverEndpointKey);
|
final String endpoint = Store.get(StoreKey.serverEndpoint);
|
||||||
AuthenticationState authState = ref.watch(authenticationProvider);
|
AuthenticationState authState = ref.watch(authenticationProvider);
|
||||||
final uploadProfileImageStatus =
|
final uploadProfileImageStatus =
|
||||||
ref.watch(uploadProfileImageProvider).status;
|
ref.watch(uploadProfileImageProvider).status;
|
||||||
|
@ -2,12 +2,9 @@ import 'dart:io';
|
|||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:hive/hive.dart';
|
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:immich_mobile/constants/hive_box.dart';
|
|
||||||
import 'package:immich_mobile/shared/models/store.dart';
|
import 'package:immich_mobile/shared/models/store.dart';
|
||||||
import 'package:immich_mobile/modules/login/models/authentication_state.model.dart';
|
import 'package:immich_mobile/modules/login/models/authentication_state.model.dart';
|
||||||
import 'package:immich_mobile/modules/login/models/hive_saved_login_info.model.dart';
|
|
||||||
import 'package:immich_mobile/modules/backup/services/backup.service.dart';
|
import 'package:immich_mobile/modules/backup/services/backup.service.dart';
|
||||||
import 'package:immich_mobile/shared/models/user.dart';
|
import 'package:immich_mobile/shared/models/user.dart';
|
||||||
import 'package:immich_mobile/shared/providers/api.provider.dart';
|
import 'package:immich_mobile/shared/providers/api.provider.dart';
|
||||||
@ -91,11 +88,10 @@ class AuthenticationNotifier extends StateNotifier<AuthenticationState> {
|
|||||||
try {
|
try {
|
||||||
await Future.wait([
|
await Future.wait([
|
||||||
_apiService.authenticationApi.logout(),
|
_apiService.authenticationApi.logout(),
|
||||||
Hive.box(userInfoBox).delete(accessTokenKey),
|
|
||||||
Store.delete(StoreKey.assetETag),
|
Store.delete(StoreKey.assetETag),
|
||||||
Store.delete(StoreKey.userRemoteId),
|
Store.delete(StoreKey.userRemoteId),
|
||||||
Store.delete(StoreKey.currentUser),
|
Store.delete(StoreKey.currentUser),
|
||||||
Hive.box<HiveSavedLoginInfo>(hiveLoginInfoBox).delete(savedLoginInfoKey)
|
Store.delete(StoreKey.accessToken),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
state = state.copyWith(isAuthenticated: false);
|
state = state.copyWith(isAuthenticated: false);
|
||||||
@ -157,14 +153,13 @@ class AuthenticationNotifier extends StateNotifier<AuthenticationState> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (userResponseDto != null) {
|
if (userResponseDto != null) {
|
||||||
var userInfoHiveBox = await Hive.openBox(userInfoBox);
|
|
||||||
var deviceInfo = await _deviceInfoService.getDeviceInfo();
|
var deviceInfo = await _deviceInfoService.getDeviceInfo();
|
||||||
userInfoHiveBox.put(deviceIdKey, deviceInfo["deviceId"]);
|
|
||||||
userInfoHiveBox.put(accessTokenKey, accessToken);
|
|
||||||
Store.put(StoreKey.deviceId, deviceInfo["deviceId"]);
|
Store.put(StoreKey.deviceId, deviceInfo["deviceId"]);
|
||||||
Store.put(StoreKey.deviceIdHash, fastHash(deviceInfo["deviceId"]));
|
Store.put(StoreKey.deviceIdHash, fastHash(deviceInfo["deviceId"]));
|
||||||
Store.put(StoreKey.userRemoteId, userResponseDto.id);
|
Store.put(StoreKey.userRemoteId, userResponseDto.id);
|
||||||
Store.put(StoreKey.currentUser, User.fromDto(userResponseDto));
|
Store.put(StoreKey.currentUser, User.fromDto(userResponseDto));
|
||||||
|
Store.put(StoreKey.serverUrl, serverUrl);
|
||||||
|
Store.put(StoreKey.accessToken, accessToken);
|
||||||
|
|
||||||
state = state.copyWith(
|
state = state.copyWith(
|
||||||
isAuthenticated: true,
|
isAuthenticated: true,
|
||||||
@ -178,17 +173,6 @@ class AuthenticationNotifier extends StateNotifier<AuthenticationState> {
|
|||||||
deviceId: deviceInfo["deviceId"],
|
deviceId: deviceInfo["deviceId"],
|
||||||
deviceType: deviceInfo["deviceType"],
|
deviceType: deviceInfo["deviceType"],
|
||||||
);
|
);
|
||||||
|
|
||||||
// Save login info to local storage
|
|
||||||
Hive.box<HiveSavedLoginInfo>(hiveLoginInfoBox).put(
|
|
||||||
savedLoginInfoKey,
|
|
||||||
HiveSavedLoginInfo(
|
|
||||||
email: "",
|
|
||||||
password: "",
|
|
||||||
serverUrl: serverUrl,
|
|
||||||
accessToken: accessToken,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Register device info
|
// Register device info
|
||||||
|
@ -1,14 +1,12 @@
|
|||||||
import 'package:auto_route/auto_route.dart';
|
import 'package:auto_route/auto_route.dart';
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
import 'package:flutter_hooks/flutter_hooks.dart' hide Store;
|
||||||
import 'package:hive/hive.dart';
|
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:immich_mobile/constants/hive_box.dart';
|
|
||||||
import 'package:immich_mobile/modules/login/models/hive_saved_login_info.model.dart';
|
|
||||||
import 'package:immich_mobile/modules/login/providers/oauth.provider.dart';
|
import 'package:immich_mobile/modules/login/providers/oauth.provider.dart';
|
||||||
import 'package:immich_mobile/modules/onboarding/providers/gallery_permission.provider.dart';
|
import 'package:immich_mobile/modules/onboarding/providers/gallery_permission.provider.dart';
|
||||||
import 'package:immich_mobile/routing/router.dart';
|
import 'package:immich_mobile/routing/router.dart';
|
||||||
|
import 'package:immich_mobile/shared/models/store.dart';
|
||||||
import 'package:immich_mobile/shared/providers/api.provider.dart';
|
import 'package:immich_mobile/shared/providers/api.provider.dart';
|
||||||
import 'package:immich_mobile/shared/providers/asset.provider.dart';
|
import 'package:immich_mobile/shared/providers/asset.provider.dart';
|
||||||
import 'package:immich_mobile/modules/login/providers/authentication.provider.dart';
|
import 'package:immich_mobile/modules/login/providers/authentication.provider.dart';
|
||||||
@ -63,8 +61,7 @@ class LoginForm extends HookConsumerWidget {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
isLoadingServer.value = true;
|
isLoadingServer.value = true;
|
||||||
final endpoint =
|
final endpoint = await apiService.resolveAndSetEndpoint(serverUrl);
|
||||||
await apiService.resolveAndSetEndpoint(serverUrl);
|
|
||||||
|
|
||||||
final loginConfig = await apiService.oAuthApi.generateConfig(
|
final loginConfig = await apiService.oAuthApi.generateConfig(
|
||||||
OAuthConfigDto(redirectUri: serverUrl),
|
OAuthConfigDto(redirectUri: serverUrl),
|
||||||
@ -104,15 +101,10 @@ class LoginForm extends HookConsumerWidget {
|
|||||||
|
|
||||||
useEffect(
|
useEffect(
|
||||||
() {
|
() {
|
||||||
var loginInfo = Hive.box<HiveSavedLoginInfo>(hiveLoginInfoBox)
|
final serverUrl = Store.tryGet(StoreKey.serverUrl);
|
||||||
.get(savedLoginInfoKey);
|
if (serverUrl != null) {
|
||||||
|
serverEndpointController.text = serverUrl;
|
||||||
if (loginInfo != null) {
|
|
||||||
usernameController.text = loginInfo.email;
|
|
||||||
passwordController.text = loginInfo.password;
|
|
||||||
serverEndpointController.text = loginInfo.serverUrl;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
},
|
},
|
||||||
[],
|
[],
|
||||||
@ -133,11 +125,11 @@ class LoginForm extends HookConsumerWidget {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
final isAuthenticated =
|
final isAuthenticated =
|
||||||
await ref.read(authenticationProvider.notifier).login(
|
await ref.read(authenticationProvider.notifier).login(
|
||||||
usernameController.text,
|
usernameController.text,
|
||||||
passwordController.text,
|
passwordController.text,
|
||||||
serverEndpointController.text.trim(),
|
serverEndpointController.text.trim(),
|
||||||
);
|
);
|
||||||
if (isAuthenticated) {
|
if (isAuthenticated) {
|
||||||
// Resume backup (if enable) then navigate
|
// Resume backup (if enable) then navigate
|
||||||
if (ref.read(authenticationProvider).shouldChangePassword &&
|
if (ref.read(authenticationProvider).shouldChangePassword &&
|
||||||
@ -283,61 +275,61 @@ class LoginForm extends HookConsumerWidget {
|
|||||||
onSubmit: login,
|
onSubmit: login,
|
||||||
),
|
),
|
||||||
|
|
||||||
// Note: This used to have an AnimatedSwitcher, but was removed
|
// Note: This used to have an AnimatedSwitcher, but was removed
|
||||||
// because of https://github.com/flutter/flutter/issues/120874
|
// because of https://github.com/flutter/flutter/issues/120874
|
||||||
isLoading.value
|
isLoading.value
|
||||||
? const Padding(
|
? const Padding(
|
||||||
padding: EdgeInsets.only(top: 18.0),
|
padding: EdgeInsets.only(top: 18.0),
|
||||||
child: SizedBox(
|
child: SizedBox(
|
||||||
width: 24,
|
width: 24,
|
||||||
height: 24,
|
height: 24,
|
||||||
child: FittedBox(
|
child: FittedBox(
|
||||||
child: CircularProgressIndicator(
|
child: CircularProgressIndicator(
|
||||||
strokeWidth: 2,
|
strokeWidth: 2,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
)
|
||||||
)
|
: Column(
|
||||||
: Column(
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
children: [
|
||||||
children: [
|
const SizedBox(height: 18),
|
||||||
const SizedBox(height: 18),
|
LoginButton(onPressed: login),
|
||||||
LoginButton(onPressed: login),
|
if (isOauthEnable.value) ...[
|
||||||
if (isOauthEnable.value) ...[
|
Padding(
|
||||||
Padding(
|
padding: const EdgeInsets.symmetric(
|
||||||
padding: const EdgeInsets.symmetric(
|
horizontal: 16.0,
|
||||||
horizontal: 16.0,
|
),
|
||||||
|
child: Divider(
|
||||||
|
color:
|
||||||
|
Brightness.dark == Theme.of(context).brightness
|
||||||
|
? Colors.white
|
||||||
|
: Colors.black,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
child: Divider(
|
OAuthLoginButton(
|
||||||
color:
|
serverEndpointController: serverEndpointController,
|
||||||
Brightness.dark == Theme.of(context).brightness
|
buttonLabel: oAuthButtonLabel.value,
|
||||||
? Colors.white
|
isLoading: isLoading,
|
||||||
: Colors.black,
|
onPressed: oAuthLogin,
|
||||||
),
|
),
|
||||||
),
|
],
|
||||||
OAuthLoginButton(
|
|
||||||
serverEndpointController: serverEndpointController,
|
|
||||||
buttonLabel: oAuthButtonLabel.value,
|
|
||||||
isLoading: isLoading,
|
|
||||||
onPressed: oAuthLogin,
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
],
|
),
|
||||||
),
|
const SizedBox(height: 12),
|
||||||
const SizedBox(height: 12),
|
TextButton.icon(
|
||||||
TextButton.icon(
|
icon: const Icon(Icons.arrow_back),
|
||||||
icon: const Icon(Icons.arrow_back),
|
onPressed: () => serverEndpoint.value = null,
|
||||||
onPressed: () => serverEndpoint.value = null,
|
label: const Text('Back'),
|
||||||
label: const Text('Back'),
|
),
|
||||||
),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
final serverSelectionOrLogin = serverEndpoint.value == null
|
|
||||||
? buildSelectServer()
|
final serverSelectionOrLogin =
|
||||||
: buildLogin();
|
serverEndpoint.value == null ? buildSelectServer() : buildLogin();
|
||||||
|
|
||||||
return LayoutBuilder(
|
return LayoutBuilder(
|
||||||
builder: (context, constraints) {
|
builder: (context, constraints) {
|
||||||
@ -545,7 +537,6 @@ class OAuthLoginButton extends ConsumerWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
|
|
||||||
return ElevatedButton.icon(
|
return ElevatedButton.icon(
|
||||||
style: ElevatedButton.styleFrom(
|
style: ElevatedButton.styleFrom(
|
||||||
backgroundColor: Theme.of(context).primaryColor.withAlpha(230),
|
backgroundColor: Theme.of(context).primaryColor.withAlpha(230),
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
import 'package:cached_network_image/cached_network_image.dart';
|
import 'package:cached_network_image/cached_network_image.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:hive_flutter/hive_flutter.dart';
|
import 'package:immich_mobile/shared/models/store.dart';
|
||||||
import 'package:immich_mobile/constants/hive_box.dart';
|
|
||||||
|
|
||||||
class ThumbnailWithInfo extends StatelessWidget {
|
class ThumbnailWithInfo extends StatelessWidget {
|
||||||
const ThumbnailWithInfo({
|
const ThumbnailWithInfo({
|
||||||
@ -19,7 +18,6 @@ class ThumbnailWithInfo extends StatelessWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
var box = Hive.box(userInfoBox);
|
|
||||||
var isDarkMode = Theme.of(context).brightness == Brightness.dark;
|
var isDarkMode = Theme.of(context).brightness == Brightness.dark;
|
||||||
var textAndIconColor = isDarkMode ? Colors.grey[100] : Colors.grey[700];
|
var textAndIconColor = isDarkMode ? Colors.grey[100] : Colors.grey[700];
|
||||||
return GestureDetector(
|
return GestureDetector(
|
||||||
@ -51,7 +49,8 @@ class ThumbnailWithInfo extends StatelessWidget {
|
|||||||
fit: BoxFit.cover,
|
fit: BoxFit.cover,
|
||||||
imageUrl: imageUrl!,
|
imageUrl: imageUrl!,
|
||||||
httpHeaders: {
|
httpHeaders: {
|
||||||
"Authorization": "Bearer ${box.get(accessTokenKey)}"
|
"Authorization":
|
||||||
|
"Bearer ${Store.get(StoreKey.accessToken)}"
|
||||||
},
|
},
|
||||||
errorWidget: (context, url, error) =>
|
errorWidget: (context, url, error) =>
|
||||||
const Icon(Icons.image_not_supported_outlined),
|
const Icon(Icons.image_not_supported_outlined),
|
||||||
|
@ -1,15 +1,14 @@
|
|||||||
import 'package:auto_route/auto_route.dart';
|
import 'package:auto_route/auto_route.dart';
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
import 'package:flutter_hooks/flutter_hooks.dart' hide Store;
|
||||||
import 'package:hive_flutter/hive_flutter.dart';
|
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:immich_mobile/constants/hive_box.dart';
|
|
||||||
import 'package:immich_mobile/modules/search/providers/search_page_state.provider.dart';
|
import 'package:immich_mobile/modules/search/providers/search_page_state.provider.dart';
|
||||||
import 'package:immich_mobile/modules/search/ui/search_bar.dart';
|
import 'package:immich_mobile/modules/search/ui/search_bar.dart';
|
||||||
import 'package:immich_mobile/modules/search/ui/search_suggestion_list.dart';
|
import 'package:immich_mobile/modules/search/ui/search_suggestion_list.dart';
|
||||||
import 'package:immich_mobile/modules/search/ui/thumbnail_with_info.dart';
|
import 'package:immich_mobile/modules/search/ui/thumbnail_with_info.dart';
|
||||||
import 'package:immich_mobile/routing/router.dart';
|
import 'package:immich_mobile/routing/router.dart';
|
||||||
|
import 'package:immich_mobile/shared/models/store.dart';
|
||||||
import 'package:immich_mobile/shared/ui/immich_loading_indicator.dart';
|
import 'package:immich_mobile/shared/ui/immich_loading_indicator.dart';
|
||||||
import 'package:immich_mobile/utils/capitalize_first_letter.dart';
|
import 'package:immich_mobile/utils/capitalize_first_letter.dart';
|
||||||
import 'package:openapi/api.dart';
|
import 'package:openapi/api.dart';
|
||||||
@ -22,7 +21,6 @@ class SearchPage extends HookConsumerWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
var box = Hive.box(userInfoBox);
|
|
||||||
final isSearchEnabled = ref.watch(searchPageStateProvider).isSearchEnabled;
|
final isSearchEnabled = ref.watch(searchPageStateProvider).isSearchEnabled;
|
||||||
AsyncValue<List<CuratedLocationsResponseDto>> curatedLocation =
|
AsyncValue<List<CuratedLocationsResponseDto>> curatedLocation =
|
||||||
ref.watch(getCuratedLocationProvider);
|
ref.watch(getCuratedLocationProvider);
|
||||||
@ -64,7 +62,7 @@ class SearchPage extends HookConsumerWidget {
|
|||||||
itemBuilder: ((context, index) {
|
itemBuilder: ((context, index) {
|
||||||
var locationInfo = curatedLocations[index];
|
var locationInfo = curatedLocations[index];
|
||||||
var thumbnailRequestUrl =
|
var thumbnailRequestUrl =
|
||||||
'${box.get(serverEndpointKey)}/asset/thumbnail/${locationInfo.id}';
|
'${Store.get(StoreKey.serverEndpoint)}/asset/thumbnail/${locationInfo.id}';
|
||||||
return ThumbnailWithInfo(
|
return ThumbnailWithInfo(
|
||||||
imageUrl: thumbnailRequestUrl,
|
imageUrl: thumbnailRequestUrl,
|
||||||
textInfo: locationInfo.city,
|
textInfo: locationInfo.city,
|
||||||
@ -113,7 +111,7 @@ class SearchPage extends HookConsumerWidget {
|
|||||||
itemBuilder: ((context, index) {
|
itemBuilder: ((context, index) {
|
||||||
var curatedObjectInfo = objects[index];
|
var curatedObjectInfo = objects[index];
|
||||||
var thumbnailRequestUrl =
|
var thumbnailRequestUrl =
|
||||||
'${box.get(serverEndpointKey)}/asset/thumbnail/${curatedObjectInfo.id}';
|
'${Store.get(StoreKey.serverEndpoint)}/asset/thumbnail/${curatedObjectInfo.id}';
|
||||||
|
|
||||||
return ThumbnailWithInfo(
|
return ThumbnailWithInfo(
|
||||||
imageUrl: thumbnailRequestUrl,
|
imageUrl: thumbnailRequestUrl,
|
||||||
|
@ -1,59 +1,63 @@
|
|||||||
import 'package:hive_flutter/hive_flutter.dart';
|
import 'package:immich_mobile/shared/models/store.dart';
|
||||||
import 'package:immich_mobile/constants/hive_box.dart';
|
|
||||||
|
|
||||||
enum AppSettingsEnum<T> {
|
enum AppSettingsEnum<T> {
|
||||||
loadPreview<bool>("loadPreview", true),
|
loadPreview<bool>(StoreKey.loadPreview, "loadPreview", true),
|
||||||
loadOriginal<bool>("loadOriginal", false),
|
loadOriginal<bool>(StoreKey.loadOriginal, "loadOriginal", false),
|
||||||
themeMode<String>("themeMode", "system"), // "light","dark","system"
|
themeMode<String>(
|
||||||
tilesPerRow<int>("tilesPerRow", 4),
|
StoreKey.themeMode,
|
||||||
dynamicLayout<bool>("dynamicLayout", false),
|
"themeMode",
|
||||||
groupAssetsBy<int>("groupBy", 0),
|
"system",
|
||||||
|
), // "light","dark","system"
|
||||||
|
tilesPerRow<int>(StoreKey.tilesPerRow, "tilesPerRow", 4),
|
||||||
|
dynamicLayout<bool>(StoreKey.dynamicLayout, "dynamicLayout", false),
|
||||||
|
groupAssetsBy<int>(StoreKey.groupAssetsBy, "groupBy", 0),
|
||||||
uploadErrorNotificationGracePeriod<int>(
|
uploadErrorNotificationGracePeriod<int>(
|
||||||
|
StoreKey.uploadErrorNotificationGracePeriod,
|
||||||
"uploadErrorNotificationGracePeriod",
|
"uploadErrorNotificationGracePeriod",
|
||||||
2,
|
2,
|
||||||
),
|
),
|
||||||
backgroundBackupTotalProgress<bool>("backgroundBackupTotalProgress", true),
|
backgroundBackupTotalProgress<bool>(
|
||||||
backgroundBackupSingleProgress<bool>("backgroundBackupSingleProgress", false),
|
StoreKey.backgroundBackupTotalProgress,
|
||||||
storageIndicator<bool>("storageIndicator", true),
|
"backgroundBackupTotalProgress",
|
||||||
thumbnailCacheSize<int>("thumbnailCacheSize", 10000),
|
true,
|
||||||
imageCacheSize<int>("imageCacheSize", 350),
|
),
|
||||||
albumThumbnailCacheSize<int>("albumThumbnailCacheSize", 200),
|
backgroundBackupSingleProgress<bool>(
|
||||||
useExperimentalAssetGrid<bool>("useExperimentalAssetGrid", false),
|
StoreKey.backgroundBackupSingleProgress,
|
||||||
selectedAlbumSortOrder<int>("selectedAlbumSortOrder", 0);
|
"backgroundBackupSingleProgress",
|
||||||
|
false,
|
||||||
|
),
|
||||||
|
storageIndicator<bool>(StoreKey.storageIndicator, "storageIndicator", true),
|
||||||
|
thumbnailCacheSize<int>(
|
||||||
|
StoreKey.thumbnailCacheSize,
|
||||||
|
"thumbnailCacheSize",
|
||||||
|
10000,
|
||||||
|
),
|
||||||
|
imageCacheSize<int>(StoreKey.imageCacheSize, "imageCacheSize", 350),
|
||||||
|
albumThumbnailCacheSize<int>(
|
||||||
|
StoreKey.albumThumbnailCacheSize,
|
||||||
|
"albumThumbnailCacheSize",
|
||||||
|
200,
|
||||||
|
),
|
||||||
|
selectedAlbumSortOrder<int>(
|
||||||
|
StoreKey.selectedAlbumSortOrder,
|
||||||
|
"selectedAlbumSortOrder",
|
||||||
|
0,
|
||||||
|
),
|
||||||
|
;
|
||||||
|
|
||||||
const AppSettingsEnum(this.hiveKey, this.defaultValue);
|
const AppSettingsEnum(this.storeKey, this.hiveKey, this.defaultValue);
|
||||||
|
|
||||||
|
final StoreKey<T> storeKey;
|
||||||
final String hiveKey;
|
final String hiveKey;
|
||||||
final T defaultValue;
|
final T defaultValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
class AppSettingsService {
|
class AppSettingsService {
|
||||||
late final Box hiveBox;
|
T getSetting<T>(AppSettingsEnum<T> setting) {
|
||||||
|
return Store.get(setting.storeKey, setting.defaultValue);
|
||||||
AppSettingsService() {
|
|
||||||
hiveBox = Hive.box(userSettingInfoBox);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
T getSetting<T>(AppSettingsEnum<T> settingType) {
|
void setSetting<T>(AppSettingsEnum<T> setting, T value) {
|
||||||
if (!hiveBox.containsKey(settingType.hiveKey)) {
|
Store.put(setting.storeKey, value);
|
||||||
return _setDefault(settingType);
|
|
||||||
}
|
|
||||||
|
|
||||||
var result = hiveBox.get(settingType.hiveKey);
|
|
||||||
|
|
||||||
if (result is! T) {
|
|
||||||
return _setDefault(settingType);
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
setSetting<T>(AppSettingsEnum<T> settingType, T value) {
|
|
||||||
hiveBox.put(settingType.hiveKey, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
T _setDefault<T>(AppSettingsEnum<T> settingType) {
|
|
||||||
hiveBox.put(settingType.hiveKey, settingType.defaultValue);
|
|
||||||
return settingType.defaultValue;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -13,7 +13,6 @@ class AuthGuard extends AutoRouteGuard {
|
|||||||
void onNavigation(NavigationResolver resolver, StackRouter router) async {
|
void onNavigation(NavigationResolver resolver, StackRouter router) async {
|
||||||
try {
|
try {
|
||||||
var res = await _apiService.authenticationApi.validateAccessToken();
|
var res = await _apiService.authenticationApi.validateAccessToken();
|
||||||
|
|
||||||
if (res != null && res.authStatus) {
|
if (res != null && res.authStatus) {
|
||||||
resolver.next(true);
|
resolver.next(true);
|
||||||
} else {
|
} else {
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
import 'package:immich_mobile/shared/models/exif_info.dart';
|
import 'package:immich_mobile/shared/models/exif_info.dart';
|
||||||
import 'package:immich_mobile/shared/models/store.dart';
|
import 'package:immich_mobile/shared/models/store.dart';
|
||||||
import 'package:immich_mobile/shared/models/user.dart';
|
|
||||||
import 'package:immich_mobile/utils/hash.dart';
|
import 'package:immich_mobile/utils/hash.dart';
|
||||||
import 'package:isar/isar.dart';
|
import 'package:isar/isar.dart';
|
||||||
import 'package:openapi/api.dart';
|
import 'package:openapi/api.dart';
|
||||||
@ -40,7 +39,7 @@ class Asset {
|
|||||||
width = local.width,
|
width = local.width,
|
||||||
fileName = local.title!,
|
fileName = local.title!,
|
||||||
deviceId = Store.get(StoreKey.deviceIdHash),
|
deviceId = Store.get(StoreKey.deviceIdHash),
|
||||||
ownerId = Store.get<User>(StoreKey.currentUser)!.isarId,
|
ownerId = Store.get(StoreKey.currentUser).isarId,
|
||||||
fileModifiedAt = local.modifiedDateTime.toUtc(),
|
fileModifiedAt = local.modifiedDateTime.toUtc(),
|
||||||
updatedAt = local.modifiedDateTime.toUtc(),
|
updatedAt = local.modifiedDateTime.toUtc(),
|
||||||
isFavorite = local.isFavorite,
|
isFavorite = local.isFavorite,
|
||||||
|
48
mobile/lib/shared/models/logger_message.model.dart
Normal file
48
mobile/lib/shared/models/logger_message.model.dart
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
// ignore_for_file: constant_identifier_names
|
||||||
|
|
||||||
|
import 'package:isar/isar.dart';
|
||||||
|
import 'package:logging/logging.dart';
|
||||||
|
|
||||||
|
part 'logger_message.model.g.dart';
|
||||||
|
|
||||||
|
@Collection(inheritance: false)
|
||||||
|
class LoggerMessage {
|
||||||
|
Id id = Isar.autoIncrement;
|
||||||
|
String message;
|
||||||
|
@Enumerated(EnumType.ordinal)
|
||||||
|
LogLevel level = LogLevel.INFO;
|
||||||
|
DateTime createdAt;
|
||||||
|
String? context1;
|
||||||
|
String? context2;
|
||||||
|
|
||||||
|
LoggerMessage({
|
||||||
|
required this.message,
|
||||||
|
required this.level,
|
||||||
|
required this.createdAt,
|
||||||
|
required this.context1,
|
||||||
|
required this.context2,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return 'InAppLoggerMessage(message: $message, level: $level, createdAt: $createdAt)';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Log levels according to dart logging [Level]
|
||||||
|
enum LogLevel {
|
||||||
|
ALL,
|
||||||
|
FINEST,
|
||||||
|
FINER,
|
||||||
|
FINE,
|
||||||
|
CONFIG,
|
||||||
|
INFO,
|
||||||
|
WARNING,
|
||||||
|
SEVERE,
|
||||||
|
SHOUT,
|
||||||
|
OFF,
|
||||||
|
}
|
||||||
|
|
||||||
|
extension LevelExtension on Level {
|
||||||
|
LogLevel toLogLevel() => LogLevel.values[Level.LEVELS.indexOf(this)];
|
||||||
|
}
|
BIN
mobile/lib/shared/models/logger_message.model.g.dart
Normal file
BIN
mobile/lib/shared/models/logger_message.model.g.dart
Normal file
Binary file not shown.
@ -1,7 +1,6 @@
|
|||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.dart';
|
||||||
import 'package:immich_mobile/shared/models/user.dart';
|
import 'package:immich_mobile/shared/models/user.dart';
|
||||||
import 'package:isar/isar.dart';
|
import 'package:isar/isar.dart';
|
||||||
import 'dart:convert';
|
|
||||||
|
|
||||||
part 'store.g.dart';
|
part 'store.g.dart';
|
||||||
|
|
||||||
@ -26,12 +25,21 @@ class Store {
|
|||||||
return _db.writeTxn(() => _db.storeValues.clear());
|
return _db.writeTxn(() => _db.storeValues.clear());
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the stored value for the given key, or the default value if null
|
/// Returns the stored value for the given key or if null the [defaultValue]
|
||||||
static T? get<T>(StoreKey key, [T? defaultValue]) =>
|
/// Throws a [StoreKeyNotFoundException] if both are null
|
||||||
_cache[key.id] ?? defaultValue;
|
static T get<T>(StoreKey<T> key, [T? defaultValue]) {
|
||||||
|
final value = _cache[key.id] ?? defaultValue;
|
||||||
|
if (value == null) {
|
||||||
|
throw StoreKeyNotFoundException(key);
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the stored value for the given key (possibly null)
|
||||||
|
static T? tryGet<T>(StoreKey<T> key) => _cache[key.id];
|
||||||
|
|
||||||
/// Stores the value synchronously in the cache and asynchronously in the DB
|
/// Stores the value synchronously in the cache and asynchronously in the DB
|
||||||
static Future<void> put<T>(StoreKey key, T value) {
|
static Future<void> put<T>(StoreKey<T> key, T value) {
|
||||||
_cache[key.id] = value;
|
_cache[key.id] = value;
|
||||||
return _db.writeTxn(
|
return _db.writeTxn(
|
||||||
() async => _db.storeValues.put(await StoreValue._of(value, key)),
|
() async => _db.storeValues.put(await StoreValue._of(value, key)),
|
||||||
@ -39,7 +47,7 @@ class Store {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Removes the value synchronously from the cache and asynchronously from the DB
|
/// Removes the value synchronously from the cache and asynchronously from the DB
|
||||||
static Future<void> delete(StoreKey key) {
|
static Future<void> delete<T>(StoreKey<T> key) {
|
||||||
_cache[key.id] = null;
|
_cache[key.id] = null;
|
||||||
return _db.writeTxn(() => _db.storeValues.delete(key.id));
|
return _db.writeTxn(() => _db.storeValues.delete(key.id));
|
||||||
}
|
}
|
||||||
@ -58,7 +66,8 @@ class Store {
|
|||||||
static void _onChangeListener(List<StoreValue>? data) {
|
static void _onChangeListener(List<StoreValue>? data) {
|
||||||
if (data != null) {
|
if (data != null) {
|
||||||
for (StoreValue value in data) {
|
for (StoreValue value in data) {
|
||||||
_cache[value.id] = value._extract(StoreKey.values[value.id]);
|
_cache[value.id] =
|
||||||
|
value._extract(StoreKey.values.firstWhere((e) => e.id == value.id));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -72,76 +81,113 @@ class StoreValue {
|
|||||||
int? intValue;
|
int? intValue;
|
||||||
String? strValue;
|
String? strValue;
|
||||||
|
|
||||||
dynamic _extract(StoreKey key) {
|
T? _extract<T>(StoreKey<T> key) {
|
||||||
switch (key.type) {
|
switch (key.type) {
|
||||||
case int:
|
case int:
|
||||||
return key.fromDb == null
|
return intValue as T?;
|
||||||
? intValue
|
|
||||||
: key.fromDb!.call(Store._db, intValue!);
|
|
||||||
case bool:
|
case bool:
|
||||||
return intValue == null ? null : intValue! == 1;
|
return intValue == null ? null : (intValue! == 1) as T;
|
||||||
case DateTime:
|
case DateTime:
|
||||||
return intValue == null
|
return intValue == null
|
||||||
? null
|
? null
|
||||||
: DateTime.fromMicrosecondsSinceEpoch(intValue!);
|
: DateTime.fromMicrosecondsSinceEpoch(intValue!) as T;
|
||||||
case String:
|
case String:
|
||||||
return key.fromJson != null
|
return strValue as T?;
|
||||||
? key.fromJson!.call(json.decode(strValue!))
|
default:
|
||||||
: strValue;
|
if (key.fromDb != null) {
|
||||||
|
return key.fromDb!.call(Store._db, intValue!);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
throw TypeError();
|
||||||
}
|
}
|
||||||
|
|
||||||
static Future<StoreValue> _of(dynamic value, StoreKey key) async {
|
static Future<StoreValue> _of<T>(T? value, StoreKey<T> key) async {
|
||||||
int? i;
|
int? i;
|
||||||
String? s;
|
String? s;
|
||||||
switch (key.type) {
|
switch (key.type) {
|
||||||
case int:
|
case int:
|
||||||
i = (key.toDb == null ? value : await key.toDb!.call(Store._db, value));
|
i = value as int?;
|
||||||
break;
|
break;
|
||||||
case bool:
|
case bool:
|
||||||
i = value == null ? null : (value ? 1 : 0);
|
i = value == null ? null : (value == true ? 1 : 0);
|
||||||
break;
|
break;
|
||||||
case DateTime:
|
case DateTime:
|
||||||
i = value == null ? null : (value as DateTime).microsecondsSinceEpoch;
|
i = value == null ? null : (value as DateTime).microsecondsSinceEpoch;
|
||||||
break;
|
break;
|
||||||
case String:
|
case String:
|
||||||
s = key.fromJson == null ? value : json.encode(value.toJson());
|
s = value as String?;
|
||||||
break;
|
break;
|
||||||
|
default:
|
||||||
|
if (key.toDb != null) {
|
||||||
|
i = await key.toDb!.call(Store._db, value);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
throw TypeError();
|
||||||
}
|
}
|
||||||
return StoreValue(key.id, intValue: i, strValue: s);
|
return StoreValue(key.id, intValue: i, strValue: s);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class StoreKeyNotFoundException implements Exception {
|
||||||
|
final StoreKey key;
|
||||||
|
StoreKeyNotFoundException(this.key);
|
||||||
|
@override
|
||||||
|
String toString() => "Key '${key.name}' not found in Store";
|
||||||
|
}
|
||||||
|
|
||||||
/// Key for each possible value in the `Store`.
|
/// Key for each possible value in the `Store`.
|
||||||
/// Defines the data type (int, String, JSON) for each value
|
/// Defines the data type for each value
|
||||||
enum StoreKey {
|
enum StoreKey<T> {
|
||||||
userRemoteId(0),
|
userRemoteId<String>(0, type: String),
|
||||||
assetETag(1),
|
assetETag<String>(1, type: String),
|
||||||
currentUser(2, type: int, fromDb: _getUser, toDb: _toUser),
|
currentUser<User>(2, type: User, fromDb: _getUser, toDb: _toUser),
|
||||||
deviceIdHash(3, type: int),
|
deviceIdHash<int>(3, type: int),
|
||||||
deviceId(4),
|
deviceId<String>(4, type: String),
|
||||||
backupFailedSince(5, type: DateTime),
|
backupFailedSince<DateTime>(5, type: DateTime),
|
||||||
backupRequireWifi(6, type: bool),
|
backupRequireWifi<bool>(6, type: bool),
|
||||||
backupRequireCharging(7, type: bool),
|
backupRequireCharging<bool>(7, type: bool),
|
||||||
backupTriggerDelay(8, type: int);
|
backupTriggerDelay<int>(8, type: int),
|
||||||
|
githubReleaseInfo<String>(9, type: String),
|
||||||
|
serverUrl<String>(10, type: String),
|
||||||
|
accessToken<String>(11, type: String),
|
||||||
|
serverEndpoint<String>(12, type: String),
|
||||||
|
// user settings from [AppSettingsEnum] below:
|
||||||
|
loadPreview<bool>(100, type: bool),
|
||||||
|
loadOriginal<bool>(101, type: bool),
|
||||||
|
themeMode<String>(102, type: String),
|
||||||
|
tilesPerRow<int>(103, type: int),
|
||||||
|
dynamicLayout<bool>(104, type: bool),
|
||||||
|
groupAssetsBy<int>(105, type: int),
|
||||||
|
uploadErrorNotificationGracePeriod<int>(106, type: int),
|
||||||
|
backgroundBackupTotalProgress<bool>(107, type: bool),
|
||||||
|
backgroundBackupSingleProgress<bool>(108, type: bool),
|
||||||
|
storageIndicator<bool>(109, type: bool),
|
||||||
|
thumbnailCacheSize<int>(110, type: int),
|
||||||
|
imageCacheSize<int>(111, type: int),
|
||||||
|
albumThumbnailCacheSize<int>(112, type: int),
|
||||||
|
selectedAlbumSortOrder<int>(113, type: int),
|
||||||
|
;
|
||||||
|
|
||||||
const StoreKey(
|
const StoreKey(
|
||||||
this.id, {
|
this.id, {
|
||||||
this.type = String,
|
required this.type,
|
||||||
this.fromDb,
|
this.fromDb,
|
||||||
this.toDb,
|
this.toDb,
|
||||||
// ignore: unused_element
|
|
||||||
this.fromJson,
|
|
||||||
});
|
});
|
||||||
final int id;
|
final int id;
|
||||||
final Type type;
|
final Type type;
|
||||||
final dynamic Function(Isar, int)? fromDb;
|
final T? Function<T>(Isar, int)? fromDb;
|
||||||
final Future<int> Function(Isar, dynamic)? toDb;
|
final Future<int> Function<T>(Isar, T)? toDb;
|
||||||
final Function(dynamic)? fromJson;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
User? _getUser(Isar db, int i) => db.users.getSync(i);
|
T? _getUser<T>(Isar db, int i) {
|
||||||
Future<int> _toUser(Isar db, dynamic u) {
|
final User? u = db.users.getSync(i);
|
||||||
User user = (u as User);
|
return u as T?;
|
||||||
return db.users.put(user);
|
}
|
||||||
|
|
||||||
|
Future<int> _toUser<T>(Isar db, T u) {
|
||||||
|
if (u is User) {
|
||||||
|
return db.users.put(u);
|
||||||
|
}
|
||||||
|
throw TypeError();
|
||||||
}
|
}
|
||||||
|
@ -1,10 +1,9 @@
|
|||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:hive_flutter/hive_flutter.dart';
|
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:http/http.dart';
|
import 'package:http/http.dart';
|
||||||
import 'package:immich_mobile/constants/hive_box.dart';
|
import 'package:immich_mobile/shared/models/store.dart';
|
||||||
import 'package:immich_mobile/shared/views/version_announcement_overlay.dart';
|
import 'package:immich_mobile/shared/views/version_announcement_overlay.dart';
|
||||||
import 'package:logging/logging.dart';
|
import 'package:logging/logging.dart';
|
||||||
|
|
||||||
@ -13,10 +12,10 @@ class ReleaseInfoNotifier extends StateNotifier<String> {
|
|||||||
final log = Logger('ReleaseInfoNotifier');
|
final log = Logger('ReleaseInfoNotifier');
|
||||||
void checkGithubReleaseInfo() async {
|
void checkGithubReleaseInfo() async {
|
||||||
final Client client = Client();
|
final Client client = Client();
|
||||||
var box = Hive.box(hiveGithubReleaseInfoBox);
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
String? localReleaseVersion = box.get(githubReleaseInfoKey);
|
final String? localReleaseVersion =
|
||||||
|
Store.tryGet(StoreKey.githubReleaseInfo);
|
||||||
final res = await client.get(
|
final res = await client.get(
|
||||||
Uri.parse(
|
Uri.parse(
|
||||||
"https://api.github.com/repos/immich-app/immich/releases/latest",
|
"https://api.github.com/repos/immich-app/immich/releases/latest",
|
||||||
@ -48,9 +47,7 @@ class ReleaseInfoNotifier extends StateNotifier<String> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void acknowledgeNewVersion() {
|
void acknowledgeNewVersion() {
|
||||||
var box = Hive.box(hiveGithubReleaseInfoBox);
|
Store.put(StoreKey.githubReleaseInfo, state);
|
||||||
|
|
||||||
box.put(githubReleaseInfoKey, state);
|
|
||||||
VersionAnnouncementOverlayController.appLoader.hide();
|
VersionAnnouncementOverlayController.appLoader.hide();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,11 +1,10 @@
|
|||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
|
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:hive/hive.dart';
|
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:immich_mobile/constants/hive_box.dart';
|
|
||||||
import 'package:immich_mobile/modules/login/providers/authentication.provider.dart';
|
import 'package:immich_mobile/modules/login/providers/authentication.provider.dart';
|
||||||
import 'package:immich_mobile/shared/models/asset.dart';
|
import 'package:immich_mobile/shared/models/asset.dart';
|
||||||
|
import 'package:immich_mobile/shared/models/store.dart';
|
||||||
import 'package:immich_mobile/shared/providers/asset.provider.dart';
|
import 'package:immich_mobile/shared/providers/asset.provider.dart';
|
||||||
import 'package:logging/logging.dart';
|
import 'package:logging/logging.dart';
|
||||||
import 'package:openapi/api.dart';
|
import 'package:openapi/api.dart';
|
||||||
@ -58,9 +57,9 @@ class WebsocketNotifier extends StateNotifier<WebsocketState> {
|
|||||||
var authenticationState = ref.read(authenticationProvider);
|
var authenticationState = ref.read(authenticationProvider);
|
||||||
|
|
||||||
if (authenticationState.isAuthenticated) {
|
if (authenticationState.isAuthenticated) {
|
||||||
var accessToken = Hive.box(userInfoBox).get(accessTokenKey);
|
final accessToken = Store.get(StoreKey.accessToken);
|
||||||
try {
|
try {
|
||||||
var endpoint = Uri.parse(Hive.box(userInfoBox).get(serverEndpointKey));
|
final endpoint = Uri.parse(Store.get(StoreKey.serverEndpoint));
|
||||||
|
|
||||||
debugPrint("Attempting to connect to websocket");
|
debugPrint("Attempting to connect to websocket");
|
||||||
// Configure socket transports must be specified
|
// Configure socket transports must be specified
|
||||||
|
@ -1,8 +1,7 @@
|
|||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:hive/hive.dart';
|
import 'package:immich_mobile/shared/models/store.dart';
|
||||||
import 'package:immich_mobile/constants/hive_box.dart';
|
|
||||||
import 'package:immich_mobile/utils/url_helper.dart';
|
import 'package:immich_mobile/utils/url_helper.dart';
|
||||||
import 'package:openapi/api.dart';
|
import 'package:openapi/api.dart';
|
||||||
import 'package:http/http.dart';
|
import 'package:http/http.dart';
|
||||||
@ -19,13 +18,9 @@ class ApiService {
|
|||||||
late DeviceInfoApi deviceInfoApi;
|
late DeviceInfoApi deviceInfoApi;
|
||||||
|
|
||||||
ApiService() {
|
ApiService() {
|
||||||
if (Hive.isBoxOpen(userInfoBox)) {
|
final endpoint = Store.tryGet(StoreKey.serverEndpoint);
|
||||||
final endpoint = Hive.box(userInfoBox).get(serverEndpointKey) as String?;
|
if (endpoint != null && endpoint.isNotEmpty) {
|
||||||
if (endpoint != null && endpoint.isNotEmpty) {
|
setEndpoint(endpoint);
|
||||||
setEndpoint(endpoint);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
debugPrint("Cannot init ApiServer endpoint, userInfoBox not open yet.");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
String? _authToken;
|
String? _authToken;
|
||||||
@ -49,7 +44,7 @@ class ApiService {
|
|||||||
setEndpoint(endpoint);
|
setEndpoint(endpoint);
|
||||||
|
|
||||||
// Save in hivebox for next startup
|
// Save in hivebox for next startup
|
||||||
Hive.box(userInfoBox).put(serverEndpointKey, endpoint);
|
Store.put(StoreKey.serverEndpoint, endpoint);
|
||||||
return endpoint;
|
return endpoint;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5,7 +5,6 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
|
|||||||
import 'package:immich_mobile/shared/models/asset.dart';
|
import 'package:immich_mobile/shared/models/asset.dart';
|
||||||
import 'package:immich_mobile/shared/models/exif_info.dart';
|
import 'package:immich_mobile/shared/models/exif_info.dart';
|
||||||
import 'package:immich_mobile/shared/models/store.dart';
|
import 'package:immich_mobile/shared/models/store.dart';
|
||||||
import 'package:immich_mobile/shared/models/user.dart';
|
|
||||||
import 'package:immich_mobile/shared/providers/api.provider.dart';
|
import 'package:immich_mobile/shared/providers/api.provider.dart';
|
||||||
import 'package:immich_mobile/shared/providers/db.provider.dart';
|
import 'package:immich_mobile/shared/providers/db.provider.dart';
|
||||||
import 'package:immich_mobile/shared/services/api.service.dart';
|
import 'package:immich_mobile/shared/services/api.service.dart';
|
||||||
@ -44,7 +43,7 @@ class AssetService {
|
|||||||
.where()
|
.where()
|
||||||
.remoteIdIsNotNull()
|
.remoteIdIsNotNull()
|
||||||
.filter()
|
.filter()
|
||||||
.ownerIdEqualTo(Store.get<User>(StoreKey.currentUser)!.isarId)
|
.ownerIdEqualTo(Store.get(StoreKey.currentUser).isarId)
|
||||||
.count();
|
.count();
|
||||||
final List<AssetResponseDto>? dtos =
|
final List<AssetResponseDto>? dtos =
|
||||||
await _getRemoteAssets(hasCache: numOwnedRemoteAssets > 0);
|
await _getRemoteAssets(hasCache: numOwnedRemoteAssets > 0);
|
||||||
@ -63,7 +62,7 @@ class AssetService {
|
|||||||
required bool hasCache,
|
required bool hasCache,
|
||||||
}) async {
|
}) async {
|
||||||
try {
|
try {
|
||||||
final etag = hasCache ? Store.get(StoreKey.assetETag) : null;
|
final etag = hasCache ? Store.tryGet(StoreKey.assetETag) : null;
|
||||||
final Pair<List<AssetResponseDto>, String?>? remote =
|
final Pair<List<AssetResponseDto>, String?>? remote =
|
||||||
await _apiService.assetApi.getAllAssetsWithETag(eTag: etag);
|
await _apiService.assetApi.getAllAssetsWithETag(eTag: etag);
|
||||||
if (remote == null) {
|
if (remote == null) {
|
||||||
|
@ -1,15 +1,15 @@
|
|||||||
|
import 'dart:async';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:flutter/widgets.dart';
|
import 'package:flutter/widgets.dart';
|
||||||
import 'package:hive/hive.dart';
|
import 'package:immich_mobile/shared/models/logger_message.model.dart';
|
||||||
import 'package:immich_mobile/constants/hive_box.dart';
|
import 'package:isar/isar.dart';
|
||||||
import 'package:immich_mobile/shared/models/immich_logger_message.model.dart';
|
|
||||||
import 'package:logging/logging.dart';
|
import 'package:logging/logging.dart';
|
||||||
import 'package:path_provider/path_provider.dart';
|
import 'package:path_provider/path_provider.dart';
|
||||||
import 'package:share_plus/share_plus.dart';
|
import 'package:share_plus/share_plus.dart';
|
||||||
|
|
||||||
/// [ImmichLogger] is a custom logger that is built on top of the [logging] package.
|
/// [ImmichLogger] is a custom logger that is built on top of the [logging] package.
|
||||||
/// The logs are written to a Hive box and onto console, using `debugPrint` method.
|
/// The logs are written to the database and onto console, using `debugPrint` method.
|
||||||
///
|
///
|
||||||
/// The logs are deleted when exceeding the `maxLogEntries` (default 200) property
|
/// The logs are deleted when exceeding the `maxLogEntries` (default 200) property
|
||||||
/// in the class.
|
/// in the class.
|
||||||
@ -17,48 +17,61 @@ import 'package:share_plus/share_plus.dart';
|
|||||||
/// Logs can be shared by calling the `shareLogs` method, which will open a share dialog
|
/// Logs can be shared by calling the `shareLogs` method, which will open a share dialog
|
||||||
/// and generate a csv file.
|
/// and generate a csv file.
|
||||||
class ImmichLogger {
|
class ImmichLogger {
|
||||||
|
static final ImmichLogger _instance = ImmichLogger._internal();
|
||||||
final maxLogEntries = 200;
|
final maxLogEntries = 200;
|
||||||
final Box<ImmichLoggerMessage> _box = Hive.box(immichLoggerBox);
|
final Isar _db = Isar.getInstance()!;
|
||||||
|
final List<LoggerMessage> _msgBuffer = [];
|
||||||
|
Timer? _timer;
|
||||||
|
|
||||||
List<ImmichLoggerMessage> get messages =>
|
factory ImmichLogger() => _instance;
|
||||||
_box.values.toList().reversed.toList();
|
|
||||||
|
|
||||||
ImmichLogger() {
|
ImmichLogger._internal() {
|
||||||
_removeOverflowMessages();
|
_removeOverflowMessages();
|
||||||
}
|
|
||||||
|
|
||||||
init() {
|
|
||||||
Logger.root.level = Level.INFO;
|
Logger.root.level = Level.INFO;
|
||||||
Logger.root.onRecord.listen(_writeLogToHiveBox);
|
Logger.root.onRecord.listen(_writeLogToDatabase);
|
||||||
}
|
}
|
||||||
|
|
||||||
_removeOverflowMessages() {
|
List<LoggerMessage> get messages {
|
||||||
if (_box.length > maxLogEntries) {
|
final inDb =
|
||||||
var numberOfEntryToBeDeleted = _box.length - maxLogEntries;
|
_db.loggerMessages.where(sort: Sort.desc).anyId().findAllSync();
|
||||||
for (var i = 0; i < numberOfEntryToBeDeleted; i++) {
|
return _msgBuffer.isEmpty ? inDb : _msgBuffer.reversed.toList() + inDb;
|
||||||
_box.deleteAt(0);
|
}
|
||||||
}
|
|
||||||
|
void _removeOverflowMessages() {
|
||||||
|
final msgCount = _db.loggerMessages.countSync();
|
||||||
|
if (msgCount > maxLogEntries) {
|
||||||
|
final numberOfEntryToBeDeleted = msgCount - maxLogEntries;
|
||||||
|
_db.loggerMessages.where().limit(numberOfEntryToBeDeleted).deleteAll();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_writeLogToHiveBox(LogRecord record) {
|
void _writeLogToDatabase(LogRecord record) {
|
||||||
final Box<ImmichLoggerMessage> box = Hive.box(immichLoggerBox);
|
|
||||||
var formattedMessage = record.message;
|
|
||||||
|
|
||||||
debugPrint('[${record.level.name}] [${record.time}] ${record.message}');
|
debugPrint('[${record.level.name}] [${record.time}] ${record.message}');
|
||||||
box.add(
|
final lm = LoggerMessage(
|
||||||
ImmichLoggerMessage(
|
message: record.message,
|
||||||
message: formattedMessage,
|
level: record.level.toLogLevel(),
|
||||||
level: record.level.name,
|
createdAt: record.time,
|
||||||
createdAt: record.time,
|
context1: record.loggerName,
|
||||||
context1: record.loggerName,
|
context2: record.stackTrace?.toString(),
|
||||||
context2: record.stackTrace?.toString(),
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
|
_msgBuffer.add(lm);
|
||||||
|
|
||||||
|
// delayed batch writing to database: increases performance when logging
|
||||||
|
// messages in quick succession and reduces NAND wear
|
||||||
|
_timer ??= Timer(const Duration(seconds: 5), _flushBufferToDatabase);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _flushBufferToDatabase() {
|
||||||
|
_timer = null;
|
||||||
|
_db.writeTxnSync(() => _db.loggerMessages.putAllSync(_msgBuffer));
|
||||||
|
_msgBuffer.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
void clearLogs() {
|
void clearLogs() {
|
||||||
_box.clear();
|
_timer?.cancel();
|
||||||
|
_timer = null;
|
||||||
|
_msgBuffer.clear();
|
||||||
|
_db.writeTxn(() => _db.loggerMessages.clear());
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> shareLogs() async {
|
Future<void> shareLogs() async {
|
||||||
@ -93,4 +106,12 @@ class ImmichLogger {
|
|||||||
// Clean up temp file
|
// Clean up temp file
|
||||||
await logFile.delete();
|
await logFile.delete();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Flush pending log messages to persistent storage
|
||||||
|
void flush() {
|
||||||
|
if (_timer != null) {
|
||||||
|
_timer!.cancel();
|
||||||
|
_flushBufferToDatabase();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -241,7 +241,7 @@ class SyncService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (album.shared || dto.shared) {
|
if (album.shared || dto.shared) {
|
||||||
final userId = Store.get<User>(StoreKey.currentUser)!.isarId;
|
final userId = Store.get(StoreKey.currentUser).isarId;
|
||||||
final foreign =
|
final foreign =
|
||||||
await album.assets.filter().not().ownerIdEqualTo(userId).findAll();
|
await album.assets.filter().not().ownerIdEqualTo(userId).findAll();
|
||||||
existing.addAll(foreign);
|
existing.addAll(foreign);
|
||||||
|
@ -42,7 +42,7 @@ class UserService {
|
|||||||
if (self) {
|
if (self) {
|
||||||
return _db.users.where().findAll();
|
return _db.users.where().findAll();
|
||||||
}
|
}
|
||||||
final int userId = Store.get<User>(StoreKey.currentUser)!.isarId;
|
final int userId = Store.get(StoreKey.currentUser).isarId;
|
||||||
return _db.users.where().isarIdNotEqualTo(userId).findAll();
|
return _db.users.where().isarIdNotEqualTo(userId).findAll();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,9 +1,8 @@
|
|||||||
import 'package:cached_network_image/cached_network_image.dart';
|
import 'package:cached_network_image/cached_network_image.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:hive_flutter/hive_flutter.dart';
|
|
||||||
import 'package:immich_mobile/constants/hive_box.dart';
|
|
||||||
import 'package:immich_mobile/shared/models/asset.dart';
|
import 'package:immich_mobile/shared/models/asset.dart';
|
||||||
|
import 'package:immich_mobile/shared/models/store.dart';
|
||||||
import 'package:immich_mobile/utils/image_url_builder.dart';
|
import 'package:immich_mobile/utils/image_url_builder.dart';
|
||||||
import 'package:photo_manager/photo_manager.dart';
|
import 'package:photo_manager/photo_manager.dart';
|
||||||
|
|
||||||
@ -84,7 +83,7 @@ class ImmichImage extends StatelessWidget {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
final String? token = Hive.box(userInfoBox).get(accessTokenKey);
|
final String? token = Store.get(StoreKey.accessToken);
|
||||||
final String thumbnailRequestUrl = getThumbnailUrl(asset);
|
final String thumbnailRequestUrl = getThumbnailUrl(asset);
|
||||||
return CachedNetworkImage(
|
return CachedNetworkImage(
|
||||||
imageUrl: thumbnailRequestUrl,
|
imageUrl: thumbnailRequestUrl,
|
||||||
|
@ -2,6 +2,7 @@ import 'package:auto_route/auto_route.dart';
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
|
import 'package:immich_mobile/shared/models/logger_message.model.dart';
|
||||||
import 'package:immich_mobile/shared/services/immich_logger.service.dart';
|
import 'package:immich_mobile/shared/services/immich_logger.service.dart';
|
||||||
import 'package:intl/intl.dart';
|
import 'package:intl/intl.dart';
|
||||||
|
|
||||||
@ -31,29 +32,29 @@ class AppLogPage extends HookConsumerWidget {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget buildLeadingIcon(String level) {
|
Widget buildLeadingIcon(LogLevel level) {
|
||||||
switch (level) {
|
switch (level) {
|
||||||
case "INFO":
|
case LogLevel.INFO:
|
||||||
return colorStatusIndicator(Theme.of(context).primaryColor);
|
return colorStatusIndicator(Theme.of(context).primaryColor);
|
||||||
case "SEVERE":
|
case LogLevel.SEVERE:
|
||||||
return colorStatusIndicator(Colors.redAccent);
|
return colorStatusIndicator(Colors.redAccent);
|
||||||
|
|
||||||
case "WARNING":
|
case LogLevel.WARNING:
|
||||||
return colorStatusIndicator(Colors.orangeAccent);
|
return colorStatusIndicator(Colors.orangeAccent);
|
||||||
default:
|
default:
|
||||||
return colorStatusIndicator(Colors.grey);
|
return colorStatusIndicator(Colors.grey);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
getTileColor(String level) {
|
getTileColor(LogLevel level) {
|
||||||
switch (level) {
|
switch (level) {
|
||||||
case "INFO":
|
case LogLevel.INFO:
|
||||||
return Colors.transparent;
|
return Colors.transparent;
|
||||||
case "SEVERE":
|
case LogLevel.SEVERE:
|
||||||
return Theme.of(context).brightness == Brightness.dark
|
return Theme.of(context).brightness == Brightness.dark
|
||||||
? Colors.redAccent.withOpacity(0.25)
|
? Colors.redAccent.withOpacity(0.25)
|
||||||
: Colors.redAccent.withOpacity(0.075);
|
: Colors.redAccent.withOpacity(0.075);
|
||||||
case "WARNING":
|
case LogLevel.WARNING:
|
||||||
return Theme.of(context).brightness == Brightness.dark
|
return Theme.of(context).brightness == Brightness.dark
|
||||||
? Colors.orangeAccent.withOpacity(0.25)
|
? Colors.orangeAccent.withOpacity(0.25)
|
||||||
: Colors.orangeAccent.withOpacity(0.075);
|
: Colors.orangeAccent.withOpacity(0.075);
|
||||||
|
@ -1,14 +1,12 @@
|
|||||||
import 'package:auto_route/auto_route.dart';
|
import 'package:auto_route/auto_route.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
import 'package:flutter_hooks/flutter_hooks.dart' hide Store;
|
||||||
import 'package:hive_flutter/hive_flutter.dart';
|
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:immich_mobile/constants/hive_box.dart';
|
|
||||||
import 'package:immich_mobile/modules/backup/providers/backup.provider.dart';
|
import 'package:immich_mobile/modules/backup/providers/backup.provider.dart';
|
||||||
import 'package:immich_mobile/modules/login/models/hive_saved_login_info.model.dart';
|
|
||||||
import 'package:immich_mobile/modules/login/providers/authentication.provider.dart';
|
import 'package:immich_mobile/modules/login/providers/authentication.provider.dart';
|
||||||
import 'package:immich_mobile/modules/onboarding/providers/gallery_permission.provider.dart';
|
import 'package:immich_mobile/modules/onboarding/providers/gallery_permission.provider.dart';
|
||||||
import 'package:immich_mobile/routing/router.dart';
|
import 'package:immich_mobile/routing/router.dart';
|
||||||
|
import 'package:immich_mobile/shared/models/store.dart';
|
||||||
import 'package:immich_mobile/shared/providers/api.provider.dart';
|
import 'package:immich_mobile/shared/providers/api.provider.dart';
|
||||||
|
|
||||||
class SplashScreenPage extends HookConsumerWidget {
|
class SplashScreenPage extends HookConsumerWidget {
|
||||||
@ -17,23 +15,23 @@ class SplashScreenPage extends HookConsumerWidget {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
final apiService = ref.watch(apiServiceProvider);
|
final apiService = ref.watch(apiServiceProvider);
|
||||||
HiveSavedLoginInfo? loginInfo =
|
final serverUrl = Store.tryGet(StoreKey.serverUrl);
|
||||||
Hive.box<HiveSavedLoginInfo>(hiveLoginInfoBox).get(savedLoginInfoKey);
|
final accessToken = Store.tryGet(StoreKey.accessToken);
|
||||||
|
|
||||||
void performLoggingIn() async {
|
void performLoggingIn() async {
|
||||||
bool isSuccess = false;
|
bool isSuccess = false;
|
||||||
if (loginInfo != null) {
|
if (accessToken != null && serverUrl != null) {
|
||||||
try {
|
try {
|
||||||
// Resolve API server endpoint from user provided serverUrl
|
// Resolve API server endpoint from user provided serverUrl
|
||||||
await apiService.resolveAndSetEndpoint(loginInfo.serverUrl);
|
await apiService.resolveAndSetEndpoint(serverUrl);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// okay, try to continue anyway if offline
|
// okay, try to continue anyway if offline
|
||||||
}
|
}
|
||||||
|
|
||||||
isSuccess =
|
isSuccess =
|
||||||
await ref.read(authenticationProvider.notifier).setSuccessLoginInfo(
|
await ref.read(authenticationProvider.notifier).setSuccessLoginInfo(
|
||||||
accessToken: loginInfo.accessToken,
|
accessToken: accessToken,
|
||||||
serverUrl: loginInfo.serverUrl,
|
serverUrl: serverUrl,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (isSuccess) {
|
if (isSuccess) {
|
||||||
@ -51,7 +49,7 @@ class SplashScreenPage extends HookConsumerWidget {
|
|||||||
|
|
||||||
useEffect(
|
useEffect(
|
||||||
() {
|
() {
|
||||||
if (loginInfo != null) {
|
if (serverUrl != null && accessToken != null) {
|
||||||
performLoggingIn();
|
performLoggingIn();
|
||||||
} else {
|
} else {
|
||||||
AutoRouter.of(context).replace(const LoginRoute());
|
AutoRouter.of(context).replace(const LoginRoute());
|
||||||
|
@ -1,10 +1,8 @@
|
|||||||
import 'package:hive/hive.dart';
|
|
||||||
import 'package:immich_mobile/shared/models/album.dart';
|
import 'package:immich_mobile/shared/models/album.dart';
|
||||||
import 'package:immich_mobile/shared/models/asset.dart';
|
import 'package:immich_mobile/shared/models/asset.dart';
|
||||||
|
import 'package:immich_mobile/shared/models/store.dart';
|
||||||
import 'package:openapi/api.dart';
|
import 'package:openapi/api.dart';
|
||||||
|
|
||||||
import '../constants/hive_box.dart';
|
|
||||||
|
|
||||||
String getThumbnailUrl(
|
String getThumbnailUrl(
|
||||||
final Asset asset, {
|
final Asset asset, {
|
||||||
ThumbnailFormat type = ThumbnailFormat.WEBP,
|
ThumbnailFormat type = ThumbnailFormat.WEBP,
|
||||||
@ -48,8 +46,7 @@ String getAlbumThumbNailCacheKey(
|
|||||||
}
|
}
|
||||||
|
|
||||||
String getImageUrl(final Asset asset) {
|
String getImageUrl(final Asset asset) {
|
||||||
final box = Hive.box(userInfoBox);
|
return '${Store.get(StoreKey.serverEndpoint)}/asset/file/${asset.remoteId}?isThumb=false';
|
||||||
return '${box.get(serverEndpointKey)}/asset/file/${asset.remoteId}?isThumb=false';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
String getImageCacheKey(final Asset asset) {
|
String getImageCacheKey(final Asset asset) {
|
||||||
@ -60,7 +57,5 @@ String _getThumbnailUrl(
|
|||||||
final String id, {
|
final String id, {
|
||||||
ThumbnailFormat type = ThumbnailFormat.WEBP,
|
ThumbnailFormat type = ThumbnailFormat.WEBP,
|
||||||
}) {
|
}) {
|
||||||
final box = Hive.box(userInfoBox);
|
return '${Store.get(StoreKey.serverEndpoint)}/asset/thumbnail/$id?format=${type.value}';
|
||||||
|
|
||||||
return '${box.get(serverEndpointKey)}/asset/thumbnail/$id?format=${type.value}';
|
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
// ignore_for_file: deprecated_member_use_from_same_package
|
// ignore_for_file: deprecated_member_use_from_same_package
|
||||||
|
|
||||||
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:flutter/cupertino.dart';
|
import 'package:flutter/cupertino.dart';
|
||||||
import 'package:hive/hive.dart';
|
import 'package:hive/hive.dart';
|
||||||
import 'package:immich_mobile/constants/hive_box.dart';
|
import 'package:immich_mobile/constants/hive_box.dart';
|
||||||
@ -8,6 +10,9 @@ import 'package:immich_mobile/modules/backup/models/backup_album.model.dart';
|
|||||||
import 'package:immich_mobile/modules/backup/models/duplicated_asset.model.dart';
|
import 'package:immich_mobile/modules/backup/models/duplicated_asset.model.dart';
|
||||||
import 'package:immich_mobile/modules/backup/models/hive_backup_albums.model.dart';
|
import 'package:immich_mobile/modules/backup/models/hive_backup_albums.model.dart';
|
||||||
import 'package:immich_mobile/modules/backup/models/hive_duplicated_assets.model.dart';
|
import 'package:immich_mobile/modules/backup/models/hive_duplicated_assets.model.dart';
|
||||||
|
import 'package:immich_mobile/modules/login/models/hive_saved_login_info.model.dart';
|
||||||
|
import 'package:immich_mobile/modules/settings/services/app_settings.service.dart';
|
||||||
|
import 'package:immich_mobile/shared/models/immich_logger_message.model.dart';
|
||||||
import 'package:immich_mobile/shared/models/store.dart';
|
import 'package:immich_mobile/shared/models/store.dart';
|
||||||
import 'package:immich_mobile/shared/services/asset_cache.service.dart';
|
import 'package:immich_mobile/shared/services/asset_cache.service.dart';
|
||||||
import 'package:isar/isar.dart';
|
import 'package:isar/isar.dart';
|
||||||
@ -23,11 +28,37 @@ Future<void> migrateHiveToStoreIfNecessary() async {
|
|||||||
duplicatedAssetsBox,
|
duplicatedAssetsBox,
|
||||||
_migrateDuplicatedAssetsBox,
|
_migrateDuplicatedAssetsBox,
|
||||||
);
|
);
|
||||||
|
await _migrateHiveBoxIfNecessary(
|
||||||
|
hiveGithubReleaseInfoBox,
|
||||||
|
_migrateReleaseInfoBox,
|
||||||
|
);
|
||||||
|
|
||||||
|
await _migrateHiveBoxIfNecessary(hiveLoginInfoBox, _migrateLoginInfoBox);
|
||||||
|
await _migrateHiveBoxIfNecessary(
|
||||||
|
immichLoggerBox,
|
||||||
|
(Box<ImmichLoggerMessage> box) => box.deleteFromDisk(),
|
||||||
|
);
|
||||||
|
await _migrateHiveBoxIfNecessary(userSettingInfoBox, _migrateAppSettingsBox);
|
||||||
|
}
|
||||||
|
|
||||||
|
FutureOr<void> _migrateReleaseInfoBox(Box box) =>
|
||||||
|
_migrateKey(box, githubReleaseInfoKey, StoreKey.githubReleaseInfo);
|
||||||
|
|
||||||
|
Future<void> _migrateLoginInfoBox(Box<HiveSavedLoginInfo> box) async {
|
||||||
|
final HiveSavedLoginInfo? info = box.get(savedLoginInfoKey);
|
||||||
|
if (info != null) {
|
||||||
|
await Store.put(StoreKey.serverUrl, info.serverUrl);
|
||||||
|
await Store.put(StoreKey.accessToken, info.accessToken);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _migrateHiveUserInfoBox(Box box) async {
|
Future<void> _migrateHiveUserInfoBox(Box box) async {
|
||||||
await _migrateKey(box, userIdKey, StoreKey.userRemoteId);
|
await _migrateKey(box, userIdKey, StoreKey.userRemoteId);
|
||||||
await _migrateKey(box, assetEtagKey, StoreKey.assetETag);
|
await _migrateKey(box, assetEtagKey, StoreKey.assetETag);
|
||||||
|
if (Store.tryGet(StoreKey.deviceId) == null) {
|
||||||
|
await _migrateKey(box, deviceIdKey, StoreKey.deviceId);
|
||||||
|
}
|
||||||
|
await _migrateKey(box, serverEndpointKey, StoreKey.serverEndpoint);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _migrateHiveBackgroundBackupInfoBox(Box box) async {
|
Future<void> _migrateHiveBackgroundBackupInfoBox(Box box) async {
|
||||||
@ -35,16 +66,15 @@ Future<void> _migrateHiveBackgroundBackupInfoBox(Box box) async {
|
|||||||
await _migrateKey(box, backupRequireWifi, StoreKey.backupRequireWifi);
|
await _migrateKey(box, backupRequireWifi, StoreKey.backupRequireWifi);
|
||||||
await _migrateKey(box, backupRequireCharging, StoreKey.backupRequireCharging);
|
await _migrateKey(box, backupRequireCharging, StoreKey.backupRequireCharging);
|
||||||
await _migrateKey(box, backupTriggerDelay, StoreKey.backupTriggerDelay);
|
await _migrateKey(box, backupTriggerDelay, StoreKey.backupTriggerDelay);
|
||||||
return box.deleteFromDisk();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _migrateBackupInfoBox(Box<HiveBackupAlbums> box) async {
|
FutureOr<void> _migrateBackupInfoBox(Box<HiveBackupAlbums> box) {
|
||||||
final Isar? db = Isar.getInstance();
|
|
||||||
if (db == null) {
|
|
||||||
throw Exception("_migrateBackupInfoBox could not load database");
|
|
||||||
}
|
|
||||||
final HiveBackupAlbums? infos = box.get(backupInfoKey);
|
final HiveBackupAlbums? infos = box.get(backupInfoKey);
|
||||||
if (infos != null) {
|
if (infos != null) {
|
||||||
|
final Isar? db = Isar.getInstance();
|
||||||
|
if (db == null) {
|
||||||
|
throw Exception("_migrateBackupInfoBox could not load database");
|
||||||
|
}
|
||||||
List<BackupAlbum> albums = [];
|
List<BackupAlbum> albums = [];
|
||||||
for (int i = 0; i < infos.selectedAlbumIds.length; i++) {
|
for (int i = 0; i < infos.selectedAlbumIds.length; i++) {
|
||||||
final album = BackupAlbum(
|
final album = BackupAlbum(
|
||||||
@ -62,48 +92,49 @@ Future<void> _migrateBackupInfoBox(Box<HiveBackupAlbums> box) async {
|
|||||||
);
|
);
|
||||||
albums.add(album);
|
albums.add(album);
|
||||||
}
|
}
|
||||||
await db.writeTxn(() => db.backupAlbums.putAll(albums));
|
return db.writeTxn(() => db.backupAlbums.putAll(albums));
|
||||||
} else {
|
|
||||||
debugPrint("_migrateBackupInfoBox deletes empty box");
|
|
||||||
}
|
}
|
||||||
return box.deleteFromDisk();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _migrateDuplicatedAssetsBox(Box<HiveDuplicatedAssets> box) async {
|
FutureOr<void> _migrateDuplicatedAssetsBox(Box<HiveDuplicatedAssets> box) {
|
||||||
final Isar? db = Isar.getInstance();
|
|
||||||
if (db == null) {
|
|
||||||
throw Exception("_migrateBackupInfoBox could not load database");
|
|
||||||
}
|
|
||||||
final HiveDuplicatedAssets? duplicatedAssets = box.get(duplicatedAssetsKey);
|
final HiveDuplicatedAssets? duplicatedAssets = box.get(duplicatedAssetsKey);
|
||||||
if (duplicatedAssets != null) {
|
if (duplicatedAssets != null) {
|
||||||
|
final Isar? db = Isar.getInstance();
|
||||||
|
if (db == null) {
|
||||||
|
throw Exception("_migrateBackupInfoBox could not load database");
|
||||||
|
}
|
||||||
final duplicatedAssetIds = duplicatedAssets.duplicatedAssetIds
|
final duplicatedAssetIds = duplicatedAssets.duplicatedAssetIds
|
||||||
.map((id) => DuplicatedAsset(id))
|
.map((id) => DuplicatedAsset(id))
|
||||||
.toList();
|
.toList();
|
||||||
await db.writeTxn(() => db.duplicatedAssets.putAll(duplicatedAssetIds));
|
return db.writeTxn(() => db.duplicatedAssets.putAll(duplicatedAssetIds));
|
||||||
} else {
|
}
|
||||||
debugPrint("_migrateDuplicatedAssetsBox deletes empty box");
|
}
|
||||||
|
|
||||||
|
Future<void> _migrateAppSettingsBox(Box box) async {
|
||||||
|
for (AppSettingsEnum s in AppSettingsEnum.values) {
|
||||||
|
await _migrateKey(box, s.hiveKey, s.storeKey);
|
||||||
}
|
}
|
||||||
return box.deleteFromDisk();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _migrateHiveBoxIfNecessary<T>(
|
Future<void> _migrateHiveBoxIfNecessary<T>(
|
||||||
String boxName,
|
String boxName,
|
||||||
Future<void> Function(Box<T>) migrate,
|
FutureOr<void> Function(Box<T>) migrate,
|
||||||
) async {
|
) async {
|
||||||
try {
|
try {
|
||||||
if (await Hive.boxExists(boxName)) {
|
if (await Hive.boxExists(boxName)) {
|
||||||
await migrate(await Hive.openBox<T>(boxName));
|
final box = await Hive.openBox<T>(boxName);
|
||||||
|
await migrate(box);
|
||||||
|
await box.deleteFromDisk();
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
debugPrint("Error while migrating $boxName $e");
|
debugPrint("Error while migrating $boxName $e");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_migrateKey(Box box, String hiveKey, StoreKey key) async {
|
FutureOr<void> _migrateKey<T>(Box box, String hiveKey, StoreKey<T> key) {
|
||||||
final String? value = box.get(hiveKey);
|
final T? value = box.get(hiveKey);
|
||||||
if (value != null) {
|
if (value != null) {
|
||||||
await Store.put(key, value);
|
return Store.put(key, value);
|
||||||
await box.delete(hiveKey);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user