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/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;
}
}

View File

@@ -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(

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