Improved connection form

This commit is contained in:
Juan Gilsanz Polo
2024-02-22 11:54:44 +01:00
parent fb35943d12
commit be7a2bcf4b
13 changed files with 385 additions and 231 deletions

View File

@@ -4,9 +4,9 @@
/// To regenerate, run: `dart run slang`
///
/// Locales: 2
/// Strings: 30 (15 per locale)
/// Strings: 48 (24 per locale)
///
/// Built on 2024-02-22 at 01:09 UTC
/// Built on 2024-02-22 at 10:40 UTC
// coverage:ignore-file
// ignore_for_file: type=lint
@@ -149,7 +149,6 @@ class Translations implements BaseTranslations<AppLocale, Translations> {
// Translations
late final _StringsOnboardingEn onboarding = _StringsOnboardingEn._(_root);
late final _StringsConnectEn connect = _StringsConnectEn._(_root);
late final _StringsLinksEn links = _StringsLinksEn._(_root);
late final _StringsSettingsEn settings = _StringsSettingsEn._(_root);
}
@@ -170,15 +169,15 @@ class _StringsOnboardingEn {
String get serverRequiredDescription => 'My Linkding it\'s not an standalone app, it requires the Linkding server to work.\nIn order to use this application, you must deploy Linkding on your home server, VPS or any other computer.';
String get installationInstructions => 'Check the installation instructions on the official GitHub repository.';
String get serverRunningConfirmation => 'I confirm that I have an instance of the Linkding server already running.';
}
// Path: connect
class _StringsConnectEn {
_StringsConnectEn._(this._root);
final Translations _root; // ignore: unused_field
// Translations
String get createConnection => 'Create a connection';
String get createConnectionSubtitle => 'Enter all the required details to create a connection to your server.';
String get ipAddressOrDomain => 'IP address or domain';
String get port => 'Port';
String get token => 'Token';
String get required => 'Required';
String get serverDetails => 'Server details';
String get authentication => 'Authentication';
String get testConnectionUrl => 'Test connection url';
String get connect => 'Connect';
String get connecting => 'Connecting...';
String get cannotConnectToServer => 'Cannot connect to the server.';
@@ -230,7 +229,6 @@ class _StringsEs implements Translations {
// Translations
@override late final _StringsOnboardingEs onboarding = _StringsOnboardingEs._(_root);
@override late final _StringsConnectEs connect = _StringsConnectEs._(_root);
@override late final _StringsLinksEs links = _StringsLinksEs._(_root);
@override late final _StringsSettingsEs settings = _StringsSettingsEs._(_root);
}
@@ -251,15 +249,15 @@ class _StringsOnboardingEs implements _StringsOnboardingEn {
@override String get serverRequiredDescription => 'My Linkding no es una aplicación independiente, requiere el servidor Linkding para funcionar.\nPara utilizar esta aplicación, debe instalar Linkding en su servidor doméstico, VPS o cualquier otro ordenador.';
@override String get installationInstructions => 'Mira las instrucciones de instalación en el repositorio oficial en GitHub.';
@override String get serverRunningConfirmation => 'Confirmo que tengo una instancia del servidor Linkding ya en funcionamiento.';
}
// Path: connect
class _StringsConnectEs implements _StringsConnectEn {
_StringsConnectEs._(this._root);
@override final _StringsEs _root; // ignore: unused_field
// Translations
@override String get createConnection => 'Crear una conexión';
@override String get createConnectionSubtitle => 'Introduce todos los detalles requeridos para crear una conexión con el servidor';
@override String get ipAddressOrDomain => 'Dirección IP o dominio';
@override String get port => 'Puerto';
@override String get token => 'Token';
@override String get required => 'Requerido';
@override String get serverDetails => 'Detalles del servidor';
@override String get authentication => 'Autenticación';
@override String get testConnectionUrl => 'Probar URL de conexión';
@override String get connect => 'Conectar';
@override String get connecting => 'Conectando...';
@override String get cannotConnectToServer => 'No se puede conectar con el servidor.';
@@ -301,9 +299,18 @@ extension on Translations {
case 'onboarding.serverRequiredDescription': return 'My Linkding it\'s not an standalone app, it requires the Linkding server to work.\nIn order to use this application, you must deploy Linkding on your home server, VPS or any other computer.';
case 'onboarding.installationInstructions': return 'Check the installation instructions on the official GitHub repository.';
case 'onboarding.serverRunningConfirmation': return 'I confirm that I have an instance of the Linkding server already running.';
case 'connect.connect': return 'Connect';
case 'connect.connecting': return 'Connecting...';
case 'connect.cannotConnectToServer': return 'Cannot connect to the server.';
case 'onboarding.createConnection': return 'Create a connection';
case 'onboarding.createConnectionSubtitle': return 'Enter all the required details to create a connection to your server.';
case 'onboarding.ipAddressOrDomain': return 'IP address or domain';
case 'onboarding.port': return 'Port';
case 'onboarding.token': return 'Token';
case 'onboarding.required': return 'Required';
case 'onboarding.serverDetails': return 'Server details';
case 'onboarding.authentication': return 'Authentication';
case 'onboarding.testConnectionUrl': return 'Test connection url';
case 'onboarding.connect': return 'Connect';
case 'onboarding.connecting': return 'Connecting...';
case 'onboarding.cannotConnectToServer': return 'Cannot connect to the server.';
case 'links.links': return 'Links';
case 'settings.settings': return 'Settings';
case 'settings.disconnectFromServer': return 'Disconnect from server';
@@ -324,9 +331,18 @@ extension on _StringsEs {
case 'onboarding.serverRequiredDescription': return 'My Linkding no es una aplicación independiente, requiere el servidor Linkding para funcionar.\nPara utilizar esta aplicación, debe instalar Linkding en su servidor doméstico, VPS o cualquier otro ordenador.';
case 'onboarding.installationInstructions': return 'Mira las instrucciones de instalación en el repositorio oficial en GitHub.';
case 'onboarding.serverRunningConfirmation': return 'Confirmo que tengo una instancia del servidor Linkding ya en funcionamiento.';
case 'connect.connect': return 'Conectar';
case 'connect.connecting': return 'Conectando...';
case 'connect.cannotConnectToServer': return 'No se puede conectar con el servidor.';
case 'onboarding.createConnection': return 'Crear una conexión';
case 'onboarding.createConnectionSubtitle': return 'Introduce todos los detalles requeridos para crear una conexión con el servidor';
case 'onboarding.ipAddressOrDomain': return 'Dirección IP o dominio';
case 'onboarding.port': return 'Puerto';
case 'onboarding.token': return 'Token';
case 'onboarding.required': return 'Requerido';
case 'onboarding.serverDetails': return 'Detalles del servidor';
case 'onboarding.authentication': return 'Autenticación';
case 'onboarding.testConnectionUrl': return 'Probar URL de conexión';
case 'onboarding.connect': return 'Conectar';
case 'onboarding.connecting': return 'Conectando...';
case 'onboarding.cannotConnectToServer': return 'No se puede conectar con el servidor.';
case 'links.links': return 'Enlaces';
case 'settings.settings': return 'Ajustes';
case 'settings.disconnectFromServer': return 'Desconectar del servidor';

View File

@@ -8,9 +8,16 @@
"serverRequired": "Servidor requerido",
"serverRequiredDescription": "My Linkding it's not an standalone app, it requires the Linkding server to work.\nIn order to use this application, you must deploy Linkding on your home server, VPS or any other computer.",
"installationInstructions": "Check the installation instructions on the official GitHub repository.",
"serverRunningConfirmation": "I confirm that I have an instance of the Linkding server already running."
},
"connect": {
"serverRunningConfirmation": "I confirm that I have an instance of the Linkding server already running.",
"createConnection": "Create a connection",
"createConnectionSubtitle": "Enter all the required details to create a connection to your server.",
"ipAddressOrDomain": "IP address or domain",
"port": "Port",
"token": "Token",
"required": "Required",
"serverDetails": "Server details",
"authentication": "Authentication",
"testConnectionUrl": "Test connection url",
"connect": "Connect",
"connecting": "Connecting...",
"cannotConnectToServer": "Cannot connect to the server."

View File

@@ -8,9 +8,16 @@
"serverRequired": "Servidor requerido",
"serverRequiredDescription": "My Linkding no es una aplicación independiente, requiere el servidor Linkding para funcionar.\nPara utilizar esta aplicación, debe instalar Linkding en su servidor doméstico, VPS o cualquier otro ordenador.",
"installationInstructions": "Mira las instrucciones de instalación en el repositorio oficial en GitHub.",
"serverRunningConfirmation": "Confirmo que tengo una instancia del servidor Linkding ya en funcionamiento."
},
"connect": {
"serverRunningConfirmation": "Confirmo que tengo una instancia del servidor Linkding ya en funcionamiento.",
"createConnection": "Crear una conexión",
"createConnectionSubtitle": "Introduce todos los detalles requeridos para crear una conexión con el servidor",
"ipAddressOrDomain": "Dirección IP o dominio",
"port": "Puerto",
"token": "Token",
"required": "Requerido",
"serverDetails": "Detalles del servidor",
"authentication": "Autenticación",
"testConnectionUrl": "Probar URL de conexión",
"connect": "Conectar",
"connecting": "Conectando...",
"cannotConnectToServer": "No se puede conectar con el servidor."

View File

@@ -1,124 +0,0 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:my_linkding/constants/enums.dart';
import 'package:my_linkding/screens/connect/provider/connect.provider.dart';
import 'package:segmented_button_slide/segmented_button_slide.dart';
class Connect extends ConsumerWidget {
const Connect({Key? key}) : super(key: key);
@override
Widget build(BuildContext context, WidgetRef ref) {
final provider = ref.watch(connectProvider);
final validValies = ref.read(connectProvider.notifier).validValues();
final connectionMethod = ConnectionMethod.values[provider.method];
return Scaffold(
body: SafeArea(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: ListView(
children: [
Container(
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 10),
margin: const EdgeInsets.only(top: 24, left: 24, right: 24),
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.primary.withOpacity(0.05),
borderRadius: BorderRadius.circular(30),
border: Border.all(color: Theme.of(context).colorScheme.primary),
),
child: Text(
"${connectionMethod.name}://${provider.ipDomainController.text}${provider.pathController.text != '' ? ':${provider.portController.text}' : ""}${provider.pathController.text}",
textAlign: TextAlign.center,
style: TextStyle(color: Theme.of(context).colorScheme.primary, fontWeight: FontWeight.w500),
),
),
const SizedBox(height: 30),
SegmentedButtonSlide(
entries: const [
SegmentedButtonSlideEntry(label: "HTTP"),
SegmentedButtonSlideEntry(label: "HTTPS"),
],
selectedEntry: provider.method,
onChange: ref.read(connectProvider.notifier).setConnectionMethod,
colors: SegmentedButtonSlideColors(
barColor: Theme.of(context).colorScheme.primary.withOpacity(0.2),
backgroundSelectedColor: Theme.of(context).colorScheme.primary,
foregroundSelectedColor: Theme.of(context).colorScheme.onPrimary,
foregroundUnselectedColor: Theme.of(context).colorScheme.onSurface,
hoverColor: Theme.of(context).colorScheme.onSurfaceVariant,
),
textOverflow: TextOverflow.ellipsis,
fontSize: 14,
height: 40,
margin: const EdgeInsets.symmetric(
horizontal: 24,
),
),
const SizedBox(height: 30),
TextFormField(
controller: provider.ipDomainController,
onChanged: ref.read(connectProvider.notifier).validateIpDomain,
decoration: InputDecoration(
prefixIcon: const Icon(Icons.link_rounded),
border: const OutlineInputBorder(
borderRadius: BorderRadius.all(
Radius.circular(10),
),
),
errorText: provider.ipDomainError,
labelText: "IP address or domain",
),
),
const SizedBox(height: 30),
TextFormField(
controller: provider.portController,
onChanged: ref.read(connectProvider.notifier).validatePort,
decoration: InputDecoration(
prefixIcon: const Icon(Icons.link_rounded),
border: const OutlineInputBorder(
borderRadius: BorderRadius.all(
Radius.circular(10),
),
),
errorText: provider.portError,
labelText: "Port",
),
),
const SizedBox(height: 30),
TextFormField(
controller: provider.tokenController,
onChanged: ref.read(connectProvider.notifier).validateToken,
decoration: InputDecoration(
prefixIcon: const Icon(Icons.key_rounded),
border: const OutlineInputBorder(
borderRadius: BorderRadius.all(
Radius.circular(10),
),
),
errorText: provider.tokenError,
labelText: "Token",
),
obscureText: true,
),
const SizedBox(height: 30),
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
FilledButton.icon(
onPressed: validValies ? () => ref.read(connectToServerProvider) : null,
icon: const Icon(Icons.login_rounded),
label: Text("Connect"),
),
],
),
],
),
),
),
);
}
}

