From 36f9332f614a5d02c87c73f608f2be7d593ff64e Mon Sep 17 00:00:00 2001 From: Juan Gilsanz Polo Date: Thu, 22 Feb 2024 02:11:10 +0100 Subject: [PATCH] Created onboarding --- assets/resources/github.svg | 1 + assets/resources/google-play.svg | 1 + lib/i18n/strings.g.dart | 88 +++++++++++- lib/i18n/strings_en.i18n.json | 15 ++ lib/i18n/strings_es.i18n.json | 15 ++ lib/providers/api_client_provider.dart | 5 + lib/providers/api_client_provider.g.dart | 2 +- lib/providers/router_provider.dart | 2 +- lib/providers/router_provider.g.dart | 2 +- lib/providers/server_instances_provider.dart | 7 +- .../server_instances_provider.g.dart | 2 +- lib/router/paths.dart | 2 +- lib/router/routes.dart | 6 +- .../connect/provider/connect_provider.g.dart | 2 +- .../onboarding/model/onboarding.model.dart | 16 +++ .../provider/onboarding.provider.dart | 35 +++++ .../provider/onboarding.provider.g.dart | 25 ++++ lib/screens/onboarding/ui/onboarding.dart | 29 ++++ .../onboarding/ui/server_required.dart | 132 ++++++++++++++++++ lib/screens/onboarding/ui/welcome.dart | 79 +++++++++++ lib/screens/settings/ui/settings.dart | 44 +++--- pubspec.lock | 64 +++++++++ pubspec.yaml | 7 +- 23 files changed, 538 insertions(+), 43 deletions(-) create mode 100644 assets/resources/github.svg create mode 100644 assets/resources/google-play.svg create mode 100644 lib/screens/onboarding/model/onboarding.model.dart create mode 100644 lib/screens/onboarding/provider/onboarding.provider.dart create mode 100644 lib/screens/onboarding/provider/onboarding.provider.g.dart create mode 100644 lib/screens/onboarding/ui/onboarding.dart create mode 100644 lib/screens/onboarding/ui/server_required.dart create mode 100644 lib/screens/onboarding/ui/welcome.dart diff --git a/assets/resources/github.svg b/assets/resources/github.svg new file mode 100644 index 0000000..25d9004 --- /dev/null +++ b/assets/resources/github.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/resources/google-play.svg b/assets/resources/google-play.svg new file mode 100644 index 0000000..eda3968 --- /dev/null +++ b/assets/resources/google-play.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/lib/i18n/strings.g.dart b/lib/i18n/strings.g.dart index 9b2692a..759323d 100644 --- a/lib/i18n/strings.g.dart +++ b/lib/i18n/strings.g.dart @@ -4,9 +4,9 @@ /// To regenerate, run: `dart run slang` /// /// Locales: 2 -/// Strings: 8 (4 per locale) +/// Strings: 30 (15 per locale) /// -/// Built on 2024-02-21 at 23:50 UTC +/// Built on 2024-02-22 at 01:09 UTC // coverage:ignore-file // ignore_for_file: type=lint @@ -148,8 +148,28 @@ class Translations implements BaseTranslations { late final Translations _root = this; // ignore: unused_field // Translations + late final _StringsOnboardingEn onboarding = _StringsOnboardingEn._(_root); late final _StringsConnectEn connect = _StringsConnectEn._(_root); late final _StringsLinksEn links = _StringsLinksEn._(_root); + late final _StringsSettingsEn settings = _StringsSettingsEn._(_root); +} + +// Path: onboarding +class _StringsOnboardingEn { + _StringsOnboardingEn._(this._root); + + final Translations _root; // ignore: unused_field + + // Translations + String get title => 'Welcome to My Linkding'; + String get subtitle => 'An application to manage your bookmarks.'; + String get start => 'Start'; + String get next => 'Next'; + String get previous => 'Previous'; + String get serverRequired => 'Servidor requerido'; + String get serverRequiredDescription => 'My Linkding it\'s not an standalone app, it requires the Linkding server to work.\nIn order to use this application, you must deploy Linkding on your home server, VPS or any other computer.'; + String get installationInstructions => 'Check the installation instructions on the official GitHub repository.'; + String get serverRunningConfirmation => 'I confirm that I have an instance of the Linkding server already running.'; } // Path: connect @@ -174,6 +194,17 @@ class _StringsLinksEn { String get links => 'Links'; } +// Path: settings +class _StringsSettingsEn { + _StringsSettingsEn._(this._root); + + final Translations _root; // ignore: unused_field + + // Translations + String get settings => 'Settings'; + String get disconnectFromServer => 'Disconnect from server'; +} + // Path: class _StringsEs implements Translations { /// You can call this constructor and build your own translation instance of this locale. @@ -198,8 +229,28 @@ class _StringsEs implements Translations { @override late final _StringsEs _root = this; // ignore: unused_field // Translations + @override late final _StringsOnboardingEs onboarding = _StringsOnboardingEs._(_root); @override late final _StringsConnectEs connect = _StringsConnectEs._(_root); @override late final _StringsLinksEs links = _StringsLinksEs._(_root); + @override late final _StringsSettingsEs settings = _StringsSettingsEs._(_root); +} + +// Path: onboarding +class _StringsOnboardingEs implements _StringsOnboardingEn { + _StringsOnboardingEs._(this._root); + + @override final _StringsEs _root; // ignore: unused_field + + // Translations + @override String get title => 'Bienvenido a My Linkding'; + @override String get subtitle => 'Una aplicación para gestionar tus enlaces favoritos.'; + @override String get start => 'Comenzar'; + @override String get next => 'Siguiente'; + @override String get previous => 'Anterior'; + @override String get serverRequired => 'Servidor requerido'; + @override String get serverRequiredDescription => 'My Linkding no es una aplicación independiente, requiere el servidor Linkding para funcionar.\nPara utilizar esta aplicación, debe instalar Linkding en su servidor doméstico, VPS o cualquier otro ordenador.'; + @override String get installationInstructions => 'Mira las instrucciones de instalación en el repositorio oficial en GitHub.'; + @override String get serverRunningConfirmation => 'Confirmo que tengo una instancia del servidor Linkding ya en funcionamiento.'; } // Path: connect @@ -224,16 +275,38 @@ class _StringsLinksEs implements _StringsLinksEn { @override String get links => 'Enlaces'; } +// Path: settings +class _StringsSettingsEs implements _StringsSettingsEn { + _StringsSettingsEs._(this._root); + + @override final _StringsEs _root; // ignore: unused_field + + // Translations + @override String get settings => 'Ajustes'; + @override String get disconnectFromServer => 'Desconectar del servidor'; +} + /// Flat map(s) containing all translations. /// Only for edge cases! For simple maps, use the map function of this library. extension on Translations { dynamic _flatMapFunction(String path) { switch (path) { + case 'onboarding.title': return 'Welcome to My Linkding'; + case 'onboarding.subtitle': return 'An application to manage your bookmarks.'; + case 'onboarding.start': return 'Start'; + case 'onboarding.next': return 'Next'; + case 'onboarding.previous': return 'Previous'; + case 'onboarding.serverRequired': return 'Servidor requerido'; + case 'onboarding.serverRequiredDescription': return 'My Linkding it\'s not an standalone app, it requires the Linkding server to work.\nIn order to use this application, you must deploy Linkding on your home server, VPS or any other computer.'; + case 'onboarding.installationInstructions': return 'Check the installation instructions on the official GitHub repository.'; + case 'onboarding.serverRunningConfirmation': return 'I confirm that I have an instance of the Linkding server already running.'; case 'connect.connect': return 'Connect'; case 'connect.connecting': return 'Connecting...'; case 'connect.cannotConnectToServer': return 'Cannot connect to the server.'; case 'links.links': return 'Links'; + case 'settings.settings': return 'Settings'; + case 'settings.disconnectFromServer': return 'Disconnect from server'; default: return null; } } @@ -242,10 +315,21 @@ extension on Translations { extension on _StringsEs { dynamic _flatMapFunction(String path) { switch (path) { + case 'onboarding.title': return 'Bienvenido a My Linkding'; + case 'onboarding.subtitle': return 'Una aplicación para gestionar tus enlaces favoritos.'; + case 'onboarding.start': return 'Comenzar'; + case 'onboarding.next': return 'Siguiente'; + case 'onboarding.previous': return 'Anterior'; + case 'onboarding.serverRequired': return 'Servidor requerido'; + case 'onboarding.serverRequiredDescription': return 'My Linkding no es una aplicación independiente, requiere el servidor Linkding para funcionar.\nPara utilizar esta aplicación, debe instalar Linkding en su servidor doméstico, VPS o cualquier otro ordenador.'; + case 'onboarding.installationInstructions': return 'Mira las instrucciones de instalación en el repositorio oficial en GitHub.'; + case 'onboarding.serverRunningConfirmation': return 'Confirmo que tengo una instancia del servidor Linkding ya en funcionamiento.'; case 'connect.connect': return 'Conectar'; case 'connect.connecting': return 'Conectando...'; case 'connect.cannotConnectToServer': return 'No se puede conectar con el servidor.'; case 'links.links': return 'Enlaces'; + case 'settings.settings': return 'Ajustes'; + case 'settings.disconnectFromServer': return 'Desconectar del servidor'; default: return null; } } diff --git a/lib/i18n/strings_en.i18n.json b/lib/i18n/strings_en.i18n.json index 92b6646..dc6b905 100644 --- a/lib/i18n/strings_en.i18n.json +++ b/lib/i18n/strings_en.i18n.json @@ -1,4 +1,15 @@ { + "onboarding": { + "title": "Welcome to My Linkding", + "subtitle": "An application to manage your bookmarks.", + "start": "Start", + "next": "Next", + "previous": "Previous", + "serverRequired": "Servidor requerido", + "serverRequiredDescription": "My Linkding it's not an standalone app, it requires the Linkding server to work.\nIn order to use this application, you must deploy Linkding on your home server, VPS or any other computer.", + "installationInstructions": "Check the installation instructions on the official GitHub repository.", + "serverRunningConfirmation": "I confirm that I have an instance of the Linkding server already running." + }, "connect": { "connect": "Connect", "connecting": "Connecting...", @@ -6,5 +17,9 @@ }, "links": { "links": "Links" + }, + "settings": { + "settings": "Settings", + "disconnectFromServer": "Disconnect from server" } } \ No newline at end of file diff --git a/lib/i18n/strings_es.i18n.json b/lib/i18n/strings_es.i18n.json index 12a0edc..d431ef4 100644 --- a/lib/i18n/strings_es.i18n.json +++ b/lib/i18n/strings_es.i18n.json @@ -1,4 +1,15 @@ { + "onboarding": { + "title": "Bienvenido a My Linkding", + "subtitle": "Una aplicación para gestionar tus enlaces favoritos.", + "start": "Comenzar", + "next": "Siguiente", + "previous": "Anterior", + "serverRequired": "Servidor requerido", + "serverRequiredDescription": "My Linkding no es una aplicación independiente, requiere el servidor Linkding para funcionar.\nPara utilizar esta aplicación, debe instalar Linkding en su servidor doméstico, VPS o cualquier otro ordenador.", + "installationInstructions": "Mira las instrucciones de instalación en el repositorio oficial en GitHub.", + "serverRunningConfirmation": "Confirmo que tengo una instancia del servidor Linkding ya en funcionamiento." + }, "connect": { "connect": "Conectar", "connecting": "Conectando...", @@ -6,5 +17,9 @@ }, "links": { "links": "Enlaces" + }, + "settings": { + "settings": "Ajustes", + "disconnectFromServer": "Desconectar del servidor" } } \ No newline at end of file diff --git a/lib/providers/api_client_provider.dart b/lib/providers/api_client_provider.dart index 7951e10..afca682 100644 --- a/lib/providers/api_client_provider.dart +++ b/lib/providers/api_client_provider.dart @@ -18,4 +18,9 @@ class ApiClientProvider extends _$ApiClientProvider { void setApiClient(ApiClient client) { state = client; } + + void disconnectApiClient() { + state = null; + ref.read(serverInstancesProvider.notifier).removeServerInstances(); + } } diff --git a/lib/providers/api_client_provider.g.dart b/lib/providers/api_client_provider.g.dart index 45ac7ca..1a90cff 100644 --- a/lib/providers/api_client_provider.g.dart +++ b/lib/providers/api_client_provider.g.dart @@ -6,7 +6,7 @@ part of 'api_client_provider.dart'; // RiverpodGenerator // ************************************************************************** -String _$apiClientProviderHash() => r'eff6aeacbfe369f2204d77f4884cc9d78ecf1e27'; +String _$apiClientProviderHash() => r'8c44873c4e0758acfcd64b181134b80a43327f46'; /// See also [ApiClientProvider]. @ProviderFor(ApiClientProvider) diff --git a/lib/providers/router_provider.dart b/lib/providers/router_provider.dart index b91c41b..38bc7d3 100644 --- a/lib/providers/router_provider.dart +++ b/lib/providers/router_provider.dart @@ -12,7 +12,7 @@ GoRouter router(RouterRef ref) { return GoRouter( redirect: (context, state) { if (ref.watch(apiClientProviderProvider) == null) { - return RoutesPaths.connect; + return RoutesPaths.onboarding; } return null; }, diff --git a/lib/providers/router_provider.g.dart b/lib/providers/router_provider.g.dart index 409ec4b..bda4d00 100644 --- a/lib/providers/router_provider.g.dart +++ b/lib/providers/router_provider.g.dart @@ -6,7 +6,7 @@ part of 'router_provider.dart'; // RiverpodGenerator // ************************************************************************** -String _$routerHash() => r'47e3f3d747dcb7b2543c73209f8a2f5561eb6ff7'; +String _$routerHash() => r'78eb4c32cddebb29e3029e0eeaeb0f4c40d77012'; /// See also [router]. @ProviderFor(router) diff --git a/lib/providers/server_instances_provider.dart b/lib/providers/server_instances_provider.dart index 5ae0c39..baeb2de 100644 --- a/lib/providers/server_instances_provider.dart +++ b/lib/providers/server_instances_provider.dart @@ -4,8 +4,6 @@ import 'package:my_linkding/config/shared_preferences_keys.dart'; import 'package:my_linkding/providers/shared_preferences_provider.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; -import 'package:my_linkding/services/api_client.dart'; -import 'package:my_linkding/providers/api_client_provider.dart'; import 'package:my_linkding/models/server_instance.dart'; part 'server_instances_provider.g.dart'; @@ -45,4 +43,9 @@ class ServerInstances extends _$ServerInstances { final stringList = instances.map((i) => jsonEncode(i.toJson())).toList(); ref.read(sharedPreferencesProvider).setStringList(SharedPreferencesKeys.serversInstances, stringList); } + + void removeServerInstances() { + state = []; + ref.read(sharedPreferencesProvider).remove(SharedPreferencesKeys.serversInstances); + } } diff --git a/lib/providers/server_instances_provider.g.dart b/lib/providers/server_instances_provider.g.dart index 7ab7573..f5aad9d 100644 --- a/lib/providers/server_instances_provider.g.dart +++ b/lib/providers/server_instances_provider.g.dart @@ -6,7 +6,7 @@ part of 'server_instances_provider.dart'; // RiverpodGenerator // ************************************************************************** -String _$serverInstancesHash() => r'82b7d3367afbe5c19ad0238977e5df35fac3499b'; +String _$serverInstancesHash() => r'ee41f5a717b9f779aa4319cc711b479a47c1c9ef'; /// See also [ServerInstances]. @ProviderFor(ServerInstances) diff --git a/lib/router/paths.dart b/lib/router/paths.dart index 44e874b..c6dff73 100644 --- a/lib/router/paths.dart +++ b/lib/router/paths.dart @@ -1,5 +1,5 @@ class RoutesPaths { - static const connect = "/connect"; + static const onboarding = "/onboarding"; static const links = "/links"; static const search = "/search"; static const settings = "/settings"; diff --git a/lib/router/routes.dart b/lib/router/routes.dart index 613570a..167c5ba 100644 --- a/lib/router/routes.dart +++ b/lib/router/routes.dart @@ -1,8 +1,8 @@ import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; -import 'package:my_linkding/screens/connect/ui/connect.dart'; import 'package:my_linkding/screens/links/ui/links.dart'; +import 'package:my_linkding/screens/onboarding/ui/onboarding.dart'; import 'package:my_linkding/screens/search/ui/search.dart'; import 'package:my_linkding/screens/settings/ui/settings.dart'; import 'package:my_linkding/widgets/layout.dart'; @@ -16,8 +16,8 @@ final settingsNavigatorKey = GlobalKey(); final List appRoutes = [ GoRoute( - path: RoutesPaths.connect, - builder: (context, state) => const Connect(), + path: RoutesPaths.onboarding, + builder: (context, state) => const Onboarding(), ), StatefulShellRoute.indexedStack( builder: (context, state, navigationShell) => Layout( diff --git a/lib/screens/connect/provider/connect_provider.g.dart b/lib/screens/connect/provider/connect_provider.g.dart index 2de0a71..d19e7ff 100644 --- a/lib/screens/connect/provider/connect_provider.g.dart +++ b/lib/screens/connect/provider/connect_provider.g.dart @@ -6,7 +6,7 @@ part of 'connect_provider.dart'; // RiverpodGenerator // ************************************************************************** -String _$connectToServerHash() => r'af66fee9a4bf26aba08b27ff5a7fe4643215117a'; +String _$connectToServerHash() => r'c40898fd5dd6dd7507140c7a69a6f336a56e30c0'; /// See also [connectToServer]. @ProviderFor(connectToServer) diff --git a/lib/screens/onboarding/model/onboarding.model.dart b/lib/screens/onboarding/model/onboarding.model.dart new file mode 100644 index 0000000..3fb0d7a --- /dev/null +++ b/lib/screens/onboarding/model/onboarding.model.dart @@ -0,0 +1,16 @@ +import 'package:flutter/material.dart'; + +class OnboardingModel { + final PageController pageController; + final bool confirmServerRunning; + + OnboardingModel({ + required this.pageController, + required this.confirmServerRunning, + }); + + OnboardingModel toggleConfirmServerRunning(bool newValue) => OnboardingModel( + pageController: pageController, + confirmServerRunning: newValue, + ); +} diff --git a/lib/screens/onboarding/provider/onboarding.provider.dart b/lib/screens/onboarding/provider/onboarding.provider.dart new file mode 100644 index 0000000..2ed2d34 --- /dev/null +++ b/lib/screens/onboarding/provider/onboarding.provider.dart @@ -0,0 +1,35 @@ +import 'package:flutter/material.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +import 'package:my_linkding/screens/onboarding/model/onboarding.model.dart'; + +part 'onboarding.provider.g.dart'; + +@riverpod +class Onboarding extends _$Onboarding { + @override + OnboardingModel build() { + return OnboardingModel( + pageController: PageController(), + confirmServerRunning: false, + ); + } + + void nextPage() { + state.pageController.nextPage( + duration: const Duration(milliseconds: 200), + curve: Curves.ease, + ); + } + + void previousPage() { + state.pageController.previousPage( + duration: const Duration(milliseconds: 200), + curve: Curves.ease, + ); + } + + void confirmServerRunning() { + state = state.toggleConfirmServerRunning(!state.confirmServerRunning); + } +} diff --git a/lib/screens/onboarding/provider/onboarding.provider.g.dart b/lib/screens/onboarding/provider/onboarding.provider.g.dart new file mode 100644 index 0000000..7e1feff --- /dev/null +++ b/lib/screens/onboarding/provider/onboarding.provider.g.dart @@ -0,0 +1,25 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'onboarding.provider.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$onboardingHash() => r'5c876d53acbca92ab15630516099700609aa4f89'; + +/// See also [Onboarding]. +@ProviderFor(Onboarding) +final onboardingProvider = + AutoDisposeNotifierProvider.internal( + Onboarding.new, + name: r'onboardingProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') ? null : _$onboardingHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef _$Onboarding = AutoDisposeNotifier; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/lib/screens/onboarding/ui/onboarding.dart b/lib/screens/onboarding/ui/onboarding.dart new file mode 100644 index 0000000..efce192 --- /dev/null +++ b/lib/screens/onboarding/ui/onboarding.dart @@ -0,0 +1,29 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +import 'package:my_linkding/screens/onboarding/provider/onboarding.provider.dart'; +import 'package:my_linkding/screens/onboarding/ui/server_required.dart'; +import 'package:my_linkding/screens/onboarding/ui/welcome.dart'; +import 'package:my_linkding/widgets/system_overlay_style.dart'; + +class Onboarding extends ConsumerWidget { + const Onboarding({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context, WidgetRef ref) { + return OverlayStyle( + child: Scaffold( + body: SafeArea( + child: PageView( + controller: ref.watch(onboardingProvider).pageController, + physics: const NeverScrollableScrollPhysics(), + children: const [ + OnboardingWelcome(), + OnboardingServerRequired(), + ], + ), + ), + ), + ); + } +} diff --git a/lib/screens/onboarding/ui/server_required.dart b/lib/screens/onboarding/ui/server_required.dart new file mode 100644 index 0000000..63999a3 --- /dev/null +++ b/lib/screens/onboarding/ui/server_required.dart @@ -0,0 +1,132 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_svg/flutter_svg.dart'; + +import 'package:my_linkding/screens/onboarding/provider/onboarding.provider.dart'; + +import 'package:my_linkding/i18n/strings.g.dart'; + +class OnboardingServerRequired extends ConsumerWidget { + const OnboardingServerRequired({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context, WidgetRef ref) { + return Padding( + padding: const EdgeInsets.all(24), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Expanded( + child: ListView( + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Icon( + Icons.dns_rounded, + size: 50, + color: Theme.of(context).colorScheme.primary, + ), + const SizedBox(height: 24), + Text( + t.onboarding.serverRequired, + style: const TextStyle(fontSize: 28), + ), + const SizedBox(height: 8), + Text( + t.onboarding.serverRequiredDescription, + style: TextStyle( + fontSize: 16, + color: Theme.of(context).colorScheme.onSurfaceVariant, + ), + ), + ], + ), + Container(height: 24), + Card( + child: InkWell( + onTap: () => {}, + borderRadius: BorderRadius.circular(10), + child: Padding( + padding: const EdgeInsets.all(24), + child: Row( + children: [ + SvgPicture.asset( + 'assets/resources/github.svg', + color: Theme.of(context).colorScheme.primary, + width: 40, + height: 40, + ), + const SizedBox(width: 24), + Flexible( + child: Text( + t.onboarding.installationInstructions, + style: TextStyle( + fontSize: 16, + color: Theme.of(context).colorScheme.primary, + fontWeight: FontWeight.w500, + ), + ), + ), + ], + ), + ), + ), + ), + const SizedBox(height: 8), + ], + ), + ), + Column( + children: [ + ListTile( + contentPadding: const EdgeInsets.all(8), + leading: Checkbox( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(5), + ), + value: ref.watch(onboardingProvider).confirmServerRunning, + onChanged: (_) => ref.read(onboardingProvider.notifier).confirmServerRunning(), + ), + title: Text( + t.onboarding.serverRunningConfirmation, + style: const TextStyle(fontSize: 14), + ), + onTap: () => ref.read(onboardingProvider.notifier).confirmServerRunning(), + ), + const SizedBox(height: 8), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + FilledButton( + onPressed: () => ref.read(onboardingProvider.notifier).previousPage(), + child: Row( + children: [ + const Icon(Icons.arrow_back_rounded), + const SizedBox(width: 8), + Text(t.onboarding.previous), + ], + ), + ), + FilledButton( + onPressed: ref.watch(onboardingProvider).confirmServerRunning == true + ? () => ref.read(onboardingProvider.notifier).nextPage() + : null, + child: Row( + children: [ + Text(t.onboarding.next), + const SizedBox(width: 8), + const Icon(Icons.arrow_forward_rounded), + ], + ), + ), + ], + ), + ], + ), + ], + ), + ); + } +} diff --git a/lib/screens/onboarding/ui/welcome.dart b/lib/screens/onboarding/ui/welcome.dart new file mode 100644 index 0000000..1a15dcc --- /dev/null +++ b/lib/screens/onboarding/ui/welcome.dart @@ -0,0 +1,79 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:my_linkding/i18n/strings.g.dart'; +import 'package:my_linkding/screens/onboarding/provider/onboarding.provider.dart'; + +class OnboardingWelcome extends ConsumerWidget { + const OnboardingWelcome({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final width = MediaQuery.of(context).size.width; + final height = MediaQuery.of(context).size.height; + + final iconSize = width > height ? height : width; + + return Padding( + padding: const EdgeInsets.all(24), + child: Stack( + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + width: 50, + height: 50, + color: Colors.red, + ), + const SizedBox(height: 24), + Text( + t.onboarding.title, + style: const TextStyle(fontSize: 28), + ), + const SizedBox(height: 8), + Text( + t.onboarding.subtitle, + style: TextStyle( + fontSize: 16, + color: Theme.of(context).colorScheme.onSurfaceVariant, + ), + ), + ], + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + const SizedBox(), + FilledButton( + onPressed: () => ref.read(onboardingProvider.notifier).nextPage(), + child: Row( + children: [ + Text(t.onboarding.start), + const SizedBox(width: 8), + const Icon(Icons.arrow_forward_rounded), + ], + ), + ), + ], + ), + ], + ), + Center( + child: Transform.rotate( + angle: 0.7, + child: Icon( + Icons.link_rounded, + size: iconSize - 48, + color: Theme.of(context).colorScheme.secondary.withOpacity(0.2), + ), + ), + ), + ], + ), + ); + } +} diff --git a/lib/screens/settings/ui/settings.dart b/lib/screens/settings/ui/settings.dart index 2459d3e..e0879f8 100644 --- a/lib/screens/settings/ui/settings.dart +++ b/lib/screens/settings/ui/settings.dart @@ -1,38 +1,28 @@ import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; -class Settings extends StatelessWidget { +import 'package:my_linkding/i18n/strings.g.dart'; +import 'package:my_linkding/providers/api_client_provider.dart'; + +class Settings extends ConsumerWidget { const Settings({Key? key}) : super(key: key); @override - Widget build(BuildContext context) { - return Material( - color: Colors.transparent, - child: NestedScrollView( - headerSliverBuilder: (context, innerBoxIsScrolled) => [ - SliverOverlapAbsorber( - handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context), - sliver: SliverAppBar.large( - pinned: true, - floating: true, - centerTitle: false, - forceElevated: innerBoxIsScrolled, - title: Text("Settings"), + Widget build(BuildContext context, WidgetRef ref) { + return Scaffold( + appBar: AppBar( + title: Text(t.settings.settings), + ), + body: ListView( + children: [ + FilledButton.icon( + onPressed: () => ref.read(apiClientProviderProvider.notifier).disconnectApiClient(), + icon: const Icon(Icons.clear_rounded), + label: Text( + t.settings.disconnectFromServer, ), ), ], - body: SafeArea( - top: false, - bottom: false, - child: Builder( - builder: (context) => CustomScrollView( - slivers: [ - SliverOverlapInjector( - handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context), - ), - ], - ), - ), - ), ), ); } diff --git a/pubspec.lock b/pubspec.lock index 272bd03..d4ad9ca 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -315,6 +315,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.4.10" + flutter_svg: + dependency: "direct main" + description: + name: flutter_svg + sha256: "7b4ca6cf3304575fe9c8ec64813c8d02ee41d2afe60bcfe0678bcb5375d596a2" + url: "https://pub.dev" + source: hosted + version: "2.0.10+1" flutter_test: dependency: "direct dev" description: flutter @@ -373,6 +381,14 @@ packages: url: "https://pub.dev" source: hosted version: "4.2.0" + http: + dependency: transitive + description: + name: http + sha256: a2bbf9d017fcced29139daa8ed2bba4ece450ab222871df93ca9eec6f80c34ba + url: "https://pub.dev" + source: hosted + version: "1.2.0" http_multi_server: dependency: transitive description: @@ -493,6 +509,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.8.3" + path_parsing: + dependency: transitive + description: + name: path_parsing + sha256: e3e67b1629e6f7e8100b367d3db6ba6af4b1f0bb80f64db18ef1fbabd2fa9ccf + url: "https://pub.dev" + source: hosted + version: "1.0.1" path_provider_linux: dependency: transitive description: @@ -517,6 +541,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.2.1" + petitparser: + dependency: transitive + description: + name: petitparser + sha256: c15605cd28af66339f8eb6fbe0e541bfe2d1b72d5825efc6598f3e0a31b9ad27 + url: "https://pub.dev" + source: hosted + version: "6.0.2" platform: dependency: transitive description: @@ -882,6 +914,30 @@ packages: url: "https://pub.dev" source: hosted version: "4.3.3" + vector_graphics: + dependency: transitive + description: + name: vector_graphics + sha256: "32c3c684e02f9bc0afb0ae0aa653337a2fe022e8ab064bcd7ffda27a74e288e3" + url: "https://pub.dev" + source: hosted + version: "1.1.11+1" + vector_graphics_codec: + dependency: transitive + description: + name: vector_graphics_codec + sha256: c86987475f162fadff579e7320c7ddda04cd2fdeffbe1129227a85d9ac9e03da + url: "https://pub.dev" + source: hosted + version: "1.1.11+1" + vector_graphics_compiler: + dependency: transitive + description: + name: vector_graphics_compiler + sha256: "12faff3f73b1741a36ca7e31b292ddeb629af819ca9efe9953b70bd63fc8cd81" + url: "https://pub.dev" + source: hosted + version: "1.1.11+1" vector_math: dependency: transitive description: @@ -938,6 +994,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.4" + xml: + dependency: transitive + description: + name: xml + sha256: b015a8ad1c488f66851d762d3090a21c600e479dc75e68328c52774040cf9226 + url: "https://pub.dev" + source: hosted + version: "6.5.0" yaml: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 545d0cc..f45e93d 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -45,6 +45,7 @@ dependencies: uuid: ^4.3.3 slang: ^3.29.0 slang_flutter: ^3.29.0 + flutter_svg: ^2.0.9 dev_dependencies: build_runner: ^2.4.8 @@ -64,9 +65,9 @@ flutter: uses-material-design: true # To add assets to your application, add an assets section, like this: - # assets: - # - images/a_dot_burr.jpeg - # - images/a_dot_ham.jpeg + assets: + - assets/resources/github.svg + - assets/resources/google-play.svg # An image asset can refer to one or more resolution-specific "variants", see # https://flutter.dev/assets-and-images/#resolution-aware