update onboarding; add missing safe areas
This commit is contained in:
@@ -41,7 +41,7 @@ final class TabRepositoryProvider
|
||||
}
|
||||
}
|
||||
|
||||
String _$tabRepositoryHash() => r'87d2a4a0bca93c2d925aa1c81ff80d443185950a';
|
||||
String _$tabRepositoryHash() => r'ce4ac2f2efbb859ba66e98c6e6d21f8205d9b4ed';
|
||||
|
||||
abstract class _$TabRepository extends $Notifier<void> {
|
||||
void build();
|
||||
|
||||
+62
-60
@@ -99,69 +99,71 @@ class BookmarkFolderEditScreen extends HookConsumerWidget {
|
||||
),
|
||||
],
|
||||
),
|
||||
body: Form(
|
||||
key: formKey,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12.0),
|
||||
child: ListView(
|
||||
children: [
|
||||
TextFormField(
|
||||
controller: nameTextController,
|
||||
decoration: const InputDecoration(
|
||||
label: Text('Name'),
|
||||
floatingLabelBehavior: FloatingLabelBehavior.always,
|
||||
),
|
||||
validator: validateRequired,
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
if (!isBookmarkRoot) ...[
|
||||
FolderTreePicker(
|
||||
selectedFolderGuid: currentParentGuid,
|
||||
excludeFolderGuids: folder != null
|
||||
? {folder!.guid}
|
||||
: const {},
|
||||
entryGuid: BookmarkRoot.root.id,
|
||||
),
|
||||
if (folder == null) ...[
|
||||
const SizedBox(height: 8),
|
||||
SwitchListTile(
|
||||
contentPadding: EdgeInsets.zero,
|
||||
title: const Text('Add to top'),
|
||||
value: addToTop.value,
|
||||
onChanged: (value) => addToTop.value = value,
|
||||
body: SafeArea(
|
||||
child: Form(
|
||||
key: formKey,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12.0),
|
||||
child: ListView(
|
||||
children: [
|
||||
TextFormField(
|
||||
controller: nameTextController,
|
||||
decoration: const InputDecoration(
|
||||
label: Text('Name'),
|
||||
floatingLabelBehavior: FloatingLabelBehavior.always,
|
||||
),
|
||||
],
|
||||
validator: validateRequired,
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
],
|
||||
if (folder != null)
|
||||
SizedBox(
|
||||
width: double.infinity,
|
||||
child: OutlinedButton.icon(
|
||||
style: OutlinedButton.styleFrom(
|
||||
side: BorderSide(
|
||||
color: Theme.of(context).colorScheme.error,
|
||||
),
|
||||
foregroundColor: Theme.of(context).colorScheme.error,
|
||||
iconColor: Theme.of(context).colorScheme.error,
|
||||
),
|
||||
label: const Text('Delete'),
|
||||
icon: const Icon(Icons.delete),
|
||||
onPressed: () async {
|
||||
final result = await showDeleteFolderDialog(context);
|
||||
|
||||
if (result == true) {
|
||||
await ref
|
||||
.read(bookmarksRepositoryProvider.notifier)
|
||||
.delete(folder!.guid);
|
||||
|
||||
if (context.mounted) {
|
||||
context.pop();
|
||||
}
|
||||
}
|
||||
},
|
||||
if (!isBookmarkRoot) ...[
|
||||
FolderTreePicker(
|
||||
selectedFolderGuid: currentParentGuid,
|
||||
excludeFolderGuids: folder != null
|
||||
? {folder!.guid}
|
||||
: const {},
|
||||
entryGuid: BookmarkRoot.root.id,
|
||||
),
|
||||
),
|
||||
],
|
||||
if (folder == null) ...[
|
||||
const SizedBox(height: 8),
|
||||
SwitchListTile(
|
||||
contentPadding: EdgeInsets.zero,
|
||||
title: const Text('Add to top'),
|
||||
value: addToTop.value,
|
||||
onChanged: (value) => addToTop.value = value,
|
||||
),
|
||||
],
|
||||
const SizedBox(height: 16),
|
||||
],
|
||||
if (folder != null)
|
||||
SizedBox(
|
||||
width: double.infinity,
|
||||
child: OutlinedButton.icon(
|
||||
style: OutlinedButton.styleFrom(
|
||||
side: BorderSide(
|
||||
color: Theme.of(context).colorScheme.error,
|
||||
),
|
||||
foregroundColor: Theme.of(context).colorScheme.error,
|
||||
iconColor: Theme.of(context).colorScheme.error,
|
||||
),
|
||||
label: const Text('Delete'),
|
||||
icon: const Icon(Icons.delete),
|
||||
onPressed: () async {
|
||||
final result = await showDeleteFolderDialog(context);
|
||||
|
||||
if (result == true) {
|
||||
await ref
|
||||
.read(bookmarksRepositoryProvider.notifier)
|
||||
.delete(folder!.guid);
|
||||
|
||||
if (context.mounted) {
|
||||
context.pop();
|
||||
}
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
+135
-304
@@ -1,6 +1,3 @@
|
||||
import 'dart:math' as math;
|
||||
import 'dart:ui';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_material_design_icons/flutter_material_design_icons.dart';
|
||||
import 'package:flutter_svg/svg.dart';
|
||||
@@ -15,20 +12,11 @@ import 'package:weblibre/features/geckoview/features/tabs/utils/container_colors
|
||||
import 'package:weblibre/features/quotes/data/database/definitions.drift.dart';
|
||||
import 'package:weblibre/features/quotes/domain/providers.dart';
|
||||
import 'package:weblibre/features/user/domain/repositories/general_settings.dart';
|
||||
import 'package:weblibre/presentation/widgets/browser_page.dart';
|
||||
|
||||
class BrowserHome extends ConsumerWidget {
|
||||
const BrowserHome({super.key});
|
||||
|
||||
static const _brandPurple = Color(0xFF9C83F8);
|
||||
static const _brandYellow = Color(0xFFFBDC6B);
|
||||
static const _brandGrey = Color(0xFFA7A7A7);
|
||||
|
||||
static const _auraPurple = Color(0xFF2C2543);
|
||||
static const _auraGold = Color(0xFF3C3827);
|
||||
static const _auraShadow = Color(0xFF222224);
|
||||
static const _auraShadowHighlight = Color(0xFF2D2D31);
|
||||
static const _auraTint = Color(0xFF000000);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final theme = Theme.of(context);
|
||||
@@ -80,286 +68,152 @@ class BrowserHome extends ConsumerWidget {
|
||||
.resumeLatestContainerTab(containerId);
|
||||
}
|
||||
|
||||
return DecoratedBox(
|
||||
decoration: BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
begin: Alignment.topLeft,
|
||||
end: Alignment.bottomRight,
|
||||
colors: [
|
||||
Color.alphaBlend(
|
||||
_auraPurple.withValues(alpha: 0.38),
|
||||
colorScheme.surfaceContainerLowest,
|
||||
),
|
||||
Color.alphaBlend(
|
||||
_auraShadow.withValues(alpha: 0.72),
|
||||
colorScheme.surface,
|
||||
),
|
||||
Color.alphaBlend(
|
||||
_auraGold.withValues(alpha: 0.34),
|
||||
colorScheme.surfaceContainerHigh,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
child: Stack(
|
||||
fit: StackFit.expand,
|
||||
return BrowserPage(
|
||||
child: BrowserPageContent(
|
||||
bottomViewportInset: bottomViewportInset,
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const Positioned(
|
||||
top: -70,
|
||||
left: -120,
|
||||
child: _BackdropOrb(width: 400, height: 400, color: _auraPurple),
|
||||
),
|
||||
const Positioned(
|
||||
top: 220,
|
||||
right: -150,
|
||||
child: _BackdropOrb(width: 340, height: 340, color: _auraGold),
|
||||
),
|
||||
const Positioned(
|
||||
bottom: 18,
|
||||
left: -8,
|
||||
child: _BackdropOrb(
|
||||
width: 320,
|
||||
height: 320,
|
||||
color: _auraShadowHighlight,
|
||||
if (containerData != null)
|
||||
_ContainerHeader(container: containerData)
|
||||
else
|
||||
BrandHeader(colorScheme: colorScheme),
|
||||
const SizedBox(height: 24),
|
||||
if (!hasTabs) ...[
|
||||
Text(
|
||||
'WebLibre is ready',
|
||||
textAlign: TextAlign.center,
|
||||
style: theme.textTheme.headlineSmall?.copyWith(
|
||||
fontWeight: FontWeight.w700,
|
||||
),
|
||||
),
|
||||
),
|
||||
Positioned.fill(
|
||||
child: IgnorePointer(
|
||||
child: ClipRect(
|
||||
child: BackdropFilter(
|
||||
filter: ImageFilter.blur(sigmaX: 72, sigmaY: 72),
|
||||
child: ColoredBox(color: _auraTint.withValues(alpha: 0.12)),
|
||||
const SizedBox(height: 10),
|
||||
Text(
|
||||
'A browser that respects you. Open a tab and experience the web, libre.',
|
||||
textAlign: TextAlign.center,
|
||||
style: theme.textTheme.bodyLarge?.copyWith(
|
||||
color: colorScheme.onSurfaceVariant,
|
||||
height: 1.45,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 28),
|
||||
] else if (containerData != null) ...[
|
||||
Text(
|
||||
containerData.name?.isNotEmpty == true
|
||||
? containerData.name!
|
||||
: 'Container',
|
||||
textAlign: TextAlign.center,
|
||||
style: theme.textTheme.headlineSmall?.copyWith(
|
||||
fontWeight: FontWeight.w700,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
Text(
|
||||
hasContainerTabs
|
||||
? 'No matching tab selected'
|
||||
: 'No open tabs in this container',
|
||||
textAlign: TextAlign.center,
|
||||
style: theme.textTheme.bodyLarge?.copyWith(
|
||||
color: colorScheme.onSurfaceVariant,
|
||||
height: 1.45,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 28),
|
||||
],
|
||||
Container(
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.all(20),
|
||||
decoration: BoxDecoration(
|
||||
color: colorScheme.surfaceContainer.withValues(alpha: 0.9),
|
||||
borderRadius: BorderRadius.circular(28),
|
||||
border: Border.all(
|
||||
color: Color.alphaBlend(
|
||||
BrowserPage.brandGrey.withValues(alpha: 0.18),
|
||||
colorScheme.outlineVariant,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
LayoutBuilder(
|
||||
builder: (context, constraints) {
|
||||
return SingleChildScrollView(
|
||||
physics: const AlwaysScrollableScrollPhysics(),
|
||||
padding: EdgeInsets.fromLTRB(
|
||||
24,
|
||||
32,
|
||||
24,
|
||||
32 + bottomViewportInset,
|
||||
),
|
||||
child: ConstrainedBox(
|
||||
constraints: BoxConstraints(
|
||||
minHeight: math.max(
|
||||
0,
|
||||
constraints.maxHeight - 64 - bottomViewportInset,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Container(
|
||||
width: 36,
|
||||
height: 36,
|
||||
decoration: BoxDecoration(
|
||||
color: theme.colorScheme.surfaceContainerHigh,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: const Icon(MdiIcons.formatQuoteOpen, size: 18),
|
||||
),
|
||||
),
|
||||
child: Center(
|
||||
child: ConstrainedBox(
|
||||
constraints: const BoxConstraints(maxWidth: 560),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
if (containerData != null)
|
||||
_ContainerHeader(container: containerData)
|
||||
else
|
||||
_BrandHeader(colorScheme: colorScheme),
|
||||
const SizedBox(height: 24),
|
||||
if (!hasTabs) ...[
|
||||
Text(
|
||||
'WebLibre is ready',
|
||||
textAlign: TextAlign.center,
|
||||
style: theme.textTheme.headlineSmall?.copyWith(
|
||||
fontWeight: FontWeight.w700,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
Text(
|
||||
'A browser that respects you. Open a tab and experience the web, libre.',
|
||||
textAlign: TextAlign.center,
|
||||
style: theme.textTheme.bodyLarge?.copyWith(
|
||||
color: colorScheme.onSurfaceVariant,
|
||||
height: 1.45,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 28),
|
||||
] else if (containerData != null) ...[
|
||||
Text(
|
||||
containerData.name?.isNotEmpty == true
|
||||
? containerData.name!
|
||||
: 'Container',
|
||||
textAlign: TextAlign.center,
|
||||
style: theme.textTheme.headlineSmall?.copyWith(
|
||||
fontWeight: FontWeight.w700,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
Text(
|
||||
hasContainerTabs
|
||||
? 'No matching tab selected'
|
||||
: 'No open tabs in this container',
|
||||
textAlign: TextAlign.center,
|
||||
style: theme.textTheme.bodyLarge?.copyWith(
|
||||
color: colorScheme.onSurfaceVariant,
|
||||
height: 1.45,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 28),
|
||||
],
|
||||
Container(
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.all(20),
|
||||
decoration: BoxDecoration(
|
||||
color: colorScheme.surfaceContainer.withValues(
|
||||
alpha: 0.9,
|
||||
),
|
||||
borderRadius: BorderRadius.circular(28),
|
||||
border: Border.all(
|
||||
color: Color.alphaBlend(
|
||||
_brandGrey.withValues(alpha: 0.18),
|
||||
colorScheme.outlineVariant,
|
||||
),
|
||||
),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Container(
|
||||
width: 36,
|
||||
height: 36,
|
||||
decoration: BoxDecoration(
|
||||
color: theme
|
||||
.colorScheme
|
||||
.surfaceContainerHigh,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: const Icon(
|
||||
MdiIcons.formatQuoteOpen,
|
||||
size: 18,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: Text(
|
||||
'A thought for the road',
|
||||
style: theme.textTheme.titleMedium
|
||||
?.copyWith(
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
),
|
||||
IconButton.filledTonal(
|
||||
tooltip: 'Refresh quote',
|
||||
onPressed: () {
|
||||
ref.invalidate(randomQuoteProvider);
|
||||
},
|
||||
icon: const Icon(Icons.refresh_rounded),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
switch (quoteAsync) {
|
||||
AsyncData(:final value) => _QuoteBlock(
|
||||
quote: value,
|
||||
),
|
||||
AsyncError() => Text(
|
||||
'Open a new tab and make this space your own.',
|
||||
style: theme.textTheme.bodyLarge?.copyWith(
|
||||
color: colorScheme.onSurfaceVariant,
|
||||
height: 1.5,
|
||||
),
|
||||
),
|
||||
_ => const LinearProgressIndicator(
|
||||
minHeight: 3,
|
||||
),
|
||||
},
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
Wrap(
|
||||
alignment: WrapAlignment.center,
|
||||
spacing: 12,
|
||||
runSpacing: 12,
|
||||
children: [
|
||||
if (hasTabs)
|
||||
OutlinedButton.icon(
|
||||
onPressed: viewTabs,
|
||||
icon: const Icon(Icons.tab_rounded),
|
||||
label: const Text('View tabs'),
|
||||
),
|
||||
FilledButton.icon(
|
||||
onPressed: openNewTab,
|
||||
icon: const Icon(Icons.add_rounded),
|
||||
label: const Text('New tab'),
|
||||
),
|
||||
if (hasContainerTabs)
|
||||
FilledButton.tonalIcon(
|
||||
onPressed: resumeLatestContainerTab,
|
||||
icon: const Icon(Icons.history_rounded),
|
||||
label: const Text('Resume last tab'),
|
||||
)
|
||||
else if (hasTabs && containerData == null)
|
||||
FilledButton.tonalIcon(
|
||||
onPressed: resumeLatestTab,
|
||||
icon: const Icon(Icons.history_rounded),
|
||||
label: const Text('Resume last tab'),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: Text(
|
||||
'A thought for the road',
|
||||
style: theme.textTheme.titleMedium?.copyWith(
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
IconButton.filledTonal(
|
||||
tooltip: 'Refresh quote',
|
||||
onPressed: () {
|
||||
ref.invalidate(randomQuoteProvider);
|
||||
},
|
||||
icon: const Icon(Icons.refresh_rounded),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
const SizedBox(height: 16),
|
||||
switch (quoteAsync) {
|
||||
AsyncData(:final value) => _QuoteBlock(quote: value),
|
||||
AsyncError() => Text(
|
||||
'Open a new tab and make this space your own.',
|
||||
style: theme.textTheme.bodyLarge?.copyWith(
|
||||
color: colorScheme.onSurfaceVariant,
|
||||
height: 1.5,
|
||||
),
|
||||
),
|
||||
_ => const LinearProgressIndicator(minHeight: 3),
|
||||
},
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
Wrap(
|
||||
alignment: WrapAlignment.center,
|
||||
spacing: 12,
|
||||
runSpacing: 12,
|
||||
children: [
|
||||
if (hasTabs)
|
||||
OutlinedButton.icon(
|
||||
onPressed: viewTabs,
|
||||
icon: const Icon(Icons.tab_rounded),
|
||||
label: const Text('View tabs'),
|
||||
),
|
||||
FilledButton.icon(
|
||||
onPressed: openNewTab,
|
||||
icon: const Icon(Icons.add_rounded),
|
||||
label: const Text('New tab'),
|
||||
),
|
||||
if (hasContainerTabs)
|
||||
FilledButton.tonalIcon(
|
||||
onPressed: resumeLatestContainerTab,
|
||||
icon: const Icon(Icons.history_rounded),
|
||||
label: const Text('Resume last tab'),
|
||||
)
|
||||
else if (hasTabs && containerData == null)
|
||||
FilledButton.tonalIcon(
|
||||
onPressed: resumeLatestTab,
|
||||
icon: const Icon(Icons.history_rounded),
|
||||
label: const Text('Resume last tab'),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _BrandHeader extends StatelessWidget {
|
||||
final ColorScheme colorScheme;
|
||||
|
||||
const _BrandHeader({required this.colorScheme});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
width: 112,
|
||||
height: 112,
|
||||
padding: const EdgeInsets.all(20),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(32),
|
||||
gradient: LinearGradient(
|
||||
begin: Alignment.topLeft,
|
||||
end: Alignment.bottomRight,
|
||||
colors: [
|
||||
Color.alphaBlend(
|
||||
BrowserHome._brandPurple.withValues(alpha: 0.18),
|
||||
colorScheme.surfaceContainerHighest,
|
||||
),
|
||||
Color.alphaBlend(
|
||||
BrowserHome._brandYellow.withValues(alpha: 0.12),
|
||||
colorScheme.surfaceContainer,
|
||||
),
|
||||
],
|
||||
),
|
||||
border: Border.all(
|
||||
color: colorScheme.outlineVariant.withValues(alpha: 0.45),
|
||||
),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: colorScheme.shadow.withValues(alpha: 0.08),
|
||||
blurRadius: 32,
|
||||
offset: const Offset(0, 18),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Center(
|
||||
child: SvgPicture.asset('assets/icon/icon.svg', width: 72, height: 72),
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -467,26 +321,3 @@ class _QuoteBlock extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _BackdropOrb extends StatelessWidget {
|
||||
final double width;
|
||||
final double height;
|
||||
final Color color;
|
||||
|
||||
const _BackdropOrb({
|
||||
required this.width,
|
||||
required this.height,
|
||||
required this.color,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return IgnorePointer(
|
||||
child: Container(
|
||||
width: width,
|
||||
height: height,
|
||||
decoration: BoxDecoration(shape: BoxShape.circle, color: color),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
+118
-114
@@ -44,131 +44,135 @@ class ContainerListScreen extends HookConsumerWidget {
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(title: const Text('Containers')),
|
||||
body: Skeletonizer(
|
||||
enabled: containersAsync.isLoading,
|
||||
child: containersAsync.when(
|
||||
skipLoadingOnReload: true,
|
||||
data: (containers) => FadingScroll(
|
||||
fadingSize: 25,
|
||||
builder: (context, controller) {
|
||||
return ListView.builder(
|
||||
controller: controller,
|
||||
itemCount: containers.length,
|
||||
itemBuilder: (context, index) {
|
||||
final container = containers[index];
|
||||
return Slidable(
|
||||
key: ValueKey(container.id),
|
||||
startActionPane: ActionPane(
|
||||
motion: const ScrollMotion(),
|
||||
children: [
|
||||
if (container.id != selectedContainer)
|
||||
SlidableAction(
|
||||
onPressed: (context) async {
|
||||
final result = await ref
|
||||
.read(selectedContainerProvider.notifier)
|
||||
.setContainerId(container.id);
|
||||
body: SafeArea(
|
||||
child: Skeletonizer(
|
||||
enabled: containersAsync.isLoading,
|
||||
child: containersAsync.when(
|
||||
skipLoadingOnReload: true,
|
||||
data: (containers) => FadingScroll(
|
||||
fadingSize: 25,
|
||||
builder: (context, controller) {
|
||||
return ListView.builder(
|
||||
controller: controller,
|
||||
itemCount: containers.length,
|
||||
itemBuilder: (context, index) {
|
||||
final container = containers[index];
|
||||
return Slidable(
|
||||
key: ValueKey(container.id),
|
||||
startActionPane: ActionPane(
|
||||
motion: const ScrollMotion(),
|
||||
children: [
|
||||
if (container.id != selectedContainer)
|
||||
SlidableAction(
|
||||
onPressed: (context) async {
|
||||
final result = await ref
|
||||
.read(selectedContainerProvider.notifier)
|
||||
.setContainerId(container.id);
|
||||
|
||||
if (context.mounted &&
|
||||
result ==
|
||||
SetContainerResult.successHasProxy) {
|
||||
final shouldStartProxy = await ref
|
||||
.read(startProxyControllerProvider.notifier)
|
||||
.shouldPromptProxyStart();
|
||||
|
||||
if (!context.mounted || !shouldStartProxy) {
|
||||
return;
|
||||
}
|
||||
|
||||
final dialogResult = await showDialog<bool>(
|
||||
context: context,
|
||||
builder: (context) {
|
||||
return const TorDialog();
|
||||
},
|
||||
);
|
||||
|
||||
if (dialogResult == true) {
|
||||
await ref
|
||||
if (context.mounted &&
|
||||
result ==
|
||||
SetContainerResult.successHasProxy) {
|
||||
final shouldStartProxy = await ref
|
||||
.read(
|
||||
startProxyControllerProvider.notifier,
|
||||
)
|
||||
.startProxy();
|
||||
.shouldPromptProxyStart();
|
||||
|
||||
if (!context.mounted || !shouldStartProxy) {
|
||||
return;
|
||||
}
|
||||
|
||||
final dialogResult = await showDialog<bool>(
|
||||
context: context,
|
||||
builder: (context) {
|
||||
return const TorDialog();
|
||||
},
|
||||
);
|
||||
|
||||
if (dialogResult == true) {
|
||||
await ref
|
||||
.read(
|
||||
startProxyControllerProvider.notifier,
|
||||
)
|
||||
.startProxy();
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
foregroundColor: Theme.of(
|
||||
context,
|
||||
).colorScheme.onPrimaryContainer,
|
||||
backgroundColor: Theme.of(
|
||||
context,
|
||||
).colorScheme.primaryContainer,
|
||||
icon: Icons.check,
|
||||
label: 'Select',
|
||||
)
|
||||
else
|
||||
},
|
||||
foregroundColor: Theme.of(
|
||||
context,
|
||||
).colorScheme.onPrimaryContainer,
|
||||
backgroundColor: Theme.of(
|
||||
context,
|
||||
).colorScheme.primaryContainer,
|
||||
icon: Icons.check,
|
||||
label: 'Select',
|
||||
)
|
||||
else
|
||||
SlidableAction(
|
||||
onPressed: (context) {
|
||||
ref
|
||||
.read(selectedContainerProvider.notifier)
|
||||
.clearContainer();
|
||||
},
|
||||
foregroundColor: Theme.of(
|
||||
context,
|
||||
).colorScheme.onPrimaryContainer,
|
||||
backgroundColor: Theme.of(
|
||||
context,
|
||||
).colorScheme.primaryContainer,
|
||||
icon: Icons.close,
|
||||
label: 'Unselect',
|
||||
),
|
||||
],
|
||||
),
|
||||
endActionPane: ActionPane(
|
||||
motion: const ScrollMotion(),
|
||||
children: [
|
||||
SlidableAction(
|
||||
onPressed: (context) {
|
||||
ref
|
||||
.read(selectedContainerProvider.notifier)
|
||||
.clearContainer();
|
||||
onPressed: (context) async {
|
||||
await ref
|
||||
.read(containerRepositoryProvider.notifier)
|
||||
.deleteContainer(container.id);
|
||||
},
|
||||
foregroundColor: Theme.of(
|
||||
context,
|
||||
).colorScheme.onPrimaryContainer,
|
||||
backgroundColor: Theme.of(
|
||||
context,
|
||||
).colorScheme.primaryContainer,
|
||||
icon: Icons.close,
|
||||
label: 'Unselect',
|
||||
).colorScheme.errorContainer,
|
||||
foregroundColor: Theme.of(
|
||||
context,
|
||||
).colorScheme.onErrorContainer,
|
||||
icon: Icons.delete,
|
||||
label: 'Delete',
|
||||
),
|
||||
],
|
||||
),
|
||||
endActionPane: ActionPane(
|
||||
motion: const ScrollMotion(),
|
||||
children: [
|
||||
SlidableAction(
|
||||
onPressed: (context) async {
|
||||
await ref
|
||||
.read(containerRepositoryProvider.notifier)
|
||||
.deleteContainer(container.id);
|
||||
},
|
||||
backgroundColor: Theme.of(
|
||||
context,
|
||||
).colorScheme.errorContainer,
|
||||
foregroundColor: Theme.of(
|
||||
context,
|
||||
).colorScheme.onErrorContainer,
|
||||
icon: Icons.delete,
|
||||
label: 'Delete',
|
||||
),
|
||||
],
|
||||
),
|
||||
child: ContainerListTile(
|
||||
container,
|
||||
isSelected: container.id == selectedContainer,
|
||||
onTap: () async {
|
||||
await ContainerEditRoute(
|
||||
containerData: jsonEncode(container.toJson()),
|
||||
).push(context);
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
error: (error, stackTrace) => Center(
|
||||
child: FailureWidget(
|
||||
title: 'Failed to load containers',
|
||||
exception: error,
|
||||
onRetry: () => ref.invalidate(watchContainersWithCountProvider),
|
||||
],
|
||||
),
|
||||
child: ContainerListTile(
|
||||
container,
|
||||
isSelected: container.id == selectedContainer,
|
||||
onTap: () async {
|
||||
await ContainerEditRoute(
|
||||
containerData: jsonEncode(container.toJson()),
|
||||
).push(context);
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
loading: () => ListView.builder(
|
||||
itemCount: 3,
|
||||
itemBuilder: (context, index) => ContainerListTile(
|
||||
ContainerData(id: 'null', color: Colors.transparent),
|
||||
onTap: null,
|
||||
isSelected: false,
|
||||
error: (error, stackTrace) => Center(
|
||||
child: FailureWidget(
|
||||
title: 'Failed to load containers',
|
||||
exception: error,
|
||||
onRetry: () => ref.invalidate(watchContainersWithCountProvider),
|
||||
),
|
||||
),
|
||||
loading: () => ListView.builder(
|
||||
itemCount: 3,
|
||||
itemBuilder: (context, index) => ContainerListTile(
|
||||
ContainerData(id: 'null', color: Colors.transparent),
|
||||
onTap: null,
|
||||
isSelected: false,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
+66
-61
@@ -45,70 +45,75 @@ class ContainerSelectionScreen extends HookConsumerWidget {
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(title: const Text('Select Container')),
|
||||
body: Skeletonizer(
|
||||
enabled: containersAsync.isLoading,
|
||||
child: containersAsync.when(
|
||||
skipLoadingOnReload: true,
|
||||
data: (containers) => FadingScroll(
|
||||
fadingSize: 25,
|
||||
builder: (context, controller) {
|
||||
return ListView.builder(
|
||||
controller: controller,
|
||||
itemCount: containers.length + 1,
|
||||
itemBuilder: (context, index) {
|
||||
if (index == 0) {
|
||||
return ListTileTheme(
|
||||
selectedColor: Theme.of(
|
||||
context,
|
||||
).colorScheme.onPrimaryContainer,
|
||||
selectedTileColor: Theme.of(
|
||||
context,
|
||||
).colorScheme.primaryContainer,
|
||||
child: ListTile(
|
||||
selected: selectedContainerId == null,
|
||||
leading: CircleAvatar(
|
||||
backgroundColor: Theme.of(
|
||||
context,
|
||||
).colorScheme.surfaceContainerHighest,
|
||||
child: const Icon(MdiIcons.folderHidden),
|
||||
body: SafeArea(
|
||||
child: Skeletonizer(
|
||||
enabled: containersAsync.isLoading,
|
||||
child: containersAsync.when(
|
||||
skipLoadingOnReload: true,
|
||||
data: (containers) => FadingScroll(
|
||||
fadingSize: 25,
|
||||
builder: (context, controller) {
|
||||
return ListView.builder(
|
||||
controller: controller,
|
||||
itemCount: containers.length + 1,
|
||||
itemBuilder: (context, index) {
|
||||
if (index == 0) {
|
||||
return ListTileTheme(
|
||||
selectedColor: Theme.of(
|
||||
context,
|
||||
).colorScheme.onPrimaryContainer,
|
||||
selectedTileColor: Theme.of(
|
||||
context,
|
||||
).colorScheme.primaryContainer,
|
||||
child: ListTile(
|
||||
selected: selectedContainerId == null,
|
||||
leading: CircleAvatar(
|
||||
backgroundColor: Theme.of(
|
||||
context,
|
||||
).colorScheme.surfaceContainerHighest,
|
||||
child: const Icon(MdiIcons.folderHidden),
|
||||
),
|
||||
title: const Text('Unassigned'),
|
||||
onTap: () {
|
||||
context.pop<ContainerSelectionResult>(
|
||||
const ContainerSelectionResult.unassigned(),
|
||||
);
|
||||
},
|
||||
),
|
||||
title: const Text('Unassigned'),
|
||||
onTap: () {
|
||||
context.pop<ContainerSelectionResult>(
|
||||
const ContainerSelectionResult.unassigned(),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
final container = containers[index - 1];
|
||||
return ContainerListTile(
|
||||
container,
|
||||
isSelected: container.id == selectedContainerId,
|
||||
onTap: () {
|
||||
context.pop<ContainerSelectionResult>(
|
||||
ContainerSelectionResult.selected(container.id),
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
error: (error, stackTrace) => Center(
|
||||
child: FailureWidget(
|
||||
title: 'Failed to load containers',
|
||||
exception: error,
|
||||
onRetry: () => ref.invalidate(watchContainersWithCountProvider),
|
||||
}
|
||||
|
||||
final container = containers[index - 1];
|
||||
return ContainerListTile(
|
||||
container,
|
||||
isSelected: container.id == selectedContainerId,
|
||||
onTap: () {
|
||||
context.pop<ContainerSelectionResult>(
|
||||
ContainerSelectionResult.selected(container.id),
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
loading: () => ListView.builder(
|
||||
itemCount: 3,
|
||||
itemBuilder: (context, index) => ContainerListTile(
|
||||
ContainerData(id: Namespace.nil.value, color: Colors.transparent),
|
||||
isSelected: false,
|
||||
onTap: null,
|
||||
error: (error, stackTrace) => Center(
|
||||
child: FailureWidget(
|
||||
title: 'Failed to load containers',
|
||||
exception: error,
|
||||
onRetry: () => ref.invalidate(watchContainersWithCountProvider),
|
||||
),
|
||||
),
|
||||
loading: () => ListView.builder(
|
||||
itemCount: 3,
|
||||
itemBuilder: (context, index) => ContainerListTile(
|
||||
ContainerData(
|
||||
id: Namespace.nil.value,
|
||||
color: Colors.transparent,
|
||||
),
|
||||
isSelected: false,
|
||||
onTap: null,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
Reference in New Issue
Block a user