View File

@@ -12,6 +12,7 @@ class ConnectModel {
final String? pathError;
final TextEditingController tokenController;
final String? tokenError;
final bool validValues;
ConnectModel({
required this.isConnecting,
@@ -24,6 +25,7 @@ class ConnectModel {
required this.pathError,
required this.tokenController,
required this.tokenError,
required this.validValues,
});
ConnectModel copyWidth({
@@ -37,6 +39,7 @@ class ConnectModel {
Wrapped<String?>? pathError,
TextEditingController? tokenController,
Wrapped<String?>? tokenError,
bool? validValues,
}) =>
ConnectModel(
isConnecting: isConnecting ?? this.isConnecting,
@@ -49,5 +52,6 @@ class ConnectModel {
pathError: pathError != null ? pathError.value : this.pathError,
tokenController: tokenController ?? this.tokenController,
tokenError: tokenError != null ? tokenError.value : this.tokenError,
validValues: validValues ?? this.validValues,
);
}

View File

@@ -2,7 +2,7 @@ import 'package:flutter/material.dart';
import 'package:uuid/uuid.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:my_linkding/screens/connect/model/connect.model.dart';
import 'package:my_linkding/screens/onboarding/model/connect.model.dart';
import 'package:my_linkding/providers/server_instances_provider.dart';
import 'package:my_linkding/i18n/strings.g.dart';
@@ -32,6 +32,7 @@ class Connect extends _$Connect {
pathError: null,
tokenController: TextEditingController(),
tokenError: null,
validValues: false,
);
}
@@ -45,6 +46,7 @@ class Connect extends _$Connect {
} else {
state = state.copyWidth(ipDomainError: const Wrapped.value("Error"));
}
state = state.copyWidth(validValues: validValues());
}
void validatePort(value) {
@@ -55,6 +57,7 @@ class Connect extends _$Connect {
state = state.copyWidth(portError: const Wrapped.value("Invalid port"));
}
}
state = state.copyWidth(validValues: validValues());
}
void validateToken(String value) {
@@ -63,6 +66,7 @@ class Connect extends _$Connect {
} else {
state = state.copyWidth(tokenError: const Wrapped.value("Error"));
}
state = state.copyWidth(validValues: validValues());
}
bool validValues() {
@@ -87,7 +91,7 @@ FutureOr<bool> connectToServer(ConnectToServerRef ref) async {
final apiClient = ApiClient(serverInstance: serverInstance);
final processModal = ProcessModal();
processModal.open(t.connect.connecting);
processModal.open(t.onboarding.connecting);
final result = await apiClient.checkConnectionInstance();
@@ -97,7 +101,7 @@ FutureOr<bool> connectToServer(ConnectToServerRef ref) async {
ref.read(serverInstancesProvider.notifier).saveNewInstance(serverInstance);
// ref.watch(routerProvider).replace(RoutesPaths.links);
} else {
showSnacbkar(label: t.connect.cannotConnectToServer, color: Colors.red);
showSnacbkar(label: t.onboarding.cannotConnectToServer, color: Colors.red);
}
return false;

View File

@@ -6,7 +6,7 @@ part of 'connect.provider.dart';
// RiverpodGenerator
// **************************************************************************
String _$connectToServerHash() => r'c40898fd5dd6dd7507140c7a69a6f336a56e30c0';
String _$connectToServerHash() => r'85925cb547fccb708e657d263e39a46867ac2bca';
/// See also [connectToServer].
@ProviderFor(connectToServer)
@@ -21,7 +21,7 @@ final connectToServerProvider = AutoDisposeFutureProvider<bool>.internal(
);
typedef ConnectToServerRef = AutoDisposeFutureProviderRef<bool>;
String _$connectHash() => r'bbdae015118381c538b7a3c15ca8345d30a28995';
String _$connectHash() => r'2c2e4fff717252259381056b4767b9a4511b5009';
/// See also [Connect].
@ProviderFor(Connect)

View File

@@ -0,0 +1,198 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:segmented_button_slide/segmented_button_slide.dart';
import 'package:my_linkding/screens/onboarding/ui/sliver_top_bar.dart';
import 'package:my_linkding/screens/onboarding/provider/connect.provider.dart';
import 'package:my_linkding/screens/onboarding/provider/onboarding.provider.dart';
import 'package:my_linkding/widgets/section_label.dart';
import 'package:my_linkding/utils/open_url.dart';
import 'package:my_linkding/constants/enums.dart';
import 'package:my_linkding/i18n/strings.g.dart';
class OnboardingConnect extends ConsumerWidget {
const OnboardingConnect({Key? key}) : super(key: key);
@override
Widget build(BuildContext context, WidgetRef ref) {
return Padding(
padding: const EdgeInsets.all(24),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: CustomScrollView(
slivers: [
SliverPersistentHeader(
pinned: true,
delegate: OnboardingSliverTopBar(
icon: Icons.login_rounded,
title: t.onboarding.createConnection,
),
),
SliverList.list(
children: const [
_ConnectForm(),
],
),
],
),
),
const SizedBox(height: 16),
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.icon(
onPressed: ref.watch(connectProvider).validValues ? () => ref.read(connectToServerProvider) : null,
icon: const Icon(Icons.login_rounded),
label: Text(t.onboarding.connect),
),
],
),
],
),
);
}
}
class _ConnectForm extends ConsumerWidget {
const _ConnectForm({Key? key}) : super(key: key);
@override
Widget build(BuildContext context, WidgetRef ref) {
final provider = ref.watch(connectProvider);
final connectionMethod = ConnectionMethod.values[provider.method];
final connectionString =
"${connectionMethod.name}://${provider.ipDomainController.text}${provider.pathController.text != '' ? ':${provider.portController.text}' : ""}${provider.pathController.text}";
return Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.start,
children: [
Flexible(
child: Text(
t.onboarding.createConnectionSubtitle,
style: TextStyle(
fontSize: 16,
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
),
),
],
),
Container(
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 10),
margin: const EdgeInsets.only(top: 30, left: 24, right: 24),
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.primary.withOpacity(0.05),
borderRadius: BorderRadius.circular(30),
border: Border.all(color: Theme.of(context).colorScheme.primary),
),
child: Text(
connectionString,
textAlign: TextAlign.center,
style: TextStyle(color: Theme.of(context).colorScheme.primary, fontWeight: FontWeight.w500),
),
),
SectionLabel(
label: t.onboarding.serverDetails,
padding: const EdgeInsets.symmetric(horizontal: 0, vertical: 24),
),
SegmentedButtonSlide(
entries: const [
SegmentedButtonSlideEntry(label: "HTTP"),
SegmentedButtonSlideEntry(label: "HTTPS"),
],
selectedEntry: provider.method,
onChange: ref.read(connectProvider.notifier).setConnectionMethod,
colors: SegmentedButtonSlideColors(
barColor: Theme.of(context).colorScheme.primary.withOpacity(0.2),
backgroundSelectedColor: Theme.of(context).colorScheme.primary,
foregroundSelectedColor: Theme.of(context).colorScheme.onPrimary,
foregroundUnselectedColor: Theme.of(context).colorScheme.onSurface,
hoverColor: Theme.of(context).colorScheme.onSurfaceVariant,
),
textOverflow: TextOverflow.ellipsis,
fontSize: 14,
height: 40,
margin: const EdgeInsets.symmetric(
horizontal: 24,
),
),
const SizedBox(height: 30),
TextFormField(
controller: provider.ipDomainController,
onChanged: ref.read(connectProvider.notifier).validateIpDomain,
decoration: InputDecoration(
prefixIcon: const Icon(Icons.link_rounded),
border: const OutlineInputBorder(
borderRadius: BorderRadius.all(
Radius.circular(10),
),
),
errorText: provider.ipDomainError,
labelText: t.onboarding.ipAddressOrDomain,
helperText: t.onboarding.required,
),
),
const SizedBox(height: 24),
TextFormField(
controller: provider.portController,
onChanged: ref.read(connectProvider.notifier).validatePort,
decoration: InputDecoration(
prefixIcon: const Icon(Icons.tag_rounded),
border: const OutlineInputBorder(
borderRadius: BorderRadius.all(
Radius.circular(10),
),
),
errorText: provider.portError,
labelText: t.onboarding.port,
),
),
SectionLabel(
label: t.onboarding.authentication,
padding: const EdgeInsets.symmetric(horizontal: 0, vertical: 24),
),
TextFormField(
controller: provider.tokenController,
onChanged: ref.read(connectProvider.notifier).validateToken,
decoration: InputDecoration(
prefixIcon: const Icon(Icons.key_rounded),
border: const OutlineInputBorder(
borderRadius: BorderRadius.all(
Radius.circular(10),
),
),
errorText: provider.tokenError,
labelText: t.onboarding.token,
helperText: t.onboarding.required,
),
obscureText: true,
),
const SizedBox(height: 24),
ElevatedButton.icon(
onPressed: ref.watch(connectProvider).validValues == true ? () => openUrl(connectionString) : null,
icon: const Icon(Icons.open_in_browser_rounded),
label: Text(t.onboarding.testConnectionUrl),
),
const SizedBox(height: 16),
],
);
}
}

