Improved onboarding
This commit is contained in:
4
lib/constants/urls.dart
Normal file
4
lib/constants/urls.dart
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
class Urls {
|
||||||
|
static const linkdingInstallationInstructions =
|
||||||
|
"https://github.com/sissbruecker/linkding?tab=readme-ov-file#installation";
|
||||||
|
}
|
||||||
@@ -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,
|
||||||
|
delegate: _Header(),
|
||||||
|
),
|
||||||
|
SliverList.list(
|
||||||
children: [
|
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(
|
Text(
|
||||||
t.onboarding.serverRequiredDescription,
|
t.onboarding.serverRequiredDescription,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
@@ -41,87 +35,83 @@ class OnboardingServerRequired extends ConsumerWidget {
|
|||||||
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
const SizedBox(height: 16),
|
||||||
),
|
Card(
|
||||||
Container(height: 24),
|
child: InkWell(
|
||||||
Card(
|
onTap: () => openUrl(Urls.linkdingInstallationInstructions),
|
||||||
child: InkWell(
|
borderRadius: BorderRadius.circular(10),
|
||||||
onTap: () => {},
|
child: Padding(
|
||||||
borderRadius: BorderRadius.circular(10),
|
padding: const EdgeInsets.all(24),
|
||||||
child: Padding(
|
child: Row(
|
||||||
padding: const EdgeInsets.all(24),
|
children: [
|
||||||
child: Row(
|
SvgPicture.asset(
|
||||||
children: [
|
'assets/resources/github.svg',
|
||||||
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,
|
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: [
|
children: [
|
||||||
ListTile(
|
FilledButton(
|
||||||
contentPadding: const EdgeInsets.all(8),
|
onPressed: () => ref.read(onboardingProvider.notifier).previousPage(),
|
||||||
leading: Checkbox(
|
child: Row(
|
||||||
shape: RoundedRectangleBorder(
|
children: [
|
||||||
borderRadius: BorderRadius.circular(5),
|
const Icon(Icons.arrow_back_rounded),
|
||||||
),
|
const SizedBox(width: 8),
|
||||||
value: ref.watch(onboardingProvider).confirmServerRunning,
|
Text(t.onboarding.previous),
|
||||||
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),
|
FilledButton(
|
||||||
Row(
|
onPressed: ref.watch(onboardingProvider).confirmServerRunning == true
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
? () => ref.read(onboardingProvider.notifier).nextPage()
|
||||||
children: [
|
: null,
|
||||||
FilledButton(
|
child: Row(
|
||||||
onPressed: () => ref.read(onboardingProvider.notifier).previousPage(),
|
children: [
|
||||||
child: Row(
|
Text(t.onboarding.next),
|
||||||
children: [
|
const SizedBox(width: 8),
|
||||||
const Icon(Icons.arrow_back_rounded),
|
const Icon(Icons.arrow_forward_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),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -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
31
lib/utils/open_url.dart
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
40
pubspec.lock
40
pubspec.lock
@@ -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:
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user