Merge branch 'main' into fix/mobile-task-cleanup

This commit is contained in:
Alex
2026-05-30 10:32:59 -05:00
committed by GitHub
117 changed files with 14161 additions and 3130 deletions
-4
View File
@@ -72,10 +72,6 @@ jobs:
run: flutter pub get
working-directory: ./mobile/packages/ui
- name: Install dependencies for UI Showcase
run: flutter pub get
working-directory: ./mobile/packages/ui/showcase
- name: Generate translation files
run: mise //mobile:codegen:translation
+18
View File
@@ -109,6 +109,24 @@ mise //mobile:translation
The mobile app asks you what backend to connect to. You can utilize the demo backend (https://demo.immich.app/) if you don't need to change server code or upload photos. Alternatively, you can run the server yourself per the instructions above.
#### UI components and widget previews
Shared design-system widgets (buttons, inputs, forms) live in the
[`immich_ui` package](https://github.com/immich-app/immich/tree/main/mobile/packages/ui/)
under `mobile/packages/ui/`. Components are defined in `lib/src/components/`
and have matching previews in `lib/src/previews/`.
To inspect a component in isolation with a light/dark toggle and hot reload,
launch [Flutter's Widget Previewer](https://docs.flutter.dev/tools/widget-previewer):
```bash
cd mobile/packages/ui
flutter widget-preview start
```
In VS Code or Android Studio with the Flutter plugin, the previewer
auto-starts when you open the **Flutter Widget Preview** tab in the sidebar.
## IDE setup
### Lint / format extensions
File diff suppressed because it is too large Load Diff
@@ -12,7 +12,7 @@ import 'package:immich_mobile/domain/models/config/theme_config.dart';
import 'package:immich_mobile/domain/models/config/timeline_config.dart';
import 'package:immich_mobile/domain/models/config/viewer_config.dart';
import 'package:immich_mobile/domain/models/log.model.dart';
import 'package:immich_mobile/domain/models/metadata_key.dart';
import 'package:immich_mobile/domain/models/settings_key.dart';
import 'package:immich_mobile/domain/models/timeline.model.dart';
import 'package:immich_mobile/providers/album/album_sort_by_options.provider.dart';
@@ -95,7 +95,7 @@ class AppConfig {
String toString() =>
'AppConfig(logLevel: $logLevel, theme: $theme, cleanup: $cleanup, map: $map, timeline: $timeline, image: $image, viewer: $viewer, slideshow: $slideshow, album: $album, backup: $backup, network: $network)';
T read<T extends Object>(MetadataKey<T> key) =>
T read<T extends Object>(SettingsKey<T> key) =>
(switch (key) {
.logLevel => logLevel,
.themePrimaryColor => theme.primaryColor,
@@ -143,10 +143,10 @@ class AppConfig {
})
as T;
factory AppConfig.fromEntries(Map<MetadataKey<Object>, Object> overrides) =>
factory AppConfig.fromEntries(Map<SettingsKey<Object>, Object> overrides) =>
overrides.entries.fold(const AppConfig(), (config, entry) => config.write(entry.key, entry.value));
AppConfig write<T extends Object>(MetadataKey<T> key, T value) {
AppConfig write<T extends Object>(SettingsKey<T> key, T value) {
return switch (key) {
.logLevel => copyWith(logLevel: value as LogLevel),
.themePrimaryColor => copyWith(theme: theme.copyWith(primaryColor: value as ImmichColorPreset)),
@@ -7,7 +7,7 @@ import 'package:immich_mobile/domain/models/log.model.dart';
import 'package:immich_mobile/domain/models/timeline.model.dart';
import 'package:immich_mobile/providers/album/album_sort_by_options.provider.dart';
enum MetadataKey<T extends Object> {
enum SettingsKey<T extends Object> {
// Theme
themePrimaryColor<ImmichColorPreset>(codec: _EnumCodec(ImmichColorPreset.values)),
themeMode<ThemeMode>(codec: _EnumCodec(ThemeMode.values)),
@@ -73,24 +73,24 @@ enum MetadataKey<T extends Object> {
slideshowLook<SlideshowLook>(codec: _EnumCodec(SlideshowLook.values)),
slideshowDirection<SlideshowDirection>(codec: _EnumCodec(SlideshowDirection.values));
final _MetadataCodec<T>? _codecOverride;
final _SettingsCodec<T>? _codecOverride;
const MetadataKey({_MetadataCodec<T>? codec}) : _codecOverride = codec;
const SettingsKey({_SettingsCodec<T>? codec}) : _codecOverride = codec;
_MetadataCodec<T> get _codec => _codecOverride ?? _MetadataCodec.forType(T);
_SettingsCodec<T> get _codec => _codecOverride ?? _SettingsCodec.forType(T);
String encode(T value) => _codec.encode(value);
T decode(String raw) => _codec.decode(raw);
}
sealed class _MetadataCodec<T extends Object> {
const _MetadataCodec();
sealed class _SettingsCodec<T extends Object> {
const _SettingsCodec();
String encode(T value);
T decode(String raw);
static const Map<Type, _MetadataCodec<Object>> _primitives = {
static const Map<Type, _SettingsCodec<Object>> _primitives = {
int: _PrimitiveCodec.integer,
double: _PrimitiveCodec.real,
bool: _PrimitiveCodec.boolean,
@@ -98,16 +98,16 @@ sealed class _MetadataCodec<T extends Object> {
DateTime: _DateTimeCodec(),
};
static _MetadataCodec<T> forType<T extends Object>(Type runtimeType) {
static _SettingsCodec<T> forType<T extends Object>(Type runtimeType) {
final codec = _primitives[runtimeType];
if (codec == null) {
throw StateError('No primitive codec for $runtimeType. Provide an explicit codec when defining the MetadataKey.');
throw StateError('No primitive codec for $runtimeType. Provide an explicit codec when defining the SettingsKey.');
}
return codec as _MetadataCodec<T>;
return codec as _SettingsCodec<T>;
}
}
final class _EnumCodec<T extends Enum> extends _MetadataCodec<T> {
final class _EnumCodec<T extends Enum> extends _SettingsCodec<T> {
final List<T> values;
const _EnumCodec(this.values);
@@ -119,7 +119,7 @@ final class _EnumCodec<T extends Enum> extends _MetadataCodec<T> {
T decode(String raw) => values.firstWhere((v) => v.name == raw);
}
final class _DateTimeCodec extends _MetadataCodec<DateTime> {
final class _DateTimeCodec extends _SettingsCodec<DateTime> {
const _DateTimeCodec();
@override
@@ -129,9 +129,9 @@ final class _DateTimeCodec extends _MetadataCodec<DateTime> {
DateTime decode(String raw) => DateTime.parse(raw);
}
final class _MapCodec<K extends Object, V extends Object> extends _MetadataCodec<Map<K, V>> {
final _MetadataCodec<K> _keyCodec;
final _MetadataCodec<V> _valueCodec;
final class _MapCodec<K extends Object, V extends Object> extends _SettingsCodec<Map<K, V>> {
final _SettingsCodec<K> _keyCodec;
final _SettingsCodec<V> _valueCodec;
const _MapCodec(this._keyCodec, this._valueCodec);
@@ -167,8 +167,8 @@ final class _MapCodec<K extends Object, V extends Object> extends _MetadataCodec
}
}
final class _ListCodec<T extends Object> extends _MetadataCodec<List<T>> {
final _MetadataCodec<T> _elementCodec;
final class _ListCodec<T extends Object> extends _SettingsCodec<List<T>> {
final _SettingsCodec<T> _elementCodec;
const _ListCodec(this._elementCodec);
@@ -197,7 +197,7 @@ final class _ListCodec<T extends Object> extends _MetadataCodec<List<T>> {
}
}
final class _PrimitiveCodec<T extends Object> extends _MetadataCodec<T> {
final class _PrimitiveCodec<T extends Object> extends _SettingsCodec<T> {
final T Function(String) _parse;
const _PrimitiveCodec._(this._parse);
@@ -11,7 +11,7 @@ import 'package:immich_mobile/entities/store.entity.dart';
import 'package:immich_mobile/extensions/platform_extensions.dart';
import 'package:immich_mobile/infrastructure/repositories/db.repository.dart';
import 'package:immich_mobile/infrastructure/repositories/logger_db.repository.dart';
import 'package:immich_mobile/infrastructure/repositories/metadata.repository.dart';
import 'package:immich_mobile/infrastructure/repositories/settings.repository.dart';
import 'package:immich_mobile/platform/background_worker_api.g.dart';
import 'package:immich_mobile/platform/background_worker_lock_api.g.dart';
import 'package:immich_mobile/providers/background_sync.provider.dart';
@@ -39,7 +39,7 @@ class BackgroundWorkerFgService {
_foregroundHostApi.saveNotificationMessage(title, body);
Future<void> configure({int? minimumDelaySeconds, bool? requireCharging}) {
final backup = MetadataRepository.instance.appConfig.backup;
final backup = SettingsRepository.instance.appConfig.backup;
return _foregroundHostApi.configure(
BackgroundWorkerSettings(
minimumDelaySeconds: minimumDelaySeconds ?? backup.triggerDelay,
@@ -67,7 +67,7 @@ class BackgroundWorkerBgService extends BackgroundWorkerFlutterApi {
BackgroundWorkerFlutterApi.setUp(this);
}
bool get _isBackupEnabled => MetadataRepository.instance.appConfig.backup.enabled;
bool get _isBackupEnabled => SettingsRepository.instance.appConfig.backup.enabled;
Future<void> init() async {
try {
+11 -11
View File
@@ -2,9 +2,9 @@ import 'dart:async';
import 'package:immich_mobile/constants/constants.dart';
import 'package:immich_mobile/domain/models/log.model.dart';
import 'package:immich_mobile/domain/models/metadata_key.dart';
import 'package:immich_mobile/domain/models/settings_key.dart';
import 'package:immich_mobile/infrastructure/repositories/log.repository.dart';
import 'package:immich_mobile/infrastructure/repositories/metadata.repository.dart';
import 'package:immich_mobile/infrastructure/repositories/settings.repository.dart';
import 'package:immich_mobile/utils/debug_print.dart';
import 'package:logging/logging.dart';
@@ -12,10 +12,10 @@ import 'package:logging/logging.dart';
///
/// It listens to Dart's [Logger.root], buffers logs in memory (optionally),
/// writes them to a persistent [LogRepository], and manages log levels via
/// [MetadataRepository].
/// [SettingsRepository].
class LogService {
final LogRepository _logRepository;
final MetadataRepository _metadataRepository;
final SettingsRepository _settingsRepository;
final List<LogMessage> _msgBuffer = [];
@@ -38,12 +38,12 @@ class LogService {
static Future<LogService> init({
required LogRepository logRepository,
required MetadataRepository metadataRepository,
required SettingsRepository settingsRepository,
bool shouldBuffer = true,
}) async {
_instance ??= await create(
logRepository: logRepository,
metadataRepository: metadataRepository,
settingsRepository: settingsRepository,
shouldBuffer: shouldBuffer,
);
return _instance!;
@@ -51,17 +51,17 @@ class LogService {
static Future<LogService> create({
required LogRepository logRepository,
required MetadataRepository metadataRepository,
required SettingsRepository settingsRepository,
bool shouldBuffer = true,
}) async {
final instance = LogService._(logRepository, metadataRepository, shouldBuffer);
final instance = LogService._(logRepository, settingsRepository, shouldBuffer);
await logRepository.truncate(limit: kLogTruncateLimit);
final level = instance._metadataRepository.appConfig.logLevel;
final level = instance._settingsRepository.appConfig.logLevel;
Logger.root.level = Level.LEVELS.elementAtOrNull(level.index) ?? Level.INFO;
return instance;
}
LogService._(this._logRepository, this._metadataRepository, this._shouldBuffer) {
LogService._(this._logRepository, this._settingsRepository, this._shouldBuffer) {
_logSubscription = Logger.root.onRecord.listen(_handleLogRecord);
}
@@ -91,7 +91,7 @@ class LogService {
}
Future<void> setLogLevel(LogLevel level) async {
await _metadataRepository.write(MetadataKey.logLevel, level);
await _settingsRepository.write(SettingsKey.logLevel, level);
Logger.root.level = level.toLevel();
}
@@ -7,7 +7,7 @@ import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
import 'package:immich_mobile/domain/models/events.model.dart';
import 'package:immich_mobile/domain/models/timeline.model.dart';
import 'package:immich_mobile/domain/utils/event_stream.dart';
import 'package:immich_mobile/infrastructure/repositories/metadata.repository.dart';
import 'package:immich_mobile/infrastructure/repositories/settings.repository.dart';
import 'package:immich_mobile/infrastructure/repositories/timeline.repository.dart';
import 'package:immich_mobile/utils/async_mutex.dart';
@@ -39,12 +39,12 @@ enum TimelineOrigin {
class TimelineFactory {
final DriftTimelineRepository _timelineRepository;
final MetadataRepository _metadataRepository;
final SettingsRepository _settingsRepository;
const TimelineFactory({required this._timelineRepository, required this._metadataRepository});
const TimelineFactory({required this._timelineRepository, required this._settingsRepository});
GroupAssetsBy get groupBy {
final group = _metadataRepository.appConfig.timeline.groupAssetsBy;
final group = _settingsRepository.appConfig.timeline.groupAssetsBy;
// We do not support auto grouping in the new timeline yet, fallback to day grouping
return group == GroupAssetsBy.auto ? GroupAssetsBy.day : group;
}
@@ -1,8 +1,8 @@
import 'package:drift/drift.dart';
import 'package:immich_mobile/infrastructure/utils/drift_default.mixin.dart';
class MetadataEntity extends Table with DriftDefaultsMixin {
const MetadataEntity();
class SettingsEntity extends Table with DriftDefaultsMixin {
const SettingsEntity();
TextColumn get key => text()();
@@ -14,5 +14,5 @@ class MetadataEntity extends Table with DriftDefaultsMixin {
Set<Column> get primaryKey => {key};
@override
String get tableName => "metadata";
String get tableName => "settings";
}
@@ -1,28 +1,28 @@
// dart format width=80
// ignore_for_file: type=lint
import 'package:drift/drift.dart' as i0;
import 'package:immich_mobile/infrastructure/entities/metadata.entity.drift.dart'
import 'package:immich_mobile/infrastructure/entities/settings.entity.drift.dart'
as i1;
import 'package:immich_mobile/infrastructure/entities/metadata.entity.dart'
import 'package:immich_mobile/infrastructure/entities/settings.entity.dart'
as i2;
import 'package:drift/src/runtime/query_builder/query_builder.dart' as i3;
typedef $$MetadataEntityTableCreateCompanionBuilder =
i1.MetadataEntityCompanion Function({
typedef $$SettingsEntityTableCreateCompanionBuilder =
i1.SettingsEntityCompanion Function({
required String key,
required String value,
i0.Value<DateTime> updatedAt,
});
typedef $$MetadataEntityTableUpdateCompanionBuilder =
i1.MetadataEntityCompanion Function({
typedef $$SettingsEntityTableUpdateCompanionBuilder =
i1.SettingsEntityCompanion Function({
i0.Value<String> key,
i0.Value<String> value,
i0.Value<DateTime> updatedAt,
});
class $$MetadataEntityTableFilterComposer
extends i0.Composer<i0.GeneratedDatabase, i1.$MetadataEntityTable> {
$$MetadataEntityTableFilterComposer({
class $$SettingsEntityTableFilterComposer
extends i0.Composer<i0.GeneratedDatabase, i1.$SettingsEntityTable> {
$$SettingsEntityTableFilterComposer({
required super.$db,
required super.$table,
super.joinBuilder,
@@ -45,9 +45,9 @@ class $$MetadataEntityTableFilterComposer
);
}
class $$MetadataEntityTableOrderingComposer
extends i0.Composer<i0.GeneratedDatabase, i1.$MetadataEntityTable> {
$$MetadataEntityTableOrderingComposer({
class $$SettingsEntityTableOrderingComposer
extends i0.Composer<i0.GeneratedDatabase, i1.$SettingsEntityTable> {
$$SettingsEntityTableOrderingComposer({
required super.$db,
required super.$table,
super.joinBuilder,
@@ -70,9 +70,9 @@ class $$MetadataEntityTableOrderingComposer
);
}
class $$MetadataEntityTableAnnotationComposer
extends i0.Composer<i0.GeneratedDatabase, i1.$MetadataEntityTable> {
$$MetadataEntityTableAnnotationComposer({
class $$SettingsEntityTableAnnotationComposer
extends i0.Composer<i0.GeneratedDatabase, i1.$SettingsEntityTable> {
$$SettingsEntityTableAnnotationComposer({
required super.$db,
required super.$table,
super.joinBuilder,
@@ -89,47 +89,47 @@ class $$MetadataEntityTableAnnotationComposer
$composableBuilder(column: $table.updatedAt, builder: (column) => column);
}
class $$MetadataEntityTableTableManager
class $$SettingsEntityTableTableManager
extends
i0.RootTableManager<
i0.GeneratedDatabase,
i1.$MetadataEntityTable,
i1.MetadataEntityData,
i1.$$MetadataEntityTableFilterComposer,
i1.$$MetadataEntityTableOrderingComposer,
i1.$$MetadataEntityTableAnnotationComposer,
$$MetadataEntityTableCreateCompanionBuilder,
$$MetadataEntityTableUpdateCompanionBuilder,
i1.$SettingsEntityTable,
i1.SettingsEntityData,
i1.$$SettingsEntityTableFilterComposer,
i1.$$SettingsEntityTableOrderingComposer,
i1.$$SettingsEntityTableAnnotationComposer,
$$SettingsEntityTableCreateCompanionBuilder,
$$SettingsEntityTableUpdateCompanionBuilder,
(
i1.MetadataEntityData,
i1.SettingsEntityData,
i0.BaseReferences<
i0.GeneratedDatabase,
i1.$MetadataEntityTable,
i1.MetadataEntityData
i1.$SettingsEntityTable,
i1.SettingsEntityData
>,
),
i1.MetadataEntityData,
i1.SettingsEntityData,
i0.PrefetchHooks Function()
> {
$$MetadataEntityTableTableManager(
$$SettingsEntityTableTableManager(
i0.GeneratedDatabase db,
i1.$MetadataEntityTable table,
i1.$SettingsEntityTable table,
) : super(
i0.TableManagerState(
db: db,
table: table,
createFilteringComposer: () =>
i1.$$MetadataEntityTableFilterComposer($db: db, $table: table),
i1.$$SettingsEntityTableFilterComposer($db: db, $table: table),
createOrderingComposer: () =>
i1.$$MetadataEntityTableOrderingComposer($db: db, $table: table),
i1.$$SettingsEntityTableOrderingComposer($db: db, $table: table),
createComputedFieldComposer: () => i1
.$$MetadataEntityTableAnnotationComposer($db: db, $table: table),
.$$SettingsEntityTableAnnotationComposer($db: db, $table: table),
updateCompanionCallback:
({
i0.Value<String> key = const i0.Value.absent(),
i0.Value<String> value = const i0.Value.absent(),
i0.Value<DateTime> updatedAt = const i0.Value.absent(),
}) => i1.MetadataEntityCompanion(
}) => i1.SettingsEntityCompanion(
key: key,
value: value,
updatedAt: updatedAt,
@@ -139,7 +139,7 @@ class $$MetadataEntityTableTableManager
required String key,
required String value,
i0.Value<DateTime> updatedAt = const i0.Value.absent(),
}) => i1.MetadataEntityCompanion.insert(
}) => i1.SettingsEntityCompanion.insert(
key: key,
value: value,
updatedAt: updatedAt,
@@ -152,34 +152,34 @@ class $$MetadataEntityTableTableManager
);
}
typedef $$MetadataEntityTableProcessedTableManager =
typedef $$SettingsEntityTableProcessedTableManager =
i0.ProcessedTableManager<
i0.GeneratedDatabase,
i1.$MetadataEntityTable,
i1.MetadataEntityData,
i1.$$MetadataEntityTableFilterComposer,
i1.$$MetadataEntityTableOrderingComposer,
i1.$$MetadataEntityTableAnnotationComposer,
$$MetadataEntityTableCreateCompanionBuilder,
$$MetadataEntityTableUpdateCompanionBuilder,
i1.$SettingsEntityTable,
i1.SettingsEntityData,
i1.$$SettingsEntityTableFilterComposer,
i1.$$SettingsEntityTableOrderingComposer,
i1.$$SettingsEntityTableAnnotationComposer,
$$SettingsEntityTableCreateCompanionBuilder,
$$SettingsEntityTableUpdateCompanionBuilder,
(
i1.MetadataEntityData,
i1.SettingsEntityData,
i0.BaseReferences<
i0.GeneratedDatabase,
i1.$MetadataEntityTable,
i1.MetadataEntityData
i1.$SettingsEntityTable,
i1.SettingsEntityData
>,
),
i1.MetadataEntityData,
i1.SettingsEntityData,
i0.PrefetchHooks Function()
>;
class $MetadataEntityTable extends i2.MetadataEntity
with i0.TableInfo<$MetadataEntityTable, i1.MetadataEntityData> {
class $SettingsEntityTable extends i2.SettingsEntity
with i0.TableInfo<$SettingsEntityTable, i1.SettingsEntityData> {
@override
final i0.GeneratedDatabase attachedDatabase;
final String? _alias;
$MetadataEntityTable(this.attachedDatabase, [this._alias]);
$SettingsEntityTable(this.attachedDatabase, [this._alias]);
static const i0.VerificationMeta _keyMeta = const i0.VerificationMeta('key');
@override
late final i0.GeneratedColumn<String> key = i0.GeneratedColumn<String>(
@@ -219,10 +219,10 @@ class $MetadataEntityTable extends i2.MetadataEntity
String get aliasedName => _alias ?? actualTableName;
@override
String get actualTableName => $name;
static const String $name = 'metadata';
static const String $name = 'settings';
@override
i0.VerificationContext validateIntegrity(
i0.Insertable<i1.MetadataEntityData> instance, {
i0.Insertable<i1.SettingsEntityData> instance, {
bool isInserting = false,
}) {
final context = i0.VerificationContext();
@@ -255,9 +255,9 @@ class $MetadataEntityTable extends i2.MetadataEntity
@override
Set<i0.GeneratedColumn> get $primaryKey => {key};
@override
i1.MetadataEntityData map(Map<String, dynamic> data, {String? tablePrefix}) {
i1.SettingsEntityData map(Map<String, dynamic> data, {String? tablePrefix}) {
final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : '';
return i1.MetadataEntityData(
return i1.SettingsEntityData(
key: attachedDatabase.typeMapping.read(
i0.DriftSqlType.string,
data['${effectivePrefix}key'],
@@ -274,8 +274,8 @@ class $MetadataEntityTable extends i2.MetadataEntity
}
@override
$MetadataEntityTable createAlias(String alias) {
return $MetadataEntityTable(attachedDatabase, alias);
$SettingsEntityTable createAlias(String alias) {
return $SettingsEntityTable(attachedDatabase, alias);
}
@override
@@ -284,12 +284,12 @@ class $MetadataEntityTable extends i2.MetadataEntity
bool get isStrict => true;
}
class MetadataEntityData extends i0.DataClass
implements i0.Insertable<i1.MetadataEntityData> {
class SettingsEntityData extends i0.DataClass
implements i0.Insertable<i1.SettingsEntityData> {
final String key;
final String value;
final DateTime updatedAt;
const MetadataEntityData({
const SettingsEntityData({
required this.key,
required this.value,
required this.updatedAt,
@@ -303,12 +303,12 @@ class MetadataEntityData extends i0.DataClass
return map;
}
factory MetadataEntityData.fromJson(
factory SettingsEntityData.fromJson(
Map<String, dynamic> json, {
i0.ValueSerializer? serializer,
}) {
serializer ??= i0.driftRuntimeOptions.defaultSerializer;
return MetadataEntityData(
return SettingsEntityData(
key: serializer.fromJson<String>(json['key']),
value: serializer.fromJson<String>(json['value']),
updatedAt: serializer.fromJson<DateTime>(json['updatedAt']),
@@ -324,17 +324,17 @@ class MetadataEntityData extends i0.DataClass
};
}
i1.MetadataEntityData copyWith({
i1.SettingsEntityData copyWith({
String? key,
String? value,
DateTime? updatedAt,
}) => i1.MetadataEntityData(
}) => i1.SettingsEntityData(
key: key ?? this.key,
value: value ?? this.value,
updatedAt: updatedAt ?? this.updatedAt,
);
MetadataEntityData copyWithCompanion(i1.MetadataEntityCompanion data) {
return MetadataEntityData(
SettingsEntityData copyWithCompanion(i1.SettingsEntityCompanion data) {
return SettingsEntityData(
key: data.key.present ? data.key.value : this.key,
value: data.value.present ? data.value.value : this.value,
updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt,
@@ -343,7 +343,7 @@ class MetadataEntityData extends i0.DataClass
@override
String toString() {
return (StringBuffer('MetadataEntityData(')
return (StringBuffer('SettingsEntityData(')
..write('key: $key, ')
..write('value: $value, ')
..write('updatedAt: $updatedAt')
@@ -356,29 +356,29 @@ class MetadataEntityData extends i0.DataClass
@override
bool operator ==(Object other) =>
identical(this, other) ||
(other is i1.MetadataEntityData &&
(other is i1.SettingsEntityData &&
other.key == this.key &&
other.value == this.value &&
other.updatedAt == this.updatedAt);
}
class MetadataEntityCompanion
extends i0.UpdateCompanion<i1.MetadataEntityData> {
class SettingsEntityCompanion
extends i0.UpdateCompanion<i1.SettingsEntityData> {
final i0.Value<String> key;
final i0.Value<String> value;
final i0.Value<DateTime> updatedAt;
const MetadataEntityCompanion({
const SettingsEntityCompanion({
this.key = const i0.Value.absent(),
this.value = const i0.Value.absent(),
this.updatedAt = const i0.Value.absent(),
});
MetadataEntityCompanion.insert({
SettingsEntityCompanion.insert({
required String key,
required String value,
this.updatedAt = const i0.Value.absent(),
}) : key = i0.Value(key),
value = i0.Value(value);
static i0.Insertable<i1.MetadataEntityData> custom({
static i0.Insertable<i1.SettingsEntityData> custom({
i0.Expression<String>? key,
i0.Expression<String>? value,
i0.Expression<DateTime>? updatedAt,
@@ -390,12 +390,12 @@ class MetadataEntityCompanion
});
}
i1.MetadataEntityCompanion copyWith({
i1.SettingsEntityCompanion copyWith({
i0.Value<String>? key,
i0.Value<String>? value,
i0.Value<DateTime>? updatedAt,
}) {
return i1.MetadataEntityCompanion(
return i1.SettingsEntityCompanion(
key: key ?? this.key,
value: value ?? this.value,
updatedAt: updatedAt ?? this.updatedAt,
@@ -419,7 +419,7 @@ class MetadataEntityCompanion
@override
String toString() {
return (StringBuffer('MetadataEntityCompanion(')
return (StringBuffer('SettingsEntityCompanion(')
..write('key: $key, ')
..write('value: $value, ')
..write('updatedAt: $updatedAt')
@@ -13,7 +13,7 @@ import 'package:immich_mobile/infrastructure/entities/local_asset.entity.dart';
import 'package:immich_mobile/infrastructure/entities/local_asset.entity.drift.dart';
import 'package:immich_mobile/infrastructure/entities/memory.entity.dart';
import 'package:immich_mobile/infrastructure/entities/memory_asset.entity.dart';
import 'package:immich_mobile/infrastructure/entities/metadata.entity.dart';
import 'package:immich_mobile/infrastructure/entities/settings.entity.dart';
import 'package:immich_mobile/infrastructure/entities/partner.entity.dart';
import 'package:immich_mobile/infrastructure/entities/person.entity.dart';
import 'package:immich_mobile/infrastructure/entities/remote_album.entity.dart';
@@ -55,7 +55,7 @@ import 'package:logging/logging.dart';
StoreEntity,
TrashedLocalAssetEntity,
AssetEditEntity,
MetadataEntity,
SettingsEntity,
],
include: {'package:immich_mobile/infrastructure/entities/merged_asset.drift'},
)
@@ -98,7 +98,7 @@ class Drift extends $Drift {
}
@override
int get schemaVersion => 26;
int get schemaVersion => 27;
@override
MigrationStrategy get migration => MigrationStrategy(
@@ -276,6 +276,9 @@ class Drift extends $Drift {
from25To26: (m, v26) async {
await m.addColumn(v26.remoteAssetEntity, v26.remoteAssetEntity.uploadedAt);
},
from26To27: (m, v27) async {
await customStatement('ALTER TABLE metadata RENAME TO settings');
},
),
);
@@ -43,7 +43,7 @@ import 'package:immich_mobile/infrastructure/entities/trashed_local_asset.entity
as i20;
import 'package:immich_mobile/infrastructure/entities/asset_edit.entity.drift.dart'
as i21;
import 'package:immich_mobile/infrastructure/entities/metadata.entity.drift.dart'
import 'package:immich_mobile/infrastructure/entities/settings.entity.drift.dart'
as i22;
import 'package:immich_mobile/infrastructure/entities/merged_asset.drift.dart'
as i23;
@@ -91,7 +91,7 @@ abstract class $Drift extends i0.GeneratedDatabase {
.$TrashedLocalAssetEntityTable(this);
late final i21.$AssetEditEntityTable assetEditEntity = i21
.$AssetEditEntityTable(this);
late final i22.$MetadataEntityTable metadataEntity = i22.$MetadataEntityTable(
late final i22.$SettingsEntityTable settingsEntity = i22.$SettingsEntityTable(
this,
);
i23.MergedAssetDrift get mergedAssetDrift => i24.ReadDatabaseContainer(
@@ -132,7 +132,7 @@ abstract class $Drift extends i0.GeneratedDatabase {
storeEntity,
trashedLocalAssetEntity,
assetEditEntity,
metadataEntity,
settingsEntity,
i10.idxPartnerSharedWithId,
i11.idxLatLng,
i11.idxRemoteExifCity,
@@ -395,6 +395,6 @@ class $DriftManager {
);
i21.$$AssetEditEntityTableTableManager get assetEditEntity =>
i21.$$AssetEditEntityTableTableManager(_db, _db.assetEditEntity);
i22.$$MetadataEntityTableTableManager get metadataEntity =>
i22.$$MetadataEntityTableTableManager(_db, _db.metadataEntity);
i22.$$SettingsEntityTableTableManager get settingsEntity =>
i22.$$SettingsEntityTableTableManager(_db, _db.settingsEntity);
}
@@ -13539,6 +13539,550 @@ i1.GeneratedColumn<String> _column_212(String aliasedName) =>
type: i1.DriftSqlType.string,
$customConstraints: 'NULL',
);
final class Schema27 extends i0.VersionedSchema {
Schema27({required super.database}) : super(version: 27);
@override
late final List<i1.DatabaseSchemaEntity> entities = [
userEntity,
remoteAssetEntity,
stackEntity,
localAssetEntity,
remoteAlbumEntity,
localAlbumEntity,
localAlbumAssetEntity,
idxLocalAlbumAssetAlbumAsset,
idxLocalAssetChecksum,
idxLocalAssetCloudId,
idxStackPrimaryAssetId,
uQRemoteAssetsOwnerChecksum,
uQRemoteAssetsOwnerLibraryChecksum,
idxRemoteAssetChecksum,
idxRemoteAssetStackId,
idxRemoteAssetOwnerVisibilityDeletedCreated,
authUserEntity,
userMetadataEntity,
partnerEntity,
remoteExifEntity,
remoteAlbumAssetEntity,
remoteAlbumUserEntity,
remoteAssetCloudIdEntity,
memoryEntity,
memoryAssetEntity,
personEntity,
assetFaceEntity,
storeEntity,
trashedLocalAssetEntity,
assetEditEntity,
settings,
idxPartnerSharedWithId,
idxLatLng,
idxRemoteExifCity,
idxRemoteAlbumAssetAlbumAsset,
idxRemoteAssetCloudId,
idxPersonOwnerId,
idxAssetFacePersonId,
idxAssetFaceAssetId,
idxAssetFaceVisiblePerson,
idxTrashedLocalAssetChecksum,
idxTrashedLocalAssetAlbum,
idxAssetEditAssetId,
];
late final Shape33 userEntity = Shape33(
source: i0.VersionedTable(
entityName: 'user_entity',
withoutRowId: true,
isStrict: true,
tableConstraints: ['PRIMARY KEY(id)'],
columns: [
_column_107,
_column_108,
_column_109,
_column_110,
_column_111,
_column_112,
],
attachedDatabase: database,
),
alias: null,
);
late final Shape50 remoteAssetEntity = Shape50(
source: i0.VersionedTable(
entityName: 'remote_asset_entity',
withoutRowId: true,
isStrict: true,
tableConstraints: ['PRIMARY KEY(id)'],
columns: [
_column_108,
_column_113,
_column_114,
_column_115,
_column_116,
_column_117,
_column_118,
_column_107,
_column_119,
_column_120,
_column_121,
_column_122,
_column_123,
_column_124,
_column_212,
_column_125,
_column_126,
_column_127,
_column_128,
_column_129,
],
attachedDatabase: database,
),
alias: null,
);
late final Shape35 stackEntity = Shape35(
source: i0.VersionedTable(
entityName: 'stack_entity',
withoutRowId: true,
isStrict: true,
tableConstraints: ['PRIMARY KEY(id)'],
columns: [
_column_107,
_column_114,
_column_115,
_column_121,
_column_130,
],
attachedDatabase: database,
),
alias: null,
);
late final Shape36 localAssetEntity = Shape36(
source: i0.VersionedTable(
entityName: 'local_asset_entity',
withoutRowId: true,
isStrict: true,
tableConstraints: ['PRIMARY KEY(id)'],
columns: [
_column_108,
_column_113,
_column_114,
_column_115,
_column_116,
_column_117,
_column_118,
_column_107,
_column_131,
_column_120,
_column_132,
_column_133,
_column_134,
_column_135,
_column_136,
_column_137,
],
attachedDatabase: database,
),
alias: null,
);
late final Shape48 remoteAlbumEntity = Shape48(
source: i0.VersionedTable(
entityName: 'remote_album_entity',
withoutRowId: true,
isStrict: true,
tableConstraints: ['PRIMARY KEY(id)'],
columns: [
_column_107,
_column_108,
_column_138,
_column_114,
_column_115,
_column_139,
_column_140,
_column_141,
],
attachedDatabase: database,
),
alias: null,
);
late final Shape38 localAlbumEntity = Shape38(
source: i0.VersionedTable(
entityName: 'local_album_entity',
withoutRowId: true,
isStrict: true,
tableConstraints: ['PRIMARY KEY(id)'],
columns: [
_column_107,
_column_108,
_column_115,
_column_142,
_column_143,
_column_144,
_column_145,
],
attachedDatabase: database,
),
alias: null,
);
late final Shape39 localAlbumAssetEntity = Shape39(
source: i0.VersionedTable(
entityName: 'local_album_asset_entity',
withoutRowId: true,
isStrict: true,
tableConstraints: ['PRIMARY KEY(asset_id, album_id)'],
columns: [_column_146, _column_147, _column_145],
attachedDatabase: database,
),
alias: null,
);
final i1.Index idxLocalAlbumAssetAlbumAsset = i1.Index(
'idx_local_album_asset_album_asset',
'CREATE INDEX IF NOT EXISTS idx_local_album_asset_album_asset ON local_album_asset_entity (album_id, asset_id)',
);
final i1.Index idxLocalAssetChecksum = i1.Index(
'idx_local_asset_checksum',
'CREATE INDEX IF NOT EXISTS idx_local_asset_checksum ON local_asset_entity (checksum)',
);
final i1.Index idxLocalAssetCloudId = i1.Index(
'idx_local_asset_cloud_id',
'CREATE INDEX IF NOT EXISTS idx_local_asset_cloud_id ON local_asset_entity (i_cloud_id)',
);
final i1.Index idxStackPrimaryAssetId = i1.Index(
'idx_stack_primary_asset_id',
'CREATE INDEX IF NOT EXISTS idx_stack_primary_asset_id ON stack_entity (primary_asset_id)',
);
final i1.Index uQRemoteAssetsOwnerChecksum = i1.Index(
'UQ_remote_assets_owner_checksum',
'CREATE UNIQUE INDEX IF NOT EXISTS UQ_remote_assets_owner_checksum ON remote_asset_entity (owner_id, checksum) WHERE(library_id IS NULL)',
);
final i1.Index uQRemoteAssetsOwnerLibraryChecksum = i1.Index(
'UQ_remote_assets_owner_library_checksum',
'CREATE UNIQUE INDEX IF NOT EXISTS UQ_remote_assets_owner_library_checksum ON remote_asset_entity (owner_id, library_id, checksum) WHERE(library_id IS NOT NULL)',
);
final i1.Index idxRemoteAssetChecksum = i1.Index(
'idx_remote_asset_checksum',
'CREATE INDEX IF NOT EXISTS idx_remote_asset_checksum ON remote_asset_entity (checksum)',
);
final i1.Index idxRemoteAssetStackId = i1.Index(
'idx_remote_asset_stack_id',
'CREATE INDEX IF NOT EXISTS idx_remote_asset_stack_id ON remote_asset_entity (stack_id)',
);
final i1.Index idxRemoteAssetOwnerVisibilityDeletedCreated = i1.Index(
'idx_remote_asset_owner_visibility_deleted_created',
'CREATE INDEX IF NOT EXISTS idx_remote_asset_owner_visibility_deleted_created ON remote_asset_entity (owner_id, visibility, deleted_at, created_at DESC)',
);
late final Shape40 authUserEntity = Shape40(
source: i0.VersionedTable(
entityName: 'auth_user_entity',
withoutRowId: true,
isStrict: true,
tableConstraints: ['PRIMARY KEY(id)'],
columns: [
_column_107,
_column_108,
_column_109,
_column_148,
_column_110,
_column_111,
_column_149,
_column_150,
_column_151,
_column_152,
],
attachedDatabase: database,
),
alias: null,
);
late final Shape4 userMetadataEntity = Shape4(
source: i0.VersionedTable(
entityName: 'user_metadata_entity',
withoutRowId: true,
isStrict: true,
tableConstraints: ['PRIMARY KEY(user_id, "key")'],
columns: [_column_153, _column_154, _column_155],
attachedDatabase: database,
),
alias: null,
);
late final Shape41 partnerEntity = Shape41(
source: i0.VersionedTable(
entityName: 'partner_entity',
withoutRowId: true,
isStrict: true,
tableConstraints: ['PRIMARY KEY(shared_by_id, shared_with_id)'],
columns: [_column_156, _column_157, _column_158],
attachedDatabase: database,
),
alias: null,
);
late final Shape42 remoteExifEntity = Shape42(
source: i0.VersionedTable(
entityName: 'remote_exif_entity',
withoutRowId: true,
isStrict: true,
tableConstraints: ['PRIMARY KEY(asset_id)'],
columns: [
_column_159,
_column_160,
_column_161,
_column_162,
_column_163,
_column_164,
_column_117,
_column_116,
_column_165,
_column_166,
_column_167,
_column_168,
_column_135,
_column_136,
_column_169,
_column_170,
_column_171,
_column_172,
_column_173,
_column_174,
_column_175,
_column_176,
],
attachedDatabase: database,
),
alias: null,
);
late final Shape7 remoteAlbumAssetEntity = Shape7(
source: i0.VersionedTable(
entityName: 'remote_album_asset_entity',
withoutRowId: true,
isStrict: true,
tableConstraints: ['PRIMARY KEY(asset_id, album_id)'],
columns: [_column_159, _column_177],
attachedDatabase: database,
),
alias: null,
);
late final Shape10 remoteAlbumUserEntity = Shape10(
source: i0.VersionedTable(
entityName: 'remote_album_user_entity',
withoutRowId: true,
isStrict: true,
tableConstraints: ['PRIMARY KEY(album_id, user_id)'],
columns: [_column_177, _column_153, _column_178],
attachedDatabase: database,
),
alias: null,
);
late final Shape43 remoteAssetCloudIdEntity = Shape43(
source: i0.VersionedTable(
entityName: 'remote_asset_cloud_id_entity',
withoutRowId: true,
isStrict: true,
tableConstraints: ['PRIMARY KEY(asset_id)'],
columns: [
_column_159,
_column_179,
_column_180,
_column_134,
_column_135,
_column_136,
],
attachedDatabase: database,
),
alias: null,
);
late final Shape44 memoryEntity = Shape44(
source: i0.VersionedTable(
entityName: 'memory_entity',
withoutRowId: true,
isStrict: true,
tableConstraints: ['PRIMARY KEY(id)'],
columns: [
_column_107,
_column_114,
_column_115,
_column_124,
_column_121,
_column_113,
_column_181,
_column_182,
_column_183,
_column_184,
_column_185,
_column_186,
],
attachedDatabase: database,
),
alias: null,
);
late final Shape12 memoryAssetEntity = Shape12(
source: i0.VersionedTable(
entityName: 'memory_asset_entity',
withoutRowId: true,
isStrict: true,
tableConstraints: ['PRIMARY KEY(asset_id, memory_id)'],
columns: [_column_159, _column_187],
attachedDatabase: database,
),
alias: null,
);
late final Shape45 personEntity = Shape45(
source: i0.VersionedTable(
entityName: 'person_entity',
withoutRowId: true,
isStrict: true,
tableConstraints: ['PRIMARY KEY(id)'],
columns: [
_column_107,
_column_114,
_column_115,
_column_121,
_column_108,
_column_188,
_column_189,
_column_190,
_column_191,
_column_192,
],
attachedDatabase: database,
),
alias: null,
);
late final Shape46 assetFaceEntity = Shape46(
source: i0.VersionedTable(
entityName: 'asset_face_entity',
withoutRowId: true,
isStrict: true,
tableConstraints: ['PRIMARY KEY(id)'],
columns: [
_column_107,
_column_159,
_column_193,
_column_194,
_column_195,
_column_196,
_column_197,
_column_198,
_column_199,
_column_200,
_column_201,
_column_124,
],
attachedDatabase: database,
),
alias: null,
);
late final Shape18 storeEntity = Shape18(
source: i0.VersionedTable(
entityName: 'store_entity',
withoutRowId: true,
isStrict: true,
tableConstraints: ['PRIMARY KEY(id)'],
columns: [_column_202, _column_203, _column_204],
attachedDatabase: database,
),
alias: null,
);
late final Shape47 trashedLocalAssetEntity = Shape47(
source: i0.VersionedTable(
entityName: 'trashed_local_asset_entity',
withoutRowId: true,
isStrict: true,
tableConstraints: ['PRIMARY KEY(id, album_id)'],
columns: [
_column_108,
_column_113,
_column_114,
_column_115,
_column_116,
_column_117,
_column_118,
_column_107,
_column_205,
_column_131,
_column_120,
_column_132,
_column_206,
_column_137,
],
attachedDatabase: database,
),
alias: null,
);
late final Shape32 assetEditEntity = Shape32(
source: i0.VersionedTable(
entityName: 'asset_edit_entity',
withoutRowId: true,
isStrict: true,
tableConstraints: ['PRIMARY KEY(id)'],
columns: [
_column_107,
_column_159,
_column_207,
_column_208,
_column_209,
],
attachedDatabase: database,
),
alias: null,
);
late final Shape49 settings = Shape49(
source: i0.VersionedTable(
entityName: 'settings',
withoutRowId: true,
isStrict: true,
tableConstraints: ['PRIMARY KEY("key")'],
columns: [_column_210, _column_211, _column_115],
attachedDatabase: database,
),
alias: null,
);
final i1.Index idxPartnerSharedWithId = i1.Index(
'idx_partner_shared_with_id',
'CREATE INDEX IF NOT EXISTS idx_partner_shared_with_id ON partner_entity (shared_with_id)',
);
final i1.Index idxLatLng = i1.Index(
'idx_lat_lng',
'CREATE INDEX IF NOT EXISTS idx_lat_lng ON remote_exif_entity (latitude, longitude)',
);
final i1.Index idxRemoteExifCity = i1.Index(
'idx_remote_exif_city',
'CREATE INDEX IF NOT EXISTS idx_remote_exif_city ON remote_exif_entity (city) WHERE city IS NOT NULL',
);
final i1.Index idxRemoteAlbumAssetAlbumAsset = i1.Index(
'idx_remote_album_asset_album_asset',
'CREATE INDEX IF NOT EXISTS idx_remote_album_asset_album_asset ON remote_album_asset_entity (album_id, asset_id)',
);
final i1.Index idxRemoteAssetCloudId = i1.Index(
'idx_remote_asset_cloud_id',
'CREATE INDEX IF NOT EXISTS idx_remote_asset_cloud_id ON remote_asset_cloud_id_entity (cloud_id)',
);
final i1.Index idxPersonOwnerId = i1.Index(
'idx_person_owner_id',
'CREATE INDEX IF NOT EXISTS idx_person_owner_id ON person_entity (owner_id)',
);
final i1.Index idxAssetFacePersonId = i1.Index(
'idx_asset_face_person_id',
'CREATE INDEX IF NOT EXISTS idx_asset_face_person_id ON asset_face_entity (person_id)',
);
final i1.Index idxAssetFaceAssetId = i1.Index(
'idx_asset_face_asset_id',
'CREATE INDEX IF NOT EXISTS idx_asset_face_asset_id ON asset_face_entity (asset_id)',
);
final i1.Index idxAssetFaceVisiblePerson = i1.Index(
'idx_asset_face_visible_person',
'CREATE INDEX IF NOT EXISTS idx_asset_face_visible_person ON asset_face_entity (person_id, asset_id) WHERE is_visible = 1 AND deleted_at IS NULL',
);
final i1.Index idxTrashedLocalAssetChecksum = i1.Index(
'idx_trashed_local_asset_checksum',
'CREATE INDEX IF NOT EXISTS idx_trashed_local_asset_checksum ON trashed_local_asset_entity (checksum)',
);
final i1.Index idxTrashedLocalAssetAlbum = i1.Index(
'idx_trashed_local_asset_album',
'CREATE INDEX IF NOT EXISTS idx_trashed_local_asset_album ON trashed_local_asset_entity (album_id)',
);
final i1.Index idxAssetEditAssetId = i1.Index(
'idx_asset_edit_asset_id',
'CREATE INDEX IF NOT EXISTS idx_asset_edit_asset_id ON asset_edit_entity (asset_id)',
);
}
i0.MigrationStepWithVersion migrationSteps({
required Future<void> Function(i1.Migrator m, Schema2 schema) from1To2,
required Future<void> Function(i1.Migrator m, Schema3 schema) from2To3,
@@ -13565,6 +14109,7 @@ i0.MigrationStepWithVersion migrationSteps({
required Future<void> Function(i1.Migrator m, Schema24 schema) from23To24,
required Future<void> Function(i1.Migrator m, Schema25 schema) from24To25,
required Future<void> Function(i1.Migrator m, Schema26 schema) from25To26,
required Future<void> Function(i1.Migrator m, Schema27 schema) from26To27,
}) {
return (currentVersion, database) async {
switch (currentVersion) {
@@ -13693,6 +14238,11 @@ i0.MigrationStepWithVersion migrationSteps({
final migrator = i1.Migrator(database, schema);
await from25To26(migrator, schema);
return 26;
case 26:
final schema = Schema27(database: database);
final migrator = i1.Migrator(database, schema);
await from26To27(migrator, schema);
return 27;
default:
throw ArgumentError.value('Unknown migration from $currentVersion');
}
@@ -13725,6 +14275,7 @@ i1.OnUpgrade stepByStep({
required Future<void> Function(i1.Migrator m, Schema24 schema) from23To24,
required Future<void> Function(i1.Migrator m, Schema25 schema) from24To25,
required Future<void> Function(i1.Migrator m, Schema26 schema) from25To26,
required Future<void> Function(i1.Migrator m, Schema27 schema) from26To27,
}) => i0.VersionedSchema.stepByStepHelper(
step: migrationSteps(
from1To2: from1To2,
@@ -13752,5 +14303,6 @@ i1.OnUpgrade stepByStep({
from23To24: from23To24,
from24To25: from24To25,
from25To26: from25To26,
from26To27: from26To27,
),
);
@@ -1,21 +1,21 @@
import 'package:collection/collection.dart';
import 'package:drift/drift.dart';
import 'package:immich_mobile/domain/models/config/app_config.dart';
import 'package:immich_mobile/domain/models/metadata_key.dart';
import 'package:immich_mobile/infrastructure/entities/metadata.entity.drift.dart';
import 'package:immich_mobile/domain/models/settings_key.dart';
import 'package:immich_mobile/infrastructure/entities/settings.entity.drift.dart';
import 'package:immich_mobile/infrastructure/repositories/db.repository.dart';
class MetadataRepository extends DriftDatabaseRepository {
class SettingsRepository extends DriftDatabaseRepository {
final Drift _db;
MetadataRepository._(this._db) : super(_db);
SettingsRepository._(this._db) : super(_db);
static MetadataRepository? _instance;
static SettingsRepository? _instance;
static MetadataRepository get instance {
static SettingsRepository get instance {
final instance = _instance;
if (instance == null) {
throw StateError('MetadataRepository not initialized. Call ensureInitialized() first');
throw StateError('SettingsRepository not initialized. Call ensureInitialized() first');
}
return instance;
}
@@ -23,31 +23,31 @@ class MetadataRepository extends DriftDatabaseRepository {
AppConfig _appConfig = const .new();
AppConfig get appConfig => _appConfig;
static Future<MetadataRepository> ensureInitialized(Drift db) async {
static Future<SettingsRepository> ensureInitialized(Drift db) async {
if (_instance == null) {
final instance = MetadataRepository._(db);
final instance = SettingsRepository._(db);
await instance.refresh();
_instance = instance;
}
return _instance!;
}
Future<void> refresh() async => _applyOverrides(await _db.select(_db.metadataEntity).get());
Future<void> refresh() async => _applyOverrides(await _db.select(_db.settingsEntity).get());
Future<void> clear(Iterable<MetadataKey> keys) async {
Future<void> clear(Iterable<SettingsKey> keys) async {
if (keys.isEmpty) {
return;
}
final names = keys.map((key) => key.name).toList();
await (_db.delete(_db.metadataEntity)..where((row) => row.key.isIn(names))).go();
await (_db.delete(_db.settingsEntity)..where((row) => row.key.isIn(names))).go();
for (final key in keys) {
_appConfig = _appConfig.write(key, defaultConfig.read(key));
}
}
Future<void> write<T extends Object, U extends T>(MetadataKey<T> key, U value) async {
Future<void> write<T extends Object, U extends T>(SettingsKey<T> key, U value) async {
if (value == _appConfig.read(key)) {
return;
}
@@ -57,22 +57,22 @@ class MetadataRepository extends DriftDatabaseRepository {
}
await _db
.into(_db.metadataEntity)
.into(_db.settingsEntity)
.insertOnConflictUpdate(
MetadataEntityCompanion.insert(key: key.name, value: key.encode(value), updatedAt: Value(DateTime.now())),
SettingsEntityCompanion.insert(key: key.name, value: key.encode(value), updatedAt: Value(DateTime.now())),
);
_appConfig = _appConfig.write(key, value);
}
Stream<AppConfig> watchConfig() => _db.select(_db.metadataEntity).watch().map((rows) {
Stream<AppConfig> watchConfig() => _db.select(_db.settingsEntity).watch().map((rows) {
_applyOverrides(rows);
return _appConfig;
});
void _applyOverrides(List<MetadataEntityData> rows) {
void _applyOverrides(List<SettingsEntityData> rows) {
_appConfig = AppConfig.fromEntries(
rows.fold({}, (overrides, row) {
final metadataKey = MetadataKey.values.firstWhereOrNull((key) => key.name == row.key);
final metadataKey = SettingsKey.values.firstWhereOrNull((key) => key.name == row.key);
if (metadataKey == null) {
return overrides;
}
+1 -1
View File
@@ -25,7 +25,7 @@ import 'package:immich_mobile/platform/background_worker_lock_api.g.dart';
import 'package:immich_mobile/providers/app_life_cycle.provider.dart';
import 'package:immich_mobile/providers/asset_viewer/share_intent_upload.provider.dart';
import 'package:immich_mobile/providers/infrastructure/db.provider.dart';
import 'package:immich_mobile/providers/infrastructure/metadata.provider.dart';
import 'package:immich_mobile/providers/infrastructure/settings.provider.dart';
import 'package:immich_mobile/providers/infrastructure/platform.provider.dart';
import 'package:immich_mobile/providers/locale_provider.dart';
import 'package:immich_mobile/providers/routes.provider.dart';
@@ -8,11 +8,11 @@ import 'package:immich_mobile/domain/models/album/local_album.model.dart';
import 'package:immich_mobile/domain/services/sync_linked_album.service.dart';
import 'package:immich_mobile/extensions/build_context_extensions.dart';
import 'package:immich_mobile/extensions/translate_extensions.dart';
import 'package:immich_mobile/infrastructure/repositories/metadata.repository.dart';
import 'package:immich_mobile/infrastructure/repositories/settings.repository.dart';
import 'package:immich_mobile/providers/background_sync.provider.dart';
import 'package:immich_mobile/providers/backup/backup_album.provider.dart';
import 'package:immich_mobile/providers/backup/drift_backup.provider.dart';
import 'package:immich_mobile/providers/infrastructure/metadata.provider.dart';
import 'package:immich_mobile/providers/infrastructure/settings.provider.dart';
import 'package:immich_mobile/providers/infrastructure/platform.provider.dart';
import 'package:immich_mobile/providers/user.provider.dart';
import 'package:immich_mobile/widgets/backup/drift_album_info_list_tile.dart';
@@ -103,7 +103,7 @@ class _DriftBackupAlbumSelectionPageState extends ConsumerState<DriftBackupAlbum
return;
}
final isBackupEnabled = MetadataRepository.instance.appConfig.backup.enabled;
final isBackupEnabled = SettingsRepository.instance.appConfig.backup.enabled;
await ref.read(driftBackupProvider.notifier).getBackupStatus(user.id);
final currentTotalAssetCount = ref.read(driftBackupProvider.select((p) => p.totalCount));
final totalChanged = currentTotalAssetCount != _initialTotalAssetCount;
@@ -4,10 +4,10 @@ import 'package:auto_route/auto_route.dart';
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/extensions/translate_extensions.dart';
import 'package:immich_mobile/infrastructure/repositories/metadata.repository.dart';
import 'package:immich_mobile/infrastructure/repositories/settings.repository.dart';
import 'package:immich_mobile/providers/background_sync.provider.dart';
import 'package:immich_mobile/providers/backup/drift_backup.provider.dart';
import 'package:immich_mobile/providers/infrastructure/metadata.provider.dart';
import 'package:immich_mobile/providers/infrastructure/settings.provider.dart';
import 'package:immich_mobile/providers/user.provider.dart';
import 'package:immich_mobile/widgets/settings/backup_settings/drift_backup_settings.dart';
import 'package:logging/logging.dart';
@@ -27,7 +27,7 @@ class DriftBackupOptionsPage extends ConsumerWidget {
// There is an issue with Flutter where the pop event
// can be triggered multiple times, so we guard it with _hasPopped
final currentBackup = ref.read(metadataProvider).appConfig.backup;
final currentBackup = ref.read(appConfigProvider).backup;
final currentCellularForVideos = currentBackup.useCellularForVideos;
final currentCellularForPhotos = currentBackup.useCellularForPhotos;
@@ -45,7 +45,7 @@ class DriftBackupOptionsPage extends ConsumerWidget {
}
await ref.read(driftBackupProvider.notifier).getBackupStatus(currentUser.id);
final isBackupEnabled = MetadataRepository.instance.appConfig.backup.enabled;
final isBackupEnabled = SettingsRepository.instance.appConfig.backup.enabled;
if (!isBackupEnabled) {
return;
}
@@ -3,10 +3,9 @@ import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/domain/models/metadata_key.dart';
import 'package:immich_mobile/generated/translations.g.dart';
import 'package:immich_mobile/providers/api.provider.dart';
import 'package:immich_mobile/providers/infrastructure/metadata.provider.dart';
import 'package:immich_mobile/providers/infrastructure/settings.provider.dart';
class SettingsHeader {
String key = "";
@@ -22,7 +21,7 @@ class HeaderSettingsPage extends HookConsumerWidget {
final headers = useState<List<SettingsHeader>>([]);
final setInitialHeaders = useState(false);
final storedHeaders = ref.read(metadataProvider).appConfig.network.customHeaders;
final storedHeaders = ref.read(appConfigProvider).network.customHeaders;
if (!setInitialHeaders.value) {
storedHeaders.forEach((k, v) {
final header = SettingsHeader();
@@ -94,7 +93,7 @@ class HeaderSettingsPage extends HookConsumerWidget {
headersMap[key] = value;
}
await ref.read(metadataProvider).write(MetadataKey.networkCustomHeaders, headersMap);
await ref.read(settingsProvider).write(.networkCustomHeaders, headersMap);
await ref.read(apiServiceProvider).updateHeaders();
}
}
@@ -12,7 +12,7 @@ import 'package:immich_mobile/domain/models/store.model.dart';
import 'package:immich_mobile/entities/store.entity.dart';
import 'package:immich_mobile/generated/codegen_loader.g.dart';
import 'package:immich_mobile/generated/translations.g.dart';
import 'package:immich_mobile/infrastructure/repositories/metadata.repository.dart';
import 'package:immich_mobile/infrastructure/repositories/settings.repository.dart';
import 'package:immich_mobile/providers/auth.provider.dart';
import 'package:immich_mobile/providers/background_sync.provider.dart';
import 'package:immich_mobile/providers/backup/drift_backup.provider.dart';
@@ -341,7 +341,7 @@ class SplashScreenPageState extends ConsumerState<SplashScreenPage> {
await backgroundManager.hashAssets();
}
if (MetadataRepository.instance.appConfig.backup.syncAlbums) {
if (SettingsRepository.instance.appConfig.backup.syncAlbums) {
await backgroundManager.syncLinkedAlbum();
}
} catch (e) {
@@ -370,7 +370,7 @@ class SplashScreenPageState extends ConsumerState<SplashScreenPage> {
}
Future<void> _resumeBackup(DriftBackupNotifier notifier) async {
final isEnableBackup = MetadataRepository.instance.appConfig.backup.enabled;
final isEnableBackup = SettingsRepository.instance.appConfig.backup.enabled;
if (isEnableBackup) {
final currentUser = Store.tryGet(StoreKey.currentUser);
@@ -17,7 +17,7 @@ import 'package:immich_mobile/presentation/widgets/asset_viewer/video_viewer.wid
import 'package:immich_mobile/presentation/widgets/images/image_provider.dart';
import 'package:immich_mobile/providers/asset_viewer/asset_viewer.provider.dart';
import 'package:immich_mobile/providers/asset_viewer/video_player_provider.dart';
import 'package:immich_mobile/providers/infrastructure/metadata.provider.dart';
import 'package:immich_mobile/providers/infrastructure/settings.provider.dart';
import 'package:immich_mobile/routing/router.dart';
import 'package:immich_mobile/widgets/common/immich_loading_indicator.dart';
import 'package:immich_mobile/widgets/photo_view/photo_view.dart';
@@ -15,12 +15,11 @@ import 'package:immich_mobile/models/albums/album_search.model.dart';
import 'package:immich_mobile/presentation/widgets/album/album_tile.dart';
import 'package:immich_mobile/presentation/widgets/album/new_album_name_modal.widget.dart';
import 'package:immich_mobile/presentation/widgets/images/thumbnail.widget.dart';
import 'package:immich_mobile/domain/models/metadata_key.dart';
import 'package:immich_mobile/providers/album/album_sort_by_options.provider.dart';
import 'package:immich_mobile/providers/asset_viewer/asset_viewer.provider.dart';
import 'package:immich_mobile/providers/infrastructure/album.provider.dart';
import 'package:immich_mobile/providers/infrastructure/asset.provider.dart';
import 'package:immich_mobile/providers/infrastructure/metadata.provider.dart';
import 'package:immich_mobile/providers/infrastructure/settings.provider.dart';
import 'package:immich_mobile/providers/timeline/multiselect.provider.dart';
import 'package:immich_mobile/providers/user.provider.dart';
import 'package:immich_mobile/routing/router.dart';
@@ -58,7 +57,7 @@ class _AlbumSelectorState extends ConsumerState<AlbumSelector> {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) {
final albumConfig = ref.read(metadataProvider).appConfig.album;
final albumConfig = ref.read(appConfigProvider).album;
setState(() {
sort = AlbumSort(mode: albumConfig.sortMode, isReverse: albumConfig.isReverse);
@@ -94,7 +93,7 @@ class _AlbumSelectorState extends ConsumerState<AlbumSelector> {
setState(() {
isGrid = !isGrid;
});
ref.read(metadataProvider).write(MetadataKey.albumIsGrid, isGrid);
ref.read(settingsProvider).write(.albumIsGrid, isGrid);
}
void changeFilter(QuickFilterMode mode) {
@@ -110,9 +109,9 @@ class _AlbumSelectorState extends ConsumerState<AlbumSelector> {
this.sort = sort;
});
final metadata = ref.read(metadataProvider);
await metadata.write(MetadataKey.albumSortMode, sort.mode);
await metadata.write(MetadataKey.albumIsReverse, sort.isReverse);
final metadata = ref.read(settingsProvider);
await metadata.write(.albumSortMode, sort.mode);
await metadata.write(.albumIsReverse, sort.isReverse);
await sortAlbums();
}
@@ -19,7 +19,7 @@ import 'package:immich_mobile/presentation/widgets/images/image_provider.dart';
import 'package:immich_mobile/presentation/widgets/images/thumbnail.widget.dart';
import 'package:immich_mobile/providers/asset_viewer/asset_viewer.provider.dart';
import 'package:immich_mobile/providers/asset_viewer/is_motion_video_playing.provider.dart';
import 'package:immich_mobile/providers/infrastructure/metadata.provider.dart';
import 'package:immich_mobile/providers/infrastructure/settings.provider.dart';
import 'package:immich_mobile/providers/infrastructure/timeline.provider.dart';
import 'package:immich_mobile/widgets/common/immich_loading_indicator.dart';
import 'package:immich_mobile/widgets/photo_view/photo_view.dart';
@@ -241,7 +241,7 @@ class _AssetPageState extends ConsumerState<AssetPage> {
return;
}
final tapToNavigate = ref.read(metadataProvider).appConfig.viewer.tapToNavigate;
final tapToNavigate = ref.read(appConfigProvider).viewer.tapToNavigate;
if (!tapToNavigate) {
_viewer.toggleControls();
return;
@@ -12,7 +12,7 @@ import 'package:immich_mobile/providers/asset_viewer/is_motion_video_playing.pro
import 'package:immich_mobile/providers/asset_viewer/video_player_provider.dart';
import 'package:immich_mobile/providers/cast.provider.dart';
import 'package:immich_mobile/providers/infrastructure/asset.provider.dart';
import 'package:immich_mobile/providers/infrastructure/metadata.provider.dart';
import 'package:immich_mobile/providers/infrastructure/settings.provider.dart';
import 'package:immich_mobile/services/api.service.dart';
import 'package:logging/logging.dart';
import 'package:native_video_player/native_video_player.dart';
@@ -128,7 +128,7 @@ class _NativeVideoViewerState extends ConsumerState<NativeVideoViewer> with Widg
final remoteId = (videoAsset as RemoteAsset).id;
final serverEndpoint = Store.get(StoreKey.serverEndpoint);
final isOriginalVideo = ref.read(metadataProvider).appConfig.viewer.loadOriginalVideo;
final isOriginalVideo = ref.read(appConfigProvider).viewer.loadOriginalVideo;
final String postfixUrl = isOriginalVideo ? 'original' : 'video/playback';
final String videoUrl = videoAsset.livePhotoVideoId != null
? '$serverEndpoint/assets/${videoAsset.livePhotoVideoId}/$postfixUrl'
@@ -161,7 +161,7 @@ class _NativeVideoViewerState extends ConsumerState<NativeVideoViewer> with Widg
return;
}
final autoPlayVideo = ref.read(metadataProvider).appConfig.viewer.autoPlayVideo;
final autoPlayVideo = ref.read(appConfigProvider).viewer.autoPlayVideo;
if (autoPlayVideo || widget.asset.isMotionPhoto) {
await _notifier.play();
}
@@ -212,7 +212,7 @@ class _NativeVideoViewerState extends ConsumerState<NativeVideoViewer> with Widg
}
await _notifier.load(source);
final loopVideo = ref.read(metadataProvider).appConfig.viewer.loopVideo;
final loopVideo = ref.read(appConfigProvider).viewer.loopVideo;
await _notifier.setLoop(!widget.asset.isMotionPhoto && loopVideo);
await _notifier.setVolume(1);
}
@@ -2,7 +2,6 @@ import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/constants/enums.dart';
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
import 'package:immich_mobile/domain/models/setting.model.dart';
import 'package:immich_mobile/extensions/build_context_extensions.dart';
import 'package:immich_mobile/providers/asset_viewer/asset_viewer.provider.dart';
import 'package:immich_mobile/providers/cast.provider.dart';
@@ -34,7 +33,7 @@ class ViewerKebabMenu extends ConsumerWidget {
final isInLockedView = ref.watch(inLockedViewProvider);
final currentAlbum = ref.watch(currentRemoteAlbumProvider);
final isArchived = asset is RemoteAsset && asset.visibility == AssetVisibility.archive;
final advancedTroubleshooting = ref.watch(settingsProvider.notifier).get(Setting.advancedTroubleshooting);
final advancedTroubleshooting = ref.watch(settingsProvider.notifier).get(.advancedTroubleshooting);
final actionContext = ActionButtonContext(
asset: asset,
@@ -1,10 +1,9 @@
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/domain/models/metadata_key.dart';
import 'package:immich_mobile/extensions/build_context_extensions.dart';
import 'package:immich_mobile/extensions/translate_extensions.dart';
import 'package:immich_mobile/providers/backup/drift_backup.provider.dart';
import 'package:immich_mobile/providers/infrastructure/metadata.provider.dart';
import 'package:immich_mobile/providers/infrastructure/settings.provider.dart';
class BackupToggleButton extends ConsumerStatefulWidget {
final VoidCallback onStart;
@@ -31,7 +30,7 @@ class BackupToggleButtonState extends ConsumerState<BackupToggleButton> with Sin
end: 1,
).animate(CurvedAnimation(parent: _animationController, curve: Curves.easeInOut));
_isEnabled = ref.read(metadataProvider).appConfig.backup.enabled;
_isEnabled = ref.read(appConfigProvider).backup.enabled;
}
@override
@@ -41,7 +40,7 @@ class BackupToggleButtonState extends ConsumerState<BackupToggleButton> with Sin
}
Future<void> _onToggle(bool value) async {
await ref.read(metadataProvider).write(MetadataKey.backupEnabled, value);
await ref.read(settingsProvider).write(.backupEnabled, value);
setState(() {
_isEnabled = value;
@@ -4,7 +4,7 @@ import 'package:async/async.dart';
import 'package:flutter/widgets.dart';
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
import 'package:immich_mobile/infrastructure/loaders/image_request.dart';
import 'package:immich_mobile/infrastructure/repositories/metadata.repository.dart';
import 'package:immich_mobile/infrastructure/repositories/settings.repository.dart';
import 'package:immich_mobile/presentation/widgets/images/local_image_provider.dart';
import 'package:immich_mobile/presentation/widgets/images/remote_image_provider.dart';
import 'package:immich_mobile/presentation/widgets/timeline/constants.dart';
@@ -189,5 +189,5 @@ ImageProvider? getThumbnailImageProvider(BaseAsset asset, {Size size = kThumbnai
bool _shouldUseLocalAsset(BaseAsset asset) =>
asset.hasLocal &&
(!asset.hasRemote || !MetadataRepository.instance.appConfig.image.preferRemote) &&
(!asset.hasRemote || !SettingsRepository.instance.appConfig.image.preferRemote) &&
!asset.isEdited;
@@ -2,7 +2,7 @@ import 'package:flutter/foundation.dart';
import 'package:flutter/widgets.dart';
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
import 'package:immich_mobile/infrastructure/loaders/image_request.dart';
import 'package:immich_mobile/infrastructure/repositories/metadata.repository.dart';
import 'package:immich_mobile/infrastructure/repositories/settings.repository.dart';
import 'package:immich_mobile/presentation/widgets/images/animated_image_stream_completer.dart';
import 'package:immich_mobile/presentation/widgets/images/image_provider.dart';
import 'package:immich_mobile/presentation/widgets/images/one_frame_multi_image_stream_completer.dart';
@@ -104,7 +104,7 @@ class LocalFullImageProvider extends CancellableImageProvider<LocalFullImageProv
return;
}
final loadOriginal = MetadataRepository.instance.appConfig.image.loadOriginal;
final loadOriginal = SettingsRepository.instance.appConfig.image.loadOriginal;
final devicePixelRatio = PlatformDispatcher.instance.views.first.devicePixelRatio;
var request = this.request = LocalImageRequest(
localId: key.id,
@@ -2,7 +2,7 @@ import 'package:flutter/foundation.dart';
import 'package:flutter/painting.dart';
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
import 'package:immich_mobile/infrastructure/loaders/image_request.dart';
import 'package:immich_mobile/infrastructure/repositories/metadata.repository.dart';
import 'package:immich_mobile/infrastructure/repositories/settings.repository.dart';
import 'package:immich_mobile/presentation/widgets/images/animated_image_stream_completer.dart';
import 'package:immich_mobile/presentation/widgets/images/image_provider.dart';
import 'package:immich_mobile/presentation/widgets/images/one_frame_multi_image_stream_completer.dart';
@@ -122,7 +122,7 @@ class RemoteFullImageProvider extends CancellableImageProvider<RemoteFullImagePr
edited: key.edited,
),
);
final loadOriginal = assetType == AssetType.image && MetadataRepository.instance.appConfig.image.loadOriginal;
final loadOriginal = assetType == AssetType.image && SettingsRepository.instance.appConfig.image.loadOriginal;
yield* loadRequest(previewRequest, decode, isFinal: !loadOriginal);
if (!loadOriginal) {
@@ -9,7 +9,7 @@ import 'package:immich_mobile/presentation/widgets/images/thumbnail.widget.dart'
import 'package:immich_mobile/presentation/widgets/timeline/constants.dart';
import 'package:immich_mobile/providers/asset_viewer/asset_viewer.provider.dart';
import 'package:immich_mobile/providers/backup/asset_upload_progress.provider.dart';
import 'package:immich_mobile/providers/infrastructure/metadata.provider.dart';
import 'package:immich_mobile/providers/infrastructure/settings.provider.dart';
import 'package:immich_mobile/providers/timeline/multiselect.provider.dart';
class ThumbnailTile extends ConsumerStatefulWidget {
@@ -1,11 +1,10 @@
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/domain/models/events.model.dart';
import 'package:immich_mobile/domain/models/metadata_key.dart';
import 'package:immich_mobile/domain/utils/event_stream.dart';
import 'package:immich_mobile/infrastructure/repositories/timeline.repository.dart';
import 'package:immich_mobile/providers/infrastructure/map.provider.dart';
import 'package:immich_mobile/providers/infrastructure/metadata.provider.dart';
import 'package:immich_mobile/providers/infrastructure/settings.provider.dart';
import 'package:immich_mobile/providers/map/map_state.provider.dart';
import 'package:maplibre_gl/maplibre_gl.dart';
@@ -81,25 +80,25 @@ class MapStateNotifier extends Notifier<MapState> {
}
void switchFavoriteOnly(bool isFavoriteOnly) {
ref.read(metadataProvider).write(MetadataKey.mapShowFavoriteOnly, isFavoriteOnly);
ref.read(settingsProvider).write(.mapShowFavoriteOnly, isFavoriteOnly);
state = state.copyWith(onlyFavorites: isFavoriteOnly);
EventStream.shared.emit(const MapMarkerReloadEvent());
}
void switchIncludeArchived(bool isIncludeArchived) {
ref.read(metadataProvider).write(MetadataKey.mapIncludeArchived, isIncludeArchived);
ref.read(settingsProvider).write(.mapIncludeArchived, isIncludeArchived);
state = state.copyWith(includeArchived: isIncludeArchived);
EventStream.shared.emit(const MapMarkerReloadEvent());
}
void switchWithPartners(bool isWithPartners) {
ref.read(metadataProvider).write(MetadataKey.mapWithPartners, isWithPartners);
ref.read(settingsProvider).write(.mapWithPartners, isWithPartners);
state = state.copyWith(withPartners: isWithPartners);
EventStream.shared.emit(const MapMarkerReloadEvent());
}
void setRelativeTime(int relativeDays) {
ref.read(metadataProvider).write(MetadataKey.mapRelativeDate, relativeDays);
ref.read(settingsProvider).write(.mapRelativeDate, relativeDays);
state = state.copyWith(relativeDays: relativeDays);
EventStream.shared.emit(const MapMarkerReloadEvent());
}
@@ -5,7 +5,7 @@ import 'package:immich_mobile/domain/models/timeline.model.dart';
import 'package:immich_mobile/presentation/widgets/timeline/constants.dart';
import 'package:immich_mobile/presentation/widgets/timeline/fixed/segment_builder.dart';
import 'package:immich_mobile/presentation/widgets/timeline/segment.model.dart';
import 'package:immich_mobile/providers/infrastructure/metadata.provider.dart';
import 'package:immich_mobile/providers/infrastructure/settings.provider.dart';
import 'package:immich_mobile/providers/infrastructure/timeline.provider.dart';
class TimelineArgs {
@@ -10,7 +10,6 @@ import 'package:flutter/rendering.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
import 'package:immich_mobile/domain/models/events.model.dart';
import 'package:immich_mobile/domain/models/metadata_key.dart';
import 'package:immich_mobile/domain/models/timeline.model.dart';
import 'package:immich_mobile/domain/utils/event_stream.dart';
import 'package:immich_mobile/extensions/asyncvalue_extensions.dart';
@@ -22,7 +21,7 @@ import 'package:immich_mobile/presentation/widgets/timeline/scrubber.widget.dart
import 'package:immich_mobile/presentation/widgets/timeline/segment.model.dart';
import 'package:immich_mobile/presentation/widgets/timeline/timeline.state.dart';
import 'package:immich_mobile/presentation/widgets/timeline/timeline_drag_region.dart';
import 'package:immich_mobile/providers/infrastructure/metadata.provider.dart';
import 'package:immich_mobile/providers/infrastructure/settings.provider.dart';
import 'package:immich_mobile/providers/infrastructure/readonly_mode.provider.dart';
import 'package:immich_mobile/providers/infrastructure/timeline.provider.dart';
import 'package:immich_mobile/providers/timeline/multiselect.provider.dart';
@@ -459,7 +458,7 @@ class _SliverTimelineState extends ConsumerState<_SliverTimeline> {
_restoreAssetIndex = targetAssetIndex;
});
ref.read(metadataProvider).write(MetadataKey.timelineTilesPerRow, _perRow);
ref.read(settingsProvider).write(.timelineTilesPerRow, _perRow);
}
};
},
@@ -9,8 +9,8 @@ import 'package:immich_mobile/providers/auth.provider.dart';
import 'package:immich_mobile/providers/background_sync.provider.dart';
import 'package:immich_mobile/providers/backup/drift_backup.provider.dart';
import 'package:immich_mobile/providers/gallery_permission.provider.dart';
import 'package:immich_mobile/providers/infrastructure/metadata.provider.dart';
import 'package:immich_mobile/providers/infrastructure/platform.provider.dart';
import 'package:immich_mobile/providers/infrastructure/settings.provider.dart';
import 'package:immich_mobile/providers/notification_permission.provider.dart';
import 'package:immich_mobile/providers/server_info.provider.dart';
import 'package:immich_mobile/providers/websocket.provider.dart';
@@ -107,7 +107,7 @@ class AppLifeCycleNotifier extends StateNotifier<AppLifeCycleEnum> {
await Future.delayed(const Duration(milliseconds: 500));
final backgroundManager = _ref.read(backgroundSyncProvider);
final isAlbumLinkedSyncEnable = _ref.read(metadataProvider).appConfig.backup.syncAlbums;
final isAlbumLinkedSyncEnable = _ref.read(appConfigProvider).backup.syncAlbums;
try {
bool syncSuccess = false;
@@ -137,7 +137,7 @@ class AppLifeCycleNotifier extends StateNotifier<AppLifeCycleEnum> {
}
Future<void> _resumeBackup() async {
final isEnableBackup = _ref.read(metadataProvider).appConfig.backup.enabled;
final isEnableBackup = _ref.read(appConfigProvider).backup.enabled;
if (isEnableBackup) {
final currentUser = Store.tryGet(StoreKey.currentUser);
+6 -7
View File
@@ -3,7 +3,6 @@ import 'dart:convert';
import 'package:flutter_udid/flutter_udid.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/constants/constants.dart';
import 'package:immich_mobile/domain/models/metadata_key.dart';
import 'package:immich_mobile/domain/models/store.model.dart';
import 'package:immich_mobile/domain/models/user.model.dart';
import 'package:immich_mobile/domain/services/user.service.dart';
@@ -11,7 +10,7 @@ import 'package:immich_mobile/entities/store.entity.dart';
import 'package:immich_mobile/models/auth/auth_state.model.dart';
import 'package:immich_mobile/models/auth/login_response.model.dart';
import 'package:immich_mobile/providers/api.provider.dart';
import 'package:immich_mobile/providers/infrastructure/metadata.provider.dart';
import 'package:immich_mobile/providers/infrastructure/settings.provider.dart';
import 'package:immich_mobile/providers/infrastructure/user.provider.dart';
import 'package:immich_mobile/services/api.service.dart';
import 'package:immich_mobile/services/auth.service.dart';
@@ -130,7 +129,7 @@ class AuthNotifier extends StateNotifier<AuthState> {
await _apiService.updateHeaders();
final serverEndpoint = Store.get(StoreKey.serverEndpoint);
final headerMap = _ref.read(metadataProvider).appConfig.network.customHeaders;
final headerMap = _ref.read(appConfigProvider).network.customHeaders;
final customHeaders = headerMap.isEmpty ? null : jsonEncode(headerMap);
await _widgetService.writeCredentials(serverEndpoint, accessToken, customHeaders);
@@ -179,19 +178,19 @@ class AuthNotifier extends StateNotifier<AuthState> {
}
Future<void> saveWifiName(String wifiName) async {
await _ref.read(metadataProvider).write(MetadataKey.networkPreferredWifiName, wifiName);
await _ref.read(settingsProvider).write(.networkPreferredWifiName, wifiName);
}
Future<void> saveLocalEndpoint(String url) async {
await _ref.read(metadataProvider).write(MetadataKey.networkLocalEndpoint, url);
await _ref.read(settingsProvider).write(.networkLocalEndpoint, url);
}
String? getSavedWifiName() {
return _ref.read(metadataProvider).appConfig.network.preferredWifiName;
return _ref.read(appConfigProvider).network.preferredWifiName;
}
String? getSavedLocalEndpoint() {
return _ref.read(metadataProvider).appConfig.network.localEndpoint;
return _ref.read(appConfigProvider).network.localEndpoint;
}
/// Returns the current server endpoint (with /api) URL from the store
+12 -12
View File
@@ -1,8 +1,8 @@
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/constants/enums.dart';
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
import 'package:immich_mobile/infrastructure/repositories/metadata.repository.dart';
import 'package:immich_mobile/providers/infrastructure/metadata.provider.dart';
import 'package:immich_mobile/infrastructure/repositories/settings.repository.dart';
import 'package:immich_mobile/providers/infrastructure/settings.provider.dart';
import 'package:immich_mobile/providers/user.provider.dart';
import 'package:immich_mobile/services/cleanup.service.dart';
@@ -54,21 +54,21 @@ final cleanupProvider = StateNotifierProvider<CleanupNotifier, CleanupState>((re
return CleanupNotifier(
ref.watch(cleanupServiceProvider),
ref.watch(currentUserProvider)?.id,
ref.watch(metadataProvider),
ref.watch(settingsProvider),
);
});
class CleanupNotifier extends StateNotifier<CleanupState> {
final CleanupService _cleanupService;
final String? _userId;
final MetadataRepository _metadataRepository;
final SettingsRepository _settingsRepository;
CleanupNotifier(this._cleanupService, this._userId, this._metadataRepository) : super(const CleanupState()) {
CleanupNotifier(this._cleanupService, this._userId, this._settingsRepository) : super(const CleanupState()) {
_loadPersistedSettings();
}
void _loadPersistedSettings() {
final cleanup = _metadataRepository.appConfig.cleanup;
final cleanup = _settingsRepository.appConfig.cleanup;
final keepFavorites = cleanup.keepFavorites;
final keepMediaType = cleanup.keepMediaType;
final keepAlbumIds = cleanup.keepAlbumIds.toSet();
@@ -87,18 +87,18 @@ class CleanupNotifier extends StateNotifier<CleanupState> {
state = state.copyWith(selectedDate: date, assetsToDelete: []);
if (date != null) {
final daysAgo = DateTime.now().difference(date).inDays;
_metadataRepository.write(.cleanupCutoffDaysAgo, daysAgo);
_settingsRepository.write(.cleanupCutoffDaysAgo, daysAgo);
}
}
void setKeepMediaType(AssetKeepType keepMediaType) {
state = state.copyWith(keepMediaType: keepMediaType, assetsToDelete: []);
_metadataRepository.write(.cleanupKeepMediaType, keepMediaType);
_settingsRepository.write(.cleanupKeepMediaType, keepMediaType);
}
void setKeepFavorites(bool keepFavorites) {
state = state.copyWith(keepFavorites: keepFavorites, assetsToDelete: []);
_metadataRepository.write(.cleanupKeepFavorites, keepFavorites);
_settingsRepository.write(.cleanupKeepFavorites, keepFavorites);
}
void toggleKeepAlbum(String albumId) {
@@ -118,7 +118,7 @@ class CleanupNotifier extends StateNotifier<CleanupState> {
}
void _persistExcludedAlbumIds(Set<String> albumIds) {
_metadataRepository.write(.cleanupKeepAlbumIds, albumIds.toList());
_settingsRepository.write(.cleanupKeepAlbumIds, albumIds.toList());
}
void cleanupStaleAlbumIds(Set<String> existingAlbumIds) {
@@ -131,7 +131,7 @@ class CleanupNotifier extends StateNotifier<CleanupState> {
}
void applyDefaultAlbumSelections(List<(String id, String name)> albums) {
final isInitialized = _metadataRepository.appConfig.cleanup.defaultsInitialized;
final isInitialized = _settingsRepository.appConfig.cleanup.defaultsInitialized;
if (isInitialized) {
return;
}
@@ -144,7 +144,7 @@ class CleanupNotifier extends StateNotifier<CleanupState> {
_persistExcludedAlbumIds(keepAlbumIds);
}
_metadataRepository.write(.cleanupDefaultsInitialized, true);
_settingsRepository.write(.cleanupDefaultsInitialized, true);
}
Future<void> scanAssets() async {
@@ -1,11 +1,11 @@
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/domain/models/config/app_config.dart';
import 'package:immich_mobile/infrastructure/repositories/metadata.repository.dart';
import 'package:immich_mobile/infrastructure/repositories/settings.repository.dart';
final metadataProvider = Provider.autoDispose<MetadataRepository>((_) => MetadataRepository.instance);
final settingsProvider = Provider.autoDispose<SettingsRepository>((_) => SettingsRepository.instance);
final appConfigProvider = Provider.autoDispose<AppConfig>((ref) {
final repo = ref.watch(metadataProvider);
final repo = ref.watch(settingsProvider);
final subscription = repo.watchConfig().listen((event) => ref.state = event);
ref.onDispose(subscription.cancel);
return repo.appConfig;
@@ -3,7 +3,7 @@ import 'package:immich_mobile/domain/services/timeline.service.dart';
import 'package:immich_mobile/infrastructure/repositories/timeline.repository.dart';
import 'package:immich_mobile/presentation/widgets/timeline/timeline.state.dart';
import 'package:immich_mobile/providers/infrastructure/db.provider.dart';
import 'package:immich_mobile/providers/infrastructure/metadata.provider.dart';
import 'package:immich_mobile/providers/infrastructure/settings.provider.dart';
import 'package:immich_mobile/providers/user.provider.dart';
final timelineRepositoryProvider = Provider<DriftTimelineRepository>(
@@ -29,7 +29,7 @@ final timelineServiceProvider = Provider<TimelineService>(
final timelineFactoryProvider = Provider<TimelineFactory>(
(ref) => TimelineFactory(
timelineRepository: ref.watch(timelineRepositoryProvider),
metadataRepository: ref.watch(metadataProvider),
settingsRepository: ref.watch(settingsProvider),
),
);
@@ -1,8 +1,7 @@
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/domain/models/metadata_key.dart';
import 'package:immich_mobile/models/map/map_state.model.dart';
import 'package:immich_mobile/providers/infrastructure/metadata.provider.dart';
import 'package:immich_mobile/providers/infrastructure/settings.provider.dart';
import 'package:immich_mobile/providers/server_info.provider.dart';
final mapStateNotifierProvider = NotifierProvider<MapStateNotifier, MapState>(MapStateNotifier.new);
@@ -27,12 +26,12 @@ class MapStateNotifier extends Notifier<MapState> {
}
void switchTheme(ThemeMode mode) {
ref.read(metadataProvider).write(MetadataKey.mapThemeMode, mode);
ref.read(settingsProvider).write(.mapThemeMode, mode);
state = state.copyWith(themeMode: mode);
}
void switchFavoriteOnly(bool isFavoriteOnly) {
ref.read(metadataProvider).write(MetadataKey.mapShowFavoriteOnly, isFavoriteOnly);
ref.read(settingsProvider).write(.mapShowFavoriteOnly, isFavoriteOnly);
state = state.copyWith(showFavoriteOnly: isFavoriteOnly, shouldRefetchMarkers: true);
}
@@ -41,17 +40,17 @@ class MapStateNotifier extends Notifier<MapState> {
}
void switchIncludeArchived(bool isIncludeArchived) {
ref.read(metadataProvider).write(MetadataKey.mapIncludeArchived, isIncludeArchived);
ref.read(settingsProvider).write(.mapIncludeArchived, isIncludeArchived);
state = state.copyWith(includeArchived: isIncludeArchived, shouldRefetchMarkers: true);
}
void switchWithPartners(bool isWithPartners) {
ref.read(metadataProvider).write(MetadataKey.mapWithPartners, isWithPartners);
ref.read(settingsProvider).write(.mapWithPartners, isWithPartners);
state = state.copyWith(withPartners: isWithPartners, shouldRefetchMarkers: true);
}
void setRelativeTime(int relativeTime) {
ref.read(metadataProvider).write(MetadataKey.mapRelativeDate, relativeTime);
ref.read(settingsProvider).write(.mapRelativeDate, relativeTime);
state = state.copyWith(relativeTime: relativeTime, shouldRefetchMarkers: true);
}
}
+1 -1
View File
@@ -1,5 +1,5 @@
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/providers/infrastructure/metadata.provider.dart';
import 'package:immich_mobile/providers/infrastructure/settings.provider.dart';
import 'package:immich_mobile/theme/color_scheme.dart';
import 'package:immich_mobile/theme/dynamic_theme.dart';
import 'package:immich_mobile/theme/theme_data.dart';
+3 -3
View File
@@ -7,7 +7,7 @@ import 'package:immich_mobile/infrastructure/repositories/network.repository.dar
import 'package:immich_mobile/models/server_info/server_version.model.dart';
import 'package:immich_mobile/providers/auth.provider.dart';
import 'package:immich_mobile/providers/background_sync.provider.dart';
import 'package:immich_mobile/providers/infrastructure/metadata.provider.dart';
import 'package:immich_mobile/providers/infrastructure/settings.provider.dart';
import 'package:immich_mobile/providers/server_info.provider.dart';
import 'package:immich_mobile/utils/debounce.dart';
import 'package:immich_mobile/utils/debug_print.dart';
@@ -193,7 +193,7 @@ class WebsocketNotifier extends StateNotifier<WebsocketState> {
return;
}
final isSyncAlbumEnabled = _ref.read(metadataProvider).appConfig.backup.syncAlbums;
final isSyncAlbumEnabled = _ref.read(appConfigProvider).backup.syncAlbums;
try {
unawaited(
_ref.read(backgroundSyncProvider).syncWebsocketBatchV1(_batchedAssetUploadReady.toList()).then((_) {
@@ -214,7 +214,7 @@ class WebsocketNotifier extends StateNotifier<WebsocketState> {
return;
}
final isSyncAlbumEnabled = _ref.read(metadataProvider).appConfig.backup.syncAlbums;
final isSyncAlbumEnabled = _ref.read(appConfigProvider).backup.syncAlbums;
try {
unawaited(
_ref.read(backgroundSyncProvider).syncWebsocketBatchV2(_batchedAssetUploadReady.toList()).then((_) {
+1 -1
View File
@@ -4,7 +4,7 @@ import 'package:immich_mobile/infrastructure/repositories/db.repository.dart';
import 'package:immich_mobile/infrastructure/repositories/sync_stream.repository.dart';
import 'package:immich_mobile/models/auth/auxilary_endpoint.model.dart';
import 'package:immich_mobile/providers/infrastructure/db.provider.dart';
import 'package:immich_mobile/providers/infrastructure/metadata.provider.dart';
import 'package:immich_mobile/providers/infrastructure/settings.provider.dart';
final authRepositoryProvider = Provider<AuthRepository>(
(ref) => AuthRepository(ref.watch(driftProvider), ref.watch(appConfigProvider)),
+3 -3
View File
@@ -5,7 +5,7 @@ import 'dart:io';
import 'package:device_info_plus/device_info_plus.dart';
import 'package:immich_mobile/domain/models/store.model.dart';
import 'package:immich_mobile/entities/store.entity.dart';
import 'package:immich_mobile/infrastructure/repositories/metadata.repository.dart';
import 'package:immich_mobile/infrastructure/repositories/settings.repository.dart';
import 'package:immich_mobile/infrastructure/repositories/network.repository.dart';
import 'package:immich_mobile/utils/debug_print.dart';
import 'package:immich_mobile/utils/url_helper.dart';
@@ -177,7 +177,7 @@ class ApiService {
if (serverEndpoint != null && serverEndpoint.isNotEmpty) {
urls.add(serverEndpoint);
}
final network = MetadataRepository.instance.appConfig.network;
final network = SettingsRepository.instance.appConfig.network;
final localEndpoint = network.localEndpoint;
if (localEndpoint.isNotEmpty) {
urls.add(localEndpoint);
@@ -191,7 +191,7 @@ class ApiService {
}
static Map<String, String> getRequestHeaders() {
return MetadataRepository.instance.appConfig.network.customHeaders;
return SettingsRepository.instance.appConfig.network.customHeaders;
}
ApiClient get apiClient => _apiClient;
+4 -4
View File
@@ -1,11 +1,11 @@
import 'dart:async';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/domain/models/metadata_key.dart';
import 'package:immich_mobile/domain/models/settings_key.dart';
import 'package:immich_mobile/domain/models/store.model.dart';
import 'package:immich_mobile/domain/utils/background_sync.dart';
import 'package:immich_mobile/entities/store.entity.dart';
import 'package:immich_mobile/infrastructure/repositories/metadata.repository.dart';
import 'package:immich_mobile/infrastructure/repositories/settings.repository.dart';
import 'package:immich_mobile/infrastructure/repositories/network.repository.dart';
import 'package:immich_mobile/models/auth/auxilary_endpoint.model.dart';
import 'package:immich_mobile/models/auth/login_response.model.dart';
@@ -100,7 +100,7 @@ class AuthService {
_log.severe("Error clearing local data", error, stackTrace);
});
await MetadataRepository.instance.write(MetadataKey.backupEnabled, false);
await SettingsRepository.instance.write(SettingsKey.backupEnabled, false);
}
}
@@ -120,7 +120,7 @@ class AuthService {
_authRepository.clearLocalData(),
Store.delete(StoreKey.currentUser),
Store.delete(StoreKey.accessToken),
MetadataRepository.instance.clear(const [
SettingsRepository.instance.clear(const [
.networkAutoEndpointSwitching,
.networkPreferredWifiName,
.networkLocalEndpoint,
@@ -13,7 +13,7 @@ import 'package:immich_mobile/entities/store.entity.dart';
import 'package:immich_mobile/extensions/platform_extensions.dart';
import 'package:immich_mobile/infrastructure/repositories/backup.repository.dart';
import 'package:immich_mobile/infrastructure/repositories/local_asset.repository.dart';
import 'package:immich_mobile/infrastructure/repositories/metadata.repository.dart';
import 'package:immich_mobile/infrastructure/repositories/settings.repository.dart';
import 'package:immich_mobile/infrastructure/repositories/storage.repository.dart';
import 'package:immich_mobile/providers/infrastructure/asset.provider.dart';
import 'package:immich_mobile/providers/infrastructure/storage.provider.dart';
@@ -359,7 +359,7 @@ class BackgroundUploadService {
}
bool _shouldRequireWiFi(LocalAsset asset) {
final backup = MetadataRepository.instance.appConfig.backup;
final backup = SettingsRepository.instance.appConfig.backup;
if (asset.isVideo && backup.useCellularForVideos) {
return false;
}
@@ -11,7 +11,7 @@ import 'package:immich_mobile/extensions/network_capability_extensions.dart';
import 'package:immich_mobile/extensions/platform_extensions.dart';
import 'package:immich_mobile/extensions/translate_extensions.dart';
import 'package:immich_mobile/infrastructure/repositories/backup.repository.dart';
import 'package:immich_mobile/infrastructure/repositories/metadata.repository.dart';
import 'package:immich_mobile/infrastructure/repositories/settings.repository.dart';
import 'package:immich_mobile/infrastructure/repositories/storage.repository.dart';
import 'package:immich_mobile/platform/connectivity_api.g.dart';
import 'package:immich_mobile/providers/infrastructure/platform.provider.dart';
@@ -451,7 +451,7 @@ class ForegroundUploadService {
}
bool _shouldRequireWiFi(LocalAsset asset) {
final backup = MetadataRepository.instance.appConfig.backup;
final backup = SettingsRepository.instance.appConfig.backup;
if (asset.isVideo && backup.useCellularForVideos) {
return false;
}
+3 -3
View File
@@ -6,7 +6,7 @@ import 'package:immich_mobile/extensions/translate_extensions.dart';
import 'package:immich_mobile/infrastructure/repositories/db.repository.dart';
import 'package:immich_mobile/infrastructure/repositories/log.repository.dart';
import 'package:immich_mobile/infrastructure/repositories/logger_db.repository.dart';
import 'package:immich_mobile/infrastructure/repositories/metadata.repository.dart';
import 'package:immich_mobile/infrastructure/repositories/settings.repository.dart';
import 'package:immich_mobile/infrastructure/repositories/network.repository.dart';
import 'package:immich_mobile/infrastructure/repositories/store.repository.dart';
import 'package:photo_manager/photo_manager.dart';
@@ -49,11 +49,11 @@ abstract final class Bootstrap {
await StoreService.init(storeRepository: storeRepo, listenUpdates: listenStoreUpdates);
final metadataRepo = await MetadataRepository.ensureInitialized(drift);
final settingsRepo = await SettingsRepository.ensureInitialized(drift);
await LogService.init(
logRepository: LogRepository(logDb),
metadataRepository: metadataRepo,
settingsRepository: settingsRepo,
shouldBuffer: shouldBufferLogs,
);
+49 -49
View File
@@ -8,11 +8,11 @@ import 'package:immich_mobile/constants/colors.dart';
import 'package:immich_mobile/constants/enums.dart';
import 'package:immich_mobile/domain/models/config/app_config.dart';
import 'package:immich_mobile/domain/models/log.model.dart';
import 'package:immich_mobile/domain/models/metadata_key.dart';
import 'package:immich_mobile/domain/models/settings_key.dart';
import 'package:immich_mobile/domain/models/store.model.dart';
import 'package:immich_mobile/domain/models/timeline.model.dart';
import 'package:immich_mobile/entities/store.entity.dart';
import 'package:immich_mobile/infrastructure/entities/metadata.entity.drift.dart';
import 'package:immich_mobile/infrastructure/entities/settings.entity.drift.dart';
import 'package:immich_mobile/infrastructure/repositories/db.repository.dart';
import 'package:immich_mobile/infrastructure/repositories/network.repository.dart';
import 'package:immich_mobile/models/auth/auxilary_endpoint.model.dart';
@@ -74,65 +74,65 @@ Future<void> _migrateTo25() async {
Future<void> _migrateTo26(Drift drift) async {
final migrator = _StoreMigrator(drift);
await migrator.migrateEnumIndex(StoreKey.legacyLogLevel, MetadataKey.logLevel, LogLevel.values);
await migrator.migrateEnumIndex(StoreKey.legacyLogLevel, SettingsKey.logLevel, LogLevel.values);
// Theme
await migrator.migrateEnumName(StoreKey.legacyThemeMode, MetadataKey.themeMode, ThemeMode.values);
await migrator.migrateEnumName(StoreKey.legacyPrimaryColor, MetadataKey.themePrimaryColor, ImmichColorPreset.values);
await migrator.migrateBool(StoreKey.legacyDynamicTheme, MetadataKey.themeDynamic);
await migrator.migrateBool(StoreKey.legacyColorfulInterface, MetadataKey.themeColorfulInterface);
await migrator.migrateEnumName(StoreKey.legacyThemeMode, SettingsKey.themeMode, ThemeMode.values);
await migrator.migrateEnumName(StoreKey.legacyPrimaryColor, SettingsKey.themePrimaryColor, ImmichColorPreset.values);
await migrator.migrateBool(StoreKey.legacyDynamicTheme, SettingsKey.themeDynamic);
await migrator.migrateBool(StoreKey.legacyColorfulInterface, SettingsKey.themeColorfulInterface);
// Cleanup
final cleanupKeepAlbumIds = await migrator.readLegacyStoreString(StoreKey.legacyCleanupKeepAlbumIds.id);
if (cleanupKeepAlbumIds != null) {
final ids = cleanupKeepAlbumIds.split(',').where((id) => id.isNotEmpty).toList();
migrator.stage(StoreKey.legacyCleanupKeepAlbumIds, MetadataKey.cleanupKeepAlbumIds, ids);
migrator.stage(StoreKey.legacyCleanupKeepAlbumIds, SettingsKey.cleanupKeepAlbumIds, ids);
}
await migrator.migrateBool(StoreKey.legacyCleanupKeepFavorites, MetadataKey.cleanupKeepFavorites);
await migrator.migrateBool(StoreKey.legacyCleanupKeepFavorites, SettingsKey.cleanupKeepFavorites);
await migrator.migrateEnumIndex(
StoreKey.legacyCleanupKeepMediaType,
MetadataKey.cleanupKeepMediaType,
SettingsKey.cleanupKeepMediaType,
AssetKeepType.values,
);
await migrator.migrateInt(StoreKey.legacyCleanupCutoffDaysAgo, MetadataKey.cleanupCutoffDaysAgo);
await migrator.migrateBool(StoreKey.legacyCleanupDefaultsInitialized, MetadataKey.cleanupDefaultsInitialized);
await migrator.migrateInt(StoreKey.legacyCleanupCutoffDaysAgo, SettingsKey.cleanupCutoffDaysAgo);
await migrator.migrateBool(StoreKey.legacyCleanupDefaultsInitialized, SettingsKey.cleanupDefaultsInitialized);
// Map
await migrator.migrateBool(StoreKey.legacyMapShowFavoriteOnly, MetadataKey.mapShowFavoriteOnly);
await migrator.migrateInt(StoreKey.legacyMapRelativeDate, MetadataKey.mapRelativeDate);
await migrator.migrateBool(StoreKey.legacyMapIncludeArchived, MetadataKey.mapIncludeArchived);
await migrator.migrateEnumIndex(StoreKey.legacyMapThemeMode, MetadataKey.mapThemeMode, ThemeMode.values);
await migrator.migrateBool(StoreKey.legacyMapwithPartners, MetadataKey.mapWithPartners);
await migrator.migrateBool(StoreKey.legacyMapShowFavoriteOnly, SettingsKey.mapShowFavoriteOnly);
await migrator.migrateInt(StoreKey.legacyMapRelativeDate, SettingsKey.mapRelativeDate);
await migrator.migrateBool(StoreKey.legacyMapIncludeArchived, SettingsKey.mapIncludeArchived);
await migrator.migrateEnumIndex(StoreKey.legacyMapThemeMode, SettingsKey.mapThemeMode, ThemeMode.values);
await migrator.migrateBool(StoreKey.legacyMapwithPartners, SettingsKey.mapWithPartners);
// Timeline
await migrator.migrateInt(StoreKey.legacyTilesPerRow, MetadataKey.timelineTilesPerRow);
await migrator.migrateInt(StoreKey.legacyTilesPerRow, SettingsKey.timelineTilesPerRow);
await migrator.migrateEnumIndex(
StoreKey.legacyGroupAssetsBy,
MetadataKey.timelineGroupAssetsBy,
SettingsKey.timelineGroupAssetsBy,
GroupAssetsBy.values,
);
await migrator.migrateBool(StoreKey.legacyStorageIndicator, MetadataKey.timelineStorageIndicator);
await migrator.migrateBool(StoreKey.legacyStorageIndicator, SettingsKey.timelineStorageIndicator);
// Image
await migrator.migrateBool(StoreKey.legacyPreferRemoteImage, MetadataKey.imagePreferRemote);
await migrator.migrateBool(StoreKey.legacyLoadOriginal, MetadataKey.imageLoadOriginal);
await migrator.migrateBool(StoreKey.legacyPreferRemoteImage, SettingsKey.imagePreferRemote);
await migrator.migrateBool(StoreKey.legacyLoadOriginal, SettingsKey.imageLoadOriginal);
// Viewer
await migrator.migrateBool(StoreKey.legacyLoopVideo, MetadataKey.viewerLoopVideo);
await migrator.migrateBool(StoreKey.legacyLoadOriginalVideo, MetadataKey.viewerLoadOriginalVideo);
await migrator.migrateBool(StoreKey.legacyAutoPlayVideo, MetadataKey.viewerAutoPlayVideo);
await migrator.migrateBool(StoreKey.legacyTapToNavigate, MetadataKey.viewerTapToNavigate);
await migrator.migrateBool(StoreKey.legacyLoopVideo, SettingsKey.viewerLoopVideo);
await migrator.migrateBool(StoreKey.legacyLoadOriginalVideo, SettingsKey.viewerLoadOriginalVideo);
await migrator.migrateBool(StoreKey.legacyAutoPlayVideo, SettingsKey.viewerAutoPlayVideo);
await migrator.migrateBool(StoreKey.legacyTapToNavigate, SettingsKey.viewerTapToNavigate);
// Network
await migrator.migrateBool(StoreKey.legacyAutoEndpointSwitching, MetadataKey.networkAutoEndpointSwitching);
await migrator.migrateString(StoreKey.legacyPreferredWifiName, MetadataKey.networkPreferredWifiName);
await migrator.migrateString(StoreKey.legacyLocalEndpoint, MetadataKey.networkLocalEndpoint);
await migrator.migrateBool(StoreKey.legacyAutoEndpointSwitching, SettingsKey.networkAutoEndpointSwitching);
await migrator.migrateString(StoreKey.legacyPreferredWifiName, SettingsKey.networkPreferredWifiName);
await migrator.migrateString(StoreKey.legacyLocalEndpoint, SettingsKey.networkLocalEndpoint);
await _migrateExternalEndpointList(migrator);
await _migrateCustomHeaders(migrator);
// Album
await _migrateAlbumSortMode(migrator);
await migrator.migrateBool(StoreKey.legacySelectedAlbumSortReverse, MetadataKey.albumIsReverse);
await migrator.migrateBool(StoreKey.legacyAlbumGridView, MetadataKey.albumIsGrid);
await migrator.migrateBool(StoreKey.legacySelectedAlbumSortReverse, SettingsKey.albumIsReverse);
await migrator.migrateBool(StoreKey.legacyAlbumGridView, SettingsKey.albumIsGrid);
// Backup
await migrator.migrateBool(StoreKey.legacyEnableBackup, MetadataKey.backupEnabled);
await migrator.migrateBool(StoreKey.legacyUseWifiForUploadVideos, MetadataKey.backupUseCellularForVideos);
await migrator.migrateBool(StoreKey.legacyUseWifiForUploadPhotos, MetadataKey.backupUseCellularForPhotos);
await migrator.migrateBool(StoreKey.legacyBackupRequireCharging, MetadataKey.backupRequireCharging);
await migrator.migrateInt(StoreKey.legacyBackupTriggerDelay, MetadataKey.backupTriggerDelay);
await migrator.migrateBool(StoreKey.legacySyncAlbums, MetadataKey.backupSyncAlbums);
await migrator.migrateBool(StoreKey.legacyEnableBackup, SettingsKey.backupEnabled);
await migrator.migrateBool(StoreKey.legacyUseWifiForUploadVideos, SettingsKey.backupUseCellularForVideos);
await migrator.migrateBool(StoreKey.legacyUseWifiForUploadPhotos, SettingsKey.backupUseCellularForPhotos);
await migrator.migrateBool(StoreKey.legacyBackupRequireCharging, SettingsKey.backupRequireCharging);
await migrator.migrateInt(StoreKey.legacyBackupTriggerDelay, SettingsKey.backupTriggerDelay);
await migrator.migrateBool(StoreKey.legacySyncAlbums, SettingsKey.backupSyncAlbums);
await migrator.complete();
}
@@ -143,7 +143,7 @@ Future<void> _migrateAlbumSortMode(_StoreMigrator migrator) async {
return;
}
migrator.stage(StoreKey.legacySelectedAlbumSortOrder, MetadataKey.albumSortMode, mode);
migrator.stage(StoreKey.legacySelectedAlbumSortOrder, SettingsKey.albumSortMode, mode);
}
Future<void> _migrateExternalEndpointList(_StoreMigrator migrator) async {
@@ -167,7 +167,7 @@ Future<void> _migrateExternalEndpointList(_StoreMigrator migrator) async {
// ignore invalid entries
}
migrator.stage(StoreKey.legacyExternalEndpointList, MetadataKey.networkExternalEndpointList, urls);
migrator.stage(StoreKey.legacyExternalEndpointList, SettingsKey.networkExternalEndpointList, urls);
}
Future<void> _migrateCustomHeaders(_StoreMigrator migrator) async {
@@ -190,17 +190,17 @@ Future<void> _migrateCustomHeaders(_StoreMigrator migrator) async {
// ignore invalid entries
}
migrator.stage(StoreKey.legacyCustomHeaders, MetadataKey.networkCustomHeaders, headers);
migrator.stage(StoreKey.legacyCustomHeaders, SettingsKey.networkCustomHeaders, headers);
}
class _StoreMigrator {
final Drift _db;
final Map<MetadataKey<Object>, Object> _cache = {};
final Map<SettingsKey<Object>, Object> _cache = {};
final List<int> _migratedStoreIds = [];
_StoreMigrator(this._db);
Future<void> migrateEnumIndex<T extends Enum>(StoreKey<int> legacyKey, MetadataKey<T> newKey, List<T> values) async {
Future<void> migrateEnumIndex<T extends Enum>(StoreKey<int> legacyKey, SettingsKey<T> newKey, List<T> values) async {
final index = await readLegacyStoreInt(legacyKey.id);
if (index == null) {
return;
@@ -217,7 +217,7 @@ class _StoreMigrator {
Future<void> migrateEnumName<T extends Enum>(
StoreKey<String> legacyKey,
MetadataKey<T> newKey,
SettingsKey<T> newKey,
List<T> values,
) async {
final name = await readLegacyStoreString(legacyKey.id);
@@ -234,7 +234,7 @@ class _StoreMigrator {
_migratedStoreIds.add(legacyKey.id);
}
Future<void> migrateBool(StoreKey<bool> legacyKey, MetadataKey<bool> newKey) async {
Future<void> migrateBool(StoreKey<bool> legacyKey, SettingsKey<bool> newKey) async {
final intValue = await readLegacyStoreInt(legacyKey.id);
if (intValue == null) {
return;
@@ -245,7 +245,7 @@ class _StoreMigrator {
_migratedStoreIds.add(legacyKey.id);
}
Future<void> migrateInt(StoreKey<int> legacyKey, MetadataKey<int> newKey) async {
Future<void> migrateInt(StoreKey<int> legacyKey, SettingsKey<int> newKey) async {
final intValue = await readLegacyStoreInt(legacyKey.id);
if (intValue == null) {
return;
@@ -255,7 +255,7 @@ class _StoreMigrator {
_migratedStoreIds.add(legacyKey.id);
}
Future<void> migrateString(StoreKey<String> legacyKey, MetadataKey<String> newKey) async {
Future<void> migrateString(StoreKey<String> legacyKey, SettingsKey<String> newKey) async {
final value = await readLegacyStoreString(legacyKey.id);
if (value == null) {
return;
@@ -265,7 +265,7 @@ class _StoreMigrator {
_migratedStoreIds.add(legacyKey.id);
}
void stage<T extends Object>(StoreKey legacyKey, MetadataKey<T> newKey, T value) {
void stage<T extends Object>(StoreKey legacyKey, SettingsKey<T> newKey, T value) {
_cache[newKey] = value;
_migratedStoreIds.add(legacyKey.id);
}
@@ -277,8 +277,8 @@ class _StoreMigrator {
continue;
}
batch.insert(
_db.metadataEntity,
MetadataEntityCompanion(key: Value(entry.key.name), value: Value(entry.key.encode(entry.value))),
_db.settingsEntity,
SettingsEntityCompanion(key: Value(entry.key.name), value: Value(entry.key.encode(entry.value))),
mode: InsertMode.insertOrReplace,
);
}
@@ -10,7 +10,7 @@ import 'package:immich_mobile/extensions/build_context_extensions.dart';
import 'package:immich_mobile/models/server_info/server_info.model.dart';
import 'package:immich_mobile/providers/backup/drift_backup.provider.dart';
import 'package:immich_mobile/providers/cast.provider.dart';
import 'package:immich_mobile/providers/infrastructure/metadata.provider.dart';
import 'package:immich_mobile/providers/infrastructure/settings.provider.dart';
import 'package:immich_mobile/providers/infrastructure/readonly_mode.provider.dart';
import 'package:immich_mobile/providers/server_info.provider.dart';
import 'package:immich_mobile/providers/sync_status.provider.dart';
@@ -15,7 +15,7 @@ import 'package:immich_mobile/domain/models/store.model.dart';
import 'package:immich_mobile/entities/store.entity.dart';
import 'package:immich_mobile/extensions/build_context_extensions.dart';
import 'package:immich_mobile/extensions/translate_extensions.dart';
import 'package:immich_mobile/infrastructure/repositories/metadata.repository.dart';
import 'package:immich_mobile/infrastructure/repositories/settings.repository.dart';
import 'package:immich_mobile/providers/auth.provider.dart';
import 'package:immich_mobile/providers/background_sync.provider.dart';
import 'package:immich_mobile/providers/gallery_permission.provider.dart';
@@ -187,7 +187,7 @@ class LoginForm extends HookConsumerWidget {
await backgroundManager.syncRemote();
await backgroundManager.hashAssets();
if (MetadataRepository.instance.appConfig.backup.syncAlbums) {
if (SettingsRepository.instance.appConfig.backup.syncAlbums) {
await backgroundManager.syncLinkedAlbum();
}
}
@@ -7,7 +7,7 @@ import 'package:flutter_hooks/flutter_hooks.dart' hide Store;
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/domain/services/log.service.dart';
import 'package:immich_mobile/extensions/build_context_extensions.dart';
import 'package:immich_mobile/providers/infrastructure/metadata.provider.dart';
import 'package:immich_mobile/providers/infrastructure/settings.provider.dart';
import 'package:immich_mobile/providers/infrastructure/platform.provider.dart';
import 'package:immich_mobile/providers/infrastructure/readonly_mode.provider.dart';
import 'package:immich_mobile/repositories/permission.repository.dart';
@@ -35,7 +35,7 @@ class AdvancedSettings extends HookConsumerWidget {
final preferRemote = useState(ref.read(appConfigProvider).image.preferRemote);
useValueChanged(
preferRemote.value,
(_, __) => ref.read(metadataProvider).write(.imagePreferRemote, preferRemote.value),
(_, __) => ref.read(settingsProvider).write(.imagePreferRemote, preferRemote.value),
);
final readonlyModeEnabled = useAppSettingsState(AppSettingsEnum.readonlyModeEnabled);
@@ -3,11 +3,10 @@ import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/domain/models/metadata_key.dart';
import 'package:immich_mobile/domain/models/timeline.model.dart';
import 'package:immich_mobile/extensions/translate_extensions.dart';
import 'package:immich_mobile/providers/app_settings.provider.dart';
import 'package:immich_mobile/providers/infrastructure/metadata.provider.dart';
import 'package:immich_mobile/providers/infrastructure/settings.provider.dart';
import 'package:immich_mobile/widgets/settings/setting_group_title.dart';
import 'package:immich_mobile/widgets/settings/settings_radio_list_tile.dart';
@@ -19,7 +18,7 @@ class GroupSettings extends HookConsumerWidget {
final groupBy = useValueNotifier(ref.watch(appConfigProvider.select((s) => s.timeline.groupAssetsBy)));
Future<void> updateAppSettings(GroupAssetsBy groupBy) async {
await ref.read(metadataProvider).write(MetadataKey.timelineGroupAssetsBy, groupBy);
await ref.read(settingsProvider).write(.timelineGroupAssetsBy, groupBy);
ref.invalidate(appSettingsServiceProvider);
}
@@ -2,10 +2,9 @@ import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/domain/models/metadata_key.dart';
import 'package:immich_mobile/extensions/translate_extensions.dart';
import 'package:immich_mobile/providers/app_settings.provider.dart';
import 'package:immich_mobile/providers/infrastructure/metadata.provider.dart';
import 'package:immich_mobile/providers/infrastructure/settings.provider.dart';
import 'package:immich_mobile/widgets/settings/setting_group_title.dart';
import 'package:immich_mobile/widgets/settings/settings_slider_list_tile.dart';
@@ -16,7 +15,7 @@ class LayoutSettings extends HookConsumerWidget {
Widget build(BuildContext context, WidgetRef ref) {
final tilesPerRow = useState(ref.read(appConfigProvider.select((s) => s.timeline.tilesPerRow)));
useValueChanged<int, void>(tilesPerRow.value, (_, __) {
ref.read(metadataProvider).write(MetadataKey.timelineTilesPerRow, tilesPerRow.value);
ref.read(settingsProvider).write(.timelineTilesPerRow, tilesPerRow.value);
});
return Column(
@@ -2,10 +2,8 @@ import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/domain/models/metadata_key.dart';
import 'package:immich_mobile/providers/app_settings.provider.dart';
import 'package:immich_mobile/providers/infrastructure/metadata.provider.dart';
import 'package:immich_mobile/providers/infrastructure/setting.provider.dart';
import 'package:immich_mobile/providers/infrastructure/settings.provider.dart';
import 'package:immich_mobile/widgets/settings/asset_list_settings/asset_list_group_settings.dart';
import 'package:immich_mobile/widgets/settings/asset_list_settings/asset_list_layout_settings.dart';
import 'package:immich_mobile/widgets/settings/settings_sub_page_scaffold.dart';
@@ -23,7 +21,7 @@ class AssetListSettings extends HookConsumerWidget {
valueNotifier: storageIndicator,
title: 'theme_setting_asset_list_storage_indicator_title'.tr(),
onChanged: (value) {
ref.read(metadataProvider).write(MetadataKey.timelineStorageIndicator, value);
ref.read(settingsProvider).write(.timelineStorageIndicator, value);
ref.invalidate(appSettingsServiceProvider);
ref.invalidate(settingsProvider);
},
@@ -3,7 +3,7 @@ import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/extensions/translate_extensions.dart';
import 'package:immich_mobile/providers/app_settings.provider.dart';
import 'package:immich_mobile/providers/infrastructure/metadata.provider.dart';
import 'package:immich_mobile/providers/infrastructure/settings.provider.dart';
import 'package:immich_mobile/widgets/settings/setting_group_title.dart';
import 'package:immich_mobile/widgets/settings/settings_switch_list_tile.dart';
@@ -14,7 +14,7 @@ class ImageViewerQualitySetting extends HookConsumerWidget {
Widget build(BuildContext context, WidgetRef ref) {
final isOriginal = useState(ref.read(appConfigProvider).image.loadOriginal);
useValueChanged<bool, void>(isOriginal.value, (_, __) {
ref.read(metadataProvider).write(.imageLoadOriginal, isOriginal.value);
ref.read(settingsProvider).write(.imageLoadOriginal, isOriginal.value);
});
return Column(
@@ -2,7 +2,7 @@ import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/providers/infrastructure/metadata.provider.dart';
import 'package:immich_mobile/providers/infrastructure/settings.provider.dart';
import 'package:immich_mobile/widgets/settings/settings_sub_title.dart';
import 'package:immich_mobile/widgets/settings/settings_switch_list_tile.dart';
@@ -13,7 +13,7 @@ class ImageViewerTapToNavigateSetting extends HookConsumerWidget {
Widget build(BuildContext context, WidgetRef ref) {
final tapToNavigate = useState(ref.read(appConfigProvider).viewer.tapToNavigate);
useValueChanged<bool, void>(tapToNavigate.value, (_, __) {
ref.read(metadataProvider).write(.viewerTapToNavigate, tapToNavigate.value);
ref.read(settingsProvider).write(.viewerTapToNavigate, tapToNavigate.value);
});
return Column(
@@ -3,7 +3,7 @@ import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/constants/enums.dart';
import 'package:immich_mobile/extensions/translate_extensions.dart';
import 'package:immich_mobile/providers/infrastructure/metadata.provider.dart';
import 'package:immich_mobile/providers/infrastructure/settings.provider.dart';
import 'package:immich_mobile/widgets/settings/setting_group_title.dart';
import 'package:immich_mobile/widgets/settings/settings_radio_list_tile.dart';
import 'package:immich_mobile/widgets/settings/settings_slider_list_tile.dart';
@@ -23,19 +23,19 @@ class SlideshowSettings extends HookConsumerWidget {
final useDirection = useState(slideshow.direction);
useValueChanged<bool, void>(useTransition.value, (_, __) {
ref.read(metadataProvider).write(.slideshowTransition, useTransition.value);
ref.read(settingsProvider).write(.slideshowTransition, useTransition.value);
});
useValueChanged<bool, void>(useRepeat.value, (_, __) {
ref.read(metadataProvider).write(.slideshowRepeat, useRepeat.value);
ref.read(settingsProvider).write(.slideshowRepeat, useRepeat.value);
});
useValueChanged<int, void>(useDuration.value, (_, __) {
ref.read(metadataProvider).write(.slideshowDuration, useDuration.value);
ref.read(settingsProvider).write(.slideshowDuration, useDuration.value);
});
useValueChanged<SlideshowLook, void>(useLook.value, (_, __) {
ref.read(metadataProvider).write(.slideshowLook, useLook.value);
ref.read(settingsProvider).write(.slideshowLook, useLook.value);
});
useValueChanged<SlideshowDirection, void>(useDirection.value, (_, __) {
ref.read(metadataProvider).write(.slideshowDirection, useDirection.value);
ref.read(settingsProvider).write(.slideshowDirection, useDirection.value);
});
return Column(
@@ -2,7 +2,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/extensions/translate_extensions.dart';
import 'package:immich_mobile/providers/infrastructure/metadata.provider.dart';
import 'package:immich_mobile/providers/infrastructure/settings.provider.dart';
import 'package:immich_mobile/widgets/settings/setting_group_title.dart';
import 'package:immich_mobile/widgets/settings/settings_switch_list_tile.dart';
@@ -17,13 +17,13 @@ class VideoViewerSettings extends HookConsumerWidget {
final useOriginalVideo = useState(viewer.loadOriginalVideo);
useValueChanged<bool, void>(useAutoPlayVideo.value, (_, __) {
ref.read(metadataProvider).write(.viewerAutoPlayVideo, useAutoPlayVideo.value);
ref.read(settingsProvider).write(.viewerAutoPlayVideo, useAutoPlayVideo.value);
});
useValueChanged<bool, void>(useLoopVideo.value, (_, __) {
ref.read(metadataProvider).write(.viewerLoopVideo, useLoopVideo.value);
ref.read(settingsProvider).write(.viewerLoopVideo, useLoopVideo.value);
});
useValueChanged<bool, void>(useOriginalVideo.value, (_, __) {
ref.read(metadataProvider).write(.viewerLoadOriginalVideo, useOriginalVideo.value);
ref.read(settingsProvider).write(.viewerLoadOriginalVideo, useOriginalVideo.value);
});
return Column(
@@ -5,15 +5,15 @@ import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/domain/models/album/local_album.model.dart';
import 'package:immich_mobile/domain/models/config/app_config.dart';
import 'package:immich_mobile/domain/models/metadata_key.dart';
import 'package:immich_mobile/domain/models/settings_key.dart';
import 'package:immich_mobile/domain/services/sync_linked_album.service.dart';
import 'package:immich_mobile/extensions/build_context_extensions.dart';
import 'package:immich_mobile/extensions/platform_extensions.dart';
import 'package:immich_mobile/extensions/translate_extensions.dart';
import 'package:immich_mobile/providers/background_sync.provider.dart';
import 'package:immich_mobile/providers/backup/backup_album.provider.dart';
import 'package:immich_mobile/providers/infrastructure/metadata.provider.dart';
import 'package:immich_mobile/providers/infrastructure/platform.provider.dart';
import 'package:immich_mobile/providers/infrastructure/settings.provider.dart';
import 'package:immich_mobile/providers/user.provider.dart';
import 'package:immich_mobile/widgets/settings/setting_group_title.dart';
import 'package:immich_mobile/widgets/settings/setting_list_tile.dart';
@@ -112,7 +112,7 @@ class _AlbumSyncActionButtonState extends ConsumerState<_AlbumSyncActionButton>
trailing: Switch(
value: albumSyncEnable,
onChanged: (bool newValue) async {
await ref.read(metadataProvider).write(MetadataKey.backupSyncAlbums, newValue);
await ref.read(settingsProvider).write(.backupSyncAlbums, newValue);
if (newValue == true) {
await _manageLinkedAlbums();
@@ -158,7 +158,7 @@ class _AlbumSyncActionButtonState extends ConsumerState<_AlbumSyncActionButton>
}
class _BackupSwitchTile extends ConsumerWidget {
final MetadataKey<bool> metadataKey;
final SettingsKey<bool> metadataKey;
final bool Function(AppConfig) selector;
final String titleKey;
final String subtitleKey;
@@ -183,7 +183,7 @@ class _BackupSwitchTile extends ConsumerWidget {
trailing: Switch(
value: value,
onChanged: (bool newValue) async {
await ref.read(metadataProvider).write(metadataKey, newValue);
await ref.read(settingsProvider).write(metadataKey, newValue);
onChanged?.call(newValue);
},
),
@@ -198,7 +198,7 @@ class _UseCellularForVideosButton extends StatelessWidget {
@override
Widget build(BuildContext context) {
return _BackupSwitchTile(
metadataKey: MetadataKey.backupUseCellularForVideos,
metadataKey: SettingsKey.backupUseCellularForVideos,
selector: (c) => c.backup.useCellularForVideos,
titleKey: "videos",
subtitleKey: "network_requirement_videos_upload",
@@ -212,7 +212,7 @@ class _UseCellularForPhotosButton extends StatelessWidget {
@override
Widget build(BuildContext context) {
return _BackupSwitchTile(
metadataKey: MetadataKey.backupUseCellularForPhotos,
metadataKey: SettingsKey.backupUseCellularForPhotos,
selector: (c) => c.backup.useCellularForPhotos,
titleKey: "photos",
subtitleKey: "network_requirement_photos_upload",
@@ -227,7 +227,7 @@ class _BackupOnlyWhenChargingButton extends ConsumerWidget {
Widget build(BuildContext context, WidgetRef ref) {
final fgService = ref.read(backgroundWorkerFgServiceProvider);
return _BackupSwitchTile(
metadataKey: MetadataKey.backupRequireCharging,
metadataKey: SettingsKey.backupRequireCharging,
selector: (c) => c.backup.requireCharging,
titleKey: "charging",
subtitleKey: "charging_requirement_mobile_backup",
@@ -282,11 +282,11 @@ class _BackupDelaySlider extends ConsumerWidget {
value: currentValue.toDouble(),
onChanged: (double v) async {
final seconds = backupDelayToSeconds(v.toInt());
await ref.read(metadataProvider).write(MetadataKey.backupTriggerDelay, seconds);
await ref.read(settingsProvider).write(SettingsKey.backupTriggerDelay, seconds);
},
onChangeEnd: (double v) async {
final seconds = backupDelayToSeconds(v.toInt());
await ref.read(metadataProvider).write(MetadataKey.backupTriggerDelay, seconds);
await ref.read(settingsProvider).write(SettingsKey.backupTriggerDelay, seconds);
},
max: 3.0,
min: 0.0,
@@ -1,11 +1,11 @@
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/domain/models/metadata_key.dart';
import 'package:immich_mobile/domain/models/settings_key.dart';
import 'package:immich_mobile/extensions/build_context_extensions.dart';
import 'package:immich_mobile/extensions/translate_extensions.dart';
import 'package:immich_mobile/models/auth/auxilary_endpoint.model.dart';
import 'package:immich_mobile/providers/infrastructure/metadata.provider.dart';
import 'package:immich_mobile/providers/infrastructure/settings.provider.dart';
import 'package:immich_mobile/widgets/settings/networking_settings/endpoint_input.dart';
class ExternalNetworkPreference extends HookConsumerWidget {
@@ -26,7 +26,7 @@ class ExternalNetworkPreference extends HookConsumerWidget {
.map((e) => e.url)
.toList();
ref.read(metadataProvider).write(MetadataKey.networkExternalEndpointList, urls);
ref.read(settingsProvider).write(SettingsKey.networkExternalEndpointList, urls);
}
updateValidationStatus(String url, int index, AuxCheckStatus status) {
@@ -5,7 +5,7 @@ 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/models/auth/auxilary_endpoint.model.dart';
import 'package:immich_mobile/providers/infrastructure/metadata.provider.dart';
import 'package:immich_mobile/providers/infrastructure/settings.provider.dart';
import 'package:immich_mobile/providers/network.provider.dart';
import 'package:immich_mobile/utils/url_helper.dart';
import 'package:immich_mobile/widgets/settings/networking_settings/external_network_preference.dart';
@@ -21,7 +21,7 @@ class NetworkingSettings extends HookConsumerWidget {
final currentEndpoint = getServerUrl();
final featureEnabled = useState(ref.read(appConfigProvider).network.autoEndpointSwitching);
useValueChanged<bool, void>(featureEnabled.value, (_, __) {
ref.read(metadataProvider).write(.networkAutoEndpointSwitching, featureEnabled.value);
ref.read(settingsProvider).write(.networkAutoEndpointSwitching, featureEnabled.value);
});
Future<void> checkWifiReadPermission() async {
@@ -4,7 +4,7 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/constants/colors.dart';
import 'package:immich_mobile/extensions/build_context_extensions.dart';
import 'package:immich_mobile/extensions/theme_extensions.dart';
import 'package:immich_mobile/providers/infrastructure/metadata.provider.dart';
import 'package:immich_mobile/providers/infrastructure/settings.provider.dart';
import 'package:immich_mobile/providers/theme.provider.dart';
import 'package:immich_mobile/theme/color_scheme.dart';
import 'package:immich_mobile/theme/dynamic_theme.dart';
@@ -26,16 +26,16 @@ class PrimaryColorSetting extends HookConsumerWidget {
}
onUseSystemColorChange(bool newValue) {
ref.read(metadataProvider).write(.themeDynamic, newValue);
ref.read(settingsProvider).write(.themeDynamic, newValue);
popBottomSheet();
}
onPrimaryColorChange(ImmichColorPreset colorPreset) {
ref.read(metadataProvider).write(.themePrimaryColor, colorPreset);
ref.read(settingsProvider).write(.themePrimaryColor, colorPreset);
//turn off system color setting
if (themeConfig.dynamicTheme) {
ref.read(metadataProvider).write(.themeDynamic, false);
ref.read(settingsProvider).write(.themeDynamic, false);
}
popBottomSheet();
}
@@ -3,7 +3,7 @@ import 'package:flutter_hooks/flutter_hooks.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/infrastructure/metadata.provider.dart';
import 'package:immich_mobile/providers/infrastructure/settings.provider.dart';
import 'package:immich_mobile/widgets/settings/preference_settings/primary_color_setting.dart';
import 'package:immich_mobile/widgets/settings/setting_group_title.dart';
import 'package:immich_mobile/widgets/settings/settings_switch_list_tile.dart';
@@ -22,7 +22,7 @@ class ThemeSetting extends HookConsumerWidget {
void onThemeChange(bool isDark) {
currentTheme.value = isDark ? ThemeMode.dark : ThemeMode.light;
ref.read(metadataProvider).write(.themeMode, currentTheme.value);
ref.read(settingsProvider).write(.themeMode, currentTheme.value);
}
void onSystemThemeChange(bool isSystem) {
@@ -39,11 +39,11 @@ class ThemeSetting extends HookConsumerWidget {
currentTheme.value = ThemeMode.dark;
}
}
ref.read(metadataProvider).write(.themeMode, currentTheme.value);
ref.read(settingsProvider).write(.themeMode, currentTheme.value);
}
void onSurfaceColorSettingChange(bool useColorfulInterface) {
ref.read(metadataProvider).write(.themeColorfulInterface, useColorfulInterface);
ref.read(settingsProvider).write(.themeColorfulInterface, useColorfulInterface);
colorfulInterface.value = useColorfulInterface;
}
+60
View File
@@ -0,0 +1,60 @@
import 'package:flutter/material.dart';
import 'package:flutter/widget_previews.dart';
import 'package:immich_ui/src/theme.dart';
const ColorScheme _lightColorScheme = ColorScheme.light(
primary: Color(0xFF4250AF),
onPrimary: Colors.white,
primaryContainer: Color(0xFFD4D6F0),
onPrimaryContainer: Color(0xFF181E44),
secondary: Color(0xFF737373),
onSecondary: Colors.white,
error: Color(0xFFE53E3E),
onError: Colors.white,
surface: Color(0xFFFAFAFA),
onSurface: Color(0xFF1A1C1E),
surfaceContainerHighest: Color(0xFFE3E4E8),
outline: Color(0xFFD1D3D9),
outlineVariant: Color(0xFFD4D4D4),
);
const ColorScheme _darkColorScheme = ColorScheme.dark(
primary: Color(0xFFACCBFA),
onPrimary: Color(0xFF0F1433),
primaryContainer: Color(0xFF616D94),
onPrimaryContainer: Color(0xFFD4D6F0),
secondary: Color(0xFFC4C6D0),
onSecondary: Color(0xFF2E3042),
error: Color(0xFFE88080),
onError: Color(0xFF0F1433),
surface: Color(0xFF0A0A0A),
onSurface: Color(0xFFE3E3E6),
surfaceContainerHighest: Color(0xFF262626),
outline: Color(0xFF8E9099),
outlineVariant: Color(0xFF43464F),
);
PreviewThemeData immichPreviewTheme() => PreviewThemeData(
materialLight: ThemeData(colorScheme: _lightColorScheme, useMaterial3: true),
materialDark: ThemeData(colorScheme: _darkColorScheme, useMaterial3: true),
);
Widget immichPreviewWrapper(Widget child) {
return Builder(
builder: (context) => ImmichThemeProvider(
colorScheme: Theme.of(context).colorScheme,
child: Scaffold(
backgroundColor: Theme.of(context).colorScheme.surface,
body: Padding(
padding: const EdgeInsets.all(16),
child: Align(alignment: Alignment.topLeft, child: child),
),
),
),
);
}
final class ImmichPreview extends Preview {
const ImmichPreview({super.name, super.group, super.size, super.textScaleFactor})
: super(theme: immichPreviewTheme, wrapper: immichPreviewWrapper);
}
@@ -0,0 +1,26 @@
import 'package:flutter/material.dart';
import 'package:immich_ui/src/components/close_button.dart';
import 'package:immich_ui/src/previews.dart';
import 'package:immich_ui/src/types.dart';
void _previewNoop() {}
@ImmichPreview(group: 'CloseButton', name: 'Variants')
Widget previewCloseButtonVariants() => const Wrap(
spacing: 12,
runSpacing: 12,
children: [
ImmichCloseButton(onPressed: _previewNoop),
ImmichCloseButton(onPressed: _previewNoop, variant: ImmichVariant.filled),
],
);
@ImmichPreview(group: 'CloseButton', name: 'Colors')
Widget previewCloseButtonColors() => const Wrap(
spacing: 12,
runSpacing: 12,
children: [
ImmichCloseButton(onPressed: _previewNoop),
ImmichCloseButton(onPressed: _previewNoop, color: ImmichColor.secondary),
],
);
@@ -0,0 +1,72 @@
import 'package:flutter/material.dart';
import 'package:immich_ui/src/components/form.dart';
import 'package:immich_ui/src/components/password_input.dart';
import 'package:immich_ui/src/components/text_input.dart';
import 'package:immich_ui/src/constants.dart';
import 'package:immich_ui/src/previews.dart';
@ImmichPreview(group: 'Form', name: 'Login Form')
Widget previewFormLogin() => const _PreviewLoginForm();
class _PreviewLoginForm extends StatefulWidget {
const _PreviewLoginForm();
@override
State<_PreviewLoginForm> createState() => _PreviewLoginFormState();
}
class _PreviewLoginFormState extends State<_PreviewLoginForm> {
final _emailController = TextEditingController();
final _passwordController = TextEditingController();
String _result = '';
@override
void dispose() {
_emailController.dispose();
_passwordController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
ImmichForm(
submitText: 'Login',
submitIcon: Icons.login,
onSubmit: () async {
await Future<void>.delayed(const Duration(seconds: 1));
if (!mounted) {
return;
}
setState(() {
_result = 'Form submitted!';
});
},
builder: (context, form) => Column(
spacing: ImmichSpacing.sm,
children: [
ImmichTextInput(
label: 'Email',
controller: _emailController,
keyboardType: TextInputType.emailAddress,
validator: (value) => value?.isEmpty ?? true ? 'Required' : null,
),
ImmichPasswordInput(
label: 'Password',
controller: _passwordController,
validator: (value) => value?.isEmpty ?? true ? 'Required' : null,
onSubmit: (_) => form.submit(),
),
],
),
),
if (_result.isNotEmpty) ...[
const SizedBox(height: 16),
Text(_result, style: const TextStyle(color: Colors.green)),
],
],
);
}
}
@@ -0,0 +1,50 @@
import 'package:flutter/material.dart';
import 'package:immich_ui/src/components/formatted_text.dart';
import 'package:immich_ui/src/previews.dart';
@ImmichPreview(group: 'FormattedText', name: 'Bold')
Widget previewFormattedTextBold() => const ImmichFormattedText('This is <b>bold text</b>.');
@ImmichPreview(group: 'FormattedText', name: 'Links')
Widget previewFormattedTextLinks() => const _PreviewFormattedTextLinks();
@ImmichPreview(group: 'FormattedText', name: 'Mixed Content')
Widget previewFormattedTextMixed() => const _PreviewFormattedTextMixed();
class _PreviewFormattedTextLinks extends StatelessWidget {
const _PreviewFormattedTextLinks();
@override
Widget build(BuildContext context) {
return ImmichFormattedText(
'Read the <docs-link>documentation</docs-link> or visit <github-link>GitHub</github-link>.',
spanBuilder: (tag) => FormattedSpan(
onTap: switch (tag) {
'docs-link' =>
() => ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('Docs link clicked!'))),
'github-link' =>
() => ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('GitHub link clicked!'))),
_ => null,
},
),
);
}
}
class _PreviewFormattedTextMixed extends StatelessWidget {
const _PreviewFormattedTextMixed();
@override
Widget build(BuildContext context) {
return ImmichFormattedText(
'You can use <b>bold text</b> and <link>links</link> together.',
spanBuilder: (tag) => switch (tag) {
'b' => const FormattedSpan(style: TextStyle(fontWeight: FontWeight.bold)),
_ => FormattedSpan(
onTap: () =>
ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('Link clicked!'))),
),
},
);
}
}
@@ -0,0 +1,41 @@
import 'package:flutter/material.dart';
import 'package:immich_ui/src/components/icon_button.dart';
import 'package:immich_ui/src/previews.dart';
import 'package:immich_ui/src/types.dart';
void _previewNoop() {}
@ImmichPreview(group: 'IconButton', name: 'Variants')
Widget previewIconButtonVariants() => const Wrap(
spacing: 12,
runSpacing: 12,
children: [
ImmichIconButton(icon: Icons.add, onPressed: _previewNoop),
ImmichIconButton(icon: Icons.edit, onPressed: _previewNoop, variant: ImmichVariant.ghost),
],
);
@ImmichPreview(group: 'IconButton', name: 'Colors')
Widget previewIconButtonColors() => const Wrap(
spacing: 12,
runSpacing: 12,
children: [
ImmichIconButton(icon: Icons.favorite, onPressed: _previewNoop),
ImmichIconButton(icon: Icons.delete, onPressed: _previewNoop, color: ImmichColor.secondary),
],
);
@ImmichPreview(group: 'IconButton', name: 'Disabled')
Widget previewIconButtonDisabled() => const Wrap(
spacing: 12,
runSpacing: 12,
children: [
ImmichIconButton(icon: Icons.settings, onPressed: _previewNoop, disabled: true),
ImmichIconButton(
icon: Icons.settings,
onPressed: _previewNoop,
disabled: true,
variant: ImmichVariant.ghost,
),
],
);
@@ -0,0 +1,18 @@
import 'package:flutter/material.dart';
import 'package:immich_ui/src/components/password_input.dart';
import 'package:immich_ui/src/previews.dart';
@ImmichPreview(group: 'PasswordInput', name: 'With Validator')
Widget previewPasswordInput() => ImmichPasswordInput(
label: 'Password',
hintText: 'Enter your password',
validator: (value) {
if (value == null || value.isEmpty) {
return 'Password is required';
}
if (value.length < 8) {
return 'Password must be at least 8 characters';
}
return null;
},
);
@@ -0,0 +1,88 @@
import 'package:flutter/material.dart';
import 'package:immich_ui/src/components/text_button.dart';
import 'package:immich_ui/src/previews.dart';
import 'package:immich_ui/src/types.dart';
void _previewNoop() {}
@ImmichPreview(group: 'TextButton', name: 'Variants')
Widget previewTextButtonVariants() => const Wrap(
spacing: 12,
runSpacing: 12,
children: [
ImmichTextButton(onPressed: _previewNoop, labelText: 'Filled', expanded: false),
ImmichTextButton(onPressed: _previewNoop, labelText: 'Ghost', variant: ImmichVariant.ghost, expanded: false),
],
);
@ImmichPreview(group: 'TextButton', name: 'Colors')
Widget previewTextButtonColors() => const Wrap(
spacing: 12,
runSpacing: 12,
children: [
ImmichTextButton(onPressed: _previewNoop, labelText: 'Primary', expanded: false),
ImmichTextButton(onPressed: _previewNoop, labelText: 'Secondary', color: ImmichColor.secondary, expanded: false),
],
);
@ImmichPreview(group: 'TextButton', name: 'With Icons')
Widget previewTextButtonWithIcons() => const Wrap(
spacing: 12,
runSpacing: 12,
children: [
ImmichTextButton(onPressed: _previewNoop, labelText: 'With Icon', icon: Icons.add, expanded: false),
ImmichTextButton(
onPressed: _previewNoop,
labelText: 'Download',
icon: Icons.download,
variant: ImmichVariant.ghost,
expanded: false,
),
],
);
@ImmichPreview(group: 'TextButton', name: 'Loading')
Widget previewTextButtonLoading() => const _PreviewLoadingDemo();
@ImmichPreview(group: 'TextButton', name: 'Disabled')
Widget previewTextButtonDisabled() => const Wrap(
spacing: 12,
runSpacing: 12,
children: [
ImmichTextButton(onPressed: _previewNoop, labelText: 'Disabled', disabled: true, expanded: false),
ImmichTextButton(
onPressed: _previewNoop,
labelText: 'Disabled Ghost',
variant: ImmichVariant.ghost,
disabled: true,
expanded: false,
),
],
);
class _PreviewLoadingDemo extends StatefulWidget {
const _PreviewLoadingDemo();
@override
State<_PreviewLoadingDemo> createState() => _PreviewLoadingDemoState();
}
class _PreviewLoadingDemoState extends State<_PreviewLoadingDemo> {
bool _isLoading = false;
@override
Widget build(BuildContext context) {
return ImmichTextButton(
onPressed: () async {
setState(() => _isLoading = true);
await Future<void>.delayed(const Duration(seconds: 2));
if (mounted) {
setState(() => _isLoading = false);
}
},
labelText: _isLoading ? 'Loading...' : 'Click Me',
loading: _isLoading,
expanded: false,
);
}
}
@@ -0,0 +1,71 @@
import 'package:flutter/material.dart';
import 'package:immich_ui/src/components/text_input.dart';
import 'package:immich_ui/src/previews.dart';
@ImmichPreview(group: 'TextInput', name: 'Basic')
Widget previewTextInputBasic() => const _PreviewTextInputBasic();
@ImmichPreview(group: 'TextInput', name: 'With Validator')
Widget previewTextInputValidator() => const _PreviewTextInputValidator();
class _PreviewTextInputBasic extends StatefulWidget {
const _PreviewTextInputBasic();
@override
State<_PreviewTextInputBasic> createState() => _PreviewTextInputBasicState();
}
class _PreviewTextInputBasicState extends State<_PreviewTextInputBasic> {
final _controller = TextEditingController();
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return ImmichTextInput(
label: 'Email',
hintText: 'Enter your email',
controller: _controller,
keyboardType: TextInputType.emailAddress,
);
}
}
class _PreviewTextInputValidator extends StatefulWidget {
const _PreviewTextInputValidator();
@override
State<_PreviewTextInputValidator> createState() => _PreviewTextInputValidatorState();
}
class _PreviewTextInputValidatorState extends State<_PreviewTextInputValidator> {
final _controller = TextEditingController();
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return ImmichTextInput(
label: 'Username',
controller: _controller,
autovalidateMode: AutovalidateMode.onUserInteraction,
validator: (value) {
if (value == null || value.isEmpty) {
return 'Username is required';
}
if (value.length < 3) {
return 'Username must be at least 3 characters';
}
return null;
},
);
}
}
@@ -0,0 +1,28 @@
import 'package:flutter/material.dart';
import 'package:immich_ui/src/components/url_input.dart';
import 'package:immich_ui/src/previews.dart';
@ImmichPreview(group: 'URLInput', name: 'Basic')
Widget previewUrlInput() => const _PreviewUrlInput();
class _PreviewUrlInput extends StatefulWidget {
const _PreviewUrlInput();
@override
State<_PreviewUrlInput> createState() => _PreviewUrlInputState();
}
class _PreviewUrlInputState extends State<_PreviewUrlInput> {
final _controller = TextEditingController();
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return ImmichURLInput(label: 'Server URL', hintText: 'https://demo.immich.com', controller: _controller);
}
}
+1 -1
View File
@@ -185,5 +185,5 @@ packages:
source: hosted
version: "15.2.0"
sdks:
dart: ">=3.11.0 <4.0.0"
dart: ">=3.12.0 <4.0.0"
flutter: ">=3.18.0-18.0.pre.54"
+1 -1
View File
@@ -2,7 +2,7 @@ name: immich_ui
publish_to: none
environment:
sdk: '>=3.11.0 <4.0.0'
sdk: '>=3.12.0 <4.0.0'
dependencies:
flutter:
-11
View File
@@ -1,11 +0,0 @@
# Build artifacts
build/
# Test cache and generated files
.dart_tool/
.packages
.flutter-plugins
.flutter-plugins-dependencies
# IDE-specific files
.vscode/
-30
View File
@@ -1,30 +0,0 @@
# This file tracks properties of this Flutter project.
# Used by Flutter tool to assess capabilities and perform upgrades etc.
#
# This file should be version controlled and should not be manually edited.
version:
revision: "adc901062556672b4138e18a4dc62a4be8f4b3c2"
channel: "stable"
project_type: app
# Tracks metadata for the flutter migrate command
migration:
platforms:
- platform: root
create_revision: adc901062556672b4138e18a4dc62a4be8f4b3c2
base_revision: adc901062556672b4138e18a4dc62a4be8f4b3c2
- platform: web
create_revision: adc901062556672b4138e18a4dc62a4be8f4b3c2
base_revision: adc901062556672b4138e18a4dc62a4be8f4b3c2
# User provided section
# List of Local paths (relative to this file) that should be
# ignored by the migrate tool.
#
# Files that are not part of the templates will be ignored by default.
unmanaged_files:
- 'lib/main.dart'
- 'ios/Runner.xcodeproj/project.pbxproj'
@@ -1 +0,0 @@
include: package:flutter_lints/flutter.yaml
Binary file not shown.

Before

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.1 KiB

@@ -1,339 +0,0 @@
{
"name": "GitHub Dark",
"settings": [
{
"settings": {
"foreground": "#e1e4e8",
"background": "#24292e"
}
},
{
"scope": [
"comment",
"punctuation.definition.comment",
"string.comment"
],
"settings": {
"foreground": "#6a737d"
}
},
{
"scope": [
"constant",
"entity.name.constant",
"variable.other.constant",
"variable.other.enummember",
"variable.language"
],
"settings": {
"foreground": "#79b8ff"
}
},
{
"scope": [
"entity",
"entity.name"
],
"settings": {
"foreground": "#b392f0"
}
},
{
"scope": "variable.parameter.function",
"settings": {
"foreground": "#e1e4e8"
}
},
{
"scope": "entity.name.tag",
"settings": {
"foreground": "#85e89d"
}
},
{
"scope": "keyword",
"settings": {
"foreground": "#f97583"
}
},
{
"scope": [
"storage",
"storage.type"
],
"settings": {
"foreground": "#f97583"
}
},
{
"scope": [
"storage.modifier.package",
"storage.modifier.import",
"storage.type.java"
],
"settings": {
"foreground": "#e1e4e8"
}
},
{
"scope": [
"string",
"punctuation.definition.string",
"string punctuation.section.embedded source"
],
"settings": {
"foreground": "#9ecbff"
}
},
{
"scope": "support",
"settings": {
"foreground": "#79b8ff"
}
},
{
"scope": "meta.property-name",
"settings": {
"foreground": "#79b8ff"
}
},
{
"scope": "variable",
"settings": {
"foreground": "#ffab70"
}
},
{
"scope": "variable.other",
"settings": {
"foreground": "#e1e4e8"
}
},
{
"scope": "invalid.broken",
"settings": {
"fontStyle": "italic",
"foreground": "#fdaeb7"
}
},
{
"scope": "invalid.deprecated",
"settings": {
"fontStyle": "italic",
"foreground": "#fdaeb7"
}
},
{
"scope": "invalid.illegal",
"settings": {
"fontStyle": "italic",
"foreground": "#fdaeb7"
}
},
{
"scope": "invalid.unimplemented",
"settings": {
"fontStyle": "italic",
"foreground": "#fdaeb7"
}
},
{
"scope": "message.error",
"settings": {
"foreground": "#fdaeb7"
}
},
{
"scope": "string variable",
"settings": {
"foreground": "#79b8ff"
}
},
{
"scope": [
"source.regexp",
"string.regexp"
],
"settings": {
"foreground": "#dbedff"
}
},
{
"scope": [
"string.regexp.character-class",
"string.regexp constant.character.escape",
"string.regexp source.ruby.embedded",
"string.regexp string.regexp.arbitrary-repitition"
],
"settings": {
"foreground": "#dbedff"
}
},
{
"scope": "string.regexp constant.character.escape",
"settings": {
"fontStyle": "bold",
"foreground": "#85e89d"
}
},
{
"scope": "support.constant",
"settings": {
"foreground": "#79b8ff"
}
},
{
"scope": "support.variable",
"settings": {
"foreground": "#79b8ff"
}
},
{
"scope": "meta.module-reference",
"settings": {
"foreground": "#79b8ff"
}
},
{
"scope": "punctuation.definition.list.begin.markdown",
"settings": {
"foreground": "#ffab70"
}
},
{
"scope": [
"markup.heading",
"markup.heading entity.name"
],
"settings": {
"fontStyle": "bold",
"foreground": "#79b8ff"
}
},
{
"scope": "markup.quote",
"settings": {
"foreground": "#85e89d"
}
},
{
"scope": "markup.italic",
"settings": {
"fontStyle": "italic",
"foreground": "#e1e4e8"
}
},
{
"scope": "markup.bold",
"settings": {
"fontStyle": "bold",
"foreground": "#e1e4e8"
}
},
{
"scope": "markup.underline",
"settings": {
"fontStyle": "underline"
}
},
{
"scope": "markup.inline.raw",
"settings": {
"foreground": "#79b8ff"
}
},
{
"scope": [
"markup.deleted",
"meta.diff.header.from-file",
"punctuation.definition.deleted"
],
"settings": {
"foreground": "#fdaeb7"
}
},
{
"scope": [
"markup.inserted",
"meta.diff.header.to-file",
"punctuation.definition.inserted"
],
"settings": {
"foreground": "#85e89d"
}
},
{
"scope": [
"markup.changed",
"punctuation.definition.changed"
],
"settings": {
"foreground": "#ffab70"
}
},
{
"scope": [
"markup.ignored",
"markup.untracked"
],
"settings": {
"foreground": "#2f363d"
}
},
{
"scope": "meta.diff.range",
"settings": {
"fontStyle": "bold",
"foreground": "#b392f0"
}
},
{
"scope": "meta.diff.header",
"settings": {
"foreground": "#79b8ff"
}
},
{
"scope": "meta.separator",
"settings": {
"fontStyle": "bold",
"foreground": "#79b8ff"
}
},
{
"scope": "meta.output",
"settings": {
"foreground": "#79b8ff"
}
},
{
"scope": [
"brackethighlighter.tag",
"brackethighlighter.curly",
"brackethighlighter.round",
"brackethighlighter.square",
"brackethighlighter.angle",
"brackethighlighter.quote"
],
"settings": {
"foreground": "#d1d5da"
}
},
{
"scope": "brackethighlighter.unmatched",
"settings": {
"foreground": "#fdaeb7"
}
},
{
"scope": [
"constant.other.reference.link",
"string.other.link"
],
"settings": {
"fontStyle": "underline",
"foreground": "#dbedff"
}
}
]
}
@@ -1,96 +0,0 @@
import 'package:flutter/material.dart';
class AppTheme {
// Light theme colors
static const _primary500 = Color(0xFF4250AF);
static const _primary100 = Color(0xFFD4D6F0);
static const _primary900 = Color(0xFF181E44);
static const _danger500 = Color(0xFFE53E3E);
static const _light50 = Color(0xFFFAFAFA);
static const _light300 = Color(0xFFD4D4D4);
static const _light500 = Color(0xFF737373);
// Dark theme colors
static const _darkPrimary500 = Color(0xFFACCBFA);
static const _darkPrimary300 = Color(0xFF616D94);
static const _darkDanger500 = Color(0xFFE88080);
static const _darkLight50 = Color(0xFF0A0A0A);
static const _darkLight100 = Color(0xFF171717);
static const _darkLight200 = Color(0xFF262626);
static ThemeData get lightTheme {
return ThemeData(
colorScheme: const ColorScheme.light(
primary: _primary500,
onPrimary: Colors.white,
primaryContainer: _primary100,
onPrimaryContainer: _primary900,
secondary: _light500,
onSecondary: Colors.white,
error: _danger500,
onError: Colors.white,
surface: _light50,
onSurface: Color(0xFF1A1C1E),
surfaceContainerHighest: Color(0xFFE3E4E8),
outline: Color(0xFFD1D3D9),
outlineVariant: _light300,
),
useMaterial3: true,
fontFamily: 'GoogleSans',
scaffoldBackgroundColor: _light50,
cardTheme: const CardThemeData(
elevation: 0,
color: Colors.white,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(12)),
side: BorderSide(color: _light300, width: 1),
),
),
appBarTheme: const AppBarTheme(
centerTitle: false,
elevation: 0,
backgroundColor: Colors.white,
surfaceTintColor: Colors.transparent,
foregroundColor: Color(0xFF1A1C1E),
),
);
}
static ThemeData get darkTheme {
return ThemeData(
colorScheme: const ColorScheme.dark(
primary: _darkPrimary500,
onPrimary: Color(0xFF0F1433),
primaryContainer: _darkPrimary300,
onPrimaryContainer: _primary100,
secondary: Color(0xFFC4C6D0),
onSecondary: Color(0xFF2E3042),
error: _darkDanger500,
onError: Color(0xFF0F1433),
surface: _darkLight50,
onSurface: Color(0xFFE3E3E6),
surfaceContainerHighest: _darkLight200,
outline: Color(0xFF8E9099),
outlineVariant: Color(0xFF43464F),
),
useMaterial3: true,
fontFamily: 'GoogleSans',
scaffoldBackgroundColor: _darkLight50,
cardTheme: const CardThemeData(
elevation: 0,
color: _darkLight100,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(12)),
side: BorderSide(color: _darkLight200, width: 1),
),
),
appBarTheme: const AppBarTheme(
centerTitle: false,
elevation: 0,
backgroundColor: _darkLight50,
surfaceTintColor: Colors.transparent,
foregroundColor: Color(0xFFE3E3E6),
),
);
}
}
@@ -1,16 +0,0 @@
const String appTitle = '@immich/ui';
class LayoutConstants {
static const double sidebarWidth = 220.0;
static const double gridSpacing = 16.0;
static const double gridAspectRatio = 2.5;
static const double borderRadiusSmall = 6.0;
static const double borderRadiusMedium = 8.0;
static const double borderRadiusLarge = 12.0;
static const double iconSizeSmall = 16.0;
static const double iconSizeMedium = 18.0;
static const double iconSizeLarge = 20.0;
}
-55
View File
@@ -1,55 +0,0 @@
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:immich_ui/immich_ui.dart';
import 'package:showcase/app_theme.dart';
import 'package:showcase/constants.dart';
import 'package:showcase/router.dart';
import 'package:showcase/widgets/example_card.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await initializeCodeHighlighter();
runApp(const ShowcaseApp());
}
class ShowcaseApp extends StatefulWidget {
const ShowcaseApp({super.key});
@override
State<ShowcaseApp> createState() => _ShowcaseAppState();
}
class _ShowcaseAppState extends State<ShowcaseApp> {
ThemeMode _themeMode = ThemeMode.light;
late final GoRouter _router;
@override
void initState() {
super.initState();
_router = AppRouter.createRouter(_toggleTheme);
}
void _toggleTheme() {
setState(() {
_themeMode = _themeMode == ThemeMode.light
? ThemeMode.dark
: ThemeMode.light;
});
}
@override
Widget build(BuildContext context) {
return MaterialApp.router(
title: appTitle,
themeMode: _themeMode,
routerConfig: _router,
theme: AppTheme.lightTheme,
darkTheme: AppTheme.darkTheme,
debugShowCheckedModeBanner: false,
builder: (context, child) => ImmichThemeProvider(
colorScheme: Theme.of(context).colorScheme,
child: child!,
),
);
}
}
@@ -1,41 +0,0 @@
import 'package:flutter/material.dart';
import 'package:immich_ui/immich_ui.dart';
import 'package:showcase/routes.dart';
import 'package:showcase/widgets/component_examples.dart';
import 'package:showcase/widgets/example_card.dart';
import 'package:showcase/widgets/page_title.dart';
class CloseButtonPage extends StatelessWidget {
const CloseButtonPage({super.key});
@override
Widget build(BuildContext context) {
return PageTitle(
title: AppRoute.closeButton.name,
child: ComponentExamples(
title: 'ImmichCloseButton',
subtitle: 'Pre-configured close button for dialogs and sheets.',
examples: [
ExampleCard(
title: 'Default & Custom',
preview: Wrap(
spacing: 12,
runSpacing: 12,
children: [
ImmichCloseButton(onPressed: () {}),
ImmichCloseButton(
variant: ImmichVariant.filled,
onPressed: () {},
),
ImmichCloseButton(
color: ImmichColor.secondary,
onPressed: () {},
),
],
),
),
],
),
);
}
}
@@ -1,11 +0,0 @@
import 'package:flutter/material.dart';
import 'package:immich_ui/immich_ui.dart';
class FormattedTextBoldText extends StatelessWidget {
const FormattedTextBoldText({super.key});
@override
Widget build(BuildContext context) {
return ImmichFormattedText('This is <b>bold text</b>.');
}
}
@@ -1,24 +0,0 @@
import 'package:flutter/material.dart';
import 'package:immich_ui/immich_ui.dart';
class FormattedTextLinks extends StatelessWidget {
const FormattedTextLinks({super.key});
@override
Widget build(BuildContext context) {
return ImmichFormattedText(
'Read the <docs-link>documentation</docs-link> or visit <github-link>GitHub</github-link>.',
spanBuilder: (tag) => FormattedSpan(
onTap: switch (tag) {
'docs-link' => () => ScaffoldMessenger.of(
context,
).showSnackBar(const SnackBar(content: Text('Docs link clicked!'))),
'github-link' => () => ScaffoldMessenger.of(
context,
).showSnackBar(const SnackBar(content: Text('GitHub link clicked!'))),
_ => null,
},
),
);
}
}
@@ -1,23 +0,0 @@
import 'package:flutter/material.dart';
import 'package:immich_ui/immich_ui.dart';
class FormattedTextMixedContent extends StatelessWidget {
const FormattedTextMixedContent({super.key});
@override
Widget build(BuildContext context) {
return ImmichFormattedText(
'You can use <b>bold text</b> and <link>links</link> together.',
spanBuilder: (tag) => switch (tag) {
'b' => const FormattedSpan(
style: TextStyle(fontWeight: FontWeight.bold),
),
_ => FormattedSpan(
onTap: () => ScaffoldMessenger.of(
context,
).showSnackBar(const SnackBar(content: Text('Link clicked!'))),
),
},
);
}
}
@@ -1,80 +0,0 @@
import 'package:flutter/material.dart';
import 'package:immich_ui/immich_ui.dart';
import 'package:showcase/routes.dart';
import 'package:showcase/widgets/component_examples.dart';
import 'package:showcase/widgets/example_card.dart';
import 'package:showcase/widgets/page_title.dart';
class FormPage extends StatefulWidget {
const FormPage({super.key});
@override
State<FormPage> createState() => _FormPageState();
}
class _FormPageState extends State<FormPage> {
final _emailController = TextEditingController();
final _passwordController = TextEditingController();
String _result = '';
@override
Widget build(BuildContext context) {
return PageTitle(
title: AppRoute.form.name,
child: ComponentExamples(
title: 'ImmichForm',
subtitle:
'Form container with built-in validation and submit handling.',
examples: [
ExampleCard(
title: 'Login Form',
preview: Column(
children: [
ImmichForm(
submitText: 'Login',
submitIcon: Icons.login,
onSubmit: () async {
await Future.delayed(const Duration(seconds: 1));
setState(() {
_result = 'Form submitted!';
});
},
builder: (context, form) => Column(
spacing: 10,
children: [
ImmichTextInput(
label: 'Email',
controller: _emailController,
keyboardType: TextInputType.emailAddress,
validator: (value) =>
value?.isEmpty ?? true ? 'Required' : null,
),
ImmichPasswordInput(
label: 'Password',
controller: _passwordController,
validator: (value) =>
value?.isEmpty ?? true ? 'Required' : null,
onSubmit: (_) => form.submit(),
),
],
),
),
if (_result.isNotEmpty) ...[
const SizedBox(height: 16),
Text(_result, style: const TextStyle(color: Colors.green)),
],
],
),
),
],
),
);
}
@override
void dispose() {
_emailController.dispose();
_passwordController.dispose();
super.dispose();
}
}
@@ -1,40 +0,0 @@
import 'package:flutter/material.dart';
import 'package:showcase/pages/components/examples/formatted_text_bold_text.dart';
import 'package:showcase/pages/components/examples/formatted_text_links.dart';
import 'package:showcase/pages/components/examples/formatted_text_mixed_tags.dart';
import 'package:showcase/routes.dart';
import 'package:showcase/widgets/component_examples.dart';
import 'package:showcase/widgets/example_card.dart';
import 'package:showcase/widgets/page_title.dart';
class FormattedTextPage extends StatelessWidget {
const FormattedTextPage({super.key});
@override
Widget build(BuildContext context) {
return PageTitle(
title: AppRoute.formattedText.name,
child: ComponentExamples(
title: 'ImmichFormattedText',
subtitle: 'Render text with HTML formatting (bold, links).',
examples: [
ExampleCard(
title: 'Bold Text',
preview: const FormattedTextBoldText(),
code: 'formatted_text_bold_text.dart',
),
ExampleCard(
title: 'Links',
preview: const FormattedTextLinks(),
code: 'formatted_text_links.dart',
),
ExampleCard(
title: 'Mixed Content',
preview: const FormattedTextMixedContent(),
code: 'formatted_text_mixed_tags.dart',
),
],
),
);
}
}
@@ -1,52 +0,0 @@
import 'package:flutter/material.dart';
import 'package:immich_ui/immich_ui.dart';
import 'package:showcase/routes.dart';
import 'package:showcase/widgets/component_examples.dart';
import 'package:showcase/widgets/example_card.dart';
import 'package:showcase/widgets/page_title.dart';
class IconButtonPage extends StatelessWidget {
const IconButtonPage({super.key});
@override
Widget build(BuildContext context) {
return PageTitle(
title: AppRoute.iconButton.name,
child: ComponentExamples(
title: 'ImmichIconButton',
subtitle: 'Icon-only button with customizable styling.',
examples: [
ExampleCard(
title: 'Variants & Colors',
preview: Wrap(
spacing: 12,
runSpacing: 12,
children: [
ImmichIconButton(
icon: Icons.add,
onPressed: () {},
variant: ImmichVariant.filled,
),
ImmichIconButton(
icon: Icons.edit,
onPressed: () {},
variant: ImmichVariant.ghost,
),
ImmichIconButton(
icon: Icons.delete,
onPressed: () {},
color: ImmichColor.secondary,
),
ImmichIconButton(
icon: Icons.settings,
onPressed: () {},
disabled: true,
),
],
),
),
],
),
);
}
}
@@ -1,39 +0,0 @@
import 'package:flutter/material.dart';
import 'package:immich_ui/immich_ui.dart';
import 'package:showcase/routes.dart';
import 'package:showcase/widgets/component_examples.dart';
import 'package:showcase/widgets/example_card.dart';
import 'package:showcase/widgets/page_title.dart';
class PasswordInputPage extends StatelessWidget {
const PasswordInputPage({super.key});
@override
Widget build(BuildContext context) {
return PageTitle(
title: AppRoute.passwordInput.name,
child: ComponentExamples(
title: 'ImmichPasswordInput',
subtitle: 'Password field with visibility toggle.',
examples: [
ExampleCard(
title: 'Password Input',
preview: ImmichPasswordInput(
label: 'Password',
hintText: 'Enter your password',
validator: (value) {
if (value == null || value.isEmpty) {
return 'Password is required';
}
if (value.length < 8) {
return 'Password must be at least 8 characters';
}
return null;
},
),
),
],
),
);
}
}
@@ -1,140 +0,0 @@
import 'package:flutter/material.dart';
import 'package:immich_ui/immich_ui.dart';
import 'package:showcase/routes.dart';
import 'package:showcase/widgets/component_examples.dart';
import 'package:showcase/widgets/example_card.dart';
import 'package:showcase/widgets/page_title.dart';
class TextButtonPage extends StatefulWidget {
const TextButtonPage({super.key});
@override
State<TextButtonPage> createState() => _TextButtonPageState();
}
class _TextButtonPageState extends State<TextButtonPage> {
bool _isLoading = false;
@override
Widget build(BuildContext context) {
return PageTitle(
title: AppRoute.textButton.name,
child: ComponentExamples(
title: 'ImmichTextButton',
subtitle:
'A versatile button component with multiple variants and color options.',
examples: [
ExampleCard(
title: 'Variants',
description:
'Filled and ghost variants for different visual hierarchy',
preview: Wrap(
spacing: 12,
runSpacing: 12,
children: [
ImmichTextButton(
onPressed: () {},
labelText: 'Filled',
variant: ImmichVariant.filled,
expanded: false,
),
ImmichTextButton(
onPressed: () {},
labelText: 'Ghost',
variant: ImmichVariant.ghost,
expanded: false,
),
],
),
),
ExampleCard(
title: 'Colors',
description: 'Primary and secondary color options',
preview: Wrap(
spacing: 12,
runSpacing: 12,
children: [
ImmichTextButton(
onPressed: () {},
labelText: 'Primary',
color: ImmichColor.primary,
expanded: false,
),
ImmichTextButton(
onPressed: () {},
labelText: 'Secondary',
color: ImmichColor.secondary,
expanded: false,
),
],
),
),
ExampleCard(
title: 'With Icons',
description: 'Add leading icons',
preview: Wrap(
spacing: 12,
runSpacing: 12,
children: [
ImmichTextButton(
onPressed: () {},
labelText: 'With Icon',
icon: Icons.add,
expanded: false,
),
ImmichTextButton(
onPressed: () {},
labelText: 'Download',
icon: Icons.download,
variant: ImmichVariant.ghost,
expanded: false,
),
],
),
),
ExampleCard(
title: 'Loading State',
description: 'Shows loading indicator during async operations',
preview: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
ImmichTextButton(
onPressed: () async {
setState(() => _isLoading = true);
await Future.delayed(const Duration(seconds: 2));
if (mounted) setState(() => _isLoading = false);
},
labelText: _isLoading ? 'Loading...' : 'Click Me',
loading: _isLoading,
expanded: false,
),
],
),
),
ExampleCard(
title: 'Disabled State',
description: 'Buttons can be disabled',
preview: Wrap(
spacing: 12,
runSpacing: 12,
children: [
ImmichTextButton(
onPressed: () {},
labelText: 'Disabled',
disabled: true,
expanded: false,
),
ImmichTextButton(
onPressed: () {},
labelText: 'Disabled Ghost',
variant: ImmichVariant.ghost,
disabled: true,
expanded: false,
),
],
),
),
],
),
);
}
}
@@ -1,65 +0,0 @@
import 'package:flutter/material.dart';
import 'package:immich_ui/immich_ui.dart';
import 'package:showcase/routes.dart';
import 'package:showcase/widgets/component_examples.dart';
import 'package:showcase/widgets/example_card.dart';
import 'package:showcase/widgets/page_title.dart';
class TextInputPage extends StatefulWidget {
const TextInputPage({super.key});
@override
State<TextInputPage> createState() => _TextInputPageState();
}
class _TextInputPageState extends State<TextInputPage> {
final _controller1 = TextEditingController();
final _controller2 = TextEditingController();
@override
Widget build(BuildContext context) {
return PageTitle(
title: AppRoute.textInput.name,
child: ComponentExamples(
title: 'ImmichTextInput',
subtitle: 'Text field with validation support.',
examples: [
ExampleCard(
title: 'Basic Usage',
preview: Column(
children: [
ImmichTextInput(
label: 'Email',
hintText: 'Enter your email',
controller: _controller1,
keyboardType: TextInputType.emailAddress,
),
const SizedBox(height: 16),
ImmichTextInput(
label: 'Username',
controller: _controller2,
validator: (value) {
if (value == null || value.isEmpty) {
return 'Username is required';
}
if (value.length < 3) {
return 'Username must be at least 3 characters';
}
return null;
},
),
],
),
),
],
),
);
}
@override
void dispose() {
_controller1.dispose();
_controller2.dispose();
super.dispose();
}
}
@@ -1,396 +0,0 @@
import 'package:flutter/material.dart';
import 'package:immich_ui/immich_ui.dart';
import 'package:showcase/routes.dart';
import 'package:showcase/widgets/component_examples.dart';
import 'package:showcase/widgets/example_card.dart';
import 'package:showcase/widgets/page_title.dart';
class ConstantsPage extends StatefulWidget {
const ConstantsPage({super.key});
@override
State<ConstantsPage> createState() => _ConstantsPageState();
}
class _ConstantsPageState extends State<ConstantsPage> {
@override
Widget build(BuildContext context) {
return PageTitle(
title: AppRoute.constants.name,
child: ComponentExamples(
title: 'Constants',
subtitle: 'Consistent spacing, sizing, and styling constants.',
expand: true,
examples: [
const ExampleCard(
title: 'Spacing',
description: 'ImmichSpacing (4.0 → 48.0)',
preview: Column(
children: [
_SpacingBox(label: 'xs', size: ImmichSpacing.xs),
_SpacingBox(label: 'sm', size: ImmichSpacing.sm),
_SpacingBox(label: 'md', size: ImmichSpacing.md),
_SpacingBox(label: 'lg', size: ImmichSpacing.lg),
_SpacingBox(label: 'xl', size: ImmichSpacing.xl),
_SpacingBox(label: 'xxl', size: ImmichSpacing.xxl),
_SpacingBox(label: 'xxxl', size: ImmichSpacing.xxxl),
],
),
),
const ExampleCard(
title: 'Border Radius',
description: 'ImmichRadius (0.0 → 24.0)',
preview: Wrap(
spacing: 12,
runSpacing: 12,
children: [
_RadiusBox(label: 'none', radius: ImmichRadius.none),
_RadiusBox(label: 'xs', radius: ImmichRadius.xs),
_RadiusBox(label: 'sm', radius: ImmichRadius.sm),
_RadiusBox(label: 'md', radius: ImmichRadius.md),
_RadiusBox(label: 'lg', radius: ImmichRadius.lg),
_RadiusBox(label: 'xl', radius: ImmichRadius.xl),
_RadiusBox(label: 'xxl', radius: ImmichRadius.xxl),
],
),
),
const ExampleCard(
title: 'Icon Sizes',
description: 'ImmichIconSize (16.0 → 48.0)',
preview: Wrap(
spacing: 16,
runSpacing: 16,
alignment: WrapAlignment.start,
children: [
_IconSizeBox(label: 'xs', size: ImmichIconSize.xs),
_IconSizeBox(label: 'sm', size: ImmichIconSize.sm),
_IconSizeBox(label: 'md', size: ImmichIconSize.md),
_IconSizeBox(label: 'lg', size: ImmichIconSize.lg),
_IconSizeBox(label: 'xl', size: ImmichIconSize.xl),
_IconSizeBox(label: 'xxl', size: ImmichIconSize.xxl),
],
),
),
const ExampleCard(
title: 'Text Sizes',
description: 'ImmichTextSize (10.0 → 60.0)',
preview: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Caption',
style: TextStyle(fontSize: ImmichTextSize.caption),
),
Text('Label', style: TextStyle(fontSize: ImmichTextSize.label)),
Text('Body', style: TextStyle(fontSize: ImmichTextSize.body)),
Text('H6', style: TextStyle(fontSize: ImmichTextSize.h6)),
Text('H5', style: TextStyle(fontSize: ImmichTextSize.h5)),
Text('H4', style: TextStyle(fontSize: ImmichTextSize.h4)),
Text('H3', style: TextStyle(fontSize: ImmichTextSize.h3)),
Text('H2', style: TextStyle(fontSize: ImmichTextSize.h2)),
Text('H1', style: TextStyle(fontSize: ImmichTextSize.h1)),
],
),
),
const ExampleCard(
title: 'Elevation',
description: 'ImmichElevation (0.0 → 16.0)',
preview: Wrap(
spacing: 12,
runSpacing: 12,
children: [
_ElevationBox(label: 'none', elevation: ImmichElevation.none),
_ElevationBox(label: 'xs', elevation: ImmichElevation.xs),
_ElevationBox(label: 'sm', elevation: ImmichElevation.sm),
_ElevationBox(label: 'md', elevation: ImmichElevation.md),
_ElevationBox(label: 'lg', elevation: ImmichElevation.lg),
_ElevationBox(label: 'xl', elevation: ImmichElevation.xl),
_ElevationBox(label: 'xxl', elevation: ImmichElevation.xxl),
],
),
),
const ExampleCard(
title: 'Border Width',
description: 'ImmichBorderWidth (0.5 → 4.0)',
preview: Column(
children: [
_BorderBox(
label: 'hairline',
borderWidth: ImmichBorderWidth.hairline,
),
_BorderBox(label: 'base', borderWidth: ImmichBorderWidth.base),
_BorderBox(label: 'md', borderWidth: ImmichBorderWidth.md),
_BorderBox(label: 'lg', borderWidth: ImmichBorderWidth.lg),
_BorderBox(label: 'xl', borderWidth: ImmichBorderWidth.xl),
],
),
),
const ExampleCard(
title: 'Animation Durations',
description: 'ImmichDuration (100ms → 700ms)',
preview: Column(
crossAxisAlignment: CrossAxisAlignment.start,
spacing: 8,
children: [
_AnimatedDurationBox(
label: 'Extra Fast',
duration: ImmichDuration.extraFast,
),
_AnimatedDurationBox(
label: 'Fast',
duration: ImmichDuration.fast,
),
_AnimatedDurationBox(
label: 'Normal',
duration: ImmichDuration.normal,
),
_AnimatedDurationBox(
label: 'Slow',
duration: ImmichDuration.slow,
),
_AnimatedDurationBox(
label: 'Extra Slow',
duration: ImmichDuration.extraSlow,
),
],
),
),
],
),
);
}
}
class _SpacingBox extends StatelessWidget {
final String label;
final double size;
const _SpacingBox({required this.label, required this.size});
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 4),
child: Row(
children: [
SizedBox(
width: 60,
child: Text(
label,
style: const TextStyle(fontFamily: 'GoogleSansCode'),
),
),
Container(
width: size,
height: 24,
color: Theme.of(context).colorScheme.primary,
),
const SizedBox(width: 8),
Text('${size.toStringAsFixed(1)}px'),
],
),
);
}
}
class _RadiusBox extends StatelessWidget {
final String label;
final double radius;
const _RadiusBox({required this.label, required this.radius});
@override
Widget build(BuildContext context) {
return Column(
children: [
Container(
width: 60,
height: 60,
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.primary,
borderRadius: BorderRadius.circular(radius),
),
),
const SizedBox(height: 4),
Text(label, style: const TextStyle(fontSize: 12)),
],
);
}
}
class _IconSizeBox extends StatelessWidget {
final String label;
final double size;
const _IconSizeBox({required this.label, required this.size});
@override
Widget build(BuildContext context) {
return Column(
children: [
Icon(Icons.palette_rounded, size: size),
const SizedBox(height: 4),
Text(label, style: const TextStyle(fontSize: 12)),
Text(
'${size.toStringAsFixed(0)}px',
style: const TextStyle(fontSize: 10, color: Colors.grey),
),
],
);
}
}
class _ElevationBox extends StatelessWidget {
final String label;
final double elevation;
const _ElevationBox({required this.label, required this.elevation});
@override
Widget build(BuildContext context) {
return Column(
children: [
Material(
elevation: elevation,
borderRadius: const BorderRadius.all(Radius.circular(8)),
child: Container(
width: 60,
height: 60,
alignment: Alignment.center,
child: Text(label, style: const TextStyle(fontSize: 12)),
),
),
const SizedBox(height: 4),
Text(
elevation.toStringAsFixed(1),
style: const TextStyle(fontSize: 10),
),
],
);
}
}
class _BorderBox extends StatelessWidget {
final String label;
final double borderWidth;
const _BorderBox({required this.label, required this.borderWidth});
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 4),
child: Row(
children: [
SizedBox(
width: 80,
child: Text(
label,
style: const TextStyle(fontFamily: 'GoogleSansCode'),
),
),
Expanded(
child: Container(
height: 40,
decoration: BoxDecoration(
border: Border.all(
color: Theme.of(context).colorScheme.primary,
width: borderWidth,
),
borderRadius: const BorderRadius.all(Radius.circular(4)),
),
),
),
const SizedBox(width: 8),
Text('${borderWidth.toStringAsFixed(1)}px'),
],
),
);
}
}
class _AnimatedDurationBox extends StatefulWidget {
final String label;
final Duration duration;
const _AnimatedDurationBox({required this.label, required this.duration});
@override
State<_AnimatedDurationBox> createState() => _AnimatedDurationBoxState();
}
class _AnimatedDurationBoxState extends State<_AnimatedDurationBox> {
bool _atEnd = false;
bool _isAnimating = false;
void _playAnimation() async {
if (_isAnimating) return;
setState(() => _isAnimating = true);
setState(() => _atEnd = true);
await Future.delayed(widget.duration);
if (!mounted) return;
setState(() => _atEnd = false);
await Future.delayed(widget.duration);
if (!mounted) return;
setState(() => _isAnimating = false);
}
@override
Widget build(BuildContext context) {
final colorScheme = Theme.of(context).colorScheme;
return Row(
children: [
SizedBox(
width: 90,
child: Text(
widget.label,
style: const TextStyle(fontFamily: 'GoogleSansCode', fontSize: 12),
),
),
Expanded(
child: Container(
height: 32,
decoration: BoxDecoration(
color: colorScheme.surfaceContainerHighest.withValues(alpha: 0.5),
borderRadius: BorderRadius.circular(6),
),
child: AnimatedAlign(
duration: widget.duration,
curve: Curves.easeInOut,
alignment: _atEnd ? Alignment.centerRight : Alignment.centerLeft,
child: Container(
width: 60,
height: 28,
margin: const EdgeInsets.symmetric(horizontal: 2),
decoration: BoxDecoration(
color: colorScheme.primary,
borderRadius: BorderRadius.circular(4),
),
alignment: Alignment.center,
child: Text(
'${widget.duration.inMilliseconds}ms',
style: TextStyle(
fontSize: 11,
color: colorScheme.onPrimary,
fontWeight: FontWeight.w500,
),
),
),
),
),
),
const SizedBox(width: 8),
IconButton(
onPressed: _isAnimating ? null : _playAnimation,
icon: Icon(
Icons.play_arrow_rounded,
color: _isAnimating ? colorScheme.outline : colorScheme.primary,
),
iconSize: 24,
padding: EdgeInsets.zero,
constraints: const BoxConstraints(minWidth: 32, minHeight: 32),
),
],
);
}
}
@@ -1,118 +0,0 @@
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:showcase/constants.dart';
import 'package:showcase/routes.dart';
class HomePage extends StatelessWidget {
final VoidCallback onThemeToggle;
const HomePage({super.key, required this.onThemeToggle});
@override
Widget build(BuildContext context) {
return Title(
title: appTitle,
color: Theme.of(context).colorScheme.primary,
child: ListView(
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 32),
children: [
Text(
appTitle,
style: Theme.of(context).textTheme.displaySmall?.copyWith(
fontWeight: FontWeight.bold,
color: Theme.of(context).colorScheme.onSurface,
),
),
const SizedBox(height: 12),
Text(
'A collection of Flutter components that are shared across all Immich projects',
style: Theme.of(context).textTheme.titleMedium?.copyWith(
color: Theme.of(context).colorScheme.onSurfaceVariant,
fontWeight: FontWeight.w400,
height: 1.5,
),
),
const SizedBox(height: 48),
...routesByCategory.entries.map((entry) {
if (entry.key == AppRouteCategory.root) {
return const SizedBox.shrink();
}
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
entry.key.displayName,
style: Theme.of(context).textTheme.headlineSmall?.copyWith(
fontWeight: FontWeight.w600,
color: Theme.of(context).colorScheme.onSurface,
),
),
const SizedBox(height: 16),
GridView.builder(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 3,
crossAxisSpacing: LayoutConstants.gridSpacing,
mainAxisSpacing: LayoutConstants.gridSpacing,
childAspectRatio: LayoutConstants.gridAspectRatio,
),
itemCount: entry.value.length,
itemBuilder: (context, index) {
return _ComponentCard(route: entry.value[index]);
},
),
const SizedBox(height: 48),
],
);
}),
],
),
);
}
}
class _ComponentCard extends StatelessWidget {
final AppRoute route;
const _ComponentCard({required this.route});
@override
Widget build(BuildContext context) {
return InkWell(
onTap: () => context.go(route.path),
borderRadius: const BorderRadius.all(Radius.circular(LayoutConstants.borderRadiusLarge)),
child: Card(
child: Padding(
padding: const EdgeInsets.all(20),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
Icon(route.icon, size: 32, color: Theme.of(context).colorScheme.primary),
const SizedBox(height: 16),
Text(
route.name,
style: Theme.of(context).textTheme.titleMedium?.copyWith(
fontWeight: FontWeight.w600,
color: Theme.of(context).colorScheme.onSurface,
),
),
const SizedBox(height: 8),
Text(
route.description,
style: Theme.of(
context,
).textTheme.bodyMedium?.copyWith(color: Theme.of(context).colorScheme.onSurfaceVariant, height: 1.4),
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
],
),
),
),
);
}
}
@@ -1,48 +0,0 @@
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:showcase/pages/components/close_button_page.dart';
import 'package:showcase/pages/components/form_page.dart';
import 'package:showcase/pages/components/formatted_text_page.dart';
import 'package:showcase/pages/components/icon_button_page.dart';
import 'package:showcase/pages/components/password_input_page.dart';
import 'package:showcase/pages/components/text_button_page.dart';
import 'package:showcase/pages/components/text_input_page.dart';
import 'package:showcase/pages/design_system/constants_page.dart';
import 'package:showcase/pages/home_page.dart';
import 'package:showcase/routes.dart';
import 'package:showcase/widgets/shell_layout.dart';
class AppRouter {
static GoRouter createRouter(VoidCallback onThemeToggle) {
return GoRouter(
initialLocation: AppRoute.home.path,
routes: [
ShellRoute(
builder: (context, state, child) =>
ShellLayout(onThemeToggle: onThemeToggle, child: child),
routes: AppRoute.values
.map(
(route) => GoRoute(
path: route.path,
pageBuilder: (context, state) => NoTransitionPage(
key: state.pageKey,
child: switch (route) {
AppRoute.home => HomePage(onThemeToggle: onThemeToggle),
AppRoute.textButton => const TextButtonPage(),
AppRoute.iconButton => const IconButtonPage(),
AppRoute.closeButton => const CloseButtonPage(),
AppRoute.textInput => const TextInputPage(),
AppRoute.passwordInput => const PasswordInputPage(),
AppRoute.form => const FormPage(),
AppRoute.formattedText => const FormattedTextPage(),
AppRoute.constants => const ConstantsPage(),
},
),
),
)
.toList(),
),
],
);
}
}
@@ -1,97 +0,0 @@
import 'package:flutter/material.dart';
enum AppRouteCategory {
root(''),
forms('Forms'),
buttons('Buttons'),
designSystem('Design System');
final String displayName;
const AppRouteCategory(this.displayName);
}
enum AppRoute {
home(
name: 'Home',
description: 'Home page',
path: '/',
category: AppRouteCategory.root,
icon: Icons.home_outlined,
),
textButton(
name: 'Text Button',
description: 'Versatile button with filled and ghost variants',
path: '/text-button',
category: AppRouteCategory.buttons,
icon: Icons.smart_button_rounded,
),
iconButton(
name: 'Icon Button',
description: 'Icon-only button with customizable styling',
path: '/icon-button',
category: AppRouteCategory.buttons,
icon: Icons.radio_button_unchecked_rounded,
),
closeButton(
name: 'Close Button',
description: 'Pre-configured close button for dialogs',
path: '/close-button',
category: AppRouteCategory.buttons,
icon: Icons.close_rounded,
),
textInput(
name: 'Text Input',
description: 'Text field with validation support',
path: '/text-input',
category: AppRouteCategory.forms,
icon: Icons.text_fields_outlined,
),
passwordInput(
name: 'Password Input',
description: 'Password field with visibility toggle',
path: '/password-input',
category: AppRouteCategory.forms,
icon: Icons.password_outlined,
),
form(
name: 'Form',
description: 'Form container with built-in validation',
path: '/form',
category: AppRouteCategory.forms,
icon: Icons.description_outlined,
),
formattedText(
name: 'Formatted Text',
description: 'Render text with HTML formatting',
path: '/formatted-text',
category: AppRouteCategory.forms,
icon: Icons.code_rounded,
),
constants(
name: 'Constants',
description: 'Spacing, colors, typography, and more',
path: '/constants',
category: AppRouteCategory.designSystem,
icon: Icons.palette_outlined,
);
final String name;
final String description;
final String path;
final AppRouteCategory category;
final IconData icon;
const AppRoute({
required this.name,
required this.description,
required this.path,
required this.category,
required this.icon,
});
}
final routesByCategory = AppRoute.values
.fold<Map<AppRouteCategory, List<AppRoute>>>({}, (map, route) {
map.putIfAbsent(route.category, () => []).add(route);
return map;
});
@@ -1,85 +0,0 @@
import 'package:flutter/material.dart';
class ComponentExamples extends StatelessWidget {
final String title;
final String? subtitle;
final List<Widget> examples;
final bool expand;
const ComponentExamples({
super.key,
required this.title,
this.subtitle,
required this.examples,
this.expand = false,
});
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.fromLTRB(10, 24, 24, 24),
child: CustomScrollView(
slivers: [
SliverToBoxAdapter(
child: _PageHeader(title: title, subtitle: subtitle),
),
const SliverPadding(padding: EdgeInsets.only(top: 24)),
if (expand)
SliverList.builder(
itemCount: examples.length,
itemBuilder: (context, index) => examples[index],
)
else
SliverLayoutBuilder(
builder: (context, constraints) {
return SliverList.builder(
itemCount: examples.length,
itemBuilder: (context, index) => Align(
alignment: Alignment.centerLeft,
child: ConstrainedBox(
constraints: BoxConstraints(
minWidth: constraints.crossAxisExtent * 0.6,
maxWidth: constraints.crossAxisExtent,
),
child: IntrinsicWidth(child: examples[index]),
),
),
);
},
),
],
),
);
}
}
class _PageHeader extends StatelessWidget {
final String title;
final String? subtitle;
const _PageHeader({required this.title, this.subtitle});
@override
Widget build(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
title,
style: Theme.of(
context,
).textTheme.headlineLarge?.copyWith(fontWeight: FontWeight.bold),
),
if (subtitle != null) ...[
const SizedBox(height: 8),
Text(
subtitle!,
style: Theme.of(context).textTheme.bodyLarge?.copyWith(
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
),
],
],
);
}
}
@@ -1,237 +0,0 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:showcase/constants.dart';
import 'package:syntax_highlight/syntax_highlight.dart';
late final Highlighter _codeHighlighter;
Future<void> initializeCodeHighlighter() async {
await Highlighter.initialize(['dart']);
final darkTheme = await HighlighterTheme.loadFromAssets([
'assets/themes/github_dark.json',
], const TextStyle(color: Color(0xFFe1e4e8)));
_codeHighlighter = Highlighter(language: 'dart', theme: darkTheme);
}
class ExampleCard extends StatefulWidget {
final String title;
final String? description;
final Widget preview;
final String? code;
const ExampleCard({
super.key,
required this.title,
this.description,
required this.preview,
this.code,
});
@override
State<ExampleCard> createState() => _ExampleCardState();
}
class _ExampleCardState extends State<ExampleCard> {
bool _showPreview = true;
String? code;
@override
void initState() {
super.initState();
if (widget.code != null) {
rootBundle
.loadString('lib/pages/components/examples/${widget.code!}')
.then((value) {
setState(() {
code = value;
});
});
}
}
@override
Widget build(BuildContext context) {
return Card(
elevation: 1,
margin: const EdgeInsets.only(bottom: 16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.all(16.0),
child: Row(
children: [
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
widget.title,
style: Theme.of(context).textTheme.titleMedium
?.copyWith(fontWeight: FontWeight.bold),
),
const SizedBox(height: 4),
if (widget.description != null)
Text(
widget.description!,
style: Theme.of(context).textTheme.bodyMedium
?.copyWith(
color: Theme.of(
context,
).colorScheme.onSurfaceVariant,
),
),
],
),
),
if (code != null) ...[
const SizedBox(width: 16),
Row(
children: [
_ToggleButton(
icon: Icons.visibility_rounded,
label: 'Preview',
isSelected: _showPreview,
onTap: () => setState(() => _showPreview = true),
),
const SizedBox(width: 8),
_ToggleButton(
icon: Icons.code_rounded,
label: 'Code',
isSelected: !_showPreview,
onTap: () => setState(() => _showPreview = false),
),
],
),
],
],
),
),
const Divider(height: 1),
if (_showPreview)
Padding(
padding: const EdgeInsets.all(16.0),
child: SizedBox(width: double.infinity, child: widget.preview),
)
else
Container(
width: double.infinity,
decoration: const BoxDecoration(
color: Color(0xFF24292e),
borderRadius: BorderRadius.only(
bottomLeft: Radius.circular(
LayoutConstants.borderRadiusMedium,
),
bottomRight: Radius.circular(
LayoutConstants.borderRadiusMedium,
),
),
),
child: _CodeCard(code: code!),
),
],
),
);
}
}
class _ToggleButton extends StatelessWidget {
final IconData icon;
final String label;
final bool isSelected;
final VoidCallback onTap;
const _ToggleButton({
required this.icon,
required this.label,
required this.isSelected,
required this.onTap,
});
@override
Widget build(BuildContext context) {
return InkWell(
onTap: onTap,
borderRadius: const BorderRadius.all(Radius.circular(24)),
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 10),
decoration: BoxDecoration(
color: isSelected
? Theme.of(context).colorScheme.primary.withValues(alpha: 0.7)
: Theme.of(context).colorScheme.primary,
borderRadius: const BorderRadius.all(Radius.circular(24)),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(
icon,
size: 16,
color: Theme.of(context).colorScheme.onPrimary,
),
const SizedBox(width: 6),
Text(
label,
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
color: Theme.of(context).colorScheme.onPrimary,
fontWeight: isSelected ? FontWeight.w600 : FontWeight.w400,
),
),
],
),
),
);
}
}
class _CodeCard extends StatelessWidget {
final String code;
const _CodeCard({required this.code});
@override
Widget build(BuildContext context) {
final lines = code.split('\n');
final lineNumberColor = Colors.white.withValues(alpha: 0.4);
return SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: Padding(
padding: const EdgeInsets.only(left: 12, top: 8, bottom: 8),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.end,
children: List.generate(
lines.length,
(index) => SizedBox(
height: 20,
child: Text(
'${index + 1}',
style: TextStyle(
fontFamily: 'GoogleSansCode',
fontSize: 13,
color: lineNumberColor,
height: 1.5,
),
),
),
),
),
const SizedBox(width: 16),
SelectableText.rich(
_codeHighlighter.highlight(code),
style: const TextStyle(
fontFamily: 'GoogleSansCode',
fontSize: 13,
height: 1.54,
),
),
],
),
),
);
}
}
@@ -1,17 +0,0 @@
import 'package:flutter/material.dart';
class PageTitle extends StatelessWidget {
final String title;
final Widget child;
const PageTitle({super.key, required this.title, required this.child});
@override
Widget build(BuildContext context) {
return Title(
title: '$title | @immich/ui',
color: Theme.of(context).colorScheme.primary,
child: child,
);
}
}

Some files were not shown because too many files have changed in this diff Show More