View File

@@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:my_linkding/screens/onboarding/provider/onboarding.provider.dart';
import 'package:my_linkding/screens/onboarding/ui/connect.dart';
import 'package:my_linkding/screens/onboarding/ui/server_required.dart';
import 'package:my_linkding/screens/onboarding/ui/welcome.dart';
import 'package:my_linkding/widgets/system_overlay_style.dart';
@@ -20,6 +21,7 @@ class Onboarding extends ConsumerWidget {
children: const [
OnboardingWelcome(),
OnboardingServerRequired(),
OnboardingConnect(),
],
),
),

View File

@@ -3,6 +3,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:my_linkding/screens/onboarding/provider/onboarding.provider.dart';
import 'package:my_linkding/screens/onboarding/ui/sliver_top_bar.dart';
import 'package:my_linkding/constants/urls.dart';
import 'package:my_linkding/i18n/strings.g.dart';
@@ -22,9 +23,12 @@ class OnboardingServerRequired extends ConsumerWidget {
Expanded(
child: CustomScrollView(
slivers: [
const SliverPersistentHeader(
SliverPersistentHeader(
pinned: true,
delegate: _Header(),
delegate: OnboardingSliverTopBar(
icon: Icons.dns_rounded,
title: t.onboarding.serverRequired,
),
),
SliverList.list(
children: [
@@ -120,69 +124,3 @@ 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

@@ -0,0 +1,73 @@
import 'package:flutter/material.dart';
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 OnboardingSliverTopBar extends SliverPersistentHeaderDelegate {
final IconData icon;
final String title;
const OnboardingSliverTopBar({
required this.icon,
required this.title,
});
@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(
icon,
size: iconSize,
color: Theme.of(context).colorScheme.primary,
),
),
Positioned(
top: textTopPosition,
left: textLeftPosition,
child: Text(
title,
style: TextStyle(fontSize: textSize),
),
),
],
),
);
}
@override
double get maxExtent => _maxExent;
@override
double get minExtent => _minExent;
@override
bool shouldRebuild(covariant SliverPersistentHeaderDelegate oldDelegate) {
return false;
}
}

View File

@@ -1,6 +1,7 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:my_linkding/i18n/strings.g.dart';
import 'package:my_linkding/screens/onboarding/provider/onboarding.provider.dart';
class OnboardingWelcome extends ConsumerWidget {

View File

@@ -0,0 +1,28 @@
import 'package:flutter/material.dart';
class SectionLabel extends StatelessWidget {
final String label;
final EdgeInsets? padding;
const SectionLabel({
Key? key,
required this.label,
this.padding,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return Row(
mainAxisAlignment: MainAxisAlignment.start,
children: [
Padding(
padding: padding ?? const EdgeInsets.all(16),
child: Text(
label,
style: TextStyle(fontWeight: FontWeight.w500, fontSize: 16, color: Theme.of(context).colorScheme.primary),
),
),
],
);
}
}