From b052893a1eefdb3b41e6080b5085f456f6108d4f Mon Sep 17 00:00:00 2001 From: shenlong <139912620+shenlong-tanwen@users.noreply.github.com> Date: Wed, 10 Dec 2025 16:18:01 -0600 Subject: [PATCH] feat(mobile): immich-ui icon button (#24502) * feat(mobile): immich-ui icon button * fix lint --------- Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com> --- .../pages/dev/feat_in_development.page.dart | 173 ------------------ .../pages/dev/ui_showcase.page.dart | 51 ++++++ .../pages/editing/drift_crop.page.dart | 31 ++-- mobile/lib/routing/router.dart | 4 +- mobile/lib/routing/router.gr.dart | 32 ++-- mobile/lib/widgets/common/immich_app_bar.dart | 4 +- .../widgets/common/immich_sliver_app_bar.dart | 4 +- mobile/packages/ui/lib/immich_ui.dart | 3 + .../ui/lib/src/buttons/close_button.dart | 25 +++ .../ui/lib/src/buttons/icon_button.dart | 48 +++++ mobile/packages/ui/lib/src/types.dart | 9 + mobile/packages/ui/pubspec.lock | 55 ++++++ mobile/packages/ui/pubspec.yaml | 12 ++ mobile/pubspec.lock | 7 + mobile/pubspec.yaml | 2 + 15 files changed, 251 insertions(+), 209 deletions(-) delete mode 100644 mobile/lib/presentation/pages/dev/feat_in_development.page.dart create mode 100644 mobile/lib/presentation/pages/dev/ui_showcase.page.dart create mode 100644 mobile/packages/ui/lib/immich_ui.dart create mode 100644 mobile/packages/ui/lib/src/buttons/close_button.dart create mode 100644 mobile/packages/ui/lib/src/buttons/icon_button.dart create mode 100644 mobile/packages/ui/lib/src/types.dart create mode 100644 mobile/packages/ui/pubspec.lock create mode 100644 mobile/packages/ui/pubspec.yaml diff --git a/mobile/lib/presentation/pages/dev/feat_in_development.page.dart b/mobile/lib/presentation/pages/dev/feat_in_development.page.dart deleted file mode 100644 index 491c38e7a8..0000000000 --- a/mobile/lib/presentation/pages/dev/feat_in_development.page.dart +++ /dev/null @@ -1,173 +0,0 @@ -import 'dart:async'; - -import 'package:auto_route/auto_route.dart'; -import 'package:drift/drift.dart' hide Column; -import 'package:easy_localization/easy_localization.dart'; -import 'package:flutter/material.dart'; -import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; -import 'package:immich_mobile/providers/background_sync.provider.dart'; -import 'package:immich_mobile/providers/infrastructure/asset.provider.dart'; -import 'package:immich_mobile/providers/infrastructure/db.provider.dart'; -import 'package:immich_mobile/providers/infrastructure/platform.provider.dart'; -import 'package:immich_mobile/providers/user.provider.dart'; -import 'package:immich_mobile/routing/router.dart'; -import 'package:logging/logging.dart'; - -final _features = [ - _Feature( - name: 'Main Timeline', - icon: Icons.timeline_rounded, - onTap: (ctx, _) => ctx.pushRoute(const TabShellRoute()), - ), - _Feature( - name: 'Selection Mode Timeline', - icon: Icons.developer_mode_rounded, - onTap: (ctx, ref) async { - final user = ref.watch(currentUserProvider); - if (user == null) { - return Future.value(); - } - - final assets = await ref.read(remoteAssetRepositoryProvider).getSome(user.id); - - final selectedAssets = await ctx.pushRoute>( - DriftAssetSelectionTimelineRoute(lockedSelectionAssets: assets.toSet()), - ); - - Logger("FeaturesInDevelopment").fine("Selected ${selectedAssets?.length ?? 0} assets"); - - return Future.value(); - }, - ), - _Feature(name: '', icon: Icons.vertical_align_center_sharp, onTap: (_, __) => Future.value()), - _Feature( - name: 'Sync Local', - icon: Icons.photo_album_rounded, - onTap: (_, ref) => ref.read(backgroundSyncProvider).syncLocal(), - ), - _Feature( - name: 'Sync Local Full (1)', - icon: Icons.photo_library_rounded, - onTap: (_, ref) => ref.read(backgroundSyncProvider).syncLocal(full: true), - ), - _Feature( - name: 'Hash Local Assets (2)', - icon: Icons.numbers_outlined, - onTap: (_, ref) => ref.read(backgroundSyncProvider).hashAssets(), - ), - _Feature( - name: 'Sync Remote (3)', - icon: Icons.refresh_rounded, - onTap: (_, ref) => ref.read(backgroundSyncProvider).syncRemote(), - ), - _Feature( - name: 'WAL Checkpoint', - icon: Icons.save_rounded, - onTap: (_, ref) => ref.read(driftProvider).customStatement("pragma wal_checkpoint(truncate)"), - ), - _Feature(name: '', icon: Icons.vertical_align_center_sharp, onTap: (_, __) => Future.value()), - _Feature( - name: 'Clear Delta Checkpoint', - icon: Icons.delete_rounded, - onTap: (_, ref) => ref.read(nativeSyncApiProvider).clearSyncCheckpoint(), - ), - _Feature( - name: 'Clear Local Data', - style: const TextStyle(color: Colors.orange, fontWeight: FontWeight.bold), - icon: Icons.delete_forever_rounded, - onTap: (_, ref) async { - final db = ref.read(driftProvider); - await db.localAssetEntity.deleteAll(); - await db.localAlbumEntity.deleteAll(); - await db.localAlbumAssetEntity.deleteAll(); - }, - ), - _Feature( - name: 'Clear Remote Data', - style: const TextStyle(color: Colors.orange, fontWeight: FontWeight.bold), - icon: Icons.delete_sweep_rounded, - onTap: (_, ref) async { - final db = ref.read(driftProvider); - await db.remoteAssetEntity.deleteAll(); - await db.remoteExifEntity.deleteAll(); - await db.remoteAlbumEntity.deleteAll(); - await db.remoteAlbumUserEntity.deleteAll(); - await db.remoteAlbumAssetEntity.deleteAll(); - await db.memoryEntity.deleteAll(); - await db.memoryAssetEntity.deleteAll(); - await db.stackEntity.deleteAll(); - await db.personEntity.deleteAll(); - await db.assetFaceEntity.deleteAll(); - }, - ), - _Feature( - name: 'Local Media Summary', - style: const TextStyle(color: Colors.indigo, fontWeight: FontWeight.bold), - icon: Icons.table_chart_rounded, - onTap: (ctx, _) => ctx.pushRoute(const LocalMediaSummaryRoute()), - ), - _Feature( - name: 'Remote Media Summary', - style: const TextStyle(color: Colors.indigo, fontWeight: FontWeight.bold), - icon: Icons.summarize_rounded, - onTap: (ctx, _) => ctx.pushRoute(const RemoteMediaSummaryRoute()), - ), - _Feature( - name: 'Reset Sqlite', - icon: Icons.table_view_rounded, - style: const TextStyle(color: Colors.red, fontWeight: FontWeight.bold), - onTap: (_, ref) async { - final drift = ref.read(driftProvider); - // ignore: invalid_use_of_protected_member, invalid_use_of_visible_for_testing_member - final migrator = drift.createMigrator(); - for (final entity in drift.allSchemaEntities) { - await migrator.drop(entity); - await migrator.create(entity); - } - }, - ), -]; - -@RoutePage() -class FeatInDevPage extends StatelessWidget { - const FeatInDevPage({super.key}); - - @override - Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar(title: Text('features_in_development'.tr()), centerTitle: true), - body: Column( - children: [ - Flexible( - flex: 1, - child: ListView.builder( - itemBuilder: (_, index) { - final feat = _features[index]; - return Consumer( - builder: (ctx, ref, _) => ListTile( - title: Text(feat.name, style: feat.style), - trailing: Icon(feat.icon), - visualDensity: VisualDensity.compact, - onTap: () => unawaited(feat.onTap(ctx, ref)), - ), - ); - }, - itemCount: _features.length, - ), - ), - const Divider(height: 0), - ], - ), - ); - } -} - -class _Feature { - const _Feature({required this.name, required this.icon, required this.onTap, this.style}); - - final String name; - final IconData icon; - final TextStyle? style; - final Future Function(BuildContext, WidgetRef _) onTap; -} diff --git a/mobile/lib/presentation/pages/dev/ui_showcase.page.dart b/mobile/lib/presentation/pages/dev/ui_showcase.page.dart new file mode 100644 index 0000000000..01fe928478 --- /dev/null +++ b/mobile/lib/presentation/pages/dev/ui_showcase.page.dart @@ -0,0 +1,51 @@ +import 'package:auto_route/auto_route.dart'; +import 'package:flutter/material.dart'; +import 'package:immich_mobile/extensions/build_context_extensions.dart'; +import 'package:immich_ui/immich_ui.dart'; + +List _showcaseBuilder(Function(ImmichVariant variant, ImmichColor color) builder) { + final children = []; + + final items = [ + (variant: ImmichVariant.filled, title: "Filled Variant"), + (variant: ImmichVariant.ghost, title: "Ghost Variant"), + ]; + + for (final (:variant, :title) in items) { + children.add(Text(title)); + children.add(Row(spacing: 10, children: [for (var color in ImmichColor.values) builder(variant, color)])); + } + + return children; +} + +@RoutePage() +class ImmichUIShowcasePage extends StatelessWidget { + const ImmichUIShowcasePage({super.key}); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar(title: const Text('Immich UI Showcase')), + body: Padding( + padding: const EdgeInsets.all(20), + child: SingleChildScrollView( + child: Column( + spacing: 10, + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text("IconButton", style: context.textTheme.titleLarge), + ..._showcaseBuilder( + (variant, color) => + ImmichIconButton(icon: Icons.favorite, color: color, variant: variant, onTap: () {}), + ), + Text("CloseButton", style: context.textTheme.titleLarge), + ..._showcaseBuilder((variant, color) => ImmichCloseButton(color: color, variant: variant, onTap: () {})), + ], + ), + ), + ), + ); + } +} diff --git a/mobile/lib/presentation/pages/editing/drift_crop.page.dart b/mobile/lib/presentation/pages/editing/drift_crop.page.dart index d8219e3b3c..1692140cd2 100644 --- a/mobile/lib/presentation/pages/editing/drift_crop.page.dart +++ b/mobile/lib/presentation/pages/editing/drift_crop.page.dart @@ -9,6 +9,7 @@ import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/routing/router.dart'; import 'package:immich_mobile/utils/hooks/crop_controller_hook.dart'; +import 'package:immich_ui/immich_ui.dart'; /// A widget for cropping an image. /// This widget uses [HookWidget] to manage its lifecycle and state. It allows @@ -30,11 +31,13 @@ class DriftCropImagePage extends HookWidget { appBar: AppBar( backgroundColor: context.scaffoldBackgroundColor, title: Text("crop".tr()), - leading: CloseButton(color: context.primaryColor), + leading: const ImmichCloseButton(), actions: [ - IconButton( - icon: Icon(Icons.done_rounded, color: context.primaryColor, size: 24), - onPressed: () async { + ImmichIconButton( + icon: Icons.done_rounded, + color: ImmichColor.primary, + variant: ImmichVariant.ghost, + onTap: () async { final croppedImage = await cropController.croppedImage(); unawaited(context.pushRoute(DriftEditImageRoute(asset: asset, image: croppedImage, isEdited: true))); }, @@ -72,17 +75,17 @@ class DriftCropImagePage extends HookWidget { child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - IconButton( - icon: Icon(Icons.rotate_left, color: context.themeData.iconTheme.color), - onPressed: () { - cropController.rotateLeft(); - }, + ImmichIconButton( + icon: Icons.rotate_left, + variant: ImmichVariant.ghost, + color: ImmichColor.secondary, + onTap: () => cropController.rotateLeft(), ), - IconButton( - icon: Icon(Icons.rotate_right, color: context.themeData.iconTheme.color), - onPressed: () { - cropController.rotateRight(); - }, + ImmichIconButton( + icon: Icons.rotate_right, + variant: ImmichVariant.ghost, + color: ImmichColor.secondary, + onTap: () => cropController.rotateRight(), ), ], ), diff --git a/mobile/lib/routing/router.dart b/mobile/lib/routing/router.dart index 383f599331..9c4a193381 100644 --- a/mobile/lib/routing/router.dart +++ b/mobile/lib/routing/router.dart @@ -78,9 +78,9 @@ import 'package:immich_mobile/pages/search/recently_taken.page.dart'; import 'package:immich_mobile/pages/search/search.page.dart'; import 'package:immich_mobile/pages/settings/sync_status.page.dart'; import 'package:immich_mobile/pages/share_intent/share_intent.page.dart'; -import 'package:immich_mobile/presentation/pages/dev/feat_in_development.page.dart'; import 'package:immich_mobile/presentation/pages/dev/main_timeline.page.dart'; import 'package:immich_mobile/presentation/pages/dev/media_stat.page.dart'; +import 'package:immich_mobile/presentation/pages/dev/ui_showcase.page.dart'; import 'package:immich_mobile/presentation/pages/download_info.page.dart'; import 'package:immich_mobile/presentation/pages/drift_activities.page.dart'; import 'package:immich_mobile/presentation/pages/drift_album.page.dart'; @@ -286,7 +286,6 @@ class AppRouter extends RootStackRouter { AutoRoute(page: ShareIntentRoute.page, guards: [_authGuard, _duplicateGuard]), AutoRoute(page: LockedRoute.page, guards: [_authGuard, _lockedGuard, _duplicateGuard]), AutoRoute(page: PinAuthRoute.page, guards: [_authGuard, _duplicateGuard]), - AutoRoute(page: FeatInDevRoute.page, guards: [_authGuard, _duplicateGuard]), AutoRoute(page: LocalMediaSummaryRoute.page, guards: [_authGuard, _duplicateGuard]), AutoRoute(page: RemoteMediaSummaryRoute.page, guards: [_authGuard, _duplicateGuard]), AutoRoute(page: DriftBackupRoute.page, guards: [_authGuard, _duplicateGuard]), @@ -338,6 +337,7 @@ class AppRouter extends RootStackRouter { AutoRoute(page: DriftBackupAssetDetailRoute.page, guards: [_authGuard, _duplicateGuard]), AutoRoute(page: AssetTroubleshootRoute.page, guards: [_authGuard, _duplicateGuard]), AutoRoute(page: DownloadInfoRoute.page, guards: [_authGuard, _duplicateGuard]), + AutoRoute(page: ImmichUIShowcaseRoute.page, guards: [_authGuard, _duplicateGuard]), // required to handle all deeplinks in deep_link.service.dart // auto_route_library#1722 RedirectRoute(path: '*', redirectTo: '/'), diff --git a/mobile/lib/routing/router.gr.dart b/mobile/lib/routing/router.gr.dart index 146b313c2d..939bf73369 100644 --- a/mobile/lib/routing/router.gr.dart +++ b/mobile/lib/routing/router.gr.dart @@ -1648,22 +1648,6 @@ class FavoritesRoute extends PageRouteInfo { ); } -/// generated route for -/// [FeatInDevPage] -class FeatInDevRoute extends PageRouteInfo { - const FeatInDevRoute({List? children}) - : super(FeatInDevRoute.name, initialChildren: children); - - static const String name = 'FeatInDevRoute'; - - static PageInfo page = PageInfo( - name, - builder: (data) { - return const FeatInDevPage(); - }, - ); -} - /// generated route for /// [FilterImagePage] class FilterImageRoute extends PageRouteInfo { @@ -1831,6 +1815,22 @@ class HeaderSettingsRoute extends PageRouteInfo { ); } +/// generated route for +/// [ImmichUIShowcasePage] +class ImmichUIShowcaseRoute extends PageRouteInfo { + const ImmichUIShowcaseRoute({List? children}) + : super(ImmichUIShowcaseRoute.name, initialChildren: children); + + static const String name = 'ImmichUIShowcaseRoute'; + + static PageInfo page = PageInfo( + name, + builder: (data) { + return const ImmichUIShowcasePage(); + }, + ); +} + /// generated route for /// [LibraryPage] class LibraryRoute extends PageRouteInfo { diff --git a/mobile/lib/widgets/common/immich_app_bar.dart b/mobile/lib/widgets/common/immich_app_bar.dart index 2bac100807..b3dc04236c 100644 --- a/mobile/lib/widgets/common/immich_app_bar.dart +++ b/mobile/lib/widgets/common/immich_app_bar.dart @@ -155,8 +155,8 @@ class ImmichAppBar extends ConsumerWidget implements PreferredSizeWidget { ...actions!.map((action) => Padding(padding: const EdgeInsets.only(right: 16), child: action)), if (kDebugMode || kProfileMode) IconButton( - icon: const Icon(Icons.science_rounded), - onPressed: () => context.pushRoute(const FeatInDevRoute()), + icon: const Icon(Icons.palette_rounded), + onPressed: () => context.pushRoute(const ImmichUIShowcaseRoute()), ), if (isCasting) Padding( diff --git a/mobile/lib/widgets/common/immich_sliver_app_bar.dart b/mobile/lib/widgets/common/immich_sliver_app_bar.dart index f68d5c9fda..dd985ebfe2 100644 --- a/mobile/lib/widgets/common/immich_sliver_app_bar.dart +++ b/mobile/lib/widgets/common/immich_sliver_app_bar.dart @@ -74,8 +74,8 @@ class ImmichSliverAppBar extends ConsumerWidget { ...actions!.map((action) => Padding(padding: const EdgeInsets.only(right: 16), child: action)), if ((kDebugMode || kProfileMode) && !isReadonlyModeEnabled) IconButton( - icon: const Icon(Icons.science_rounded), - onPressed: () => context.pushRoute(const FeatInDevRoute()), + icon: const Icon(Icons.palette_rounded), + onPressed: () => context.pushRoute(const ImmichUIShowcaseRoute()), ), if (showUploadButton && !isReadonlyModeEnabled) const Padding(padding: EdgeInsets.only(right: 20), child: _BackupIndicator()), diff --git a/mobile/packages/ui/lib/immich_ui.dart b/mobile/packages/ui/lib/immich_ui.dart new file mode 100644 index 0000000000..2417149f76 --- /dev/null +++ b/mobile/packages/ui/lib/immich_ui.dart @@ -0,0 +1,3 @@ +export 'src/buttons/close_button.dart'; +export 'src/buttons/icon_button.dart'; +export 'src/types.dart'; diff --git a/mobile/packages/ui/lib/src/buttons/close_button.dart b/mobile/packages/ui/lib/src/buttons/close_button.dart new file mode 100644 index 0000000000..c8c5d62a12 --- /dev/null +++ b/mobile/packages/ui/lib/src/buttons/close_button.dart @@ -0,0 +1,25 @@ +import 'package:flutter/material.dart'; +import 'package:immich_ui/src/buttons/icon_button.dart'; +import 'package:immich_ui/src/types.dart'; + +class ImmichCloseButton extends StatelessWidget { + final VoidCallback? onTap; + final ImmichVariant variant; + final ImmichColor color; + + const ImmichCloseButton({ + super.key, + this.onTap, + this.color = ImmichColor.primary, + this.variant = ImmichVariant.ghost, + }); + + @override + Widget build(BuildContext context) => ImmichIconButton( + key: key, + icon: Icons.close, + color: color, + variant: variant, + onTap: onTap ?? () => Navigator.of(context).pop(), + ); +} diff --git a/mobile/packages/ui/lib/src/buttons/icon_button.dart b/mobile/packages/ui/lib/src/buttons/icon_button.dart new file mode 100644 index 0000000000..5c62ee8eda --- /dev/null +++ b/mobile/packages/ui/lib/src/buttons/icon_button.dart @@ -0,0 +1,48 @@ +import 'package:flutter/material.dart'; +import 'package:immich_ui/src/types.dart'; + +class ImmichIconButton extends StatelessWidget { + final IconData icon; + final VoidCallback onTap; + final ImmichVariant variant; + final ImmichColor color; + + const ImmichIconButton({ + super.key, + required this.icon, + required this.onTap, + this.color = ImmichColor.primary, + this.variant = ImmichVariant.filled, + }); + + @override + Widget build(BuildContext context) { + final background = switch (variant) { + ImmichVariant.filled => switch (color) { + ImmichColor.primary => Theme.of(context).colorScheme.primary, + ImmichColor.secondary => Theme.of(context).colorScheme.secondary, + }, + ImmichVariant.ghost => Colors.transparent, + }; + + final foreground = switch (variant) { + ImmichVariant.filled => switch (color) { + ImmichColor.primary => Theme.of(context).colorScheme.onPrimary, + ImmichColor.secondary => Theme.of(context).colorScheme.onSecondary, + }, + ImmichVariant.ghost => switch (color) { + ImmichColor.primary => Theme.of(context).colorScheme.primary, + ImmichColor.secondary => Theme.of(context).colorScheme.secondary, + }, + }; + + return IconButton( + icon: Icon(icon), + onPressed: onTap, + style: IconButton.styleFrom( + backgroundColor: background, + foregroundColor: foreground, + ), + ); + } +} diff --git a/mobile/packages/ui/lib/src/types.dart b/mobile/packages/ui/lib/src/types.dart new file mode 100644 index 0000000000..2c0c7b7760 --- /dev/null +++ b/mobile/packages/ui/lib/src/types.dart @@ -0,0 +1,9 @@ +enum ImmichVariant { + filled, + ghost, +} + +enum ImmichColor { + primary, + secondary, +} diff --git a/mobile/packages/ui/pubspec.lock b/mobile/packages/ui/pubspec.lock new file mode 100644 index 0000000000..b9d150f174 --- /dev/null +++ b/mobile/packages/ui/pubspec.lock @@ -0,0 +1,55 @@ +# Generated by pub +# See https://dart.dev/tools/pub/glossary#lockfile +packages: + characters: + dependency: transitive + description: + name: characters + sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803 + url: "https://pub.dev" + source: hosted + version: "1.4.0" + collection: + dependency: transitive + description: + name: collection + sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76" + url: "https://pub.dev" + source: hosted + version: "1.19.1" + flutter: + dependency: "direct main" + description: flutter + source: sdk + version: "0.0.0" + material_color_utilities: + dependency: transitive + description: + name: material_color_utilities + sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec + url: "https://pub.dev" + source: hosted + version: "0.11.1" + meta: + dependency: transitive + description: + name: meta + sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c + url: "https://pub.dev" + source: hosted + version: "1.16.0" + sky_engine: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" + vector_math: + dependency: transitive + description: + name: vector_math + sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b + url: "https://pub.dev" + source: hosted + version: "2.2.0" +sdks: + dart: ">=3.8.0-0 <4.0.0" diff --git a/mobile/packages/ui/pubspec.yaml b/mobile/packages/ui/pubspec.yaml new file mode 100644 index 0000000000..47b9a9dd8a --- /dev/null +++ b/mobile/packages/ui/pubspec.yaml @@ -0,0 +1,12 @@ +name: immich_ui +publish_to: none + +environment: + sdk: '>=3.0.0 <4.0.0' + +dependencies: + flutter: + sdk: flutter + +flutter: + uses-material-design: true \ No newline at end of file diff --git a/mobile/pubspec.lock b/mobile/pubspec.lock index 6a067f509f..3179d71bd1 100644 --- a/mobile/pubspec.lock +++ b/mobile/pubspec.lock @@ -1015,6 +1015,13 @@ packages: relative: true source: path version: "0.0.0" + immich_ui: + dependency: "direct main" + description: + path: "packages/ui" + relative: true + source: path + version: "0.0.0" integration_test: dependency: "direct dev" description: flutter diff --git a/mobile/pubspec.yaml b/mobile/pubspec.yaml index a49a012031..1633a54cb6 100644 --- a/mobile/pubspec.yaml +++ b/mobile/pubspec.yaml @@ -43,6 +43,8 @@ dependencies: hooks_riverpod: ^2.6.1 http: ^1.5.0 image_picker: ^1.2.0 + immich_ui: + path: './packages/ui' intl: ^0.20.2 isar: git: