mirror of
https://github.com/KevinMidboe/immich.git
synced 2025-10-29 17:40:28 +00:00
refactor(mobile): migrate all Hive boxes to Isar database (#2036)
This commit is contained in:
committed by
GitHub
parent
0616a66b05
commit
eccde8fa07
@@ -1,6 +1,5 @@
|
||||
import 'package:immich_mobile/shared/models/exif_info.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:isar/isar.dart';
|
||||
import 'package:openapi/api.dart';
|
||||
@@ -40,7 +39,7 @@ class Asset {
|
||||
width = local.width,
|
||||
fileName = local.title!,
|
||||
deviceId = Store.get(StoreKey.deviceIdHash),
|
||||
ownerId = Store.get<User>(StoreKey.currentUser)!.isarId,
|
||||
ownerId = Store.get(StoreKey.currentUser).isarId,
|
||||
fileModifiedAt = local.modifiedDateTime.toUtc(),
|
||||
updatedAt = local.modifiedDateTime.toUtc(),
|
||||
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)];
|
||||
}
|
||||
1092
mobile/lib/shared/models/logger_message.model.g.dart
Normal file
1092
mobile/lib/shared/models/logger_message.model.g.dart
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,7 +1,6 @@
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:immich_mobile/shared/models/user.dart';
|
||||
import 'package:isar/isar.dart';
|
||||
import 'dart:convert';
|
||||
|
||||
part 'store.g.dart';
|
||||
|
||||
@@ -26,12 +25,21 @@ class Store {
|
||||
return _db.writeTxn(() => _db.storeValues.clear());
|
||||
}
|
||||
|
||||
/// Returns the stored value for the given key, or the default value if null
|
||||
static T? get<T>(StoreKey key, [T? defaultValue]) =>
|
||||
_cache[key.id] ?? defaultValue;
|
||||
/// Returns the stored value for the given key or if null the [defaultValue]
|
||||
/// Throws a [StoreKeyNotFoundException] if both are null
|
||||
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
|
||||
static Future<void> put<T>(StoreKey key, T value) {
|
||||
static Future<void> put<T>(StoreKey<T> key, T value) {
|
||||
_cache[key.id] = value;
|
||||
return _db.writeTxn(
|
||||
() 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
|
||||
static Future<void> delete(StoreKey key) {
|
||||
static Future<void> delete<T>(StoreKey<T> key) {
|
||||
_cache[key.id] = null;
|
||||
return _db.writeTxn(() => _db.storeValues.delete(key.id));
|
||||
}
|
||||
@@ -58,7 +66,8 @@ class Store {
|
||||
static void _onChangeListener(List<StoreValue>? data) {
|
||||
if (data != null) {
|
||||
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;
|
||||
String? strValue;
|
||||
|
||||
dynamic _extract(StoreKey key) {
|
||||
T? _extract<T>(StoreKey<T> key) {
|
||||
switch (key.type) {
|
||||
case int:
|
||||
return key.fromDb == null
|
||||
? intValue
|
||||
: key.fromDb!.call(Store._db, intValue!);
|
||||
return intValue as T?;
|
||||
case bool:
|
||||
return intValue == null ? null : intValue! == 1;
|
||||
return intValue == null ? null : (intValue! == 1) as T;
|
||||
case DateTime:
|
||||
return intValue == null
|
||||
? null
|
||||
: DateTime.fromMicrosecondsSinceEpoch(intValue!);
|
||||
: DateTime.fromMicrosecondsSinceEpoch(intValue!) as T;
|
||||
case String:
|
||||
return key.fromJson != null
|
||||
? key.fromJson!.call(json.decode(strValue!))
|
||||
: strValue;
|
||||
return strValue as T?;
|
||||
default:
|
||||
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;
|
||||
String? s;
|
||||
switch (key.type) {
|
||||
case int:
|
||||
i = (key.toDb == null ? value : await key.toDb!.call(Store._db, value));
|
||||
i = value as int?;
|
||||
break;
|
||||
case bool:
|
||||
i = value == null ? null : (value ? 1 : 0);
|
||||
i = value == null ? null : (value == true ? 1 : 0);
|
||||
break;
|
||||
case DateTime:
|
||||
i = value == null ? null : (value as DateTime).microsecondsSinceEpoch;
|
||||
break;
|
||||
case String:
|
||||
s = key.fromJson == null ? value : json.encode(value.toJson());
|
||||
s = value as String?;
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
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`.
|
||||
/// Defines the data type (int, String, JSON) for each value
|
||||
enum StoreKey {
|
||||
userRemoteId(0),
|
||||
assetETag(1),
|
||||
currentUser(2, type: int, fromDb: _getUser, toDb: _toUser),
|
||||
deviceIdHash(3, type: int),
|
||||
deviceId(4),
|
||||
backupFailedSince(5, type: DateTime),
|
||||
backupRequireWifi(6, type: bool),
|
||||
backupRequireCharging(7, type: bool),
|
||||
backupTriggerDelay(8, type: int);
|
||||
/// Defines the data type for each value
|
||||
enum StoreKey<T> {
|
||||
userRemoteId<String>(0, type: String),
|
||||
assetETag<String>(1, type: String),
|
||||
currentUser<User>(2, type: User, fromDb: _getUser, toDb: _toUser),
|
||||
deviceIdHash<int>(3, type: int),
|
||||
deviceId<String>(4, type: String),
|
||||
backupFailedSince<DateTime>(5, type: DateTime),
|
||||
backupRequireWifi<bool>(6, type: bool),
|
||||
backupRequireCharging<bool>(7, type: bool),
|
||||
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(
|
||||
this.id, {
|
||||
this.type = String,
|
||||
required this.type,
|
||||
this.fromDb,
|
||||
this.toDb,
|
||||
// ignore: unused_element
|
||||
this.fromJson,
|
||||
});
|
||||
final int id;
|
||||
final Type type;
|
||||
final dynamic Function(Isar, int)? fromDb;
|
||||
final Future<int> Function(Isar, dynamic)? toDb;
|
||||
final Function(dynamic)? fromJson;
|
||||
final T? Function<T>(Isar, int)? fromDb;
|
||||
final Future<int> Function<T>(Isar, T)? toDb;
|
||||
}
|
||||
|
||||
User? _getUser(Isar db, int i) => db.users.getSync(i);
|
||||
Future<int> _toUser(Isar db, dynamic u) {
|
||||
User user = (u as User);
|
||||
return db.users.put(user);
|
||||
T? _getUser<T>(Isar db, int i) {
|
||||
final User? u = db.users.getSync(i);
|
||||
return u as T?;
|
||||
}
|
||||
|
||||
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 'package:flutter/material.dart';
|
||||
import 'package:hive_flutter/hive_flutter.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.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:logging/logging.dart';
|
||||
|
||||
@@ -13,10 +12,10 @@ class ReleaseInfoNotifier extends StateNotifier<String> {
|
||||
final log = Logger('ReleaseInfoNotifier');
|
||||
void checkGithubReleaseInfo() async {
|
||||
final Client client = Client();
|
||||
var box = Hive.box(hiveGithubReleaseInfoBox);
|
||||
|
||||
try {
|
||||
String? localReleaseVersion = box.get(githubReleaseInfoKey);
|
||||
final String? localReleaseVersion =
|
||||
Store.tryGet(StoreKey.githubReleaseInfo);
|
||||
final res = await client.get(
|
||||
Uri.parse(
|
||||
"https://api.github.com/repos/immich-app/immich/releases/latest",
|
||||
@@ -48,9 +47,7 @@ class ReleaseInfoNotifier extends StateNotifier<String> {
|
||||
}
|
||||
|
||||
void acknowledgeNewVersion() {
|
||||
var box = Hive.box(hiveGithubReleaseInfoBox);
|
||||
|
||||
box.put(githubReleaseInfoKey, state);
|
||||
Store.put(StoreKey.githubReleaseInfo, state);
|
||||
VersionAnnouncementOverlayController.appLoader.hide();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:hive/hive.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/shared/models/asset.dart';
|
||||
import 'package:immich_mobile/shared/models/store.dart';
|
||||
import 'package:immich_mobile/shared/providers/asset.provider.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:openapi/api.dart';
|
||||
@@ -58,9 +57,9 @@ class WebsocketNotifier extends StateNotifier<WebsocketState> {
|
||||
var authenticationState = ref.read(authenticationProvider);
|
||||
|
||||
if (authenticationState.isAuthenticated) {
|
||||
var accessToken = Hive.box(userInfoBox).get(accessTokenKey);
|
||||
final accessToken = Store.get(StoreKey.accessToken);
|
||||
try {
|
||||
var endpoint = Uri.parse(Hive.box(userInfoBox).get(serverEndpointKey));
|
||||
final endpoint = Uri.parse(Store.get(StoreKey.serverEndpoint));
|
||||
|
||||
debugPrint("Attempting to connect to websocket");
|
||||
// Configure socket transports must be specified
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:immich_mobile/constants/hive_box.dart';
|
||||
import 'package:immich_mobile/shared/models/store.dart';
|
||||
import 'package:immich_mobile/utils/url_helper.dart';
|
||||
import 'package:openapi/api.dart';
|
||||
import 'package:http/http.dart';
|
||||
@@ -19,13 +18,9 @@ class ApiService {
|
||||
late DeviceInfoApi deviceInfoApi;
|
||||
|
||||
ApiService() {
|
||||
if (Hive.isBoxOpen(userInfoBox)) {
|
||||
final endpoint = Hive.box(userInfoBox).get(serverEndpointKey) as String?;
|
||||
if (endpoint != null && endpoint.isNotEmpty) {
|
||||
setEndpoint(endpoint);
|
||||
}
|
||||
} else {
|
||||
debugPrint("Cannot init ApiServer endpoint, userInfoBox not open yet.");
|
||||
final endpoint = Store.tryGet(StoreKey.serverEndpoint);
|
||||
if (endpoint != null && endpoint.isNotEmpty) {
|
||||
setEndpoint(endpoint);
|
||||
}
|
||||
}
|
||||
String? _authToken;
|
||||
@@ -49,7 +44,7 @@ class ApiService {
|
||||
setEndpoint(endpoint);
|
||||
|
||||
// Save in hivebox for next startup
|
||||
Hive.box(userInfoBox).put(serverEndpointKey, endpoint);
|
||||
Store.put(StoreKey.serverEndpoint, 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/exif_info.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/db.provider.dart';
|
||||
import 'package:immich_mobile/shared/services/api.service.dart';
|
||||
@@ -44,7 +43,7 @@ class AssetService {
|
||||
.where()
|
||||
.remoteIdIsNotNull()
|
||||
.filter()
|
||||
.ownerIdEqualTo(Store.get<User>(StoreKey.currentUser)!.isarId)
|
||||
.ownerIdEqualTo(Store.get(StoreKey.currentUser).isarId)
|
||||
.count();
|
||||
final List<AssetResponseDto>? dtos =
|
||||
await _getRemoteAssets(hasCache: numOwnedRemoteAssets > 0);
|
||||
@@ -63,7 +62,7 @@ class AssetService {
|
||||
required bool hasCache,
|
||||
}) async {
|
||||
try {
|
||||
final etag = hasCache ? Store.get(StoreKey.assetETag) : null;
|
||||
final etag = hasCache ? Store.tryGet(StoreKey.assetETag) : null;
|
||||
final Pair<List<AssetResponseDto>, String?>? remote =
|
||||
await _apiService.assetApi.getAllAssetsWithETag(eTag: etag);
|
||||
if (remote == null) {
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:immich_mobile/constants/hive_box.dart';
|
||||
import 'package:immich_mobile/shared/models/immich_logger_message.model.dart';
|
||||
import 'package:immich_mobile/shared/models/logger_message.model.dart';
|
||||
import 'package:isar/isar.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'package:share_plus/share_plus.dart';
|
||||
|
||||
/// [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
|
||||
/// 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
|
||||
/// and generate a csv file.
|
||||
class ImmichLogger {
|
||||
static final ImmichLogger _instance = ImmichLogger._internal();
|
||||
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 =>
|
||||
_box.values.toList().reversed.toList();
|
||||
factory ImmichLogger() => _instance;
|
||||
|
||||
ImmichLogger() {
|
||||
ImmichLogger._internal() {
|
||||
_removeOverflowMessages();
|
||||
}
|
||||
|
||||
init() {
|
||||
Logger.root.level = Level.INFO;
|
||||
Logger.root.onRecord.listen(_writeLogToHiveBox);
|
||||
Logger.root.onRecord.listen(_writeLogToDatabase);
|
||||
}
|
||||
|
||||
_removeOverflowMessages() {
|
||||
if (_box.length > maxLogEntries) {
|
||||
var numberOfEntryToBeDeleted = _box.length - maxLogEntries;
|
||||
for (var i = 0; i < numberOfEntryToBeDeleted; i++) {
|
||||
_box.deleteAt(0);
|
||||
}
|
||||
List<LoggerMessage> get messages {
|
||||
final inDb =
|
||||
_db.loggerMessages.where(sort: Sort.desc).anyId().findAllSync();
|
||||
return _msgBuffer.isEmpty ? inDb : _msgBuffer.reversed.toList() + inDb;
|
||||
}
|
||||
|
||||
void _removeOverflowMessages() {
|
||||
final msgCount = _db.loggerMessages.countSync();
|
||||
if (msgCount > maxLogEntries) {
|
||||
final numberOfEntryToBeDeleted = msgCount - maxLogEntries;
|
||||
_db.loggerMessages.where().limit(numberOfEntryToBeDeleted).deleteAll();
|
||||
}
|
||||
}
|
||||
|
||||
_writeLogToHiveBox(LogRecord record) {
|
||||
final Box<ImmichLoggerMessage> box = Hive.box(immichLoggerBox);
|
||||
var formattedMessage = record.message;
|
||||
|
||||
void _writeLogToDatabase(LogRecord record) {
|
||||
debugPrint('[${record.level.name}] [${record.time}] ${record.message}');
|
||||
box.add(
|
||||
ImmichLoggerMessage(
|
||||
message: formattedMessage,
|
||||
level: record.level.name,
|
||||
createdAt: record.time,
|
||||
context1: record.loggerName,
|
||||
context2: record.stackTrace?.toString(),
|
||||
),
|
||||
final lm = LoggerMessage(
|
||||
message: record.message,
|
||||
level: record.level.toLogLevel(),
|
||||
createdAt: record.time,
|
||||
context1: record.loggerName,
|
||||
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() {
|
||||
_box.clear();
|
||||
_timer?.cancel();
|
||||
_timer = null;
|
||||
_msgBuffer.clear();
|
||||
_db.writeTxn(() => _db.loggerMessages.clear());
|
||||
}
|
||||
|
||||
Future<void> shareLogs() async {
|
||||
@@ -93,4 +106,12 @@ class ImmichLogger {
|
||||
// Clean up temp file
|
||||
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) {
|
||||
final userId = Store.get<User>(StoreKey.currentUser)!.isarId;
|
||||
final userId = Store.get(StoreKey.currentUser).isarId;
|
||||
final foreign =
|
||||
await album.assets.filter().not().ownerIdEqualTo(userId).findAll();
|
||||
existing.addAll(foreign);
|
||||
|
||||
@@ -42,7 +42,7 @@ class UserService {
|
||||
if (self) {
|
||||
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();
|
||||
}
|
||||
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import 'package:cached_network_image/cached_network_image.dart';
|
||||
import 'package:flutter/material.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/store.dart';
|
||||
import 'package:immich_mobile/utils/image_url_builder.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);
|
||||
return CachedNetworkImage(
|
||||
imageUrl: thumbnailRequestUrl,
|
||||
|
||||
@@ -2,6 +2,7 @@ import 'package:auto_route/auto_route.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_hooks/flutter_hooks.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:intl/intl.dart';
|
||||
|
||||
@@ -31,29 +32,29 @@ class AppLogPage extends HookConsumerWidget {
|
||||
);
|
||||
}
|
||||
|
||||
Widget buildLeadingIcon(String level) {
|
||||
Widget buildLeadingIcon(LogLevel level) {
|
||||
switch (level) {
|
||||
case "INFO":
|
||||
case LogLevel.INFO:
|
||||
return colorStatusIndicator(Theme.of(context).primaryColor);
|
||||
case "SEVERE":
|
||||
case LogLevel.SEVERE:
|
||||
return colorStatusIndicator(Colors.redAccent);
|
||||
|
||||
case "WARNING":
|
||||
case LogLevel.WARNING:
|
||||
return colorStatusIndicator(Colors.orangeAccent);
|
||||
default:
|
||||
return colorStatusIndicator(Colors.grey);
|
||||
}
|
||||
}
|
||||
|
||||
getTileColor(String level) {
|
||||
getTileColor(LogLevel level) {
|
||||
switch (level) {
|
||||
case "INFO":
|
||||
case LogLevel.INFO:
|
||||
return Colors.transparent;
|
||||
case "SEVERE":
|
||||
case LogLevel.SEVERE:
|
||||
return Theme.of(context).brightness == Brightness.dark
|
||||
? Colors.redAccent.withOpacity(0.25)
|
||||
: Colors.redAccent.withOpacity(0.075);
|
||||
case "WARNING":
|
||||
case LogLevel.WARNING:
|
||||
return Theme.of(context).brightness == Brightness.dark
|
||||
? Colors.orangeAccent.withOpacity(0.25)
|
||||
: Colors.orangeAccent.withOpacity(0.075);
|
||||
|
||||
@@ -1,14 +1,12 @@
|
||||
import 'package:auto_route/auto_route.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:hive_flutter/hive_flutter.dart';
|
||||
import 'package:flutter_hooks/flutter_hooks.dart' hide Store;
|
||||
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/login/models/hive_saved_login_info.model.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/routing/router.dart';
|
||||
import 'package:immich_mobile/shared/models/store.dart';
|
||||
import 'package:immich_mobile/shared/providers/api.provider.dart';
|
||||
|
||||
class SplashScreenPage extends HookConsumerWidget {
|
||||
@@ -17,23 +15,23 @@ class SplashScreenPage extends HookConsumerWidget {
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final apiService = ref.watch(apiServiceProvider);
|
||||
HiveSavedLoginInfo? loginInfo =
|
||||
Hive.box<HiveSavedLoginInfo>(hiveLoginInfoBox).get(savedLoginInfoKey);
|
||||
final serverUrl = Store.tryGet(StoreKey.serverUrl);
|
||||
final accessToken = Store.tryGet(StoreKey.accessToken);
|
||||
|
||||
void performLoggingIn() async {
|
||||
bool isSuccess = false;
|
||||
if (loginInfo != null) {
|
||||
if (accessToken != null && serverUrl != null) {
|
||||
try {
|
||||
// Resolve API server endpoint from user provided serverUrl
|
||||
await apiService.resolveAndSetEndpoint(loginInfo.serverUrl);
|
||||
await apiService.resolveAndSetEndpoint(serverUrl);
|
||||
} catch (e) {
|
||||
// okay, try to continue anyway if offline
|
||||
}
|
||||
|
||||
isSuccess =
|
||||
await ref.read(authenticationProvider.notifier).setSuccessLoginInfo(
|
||||
accessToken: loginInfo.accessToken,
|
||||
serverUrl: loginInfo.serverUrl,
|
||||
accessToken: accessToken,
|
||||
serverUrl: serverUrl,
|
||||
);
|
||||
}
|
||||
if (isSuccess) {
|
||||
@@ -51,7 +49,7 @@ class SplashScreenPage extends HookConsumerWidget {
|
||||
|
||||
useEffect(
|
||||
() {
|
||||
if (loginInfo != null) {
|
||||
if (serverUrl != null && accessToken != null) {
|
||||
performLoggingIn();
|
||||
} else {
|
||||
AutoRouter.of(context).replace(const LoginRoute());
|
||||
|
||||
Reference in New Issue
Block a user