diff --git a/.github/workflows/static_analysis.yml b/.github/workflows/static_analysis.yml index d347317793..6fdd7af18b 100644 --- a/.github/workflows/static_analysis.yml +++ b/.github/workflows/static_analysis.yml @@ -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 diff --git a/docs/docs/developer/setup.md b/docs/docs/developer/setup.md index c5d782fb52..9cbee642f5 100644 --- a/docs/docs/developer/setup.md +++ b/docs/docs/developer/setup.md @@ -108,6 +108,24 @@ make 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 diff --git a/mobile/packages/ui/lib/src/previews.dart b/mobile/packages/ui/lib/src/previews.dart new file mode 100644 index 0000000000..076bc253c9 --- /dev/null +++ b/mobile/packages/ui/lib/src/previews.dart @@ -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); +} diff --git a/mobile/packages/ui/lib/src/previews/close_button.dart b/mobile/packages/ui/lib/src/previews/close_button.dart new file mode 100644 index 0000000000..678d6bed18 --- /dev/null +++ b/mobile/packages/ui/lib/src/previews/close_button.dart @@ -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), + ], + ); diff --git a/mobile/packages/ui/lib/src/previews/form.dart b/mobile/packages/ui/lib/src/previews/form.dart new file mode 100644 index 0000000000..9e488cfd68 --- /dev/null +++ b/mobile/packages/ui/lib/src/previews/form.dart @@ -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.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)), + ], + ], + ); + } +} diff --git a/mobile/packages/ui/lib/src/previews/formatted_text.dart b/mobile/packages/ui/lib/src/previews/formatted_text.dart new file mode 100644 index 0000000000..9ef3b4b851 --- /dev/null +++ b/mobile/packages/ui/lib/src/previews/formatted_text.dart @@ -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 bold text.'); + +@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 documentation or visit GitHub.', + 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 bold text and links 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!'))), + ), + }, + ); + } +} diff --git a/mobile/packages/ui/lib/src/previews/icon_button.dart b/mobile/packages/ui/lib/src/previews/icon_button.dart new file mode 100644 index 0000000000..6a0196bc81 --- /dev/null +++ b/mobile/packages/ui/lib/src/previews/icon_button.dart @@ -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, + ), + ], + ); diff --git a/mobile/packages/ui/lib/src/previews/password_input.dart b/mobile/packages/ui/lib/src/previews/password_input.dart new file mode 100644 index 0000000000..72bd9cbfc5 --- /dev/null +++ b/mobile/packages/ui/lib/src/previews/password_input.dart @@ -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; + }, + ); diff --git a/mobile/packages/ui/lib/src/previews/text_button.dart b/mobile/packages/ui/lib/src/previews/text_button.dart new file mode 100644 index 0000000000..46c689bbae --- /dev/null +++ b/mobile/packages/ui/lib/src/previews/text_button.dart @@ -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.delayed(const Duration(seconds: 2)); + if (mounted) { + setState(() => _isLoading = false); + } + }, + labelText: _isLoading ? 'Loading...' : 'Click Me', + loading: _isLoading, + expanded: false, + ); + } +} diff --git a/mobile/packages/ui/lib/src/previews/text_input.dart b/mobile/packages/ui/lib/src/previews/text_input.dart new file mode 100644 index 0000000000..fab58c8cc4 --- /dev/null +++ b/mobile/packages/ui/lib/src/previews/text_input.dart @@ -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; + }, + ); + } +} diff --git a/mobile/packages/ui/lib/src/previews/url_input.dart b/mobile/packages/ui/lib/src/previews/url_input.dart new file mode 100644 index 0000000000..6819ac5796 --- /dev/null +++ b/mobile/packages/ui/lib/src/previews/url_input.dart @@ -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); + } +} diff --git a/mobile/packages/ui/pubspec.lock b/mobile/packages/ui/pubspec.lock index 294c46e424..9d11b49253 100644 --- a/mobile/packages/ui/pubspec.lock +++ b/mobile/packages/ui/pubspec.lock @@ -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" diff --git a/mobile/packages/ui/pubspec.yaml b/mobile/packages/ui/pubspec.yaml index de50e0a429..1f44694ace 100644 --- a/mobile/packages/ui/pubspec.yaml +++ b/mobile/packages/ui/pubspec.yaml @@ -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: diff --git a/mobile/packages/ui/showcase/.gitignore b/mobile/packages/ui/showcase/.gitignore deleted file mode 100644 index b285cd608b..0000000000 --- a/mobile/packages/ui/showcase/.gitignore +++ /dev/null @@ -1,11 +0,0 @@ -# Build artifacts -build/ - -# Test cache and generated files -.dart_tool/ -.packages -.flutter-plugins -.flutter-plugins-dependencies - -# IDE-specific files -.vscode/ \ No newline at end of file diff --git a/mobile/packages/ui/showcase/.metadata b/mobile/packages/ui/showcase/.metadata deleted file mode 100644 index b95fa4d74e..0000000000 --- a/mobile/packages/ui/showcase/.metadata +++ /dev/null @@ -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' diff --git a/mobile/packages/ui/showcase/analysis_options.yaml b/mobile/packages/ui/showcase/analysis_options.yaml deleted file mode 100644 index f9b303465f..0000000000 --- a/mobile/packages/ui/showcase/analysis_options.yaml +++ /dev/null @@ -1 +0,0 @@ -include: package:flutter_lints/flutter.yaml diff --git a/mobile/packages/ui/showcase/assets/immich-text-dark.png b/mobile/packages/ui/showcase/assets/immich-text-dark.png deleted file mode 100644 index 215687af8f..0000000000 Binary files a/mobile/packages/ui/showcase/assets/immich-text-dark.png and /dev/null differ diff --git a/mobile/packages/ui/showcase/assets/immich-text-light.png b/mobile/packages/ui/showcase/assets/immich-text-light.png deleted file mode 100644 index 478158d39c..0000000000 Binary files a/mobile/packages/ui/showcase/assets/immich-text-light.png and /dev/null differ diff --git a/mobile/packages/ui/showcase/assets/immich_logo.png b/mobile/packages/ui/showcase/assets/immich_logo.png deleted file mode 100644 index 49fd3ae289..0000000000 Binary files a/mobile/packages/ui/showcase/assets/immich_logo.png and /dev/null differ diff --git a/mobile/packages/ui/showcase/assets/themes/github_dark.json b/mobile/packages/ui/showcase/assets/themes/github_dark.json deleted file mode 100644 index bd4801482e..0000000000 --- a/mobile/packages/ui/showcase/assets/themes/github_dark.json +++ /dev/null @@ -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" - } - } - ] -} diff --git a/mobile/packages/ui/showcase/lib/app_theme.dart b/mobile/packages/ui/showcase/lib/app_theme.dart deleted file mode 100644 index 995bf3c91e..0000000000 --- a/mobile/packages/ui/showcase/lib/app_theme.dart +++ /dev/null @@ -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), - ), - ); - } -} diff --git a/mobile/packages/ui/showcase/lib/constants.dart b/mobile/packages/ui/showcase/lib/constants.dart deleted file mode 100644 index cfca4cfda9..0000000000 --- a/mobile/packages/ui/showcase/lib/constants.dart +++ /dev/null @@ -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; -} diff --git a/mobile/packages/ui/showcase/lib/main.dart b/mobile/packages/ui/showcase/lib/main.dart deleted file mode 100644 index 6cd2df4fe5..0000000000 --- a/mobile/packages/ui/showcase/lib/main.dart +++ /dev/null @@ -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 createState() => _ShowcaseAppState(); -} - -class _ShowcaseAppState extends State { - 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!, - ), - ); - } -} diff --git a/mobile/packages/ui/showcase/lib/pages/components/close_button_page.dart b/mobile/packages/ui/showcase/lib/pages/components/close_button_page.dart deleted file mode 100644 index 1bae98e0a4..0000000000 --- a/mobile/packages/ui/showcase/lib/pages/components/close_button_page.dart +++ /dev/null @@ -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: () {}, - ), - ], - ), - ), - ], - ), - ); - } -} diff --git a/mobile/packages/ui/showcase/lib/pages/components/examples/formatted_text_bold_text.dart b/mobile/packages/ui/showcase/lib/pages/components/examples/formatted_text_bold_text.dart deleted file mode 100644 index 7e36ac7537..0000000000 --- a/mobile/packages/ui/showcase/lib/pages/components/examples/formatted_text_bold_text.dart +++ /dev/null @@ -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 bold text.'); - } -} diff --git a/mobile/packages/ui/showcase/lib/pages/components/examples/formatted_text_links.dart b/mobile/packages/ui/showcase/lib/pages/components/examples/formatted_text_links.dart deleted file mode 100644 index 3910a5117a..0000000000 --- a/mobile/packages/ui/showcase/lib/pages/components/examples/formatted_text_links.dart +++ /dev/null @@ -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 documentation or visit GitHub.', - 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, - }, - ), - ); - } -} diff --git a/mobile/packages/ui/showcase/lib/pages/components/examples/formatted_text_mixed_tags.dart b/mobile/packages/ui/showcase/lib/pages/components/examples/formatted_text_mixed_tags.dart deleted file mode 100644 index 3490b1c386..0000000000 --- a/mobile/packages/ui/showcase/lib/pages/components/examples/formatted_text_mixed_tags.dart +++ /dev/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 bold text and links 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!'))), - ), - }, - ); - } -} diff --git a/mobile/packages/ui/showcase/lib/pages/components/form_page.dart b/mobile/packages/ui/showcase/lib/pages/components/form_page.dart deleted file mode 100644 index f4480026b3..0000000000 --- a/mobile/packages/ui/showcase/lib/pages/components/form_page.dart +++ /dev/null @@ -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 createState() => _FormPageState(); -} - -class _FormPageState extends State { - 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(); - } -} diff --git a/mobile/packages/ui/showcase/lib/pages/components/formatted_text_page.dart b/mobile/packages/ui/showcase/lib/pages/components/formatted_text_page.dart deleted file mode 100644 index b827e0340b..0000000000 --- a/mobile/packages/ui/showcase/lib/pages/components/formatted_text_page.dart +++ /dev/null @@ -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', - ), - ], - ), - ); - } -} diff --git a/mobile/packages/ui/showcase/lib/pages/components/icon_button_page.dart b/mobile/packages/ui/showcase/lib/pages/components/icon_button_page.dart deleted file mode 100644 index 4418b1de4f..0000000000 --- a/mobile/packages/ui/showcase/lib/pages/components/icon_button_page.dart +++ /dev/null @@ -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, - ), - ], - ), - ), - ], - ), - ); - } -} diff --git a/mobile/packages/ui/showcase/lib/pages/components/password_input_page.dart b/mobile/packages/ui/showcase/lib/pages/components/password_input_page.dart deleted file mode 100644 index 772dd7882f..0000000000 --- a/mobile/packages/ui/showcase/lib/pages/components/password_input_page.dart +++ /dev/null @@ -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; - }, - ), - ), - ], - ), - ); - } -} diff --git a/mobile/packages/ui/showcase/lib/pages/components/text_button_page.dart b/mobile/packages/ui/showcase/lib/pages/components/text_button_page.dart deleted file mode 100644 index 59e5b86294..0000000000 --- a/mobile/packages/ui/showcase/lib/pages/components/text_button_page.dart +++ /dev/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 createState() => _TextButtonPageState(); -} - -class _TextButtonPageState extends State { - 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, - ), - ], - ), - ), - ], - ), - ); - } -} diff --git a/mobile/packages/ui/showcase/lib/pages/components/text_input_page.dart b/mobile/packages/ui/showcase/lib/pages/components/text_input_page.dart deleted file mode 100644 index 5a0bfec6cd..0000000000 --- a/mobile/packages/ui/showcase/lib/pages/components/text_input_page.dart +++ /dev/null @@ -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 createState() => _TextInputPageState(); -} - -class _TextInputPageState extends State { - 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(); - } -} diff --git a/mobile/packages/ui/showcase/lib/pages/design_system/constants_page.dart b/mobile/packages/ui/showcase/lib/pages/design_system/constants_page.dart deleted file mode 100644 index 17de02d80a..0000000000 --- a/mobile/packages/ui/showcase/lib/pages/design_system/constants_page.dart +++ /dev/null @@ -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 createState() => _ConstantsPageState(); -} - -class _ConstantsPageState extends State { - @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), - ), - ], - ); - } -} diff --git a/mobile/packages/ui/showcase/lib/pages/home_page.dart b/mobile/packages/ui/showcase/lib/pages/home_page.dart deleted file mode 100644 index de7af6c26b..0000000000 --- a/mobile/packages/ui/showcase/lib/pages/home_page.dart +++ /dev/null @@ -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, - ), - ], - ), - ), - ), - ); - } -} diff --git a/mobile/packages/ui/showcase/lib/router.dart b/mobile/packages/ui/showcase/lib/router.dart deleted file mode 100644 index 34393da508..0000000000 --- a/mobile/packages/ui/showcase/lib/router.dart +++ /dev/null @@ -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(), - ), - ], - ); - } -} diff --git a/mobile/packages/ui/showcase/lib/routes.dart b/mobile/packages/ui/showcase/lib/routes.dart deleted file mode 100644 index 4feeeafdb6..0000000000 --- a/mobile/packages/ui/showcase/lib/routes.dart +++ /dev/null @@ -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, route) { - map.putIfAbsent(route.category, () => []).add(route); - return map; - }); diff --git a/mobile/packages/ui/showcase/lib/widgets/component_examples.dart b/mobile/packages/ui/showcase/lib/widgets/component_examples.dart deleted file mode 100644 index 21e6516079..0000000000 --- a/mobile/packages/ui/showcase/lib/widgets/component_examples.dart +++ /dev/null @@ -1,85 +0,0 @@ -import 'package:flutter/material.dart'; - -class ComponentExamples extends StatelessWidget { - final String title; - final String? subtitle; - final List 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, - ), - ), - ], - ], - ); - } -} diff --git a/mobile/packages/ui/showcase/lib/widgets/example_card.dart b/mobile/packages/ui/showcase/lib/widgets/example_card.dart deleted file mode 100644 index fea561afb6..0000000000 --- a/mobile/packages/ui/showcase/lib/widgets/example_card.dart +++ /dev/null @@ -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 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 createState() => _ExampleCardState(); -} - -class _ExampleCardState extends State { - 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, - ), - ), - ], - ), - ), - ); - } -} diff --git a/mobile/packages/ui/showcase/lib/widgets/page_title.dart b/mobile/packages/ui/showcase/lib/widgets/page_title.dart deleted file mode 100644 index eae3bf6ffb..0000000000 --- a/mobile/packages/ui/showcase/lib/widgets/page_title.dart +++ /dev/null @@ -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, - ); - } -} diff --git a/mobile/packages/ui/showcase/lib/widgets/shell_layout.dart b/mobile/packages/ui/showcase/lib/widgets/shell_layout.dart deleted file mode 100644 index 8bcb687e75..0000000000 --- a/mobile/packages/ui/showcase/lib/widgets/shell_layout.dart +++ /dev/null @@ -1,59 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:showcase/constants.dart'; -import 'package:showcase/widgets/sidebar_navigation.dart'; - -class ShellLayout extends StatelessWidget { - final Widget child; - final VoidCallback onThemeToggle; - - const ShellLayout({ - super.key, - required this.child, - required this.onThemeToggle, - }); - - @override - Widget build(BuildContext context) { - final isDark = Theme.of(context).brightness == Brightness.dark; - - return Scaffold( - appBar: AppBar( - title: Row( - mainAxisSize: MainAxisSize.min, - children: [ - Image.asset('assets/immich_logo.png', height: 32, width: 32), - const SizedBox(width: 8), - Image.asset( - isDark - ? 'assets/immich-text-dark.png' - : 'assets/immich-text-light.png', - height: 24, - filterQuality: FilterQuality.none, - isAntiAlias: true, - ), - ], - ), - actions: [ - IconButton( - icon: Icon( - isDark ? Icons.light_mode_outlined : Icons.dark_mode_outlined, - size: LayoutConstants.iconSizeLarge, - ), - onPressed: onThemeToggle, - tooltip: 'Toggle theme', - ), - ], - shape: Border( - bottom: BorderSide(color: Theme.of(context).dividerColor, width: 1), - ), - ), - body: Row( - children: [ - const SidebarNavigation(), - const VerticalDivider(), - Expanded(child: child), - ], - ), - ); - } -} diff --git a/mobile/packages/ui/showcase/lib/widgets/sidebar_navigation.dart b/mobile/packages/ui/showcase/lib/widgets/sidebar_navigation.dart deleted file mode 100644 index 10eba170e6..0000000000 --- a/mobile/packages/ui/showcase/lib/widgets/sidebar_navigation.dart +++ /dev/null @@ -1,117 +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 SidebarNavigation extends StatelessWidget { - const SidebarNavigation({super.key}); - - @override - Widget build(BuildContext context) { - return Container( - width: LayoutConstants.sidebarWidth, - decoration: BoxDecoration(color: Theme.of(context).colorScheme.surface), - child: ListView( - padding: const EdgeInsets.symmetric(vertical: 20, horizontal: 16), - children: [ - ...routesByCategory.entries.expand((entry) { - final category = entry.key; - final routes = entry.value; - return [ - if (category != AppRouteCategory.root) _CategoryHeader(category), - ...routes.map((route) => _NavItem(route)), - const SizedBox(height: 24), - ]; - }), - ], - ), - ); - } -} - -class _CategoryHeader extends StatelessWidget { - final AppRouteCategory category; - - const _CategoryHeader(this.category); - - @override - Widget build(BuildContext context) { - return Padding( - padding: const EdgeInsets.only(left: 12, top: 8, bottom: 8), - child: Text( - category.displayName, - style: Theme.of(context).textTheme.bodySmall?.copyWith( - color: Theme.of(context).colorScheme.onSurfaceVariant, - fontWeight: FontWeight.w600, - letterSpacing: 0.5, - ), - ), - ); - } -} - -class _NavItem extends StatelessWidget { - final AppRoute route; - - const _NavItem(this.route); - - @override - Widget build(BuildContext context) { - final currentRoute = GoRouterState.of(context).uri.toString(); - final isSelected = currentRoute == route.path; - final isDark = Theme.of(context).brightness == Brightness.dark; - - return Padding( - padding: const EdgeInsets.symmetric(vertical: 4), - child: Material( - color: Colors.transparent, - child: InkWell( - onTap: () { - context.go(route.path); - }, - borderRadius: BorderRadius.circular( - LayoutConstants.borderRadiusMedium, - ), - child: Container( - padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), - decoration: BoxDecoration( - color: isSelected - ? (isDark - ? Colors.white.withValues(alpha: 0.1) - : Theme.of( - context, - ).colorScheme.primaryContainer.withValues(alpha: 0.5)) - : Colors.transparent, - borderRadius: BorderRadius.circular( - LayoutConstants.borderRadiusMedium, - ), - ), - child: Row( - children: [ - Icon( - route.icon, - size: 20, - color: isSelected - ? Theme.of(context).colorScheme.primary - : Theme.of(context).colorScheme.onSurfaceVariant, - ), - const SizedBox(width: 16), - Expanded( - child: Text( - route.name, - style: Theme.of(context).textTheme.bodyMedium?.copyWith( - color: isSelected - ? Theme.of(context).colorScheme.primary - : Theme.of(context).colorScheme.onSurface, - fontWeight: FontWeight.w500, - ), - ), - ), - ], - ), - ), - ), - ), - ); - } -} diff --git a/mobile/packages/ui/showcase/pubspec.lock b/mobile/packages/ui/showcase/pubspec.lock deleted file mode 100644 index d375d4b05b..0000000000 --- a/mobile/packages/ui/showcase/pubspec.lock +++ /dev/null @@ -1,377 +0,0 @@ -# Generated by pub -# See https://dart.dev/tools/pub/glossary#lockfile -packages: - async: - dependency: transitive - description: - name: async - sha256: e2eb0491ba5ddb6177742d2da23904574082139b07c1e33b8503b9f46f3e1a37 - url: "https://pub.dev" - source: hosted - version: "2.13.1" - boolean_selector: - dependency: transitive - description: - name: boolean_selector - sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea" - url: "https://pub.dev" - source: hosted - version: "2.1.2" - characters: - dependency: transitive - description: - name: characters - sha256: faf38497bda5ead2a8c7615f4f7939df04333478bf32e4173fcb06d428b5716b - url: "https://pub.dev" - source: hosted - version: "1.4.1" - clock: - dependency: transitive - description: - name: clock - sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b - url: "https://pub.dev" - source: hosted - version: "1.1.2" - collection: - dependency: transitive - description: - name: collection - sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76" - url: "https://pub.dev" - source: hosted - version: "1.19.1" - crypto: - dependency: transitive - description: - name: crypto - sha256: c8ea0233063ba03258fbcf2ca4d6dadfefe14f02fab57702265467a19f27fadf - url: "https://pub.dev" - source: hosted - version: "3.0.7" - device_info_plus: - dependency: transitive - description: - name: device_info_plus - sha256: "98f28b42168cc509abc92f88518882fd58061ea372d7999aecc424345c7bff6a" - url: "https://pub.dev" - source: hosted - version: "11.5.0" - device_info_plus_platform_interface: - dependency: transitive - description: - name: device_info_plus_platform_interface - sha256: e1ea89119e34903dca74b883d0dd78eb762814f97fb6c76f35e9ff74d261a18f - url: "https://pub.dev" - source: hosted - version: "7.0.3" - fake_async: - dependency: transitive - description: - name: fake_async - sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44" - url: "https://pub.dev" - source: hosted - version: "1.3.3" - ffi: - dependency: transitive - description: - name: ffi - sha256: "6d7fd89431262d8f3125e81b50d3847a091d846eafcd4fdb88dd06f36d705a45" - url: "https://pub.dev" - source: hosted - version: "2.2.0" - file: - dependency: transitive - description: - name: file - sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4 - url: "https://pub.dev" - source: hosted - version: "7.0.1" - fixnum: - dependency: transitive - description: - name: fixnum - sha256: b6dc7065e46c974bc7c5f143080a6764ec7a4be6da1285ececdc37be96de53be - url: "https://pub.dev" - source: hosted - version: "1.1.1" - flutter: - dependency: "direct main" - description: flutter - source: sdk - version: "0.0.0" - flutter_lints: - dependency: "direct dev" - description: - name: flutter_lints - sha256: "3105dc8492f6183fb076ccf1f351ac3d60564bff92e20bfc4af9cc1651f4e7e1" - url: "https://pub.dev" - source: hosted - version: "6.0.0" - flutter_test: - dependency: "direct dev" - description: flutter - source: sdk - version: "0.0.0" - flutter_web_plugins: - dependency: transitive - description: flutter - source: sdk - version: "0.0.0" - go_router: - dependency: "direct main" - description: - name: go_router - sha256: "92d8cee7c57dff0a6c409c05597b460002434eccf7424a712283225b3962d03f" - url: "https://pub.dev" - source: hosted - version: "17.2.3" - immich_ui: - dependency: "direct main" - description: - path: ".." - relative: true - source: path - version: "0.0.0" - irondash_engine_context: - dependency: transitive - description: - name: irondash_engine_context - sha256: "2bb0bc13dfda9f5aaef8dde06ecc5feb1379f5bb387d59716d799554f3f305d7" - url: "https://pub.dev" - source: hosted - version: "0.5.5" - irondash_message_channel: - dependency: transitive - description: - name: irondash_message_channel - sha256: b4101669776509c76133b8917ab8cfc704d3ad92a8c450b92934dd8884a2f060 - url: "https://pub.dev" - source: hosted - version: "0.7.0" - leak_tracker: - dependency: transitive - description: - name: leak_tracker - sha256: "33e2e26bdd85a0112ec15400c8cbffea70d0f9c3407491f672a2fad47915e2de" - url: "https://pub.dev" - source: hosted - version: "11.0.2" - leak_tracker_flutter_testing: - dependency: transitive - description: - name: leak_tracker_flutter_testing - sha256: "1dbc140bb5a23c75ea9c4811222756104fbcd1a27173f0c34ca01e16bea473c1" - url: "https://pub.dev" - source: hosted - version: "3.0.10" - leak_tracker_testing: - dependency: transitive - description: - name: leak_tracker_testing - sha256: "8d5a2d49f4a66b49744b23b018848400d23e54caf9463f4eb20df3eb8acb2eb1" - url: "https://pub.dev" - source: hosted - version: "3.0.2" - lints: - dependency: transitive - description: - name: lints - sha256: "12f842a479589fea194fe5c5a3095abc7be0c1f2ddfa9a0e76aed1dbd26a87df" - url: "https://pub.dev" - source: hosted - version: "6.1.0" - logging: - dependency: transitive - description: - name: logging - sha256: c8245ada5f1717ed44271ed1c26b8ce85ca3228fd2ffdb75468ab01979309d61 - url: "https://pub.dev" - source: hosted - version: "1.3.0" - matcher: - dependency: transitive - description: - name: matcher - sha256: dc0b7dc7651697ea4ff3e69ef44b0407ea32c487a39fff6a4004fa585e901861 - url: "https://pub.dev" - source: hosted - version: "0.12.19" - material_color_utilities: - dependency: transitive - description: - name: material_color_utilities - sha256: "9c337007e82b1889149c82ed242ed1cb24a66044e30979c44912381e9be4c48b" - url: "https://pub.dev" - source: hosted - version: "0.13.0" - meta: - dependency: transitive - description: - name: meta - sha256: "1741988757a65eb6b36abe716829688cf01910bbf91c34354ff7ec1c3de2b349" - url: "https://pub.dev" - source: hosted - version: "1.18.0" - path: - dependency: transitive - description: - name: path - sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5" - url: "https://pub.dev" - source: hosted - version: "1.9.1" - pixel_snap: - dependency: transitive - description: - name: pixel_snap - sha256: "677410ea37b07cd37ecb6d5e6c0d8d7615a7cf3bd92ba406fd1ac57e937d1fb0" - url: "https://pub.dev" - source: hosted - version: "0.1.5" - plugin_platform_interface: - dependency: transitive - description: - name: plugin_platform_interface - sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02" - url: "https://pub.dev" - source: hosted - version: "2.1.8" - sky_engine: - dependency: transitive - description: flutter - source: sdk - version: "0.0.0" - source_span: - dependency: transitive - description: - name: source_span - sha256: "56a02f1f4cd1a2d96303c0144c93bd6d909eea6bee6bf5a0e0b685edbd4c47ab" - url: "https://pub.dev" - source: hosted - version: "1.10.2" - stack_trace: - dependency: transitive - description: - name: stack_trace - sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1" - url: "https://pub.dev" - source: hosted - version: "1.12.1" - stream_channel: - dependency: transitive - description: - name: stream_channel - sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d" - url: "https://pub.dev" - source: hosted - version: "2.1.4" - string_scanner: - dependency: transitive - description: - name: string_scanner - sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43" - url: "https://pub.dev" - source: hosted - version: "1.4.1" - super_clipboard: - dependency: transitive - description: - name: super_clipboard - sha256: e73f3bb7e66cc9260efa1dc507f979138e7e106c3521e2dda2d0311f6d728a16 - url: "https://pub.dev" - source: hosted - version: "0.9.1" - super_native_extensions: - dependency: transitive - description: - name: super_native_extensions - sha256: b9611dcb68f1047d6f3ef11af25e4e68a21b1a705bbcc3eb8cb4e9f5c3148569 - url: "https://pub.dev" - source: hosted - version: "0.9.1" - syntax_highlight: - dependency: "direct main" - description: - name: syntax_highlight - sha256: "4d3ba40658cadba6ba55d697f29f00b43538ebb6eb4a0ca0e895c568eaced138" - url: "https://pub.dev" - source: hosted - version: "0.5.0" - term_glyph: - dependency: transitive - description: - name: term_glyph - sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e" - url: "https://pub.dev" - source: hosted - version: "1.2.2" - test_api: - dependency: transitive - description: - name: test_api - sha256: "949a932224383300f01be9221c39180316445ecb8e7547f70a41a35bf421fb9e" - url: "https://pub.dev" - source: hosted - version: "0.7.11" - typed_data: - dependency: transitive - description: - name: typed_data - sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006 - url: "https://pub.dev" - source: hosted - version: "1.4.0" - uuid: - dependency: transitive - description: - name: uuid - sha256: "1fef9e8e11e2991bb773070d4656b7bd5d850967a2456cfc83cf47925ba79489" - url: "https://pub.dev" - source: hosted - version: "4.5.3" - vector_math: - dependency: transitive - description: - name: vector_math - sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b - url: "https://pub.dev" - source: hosted - version: "2.2.0" - vm_service: - dependency: transitive - description: - name: vm_service - sha256: "0016aef94fc66495ac78af5859181e3f3bf2026bd8eecc72b9565601e19ab360" - url: "https://pub.dev" - source: hosted - version: "15.2.0" - web: - dependency: transitive - description: - name: web - sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a" - url: "https://pub.dev" - source: hosted - version: "1.1.1" - win32: - dependency: transitive - description: - name: win32 - sha256: d7cb55e04cd34096cd3a79b3330245f54cb96a370a1c27adb3c84b917de8b08e - url: "https://pub.dev" - source: hosted - version: "5.15.0" - win32_registry: - dependency: transitive - description: - name: win32_registry - sha256: "6f1b564492d0147b330dd794fee8f512cec4977957f310f9951b5f9d83618dae" - url: "https://pub.dev" - source: hosted - version: "2.1.0" -sdks: - dart: ">=3.11.0 <4.0.0" - flutter: ">=3.35.0" diff --git a/mobile/packages/ui/showcase/pubspec.yaml b/mobile/packages/ui/showcase/pubspec.yaml deleted file mode 100644 index 6353600ce3..0000000000 --- a/mobile/packages/ui/showcase/pubspec.yaml +++ /dev/null @@ -1,47 +0,0 @@ -name: showcase -publish_to: 'none' - -version: 1.0.0+1 - -environment: - sdk: ^3.11.0 - -dependencies: - flutter: - sdk: flutter - immich_ui: - path: ../ - go_router: ^17.2.1 - syntax_highlight: ^0.5.0 - -dev_dependencies: - flutter_test: - sdk: flutter - flutter_lints: ^6.0.0 - -flutter: - uses-material-design: true - assets: - - assets/ - - assets/themes/ - - lib/pages/components/examples/ - - fonts: - - family: GoogleSans - fonts: - - asset: ../../../fonts/GoogleSans/GoogleSans-Regular.ttf - - asset: ../../../fonts/GoogleSans/GoogleSans-Italic.ttf - style: italic - - asset: ../../../fonts/GoogleSans/GoogleSans-Medium.ttf - weight: 500 - - asset: ../../../fonts/GoogleSans/GoogleSans-SemiBold.ttf - weight: 600 - - asset: ../../../fonts/GoogleSans/GoogleSans-Bold.ttf - weight: 700 - - family: GoogleSansCode - fonts: - - asset: ../../../fonts/GoogleSansCode/GoogleSansCode-Regular.ttf - - asset: ../../../fonts/GoogleSansCode/GoogleSansCode-Medium.ttf - weight: 500 - - asset: ../../../fonts/GoogleSansCode/GoogleSansCode-SemiBold.ttf - weight: 600 \ No newline at end of file diff --git a/mobile/packages/ui/showcase/web/favicon.ico b/mobile/packages/ui/showcase/web/favicon.ico deleted file mode 100644 index 7ec34e9e53..0000000000 Binary files a/mobile/packages/ui/showcase/web/favicon.ico and /dev/null differ diff --git a/mobile/packages/ui/showcase/web/icons/Icon-maskable-192.png b/mobile/packages/ui/showcase/web/icons/Icon-maskable-192.png deleted file mode 100644 index 49fd3ae289..0000000000 Binary files a/mobile/packages/ui/showcase/web/icons/Icon-maskable-192.png and /dev/null differ diff --git a/mobile/packages/ui/showcase/web/icons/Icon-maskable-512.png b/mobile/packages/ui/showcase/web/icons/Icon-maskable-512.png deleted file mode 100644 index a7220554bc..0000000000 Binary files a/mobile/packages/ui/showcase/web/icons/Icon-maskable-512.png and /dev/null differ diff --git a/mobile/packages/ui/showcase/web/icons/apple-icon-180.png b/mobile/packages/ui/showcase/web/icons/apple-icon-180.png deleted file mode 100644 index 4e642631a3..0000000000 Binary files a/mobile/packages/ui/showcase/web/icons/apple-icon-180.png and /dev/null differ diff --git a/mobile/packages/ui/showcase/web/index.html b/mobile/packages/ui/showcase/web/index.html deleted file mode 100644 index abf42ad1fd..0000000000 --- a/mobile/packages/ui/showcase/web/index.html +++ /dev/null @@ -1,38 +0,0 @@ - - - - - - - - - - - - - - - - - - - - @immich/ui - - - - - - diff --git a/mobile/packages/ui/showcase/web/manifest.json b/mobile/packages/ui/showcase/web/manifest.json deleted file mode 100644 index 25b44bd1ae..0000000000 --- a/mobile/packages/ui/showcase/web/manifest.json +++ /dev/null @@ -1,37 +0,0 @@ -{ - "name": "@immich/ui Showcase", - "short_name": "@immich/ui", - "start_url": ".", - "display": "standalone", - "background_color": "#FCFCFD", - "theme_color": "#4250AF", - "description": "Immich UI component library showcase and documentation", - "orientation": "landscape", - "prefer_related_applications": false, - "icons": [ - { - "src": "icons/Icon-maskable-192.png", - "sizes": "192x192", - "type": "image/png", - "purpose": "any" - }, - { - "src": "icons/Icon-maskable-192.png", - "sizes": "192x192", - "type": "image/png", - "purpose": "maskable" - }, - { - "src": "icons/Icon-maskable-512.png", - "sizes": "512x512", - "type": "image/png", - "purpose": "any" - }, - { - "src": "icons/Icon-maskable-512.png", - "sizes": "512x512", - "type": "image/png", - "purpose": "maskable" - } - ] -}