diff --git a/lib/main.dart b/lib/main.dart index bf2c432..9578083 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -40,15 +40,20 @@ class MyApp extends ConsumerWidget { return apiSettings.activeUser == null || servers.isEmpty; } + var routerConfig = MyAppRouter(needOnboarding: needOnboarding()).config; + // if (needOnboarding()) { + // routerConfig.goNamed(Routes.onboarding); + // } + + return MaterialApp.router( theme: lightTheme, darkTheme: darkTheme, themeMode: ref.watch(appSettingsProvider).isDarkMode ? ThemeMode.dark : ThemeMode.light, - routerConfig: MyAppRouter(needOnboarding: needOnboarding()).config, + routerConfig: routerConfig, // routerConfig: _router.config, - ); } } diff --git a/lib/pages/navigation.dart b/lib/pages/navigation.dart new file mode 100644 index 0000000..400e3df --- /dev/null +++ b/lib/pages/navigation.dart @@ -0,0 +1,32 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; + +class AppNavigation extends HookConsumerWidget { + const AppNavigation({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final currentIndex = useState(0); + return Scaffold( + bottomNavigationBar: BottomNavigationBar( + currentIndex: currentIndex.value, + onTap: (value) => currentIndex.value = value, + items: const [ + BottomNavigationBarItem( + icon: Icon(Icons.home), + label: 'Home', + ), + BottomNavigationBarItem( + icon: Icon(Icons.business), + label: 'Business', + ), + BottomNavigationBarItem( + icon: Icon(Icons.school), + label: 'School', + ), + ], + ), + ); + } +} diff --git a/lib/pages/onboarding/onboarding.dart b/lib/pages/onboarding/onboarding.dart deleted file mode 100644 index 7971df1..0000000 --- a/lib/pages/onboarding/onboarding.dart +++ /dev/null @@ -1,89 +0,0 @@ -import 'package:coast/coast.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_hooks/flutter_hooks.dart'; -import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:whispering_pages/pages/onboarding/server_setup.dart'; -import 'package:whispering_pages/pages/onboarding/user_login.dart'; -import 'package:whispering_pages/settings/api_settings_provider.dart'; - -const _serverTag = 'server'; - -class OnboardingPage extends StatefulHookConsumerWidget { - const OnboardingPage({super.key}); - - @override - OnboardingPageState createState() => OnboardingPageState(); -} - -class OnboardingPageState extends ConsumerState { - final coastController = CoastController(); - - @override - void dispose() { - coastController.dispose(); - super.dispose(); - } - - @override - Widget build(BuildContext context) { - final apiSettings = ref.watch(apiSettingsProvider); - - final serverUriController = useTextEditingController( - text: apiSettings.activeServer?.serverUrl.toString(), - ); - - bool isUserLoginAvailable() { - return apiSettings.activeServer != null; - } - - // ignore: invalid_use_of_protected_member - if (isUserLoginAvailable()) { - try { - coastController.animateTo( - beach: 1, - duration: const Duration(milliseconds: 500), - curve: Curves.easeInOut, - ); - } catch (e) { - debugPrint('Error: $e'); - } - } - - final beaches = [ - Beach( - builder: (context) => FirstTimeServerSetupPage( - controller: serverUriController, - heroServerTag: _serverTag, - ), - ), - isUserLoginAvailable() - ? Beach( - builder: (context) => FirstTimeUserLoginPage( - serverUriController: serverUriController, - heroServerTag: _serverTag, - ), - ) - : null, - ].nonNulls.toList(); - const activeStep = 0; - return Scaffold( - body: Stack( - children: [ - Padding( - padding: const EdgeInsets.all(8.0), - child: Coast( - beaches: beaches, - controller: coastController, - observers: [ - CrabController(), - ], - ), - ), - Positioned( - child: Container(), - ), - ], - ), - ); - } -} diff --git a/lib/pages/onboarding/onboarding_single_page.dart b/lib/pages/onboarding/onboarding_single_page.dart new file mode 100644 index 0000000..20c6ca1 --- /dev/null +++ b/lib/pages/onboarding/onboarding_single_page.dart @@ -0,0 +1,215 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_animate/flutter_animate.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:url_launcher/url_launcher.dart'; +import 'package:whispering_pages/api/api_provider.dart'; +import 'package:whispering_pages/api/authenticated_user_provider.dart'; +import 'package:whispering_pages/api/server_provider.dart'; +import 'package:whispering_pages/settings/api_settings_provider.dart'; +import 'package:whispering_pages/settings/models/models.dart' as model; +import 'package:whispering_pages/widgets/add_new_server.dart'; +import 'package:whispering_pages/widgets/user_login.dart'; + +class OnboardingSinglePage extends HookConsumerWidget { + const OnboardingSinglePage({ + super.key, + }); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final apiSettings = ref.watch(apiSettingsProvider); + final serverUriController = useTextEditingController( + text: apiSettings.activeServer?.serverUrl.toString() ?? '', + ); + final api = ref + .watch(audiobookshelfApiProvider(Uri.https(serverUriController.text))); + + final isUserLoginAvailable = useState(apiSettings.activeServer != null); + + final usernameController = useTextEditingController(); + final passwordController = useTextEditingController(); + + // reverse the animation if the user is not logged in + void addServer() { + var newServer = serverUriController.text.isEmpty + ? null + : model.AudiobookShelfServer( + serverUrl: Uri.parse(serverUriController.text), + ); + try { + // add the server to the list of servers + if (newServer != null) { + ref.read(audiobookShelfServerProvider.notifier).addServer( + newServer, + ); + } + // else remove the server from the list of servers + else if (apiSettings.activeServer != null) { + ref + .read(audiobookShelfServerProvider.notifier) + .removeServer(apiSettings.activeServer!); + } + } on ServerAlreadyExistsException catch (e) { + newServer = e.server; + } finally { + ref.read(apiSettingsProvider.notifier).updateState( + apiSettings.copyWith( + activeServer: newServer, + ), + ); + } + } + + /// Login to the server and save the user + Future loginAndSave() async { + final username = usernameController.text; + final password = passwordController.text; + final success = await api.login(username: username, password: password); + if (success != null) { + // save the server + addServer(); + var authenticatedUser = model.AuthenticatedUser( + server: model.AudiobookShelfServer( + serverUrl: Uri.parse(serverUriController.text), + ), + id: success.user.id, + password: password, + username: username, + authToken: api.token!, + ); + // add the user to the list of users + ref.read(authenticatedUserProvider.notifier).addUser(authenticatedUser); + + // set the active user + ref.read(apiSettingsProvider.notifier).updateState( + apiSettings.copyWith( + activeUser: authenticatedUser, + ), + ); + } else { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('Login failed'), + ), + ); + // give focus back to the username field + } + } + + return Scaffold( + body: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Padding( + padding: const EdgeInsets.all(8.0), + child: Text( + 'Welcome to Whispering Pages', + style: Theme.of(context).textTheme.headlineSmall, + ), + ), + const SizedBox.square( + dimension: 16.0, + ), + Padding( + padding: const EdgeInsets.all(8.0), + child: Text( + 'Please enter the URL of your AudiobookShelf Server', + style: Theme.of(context).textTheme.bodyMedium, + ), + ), + Padding( + padding: const EdgeInsets.all(8.0), + child: AddNewServer( + controller: serverUriController, + allowEmpty: true, + onPressed: () { + isUserLoginAvailable.value = + serverUriController.text.isNotEmpty; + }, + ), + ), + AnimatedSwitcher( + duration: 500.ms, + transitionBuilder: (child, animation) { + return FadeTransition( + opacity: animation, + child: SlideTransition( + position: Tween( + begin: const Offset(0, 0.3), + end: const Offset(0, 0), + ).animate(animation), + child: child, + ), + ); + }, + child: isUserLoginAvailable.value + ? UserLogin( + passwordController: passwordController, + usernameController: usernameController, + onPressed: loginAndSave, + ) + // ).animate().fade(duration: 600.ms).slideY(begin: 0.3, end: 0) + : const RedirectToABS().animate().fadeIn().slideY( + curve: Curves.easeInOut, + duration: 500.ms, + ), + ), + ], + ), + ); + } +} + +class RedirectToABS extends StatelessWidget { + const RedirectToABS({ + super.key, + }); + + @override + Widget build(BuildContext context) { + return FittedBox( + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + // a simple text with hyper link to only the "click here" part + const Text('Do not have a server? '), + // a simple text with hyper link to the github page + TextButton( + autofocus: false, + isSemanticButton: false, + style: ButtonStyle( + elevation: MaterialStateProperty.all(0), + padding: MaterialStateProperty.all( + const EdgeInsets.all(0), + ), + ), + onPressed: () async { + // open the github page + // ignore: avoid_print + print('Opening the github page'); + await _launchUrl( + Uri.parse( + 'https://www.audiobookshelf.org', + ), + ); + }, + child: const Text('Click here'), + ), + const Text(' to know how to setup a server.'), + ], + ), + ); + } +} + +Future _launchUrl(Uri url) async { + if (!await launchUrl( + url, + mode: LaunchMode.inAppWebView, + webOnlyWindowName: '_blank', + )) { + // throw Exception('Could not launch $url'); + debugPrint('Could not launch $url'); + } +} diff --git a/lib/pages/onboarding/server_setup.dart b/lib/pages/onboarding/server_setup.dart deleted file mode 100644 index cbd7c6c..0000000 --- a/lib/pages/onboarding/server_setup.dart +++ /dev/null @@ -1,66 +0,0 @@ -import 'package:coast/coast.dart'; -import 'package:flutter/material.dart'; -import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:whispering_pages/api/server_provider.dart'; -import 'package:whispering_pages/settings/models/audiobookshelf_server.dart' as model; -import 'package:whispering_pages/settings/api_settings_provider.dart'; -import 'package:whispering_pages/widgets/add_new_server.dart'; - -class FirstTimeServerSetupPage extends HookConsumerWidget { - const FirstTimeServerSetupPage({ - super.key, - required this.controller, - required this.heroServerTag, - }); - final TextEditingController controller; - final String heroServerTag; - - @override - Widget build(BuildContext context, WidgetRef ref) { - final apiSettings = ref.watch(apiSettingsProvider); - return Center( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - const Text('Welcome to Whispering Pages'), - Crab( - tag: heroServerTag, - child: AddNewServer( - controller: controller, - allowEmpty: true, - onPressed: () { - var newServer = controller.text.isEmpty - ? null - : model.AudiobookShelfServer( - serverUrl: Uri.parse(controller.text), - ); - try { - // add the server to the list of servers - if (newServer != null) { - ref.read(audiobookShelfServerProvider.notifier).addServer( - newServer, - ); - } - // else remove the server from the list of servers - else if (apiSettings.activeServer != null) { - ref - .read(audiobookShelfServerProvider.notifier) - .removeServer(apiSettings.activeServer!); - } - } on ServerAlreadyExistsException catch (e) { - newServer = e.server; - } finally { - ref.read(apiSettingsProvider.notifier).updateState( - apiSettings.copyWith( - activeServer: newServer, - ), - ); - } - }, - ), - ), - ], - ), - ); - } -} diff --git a/lib/pages/onboarding/user_login.dart b/lib/pages/onboarding/user_login.dart deleted file mode 100644 index 32dba03..0000000 --- a/lib/pages/onboarding/user_login.dart +++ /dev/null @@ -1,94 +0,0 @@ -import 'package:coast/coast.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_hooks/flutter_hooks.dart'; -import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:whispering_pages/api/api_provider.dart'; -import 'package:whispering_pages/api/authenticated_user_provider.dart' - show authenticatedUserProvider; -import 'package:whispering_pages/settings/models/audiobookshelf_server.dart'; -import 'package:whispering_pages/settings/models/authenticated_user.dart'; -import 'package:whispering_pages/settings/api_settings_provider.dart'; -import 'package:whispering_pages/widgets/add_new_server.dart'; -import 'package:whispering_pages/widgets/user_login.dart'; - - -/// Once the user has selected a server, they can login to it. -class FirstTimeUserLoginPage extends HookConsumerWidget { - const FirstTimeUserLoginPage({ - super.key, - required this.serverUriController, - required this.heroServerTag, - }); - - final TextEditingController serverUriController; - final String heroServerTag; - - @override - Widget build(BuildContext context, WidgetRef ref) { - final usernameController = useTextEditingController(); - final passwordController = useTextEditingController(); - - final apiSettings = ref.watch(apiSettingsProvider); - final api = ref - .watch(audiobookshelfApiProvider(Uri.https(serverUriController.text))); - - /// Login to the server and save the user - void loginAndSave() async { - final username = usernameController.text; - final password = passwordController.text; - final success = await api.login(username: username, password: password); - // debugPrint('Login success: $success'); - if (success != null) { - var authenticatedUser = AuthenticatedUser( - server: AudiobookShelfServer( - serverUrl: Uri.parse(serverUriController.text), - ), - id: success.user.id, - password: password, - username: username, - authToken: api.token!, - ); - // add the user to the list of users - ref.read(authenticatedUserProvider.notifier).addUser(authenticatedUser); - - // set the active user - ref.read(apiSettingsProvider.notifier).updateState( - apiSettings.copyWith( - activeUser: authenticatedUser, - ), - ); - } else { - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar( - content: Text('Login failed'), - ), - ); - // give focus back to the username field - } - } - - return Center( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Crab( - tag: heroServerTag, - child: AddNewServer( - controller: serverUriController, - onPressed: () {}, - readOnly: true, - ), - ), - const SizedBox(height: 30), - const Text('Login to server'), - const SizedBox(height: 30), - UserLogin( - usernameController: usernameController, - passwordController: passwordController, - onPressed: loginAndSave, - ), - ], - ), - ); - } -} diff --git a/lib/router/router.dart b/lib/router/router.dart index ecd47f3..3fba78c 100644 --- a/lib/router/router.dart +++ b/lib/router/router.dart @@ -2,7 +2,7 @@ import 'package:go_router/go_router.dart'; import 'package:whispering_pages/pages/home_page.dart'; import 'package:whispering_pages/pages/library_item_page.dart'; import 'package:whispering_pages/pages/library_page.dart'; -import 'package:whispering_pages/pages/onboarding/onboarding.dart'; +import 'package:whispering_pages/pages/onboarding/onboarding_single_page.dart'; part 'constants.dart'; @@ -17,7 +17,7 @@ class MyAppRouter { GoRoute( path: '/login', name: Routes.onboarding, - builder: (context, state) => const OnboardingPage(), + builder: (context, state) => const OnboardingSinglePage(), ), GoRoute( path: '/', @@ -44,7 +44,7 @@ class MyAppRouter { ], redirect: (context, state) { if (needOnboarding) { - return context.namedLocation(Routes.onboarding); + return config.namedLocation(Routes.onboarding); } return null; }, diff --git a/lib/widgets/user_login.dart b/lib/widgets/user_login.dart index 1990629..9f9fc29 100644 --- a/lib/widgets/user_login.dart +++ b/lib/widgets/user_login.dart @@ -42,75 +42,78 @@ class UserLogin extends HookConsumerWidget { return Center( child: InactiveFocusScopeObserver( child: AutofillGroup( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - TextFormField( - controller: usernameController, - autofocus: true, - autofillHints: const [AutofillHints.username], - textInputAction: TextInputAction.next, - decoration: InputDecoration( - labelText: 'Username', - labelStyle: TextStyle( - color: Theme.of(context) - .colorScheme - .onBackground - .withOpacity(0.8), - ), - border: const OutlineInputBorder(), - ), - ), - const SizedBox(height: 10), - TextFormField( - controller: passwordController, - autofillHints: const [AutofillHints.password], - textInputAction: TextInputAction.done, - obscureText: !isPasswordVisible.value, - onFieldSubmitted: onPressed != null - ? (_) { - onPressed!(); - } - : null, - decoration: InputDecoration( - labelText: 'Password', - labelStyle: TextStyle( - color: Theme.of(context) - .colorScheme - .onBackground - .withOpacity(0.8), - ), - border: const OutlineInputBorder(), - suffixIcon: ColorFiltered( - colorFilter: ColorFilter.mode( - Theme.of(context).colorScheme.primary.withOpacity(0.8), - BlendMode.srcIn, + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + TextFormField( + controller: usernameController, + autofocus: true, + autofillHints: const [AutofillHints.username], + textInputAction: TextInputAction.next, + decoration: InputDecoration( + labelText: 'Username', + labelStyle: TextStyle( + color: Theme.of(context) + .colorScheme + .onBackground + .withOpacity(0.8), ), - child: InkWell( - borderRadius: BorderRadius.circular(50), - onTap: () { - isPasswordVisible.value = !isPasswordVisible.value; - }, - child: Container( - margin: const EdgeInsets.only(left: 8, right: 8), - child: Lottie.asset( - 'assets/animations/Animation - 1714930099660.json', - controller: isPasswordVisibleAnimationController, + border: const OutlineInputBorder(), + ), + ), + const SizedBox(height: 10), + TextFormField( + controller: passwordController, + autofillHints: const [AutofillHints.password], + textInputAction: TextInputAction.done, + obscureText: !isPasswordVisible.value, + onFieldSubmitted: onPressed != null + ? (_) { + onPressed!(); + } + : null, + decoration: InputDecoration( + labelText: 'Password', + labelStyle: TextStyle( + color: Theme.of(context) + .colorScheme + .onBackground + .withOpacity(0.8), + ), + border: const OutlineInputBorder(), + suffixIcon: ColorFiltered( + colorFilter: ColorFilter.mode( + Theme.of(context).colorScheme.primary.withOpacity(0.8), + BlendMode.srcIn, + ), + child: InkWell( + borderRadius: BorderRadius.circular(50), + onTap: () { + isPasswordVisible.value = !isPasswordVisible.value; + }, + child: Container( + margin: const EdgeInsets.only(left: 8, right: 8), + child: Lottie.asset( + 'assets/animations/Animation - 1714930099660.json', + controller: isPasswordVisibleAnimationController, + ), ), ), ), - ), - suffixIconConstraints: const BoxConstraints( - maxHeight: 45, + suffixIconConstraints: const BoxConstraints( + maxHeight: 45, + ), ), ), - ), - const SizedBox(height: 30), - ElevatedButton( - onPressed: onPressed, - child: const Text('Login'), - ), - ], + const SizedBox(height: 30), + ElevatedButton( + onPressed: onPressed, + child: const Text('Login'), + ), + ], + ), ), ), ), diff --git a/linux/flutter/generated_plugin_registrant.cc b/linux/flutter/generated_plugin_registrant.cc index b898c8c..bfc0d08 100644 --- a/linux/flutter/generated_plugin_registrant.cc +++ b/linux/flutter/generated_plugin_registrant.cc @@ -7,9 +7,13 @@ #include "generated_plugin_registrant.h" #include +#include void fl_register_plugins(FlPluginRegistry* registry) { g_autoptr(FlPluginRegistrar) isar_flutter_libs_registrar = fl_plugin_registry_get_registrar_for_plugin(registry, "IsarFlutterLibsPlugin"); isar_flutter_libs_plugin_register_with_registrar(isar_flutter_libs_registrar); + g_autoptr(FlPluginRegistrar) url_launcher_linux_registrar = + fl_plugin_registry_get_registrar_for_plugin(registry, "UrlLauncherPlugin"); + url_launcher_plugin_register_with_registrar(url_launcher_linux_registrar); } diff --git a/linux/flutter/generated_plugins.cmake b/linux/flutter/generated_plugins.cmake index cb083af..6237f02 100644 --- a/linux/flutter/generated_plugins.cmake +++ b/linux/flutter/generated_plugins.cmake @@ -4,6 +4,7 @@ list(APPEND FLUTTER_PLUGIN_LIST isar_flutter_libs + url_launcher_linux ) list(APPEND FLUTTER_FFI_PLUGIN_LIST diff --git a/pubspec.lock b/pubspec.lock index 0bd3643..71cc39a 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -334,6 +334,14 @@ packages: description: flutter source: sdk version: "0.0.0" + flutter_animate: + dependency: "direct main" + description: + name: flutter_animate + sha256: "7c8a6594a9252dad30cc2ef16e33270b6248c4dedc3b3d06c86c4f3f4dc05ae5" + url: "https://pub.dev" + source: hosted + version: "4.5.0" flutter_cache_manager: dependency: "direct main" description: @@ -354,10 +362,10 @@ packages: dependency: "direct dev" description: name: flutter_lints - sha256: "9e8c3858111da373efc5aa341de011d9bd23e2c5c5e0c62bccf32438e192d7b1" + sha256: "3f41d009ba7172d5ff9be5f6e6e6abb4300e263aab8866d2a0842ed2a70f8f0c" url: "https://pub.dev" source: hosted - version: "3.0.2" + version: "4.0.0" flutter_riverpod: dependency: transitive description: @@ -374,6 +382,14 @@ packages: url: "https://pub.dev" source: hosted version: "3.0.1" + flutter_shaders: + dependency: transitive + description: + name: flutter_shaders + sha256: "02750b545c01ff4d8e9bbe8f27a7731aa3778402506c67daa1de7f5fc3f4befe" + url: "https://pub.dev" + source: hosted + version: "0.1.2" flutter_test: dependency: "direct dev" description: flutter @@ -420,10 +436,10 @@ packages: dependency: "direct main" description: name: go_router - sha256: "9e0f7d1a3e7dc5010903e330fbc5497872c4c3cf6626381d69083cc1d5113c1e" + sha256: "81f94e16d7063b60f0da3a79f872e140d6518f306749303bf981abd7d6b46734" url: "https://pub.dev" source: hosted - version: "14.0.2" + version: "14.1.0" graphs: dependency: transitive description: @@ -556,10 +572,10 @@ packages: dependency: transitive description: name: lints - sha256: cbf8d4b858bb0134ef3ef87841abdf8d63bfc255c266b7bf6b39daa1085c4290 + sha256: "976c774dd944a42e83e2467f4cc670daef7eed6295b10b36ae8c85bcbf828235" url: "https://pub.dev" source: hosted - version: "3.0.0" + version: "4.0.0" logging: dependency: transitive description: @@ -956,6 +972,70 @@ packages: url: "https://pub.dev" source: hosted version: "1.3.2" + url_launcher: + dependency: "direct main" + description: + name: url_launcher + sha256: "6ce1e04375be4eed30548f10a315826fd933c1e493206eab82eed01f438c8d2e" + url: "https://pub.dev" + source: hosted + version: "6.2.6" + url_launcher_android: + dependency: transitive + description: + name: url_launcher_android + sha256: "360a6ed2027f18b73c8d98e159dda67a61b7f2e0f6ec26e86c3ada33b0621775" + url: "https://pub.dev" + source: hosted + version: "6.3.1" + url_launcher_ios: + dependency: transitive + description: + name: url_launcher_ios + sha256: "9149d493b075ed740901f3ee844a38a00b33116c7c5c10d7fb27df8987fb51d5" + url: "https://pub.dev" + source: hosted + version: "6.2.5" + url_launcher_linux: + dependency: transitive + description: + name: url_launcher_linux + sha256: ab360eb661f8879369acac07b6bb3ff09d9471155357da8443fd5d3cf7363811 + url: "https://pub.dev" + source: hosted + version: "3.1.1" + url_launcher_macos: + dependency: transitive + description: + name: url_launcher_macos + sha256: b7244901ea3cf489c5335bdacda07264a6e960b1c1b1a9f91e4bc371d9e68234 + url: "https://pub.dev" + source: hosted + version: "3.1.0" + url_launcher_platform_interface: + dependency: transitive + description: + name: url_launcher_platform_interface + sha256: "552f8a1e663569be95a8190206a38187b531910283c3e982193e4f2733f01029" + url: "https://pub.dev" + source: hosted + version: "2.3.2" + url_launcher_web: + dependency: transitive + description: + name: url_launcher_web + sha256: "8d9e750d8c9338601e709cd0885f95825086bd8b642547f26bda435aade95d8a" + url: "https://pub.dev" + source: hosted + version: "2.3.1" + url_launcher_windows: + dependency: transitive + description: + name: url_launcher_windows + sha256: ecf9725510600aa2bb6d7ddabe16357691b6d2805f66216a97d1b881e21beff7 + url: "https://pub.dev" + source: hosted + version: "3.1.1" uuid: dependency: transitive description: @@ -1030,4 +1110,4 @@ packages: version: "3.1.2" sdks: dart: ">=3.3.4 <4.0.0" - flutter: ">=3.16.0" + flutter: ">=3.19.0" diff --git a/pubspec.yaml b/pubspec.yaml index 32a86b8..525d84d 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -40,6 +40,7 @@ dependencies: easy_stepper: ^0.8.4 flutter: sdk: flutter + flutter_animate: ^4.5.0 flutter_cache_manager: ^3.3.2 flutter_hooks: ^0.20.5 flutter_settings_ui: ^3.0.1 @@ -58,10 +59,11 @@ dependencies: shelfsdk: path: ../../_dart/shelfsdk shimmer: ^3.0.0 + url_launcher: ^6.2.6 dev_dependencies: build_runner: ^2.4.9 custom_lint: ^0.6.4 - flutter_lints: ^3.0.0 + flutter_lints: ^4.0.0 flutter_test: sdk: flutter freezed: ^2.5.2 diff --git a/windows/flutter/generated_plugin_registrant.cc b/windows/flutter/generated_plugin_registrant.cc index afc39a1..65cb334 100644 --- a/windows/flutter/generated_plugin_registrant.cc +++ b/windows/flutter/generated_plugin_registrant.cc @@ -7,8 +7,11 @@ #include "generated_plugin_registrant.h" #include +#include void RegisterPlugins(flutter::PluginRegistry* registry) { IsarFlutterLibsPluginRegisterWithRegistrar( registry->GetRegistrarForPlugin("IsarFlutterLibsPlugin")); + UrlLauncherWindowsRegisterWithRegistrar( + registry->GetRegistrarForPlugin("UrlLauncherWindows")); } diff --git a/windows/flutter/generated_plugins.cmake b/windows/flutter/generated_plugins.cmake index 2a57005..439b324 100644 --- a/windows/flutter/generated_plugins.cmake +++ b/windows/flutter/generated_plugins.cmake @@ -4,6 +4,7 @@ list(APPEND FLUTTER_PLUGIN_LIST isar_flutter_libs + url_launcher_windows ) list(APPEND FLUTTER_FFI_PLUGIN_LIST