Improved onboarding

This commit is contained in:
Juan Gilsanz Polo
2024-02-22 11:08:11 +01:00
parent a7b038c6ab
commit fb35943d12
6 changed files with 217 additions and 85 deletions

4
lib/constants/urls.dart Normal file
View File

@@ -0,0 +1,4 @@
class Urls {
static const linkdingInstallationInstructions =
"https://github.com/sissbruecker/linkding?tab=readme-ov-file#installation";
}

View File

@@ -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/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/i18n/strings.g.dart';
import 'package:my_linkding/utils/open_url.dart';
class OnboardingServerRequired extends ConsumerWidget { class OnboardingServerRequired extends ConsumerWidget {
const OnboardingServerRequired({Key? key}) : super(key: key); const OnboardingServerRequired({Key? key}) : super(key: key);
@@ -18,22 +20,14 @@ class OnboardingServerRequired extends ConsumerWidget {
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
Expanded( Expanded(
child: ListView( child: CustomScrollView(
children: [ slivers: [
Column( const SliverPersistentHeader(
crossAxisAlignment: CrossAxisAlignment.start, pinned: true,
children: [ delegate: _Header(),
Icon(
Icons.dns_rounded,
size: 50,
color: Theme.of(context).colorScheme.primary,
), ),
const SizedBox(height: 24), SliverList.list(
Text( children: [
t.onboarding.serverRequired,
style: const TextStyle(fontSize: 28),
),
const SizedBox(height: 8),
Text( Text(
t.onboarding.serverRequiredDescription, t.onboarding.serverRequiredDescription,
style: TextStyle( style: TextStyle(
@@ -41,12 +35,10 @@ class OnboardingServerRequired extends ConsumerWidget {
color: Theme.of(context).colorScheme.onSurfaceVariant, color: Theme.of(context).colorScheme.onSurfaceVariant,
), ),
), ),
], const SizedBox(height: 16),
),
Container(height: 24),
Card( Card(
child: InkWell( child: InkWell(
onTap: () => {}, onTap: () => openUrl(Urls.linkdingInstallationInstructions),
borderRadius: BorderRadius.circular(10), borderRadius: BorderRadius.circular(10),
child: Padding( child: Padding(
padding: const EdgeInsets.all(24), padding: const EdgeInsets.all(24),
@@ -74,14 +66,9 @@ class OnboardingServerRequired extends ConsumerWidget {
), ),
), ),
), ),
const SizedBox(height: 8), const SizedBox(height: 16),
],
),
),
Column(
children: [
ListTile( ListTile(
contentPadding: const EdgeInsets.all(8), contentPadding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2),
leading: Checkbox( leading: Checkbox(
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(5), borderRadius: BorderRadius.circular(5),
@@ -95,7 +82,12 @@ class OnboardingServerRequired extends ConsumerWidget {
), ),
onTap: () => ref.read(onboardingProvider.notifier).confirmServerRunning(), onTap: () => ref.read(onboardingProvider.notifier).confirmServerRunning(),
), ),
const SizedBox(height: 8), ],
),
],
),
),
const SizedBox(height: 16),
Row( Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
@@ -125,8 +117,72 @@ 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;
}
} }

View File

@@ -34,7 +34,7 @@ class OnboardingWelcome extends ConsumerWidget {
t.onboarding.title, t.onboarding.title,
style: const TextStyle(fontSize: 28), style: const TextStyle(fontSize: 28),
), ),
const SizedBox(height: 8), const SizedBox(height: 16),
Text( Text(
t.onboarding.subtitle, t.onboarding.subtitle,
style: TextStyle( style: TextStyle(

31
lib/utils/open_url.dart Normal file
View File

@@ -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);
}
}
}

View File

@@ -294,6 +294,46 @@ packages:
description: flutter description: flutter
source: sdk source: sdk
version: "0.0.0" 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: flutter_lints:
dependency: "direct dev" dependency: "direct dev"
description: description:

View File

@@ -46,6 +46,7 @@ dependencies:
slang: ^3.29.0 slang: ^3.29.0
slang_flutter: ^3.29.0 slang_flutter: ^3.29.0
flutter_svg: ^2.0.9 flutter_svg: ^2.0.9
flutter_custom_tabs: ^2.0.0+1
dev_dependencies: dev_dependencies:
build_runner: ^2.4.8 build_runner: ^2.4.8