diff --git a/lib/constants/urls.dart b/lib/constants/urls.dart new file mode 100644 index 0000000..a8e7da7 --- /dev/null +++ b/lib/constants/urls.dart @@ -0,0 +1,4 @@ +class Urls { + static const linkdingInstallationInstructions = + "https://github.com/sissbruecker/linkding?tab=readme-ov-file#installation"; +} diff --git a/lib/screens/onboarding/ui/server_required.dart b/lib/screens/onboarding/ui/server_required.dart index 63999a3..f51d863 100644 --- a/lib/screens/onboarding/ui/server_required.dart +++ b/lib/screens/onboarding/ui/server_required.dart @@ -4,7 +4,9 @@ import 'package:flutter_svg/flutter_svg.dart'; import 'package:my_linkding/screens/onboarding/provider/onboarding.provider.dart'; +import 'package:my_linkding/constants/urls.dart'; import 'package:my_linkding/i18n/strings.g.dart'; +import 'package:my_linkding/utils/open_url.dart'; class OnboardingServerRequired extends ConsumerWidget { const OnboardingServerRequired({Key? key}) : super(key: key); @@ -18,22 +20,14 @@ class OnboardingServerRequired extends ConsumerWidget { mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Expanded( - child: ListView( - children: [ - Column( - crossAxisAlignment: CrossAxisAlignment.start, + child: CustomScrollView( + slivers: [ + const SliverPersistentHeader( + pinned: true, + delegate: _Header(), + ), + SliverList.list( 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( @@ -41,87 +35,83 @@ class OnboardingServerRequired extends ConsumerWidget { 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, + const SizedBox(height: 16), + Card( + child: InkWell( + onTap: () => openUrl(Urls.linkdingInstallationInstructions), + 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, - fontWeight: FontWeight.w500, + 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: 16), + ListTile( + contentPadding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2), + 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), ], ), ), - Column( + const SizedBox(height: 16), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, 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(), + FilledButton( + onPressed: () => ref.read(onboardingProvider.notifier).previousPage(), + child: Row( + children: [ + const Icon(Icons.arrow_back_rounded), + const SizedBox(width: 8), + Text(t.onboarding.previous), + ], ), - 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), - ], - ), - ), - ], + 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), + ], + ), ), ], ), @@ -130,3 +120,69 @@ class OnboardingServerRequired extends ConsumerWidget { ); } } + +const _minExent = 50.0; +const _maxExent = 130.0; + +const _iconMaxSize = 50; +const _iconMinSize = 30; + +const _textMaxSize = 28; +const _textMinSize = 22; + +const _textMaxTopPosition = 70; +const _textMinTopPosition = 0; + +const _textMaxLeftPosition = 0; +const _textMinLeftPosition = 44; + +class _Header extends SliverPersistentHeaderDelegate { + const _Header(); + + @override + Widget build(BuildContext context, double shrinkOffset, bool overlapsContent) { + final iconPercentage = shrinkOffset.clamp(0, _maxExent - _minExent) / (_maxExent - _minExent); + final textPercentage = shrinkOffset.clamp(0, _maxExent - _minExent) / (_maxExent - _minExent); + + final iconSize = _iconMinSize + (_iconMaxSize - _iconMinSize) * (1 - iconPercentage); + final textSize = _textMinSize + (_textMaxSize - _textMinSize) * (1 - textPercentage); + final textTopPosition = _textMinTopPosition + (_textMaxTopPosition - _textMinTopPosition) * (1 - textPercentage); + final textLeftPosition = + _textMinLeftPosition + (_textMaxLeftPosition - _textMinLeftPosition) * (1 - textPercentage); + + return Container( + color: Theme.of(context).colorScheme.surface, + child: Stack( + children: [ + Positioned( + top: 0, + child: Icon( + Icons.dns_rounded, + size: iconSize, + color: Theme.of(context).colorScheme.primary, + ), + ), + Positioned( + top: textTopPosition, + left: textLeftPosition, + child: Text( + t.onboarding.serverRequired, + style: TextStyle(fontSize: textSize), + ), + ), + ], + ), + ); + } + + @override + double get maxExtent => _maxExent; + + @override + double get minExtent => _minExent; + + @override + bool shouldRebuild(covariant SliverPersistentHeaderDelegate oldDelegate) { + return false; + } +} diff --git a/lib/screens/onboarding/ui/welcome.dart b/lib/screens/onboarding/ui/welcome.dart index 1a15dcc..74370fa 100644 --- a/lib/screens/onboarding/ui/welcome.dart +++ b/lib/screens/onboarding/ui/welcome.dart @@ -34,7 +34,7 @@ class OnboardingWelcome extends ConsumerWidget { t.onboarding.title, style: const TextStyle(fontSize: 28), ), - const SizedBox(height: 8), + const SizedBox(height: 16), Text( t.onboarding.subtitle, style: TextStyle( diff --git a/lib/utils/open_url.dart b/lib/utils/open_url.dart new file mode 100644 index 0000000..9a2e3f9 --- /dev/null +++ b/lib/utils/open_url.dart @@ -0,0 +1,31 @@ +import 'dart:io'; + +import 'package:flutter_custom_tabs/flutter_custom_tabs.dart' as flutter_custom_tabs; +import 'package:url_launcher/url_launcher.dart' as url_launcher; + +void openUrl(String url) async { + if (Platform.isAndroid || Platform.isIOS) { + try { + await flutter_custom_tabs.launchUrl( + Uri.parse(url), + customTabsOptions: const flutter_custom_tabs.CustomTabsOptions( + shareState: flutter_custom_tabs.CustomTabsShareState.browserDefault, + urlBarHidingEnabled: true, + showTitle: true, + ), + safariVCOptions: const flutter_custom_tabs.SafariViewControllerOptions( + barCollapsingEnabled: true, + dismissButtonStyle: flutter_custom_tabs.SafariViewControllerDismissButtonStyle.close, + ), + ); + } catch (e, stackTrace) { + // Sentry.captureException(e, stackTrace: stackTrace); + } + } else { + try { + url_launcher.launchUrl(Uri.parse(url)); + } catch (e, stackTrace) { + // Sentry.captureException(e, stackTrace: stackTrace); + } + } +} diff --git a/pubspec.lock b/pubspec.lock index d4ad9ca..f58fd47 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -294,6 +294,46 @@ packages: description: flutter source: sdk version: "0.0.0" + flutter_custom_tabs: + dependency: "direct main" + description: + name: flutter_custom_tabs + sha256: "961fe962ae55e9e41097c34e68c6d7d2de10e9a71034f83834919e31a891e728" + url: "https://pub.dev" + source: hosted + version: "2.0.0+1" + flutter_custom_tabs_android: + dependency: transitive + description: + name: flutter_custom_tabs_android + sha256: "5701a3e38117dfc59e5fa9e84a29eea9203e0062de7be56d1f775845c34456d9" + url: "https://pub.dev" + source: hosted + version: "2.0.0+1" + flutter_custom_tabs_ios: + dependency: transitive + description: + name: flutter_custom_tabs_ios + sha256: b29f687f7f366159d37347f888369fcd1259adac8ca6c7f07d356a80b091e08a + url: "https://pub.dev" + source: hosted + version: "2.0.0" + flutter_custom_tabs_platform_interface: + dependency: transitive + description: + name: flutter_custom_tabs_platform_interface + sha256: "13531a743486695d87b462b151801e11476b1b5a15274caec092420cc1943064" + url: "https://pub.dev" + source: hosted + version: "2.0.0" + flutter_custom_tabs_web: + dependency: transitive + description: + name: flutter_custom_tabs_web + sha256: "3114b511d3942ed42c502cc57ad47eaef9622ac06dd036e2b21b19d8876f6498" + url: "https://pub.dev" + source: hosted + version: "2.0.0" flutter_lints: dependency: "direct dev" description: diff --git a/pubspec.yaml b/pubspec.yaml index f45e93d..d56772d 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -46,6 +46,7 @@ dependencies: slang: ^3.29.0 slang_flutter: ^3.29.0 flutter_svg: ^2.0.9 + flutter_custom_tabs: ^2.0.0+1 dev_dependencies: build_runner: ^2.4.8