replace bloc with watch_it
This commit is contained in:
@@ -5,7 +5,7 @@ import 'package:immich_mobile/domain/models/store.model.dart';
|
||||
abstract class IStoreRepository {
|
||||
FutureOr<T?> getValue<T>(StoreKey key);
|
||||
|
||||
FutureOr<void> setValue<T>(StoreKey<T> key, T value);
|
||||
FutureOr<void> setValue<T>(StoreKey key, T value);
|
||||
|
||||
FutureOr<void> deleteValue(StoreKey key);
|
||||
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
import 'package:immich_mobile/domain/models/store.model.dart';
|
||||
|
||||
enum AppSettings<T> {
|
||||
appTheme<int>(StoreKey.appTheme, 10);
|
||||
|
||||
const AppSettings(this.storeKey, this.defaultValue);
|
||||
|
||||
final StoreKey storeKey;
|
||||
final T defaultValue;
|
||||
}
|
||||
@@ -1,10 +1,7 @@
|
||||
/// Key for each possible value in the `Store`.
|
||||
/// Defines the data type for each value
|
||||
enum StoreKey<T> {
|
||||
// Server endpoint related stores
|
||||
accessToken<String>(0, type: String),
|
||||
serverEndpoint<String>(1, type: String),
|
||||
;
|
||||
enum StoreKey {
|
||||
appTheme(1000, type: int);
|
||||
|
||||
const StoreKey(this.id, {required this.type});
|
||||
final int id;
|
||||
@@ -45,7 +42,7 @@ class StoreValue {
|
||||
}
|
||||
}
|
||||
|
||||
static StoreValue of<T>(StoreKey<T> key, T? value) {
|
||||
static StoreValue of<T>(StoreKey key, T? value) {
|
||||
int? i;
|
||||
String? s;
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ class StoreDriftRepository implements IStoreRepository {
|
||||
}
|
||||
|
||||
@override
|
||||
FutureOr<void> setValue<T>(StoreKey<T> key, T value) {
|
||||
FutureOr<void> setValue<T>(StoreKey key, T value) {
|
||||
return db.transaction(() async {
|
||||
final storeValue = StoreValue.of(key, value);
|
||||
await db.into(db.store).insertOnConflictUpdate(StoreCompanion.insert(
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
import 'package:get_it/get_it.dart';
|
||||
import 'package:immich_mobile/domain/interfaces/log.interface.dart';
|
||||
import 'package:immich_mobile/domain/interfaces/store.interface.dart';
|
||||
import 'package:immich_mobile/domain/repositories/database.repository.dart';
|
||||
import 'package:immich_mobile/domain/repositories/log.repository.dart';
|
||||
import 'package:immich_mobile/domain/repositories/store.repository.dart';
|
||||
import 'package:immich_mobile/domain/store_manager.dart';
|
||||
|
||||
/// Ambient instance
|
||||
final getIt = GetIt.instance;
|
||||
|
||||
class ServiceLocator {
|
||||
const ServiceLocator._internal();
|
||||
|
||||
static void configureServices() {
|
||||
// Register DB
|
||||
getIt.registerSingleton<DriftDatabaseRepository>(DriftDatabaseRepository());
|
||||
_registerCoreServices();
|
||||
}
|
||||
|
||||
static void _registerCoreServices() {
|
||||
// Init store
|
||||
getIt
|
||||
.registerFactory<IStoreRepository>(() => StoreDriftRepository(getIt()));
|
||||
getIt.registerSingleton<StoreManager>(StoreManager(getIt()));
|
||||
// Logs
|
||||
getIt.registerFactory<ILogRepository>(() => LogDriftRepository(getIt()));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
import 'package:immich_mobile/domain/models/app_setting.model.dart';
|
||||
import 'package:immich_mobile/domain/store_manager.dart';
|
||||
|
||||
class AppSettingsService {
|
||||
final StoreManager store;
|
||||
|
||||
const AppSettingsService(this.store);
|
||||
|
||||
T getSetting<T>(AppSettings<T> setting) {
|
||||
return store.get(setting.storeKey, setting.defaultValue);
|
||||
}
|
||||
|
||||
void setSetting<T>(AppSettings<T> setting, T value) {
|
||||
store.put(setting.storeKey, value);
|
||||
}
|
||||
|
||||
Stream<T> watchSetting<T>(AppSettings<T> setting) {
|
||||
return store
|
||||
.watch<T>(setting.storeKey)
|
||||
.map((value) => value ?? setting.defaultValue);
|
||||
}
|
||||
}
|
||||
@@ -55,11 +55,11 @@ class StoreManager with LogContext {
|
||||
}
|
||||
|
||||
/// Returns the stored value for the given key (possibly null)
|
||||
T? tryGet<T>(StoreKey<T> key) => _cache[key.id] as T?;
|
||||
T? tryGet<T>(StoreKey key) => _cache[key.id] as T?;
|
||||
|
||||
/// Returns the stored value for the given key or if null the [defaultValue]
|
||||
/// Throws a [StoreKeyNotFoundException] if both are null
|
||||
T get<T>(StoreKey<T> key, [T? defaultValue]) {
|
||||
T get<T>(StoreKey key, [T? defaultValue]) {
|
||||
final value = _cache[key.id] ?? defaultValue;
|
||||
if (value == null) {
|
||||
throw StoreKeyNotFoundException(key);
|
||||
@@ -68,17 +68,17 @@ class StoreManager with LogContext {
|
||||
}
|
||||
|
||||
/// Watches a specific key for changes
|
||||
Stream<T?> watch<T>(StoreKey<T> key) => _db.watchValue(key);
|
||||
Stream<T?> watch<T>(StoreKey key) => _db.watchValue(key);
|
||||
|
||||
/// Stores the value synchronously in the cache and asynchronously in the DB
|
||||
FutureOr<void> put<T>(StoreKey<T> key, T value) async {
|
||||
FutureOr<void> put<T>(StoreKey key, T value) async {
|
||||
if (_cache[key.id] == value) return Future.value();
|
||||
_cache[key.id] = value;
|
||||
return await _db.setValue(key, value);
|
||||
}
|
||||
|
||||
/// Removes the value synchronously from the cache and asynchronously from the DB
|
||||
Future<void> delete<T>(StoreKey<T> key) async {
|
||||
Future<void> delete(StoreKey key) async {
|
||||
if (_cache[key.id] == null) return Future.value();
|
||||
_cache.remove(key.id);
|
||||
return await _db.deleteValue(key);
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_localizations/flutter_localizations.dart';
|
||||
import 'package:immich_mobile/i18n/strings.g.dart';
|
||||
import 'package:immich_mobile/presentation/router/router.dart';
|
||||
import 'package:watch_it/watch_it.dart';
|
||||
|
||||
class ImmichApp extends StatefulWidget {
|
||||
final ThemeData lightTheme;
|
||||
final ThemeData darkTheme;
|
||||
|
||||
const ImmichApp({
|
||||
required this.lightTheme,
|
||||
required this.darkTheme,
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
State createState() => _ImmichAppState();
|
||||
}
|
||||
|
||||
class _ImmichAppState extends State<ImmichApp> with WidgetsBindingObserver {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final router = di<AppRouter>();
|
||||
|
||||
return MaterialApp.router(
|
||||
locale: TranslationProvider.of(context).flutterLocale,
|
||||
supportedLocales: AppLocaleUtils.supportedLocales,
|
||||
localizationsDelegates: GlobalMaterialLocalizations.delegates,
|
||||
theme: widget.lightTheme,
|
||||
darkTheme: widget.darkTheme,
|
||||
routerConfig: router.config(),
|
||||
);
|
||||
}
|
||||
}
|
||||
+16
-46
@@ -1,63 +1,33 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:immich_mobile/domain/service_locator.dart';
|
||||
import 'package:immich_mobile/presentation/home_page/cubit/home_cubit.dart';
|
||||
import 'package:immich_mobile/i18n/strings.g.dart';
|
||||
import 'package:immich_mobile/immich_app.dart';
|
||||
import 'package:immich_mobile/presentation/theme/states/app_theme.state.dart';
|
||||
import 'package:immich_mobile/presentation/theme/widgets/app_theme_builder.dart';
|
||||
import 'package:immich_mobile/service_locator.dart';
|
||||
import 'package:watch_it/watch_it.dart';
|
||||
|
||||
void main() {
|
||||
// Ensure the bindings are initialized
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
// DI Injection
|
||||
ServiceLocator.configureServices();
|
||||
|
||||
// Init localization
|
||||
LocaleSettings.useDeviceLocale();
|
||||
runApp(const MainWidget());
|
||||
}
|
||||
|
||||
class MainWidget extends StatelessWidget {
|
||||
class MainWidget extends StatelessWidget with WatchItMixin {
|
||||
const MainWidget({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MaterialApp(
|
||||
title: 'Flutter Demo',
|
||||
theme: ThemeData(
|
||||
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
|
||||
useMaterial3: true,
|
||||
),
|
||||
home: MultiBlocProvider(
|
||||
providers: [BlocProvider(create: (context) => HomeCubit())],
|
||||
child: Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text("Immich v2"),
|
||||
),
|
||||
body: BlocConsumer<HomeCubit, HomeState>(
|
||||
listener: (context, state) {
|
||||
print(state);
|
||||
},
|
||||
builder: (context, state) {
|
||||
return Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Text("Album count: ${state.albumCount}"),
|
||||
ElevatedButton(
|
||||
onPressed: () {
|
||||
context.read<HomeCubit>().increaseAlbumCount();
|
||||
},
|
||||
child: const Text("Increase"),
|
||||
),
|
||||
ElevatedButton(
|
||||
onPressed: () {
|
||||
context.read<HomeCubit>().decreaseAlbumCount();
|
||||
},
|
||||
child: const Text("Decrease"),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
final appTheme = watchIt<AppThemeState>().value;
|
||||
|
||||
return TranslationProvider(
|
||||
child: AppThemeBuilder(
|
||||
theme: appTheme,
|
||||
builder: (lightTheme, darkTheme) =>
|
||||
ImmichApp(lightTheme: lightTheme, darkTheme: darkTheme),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
import 'package:bloc/bloc.dart';
|
||||
|
||||
part 'home_state.dart';
|
||||
|
||||
class HomeCubit extends Cubit<HomeState> {
|
||||
HomeCubit() : super(HomeState(albumCount: 0));
|
||||
|
||||
void increaseAlbumCount() {
|
||||
emit(state.copyWith(albumCount: state.albumCount + 1));
|
||||
}
|
||||
|
||||
void decreaseAlbumCount() {
|
||||
emit(state.copyWith(albumCount: state.albumCount - 1));
|
||||
}
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
part of 'home_cubit.dart';
|
||||
|
||||
class HomeState {
|
||||
final int albumCount;
|
||||
HomeState({
|
||||
required this.albumCount,
|
||||
});
|
||||
|
||||
HomeState copyWith({
|
||||
int? albumCount,
|
||||
}) {
|
||||
return HomeState(
|
||||
albumCount: albumCount ?? this.albumCount,
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toMap() {
|
||||
return {
|
||||
'albumCount': albumCount,
|
||||
};
|
||||
}
|
||||
|
||||
factory HomeState.fromMap(Map<String, dynamic> map) {
|
||||
return HomeState(
|
||||
albumCount: map['albumCount']?.toInt() ?? 0,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() => 'HomeState(albumCount: $albumCount)';
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
if (identical(this, other)) return true;
|
||||
|
||||
return other is HomeState && other.albumCount == albumCount;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => albumCount.hashCode;
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
import 'package:auto_route/auto_route.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
@RoutePage()
|
||||
class HomePage extends StatelessWidget {
|
||||
const HomePage({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
import 'package:auto_route/auto_route.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
@RoutePage()
|
||||
class LibraryPage extends StatelessWidget {
|
||||
const LibraryPage({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
import 'package:auto_route/auto_route.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
@RoutePage()
|
||||
class SearchPage extends StatelessWidget {
|
||||
const SearchPage({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
import 'package:auto_route/auto_route.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
@RoutePage()
|
||||
class SettingsPage extends StatelessWidget {
|
||||
const SettingsPage({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
import 'package:auto_route/auto_route.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
@RoutePage()
|
||||
class SharingPage extends StatelessWidget {
|
||||
const SharingPage({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container();
|
||||
}
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
import 'package:auto_route/auto_route.dart';
|
||||
|
||||
part 'router.gr.dart';
|
||||
|
||||
@AutoRouterConfig(replaceInRouteName: 'Page,Route')
|
||||
class AppRouter extends _$AppRouter {
|
||||
@override
|
||||
List<AutoRoute> get routes => [];
|
||||
}
|
||||
@@ -0,0 +1,132 @@
|
||||
import 'package:auto_route/auto_route.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_adaptive_scaffold/flutter_adaptive_scaffold.dart';
|
||||
import 'package:immich_mobile/i18n/strings.g.dart';
|
||||
import 'package:immich_mobile/presentation/router/router.dart';
|
||||
import 'package:material_symbols_icons/symbols.dart';
|
||||
|
||||
@RoutePage()
|
||||
class TabControllerPage extends StatelessWidget {
|
||||
const TabControllerPage({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return AutoTabsRouter(
|
||||
routes: const [
|
||||
HomeRoute(),
|
||||
SearchRoute(),
|
||||
SharingRoute(),
|
||||
LibraryRoute(),
|
||||
],
|
||||
builder: (ctx, child) {
|
||||
final tabsRouter = AutoTabsRouter.of(ctx);
|
||||
// Pop-back to photos tab or if already in photos tab, close the app
|
||||
return PopScope(
|
||||
canPop: tabsRouter.activeIndex == 0,
|
||||
onPopInvoked: (didPop) =>
|
||||
!didPop ? tabsRouter.setActiveIndex(0) : null,
|
||||
child: _TabControllerAdaptiveScaffold(
|
||||
body: (ctxx) => child,
|
||||
selectedIndex: tabsRouter.activeIndex,
|
||||
onSelectedIndexChange: (index) => tabsRouter.setActiveIndex(index),
|
||||
destinations: [
|
||||
NavigationDestination(
|
||||
icon: const Icon(Symbols.photo_rounded),
|
||||
selectedIcon: const Icon(Symbols.photo_rounded, fill: 1.0),
|
||||
label: context.t.tab_controller.photos,
|
||||
),
|
||||
NavigationDestination(
|
||||
icon: const Icon(Symbols.search_rounded),
|
||||
selectedIcon: const Icon(Symbols.search_rounded, fill: 1.0),
|
||||
label: context.t.tab_controller.search,
|
||||
),
|
||||
NavigationDestination(
|
||||
icon: const Icon(Symbols.group_rounded),
|
||||
selectedIcon: const Icon(Symbols.group_rounded, fill: 1.0),
|
||||
label: context.t.tab_controller.sharing,
|
||||
),
|
||||
NavigationDestination(
|
||||
icon: const Icon(Symbols.newsstand_rounded),
|
||||
selectedIcon: const Icon(Symbols.newsstand_rounded, fill: 1.0),
|
||||
label: context.t.tab_controller.library,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Adaptive scaffold to layout bottom navigation bar and navigation rail for the main
|
||||
/// tab controller layout. This is not used elsewhere so is private to this widget
|
||||
class _TabControllerAdaptiveScaffold extends StatelessWidget {
|
||||
const _TabControllerAdaptiveScaffold({
|
||||
required this.body,
|
||||
required this.selectedIndex,
|
||||
required this.onSelectedIndexChange,
|
||||
required this.destinations,
|
||||
});
|
||||
|
||||
final WidgetBuilder body;
|
||||
final List<NavigationDestination> destinations;
|
||||
final int selectedIndex;
|
||||
final void Function(int) onSelectedIndexChange;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final NavigationRailThemeData navRailTheme =
|
||||
Theme.of(context).navigationRailTheme;
|
||||
|
||||
return Scaffold(
|
||||
body: AdaptiveLayout(
|
||||
// No animation on layout change
|
||||
transitionDuration: Duration.zero,
|
||||
primaryNavigation: SlotLayout(
|
||||
config: <Breakpoint, SlotLayoutConfig>{
|
||||
Breakpoints.mediumAndUp: SlotLayout.from(
|
||||
key: const Key(
|
||||
'_TabControllerAdaptiveScaffold Primary Navigation Medium',
|
||||
),
|
||||
builder: (_) => AdaptiveScaffold.standardNavigationRail(
|
||||
selectedIndex: selectedIndex,
|
||||
destinations: destinations
|
||||
.map((NavigationDestination destination) =>
|
||||
AdaptiveScaffold.toRailDestination(destination))
|
||||
.toList(),
|
||||
onDestinationSelected: onSelectedIndexChange,
|
||||
backgroundColor: navRailTheme.backgroundColor,
|
||||
selectedIconTheme: navRailTheme.selectedIconTheme,
|
||||
unselectedIconTheme: navRailTheme.unselectedIconTheme,
|
||||
selectedLabelTextStyle: navRailTheme.selectedLabelTextStyle,
|
||||
unSelectedLabelTextStyle: navRailTheme.unselectedLabelTextStyle,
|
||||
),
|
||||
),
|
||||
},
|
||||
),
|
||||
body: SlotLayout(
|
||||
config: {
|
||||
Breakpoints.standard: SlotLayout.from(
|
||||
key: const Key('_TabControllerAdaptiveScaffold Body'),
|
||||
builder: body,
|
||||
),
|
||||
},
|
||||
),
|
||||
),
|
||||
bottomNavigationBar: SlotLayout(
|
||||
config: <Breakpoint, SlotLayoutConfig>{
|
||||
Breakpoints.small: SlotLayout.from(
|
||||
key: const Key(
|
||||
'_TabControllerAdaptiveScaffold Bottom Navigation Small',
|
||||
),
|
||||
builder: (_) => AdaptiveScaffold.standardBottomNavigationBar(
|
||||
currentIndex: selectedIndex,
|
||||
destinations: destinations,
|
||||
onDestinationSelected: onSelectedIndexChange,
|
||||
),
|
||||
),
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
import 'package:auto_route/auto_route.dart';
|
||||
import 'package:immich_mobile/presentation/modules/home/pages/home.page.dart';
|
||||
import 'package:immich_mobile/presentation/modules/library/pages/library.page.dart';
|
||||
import 'package:immich_mobile/presentation/modules/search/pages/search.page.dart';
|
||||
import 'package:immich_mobile/presentation/modules/settings/pages/settings.page.dart';
|
||||
import 'package:immich_mobile/presentation/modules/sharing/pages/sharing.page.dart';
|
||||
import 'package:immich_mobile/presentation/router/pages/tab_controller.page.dart';
|
||||
|
||||
part 'router.gr.dart';
|
||||
|
||||
@AutoRouterConfig(replaceInRouteName: 'Page,Route')
|
||||
class AppRouter extends _$AppRouter {
|
||||
AppRouter();
|
||||
|
||||
@override
|
||||
List<AutoRoute> get routes => [
|
||||
AutoRoute(page: TabControllerRoute.page, initial: true, children: [
|
||||
AutoRoute(page: HomeRoute.page),
|
||||
AutoRoute(page: SearchRoute.page),
|
||||
AutoRoute(page: SharingRoute.page),
|
||||
AutoRoute(page: LibraryRoute.page),
|
||||
]),
|
||||
AutoRoute(page: SettingsRoute.page),
|
||||
];
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:immich_mobile/domain/models/app_setting.model.dart';
|
||||
import 'package:immich_mobile/domain/services/app_setting.service.dart';
|
||||
import 'package:immich_mobile/presentation/theme/utils/colors.dart';
|
||||
|
||||
class AppThemeState extends ValueNotifier<AppTheme> {
|
||||
final AppSettingsService _appSettings;
|
||||
StreamSubscription? _appSettingSubscription;
|
||||
|
||||
AppThemeState({required AppSettingsService appSettings})
|
||||
: _appSettings = appSettings,
|
||||
super(AppTheme.blue);
|
||||
|
||||
void init() {
|
||||
_appSettingSubscription =
|
||||
_appSettings.watchSetting(AppSettings.appTheme).listen((themeIndex) {
|
||||
final theme =
|
||||
AppTheme.values.elementAtOrNull(themeIndex) ?? AppTheme.blue;
|
||||
value = theme;
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_appSettingSubscription?.cancel();
|
||||
return super.dispose();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
enum AppTheme {
|
||||
blue(AppColors._blueLight, AppColors._blueDark),
|
||||
// Fallback color for dynamic theme for non-supported platforms
|
||||
dynamic(AppColors._blueLight, AppColors._blueDark);
|
||||
|
||||
final ColorScheme lightSchema;
|
||||
final ColorScheme darkSchema;
|
||||
|
||||
const AppTheme(this.lightSchema, this.darkSchema);
|
||||
}
|
||||
|
||||
class AppColors {
|
||||
const AppColors();
|
||||
|
||||
/// Blue color
|
||||
static const ColorScheme _blueLight = ColorScheme(
|
||||
brightness: Brightness.light,
|
||||
primary: Color(0xff1565c0),
|
||||
onPrimary: Color(0xffffffff),
|
||||
primaryContainer: Color(0xffd6e3ff),
|
||||
onPrimaryContainer: Color(0xff001b3d),
|
||||
secondary: Color(0xff3277d2),
|
||||
onSecondary: Color(0xfffdfbff),
|
||||
secondaryContainer: Color(0xffecf0ff),
|
||||
onSecondaryContainer: Color(0xff001b3d),
|
||||
tertiary: Color(0xff7b4d88),
|
||||
onTertiary: Color(0xfffffbff),
|
||||
tertiaryContainer: Color(0xfffad7ff),
|
||||
onTertiaryContainer: Color(0xff310540),
|
||||
error: Color(0xffba1a1a),
|
||||
onError: Color(0xfffffbff),
|
||||
errorContainer: Color(0xffffdad6),
|
||||
onErrorContainer: Color(0xff410002),
|
||||
background: Color(0xfffcfafe),
|
||||
onBackground: Color(0xff191c20),
|
||||
surface: Color(0xfffdfbff),
|
||||
onSurface: Color(0xff191c20),
|
||||
surfaceVariant: Color(0xffdfe2ef),
|
||||
onSurfaceVariant: Color(0xff424751),
|
||||
outline: Color(0xff737782),
|
||||
outlineVariant: Color(0xffc2c6d2),
|
||||
shadow: Color(0xff000000),
|
||||
scrim: Color(0xff000000),
|
||||
inverseSurface: Color(0xff2e3036),
|
||||
onInverseSurface: Color(0xfff0f0f7),
|
||||
inversePrimary: Color(0xffa9c7ff),
|
||||
surfaceTint: Color(0xff00468c),
|
||||
);
|
||||
|
||||
static const ColorScheme _blueDark = ColorScheme(
|
||||
brightness: Brightness.dark,
|
||||
primary: Color(0xffa9c7ff),
|
||||
onPrimary: Color(0xff001b3d),
|
||||
primaryContainer: Color(0xff00468c),
|
||||
onPrimaryContainer: Color(0xffd6e3ff),
|
||||
secondary: Color(0xffd6e3ff),
|
||||
onSecondary: Color(0xff001b3d),
|
||||
secondaryContainer: Color(0xff003063),
|
||||
onSecondaryContainer: Color(0xffd6e3ff),
|
||||
tertiary: Color(0xffeab4f6),
|
||||
onTertiary: Color(0xff310540),
|
||||
tertiaryContainer: Color(0xff61356e),
|
||||
onTertiaryContainer: Color(0xfffad7ff),
|
||||
error: Color(0xffffb4ab),
|
||||
onError: Color(0xff410002),
|
||||
errorContainer: Color(0xff93000a),
|
||||
onErrorContainer: Color(0xffffb4ab),
|
||||
background: Color(0xff1a1d21),
|
||||
onBackground: Color(0xffe2e2e9),
|
||||
surface: Color(0xff1a1e22),
|
||||
onSurface: Color(0xffe2e2e9),
|
||||
surfaceVariant: Color(0xff424852),
|
||||
onSurfaceVariant: Color(0xffc2c6d2),
|
||||
outline: Color(0xff8c919c),
|
||||
outlineVariant: Color(0xff424751),
|
||||
shadow: Color(0xff000000),
|
||||
scrim: Color(0xff000000),
|
||||
inverseSurface: Color(0xffe1e1e9),
|
||||
onInverseSurface: Color(0xff2e3036),
|
||||
inversePrimary: Color(0xff005db7),
|
||||
surfaceTint: Color(0xffa9c7ff),
|
||||
);
|
||||
|
||||
static ThemeData getThemeForColorScheme(ColorScheme color) {
|
||||
return ThemeData(
|
||||
primaryColor: color.primary,
|
||||
iconTheme: const IconThemeData(weight: 400),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
import 'package:dynamic_color/dynamic_color.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:immich_mobile/presentation/theme/utils/colors.dart';
|
||||
|
||||
class AppThemeBuilder extends StatelessWidget {
|
||||
const AppThemeBuilder({
|
||||
super.key,
|
||||
required this.theme,
|
||||
required this.builder,
|
||||
});
|
||||
|
||||
/// Current app theme to switch the theme data used
|
||||
final AppTheme theme;
|
||||
|
||||
/// Builds the child widget of this widget, providing a light and dark [ThemeData] based on the
|
||||
/// [theme] passed.
|
||||
final Widget Function(ThemeData lightTheme, ThemeData darkTheme) builder;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
// Static colors
|
||||
if (theme != AppTheme.dynamic) {
|
||||
final lightTheme = AppColors.getThemeForColorScheme(theme.lightSchema);
|
||||
final darkTheme = AppColors.getThemeForColorScheme(theme.darkSchema);
|
||||
|
||||
return builder(lightTheme, darkTheme);
|
||||
}
|
||||
|
||||
// Dynamic color builder
|
||||
return DynamicColorBuilder(builder: (lightDynamic, darkDynamic) {
|
||||
final lightTheme =
|
||||
AppColors.getThemeForColorScheme(lightDynamic ?? theme.lightSchema);
|
||||
final darkTheme =
|
||||
AppColors.getThemeForColorScheme(darkDynamic ?? theme.darkSchema);
|
||||
|
||||
return builder(lightTheme, darkTheme);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
import 'package:immich_mobile/domain/interfaces/log.interface.dart';
|
||||
import 'package:immich_mobile/domain/interfaces/store.interface.dart';
|
||||
import 'package:immich_mobile/domain/repositories/database.repository.dart';
|
||||
import 'package:immich_mobile/domain/repositories/log.repository.dart';
|
||||
import 'package:immich_mobile/domain/repositories/store.repository.dart';
|
||||
import 'package:immich_mobile/domain/services/app_setting.service.dart';
|
||||
import 'package:immich_mobile/domain/store_manager.dart';
|
||||
import 'package:immich_mobile/presentation/router/router.dart';
|
||||
import 'package:immich_mobile/presentation/theme/states/app_theme.state.dart';
|
||||
import 'package:watch_it/watch_it.dart';
|
||||
|
||||
class ServiceLocator {
|
||||
const ServiceLocator._internal();
|
||||
|
||||
static void configureServices() {
|
||||
// Register DB
|
||||
di.registerSingleton<DriftDatabaseRepository>(DriftDatabaseRepository());
|
||||
_registerDomainServices();
|
||||
_registerPresentationService();
|
||||
}
|
||||
|
||||
static void _registerDomainServices() {
|
||||
// Init store
|
||||
di.registerFactory<IStoreRepository>(() => StoreDriftRepository(di()));
|
||||
di.registerSingleton<StoreManager>(StoreManager(di()));
|
||||
// Logs
|
||||
di.registerFactory<ILogRepository>(() => LogDriftRepository(di()));
|
||||
// App Settings
|
||||
di.registerFactory<AppSettingsService>(() => AppSettingsService(di()));
|
||||
}
|
||||
|
||||
static void _registerPresentationService() {
|
||||
// App router
|
||||
di.registerSingleton<AppRouter>(AppRouter());
|
||||
// Global states
|
||||
di.registerLazySingleton<AppThemeState>(
|
||||
() => AppThemeState(appSettings: di())..init(),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user