mirror of
https://github.com/immich-app/immich.git
synced 2025-07-31 15:08:44 -04:00
feat: add toggle to switch between Isar and Sqlite (#19953)
This commit is contained in:
parent
b256c51b6b
commit
531515daf9
@ -373,6 +373,8 @@
|
|||||||
"admin_password": "Admin Password",
|
"admin_password": "Admin Password",
|
||||||
"administration": "Administration",
|
"administration": "Administration",
|
||||||
"advanced": "Advanced",
|
"advanced": "Advanced",
|
||||||
|
"advanced_settings_beta_timeline_subtitle": "Try the new app experience.",
|
||||||
|
"advanced_settings_beta_timeline_title": "Beta Timeline",
|
||||||
"advanced_settings_enable_alternate_media_filter_subtitle": "Use this option to filter media during sync based on alternate criteria. Only try this if you have issues with the app detecting all albums.",
|
"advanced_settings_enable_alternate_media_filter_subtitle": "Use this option to filter media during sync based on alternate criteria. Only try this if you have issues with the app detecting all albums.",
|
||||||
"advanced_settings_enable_alternate_media_filter_title": "[EXPERIMENTAL] Use alternate device album sync filter",
|
"advanced_settings_enable_alternate_media_filter_title": "[EXPERIMENTAL] Use alternate device album sync filter",
|
||||||
"advanced_settings_log_level_title": "Log level: {level}",
|
"advanced_settings_log_level_title": "Log level: {level}",
|
||||||
|
1
mobile/drift_schemas/main/drift_schema_v2.json
generated
Normal file
1
mobile/drift_schemas/main/drift_schema_v2.json
generated
Normal file
File diff suppressed because one or more lines are too long
@ -68,7 +68,9 @@ enum StoreKey<T> {
|
|||||||
manageLocalMediaAndroid<bool>._(137),
|
manageLocalMediaAndroid<bool>._(137),
|
||||||
|
|
||||||
// Experimental stuff
|
// Experimental stuff
|
||||||
photoManagerCustomFilter<bool>._(1000);
|
photoManagerCustomFilter<bool>._(1000),
|
||||||
|
betaPromptShown<bool>._(1001),
|
||||||
|
betaTimeline<bool>._(1002);
|
||||||
|
|
||||||
const StoreKey._(this.id);
|
const StoreKey._(this.id);
|
||||||
final int id;
|
final int id;
|
||||||
|
@ -93,6 +93,8 @@ class StoreService {
|
|||||||
await _storeRepository.deleteAll();
|
await _storeRepository.deleteAll();
|
||||||
_cache.clear();
|
_cache.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool get isBetaTimelineEnabled => tryGet(StoreKey.betaTimeline) ?? false;
|
||||||
}
|
}
|
||||||
|
|
||||||
class StoreKeyNotFoundException implements Exception {
|
class StoreKeyNotFoundException implements Exception {
|
||||||
|
@ -2,6 +2,7 @@ import 'dart:async';
|
|||||||
|
|
||||||
import 'package:drift/drift.dart';
|
import 'package:drift/drift.dart';
|
||||||
import 'package:drift_flutter/drift_flutter.dart';
|
import 'package:drift_flutter/drift_flutter.dart';
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:immich_mobile/domain/interfaces/db.interface.dart';
|
import 'package:immich_mobile/domain/interfaces/db.interface.dart';
|
||||||
import 'package:immich_mobile/infrastructure/entities/exif.entity.dart';
|
import 'package:immich_mobile/infrastructure/entities/exif.entity.dart';
|
||||||
import 'package:immich_mobile/infrastructure/entities/local_album.entity.dart';
|
import 'package:immich_mobile/infrastructure/entities/local_album.entity.dart';
|
||||||
@ -17,6 +18,7 @@ import 'package:immich_mobile/infrastructure/entities/remote_asset.entity.dart';
|
|||||||
import 'package:immich_mobile/infrastructure/entities/stack.entity.dart';
|
import 'package:immich_mobile/infrastructure/entities/stack.entity.dart';
|
||||||
import 'package:immich_mobile/infrastructure/entities/user.entity.dart';
|
import 'package:immich_mobile/infrastructure/entities/user.entity.dart';
|
||||||
import 'package:immich_mobile/infrastructure/entities/user_metadata.entity.dart';
|
import 'package:immich_mobile/infrastructure/entities/user_metadata.entity.dart';
|
||||||
|
import 'package:immich_mobile/infrastructure/repositories/db.repository.steps.dart';
|
||||||
import 'package:isar/isar.dart';
|
import 'package:isar/isar.dart';
|
||||||
|
|
||||||
import 'db.repository.drift.dart';
|
import 'db.repository.drift.dart';
|
||||||
@ -68,10 +70,36 @@ class Drift extends $Drift implements IDatabaseRepository {
|
|||||||
);
|
);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
int get schemaVersion => 1;
|
int get schemaVersion => 2;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
MigrationStrategy get migration => MigrationStrategy(
|
MigrationStrategy get migration => MigrationStrategy(
|
||||||
|
onUpgrade: (m, from, to) async {
|
||||||
|
// Run migration steps without foreign keys and re-enable them later
|
||||||
|
await customStatement('PRAGMA foreign_keys = OFF');
|
||||||
|
|
||||||
|
await m.runMigrationSteps(
|
||||||
|
from: from,
|
||||||
|
to: to,
|
||||||
|
steps: migrationSteps(
|
||||||
|
from1To2: (m, _) async {
|
||||||
|
for (final entity in allSchemaEntities) {
|
||||||
|
await m.drop(entity);
|
||||||
|
await m.create(entity);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (kDebugMode) {
|
||||||
|
// Fail if the migration broke foreign keys
|
||||||
|
final wrongFKs =
|
||||||
|
await customSelect('PRAGMA foreign_key_check').get();
|
||||||
|
assert(wrongFKs.isEmpty, '${wrongFKs.map((e) => e.data)}');
|
||||||
|
}
|
||||||
|
|
||||||
|
await customStatement('PRAGMA foreign_keys = ON;');
|
||||||
|
},
|
||||||
beforeOpen: (details) async {
|
beforeOpen: (details) async {
|
||||||
await customStatement('PRAGMA foreign_keys = ON');
|
await customStatement('PRAGMA foreign_keys = ON');
|
||||||
await customStatement('PRAGMA synchronous = NORMAL');
|
await customStatement('PRAGMA synchronous = NORMAL');
|
||||||
|
864
mobile/lib/infrastructure/repositories/db.repository.steps.dart
Normal file
864
mobile/lib/infrastructure/repositories/db.repository.steps.dart
Normal file
@ -0,0 +1,864 @@
|
|||||||
|
// dart format width=80
|
||||||
|
import 'package:drift/internal/versioned_schema.dart' as i0;
|
||||||
|
import 'package:drift/drift.dart' as i1;
|
||||||
|
import 'dart:typed_data' as i2;
|
||||||
|
import 'package:drift/drift.dart'; // ignore_for_file: type=lint,unused_import
|
||||||
|
|
||||||
|
// GENERATED BY drift_dev, DO NOT MODIFY.
|
||||||
|
final class Schema2 extends i0.VersionedSchema {
|
||||||
|
Schema2({required super.database}) : super(version: 2);
|
||||||
|
@override
|
||||||
|
late final List<i1.DatabaseSchemaEntity> entities = [
|
||||||
|
userEntity,
|
||||||
|
remoteAssetEntity,
|
||||||
|
localAssetEntity,
|
||||||
|
idxLocalAssetChecksum,
|
||||||
|
uQRemoteAssetOwnerChecksum,
|
||||||
|
idxRemoteAssetChecksum,
|
||||||
|
userMetadataEntity,
|
||||||
|
partnerEntity,
|
||||||
|
localAlbumEntity,
|
||||||
|
localAlbumAssetEntity,
|
||||||
|
remoteExifEntity,
|
||||||
|
remoteAlbumEntity,
|
||||||
|
remoteAlbumAssetEntity,
|
||||||
|
remoteAlbumUserEntity,
|
||||||
|
memoryEntity,
|
||||||
|
memoryAssetEntity,
|
||||||
|
stackEntity,
|
||||||
|
];
|
||||||
|
late final Shape0 userEntity = Shape0(
|
||||||
|
source: i0.VersionedTable(
|
||||||
|
entityName: 'user_entity',
|
||||||
|
withoutRowId: true,
|
||||||
|
isStrict: true,
|
||||||
|
tableConstraints: [
|
||||||
|
'PRIMARY KEY(id)',
|
||||||
|
],
|
||||||
|
columns: [
|
||||||
|
_column_0,
|
||||||
|
_column_1,
|
||||||
|
_column_2,
|
||||||
|
_column_3,
|
||||||
|
_column_4,
|
||||||
|
_column_5,
|
||||||
|
_column_6,
|
||||||
|
_column_7,
|
||||||
|
],
|
||||||
|
attachedDatabase: database,
|
||||||
|
),
|
||||||
|
alias: null);
|
||||||
|
late final Shape1 remoteAssetEntity = Shape1(
|
||||||
|
source: i0.VersionedTable(
|
||||||
|
entityName: 'remote_asset_entity',
|
||||||
|
withoutRowId: true,
|
||||||
|
isStrict: true,
|
||||||
|
tableConstraints: [
|
||||||
|
'PRIMARY KEY(id)',
|
||||||
|
],
|
||||||
|
columns: [
|
||||||
|
_column_1,
|
||||||
|
_column_8,
|
||||||
|
_column_9,
|
||||||
|
_column_5,
|
||||||
|
_column_10,
|
||||||
|
_column_11,
|
||||||
|
_column_12,
|
||||||
|
_column_0,
|
||||||
|
_column_13,
|
||||||
|
_column_14,
|
||||||
|
_column_15,
|
||||||
|
_column_16,
|
||||||
|
_column_17,
|
||||||
|
_column_18,
|
||||||
|
_column_19,
|
||||||
|
_column_20,
|
||||||
|
],
|
||||||
|
attachedDatabase: database,
|
||||||
|
),
|
||||||
|
alias: null);
|
||||||
|
late final Shape2 localAssetEntity = Shape2(
|
||||||
|
source: i0.VersionedTable(
|
||||||
|
entityName: 'local_asset_entity',
|
||||||
|
withoutRowId: true,
|
||||||
|
isStrict: true,
|
||||||
|
tableConstraints: [
|
||||||
|
'PRIMARY KEY(id)',
|
||||||
|
],
|
||||||
|
columns: [
|
||||||
|
_column_1,
|
||||||
|
_column_8,
|
||||||
|
_column_9,
|
||||||
|
_column_5,
|
||||||
|
_column_10,
|
||||||
|
_column_11,
|
||||||
|
_column_12,
|
||||||
|
_column_0,
|
||||||
|
_column_21,
|
||||||
|
_column_14,
|
||||||
|
_column_22,
|
||||||
|
],
|
||||||
|
attachedDatabase: database,
|
||||||
|
),
|
||||||
|
alias: null);
|
||||||
|
final i1.Index idxLocalAssetChecksum = i1.Index('idx_local_asset_checksum',
|
||||||
|
'CREATE INDEX idx_local_asset_checksum ON local_asset_entity (checksum)');
|
||||||
|
final i1.Index uQRemoteAssetOwnerChecksum = i1.Index(
|
||||||
|
'UQ_remote_asset_owner_checksum',
|
||||||
|
'CREATE UNIQUE INDEX UQ_remote_asset_owner_checksum ON remote_asset_entity (checksum, owner_id)');
|
||||||
|
final i1.Index idxRemoteAssetChecksum = i1.Index('idx_remote_asset_checksum',
|
||||||
|
'CREATE INDEX idx_remote_asset_checksum ON remote_asset_entity (checksum)');
|
||||||
|
late final Shape3 userMetadataEntity = Shape3(
|
||||||
|
source: i0.VersionedTable(
|
||||||
|
entityName: 'user_metadata_entity',
|
||||||
|
withoutRowId: true,
|
||||||
|
isStrict: true,
|
||||||
|
tableConstraints: [
|
||||||
|
'PRIMARY KEY(user_id, "key")',
|
||||||
|
],
|
||||||
|
columns: [
|
||||||
|
_column_23,
|
||||||
|
_column_24,
|
||||||
|
_column_25,
|
||||||
|
],
|
||||||
|
attachedDatabase: database,
|
||||||
|
),
|
||||||
|
alias: null);
|
||||||
|
late final Shape4 partnerEntity = Shape4(
|
||||||
|
source: i0.VersionedTable(
|
||||||
|
entityName: 'partner_entity',
|
||||||
|
withoutRowId: true,
|
||||||
|
isStrict: true,
|
||||||
|
tableConstraints: [
|
||||||
|
'PRIMARY KEY(shared_by_id, shared_with_id)',
|
||||||
|
],
|
||||||
|
columns: [
|
||||||
|
_column_26,
|
||||||
|
_column_27,
|
||||||
|
_column_28,
|
||||||
|
],
|
||||||
|
attachedDatabase: database,
|
||||||
|
),
|
||||||
|
alias: null);
|
||||||
|
late final Shape5 localAlbumEntity = Shape5(
|
||||||
|
source: i0.VersionedTable(
|
||||||
|
entityName: 'local_album_entity',
|
||||||
|
withoutRowId: true,
|
||||||
|
isStrict: true,
|
||||||
|
tableConstraints: [
|
||||||
|
'PRIMARY KEY(id)',
|
||||||
|
],
|
||||||
|
columns: [
|
||||||
|
_column_0,
|
||||||
|
_column_1,
|
||||||
|
_column_5,
|
||||||
|
_column_29,
|
||||||
|
_column_30,
|
||||||
|
_column_31,
|
||||||
|
],
|
||||||
|
attachedDatabase: database,
|
||||||
|
),
|
||||||
|
alias: null);
|
||||||
|
late final Shape6 localAlbumAssetEntity = Shape6(
|
||||||
|
source: i0.VersionedTable(
|
||||||
|
entityName: 'local_album_asset_entity',
|
||||||
|
withoutRowId: true,
|
||||||
|
isStrict: true,
|
||||||
|
tableConstraints: [
|
||||||
|
'PRIMARY KEY(asset_id, album_id)',
|
||||||
|
],
|
||||||
|
columns: [
|
||||||
|
_column_32,
|
||||||
|
_column_33,
|
||||||
|
],
|
||||||
|
attachedDatabase: database,
|
||||||
|
),
|
||||||
|
alias: null);
|
||||||
|
late final Shape7 remoteExifEntity = Shape7(
|
||||||
|
source: i0.VersionedTable(
|
||||||
|
entityName: 'remote_exif_entity',
|
||||||
|
withoutRowId: true,
|
||||||
|
isStrict: true,
|
||||||
|
tableConstraints: [
|
||||||
|
'PRIMARY KEY(asset_id)',
|
||||||
|
],
|
||||||
|
columns: [
|
||||||
|
_column_34,
|
||||||
|
_column_35,
|
||||||
|
_column_36,
|
||||||
|
_column_37,
|
||||||
|
_column_38,
|
||||||
|
_column_39,
|
||||||
|
_column_11,
|
||||||
|
_column_10,
|
||||||
|
_column_40,
|
||||||
|
_column_41,
|
||||||
|
_column_42,
|
||||||
|
_column_43,
|
||||||
|
_column_44,
|
||||||
|
_column_45,
|
||||||
|
_column_46,
|
||||||
|
_column_47,
|
||||||
|
_column_48,
|
||||||
|
_column_49,
|
||||||
|
_column_50,
|
||||||
|
_column_51,
|
||||||
|
_column_52,
|
||||||
|
_column_53,
|
||||||
|
],
|
||||||
|
attachedDatabase: database,
|
||||||
|
),
|
||||||
|
alias: null);
|
||||||
|
late final Shape8 remoteAlbumEntity = Shape8(
|
||||||
|
source: i0.VersionedTable(
|
||||||
|
entityName: 'remote_album_entity',
|
||||||
|
withoutRowId: true,
|
||||||
|
isStrict: true,
|
||||||
|
tableConstraints: [
|
||||||
|
'PRIMARY KEY(id)',
|
||||||
|
],
|
||||||
|
columns: [
|
||||||
|
_column_0,
|
||||||
|
_column_1,
|
||||||
|
_column_54,
|
||||||
|
_column_9,
|
||||||
|
_column_5,
|
||||||
|
_column_15,
|
||||||
|
_column_55,
|
||||||
|
_column_56,
|
||||||
|
_column_57,
|
||||||
|
],
|
||||||
|
attachedDatabase: database,
|
||||||
|
),
|
||||||
|
alias: null);
|
||||||
|
late final Shape6 remoteAlbumAssetEntity = Shape6(
|
||||||
|
source: i0.VersionedTable(
|
||||||
|
entityName: 'remote_album_asset_entity',
|
||||||
|
withoutRowId: true,
|
||||||
|
isStrict: true,
|
||||||
|
tableConstraints: [
|
||||||
|
'PRIMARY KEY(asset_id, album_id)',
|
||||||
|
],
|
||||||
|
columns: [
|
||||||
|
_column_34,
|
||||||
|
_column_58,
|
||||||
|
],
|
||||||
|
attachedDatabase: database,
|
||||||
|
),
|
||||||
|
alias: null);
|
||||||
|
late final Shape9 remoteAlbumUserEntity = Shape9(
|
||||||
|
source: i0.VersionedTable(
|
||||||
|
entityName: 'remote_album_user_entity',
|
||||||
|
withoutRowId: true,
|
||||||
|
isStrict: true,
|
||||||
|
tableConstraints: [
|
||||||
|
'PRIMARY KEY(album_id, user_id)',
|
||||||
|
],
|
||||||
|
columns: [
|
||||||
|
_column_58,
|
||||||
|
_column_23,
|
||||||
|
_column_59,
|
||||||
|
],
|
||||||
|
attachedDatabase: database,
|
||||||
|
),
|
||||||
|
alias: null);
|
||||||
|
late final Shape10 memoryEntity = Shape10(
|
||||||
|
source: i0.VersionedTable(
|
||||||
|
entityName: 'memory_entity',
|
||||||
|
withoutRowId: true,
|
||||||
|
isStrict: true,
|
||||||
|
tableConstraints: [
|
||||||
|
'PRIMARY KEY(id)',
|
||||||
|
],
|
||||||
|
columns: [
|
||||||
|
_column_0,
|
||||||
|
_column_9,
|
||||||
|
_column_5,
|
||||||
|
_column_18,
|
||||||
|
_column_15,
|
||||||
|
_column_8,
|
||||||
|
_column_60,
|
||||||
|
_column_61,
|
||||||
|
_column_62,
|
||||||
|
_column_63,
|
||||||
|
_column_64,
|
||||||
|
_column_65,
|
||||||
|
],
|
||||||
|
attachedDatabase: database,
|
||||||
|
),
|
||||||
|
alias: null);
|
||||||
|
late final Shape11 memoryAssetEntity = Shape11(
|
||||||
|
source: i0.VersionedTable(
|
||||||
|
entityName: 'memory_asset_entity',
|
||||||
|
withoutRowId: true,
|
||||||
|
isStrict: true,
|
||||||
|
tableConstraints: [
|
||||||
|
'PRIMARY KEY(asset_id, memory_id)',
|
||||||
|
],
|
||||||
|
columns: [
|
||||||
|
_column_34,
|
||||||
|
_column_66,
|
||||||
|
],
|
||||||
|
attachedDatabase: database,
|
||||||
|
),
|
||||||
|
alias: null);
|
||||||
|
late final Shape12 stackEntity = Shape12(
|
||||||
|
source: i0.VersionedTable(
|
||||||
|
entityName: 'stack_entity',
|
||||||
|
withoutRowId: true,
|
||||||
|
isStrict: true,
|
||||||
|
tableConstraints: [
|
||||||
|
'PRIMARY KEY(id)',
|
||||||
|
],
|
||||||
|
columns: [
|
||||||
|
_column_0,
|
||||||
|
_column_9,
|
||||||
|
_column_5,
|
||||||
|
_column_15,
|
||||||
|
_column_67,
|
||||||
|
],
|
||||||
|
attachedDatabase: database,
|
||||||
|
),
|
||||||
|
alias: null);
|
||||||
|
}
|
||||||
|
|
||||||
|
class Shape0 extends i0.VersionedTable {
|
||||||
|
Shape0({required super.source, required super.alias}) : super.aliased();
|
||||||
|
i1.GeneratedColumn<String> get id =>
|
||||||
|
columnsByName['id']! as i1.GeneratedColumn<String>;
|
||||||
|
i1.GeneratedColumn<String> get name =>
|
||||||
|
columnsByName['name']! as i1.GeneratedColumn<String>;
|
||||||
|
i1.GeneratedColumn<bool> get isAdmin =>
|
||||||
|
columnsByName['is_admin']! as i1.GeneratedColumn<bool>;
|
||||||
|
i1.GeneratedColumn<String> get email =>
|
||||||
|
columnsByName['email']! as i1.GeneratedColumn<String>;
|
||||||
|
i1.GeneratedColumn<String> get profileImagePath =>
|
||||||
|
columnsByName['profile_image_path']! as i1.GeneratedColumn<String>;
|
||||||
|
i1.GeneratedColumn<DateTime> get updatedAt =>
|
||||||
|
columnsByName['updated_at']! as i1.GeneratedColumn<DateTime>;
|
||||||
|
i1.GeneratedColumn<int> get quotaSizeInBytes =>
|
||||||
|
columnsByName['quota_size_in_bytes']! as i1.GeneratedColumn<int>;
|
||||||
|
i1.GeneratedColumn<int> get quotaUsageInBytes =>
|
||||||
|
columnsByName['quota_usage_in_bytes']! as i1.GeneratedColumn<int>;
|
||||||
|
}
|
||||||
|
|
||||||
|
i1.GeneratedColumn<String> _column_0(String aliasedName) =>
|
||||||
|
i1.GeneratedColumn<String>('id', aliasedName, false,
|
||||||
|
type: i1.DriftSqlType.string);
|
||||||
|
i1.GeneratedColumn<String> _column_1(String aliasedName) =>
|
||||||
|
i1.GeneratedColumn<String>('name', aliasedName, false,
|
||||||
|
type: i1.DriftSqlType.string);
|
||||||
|
i1.GeneratedColumn<bool> _column_2(String aliasedName) =>
|
||||||
|
i1.GeneratedColumn<bool>('is_admin', aliasedName, false,
|
||||||
|
type: i1.DriftSqlType.bool,
|
||||||
|
defaultConstraints: i1.GeneratedColumn.constraintIsAlways(
|
||||||
|
'CHECK ("is_admin" IN (0, 1))'),
|
||||||
|
defaultValue: const CustomExpression('0'));
|
||||||
|
i1.GeneratedColumn<String> _column_3(String aliasedName) =>
|
||||||
|
i1.GeneratedColumn<String>('email', aliasedName, false,
|
||||||
|
type: i1.DriftSqlType.string);
|
||||||
|
i1.GeneratedColumn<String> _column_4(String aliasedName) =>
|
||||||
|
i1.GeneratedColumn<String>('profile_image_path', aliasedName, true,
|
||||||
|
type: i1.DriftSqlType.string);
|
||||||
|
i1.GeneratedColumn<DateTime> _column_5(String aliasedName) =>
|
||||||
|
i1.GeneratedColumn<DateTime>('updated_at', aliasedName, false,
|
||||||
|
type: i1.DriftSqlType.dateTime,
|
||||||
|
defaultValue: const CustomExpression('CURRENT_TIMESTAMP'));
|
||||||
|
i1.GeneratedColumn<int> _column_6(String aliasedName) =>
|
||||||
|
i1.GeneratedColumn<int>('quota_size_in_bytes', aliasedName, true,
|
||||||
|
type: i1.DriftSqlType.int);
|
||||||
|
i1.GeneratedColumn<int> _column_7(String aliasedName) =>
|
||||||
|
i1.GeneratedColumn<int>('quota_usage_in_bytes', aliasedName, false,
|
||||||
|
type: i1.DriftSqlType.int, defaultValue: const CustomExpression('0'));
|
||||||
|
|
||||||
|
class Shape1 extends i0.VersionedTable {
|
||||||
|
Shape1({required super.source, required super.alias}) : super.aliased();
|
||||||
|
i1.GeneratedColumn<String> get name =>
|
||||||
|
columnsByName['name']! as i1.GeneratedColumn<String>;
|
||||||
|
i1.GeneratedColumn<int> get type =>
|
||||||
|
columnsByName['type']! as i1.GeneratedColumn<int>;
|
||||||
|
i1.GeneratedColumn<DateTime> get createdAt =>
|
||||||
|
columnsByName['created_at']! as i1.GeneratedColumn<DateTime>;
|
||||||
|
i1.GeneratedColumn<DateTime> get updatedAt =>
|
||||||
|
columnsByName['updated_at']! as i1.GeneratedColumn<DateTime>;
|
||||||
|
i1.GeneratedColumn<int> get width =>
|
||||||
|
columnsByName['width']! as i1.GeneratedColumn<int>;
|
||||||
|
i1.GeneratedColumn<int> get height =>
|
||||||
|
columnsByName['height']! as i1.GeneratedColumn<int>;
|
||||||
|
i1.GeneratedColumn<int> get durationInSeconds =>
|
||||||
|
columnsByName['duration_in_seconds']! as i1.GeneratedColumn<int>;
|
||||||
|
i1.GeneratedColumn<String> get id =>
|
||||||
|
columnsByName['id']! as i1.GeneratedColumn<String>;
|
||||||
|
i1.GeneratedColumn<String> get checksum =>
|
||||||
|
columnsByName['checksum']! as i1.GeneratedColumn<String>;
|
||||||
|
i1.GeneratedColumn<bool> get isFavorite =>
|
||||||
|
columnsByName['is_favorite']! as i1.GeneratedColumn<bool>;
|
||||||
|
i1.GeneratedColumn<String> get ownerId =>
|
||||||
|
columnsByName['owner_id']! as i1.GeneratedColumn<String>;
|
||||||
|
i1.GeneratedColumn<DateTime> get localDateTime =>
|
||||||
|
columnsByName['local_date_time']! as i1.GeneratedColumn<DateTime>;
|
||||||
|
i1.GeneratedColumn<String> get thumbHash =>
|
||||||
|
columnsByName['thumb_hash']! as i1.GeneratedColumn<String>;
|
||||||
|
i1.GeneratedColumn<DateTime> get deletedAt =>
|
||||||
|
columnsByName['deleted_at']! as i1.GeneratedColumn<DateTime>;
|
||||||
|
i1.GeneratedColumn<String> get livePhotoVideoId =>
|
||||||
|
columnsByName['live_photo_video_id']! as i1.GeneratedColumn<String>;
|
||||||
|
i1.GeneratedColumn<int> get visibility =>
|
||||||
|
columnsByName['visibility']! as i1.GeneratedColumn<int>;
|
||||||
|
}
|
||||||
|
|
||||||
|
i1.GeneratedColumn<int> _column_8(String aliasedName) =>
|
||||||
|
i1.GeneratedColumn<int>('type', aliasedName, false,
|
||||||
|
type: i1.DriftSqlType.int);
|
||||||
|
i1.GeneratedColumn<DateTime> _column_9(String aliasedName) =>
|
||||||
|
i1.GeneratedColumn<DateTime>('created_at', aliasedName, false,
|
||||||
|
type: i1.DriftSqlType.dateTime,
|
||||||
|
defaultValue: const CustomExpression('CURRENT_TIMESTAMP'));
|
||||||
|
i1.GeneratedColumn<int> _column_10(String aliasedName) =>
|
||||||
|
i1.GeneratedColumn<int>('width', aliasedName, true,
|
||||||
|
type: i1.DriftSqlType.int);
|
||||||
|
i1.GeneratedColumn<int> _column_11(String aliasedName) =>
|
||||||
|
i1.GeneratedColumn<int>('height', aliasedName, true,
|
||||||
|
type: i1.DriftSqlType.int);
|
||||||
|
i1.GeneratedColumn<int> _column_12(String aliasedName) =>
|
||||||
|
i1.GeneratedColumn<int>('duration_in_seconds', aliasedName, true,
|
||||||
|
type: i1.DriftSqlType.int);
|
||||||
|
i1.GeneratedColumn<String> _column_13(String aliasedName) =>
|
||||||
|
i1.GeneratedColumn<String>('checksum', aliasedName, false,
|
||||||
|
type: i1.DriftSqlType.string);
|
||||||
|
i1.GeneratedColumn<bool> _column_14(String aliasedName) =>
|
||||||
|
i1.GeneratedColumn<bool>('is_favorite', aliasedName, false,
|
||||||
|
type: i1.DriftSqlType.bool,
|
||||||
|
defaultConstraints: i1.GeneratedColumn.constraintIsAlways(
|
||||||
|
'CHECK ("is_favorite" IN (0, 1))'),
|
||||||
|
defaultValue: const CustomExpression('0'));
|
||||||
|
i1.GeneratedColumn<String> _column_15(String aliasedName) =>
|
||||||
|
i1.GeneratedColumn<String>('owner_id', aliasedName, false,
|
||||||
|
type: i1.DriftSqlType.string,
|
||||||
|
defaultConstraints: i1.GeneratedColumn.constraintIsAlways(
|
||||||
|
'REFERENCES user_entity (id) ON DELETE CASCADE'));
|
||||||
|
i1.GeneratedColumn<DateTime> _column_16(String aliasedName) =>
|
||||||
|
i1.GeneratedColumn<DateTime>('local_date_time', aliasedName, true,
|
||||||
|
type: i1.DriftSqlType.dateTime);
|
||||||
|
i1.GeneratedColumn<String> _column_17(String aliasedName) =>
|
||||||
|
i1.GeneratedColumn<String>('thumb_hash', aliasedName, true,
|
||||||
|
type: i1.DriftSqlType.string);
|
||||||
|
i1.GeneratedColumn<DateTime> _column_18(String aliasedName) =>
|
||||||
|
i1.GeneratedColumn<DateTime>('deleted_at', aliasedName, true,
|
||||||
|
type: i1.DriftSqlType.dateTime);
|
||||||
|
i1.GeneratedColumn<String> _column_19(String aliasedName) =>
|
||||||
|
i1.GeneratedColumn<String>('live_photo_video_id', aliasedName, true,
|
||||||
|
type: i1.DriftSqlType.string);
|
||||||
|
i1.GeneratedColumn<int> _column_20(String aliasedName) =>
|
||||||
|
i1.GeneratedColumn<int>('visibility', aliasedName, false,
|
||||||
|
type: i1.DriftSqlType.int);
|
||||||
|
|
||||||
|
class Shape2 extends i0.VersionedTable {
|
||||||
|
Shape2({required super.source, required super.alias}) : super.aliased();
|
||||||
|
i1.GeneratedColumn<String> get name =>
|
||||||
|
columnsByName['name']! as i1.GeneratedColumn<String>;
|
||||||
|
i1.GeneratedColumn<int> get type =>
|
||||||
|
columnsByName['type']! as i1.GeneratedColumn<int>;
|
||||||
|
i1.GeneratedColumn<DateTime> get createdAt =>
|
||||||
|
columnsByName['created_at']! as i1.GeneratedColumn<DateTime>;
|
||||||
|
i1.GeneratedColumn<DateTime> get updatedAt =>
|
||||||
|
columnsByName['updated_at']! as i1.GeneratedColumn<DateTime>;
|
||||||
|
i1.GeneratedColumn<int> get width =>
|
||||||
|
columnsByName['width']! as i1.GeneratedColumn<int>;
|
||||||
|
i1.GeneratedColumn<int> get height =>
|
||||||
|
columnsByName['height']! as i1.GeneratedColumn<int>;
|
||||||
|
i1.GeneratedColumn<int> get durationInSeconds =>
|
||||||
|
columnsByName['duration_in_seconds']! as i1.GeneratedColumn<int>;
|
||||||
|
i1.GeneratedColumn<String> get id =>
|
||||||
|
columnsByName['id']! as i1.GeneratedColumn<String>;
|
||||||
|
i1.GeneratedColumn<String> get checksum =>
|
||||||
|
columnsByName['checksum']! as i1.GeneratedColumn<String>;
|
||||||
|
i1.GeneratedColumn<bool> get isFavorite =>
|
||||||
|
columnsByName['is_favorite']! as i1.GeneratedColumn<bool>;
|
||||||
|
i1.GeneratedColumn<int> get orientation =>
|
||||||
|
columnsByName['orientation']! as i1.GeneratedColumn<int>;
|
||||||
|
}
|
||||||
|
|
||||||
|
i1.GeneratedColumn<String> _column_21(String aliasedName) =>
|
||||||
|
i1.GeneratedColumn<String>('checksum', aliasedName, true,
|
||||||
|
type: i1.DriftSqlType.string);
|
||||||
|
i1.GeneratedColumn<int> _column_22(String aliasedName) =>
|
||||||
|
i1.GeneratedColumn<int>('orientation', aliasedName, false,
|
||||||
|
type: i1.DriftSqlType.int, defaultValue: const CustomExpression('0'));
|
||||||
|
|
||||||
|
class Shape3 extends i0.VersionedTable {
|
||||||
|
Shape3({required super.source, required super.alias}) : super.aliased();
|
||||||
|
i1.GeneratedColumn<String> get userId =>
|
||||||
|
columnsByName['user_id']! as i1.GeneratedColumn<String>;
|
||||||
|
i1.GeneratedColumn<int> get key =>
|
||||||
|
columnsByName['key']! as i1.GeneratedColumn<int>;
|
||||||
|
i1.GeneratedColumn<i2.Uint8List> get value =>
|
||||||
|
columnsByName['value']! as i1.GeneratedColumn<i2.Uint8List>;
|
||||||
|
}
|
||||||
|
|
||||||
|
i1.GeneratedColumn<String> _column_23(String aliasedName) =>
|
||||||
|
i1.GeneratedColumn<String>('user_id', aliasedName, false,
|
||||||
|
type: i1.DriftSqlType.string,
|
||||||
|
defaultConstraints: i1.GeneratedColumn.constraintIsAlways(
|
||||||
|
'REFERENCES user_entity (id) ON DELETE CASCADE'));
|
||||||
|
i1.GeneratedColumn<int> _column_24(String aliasedName) =>
|
||||||
|
i1.GeneratedColumn<int>('key', aliasedName, false,
|
||||||
|
type: i1.DriftSqlType.int);
|
||||||
|
i1.GeneratedColumn<i2.Uint8List> _column_25(String aliasedName) =>
|
||||||
|
i1.GeneratedColumn<i2.Uint8List>('value', aliasedName, false,
|
||||||
|
type: i1.DriftSqlType.blob);
|
||||||
|
|
||||||
|
class Shape4 extends i0.VersionedTable {
|
||||||
|
Shape4({required super.source, required super.alias}) : super.aliased();
|
||||||
|
i1.GeneratedColumn<String> get sharedById =>
|
||||||
|
columnsByName['shared_by_id']! as i1.GeneratedColumn<String>;
|
||||||
|
i1.GeneratedColumn<String> get sharedWithId =>
|
||||||
|
columnsByName['shared_with_id']! as i1.GeneratedColumn<String>;
|
||||||
|
i1.GeneratedColumn<bool> get inTimeline =>
|
||||||
|
columnsByName['in_timeline']! as i1.GeneratedColumn<bool>;
|
||||||
|
}
|
||||||
|
|
||||||
|
i1.GeneratedColumn<String> _column_26(String aliasedName) =>
|
||||||
|
i1.GeneratedColumn<String>('shared_by_id', aliasedName, false,
|
||||||
|
type: i1.DriftSqlType.string,
|
||||||
|
defaultConstraints: i1.GeneratedColumn.constraintIsAlways(
|
||||||
|
'REFERENCES user_entity (id) ON DELETE CASCADE'));
|
||||||
|
i1.GeneratedColumn<String> _column_27(String aliasedName) =>
|
||||||
|
i1.GeneratedColumn<String>('shared_with_id', aliasedName, false,
|
||||||
|
type: i1.DriftSqlType.string,
|
||||||
|
defaultConstraints: i1.GeneratedColumn.constraintIsAlways(
|
||||||
|
'REFERENCES user_entity (id) ON DELETE CASCADE'));
|
||||||
|
i1.GeneratedColumn<bool> _column_28(String aliasedName) =>
|
||||||
|
i1.GeneratedColumn<bool>('in_timeline', aliasedName, false,
|
||||||
|
type: i1.DriftSqlType.bool,
|
||||||
|
defaultConstraints: i1.GeneratedColumn.constraintIsAlways(
|
||||||
|
'CHECK ("in_timeline" IN (0, 1))'),
|
||||||
|
defaultValue: const CustomExpression('0'));
|
||||||
|
|
||||||
|
class Shape5 extends i0.VersionedTable {
|
||||||
|
Shape5({required super.source, required super.alias}) : super.aliased();
|
||||||
|
i1.GeneratedColumn<String> get id =>
|
||||||
|
columnsByName['id']! as i1.GeneratedColumn<String>;
|
||||||
|
i1.GeneratedColumn<String> get name =>
|
||||||
|
columnsByName['name']! as i1.GeneratedColumn<String>;
|
||||||
|
i1.GeneratedColumn<DateTime> get updatedAt =>
|
||||||
|
columnsByName['updated_at']! as i1.GeneratedColumn<DateTime>;
|
||||||
|
i1.GeneratedColumn<int> get backupSelection =>
|
||||||
|
columnsByName['backup_selection']! as i1.GeneratedColumn<int>;
|
||||||
|
i1.GeneratedColumn<bool> get isIosSharedAlbum =>
|
||||||
|
columnsByName['is_ios_shared_album']! as i1.GeneratedColumn<bool>;
|
||||||
|
i1.GeneratedColumn<bool> get marker_ =>
|
||||||
|
columnsByName['marker']! as i1.GeneratedColumn<bool>;
|
||||||
|
}
|
||||||
|
|
||||||
|
i1.GeneratedColumn<int> _column_29(String aliasedName) =>
|
||||||
|
i1.GeneratedColumn<int>('backup_selection', aliasedName, false,
|
||||||
|
type: i1.DriftSqlType.int);
|
||||||
|
i1.GeneratedColumn<bool> _column_30(String aliasedName) =>
|
||||||
|
i1.GeneratedColumn<bool>('is_ios_shared_album', aliasedName, false,
|
||||||
|
type: i1.DriftSqlType.bool,
|
||||||
|
defaultConstraints: i1.GeneratedColumn.constraintIsAlways(
|
||||||
|
'CHECK ("is_ios_shared_album" IN (0, 1))'),
|
||||||
|
defaultValue: const CustomExpression('0'));
|
||||||
|
i1.GeneratedColumn<bool> _column_31(String aliasedName) =>
|
||||||
|
i1.GeneratedColumn<bool>('marker', aliasedName, true,
|
||||||
|
type: i1.DriftSqlType.bool,
|
||||||
|
defaultConstraints: i1.GeneratedColumn.constraintIsAlways(
|
||||||
|
'CHECK ("marker" IN (0, 1))'));
|
||||||
|
|
||||||
|
class Shape6 extends i0.VersionedTable {
|
||||||
|
Shape6({required super.source, required super.alias}) : super.aliased();
|
||||||
|
i1.GeneratedColumn<String> get assetId =>
|
||||||
|
columnsByName['asset_id']! as i1.GeneratedColumn<String>;
|
||||||
|
i1.GeneratedColumn<String> get albumId =>
|
||||||
|
columnsByName['album_id']! as i1.GeneratedColumn<String>;
|
||||||
|
}
|
||||||
|
|
||||||
|
i1.GeneratedColumn<String> _column_32(String aliasedName) =>
|
||||||
|
i1.GeneratedColumn<String>('asset_id', aliasedName, false,
|
||||||
|
type: i1.DriftSqlType.string,
|
||||||
|
defaultConstraints: i1.GeneratedColumn.constraintIsAlways(
|
||||||
|
'REFERENCES local_asset_entity (id) ON DELETE CASCADE'));
|
||||||
|
i1.GeneratedColumn<String> _column_33(String aliasedName) =>
|
||||||
|
i1.GeneratedColumn<String>('album_id', aliasedName, false,
|
||||||
|
type: i1.DriftSqlType.string,
|
||||||
|
defaultConstraints: i1.GeneratedColumn.constraintIsAlways(
|
||||||
|
'REFERENCES local_album_entity (id) ON DELETE CASCADE'));
|
||||||
|
|
||||||
|
class Shape7 extends i0.VersionedTable {
|
||||||
|
Shape7({required super.source, required super.alias}) : super.aliased();
|
||||||
|
i1.GeneratedColumn<String> get assetId =>
|
||||||
|
columnsByName['asset_id']! as i1.GeneratedColumn<String>;
|
||||||
|
i1.GeneratedColumn<String> get city =>
|
||||||
|
columnsByName['city']! as i1.GeneratedColumn<String>;
|
||||||
|
i1.GeneratedColumn<String> get state =>
|
||||||
|
columnsByName['state']! as i1.GeneratedColumn<String>;
|
||||||
|
i1.GeneratedColumn<String> get country =>
|
||||||
|
columnsByName['country']! as i1.GeneratedColumn<String>;
|
||||||
|
i1.GeneratedColumn<DateTime> get dateTimeOriginal =>
|
||||||
|
columnsByName['date_time_original']! as i1.GeneratedColumn<DateTime>;
|
||||||
|
i1.GeneratedColumn<String> get description =>
|
||||||
|
columnsByName['description']! as i1.GeneratedColumn<String>;
|
||||||
|
i1.GeneratedColumn<int> get height =>
|
||||||
|
columnsByName['height']! as i1.GeneratedColumn<int>;
|
||||||
|
i1.GeneratedColumn<int> get width =>
|
||||||
|
columnsByName['width']! as i1.GeneratedColumn<int>;
|
||||||
|
i1.GeneratedColumn<String> get exposureTime =>
|
||||||
|
columnsByName['exposure_time']! as i1.GeneratedColumn<String>;
|
||||||
|
i1.GeneratedColumn<double> get fNumber =>
|
||||||
|
columnsByName['f_number']! as i1.GeneratedColumn<double>;
|
||||||
|
i1.GeneratedColumn<int> get fileSize =>
|
||||||
|
columnsByName['file_size']! as i1.GeneratedColumn<int>;
|
||||||
|
i1.GeneratedColumn<double> get focalLength =>
|
||||||
|
columnsByName['focal_length']! as i1.GeneratedColumn<double>;
|
||||||
|
i1.GeneratedColumn<double> get latitude =>
|
||||||
|
columnsByName['latitude']! as i1.GeneratedColumn<double>;
|
||||||
|
i1.GeneratedColumn<double> get longitude =>
|
||||||
|
columnsByName['longitude']! as i1.GeneratedColumn<double>;
|
||||||
|
i1.GeneratedColumn<int> get iso =>
|
||||||
|
columnsByName['iso']! as i1.GeneratedColumn<int>;
|
||||||
|
i1.GeneratedColumn<String> get make =>
|
||||||
|
columnsByName['make']! as i1.GeneratedColumn<String>;
|
||||||
|
i1.GeneratedColumn<String> get model =>
|
||||||
|
columnsByName['model']! as i1.GeneratedColumn<String>;
|
||||||
|
i1.GeneratedColumn<String> get lens =>
|
||||||
|
columnsByName['lens']! as i1.GeneratedColumn<String>;
|
||||||
|
i1.GeneratedColumn<String> get orientation =>
|
||||||
|
columnsByName['orientation']! as i1.GeneratedColumn<String>;
|
||||||
|
i1.GeneratedColumn<String> get timeZone =>
|
||||||
|
columnsByName['time_zone']! as i1.GeneratedColumn<String>;
|
||||||
|
i1.GeneratedColumn<int> get rating =>
|
||||||
|
columnsByName['rating']! as i1.GeneratedColumn<int>;
|
||||||
|
i1.GeneratedColumn<String> get projectionType =>
|
||||||
|
columnsByName['projection_type']! as i1.GeneratedColumn<String>;
|
||||||
|
}
|
||||||
|
|
||||||
|
i1.GeneratedColumn<String> _column_34(String aliasedName) =>
|
||||||
|
i1.GeneratedColumn<String>('asset_id', aliasedName, false,
|
||||||
|
type: i1.DriftSqlType.string,
|
||||||
|
defaultConstraints: i1.GeneratedColumn.constraintIsAlways(
|
||||||
|
'REFERENCES remote_asset_entity (id) ON DELETE CASCADE'));
|
||||||
|
i1.GeneratedColumn<String> _column_35(String aliasedName) =>
|
||||||
|
i1.GeneratedColumn<String>('city', aliasedName, true,
|
||||||
|
type: i1.DriftSqlType.string);
|
||||||
|
i1.GeneratedColumn<String> _column_36(String aliasedName) =>
|
||||||
|
i1.GeneratedColumn<String>('state', aliasedName, true,
|
||||||
|
type: i1.DriftSqlType.string);
|
||||||
|
i1.GeneratedColumn<String> _column_37(String aliasedName) =>
|
||||||
|
i1.GeneratedColumn<String>('country', aliasedName, true,
|
||||||
|
type: i1.DriftSqlType.string);
|
||||||
|
i1.GeneratedColumn<DateTime> _column_38(String aliasedName) =>
|
||||||
|
i1.GeneratedColumn<DateTime>('date_time_original', aliasedName, true,
|
||||||
|
type: i1.DriftSqlType.dateTime);
|
||||||
|
i1.GeneratedColumn<String> _column_39(String aliasedName) =>
|
||||||
|
i1.GeneratedColumn<String>('description', aliasedName, true,
|
||||||
|
type: i1.DriftSqlType.string);
|
||||||
|
i1.GeneratedColumn<String> _column_40(String aliasedName) =>
|
||||||
|
i1.GeneratedColumn<String>('exposure_time', aliasedName, true,
|
||||||
|
type: i1.DriftSqlType.string);
|
||||||
|
i1.GeneratedColumn<double> _column_41(String aliasedName) =>
|
||||||
|
i1.GeneratedColumn<double>('f_number', aliasedName, true,
|
||||||
|
type: i1.DriftSqlType.double);
|
||||||
|
i1.GeneratedColumn<int> _column_42(String aliasedName) =>
|
||||||
|
i1.GeneratedColumn<int>('file_size', aliasedName, true,
|
||||||
|
type: i1.DriftSqlType.int);
|
||||||
|
i1.GeneratedColumn<double> _column_43(String aliasedName) =>
|
||||||
|
i1.GeneratedColumn<double>('focal_length', aliasedName, true,
|
||||||
|
type: i1.DriftSqlType.double);
|
||||||
|
i1.GeneratedColumn<double> _column_44(String aliasedName) =>
|
||||||
|
i1.GeneratedColumn<double>('latitude', aliasedName, true,
|
||||||
|
type: i1.DriftSqlType.double);
|
||||||
|
i1.GeneratedColumn<double> _column_45(String aliasedName) =>
|
||||||
|
i1.GeneratedColumn<double>('longitude', aliasedName, true,
|
||||||
|
type: i1.DriftSqlType.double);
|
||||||
|
i1.GeneratedColumn<int> _column_46(String aliasedName) =>
|
||||||
|
i1.GeneratedColumn<int>('iso', aliasedName, true,
|
||||||
|
type: i1.DriftSqlType.int);
|
||||||
|
i1.GeneratedColumn<String> _column_47(String aliasedName) =>
|
||||||
|
i1.GeneratedColumn<String>('make', aliasedName, true,
|
||||||
|
type: i1.DriftSqlType.string);
|
||||||
|
i1.GeneratedColumn<String> _column_48(String aliasedName) =>
|
||||||
|
i1.GeneratedColumn<String>('model', aliasedName, true,
|
||||||
|
type: i1.DriftSqlType.string);
|
||||||
|
i1.GeneratedColumn<String> _column_49(String aliasedName) =>
|
||||||
|
i1.GeneratedColumn<String>('lens', aliasedName, true,
|
||||||
|
type: i1.DriftSqlType.string);
|
||||||
|
i1.GeneratedColumn<String> _column_50(String aliasedName) =>
|
||||||
|
i1.GeneratedColumn<String>('orientation', aliasedName, true,
|
||||||
|
type: i1.DriftSqlType.string);
|
||||||
|
i1.GeneratedColumn<String> _column_51(String aliasedName) =>
|
||||||
|
i1.GeneratedColumn<String>('time_zone', aliasedName, true,
|
||||||
|
type: i1.DriftSqlType.string);
|
||||||
|
i1.GeneratedColumn<int> _column_52(String aliasedName) =>
|
||||||
|
i1.GeneratedColumn<int>('rating', aliasedName, true,
|
||||||
|
type: i1.DriftSqlType.int);
|
||||||
|
i1.GeneratedColumn<String> _column_53(String aliasedName) =>
|
||||||
|
i1.GeneratedColumn<String>('projection_type', aliasedName, true,
|
||||||
|
type: i1.DriftSqlType.string);
|
||||||
|
|
||||||
|
class Shape8 extends i0.VersionedTable {
|
||||||
|
Shape8({required super.source, required super.alias}) : super.aliased();
|
||||||
|
i1.GeneratedColumn<String> get id =>
|
||||||
|
columnsByName['id']! as i1.GeneratedColumn<String>;
|
||||||
|
i1.GeneratedColumn<String> get name =>
|
||||||
|
columnsByName['name']! as i1.GeneratedColumn<String>;
|
||||||
|
i1.GeneratedColumn<String> get description =>
|
||||||
|
columnsByName['description']! as i1.GeneratedColumn<String>;
|
||||||
|
i1.GeneratedColumn<DateTime> get createdAt =>
|
||||||
|
columnsByName['created_at']! as i1.GeneratedColumn<DateTime>;
|
||||||
|
i1.GeneratedColumn<DateTime> get updatedAt =>
|
||||||
|
columnsByName['updated_at']! as i1.GeneratedColumn<DateTime>;
|
||||||
|
i1.GeneratedColumn<String> get ownerId =>
|
||||||
|
columnsByName['owner_id']! as i1.GeneratedColumn<String>;
|
||||||
|
i1.GeneratedColumn<String> get thumbnailAssetId =>
|
||||||
|
columnsByName['thumbnail_asset_id']! as i1.GeneratedColumn<String>;
|
||||||
|
i1.GeneratedColumn<bool> get isActivityEnabled =>
|
||||||
|
columnsByName['is_activity_enabled']! as i1.GeneratedColumn<bool>;
|
||||||
|
i1.GeneratedColumn<int> get order =>
|
||||||
|
columnsByName['order']! as i1.GeneratedColumn<int>;
|
||||||
|
}
|
||||||
|
|
||||||
|
i1.GeneratedColumn<String> _column_54(String aliasedName) =>
|
||||||
|
i1.GeneratedColumn<String>('description', aliasedName, false,
|
||||||
|
type: i1.DriftSqlType.string,
|
||||||
|
defaultValue: const CustomExpression('\'\''));
|
||||||
|
i1.GeneratedColumn<String> _column_55(String aliasedName) =>
|
||||||
|
i1.GeneratedColumn<String>('thumbnail_asset_id', aliasedName, true,
|
||||||
|
type: i1.DriftSqlType.string,
|
||||||
|
defaultConstraints: i1.GeneratedColumn.constraintIsAlways(
|
||||||
|
'REFERENCES remote_asset_entity (id) ON DELETE SET NULL'));
|
||||||
|
i1.GeneratedColumn<bool> _column_56(String aliasedName) =>
|
||||||
|
i1.GeneratedColumn<bool>('is_activity_enabled', aliasedName, false,
|
||||||
|
type: i1.DriftSqlType.bool,
|
||||||
|
defaultConstraints: i1.GeneratedColumn.constraintIsAlways(
|
||||||
|
'CHECK ("is_activity_enabled" IN (0, 1))'),
|
||||||
|
defaultValue: const CustomExpression('1'));
|
||||||
|
i1.GeneratedColumn<int> _column_57(String aliasedName) =>
|
||||||
|
i1.GeneratedColumn<int>('order', aliasedName, false,
|
||||||
|
type: i1.DriftSqlType.int);
|
||||||
|
i1.GeneratedColumn<String> _column_58(String aliasedName) =>
|
||||||
|
i1.GeneratedColumn<String>('album_id', aliasedName, false,
|
||||||
|
type: i1.DriftSqlType.string,
|
||||||
|
defaultConstraints: i1.GeneratedColumn.constraintIsAlways(
|
||||||
|
'REFERENCES remote_album_entity (id) ON DELETE CASCADE'));
|
||||||
|
|
||||||
|
class Shape9 extends i0.VersionedTable {
|
||||||
|
Shape9({required super.source, required super.alias}) : super.aliased();
|
||||||
|
i1.GeneratedColumn<String> get albumId =>
|
||||||
|
columnsByName['album_id']! as i1.GeneratedColumn<String>;
|
||||||
|
i1.GeneratedColumn<String> get userId =>
|
||||||
|
columnsByName['user_id']! as i1.GeneratedColumn<String>;
|
||||||
|
i1.GeneratedColumn<int> get role =>
|
||||||
|
columnsByName['role']! as i1.GeneratedColumn<int>;
|
||||||
|
}
|
||||||
|
|
||||||
|
i1.GeneratedColumn<int> _column_59(String aliasedName) =>
|
||||||
|
i1.GeneratedColumn<int>('role', aliasedName, false,
|
||||||
|
type: i1.DriftSqlType.int);
|
||||||
|
|
||||||
|
class Shape10 extends i0.VersionedTable {
|
||||||
|
Shape10({required super.source, required super.alias}) : super.aliased();
|
||||||
|
i1.GeneratedColumn<String> get id =>
|
||||||
|
columnsByName['id']! as i1.GeneratedColumn<String>;
|
||||||
|
i1.GeneratedColumn<DateTime> get createdAt =>
|
||||||
|
columnsByName['created_at']! as i1.GeneratedColumn<DateTime>;
|
||||||
|
i1.GeneratedColumn<DateTime> get updatedAt =>
|
||||||
|
columnsByName['updated_at']! as i1.GeneratedColumn<DateTime>;
|
||||||
|
i1.GeneratedColumn<DateTime> get deletedAt =>
|
||||||
|
columnsByName['deleted_at']! as i1.GeneratedColumn<DateTime>;
|
||||||
|
i1.GeneratedColumn<String> get ownerId =>
|
||||||
|
columnsByName['owner_id']! as i1.GeneratedColumn<String>;
|
||||||
|
i1.GeneratedColumn<int> get type =>
|
||||||
|
columnsByName['type']! as i1.GeneratedColumn<int>;
|
||||||
|
i1.GeneratedColumn<String> get data =>
|
||||||
|
columnsByName['data']! as i1.GeneratedColumn<String>;
|
||||||
|
i1.GeneratedColumn<bool> get isSaved =>
|
||||||
|
columnsByName['is_saved']! as i1.GeneratedColumn<bool>;
|
||||||
|
i1.GeneratedColumn<DateTime> get memoryAt =>
|
||||||
|
columnsByName['memory_at']! as i1.GeneratedColumn<DateTime>;
|
||||||
|
i1.GeneratedColumn<DateTime> get seenAt =>
|
||||||
|
columnsByName['seen_at']! as i1.GeneratedColumn<DateTime>;
|
||||||
|
i1.GeneratedColumn<DateTime> get showAt =>
|
||||||
|
columnsByName['show_at']! as i1.GeneratedColumn<DateTime>;
|
||||||
|
i1.GeneratedColumn<DateTime> get hideAt =>
|
||||||
|
columnsByName['hide_at']! as i1.GeneratedColumn<DateTime>;
|
||||||
|
}
|
||||||
|
|
||||||
|
i1.GeneratedColumn<String> _column_60(String aliasedName) =>
|
||||||
|
i1.GeneratedColumn<String>('data', aliasedName, false,
|
||||||
|
type: i1.DriftSqlType.string);
|
||||||
|
i1.GeneratedColumn<bool> _column_61(String aliasedName) =>
|
||||||
|
i1.GeneratedColumn<bool>('is_saved', aliasedName, false,
|
||||||
|
type: i1.DriftSqlType.bool,
|
||||||
|
defaultConstraints: i1.GeneratedColumn.constraintIsAlways(
|
||||||
|
'CHECK ("is_saved" IN (0, 1))'),
|
||||||
|
defaultValue: const CustomExpression('0'));
|
||||||
|
i1.GeneratedColumn<DateTime> _column_62(String aliasedName) =>
|
||||||
|
i1.GeneratedColumn<DateTime>('memory_at', aliasedName, false,
|
||||||
|
type: i1.DriftSqlType.dateTime);
|
||||||
|
i1.GeneratedColumn<DateTime> _column_63(String aliasedName) =>
|
||||||
|
i1.GeneratedColumn<DateTime>('seen_at', aliasedName, true,
|
||||||
|
type: i1.DriftSqlType.dateTime);
|
||||||
|
i1.GeneratedColumn<DateTime> _column_64(String aliasedName) =>
|
||||||
|
i1.GeneratedColumn<DateTime>('show_at', aliasedName, true,
|
||||||
|
type: i1.DriftSqlType.dateTime);
|
||||||
|
i1.GeneratedColumn<DateTime> _column_65(String aliasedName) =>
|
||||||
|
i1.GeneratedColumn<DateTime>('hide_at', aliasedName, true,
|
||||||
|
type: i1.DriftSqlType.dateTime);
|
||||||
|
|
||||||
|
class Shape11 extends i0.VersionedTable {
|
||||||
|
Shape11({required super.source, required super.alias}) : super.aliased();
|
||||||
|
i1.GeneratedColumn<String> get assetId =>
|
||||||
|
columnsByName['asset_id']! as i1.GeneratedColumn<String>;
|
||||||
|
i1.GeneratedColumn<String> get memoryId =>
|
||||||
|
columnsByName['memory_id']! as i1.GeneratedColumn<String>;
|
||||||
|
}
|
||||||
|
|
||||||
|
i1.GeneratedColumn<String> _column_66(String aliasedName) =>
|
||||||
|
i1.GeneratedColumn<String>('memory_id', aliasedName, false,
|
||||||
|
type: i1.DriftSqlType.string,
|
||||||
|
defaultConstraints: i1.GeneratedColumn.constraintIsAlways(
|
||||||
|
'REFERENCES memory_entity (id) ON DELETE CASCADE'));
|
||||||
|
|
||||||
|
class Shape12 extends i0.VersionedTable {
|
||||||
|
Shape12({required super.source, required super.alias}) : super.aliased();
|
||||||
|
i1.GeneratedColumn<String> get id =>
|
||||||
|
columnsByName['id']! as i1.GeneratedColumn<String>;
|
||||||
|
i1.GeneratedColumn<DateTime> get createdAt =>
|
||||||
|
columnsByName['created_at']! as i1.GeneratedColumn<DateTime>;
|
||||||
|
i1.GeneratedColumn<DateTime> get updatedAt =>
|
||||||
|
columnsByName['updated_at']! as i1.GeneratedColumn<DateTime>;
|
||||||
|
i1.GeneratedColumn<String> get ownerId =>
|
||||||
|
columnsByName['owner_id']! as i1.GeneratedColumn<String>;
|
||||||
|
i1.GeneratedColumn<String> get primaryAssetId =>
|
||||||
|
columnsByName['primary_asset_id']! as i1.GeneratedColumn<String>;
|
||||||
|
}
|
||||||
|
|
||||||
|
i1.GeneratedColumn<String> _column_67(String aliasedName) =>
|
||||||
|
i1.GeneratedColumn<String>('primary_asset_id', aliasedName, false,
|
||||||
|
type: i1.DriftSqlType.string,
|
||||||
|
defaultConstraints: i1.GeneratedColumn.constraintIsAlways(
|
||||||
|
'REFERENCES remote_asset_entity (id)'));
|
||||||
|
i0.MigrationStepWithVersion migrationSteps({
|
||||||
|
required Future<void> Function(i1.Migrator m, Schema2 schema) from1To2,
|
||||||
|
}) {
|
||||||
|
return (currentVersion, database) async {
|
||||||
|
switch (currentVersion) {
|
||||||
|
case 1:
|
||||||
|
final schema = Schema2(database: database);
|
||||||
|
final migrator = i1.Migrator(database, schema);
|
||||||
|
await from1To2(migrator, schema);
|
||||||
|
return 2;
|
||||||
|
default:
|
||||||
|
throw ArgumentError.value('Unknown migration from $currentVersion');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
i1.OnUpgrade stepByStep({
|
||||||
|
required Future<void> Function(i1.Migrator m, Schema2 schema) from1To2,
|
||||||
|
}) =>
|
||||||
|
i0.VersionedSchema.stepByStepHelper(
|
||||||
|
step: migrationSteps(
|
||||||
|
from1To2: from1To2,
|
||||||
|
));
|
126
mobile/lib/pages/common/change_experience.page.dart
Normal file
126
mobile/lib/pages/common/change_experience.page.dart
Normal file
@ -0,0 +1,126 @@
|
|||||||
|
import 'package:auto_route/auto_route.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
|
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
||||||
|
import 'package:immich_mobile/providers/album/album.provider.dart';
|
||||||
|
import 'package:immich_mobile/providers/asset.provider.dart';
|
||||||
|
import 'package:immich_mobile/providers/background_sync.provider.dart';
|
||||||
|
import 'package:immich_mobile/providers/gallery_permission.provider.dart';
|
||||||
|
import 'package:immich_mobile/providers/infrastructure/db.provider.dart';
|
||||||
|
import 'package:immich_mobile/routing/router.dart';
|
||||||
|
import 'package:immich_mobile/utils/migration.dart';
|
||||||
|
import 'package:permission_handler/permission_handler.dart';
|
||||||
|
|
||||||
|
@RoutePage()
|
||||||
|
class ChangeExperiencePage extends ConsumerStatefulWidget {
|
||||||
|
final bool switchingToBeta;
|
||||||
|
|
||||||
|
const ChangeExperiencePage({super.key, required this.switchingToBeta});
|
||||||
|
|
||||||
|
@override
|
||||||
|
ConsumerState createState() => _ChangeExperiencePageState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ChangeExperiencePageState extends ConsumerState<ChangeExperiencePage> {
|
||||||
|
bool hasMigrated = false;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((_) => _handleMigration());
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _handleMigration() async {
|
||||||
|
if (widget.switchingToBeta) {
|
||||||
|
final assetNotifier = ref.read(assetProvider.notifier);
|
||||||
|
if (assetNotifier.mounted) {
|
||||||
|
assetNotifier.dispose();
|
||||||
|
}
|
||||||
|
final albumNotifier = ref.read(albumProvider.notifier);
|
||||||
|
if (albumNotifier.mounted) {
|
||||||
|
albumNotifier.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
final permission = await ref
|
||||||
|
.read(galleryPermissionNotifier.notifier)
|
||||||
|
.requestGalleryPermission();
|
||||||
|
|
||||||
|
if (permission.isGranted) {
|
||||||
|
await ref.read(backgroundSyncProvider).syncLocal(full: true);
|
||||||
|
await migrateDeviceAssetToSqlite(
|
||||||
|
ref.read(isarProvider),
|
||||||
|
ref.read(driftProvider),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
await ref.read(backgroundSyncProvider).cancel();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future.delayed(const Duration(seconds: 3), () {
|
||||||
|
context.replaceRoute(
|
||||||
|
widget.switchingToBeta
|
||||||
|
? const TabShellRoute()
|
||||||
|
: const TabControllerRoute(),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (mounted) {
|
||||||
|
setState(() {
|
||||||
|
HapticFeedback.heavyImpact();
|
||||||
|
hasMigrated = true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Scaffold(
|
||||||
|
body: Center(
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
AnimatedSwitcher(
|
||||||
|
duration: Durations.long4,
|
||||||
|
child: hasMigrated
|
||||||
|
? const Icon(
|
||||||
|
Icons.check_circle_rounded,
|
||||||
|
color: Colors.green,
|
||||||
|
size: 48.0,
|
||||||
|
)
|
||||||
|
: const SizedBox(
|
||||||
|
width: 50.0,
|
||||||
|
height: 50.0,
|
||||||
|
child: CircularProgressIndicator(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16.0),
|
||||||
|
Center(
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
SizedBox(
|
||||||
|
width: 300.0,
|
||||||
|
child: AnimatedSwitcher(
|
||||||
|
duration: Durations.long4,
|
||||||
|
child: hasMigrated
|
||||||
|
? Text(
|
||||||
|
"Migration success. Navigating to the new timeline...",
|
||||||
|
style: context.textTheme.titleMedium,
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
)
|
||||||
|
: Text(
|
||||||
|
"Data migration in progress...\nPlease wait and don't close this page",
|
||||||
|
style: context.textTheme.titleMedium,
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -8,6 +8,7 @@ import 'package:immich_mobile/widgets/settings/advanced_settings.dart';
|
|||||||
import 'package:immich_mobile/widgets/settings/asset_list_settings/asset_list_settings.dart';
|
import 'package:immich_mobile/widgets/settings/asset_list_settings/asset_list_settings.dart';
|
||||||
import 'package:immich_mobile/widgets/settings/asset_viewer_settings/asset_viewer_settings.dart';
|
import 'package:immich_mobile/widgets/settings/asset_viewer_settings/asset_viewer_settings.dart';
|
||||||
import 'package:immich_mobile/widgets/settings/backup_settings/backup_settings.dart';
|
import 'package:immich_mobile/widgets/settings/backup_settings/backup_settings.dart';
|
||||||
|
import 'package:immich_mobile/widgets/settings/beta_timeline_list_tile.dart';
|
||||||
import 'package:immich_mobile/widgets/settings/language_settings.dart';
|
import 'package:immich_mobile/widgets/settings/language_settings.dart';
|
||||||
import 'package:immich_mobile/widgets/settings/networking_settings/networking_settings.dart';
|
import 'package:immich_mobile/widgets/settings/networking_settings/networking_settings.dart';
|
||||||
import 'package:immich_mobile/widgets/settings/notification_setting.dart';
|
import 'package:immich_mobile/widgets/settings/notification_setting.dart';
|
||||||
@ -94,55 +95,59 @@ class _MobileLayout extends StatelessWidget {
|
|||||||
const _MobileLayout();
|
const _MobileLayout();
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
final List<Widget> settings = SettingSection.values
|
||||||
|
.map(
|
||||||
|
(setting) => Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 16.0,
|
||||||
|
),
|
||||||
|
child: Card(
|
||||||
|
elevation: 0,
|
||||||
|
clipBehavior: Clip.antiAlias,
|
||||||
|
color: context.colorScheme.surfaceContainer,
|
||||||
|
shape: const RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.all(Radius.circular(16)),
|
||||||
|
),
|
||||||
|
margin: const EdgeInsets.symmetric(vertical: 4.0),
|
||||||
|
child: ListTile(
|
||||||
|
contentPadding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 16.0,
|
||||||
|
),
|
||||||
|
leading: Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
borderRadius: const BorderRadius.all(Radius.circular(16)),
|
||||||
|
color: context.isDarkTheme
|
||||||
|
? Colors.black26
|
||||||
|
: Colors.white.withAlpha(100),
|
||||||
|
),
|
||||||
|
padding: const EdgeInsets.all(16.0),
|
||||||
|
child: Icon(setting.icon, color: context.primaryColor),
|
||||||
|
),
|
||||||
|
title: Text(
|
||||||
|
setting.title,
|
||||||
|
style: context.textTheme.titleMedium!.copyWith(
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
color: context.primaryColor,
|
||||||
|
),
|
||||||
|
).tr(),
|
||||||
|
subtitle: Text(
|
||||||
|
setting.subtitle,
|
||||||
|
style: context.textTheme.labelLarge,
|
||||||
|
).tr(),
|
||||||
|
onTap: () =>
|
||||||
|
context.pushRoute(SettingsSubRoute(section: setting)),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.toList();
|
||||||
return ListView(
|
return ListView(
|
||||||
physics: const ClampingScrollPhysics(),
|
physics: const ClampingScrollPhysics(),
|
||||||
padding: const EdgeInsets.symmetric(vertical: 10.0),
|
padding: const EdgeInsets.symmetric(vertical: 10.0),
|
||||||
children: SettingSection.values
|
children: [
|
||||||
.map(
|
const BetaTimelineListTile(),
|
||||||
(setting) => Padding(
|
...settings,
|
||||||
padding: const EdgeInsets.symmetric(
|
],
|
||||||
horizontal: 16.0,
|
|
||||||
),
|
|
||||||
child: Card(
|
|
||||||
elevation: 0,
|
|
||||||
clipBehavior: Clip.antiAlias,
|
|
||||||
color: context.colorScheme.surfaceContainer,
|
|
||||||
shape: const RoundedRectangleBorder(
|
|
||||||
borderRadius: BorderRadius.all(Radius.circular(16)),
|
|
||||||
),
|
|
||||||
margin: const EdgeInsets.symmetric(vertical: 4.0),
|
|
||||||
child: ListTile(
|
|
||||||
contentPadding: const EdgeInsets.symmetric(
|
|
||||||
horizontal: 16.0,
|
|
||||||
),
|
|
||||||
leading: Container(
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
borderRadius: const BorderRadius.all(Radius.circular(16)),
|
|
||||||
color: context.isDarkTheme
|
|
||||||
? Colors.black26
|
|
||||||
: Colors.white.withAlpha(100),
|
|
||||||
),
|
|
||||||
padding: const EdgeInsets.all(16.0),
|
|
||||||
child: Icon(setting.icon, color: context.primaryColor),
|
|
||||||
),
|
|
||||||
title: Text(
|
|
||||||
setting.title,
|
|
||||||
style: context.textTheme.titleMedium!.copyWith(
|
|
||||||
fontWeight: FontWeight.w600,
|
|
||||||
color: context.primaryColor,
|
|
||||||
),
|
|
||||||
).tr(),
|
|
||||||
subtitle: Text(
|
|
||||||
setting.subtitle,
|
|
||||||
style: context.textTheme.labelLarge,
|
|
||||||
).tr(),
|
|
||||||
onTap: () =>
|
|
||||||
context.pushRoute(SettingsSubRoute(section: setting)),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.toList(),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -73,7 +73,15 @@ class SplashScreenPageState extends ConsumerState<SplashScreenPage> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (context.router.current.name == SplashScreenRoute.name) {
|
if (context.router.current.name == SplashScreenRoute.name) {
|
||||||
context.replaceRoute(const TabControllerRoute());
|
context.replaceRoute(
|
||||||
|
Store.isBetaTimelineEnabled
|
||||||
|
? const TabShellRoute()
|
||||||
|
: const TabControllerRoute(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Store.isBetaTimelineEnabled) {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
final hasPermission =
|
final hasPermission =
|
||||||
|
@ -10,13 +10,28 @@ import 'package:immich_mobile/providers/search/search_input_focus.provider.dart'
|
|||||||
import 'package:immich_mobile/providers/tab.provider.dart';
|
import 'package:immich_mobile/providers/tab.provider.dart';
|
||||||
import 'package:immich_mobile/providers/timeline/multiselect.provider.dart';
|
import 'package:immich_mobile/providers/timeline/multiselect.provider.dart';
|
||||||
import 'package:immich_mobile/routing/router.dart';
|
import 'package:immich_mobile/routing/router.dart';
|
||||||
|
import 'package:immich_mobile/utils/migration.dart';
|
||||||
|
|
||||||
@RoutePage()
|
@RoutePage()
|
||||||
class TabShellPage extends ConsumerWidget {
|
class TabShellPage extends ConsumerStatefulWidget {
|
||||||
const TabShellPage({super.key});
|
const TabShellPage({super.key});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
ConsumerState<TabShellPage> createState() => _TabShellPageState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _TabShellPageState extends ConsumerState<TabShellPage> {
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
|
runNewSync(ref, full: true);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
final isScreenLandscape = context.orientation == Orientation.landscape;
|
final isScreenLandscape = context.orientation == Orientation.landscape;
|
||||||
|
|
||||||
Widget buildIcon({required Widget icon, required bool isProcessing}) {
|
Widget buildIcon({required Widget icon, required bool isProcessing}) {
|
||||||
|
@ -3,6 +3,7 @@ import 'package:easy_localization/easy_localization.dart';
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_hooks/flutter_hooks.dart' hide Store;
|
import 'package:flutter_hooks/flutter_hooks.dart' hide Store;
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
|
import 'package:immich_mobile/entities/store.entity.dart';
|
||||||
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
||||||
import 'package:immich_mobile/models/upload/share_intent_attachment.model.dart';
|
import 'package:immich_mobile/models/upload/share_intent_attachment.model.dart';
|
||||||
import 'package:immich_mobile/pages/common/large_leading_tile.dart';
|
import 'package:immich_mobile/pages/common/large_leading_tile.dart';
|
||||||
@ -75,7 +76,9 @@ class ShareIntentPage extends HookConsumerWidget {
|
|||||||
leading: IconButton(
|
leading: IconButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
context.navigateTo(
|
context.navigateTo(
|
||||||
const TabControllerRoute(),
|
Store.isBetaTimelineEnabled
|
||||||
|
? const TabShellRoute()
|
||||||
|
: const TabControllerRoute(),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
icon: const Icon(Icons.arrow_back),
|
icon: const Icon(Icons.arrow_back),
|
||||||
|
@ -3,10 +3,12 @@ import 'dart:async';
|
|||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:immich_mobile/domain/services/log.service.dart';
|
import 'package:immich_mobile/domain/services/log.service.dart';
|
||||||
|
import 'package:immich_mobile/entities/store.entity.dart';
|
||||||
import 'package:immich_mobile/models/backup/backup_state.model.dart';
|
import 'package:immich_mobile/models/backup/backup_state.model.dart';
|
||||||
import 'package:immich_mobile/providers/album/album.provider.dart';
|
import 'package:immich_mobile/providers/album/album.provider.dart';
|
||||||
import 'package:immich_mobile/providers/asset.provider.dart';
|
import 'package:immich_mobile/providers/asset.provider.dart';
|
||||||
import 'package:immich_mobile/providers/auth.provider.dart';
|
import 'package:immich_mobile/providers/auth.provider.dart';
|
||||||
|
import 'package:immich_mobile/providers/background_sync.provider.dart';
|
||||||
import 'package:immich_mobile/providers/backup/backup.provider.dart';
|
import 'package:immich_mobile/providers/backup/backup.provider.dart';
|
||||||
import 'package:immich_mobile/providers/backup/ios_background_settings.provider.dart';
|
import 'package:immich_mobile/providers/backup/ios_background_settings.provider.dart';
|
||||||
import 'package:immich_mobile/providers/backup/manual_upload.provider.dart';
|
import 'package:immich_mobile/providers/backup/manual_upload.provider.dart';
|
||||||
@ -18,6 +20,7 @@ import 'package:immich_mobile/providers/tab.provider.dart';
|
|||||||
import 'package:immich_mobile/providers/websocket.provider.dart';
|
import 'package:immich_mobile/providers/websocket.provider.dart';
|
||||||
import 'package:immich_mobile/services/background.service.dart';
|
import 'package:immich_mobile/services/background.service.dart';
|
||||||
import 'package:isar/isar.dart';
|
import 'package:isar/isar.dart';
|
||||||
|
import 'package:logging/logging.dart';
|
||||||
import 'package:permission_handler/permission_handler.dart';
|
import 'package:permission_handler/permission_handler.dart';
|
||||||
|
|
||||||
enum AppLifeCycleEnum {
|
enum AppLifeCycleEnum {
|
||||||
@ -57,29 +60,59 @@ class AppLifeCycleNotifier extends StateNotifier<AppLifeCycleEnum> {
|
|||||||
debugPrint("Using server URL: $endpoint");
|
debugPrint("Using server URL: $endpoint");
|
||||||
}
|
}
|
||||||
|
|
||||||
final permission = _ref.watch(galleryPermissionNotifier);
|
if (!Store.isBetaTimelineEnabled) {
|
||||||
if (permission.isGranted || permission.isLimited) {
|
final permission = _ref.watch(galleryPermissionNotifier);
|
||||||
await _ref.read(backupProvider.notifier).resumeBackup();
|
if (permission.isGranted || permission.isLimited) {
|
||||||
await _ref.read(backgroundServiceProvider).resumeServiceIfEnabled();
|
await _ref.read(backupProvider.notifier).resumeBackup();
|
||||||
|
await _ref.read(backgroundServiceProvider).resumeServiceIfEnabled();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
await _ref.read(serverInfoProvider.notifier).getServerVersion();
|
await _ref.read(serverInfoProvider.notifier).getServerVersion();
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (_ref.read(tabProvider)) {
|
if (!Store.isBetaTimelineEnabled) {
|
||||||
case TabEnum.home:
|
switch (_ref.read(tabProvider)) {
|
||||||
await _ref.read(assetProvider.notifier).getAllAsset();
|
case TabEnum.home:
|
||||||
break;
|
await _ref.read(assetProvider.notifier).getAllAsset();
|
||||||
case TabEnum.search:
|
break;
|
||||||
// nothing to do
|
case TabEnum.search:
|
||||||
break;
|
// nothing to do
|
||||||
|
break;
|
||||||
|
|
||||||
case TabEnum.albums:
|
case TabEnum.albums:
|
||||||
await _ref.read(albumProvider.notifier).refreshRemoteAlbums();
|
await _ref.read(albumProvider.notifier).refreshRemoteAlbums();
|
||||||
break;
|
break;
|
||||||
case TabEnum.library:
|
case TabEnum.library:
|
||||||
// nothing to do
|
// nothing to do
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
_ref.read(backupProvider.notifier).cancelBackup();
|
||||||
|
|
||||||
|
final backgroundManager = _ref.read(backgroundSyncProvider);
|
||||||
|
// Ensure proper cleanup before starting new background tasks
|
||||||
|
try {
|
||||||
|
await Future.wait([
|
||||||
|
backgroundManager.syncLocal().then(
|
||||||
|
(_) {
|
||||||
|
Logger("AppLifeCycleNotifier")
|
||||||
|
.fine("Hashing assets after syncLocal");
|
||||||
|
// Check if app is still active before hashing
|
||||||
|
if (state == AppLifeCycleEnum.resumed) {
|
||||||
|
backgroundManager.hashAssets();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
backgroundManager.syncRemote(),
|
||||||
|
]);
|
||||||
|
} catch (e, stackTrace) {
|
||||||
|
Logger("AppLifeCycleNotifier").severe(
|
||||||
|
"Error during background sync",
|
||||||
|
e,
|
||||||
|
stackTrace,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_ref.read(websocketProvider.notifier).connect();
|
_ref.read(websocketProvider.notifier).connect();
|
||||||
@ -92,9 +125,11 @@ class AppLifeCycleNotifier extends StateNotifier<AppLifeCycleEnum> {
|
|||||||
.read(galleryPermissionNotifier.notifier)
|
.read(galleryPermissionNotifier.notifier)
|
||||||
.getGalleryPermissionStatus();
|
.getGalleryPermissionStatus();
|
||||||
|
|
||||||
await _ref.read(iOSBackgroundSettingsProvider.notifier).refresh();
|
if (!Store.isBetaTimelineEnabled) {
|
||||||
|
await _ref.read(iOSBackgroundSettingsProvider.notifier).refresh();
|
||||||
|
|
||||||
_ref.invalidate(memoryFutureProvider);
|
_ref.invalidate(memoryFutureProvider);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void handleAppInactivity() {
|
void handleAppInactivity() {
|
||||||
@ -106,7 +141,8 @@ class AppLifeCycleNotifier extends StateNotifier<AppLifeCycleEnum> {
|
|||||||
state = AppLifeCycleEnum.paused;
|
state = AppLifeCycleEnum.paused;
|
||||||
_wasPaused = true;
|
_wasPaused = true;
|
||||||
|
|
||||||
if (_ref.read(authProvider).isAuthenticated) {
|
if (!Store.isBetaTimelineEnabled &&
|
||||||
|
_ref.read(authProvider).isAuthenticated) {
|
||||||
// Do not cancel backup if manual upload is in progress
|
// Do not cancel backup if manual upload is in progress
|
||||||
if (_ref.read(backupProvider.notifier).backupProgress !=
|
if (_ref.read(backupProvider.notifier).backupProgress !=
|
||||||
BackUpProgressEnum.manualInProgress) {
|
BackUpProgressEnum.manualInProgress) {
|
||||||
@ -115,15 +151,43 @@ class AppLifeCycleNotifier extends StateNotifier<AppLifeCycleEnum> {
|
|||||||
_ref.read(websocketProvider.notifier).disconnect();
|
_ref.read(websocketProvider.notifier).disconnect();
|
||||||
}
|
}
|
||||||
|
|
||||||
LogService.I.flush();
|
try {
|
||||||
|
LogService.I.flush();
|
||||||
|
} catch (e) {
|
||||||
|
// Ignore flush errors during pause
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> handleAppDetached() async {
|
Future<void> handleAppDetached() async {
|
||||||
state = AppLifeCycleEnum.detached;
|
state = AppLifeCycleEnum.detached;
|
||||||
LogService.I.flush();
|
|
||||||
await Isar.getInstance()?.close();
|
// Flush logs before closing database
|
||||||
|
try {
|
||||||
|
LogService.I.flush();
|
||||||
|
} catch (e) {
|
||||||
|
// Ignore flush errors during shutdown
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close Isar database safely
|
||||||
|
try {
|
||||||
|
final isar = Isar.getInstance();
|
||||||
|
if (isar != null && isar.isOpen) {
|
||||||
|
await isar.close();
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
// Ignore close errors during shutdown
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Store.isBetaTimelineEnabled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// no guarantee this is called at all
|
// no guarantee this is called at all
|
||||||
_ref.read(manualUploadProvider.notifier).cancelBackup();
|
try {
|
||||||
|
_ref.read(manualUploadProvider.notifier).cancelBackup();
|
||||||
|
} catch (e) {
|
||||||
|
// Ignore errors during shutdown
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void handleAppHidden() {
|
void handleAppHidden() {
|
||||||
|
@ -13,5 +13,6 @@ Isar isar(Ref ref) => throw UnimplementedError('isar');
|
|||||||
final driftProvider = Provider<Drift>((ref) {
|
final driftProvider = Provider<Drift>((ref) {
|
||||||
final drift = Drift();
|
final drift = Drift();
|
||||||
ref.onDispose(() => unawaited(drift.close()));
|
ref.onDispose(() => unawaited(drift.close()));
|
||||||
|
ref.keepAlive();
|
||||||
return drift;
|
return drift;
|
||||||
});
|
});
|
||||||
|
@ -24,7 +24,25 @@ class AuthRepository extends DatabaseRepository {
|
|||||||
|
|
||||||
const AuthRepository(super.db, this._drift);
|
const AuthRepository(super.db, this._drift);
|
||||||
|
|
||||||
Future<void> clearLocalData() {
|
Future<void> clearLocalData() async {
|
||||||
|
// Drift deletions - child entities first (those with foreign keys)
|
||||||
|
await Future.wait([
|
||||||
|
_drift.memoryAssetEntity.deleteAll(),
|
||||||
|
_drift.remoteAlbumAssetEntity.deleteAll(),
|
||||||
|
_drift.remoteAlbumUserEntity.deleteAll(),
|
||||||
|
_drift.remoteExifEntity.deleteAll(),
|
||||||
|
_drift.userMetadataEntity.deleteAll(),
|
||||||
|
_drift.partnerEntity.deleteAll(),
|
||||||
|
_drift.stackEntity.deleteAll(),
|
||||||
|
]);
|
||||||
|
// Drift deletions - parent entities
|
||||||
|
await Future.wait([
|
||||||
|
_drift.memoryEntity.deleteAll(),
|
||||||
|
_drift.remoteAlbumEntity.deleteAll(),
|
||||||
|
_drift.remoteAssetEntity.deleteAll(),
|
||||||
|
_drift.userEntity.deleteAll(),
|
||||||
|
]);
|
||||||
|
|
||||||
return db.writeTxn(() {
|
return db.writeTxn(() {
|
||||||
return Future.wait([
|
return Future.wait([
|
||||||
db.assets.clear(),
|
db.assets.clear(),
|
||||||
@ -32,17 +50,6 @@ class AuthRepository extends DatabaseRepository {
|
|||||||
db.albums.clear(),
|
db.albums.clear(),
|
||||||
db.eTags.clear(),
|
db.eTags.clear(),
|
||||||
db.users.clear(),
|
db.users.clear(),
|
||||||
_drift.remoteAssetEntity.deleteAll(),
|
|
||||||
_drift.remoteExifEntity.deleteAll(),
|
|
||||||
_drift.userEntity.deleteAll(),
|
|
||||||
_drift.userMetadataEntity.deleteAll(),
|
|
||||||
_drift.partnerEntity.deleteAll(),
|
|
||||||
_drift.remoteAlbumEntity.deleteAll(),
|
|
||||||
_drift.remoteAlbumAssetEntity.deleteAll(),
|
|
||||||
_drift.remoteAlbumUserEntity.deleteAll(),
|
|
||||||
_drift.memoryEntity.deleteAll(),
|
|
||||||
_drift.memoryAssetEntity.deleteAll(),
|
|
||||||
_drift.stackEntity.deleteAll(),
|
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -29,6 +29,7 @@ import 'package:immich_mobile/pages/backup/failed_backup_status.page.dart';
|
|||||||
import 'package:immich_mobile/pages/common/activities.page.dart';
|
import 'package:immich_mobile/pages/common/activities.page.dart';
|
||||||
import 'package:immich_mobile/pages/common/app_log.page.dart';
|
import 'package:immich_mobile/pages/common/app_log.page.dart';
|
||||||
import 'package:immich_mobile/pages/common/app_log_detail.page.dart';
|
import 'package:immich_mobile/pages/common/app_log_detail.page.dart';
|
||||||
|
import 'package:immich_mobile/pages/common/change_experience.page.dart';
|
||||||
import 'package:immich_mobile/pages/common/create_album.page.dart';
|
import 'package:immich_mobile/pages/common/create_album.page.dart';
|
||||||
import 'package:immich_mobile/pages/common/gallery_viewer.page.dart';
|
import 'package:immich_mobile/pages/common/gallery_viewer.page.dart';
|
||||||
import 'package:immich_mobile/pages/common/headers_settings.page.dart';
|
import 'package:immich_mobile/pages/common/headers_settings.page.dart';
|
||||||
@ -69,27 +70,27 @@ import 'package:immich_mobile/pages/search/person_result.page.dart';
|
|||||||
import 'package:immich_mobile/pages/search/recently_taken.page.dart';
|
import 'package:immich_mobile/pages/search/recently_taken.page.dart';
|
||||||
import 'package:immich_mobile/pages/search/search.page.dart';
|
import 'package:immich_mobile/pages/search/search.page.dart';
|
||||||
import 'package:immich_mobile/pages/share_intent/share_intent.page.dart';
|
import 'package:immich_mobile/pages/share_intent/share_intent.page.dart';
|
||||||
|
import 'package:immich_mobile/presentation/pages/dev/feat_in_development.page.dart';
|
||||||
|
import 'package:immich_mobile/presentation/pages/dev/main_timeline.page.dart';
|
||||||
|
import 'package:immich_mobile/presentation/pages/dev/media_stat.page.dart';
|
||||||
|
import 'package:immich_mobile/presentation/pages/drift_album.page.dart';
|
||||||
|
import 'package:immich_mobile/presentation/pages/drift_archive.page.dart';
|
||||||
|
import 'package:immich_mobile/presentation/pages/drift_asset_selection_timeline.page.dart';
|
||||||
|
import 'package:immich_mobile/presentation/pages/drift_create_album.page.dart';
|
||||||
import 'package:immich_mobile/presentation/pages/drift_favorite.page.dart';
|
import 'package:immich_mobile/presentation/pages/drift_favorite.page.dart';
|
||||||
import 'package:immich_mobile/presentation/pages/drift_partner_detail.page.dart';
|
import 'package:immich_mobile/presentation/pages/drift_library.page.dart';
|
||||||
import 'package:immich_mobile/presentation/pages/drift_local_album.page.dart';
|
import 'package:immich_mobile/presentation/pages/drift_local_album.page.dart';
|
||||||
|
import 'package:immich_mobile/presentation/pages/drift_locked_folder.page.dart';
|
||||||
|
import 'package:immich_mobile/presentation/pages/drift_memory.page.dart';
|
||||||
|
import 'package:immich_mobile/presentation/pages/drift_partner_detail.page.dart';
|
||||||
import 'package:immich_mobile/presentation/pages/drift_place.page.dart';
|
import 'package:immich_mobile/presentation/pages/drift_place.page.dart';
|
||||||
import 'package:immich_mobile/presentation/pages/drift_place_detail.page.dart';
|
import 'package:immich_mobile/presentation/pages/drift_place_detail.page.dart';
|
||||||
import 'package:immich_mobile/presentation/pages/drift_recently_taken.page.dart';
|
import 'package:immich_mobile/presentation/pages/drift_recently_taken.page.dart';
|
||||||
import 'package:immich_mobile/presentation/pages/drift_user_selection.page.dart';
|
import 'package:immich_mobile/presentation/pages/drift_user_selection.page.dart';
|
||||||
import 'package:immich_mobile/presentation/pages/drift_video.page.dart';
|
|
||||||
import 'package:immich_mobile/presentation/pages/drift_trash.page.dart';
|
|
||||||
import 'package:immich_mobile/presentation/pages/drift_archive.page.dart';
|
|
||||||
import 'package:immich_mobile/presentation/pages/drift_locked_folder.page.dart';
|
|
||||||
import 'package:immich_mobile/presentation/pages/dev/feat_in_development.page.dart';
|
|
||||||
import 'package:immich_mobile/presentation/pages/local_timeline.page.dart';
|
|
||||||
import 'package:immich_mobile/presentation/pages/dev/main_timeline.page.dart';
|
|
||||||
import 'package:immich_mobile/presentation/pages/dev/media_stat.page.dart';
|
|
||||||
import 'package:immich_mobile/presentation/pages/drift_remote_album.page.dart';
|
import 'package:immich_mobile/presentation/pages/drift_remote_album.page.dart';
|
||||||
import 'package:immich_mobile/presentation/pages/drift_album.page.dart';
|
import 'package:immich_mobile/presentation/pages/drift_trash.page.dart';
|
||||||
import 'package:immich_mobile/presentation/pages/drift_library.page.dart';
|
import 'package:immich_mobile/presentation/pages/drift_video.page.dart';
|
||||||
import 'package:immich_mobile/presentation/pages/drift_asset_selection_timeline.page.dart';
|
import 'package:immich_mobile/presentation/pages/local_timeline.page.dart';
|
||||||
import 'package:immich_mobile/presentation/pages/drift_create_album.page.dart';
|
|
||||||
import 'package:immich_mobile/presentation/pages/drift_memory.page.dart';
|
|
||||||
import 'package:immich_mobile/presentation/widgets/asset_viewer/asset_viewer.page.dart';
|
import 'package:immich_mobile/presentation/widgets/asset_viewer/asset_viewer.page.dart';
|
||||||
import 'package:immich_mobile/providers/api.provider.dart';
|
import 'package:immich_mobile/providers/api.provider.dart';
|
||||||
import 'package:immich_mobile/providers/gallery_permission.provider.dart';
|
import 'package:immich_mobile/providers/gallery_permission.provider.dart';
|
||||||
@ -103,7 +104,6 @@ import 'package:immich_mobile/services/api.service.dart';
|
|||||||
import 'package:immich_mobile/services/local_auth.service.dart';
|
import 'package:immich_mobile/services/local_auth.service.dart';
|
||||||
import 'package:immich_mobile/services/secure_storage.service.dart';
|
import 'package:immich_mobile/services/secure_storage.service.dart';
|
||||||
import 'package:immich_mobile/widgets/asset_grid/asset_grid_data_structure.dart';
|
import 'package:immich_mobile/widgets/asset_grid/asset_grid_data_structure.dart';
|
||||||
|
|
||||||
import 'package:maplibre_gl/maplibre_gl.dart';
|
import 'package:maplibre_gl/maplibre_gl.dart';
|
||||||
|
|
||||||
part 'router.gr.dart';
|
part 'router.gr.dart';
|
||||||
@ -469,6 +469,10 @@ class AppRouter extends RootStackRouter {
|
|||||||
guards: [_authGuard, _duplicateGuard],
|
guards: [_authGuard, _duplicateGuard],
|
||||||
),
|
),
|
||||||
|
|
||||||
|
AutoRoute(
|
||||||
|
page: ChangeExperienceRoute.page,
|
||||||
|
guards: [_authGuard, _duplicateGuard],
|
||||||
|
),
|
||||||
// required to handle all deeplinks in deep_link.service.dart
|
// required to handle all deeplinks in deep_link.service.dart
|
||||||
// auto_route_library#1722
|
// auto_route_library#1722
|
||||||
RedirectRoute(path: '*', redirectTo: '/'),
|
RedirectRoute(path: '*', redirectTo: '/'),
|
||||||
|
@ -503,6 +503,49 @@ class BackupOptionsRoute extends PageRouteInfo<void> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// generated route for
|
||||||
|
/// [ChangeExperiencePage]
|
||||||
|
class ChangeExperienceRoute extends PageRouteInfo<ChangeExperienceRouteArgs> {
|
||||||
|
ChangeExperienceRoute({
|
||||||
|
Key? key,
|
||||||
|
required bool switchingToBeta,
|
||||||
|
List<PageRouteInfo>? children,
|
||||||
|
}) : super(
|
||||||
|
ChangeExperienceRoute.name,
|
||||||
|
args: ChangeExperienceRouteArgs(
|
||||||
|
key: key,
|
||||||
|
switchingToBeta: switchingToBeta,
|
||||||
|
),
|
||||||
|
initialChildren: children,
|
||||||
|
);
|
||||||
|
|
||||||
|
static const String name = 'ChangeExperienceRoute';
|
||||||
|
|
||||||
|
static PageInfo page = PageInfo(
|
||||||
|
name,
|
||||||
|
builder: (data) {
|
||||||
|
final args = data.argsAs<ChangeExperienceRouteArgs>();
|
||||||
|
return ChangeExperiencePage(
|
||||||
|
key: args.key,
|
||||||
|
switchingToBeta: args.switchingToBeta,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
class ChangeExperienceRouteArgs {
|
||||||
|
const ChangeExperienceRouteArgs({this.key, required this.switchingToBeta});
|
||||||
|
|
||||||
|
final Key? key;
|
||||||
|
|
||||||
|
final bool switchingToBeta;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return 'ChangeExperienceRouteArgs{key: $key, switchingToBeta: $switchingToBeta}';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// generated route for
|
/// generated route for
|
||||||
/// [ChangePasswordPage]
|
/// [ChangePasswordPage]
|
||||||
class ChangePasswordRoute extends PageRouteInfo<void> {
|
class ChangePasswordRoute extends PageRouteInfo<void> {
|
||||||
|
@ -90,6 +90,7 @@ enum AppSettingsEnum<T> {
|
|||||||
null,
|
null,
|
||||||
true,
|
true,
|
||||||
),
|
),
|
||||||
|
betaTimeline<bool>(StoreKey.betaTimeline, null, false),
|
||||||
;
|
;
|
||||||
|
|
||||||
const AppSettingsEnum(this.storeKey, this.hiveKey, this.defaultValue);
|
const AppSettingsEnum(this.storeKey, this.hiveKey, this.defaultValue);
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:ui';
|
import 'dart:ui';
|
||||||
|
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:immich_mobile/domain/services/log.service.dart';
|
import 'package:immich_mobile/domain/services/log.service.dart';
|
||||||
@ -59,9 +60,18 @@ Cancelable<T?> runInIsolateGentle<T>({
|
|||||||
stack,
|
stack,
|
||||||
);
|
);
|
||||||
} finally {
|
} finally {
|
||||||
await LogService.I.flushBuffer();
|
try {
|
||||||
ref.read(driftProvider).close();
|
await LogService.I.flushBuffer();
|
||||||
ref.read(isarProvider).close();
|
await ref.read(driftProvider).close();
|
||||||
|
await ref.read(isarProvider).close();
|
||||||
|
ref.dispose();
|
||||||
|
} catch (error) {
|
||||||
|
debugPrint("Error closing resources in isolate: $error");
|
||||||
|
} finally {
|
||||||
|
ref.dispose();
|
||||||
|
// Delay to ensure all resources are released
|
||||||
|
await Future.delayed(const Duration(seconds: 2));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
});
|
});
|
||||||
|
@ -4,6 +4,7 @@ import 'dart:io';
|
|||||||
|
|
||||||
import 'package:drift/drift.dart';
|
import 'package:drift/drift.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:immich_mobile/domain/models/store.model.dart';
|
import 'package:immich_mobile/domain/models/store.model.dart';
|
||||||
import 'package:immich_mobile/domain/utils/background_sync.dart';
|
import 'package:immich_mobile/domain/utils/background_sync.dart';
|
||||||
import 'package:immich_mobile/entities/album.entity.dart';
|
import 'package:immich_mobile/entities/album.entity.dart';
|
||||||
@ -18,12 +19,15 @@ import 'package:immich_mobile/infrastructure/entities/local_asset.entity.drift.d
|
|||||||
import 'package:immich_mobile/infrastructure/entities/store.entity.dart';
|
import 'package:immich_mobile/infrastructure/entities/store.entity.dart';
|
||||||
import 'package:immich_mobile/infrastructure/entities/user.entity.dart';
|
import 'package:immich_mobile/infrastructure/entities/user.entity.dart';
|
||||||
import 'package:immich_mobile/infrastructure/repositories/db.repository.dart';
|
import 'package:immich_mobile/infrastructure/repositories/db.repository.dart';
|
||||||
|
import 'package:immich_mobile/providers/background_sync.provider.dart';
|
||||||
|
import 'package:immich_mobile/providers/backup/backup.provider.dart';
|
||||||
import 'package:immich_mobile/utils/diff.dart';
|
import 'package:immich_mobile/utils/diff.dart';
|
||||||
import 'package:isar/isar.dart';
|
import 'package:isar/isar.dart';
|
||||||
|
import 'package:logging/logging.dart';
|
||||||
// ignore: import_rule_photo_manager
|
// ignore: import_rule_photo_manager
|
||||||
import 'package:photo_manager/photo_manager.dart';
|
import 'package:photo_manager/photo_manager.dart';
|
||||||
|
|
||||||
const int targetVersion = 13;
|
const int targetVersion = 14;
|
||||||
|
|
||||||
Future<void> migrateDatabaseIfNeeded(Isar db) async {
|
Future<void> migrateDatabaseIfNeeded(Isar db) async {
|
||||||
final int version = Store.get(StoreKey.version, targetVersion);
|
final int version = Store.get(StoreKey.version, targetVersion);
|
||||||
@ -48,18 +52,22 @@ Future<void> migrateDatabaseIfNeeded(Isar db) async {
|
|||||||
await _migrateDeviceAsset(db);
|
await _migrateDeviceAsset(db);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (version < 12 && (!kReleaseMode)) {
|
if (version < 13) {
|
||||||
|
await Store.put(StoreKey.photoManagerCustomFilter, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (version < 14) {
|
||||||
|
if (!Store.isBetaTimelineEnabled) {
|
||||||
|
// Try again when beta timeline is enabled and the app is restarted
|
||||||
|
return;
|
||||||
|
}
|
||||||
final backgroundSync = BackgroundSyncManager();
|
final backgroundSync = BackgroundSyncManager();
|
||||||
await backgroundSync.syncLocal();
|
await backgroundSync.syncLocal();
|
||||||
final drift = Drift();
|
final drift = Drift();
|
||||||
await _migrateDeviceAssetToSqlite(db, drift);
|
await migrateDeviceAssetToSqlite(db, drift);
|
||||||
await drift.close();
|
await drift.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (version < 13) {
|
|
||||||
await Store.put(StoreKey.photoManagerCustomFilter, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (targetVersion >= 12) {
|
if (targetVersion >= 12) {
|
||||||
await Store.put(StoreKey.version, targetVersion);
|
await Store.put(StoreKey.version, targetVersion);
|
||||||
return;
|
return;
|
||||||
@ -175,33 +183,24 @@ Future<void> _migrateDeviceAsset(Isar db) async {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _migrateDeviceAssetToSqlite(Isar db, Drift drift) async {
|
Future<void> migrateDeviceAssetToSqlite(Isar db, Drift drift) async {
|
||||||
try {
|
try {
|
||||||
final isarDeviceAssets =
|
final isarDeviceAssets = await db.deviceAssetEntitys.where().findAll();
|
||||||
await db.deviceAssetEntitys.where().sortByAssetId().findAll();
|
|
||||||
await drift.batch((batch) {
|
await drift.batch((batch) {
|
||||||
for (final deviceAsset in isarDeviceAssets) {
|
for (final deviceAsset in isarDeviceAssets) {
|
||||||
final companion = LocalAssetEntityCompanion(
|
batch.update(
|
||||||
updatedAt: Value(deviceAsset.modifiedTime),
|
|
||||||
id: Value(deviceAsset.assetId),
|
|
||||||
checksum: Value(base64.encode(deviceAsset.hash)),
|
|
||||||
);
|
|
||||||
batch.insert<$LocalAssetEntityTable, LocalAssetEntityData>(
|
|
||||||
drift.localAssetEntity,
|
drift.localAssetEntity,
|
||||||
companion,
|
LocalAssetEntityCompanion(
|
||||||
onConflict: DoUpdate(
|
checksum: Value(base64.encode(deviceAsset.hash)),
|
||||||
(_) => companion,
|
|
||||||
where: (old) => old.updatedAt.equals(deviceAsset.modifiedTime),
|
|
||||||
),
|
),
|
||||||
|
where: (t) => t.id.equals(deviceAsset.assetId),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (kDebugMode) {
|
debugPrint(
|
||||||
debugPrint(
|
"[MIGRATION] Error while migrating device assets to SQLite: $error",
|
||||||
"[MIGRATION] Error while migrating device assets to SQLite: $error",
|
);
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -212,3 +211,18 @@ class _DeviceAsset {
|
|||||||
|
|
||||||
const _DeviceAsset({required this.assetId, this.hash, this.dateTime});
|
const _DeviceAsset({required this.assetId, this.hash, this.dateTime});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> runNewSync(WidgetRef ref, {bool full = false}) async {
|
||||||
|
ref.read(backupProvider.notifier).cancelBackup();
|
||||||
|
|
||||||
|
final backgroundManager = ref.read(backgroundSyncProvider);
|
||||||
|
Future.wait([
|
||||||
|
backgroundManager.syncLocal(full: full).then(
|
||||||
|
(_) {
|
||||||
|
Logger("runNewSync").fine("Hashing assets after syncLocal");
|
||||||
|
backgroundManager.hashAssets();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
backgroundManager.syncRemote(),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
@ -6,7 +6,6 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
|
|||||||
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
||||||
import 'package:immich_mobile/models/backup/backup_state.model.dart';
|
import 'package:immich_mobile/models/backup/backup_state.model.dart';
|
||||||
import 'package:immich_mobile/models/server_info/server_info.model.dart';
|
import 'package:immich_mobile/models/server_info/server_info.model.dart';
|
||||||
import 'package:immich_mobile/providers/background_sync.provider.dart';
|
|
||||||
import 'package:immich_mobile/providers/backup/backup.provider.dart';
|
import 'package:immich_mobile/providers/backup/backup.provider.dart';
|
||||||
import 'package:immich_mobile/providers/cast.provider.dart';
|
import 'package:immich_mobile/providers/cast.provider.dart';
|
||||||
import 'package:immich_mobile/providers/server_info.provider.dart';
|
import 'package:immich_mobile/providers/server_info.provider.dart';
|
||||||
@ -51,7 +50,6 @@ class ImmichSliverAppBar extends ConsumerWidget {
|
|||||||
pinned: pinned,
|
pinned: pinned,
|
||||||
snap: snap,
|
snap: snap,
|
||||||
expandedHeight: expandedHeight,
|
expandedHeight: expandedHeight,
|
||||||
backgroundColor: context.colorScheme.surfaceContainer,
|
|
||||||
shape: const RoundedRectangleBorder(
|
shape: const RoundedRectangleBorder(
|
||||||
borderRadius: BorderRadius.all(
|
borderRadius: BorderRadius.all(
|
||||||
Radius.circular(5),
|
Radius.circular(5),
|
||||||
@ -68,24 +66,6 @@ class ImmichSliverAppBar extends ConsumerWidget {
|
|||||||
child: action,
|
child: action,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
IconButton(
|
|
||||||
icon: const Icon(Icons.swipe_left_alt_rounded),
|
|
||||||
onPressed: () => context.pop(),
|
|
||||||
),
|
|
||||||
IconButton(
|
|
||||||
onPressed: () {
|
|
||||||
ref.read(backgroundSyncProvider).syncLocal(full: true);
|
|
||||||
ref.read(backgroundSyncProvider).syncRemote();
|
|
||||||
|
|
||||||
Future.delayed(
|
|
||||||
const Duration(seconds: 10),
|
|
||||||
() => ref.read(backgroundSyncProvider).hashAssets(),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
icon: const Icon(
|
|
||||||
Icons.sync,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
if (isCasting)
|
if (isCasting)
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.only(right: 12),
|
padding: const EdgeInsets.only(right: 12),
|
||||||
@ -127,13 +107,30 @@ class _ImmichLogoWithText extends StatelessWidget {
|
|||||||
children: [
|
children: [
|
||||||
Builder(
|
Builder(
|
||||||
builder: (context) {
|
builder: (context) {
|
||||||
return Padding(
|
return Badge(
|
||||||
padding: const EdgeInsets.only(top: 3.0),
|
padding:
|
||||||
child: SvgPicture.asset(
|
const EdgeInsets.symmetric(horizontal: 6, vertical: 4),
|
||||||
context.isDarkTheme
|
backgroundColor: context.primaryColor,
|
||||||
? 'assets/immich-logo-inline-dark.svg'
|
alignment: Alignment.centerRight,
|
||||||
: 'assets/immich-logo-inline-light.svg',
|
offset: const Offset(16, -8),
|
||||||
height: 40,
|
label: Text(
|
||||||
|
'β',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 11,
|
||||||
|
color: context.colorScheme.onPrimary,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
fontFamily: 'OverpassMono',
|
||||||
|
height: 1.2,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.only(top: 3.0),
|
||||||
|
child: SvgPicture.asset(
|
||||||
|
context.isDarkTheme
|
||||||
|
? 'assets/immich-logo-inline-dark.svg'
|
||||||
|
: 'assets/immich-logo-inline-light.svg',
|
||||||
|
height: 40,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
@ -10,6 +10,7 @@ import 'package:flutter/services.dart';
|
|||||||
import 'package:flutter_hooks/flutter_hooks.dart' hide Store;
|
import 'package:flutter_hooks/flutter_hooks.dart' hide Store;
|
||||||
import 'package:fluttertoast/fluttertoast.dart';
|
import 'package:fluttertoast/fluttertoast.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
|
import 'package:immich_mobile/entities/store.entity.dart';
|
||||||
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
||||||
import 'package:immich_mobile/providers/auth.provider.dart';
|
import 'package:immich_mobile/providers/auth.provider.dart';
|
||||||
import 'package:immich_mobile/providers/backup/backup.provider.dart';
|
import 'package:immich_mobile/providers/backup/backup.provider.dart';
|
||||||
@ -17,6 +18,7 @@ import 'package:immich_mobile/providers/gallery_permission.provider.dart';
|
|||||||
import 'package:immich_mobile/providers/oauth.provider.dart';
|
import 'package:immich_mobile/providers/oauth.provider.dart';
|
||||||
import 'package:immich_mobile/providers/server_info.provider.dart';
|
import 'package:immich_mobile/providers/server_info.provider.dart';
|
||||||
import 'package:immich_mobile/routing/router.dart';
|
import 'package:immich_mobile/routing/router.dart';
|
||||||
|
import 'package:immich_mobile/utils/migration.dart';
|
||||||
import 'package:immich_mobile/utils/provider_utils.dart';
|
import 'package:immich_mobile/utils/provider_utils.dart';
|
||||||
import 'package:immich_mobile/utils/url_helper.dart';
|
import 'package:immich_mobile/utils/url_helper.dart';
|
||||||
import 'package:immich_mobile/utils/version_compatibility.dart';
|
import 'package:immich_mobile/utils/version_compatibility.dart';
|
||||||
@ -192,6 +194,15 @@ class LoginForm extends HookConsumerWidget {
|
|||||||
if (result.shouldChangePassword && !result.isAdmin) {
|
if (result.shouldChangePassword && !result.isAdmin) {
|
||||||
context.pushRoute(const ChangePasswordRoute());
|
context.pushRoute(const ChangePasswordRoute());
|
||||||
} else {
|
} else {
|
||||||
|
final isBeta = Store.isBetaTimelineEnabled;
|
||||||
|
if (isBeta) {
|
||||||
|
await ref
|
||||||
|
.read(galleryPermissionNotifier.notifier)
|
||||||
|
.requestGalleryPermission();
|
||||||
|
await runNewSync(ref);
|
||||||
|
context.replaceRoute(const TabShellRoute());
|
||||||
|
return;
|
||||||
|
}
|
||||||
context.replaceRoute(const TabControllerRoute());
|
context.replaceRoute(const TabControllerRoute());
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@ -292,9 +303,18 @@ class LoginForm extends HookConsumerWidget {
|
|||||||
if (isSuccess) {
|
if (isSuccess) {
|
||||||
isLoading.value = false;
|
isLoading.value = false;
|
||||||
final permission = ref.watch(galleryPermissionNotifier);
|
final permission = ref.watch(galleryPermissionNotifier);
|
||||||
if (permission.isGranted || permission.isLimited) {
|
final isBeta = Store.isBetaTimelineEnabled;
|
||||||
|
if (!isBeta && (permission.isGranted || permission.isLimited)) {
|
||||||
ref.watch(backupProvider.notifier).resumeBackup();
|
ref.watch(backupProvider.notifier).resumeBackup();
|
||||||
}
|
}
|
||||||
|
if (isBeta) {
|
||||||
|
await ref
|
||||||
|
.read(galleryPermissionNotifier.notifier)
|
||||||
|
.requestGalleryPermission();
|
||||||
|
await runNewSync(ref);
|
||||||
|
context.replaceRoute(const TabShellRoute());
|
||||||
|
return;
|
||||||
|
}
|
||||||
context.replaceRoute(const TabControllerRoute());
|
context.replaceRoute(const TabControllerRoute());
|
||||||
}
|
}
|
||||||
} catch (error, stack) {
|
} catch (error, stack) {
|
||||||
|
269
mobile/lib/widgets/settings/beta_timeline_list_tile.dart
Normal file
269
mobile/lib/widgets/settings/beta_timeline_list_tile.dart
Normal file
@ -0,0 +1,269 @@
|
|||||||
|
import 'dart:math' as math;
|
||||||
|
|
||||||
|
import 'package:auto_route/auto_route.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
|
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
||||||
|
import 'package:immich_mobile/extensions/translate_extensions.dart';
|
||||||
|
import 'package:immich_mobile/providers/app_settings.provider.dart';
|
||||||
|
import 'package:immich_mobile/routing/router.dart';
|
||||||
|
import 'package:immich_mobile/services/app_settings.service.dart';
|
||||||
|
|
||||||
|
class BetaTimelineListTile extends ConsumerStatefulWidget {
|
||||||
|
const BetaTimelineListTile({
|
||||||
|
super.key,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
ConsumerState<BetaTimelineListTile> createState() =>
|
||||||
|
_BetaTimelineListTileState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _BetaTimelineListTileState extends ConsumerState<BetaTimelineListTile>
|
||||||
|
with SingleTickerProviderStateMixin {
|
||||||
|
late AnimationController _animationController;
|
||||||
|
late Animation<double> _rotationAnimation;
|
||||||
|
late Animation<double> _pulseAnimation;
|
||||||
|
late Animation<double> _gradientAnimation;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_animationController = AnimationController(
|
||||||
|
duration: const Duration(seconds: 3),
|
||||||
|
vsync: this,
|
||||||
|
);
|
||||||
|
|
||||||
|
_rotationAnimation = Tween<double>(begin: 0, end: 2 * math.pi).animate(
|
||||||
|
CurvedAnimation(
|
||||||
|
parent: _animationController,
|
||||||
|
curve: Curves.linear,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
_pulseAnimation = Tween<double>(begin: 1, end: 1.1).animate(
|
||||||
|
CurvedAnimation(
|
||||||
|
parent: _animationController,
|
||||||
|
curve: Curves.easeInOut,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
_gradientAnimation = Tween<double>(begin: 0, end: 1).animate(
|
||||||
|
CurvedAnimation(
|
||||||
|
parent: _animationController,
|
||||||
|
curve: Curves.easeInOut,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
_animationController.repeat(reverse: true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_animationController.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final betaTimelineValue = ref
|
||||||
|
.watch(appSettingsServiceProvider)
|
||||||
|
.getSetting<bool>(AppSettingsEnum.betaTimeline);
|
||||||
|
|
||||||
|
return AnimatedBuilder(
|
||||||
|
animation: _animationController,
|
||||||
|
builder: (context, child) {
|
||||||
|
void onSwitchChanged(bool value) {
|
||||||
|
showDialog(
|
||||||
|
context: context,
|
||||||
|
builder: (context) {
|
||||||
|
return AlertDialog(
|
||||||
|
title: value
|
||||||
|
? const Text("Enable Beta Timeline")
|
||||||
|
: const Text("Disable Beta Timeline"),
|
||||||
|
content: value
|
||||||
|
? const Text(
|
||||||
|
"Are you sure you want to enable the beta timeline?",
|
||||||
|
)
|
||||||
|
: const Text(
|
||||||
|
"Are you sure you want to disable the beta timeline?",
|
||||||
|
),
|
||||||
|
actions: [
|
||||||
|
TextButton(
|
||||||
|
onPressed: () async {
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
await ref.read(appSettingsServiceProvider).setSetting(
|
||||||
|
AppSettingsEnum.betaTimeline,
|
||||||
|
value,
|
||||||
|
);
|
||||||
|
context.router.replaceAll(
|
||||||
|
[ChangeExperienceRoute(switchingToBeta: value)],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
child: const Text("Yes"),
|
||||||
|
),
|
||||||
|
TextButton(
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
},
|
||||||
|
child: const Text("No"),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
final gradientColors = [
|
||||||
|
Color.lerp(
|
||||||
|
context.primaryColor.withValues(alpha: 0.3),
|
||||||
|
context.primaryColor.withValues(alpha: 0.1),
|
||||||
|
_gradientAnimation.value,
|
||||||
|
)!,
|
||||||
|
Color.lerp(
|
||||||
|
context.primaryColor.withValues(alpha: 0.2),
|
||||||
|
context.primaryColor.withValues(alpha: 0.4),
|
||||||
|
_gradientAnimation.value,
|
||||||
|
)!,
|
||||||
|
Color.lerp(
|
||||||
|
context.primaryColor.withValues(alpha: 0.1),
|
||||||
|
context.primaryColor.withValues(alpha: 0.3),
|
||||||
|
_gradientAnimation.value,
|
||||||
|
)!,
|
||||||
|
];
|
||||||
|
|
||||||
|
return Container(
|
||||||
|
margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 4),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
borderRadius: const BorderRadius.all(Radius.circular(12)),
|
||||||
|
gradient: LinearGradient(
|
||||||
|
colors: gradientColors,
|
||||||
|
stops: const [0.0, 0.5, 1.0],
|
||||||
|
begin: Alignment.topLeft,
|
||||||
|
end: Alignment.bottomRight,
|
||||||
|
transform: GradientRotation(_rotationAnimation.value * 0.1),
|
||||||
|
),
|
||||||
|
boxShadow: [
|
||||||
|
BoxShadow(
|
||||||
|
color: context.primaryColor.withValues(alpha: 0.1),
|
||||||
|
blurRadius: 8,
|
||||||
|
offset: const Offset(0, 2),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
child: Container(
|
||||||
|
margin: const EdgeInsets.all(1.5),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
borderRadius: const BorderRadius.all(Radius.circular(10.5)),
|
||||||
|
color: context.scaffoldBackgroundColor,
|
||||||
|
),
|
||||||
|
child: Material(
|
||||||
|
color: Colors.transparent,
|
||||||
|
borderRadius: const BorderRadius.all(Radius.circular(10.5)),
|
||||||
|
child: InkWell(
|
||||||
|
borderRadius: const BorderRadius.all(Radius.circular(10.5)),
|
||||||
|
onTap: () => onSwitchChanged(!betaTimelineValue),
|
||||||
|
child: Padding(
|
||||||
|
padding:
|
||||||
|
const EdgeInsets.symmetric(horizontal: 20, vertical: 16),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
Transform.scale(
|
||||||
|
scale: _pulseAnimation.value,
|
||||||
|
child: Transform.rotate(
|
||||||
|
angle: _rotationAnimation.value * 0.02,
|
||||||
|
child: Container(
|
||||||
|
padding: const EdgeInsets.all(8),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
shape: BoxShape.circle,
|
||||||
|
gradient: LinearGradient(
|
||||||
|
colors: [
|
||||||
|
context.primaryColor.withValues(alpha: 0.2),
|
||||||
|
context.primaryColor.withValues(alpha: 0.1),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: Icon(
|
||||||
|
Icons.auto_awesome,
|
||||||
|
color: context.primaryColor,
|
||||||
|
size: 20,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 16),
|
||||||
|
Expanded(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
"advanced_settings_beta_timeline_title"
|
||||||
|
.t(context: context),
|
||||||
|
style:
|
||||||
|
context.textTheme.titleMedium?.copyWith(
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 8),
|
||||||
|
Container(
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 6,
|
||||||
|
vertical: 2,
|
||||||
|
),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
borderRadius: const BorderRadius.all(
|
||||||
|
Radius.circular(8),
|
||||||
|
),
|
||||||
|
gradient: LinearGradient(
|
||||||
|
colors: [
|
||||||
|
context.primaryColor
|
||||||
|
.withValues(alpha: 0.8),
|
||||||
|
context.primaryColor
|
||||||
|
.withValues(alpha: 0.6),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: Text(
|
||||||
|
'NEW',
|
||||||
|
style:
|
||||||
|
context.textTheme.labelSmall?.copyWith(
|
||||||
|
color: Colors.white,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
fontSize: 10,
|
||||||
|
height: 1.2,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(height: 4),
|
||||||
|
Text(
|
||||||
|
"advanced_settings_beta_timeline_subtitle"
|
||||||
|
.t(context: context),
|
||||||
|
style: context.textTheme.labelLarge?.copyWith(
|
||||||
|
color: context.textTheme.labelLarge?.color
|
||||||
|
?.withValues(alpha: 0.7),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Switch.adaptive(
|
||||||
|
value: betaTimelineValue,
|
||||||
|
onChanged: onSwitchChanged,
|
||||||
|
activeColor: context.primaryColor,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -139,7 +139,6 @@ flutter:
|
|||||||
- family: OverpassMono
|
- family: OverpassMono
|
||||||
fonts:
|
fonts:
|
||||||
- asset: fonts/overpass/OverpassMono.ttf
|
- asset: fonts/overpass/OverpassMono.ttf
|
||||||
|
|
||||||
flutter_launcher_icons:
|
flutter_launcher_icons:
|
||||||
image_path_android: 'assets/immich-logo.png'
|
image_path_android: 'assets/immich-logo.png'
|
||||||
adaptive_icon_background: '#ffffff'
|
adaptive_icon_background: '#ffffff'
|
||||||
|
23
mobile/test/drift/main/generated/schema.dart
Normal file
23
mobile/test/drift/main/generated/schema.dart
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
// dart format width=80
|
||||||
|
// GENERATED CODE, DO NOT EDIT BY HAND.
|
||||||
|
// ignore_for_file: type=lint
|
||||||
|
import 'package:drift/drift.dart';
|
||||||
|
import 'package:drift/internal/migrations.dart';
|
||||||
|
import 'schema_v1.dart' as v1;
|
||||||
|
import 'schema_v2.dart' as v2;
|
||||||
|
|
||||||
|
class GeneratedHelper implements SchemaInstantiationHelper {
|
||||||
|
@override
|
||||||
|
GeneratedDatabase databaseForVersion(QueryExecutor db, int version) {
|
||||||
|
switch (version) {
|
||||||
|
case 1:
|
||||||
|
return v1.DatabaseAtV1(db);
|
||||||
|
case 2:
|
||||||
|
return v2.DatabaseAtV2(db);
|
||||||
|
default:
|
||||||
|
throw MissingSchemaException(version, versions);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static const versions = const [1, 2];
|
||||||
|
}
|
4638
mobile/test/drift/main/generated/schema_v1.dart
Normal file
4638
mobile/test/drift/main/generated/schema_v1.dart
Normal file
File diff suppressed because it is too large
Load Diff
4672
mobile/test/drift/main/generated/schema_v2.dart
Normal file
4672
mobile/test/drift/main/generated/schema_v2.dart
Normal file
File diff suppressed because it is too large
Load Diff
38
mobile/test/drift/main/migration_test.dart
Normal file
38
mobile/test/drift/main/migration_test.dart
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
// dart format width=80
|
||||||
|
// ignore_for_file: unused_local_variable, unused_import
|
||||||
|
import 'package:drift/drift.dart';
|
||||||
|
import 'package:drift_dev/api/migrations_native.dart';
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
import 'package:immich_mobile/infrastructure/repositories/db.repository.dart';
|
||||||
|
|
||||||
|
import 'generated/schema.dart';
|
||||||
|
import 'generated/schema_v1.dart' as v1;
|
||||||
|
import 'generated/schema_v2.dart' as v2;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
driftRuntimeOptions.dontWarnAboutMultipleDatabases = true;
|
||||||
|
late SchemaVerifier verifier;
|
||||||
|
|
||||||
|
setUpAll(() {
|
||||||
|
verifier = SchemaVerifier(GeneratedHelper());
|
||||||
|
});
|
||||||
|
|
||||||
|
group('simple database migrations', () {
|
||||||
|
// These simple tests verify all possible schema updates with a simple (no
|
||||||
|
// data) migration. This is a quick way to ensure that written database
|
||||||
|
// migrations properly alter the schema.
|
||||||
|
const versions = GeneratedHelper.versions;
|
||||||
|
for (final (i, fromVersion) in versions.indexed) {
|
||||||
|
group('from $fromVersion', () {
|
||||||
|
for (final toVersion in versions.skip(i + 1)) {
|
||||||
|
test('to $toVersion', () async {
|
||||||
|
final schema = await verifier.schemaAt(fromVersion);
|
||||||
|
final db = Drift(schema.newConnection());
|
||||||
|
await verifier.migrateAndValidate(db, toVersion);
|
||||||
|
await db.close();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user