♻️ Refactored background image (skip ci)

This commit is contained in:
2025-01-21 20:35:04 +08:00
parent 36bcff7a7c
commit c82dc7ad85
37 changed files with 755 additions and 609 deletions

View File

@ -6,6 +6,7 @@ import 'package:provider/provider.dart';
import 'package:styled_widget/styled_widget.dart';
import 'package:surface/providers/sn_network.dart';
import 'package:surface/widgets/dialog.dart';
import 'package:surface/widgets/navigation/app_scaffold.dart';
import '../types/account.dart';
@ -56,7 +57,11 @@ class _AbuseReportScreenState extends State<AbuseReportScreen> {
@override
Widget build(BuildContext context) {
return Scaffold(
return AppScaffold(
appBar: AppBar(
leading: const PageBackButton(),
title: Text('screenAbuseReport').tr(),
),
body: Column(
children: [
ListTile(
@ -73,6 +78,7 @@ class _AbuseReportScreenState extends State<AbuseReportScreen> {
else
Expanded(
child: ListView.builder(
padding: EdgeInsets.only(top: 8),
itemCount: _reports.length,
itemBuilder: (context, idx) {
return ListTile(

View File

@ -12,6 +12,7 @@ import 'package:surface/providers/websocket.dart';
import 'package:surface/widgets/account/account_image.dart';
import 'package:surface/widgets/app_bar_leading.dart';
import 'package:surface/widgets/dialog.dart';
import 'package:surface/widgets/navigation/app_scaffold.dart';
class AccountScreen extends StatelessWidget {
const AccountScreen({super.key});
@ -20,7 +21,7 @@ class AccountScreen extends StatelessWidget {
Widget build(BuildContext context) {
final ua = context.watch<UserProvider>();
return Scaffold(
return AppScaffold(
appBar: AppBar(
leading: AutoAppBarLeading(),
title: Text("screenAccount").tr(),

View File

@ -18,6 +18,7 @@ import 'package:surface/providers/userinfo.dart';
import 'package:surface/widgets/account/account_image.dart';
import 'package:surface/widgets/dialog.dart';
import 'package:surface/widgets/loading_indicator.dart';
import 'package:surface/widgets/navigation/app_scaffold.dart';
import 'package:surface/widgets/universal_image.dart';
class ProfileEditScreen extends StatefulWidget {
@ -81,8 +82,7 @@ class _ProfileEditScreenState extends State<ProfileEditScreen> {
onDateTimeChanged: (DateTime newDate) {
setState(() {
_birthday = newDate;
_birthdayController.text =
DateFormat(_kDateFormat).format(_birthday!);
_birthdayController.text = DateFormat(_kDateFormat).format(_birthday!);
});
},
),
@ -96,11 +96,9 @@ class _ProfileEditScreenState extends State<ProfileEditScreen> {
if (image == null) return;
if (!mounted) return;
final ImageProvider imageProvider =
kIsWeb ? NetworkImage(image.path) : FileImage(File(image.path));
final aspectRatios = place == 'banner'
? [CropAspectRatio(width: 16, height: 7)]
: [CropAspectRatio(width: 1, height: 1)];
final ImageProvider imageProvider = kIsWeb ? NetworkImage(image.path) : FileImage(File(image.path));
final aspectRatios =
place == 'banner' ? [CropAspectRatio(width: 16, height: 7)] : [CropAspectRatio(width: 1, height: 1)];
final result = (!kIsWeb && (Platform.isIOS || Platform.isMacOS))
? await showCupertinoImageCropper(
// ignore: use_build_context_synchronously
@ -122,10 +120,7 @@ class _ProfileEditScreenState extends State<ProfileEditScreen> {
setState(() => _isBusy = true);
final rawBytes =
(await result.uiImage.toByteData(format: ImageByteFormat.png))!
.buffer
.asUint8List();
final rawBytes = (await result.uiImage.toByteData(format: ImageByteFormat.png))!.buffer.asUint8List();
try {
final attachment = await attach.directUploadOne(
@ -212,136 +207,141 @@ class _ProfileEditScreenState extends State<ProfileEditScreen> {
final sn = context.read<SnNetworkProvider>();
return SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
LoadingIndicator(isActive: _isBusy),
const Gap(24),
Stack(
clipBehavior: Clip.none,
children: [
Material(
elevation: 0,
child: InkWell(
child: ClipRRect(
borderRadius: const BorderRadius.all(Radius.circular(8)),
child: AspectRatio(
aspectRatio: 16 / 9,
child: Container(
color:
Theme.of(context).colorScheme.surfaceContainerHigh,
child: _banner != null
? AutoResizeUniversalImage(
sn.getAttachmentUrl(_banner!),
fit: BoxFit.cover,
)
: const SizedBox.shrink(),
return AppScaffold(
appBar: AppBar(
leading: const PageBackButton(),
title: Text('screenProfileEdit').tr(),
),
body: SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
LoadingIndicator(isActive: _isBusy),
const Gap(24),
Stack(
clipBehavior: Clip.none,
children: [
Material(
elevation: 0,
child: InkWell(
child: ClipRRect(
borderRadius: const BorderRadius.all(Radius.circular(8)),
child: AspectRatio(
aspectRatio: 16 / 9,
child: Container(
color: Theme.of(context).colorScheme.surfaceContainerHigh,
child: _banner != null
? AutoResizeUniversalImage(
sn.getAttachmentUrl(_banner!),
fit: BoxFit.cover,
)
: const SizedBox.shrink(),
),
),
),
),
onTap: () {
_updateImage('banner');
},
),
),
Positioned(
bottom: -28,
left: 16,
child: Material(
elevation: 2,
borderRadius: const BorderRadius.all(Radius.circular(40)),
child: InkWell(
child: AccountImage(content: _avatar, radius: 40),
onTap: () {
_updateImage('avatar');
_updateImage('banner');
},
),
),
),
],
).padding(horizontal: padding),
const Gap(8 + 28),
Column(
children: [
TextField(
readOnly: true,
controller: _usernameController,
decoration: InputDecoration(
border: const UnderlineInputBorder(),
labelText: 'fieldUsername'.tr(),
helperText: 'fieldUsernameCannotEditHint'.tr(),
),
),
const Gap(4),
TextField(
controller: _nicknameController,
decoration: InputDecoration(
border: const UnderlineInputBorder(),
labelText: 'fieldNickname'.tr(),
),
),
const Gap(4),
Row(
children: [
Flexible(
flex: 1,
child: TextField(
controller: _firstNameController,
decoration: InputDecoration(
border: const UnderlineInputBorder(),
labelText: 'fieldFirstName'.tr(),
),
Positioned(
bottom: -28,
left: 16,
child: Material(
elevation: 2,
borderRadius: const BorderRadius.all(Radius.circular(40)),
child: InkWell(
child: AccountImage(content: _avatar, radius: 40),
onTap: () {
_updateImage('avatar');
},
),
),
const Gap(8),
Flexible(
flex: 1,
child: TextField(
controller: _lastNameController,
decoration: InputDecoration(
border: const UnderlineInputBorder(),
labelText: 'fieldLastName'.tr(),
),
],
).padding(horizontal: padding),
const Gap(8 + 28),
Column(
children: [
TextField(
readOnly: true,
controller: _usernameController,
decoration: InputDecoration(
border: const UnderlineInputBorder(),
labelText: 'fieldUsername'.tr(),
helperText: 'fieldUsernameCannotEditHint'.tr(),
),
),
const Gap(4),
TextField(
controller: _nicknameController,
decoration: InputDecoration(
border: const UnderlineInputBorder(),
labelText: 'fieldNickname'.tr(),
),
),
const Gap(4),
Row(
children: [
Flexible(
flex: 1,
child: TextField(
controller: _firstNameController,
decoration: InputDecoration(
border: const UnderlineInputBorder(),
labelText: 'fieldFirstName'.tr(),
),
),
),
const Gap(8),
Flexible(
flex: 1,
child: TextField(
controller: _lastNameController,
decoration: InputDecoration(
border: const UnderlineInputBorder(),
labelText: 'fieldLastName'.tr(),
),
),
),
],
),
const Gap(4),
TextField(
controller: _descriptionController,
keyboardType: TextInputType.multiline,
maxLines: null,
minLines: 3,
decoration: InputDecoration(
border: const UnderlineInputBorder(),
labelText: 'fieldDescription'.tr(),
),
],
),
const Gap(4),
TextField(
controller: _descriptionController,
keyboardType: TextInputType.multiline,
maxLines: null,
minLines: 3,
decoration: InputDecoration(
border: const UnderlineInputBorder(),
labelText: 'fieldDescription'.tr(),
),
),
const Gap(4),
TextField(
controller: _birthdayController,
readOnly: true,
decoration: InputDecoration(
border: const UnderlineInputBorder(),
labelText: 'fieldBirthday'.tr(),
const Gap(4),
TextField(
controller: _birthdayController,
readOnly: true,
decoration: InputDecoration(
border: const UnderlineInputBorder(),
labelText: 'fieldBirthday'.tr(),
),
onTap: () => _selectBirthday(),
),
onTap: () => _selectBirthday(),
),
],
).padding(horizontal: padding + 8),
const Gap(12),
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
ElevatedButton.icon(
onPressed: _isBusy ? null : _updateUserInfo,
icon: const Icon(Symbols.save),
label: Text('apply').tr(),
),
],
).padding(horizontal: padding),
],
],
).padding(horizontal: padding + 8),
const Gap(12),
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
ElevatedButton.icon(
onPressed: _isBusy ? null : _updateUserInfo,
icon: const Icon(Symbols.save),
label: Text('apply').tr(),
),
],
).padding(horizontal: padding),
],
),
),
);
}

View File

@ -19,6 +19,7 @@ import 'package:surface/types/check_in.dart';
import 'package:surface/types/post.dart';
import 'package:surface/widgets/account/account_image.dart';
import 'package:surface/widgets/dialog.dart';
import 'package:surface/widgets/navigation/app_scaffold.dart';
import 'package:surface/widgets/universal_image.dart';
const Map<String, (String, IconData, Color)> kBadgesMeta = {
@ -241,6 +242,7 @@ class _UserScreenState extends State<UserScreen> with SingleTickerProviderStateM
final sn = context.read<SnNetworkProvider>();
return Scaffold(
backgroundColor: Colors.transparent,
body: CustomScrollView(
controller: _scrollController,
slivers: [

View File

@ -18,6 +18,7 @@ import 'package:surface/types/post.dart';
import 'package:surface/widgets/account/account_image.dart';
import 'package:surface/widgets/dialog.dart';
import 'package:surface/widgets/loading_indicator.dart';
import 'package:surface/widgets/navigation/app_scaffold.dart';
import 'package:surface/widgets/universal_image.dart';
class AccountPublisherEditScreen extends StatefulWidget {
@ -176,7 +177,7 @@ class _AccountPublisherEditScreenState extends State<AccountPublisherEditScreen>
Widget build(BuildContext context) {
final sn = context.read<SnNetworkProvider>();
return Scaffold(
return AppScaffold(
body: SingleChildScrollView(
child: Column(
children: [

View File

@ -10,6 +10,7 @@ import 'package:surface/providers/userinfo.dart';
import 'package:surface/types/realm.dart';
import 'package:surface/widgets/account/account_image.dart';
import 'package:surface/widgets/dialog.dart';
import 'package:surface/widgets/navigation/app_scaffold.dart';
class AccountPublisherNewScreen extends StatefulWidget {
const AccountPublisherNewScreen({super.key});
@ -24,7 +25,11 @@ class _AccountPublisherNewScreenState extends State<AccountPublisherNewScreen> {
@override
Widget build(BuildContext context) {
return Scaffold(
return AppScaffold(
appBar: AppBar(
leading: const PageBackButton(),
title: Text('screenAccountPublisherNew').tr(),
),
body: SingleChildScrollView(
child: Column(
children: [

View File

@ -10,6 +10,7 @@ import 'package:surface/types/post.dart';
import 'package:surface/widgets/account/account_image.dart';
import 'package:surface/widgets/dialog.dart';
import 'package:surface/widgets/loading_indicator.dart';
import 'package:surface/widgets/navigation/app_scaffold.dart';
class PublisherScreen extends StatefulWidget {
const PublisherScreen({super.key});
@ -32,8 +33,7 @@ class _PublisherScreenState extends State<PublisherScreen> {
try {
final resp = await sn.client.get('/cgi/co/publishers/me');
final List<SnPublisher> out = List<SnPublisher>.from(
resp.data?.map((e) => SnPublisher.fromJson(e)) ?? []);
final List<SnPublisher> out = List<SnPublisher>.from(resp.data?.map((e) => SnPublisher.fromJson(e)) ?? []);
if (!mounted) return;
@ -53,7 +53,11 @@ class _PublisherScreenState extends State<PublisherScreen> {
@override
Widget build(BuildContext context) {
return Scaffold(
return AppScaffold(
appBar: AppBar(
leading: const PageBackButton(),
title: Text('screenAccountPublishers').tr(),
),
body: Column(
children: [
ListTile(
@ -62,9 +66,7 @@ class _PublisherScreenState extends State<PublisherScreen> {
contentPadding: const EdgeInsets.symmetric(horizontal: 24),
leading: const Icon(Symbols.add_circle),
onTap: () {
GoRouter.of(context)
.pushNamed('accountPublisherNew')
.then((value) {
GoRouter.of(context).pushNamed('accountPublisherNew').then((value) {
if (value == true) {
_publishers.clear();
_fetchPublishers();
@ -75,48 +77,52 @@ class _PublisherScreenState extends State<PublisherScreen> {
const Divider(height: 1),
LoadingIndicator(isActive: _isBusy),
Expanded(
child: RefreshIndicator(
onRefresh: () {
_publishers.clear();
return _fetchPublishers();
},
child: ListView.builder(
itemCount: _publishers.length,
itemBuilder: (context, idx) {
final publisher = _publishers[idx];
return ListTile(
title: Text(publisher.nick),
subtitle: Text('@${publisher.name}'),
contentPadding: const EdgeInsets.symmetric(horizontal: 16),
leading: AccountImage(content: publisher.avatar),
trailing: PopupMenuButton(
itemBuilder: (BuildContext context) => [
PopupMenuItem(
child: Row(
children: [
const Icon(Symbols.edit),
const Gap(16),
Text('edit').tr(),
],
),
onTap: () {
GoRouter.of(context).pushNamed(
'accountPublisherEdit',
pathParameters: {
'name': publisher.name,
},
).then((value) {
if (value == true) {
_publishers.clear();
_fetchPublishers();
}
});
},
),
],
),
);
child: MediaQuery.removePadding(
context: context,
removeTop: true,
child: RefreshIndicator(
onRefresh: () {
_publishers.clear();
return _fetchPublishers();
},
child: ListView.builder(
itemCount: _publishers.length,
itemBuilder: (context, idx) {
final publisher = _publishers[idx];
return ListTile(
title: Text(publisher.nick),
subtitle: Text('@${publisher.name}'),
contentPadding: const EdgeInsets.symmetric(horizontal: 16),
leading: AccountImage(content: publisher.avatar),
trailing: PopupMenuButton(
itemBuilder: (BuildContext context) => [
PopupMenuItem(
child: Row(
children: [
const Icon(Symbols.edit),
const Gap(16),
Text('edit').tr(),
],
),
onTap: () {
GoRouter.of(context).pushNamed(
'accountPublisherEdit',
pathParameters: {
'name': publisher.name,
},
).then((value) {
if (value == true) {
_publishers.clear();
_fetchPublishers();
}
});
},
),
],
),
);
},
),
),
),
),

View File

@ -11,6 +11,7 @@ import 'package:surface/widgets/app_bar_leading.dart';
import 'package:surface/widgets/attachment/attachment_zoom.dart';
import 'package:surface/widgets/attachment/attachment_item.dart';
import 'package:surface/widgets/dialog.dart';
import 'package:surface/widgets/navigation/app_scaffold.dart';
import 'package:uuid/uuid.dart';
class AlbumScreen extends StatefulWidget {
@ -82,7 +83,7 @@ class _AlbumScreenState extends State<AlbumScreen> {
@override
Widget build(BuildContext context) {
return Scaffold(
return AppScaffold(
body: CustomScrollView(
controller: _scrollController,
slivers: [

View File

@ -9,6 +9,7 @@ import 'package:surface/providers/sn_network.dart';
import 'package:surface/providers/userinfo.dart';
import 'package:surface/types/auth.dart';
import 'package:surface/widgets/dialog.dart';
import 'package:surface/widgets/navigation/app_scaffold.dart';
import 'package:url_launcher/url_launcher_string.dart';
import '../../providers/websocket.dart';
@ -35,67 +36,73 @@ class _LoginScreenState extends State<LoginScreen> {
@override
Widget build(BuildContext context) {
return Theme(
data: Theme.of(context).copyWith(canvasColor: Colors.transparent),
child: SingleChildScrollView(
child: PageTransitionSwitcher(
transitionBuilder: (
Widget child,
Animation<double> primaryAnimation,
Animation<double> secondaryAnimation,
) {
return SharedAxisTransition(
animation: primaryAnimation,
secondaryAnimation: secondaryAnimation,
transitionType: SharedAxisTransitionType.horizontal,
child: Container(
constraints: BoxConstraints(maxWidth: 380),
child: child,
),
);
},
child: switch (_period % 3) {
1 => _LoginPickerScreen(
key: const ValueKey(1),
ticket: _currentTicket,
factors: _factors,
onTicket: (p0) => setState(() {
_currentTicket = p0;
}),
onPickFactor: (p0) => setState(() {
_factorPicked = p0;
}),
onNext: () => setState(() {
_period++;
}),
),
2 => _LoginCheckScreen(
key: const ValueKey(2),
ticket: _currentTicket,
factor: _factorPicked,
onTicket: (p0) => setState(() {
_currentTicket = p0;
}),
onNext: () => setState(() {
_period = 1;
}),
),
_ => _LoginLookupScreen(
key: const ValueKey(0),
ticket: _currentTicket,
onTicket: (p0) => setState(() {
_currentTicket = p0;
}),
onFactor: (p0) => setState(() {
_factors = p0;
}),
onNext: () => setState(() {
_period++;
}),
),
},
).padding(all: 24),
).center(),
return AppScaffold(
appBar: AppBar(
leading: const PageBackButton(),
title: Text('screenAuthLogin').tr(),
),
body: Theme(
data: Theme.of(context).copyWith(canvasColor: Colors.transparent),
child: SingleChildScrollView(
child: PageTransitionSwitcher(
transitionBuilder: (
Widget child,
Animation<double> primaryAnimation,
Animation<double> secondaryAnimation,
) {
return SharedAxisTransition(
animation: primaryAnimation,
secondaryAnimation: secondaryAnimation,
transitionType: SharedAxisTransitionType.horizontal,
child: Container(
constraints: BoxConstraints(maxWidth: 380),
child: child,
),
);
},
child: switch (_period % 3) {
1 => _LoginPickerScreen(
key: const ValueKey(1),
ticket: _currentTicket,
factors: _factors,
onTicket: (p0) => setState(() {
_currentTicket = p0;
}),
onPickFactor: (p0) => setState(() {
_factorPicked = p0;
}),
onNext: () => setState(() {
_period++;
}),
),
2 => _LoginCheckScreen(
key: const ValueKey(2),
ticket: _currentTicket,
factor: _factorPicked,
onTicket: (p0) => setState(() {
_currentTicket = p0;
}),
onNext: () => setState(() {
_period = 1;
}),
),
_ => _LoginLookupScreen(
key: const ValueKey(0),
ticket: _currentTicket,
onTicket: (p0) => setState(() {
_currentTicket = p0;
}),
onFactor: (p0) => setState(() {
_factors = p0;
}),
onNext: () => setState(() {
_period++;
}),
),
},
).padding(all: 24),
).center(),
),
);
}
}
@ -441,7 +448,7 @@ class _LoginLookupScreenState extends State<_LoginLookupScreen> {
widget.onNext();
} catch (err) {
if(mounted) context.showErrorDialog(err);
if (mounted) context.showErrorDialog(err);
return;
} finally {
setState(() => _isBusy = false);

View File

@ -8,6 +8,7 @@ import 'package:provider/provider.dart';
import 'package:styled_widget/styled_widget.dart';
import 'package:surface/providers/sn_network.dart';
import 'package:surface/widgets/dialog.dart';
import 'package:surface/widgets/navigation/app_scaffold.dart';
import 'package:url_launcher/url_launcher_string.dart';
class RegisterScreen extends StatefulWidget {
@ -54,175 +55,178 @@ class _RegisterScreenState extends State<RegisterScreen> {
@override
Widget build(BuildContext context) {
return StyledWidget(Container(
constraints: const BoxConstraints(maxWidth: 380),
child: SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Align(
alignment: Alignment.centerLeft,
child: CircleAvatar(
radius: 26,
child: const Icon(
Symbols.person_add,
size: 28,
),
).padding(bottom: 8),
),
Text(
'screenAuthRegister',
style: const TextStyle(
fontSize: 28,
fontWeight: FontWeight.w900,
return AppScaffold(
appBar: AppBar(
leading: const PageBackButton(),
title: Text('screenAuthRegister').tr(),
),
body: StyledWidget(Container(
constraints: const BoxConstraints(maxWidth: 380),
child: SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Align(
alignment: Alignment.centerLeft,
child: CircleAvatar(
radius: 26,
child: const Icon(
Symbols.person_add,
size: 28,
),
).padding(bottom: 8),
),
).tr().padding(left: 4, bottom: 16),
Form(
key: _formKey,
autovalidateMode: AutovalidateMode.onUserInteraction,
child: Column(
children: [
TextFormField(
validator: (value) {
if (value == null || value.length < 4 || value.length > 32) {
return 'fieldUsernameLengthLimit'.tr(args: [4.toString(), 32.toString()]);
}
if (!RegExp(r'^[a-zA-Z0-9_]+$').hasMatch(value)) {
return 'fieldUsernameAlphanumOnly'.tr();
}
return null;
},
autocorrect: false,
enableSuggestions: false,
controller: _usernameController,
autofillHints: const [AutofillHints.username],
decoration: InputDecoration(
isDense: true,
border: const UnderlineInputBorder(),
labelText: 'fieldUsername'.tr(),
),
onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(),
),
const Gap(12),
TextFormField(
validator: (value) {
if (value == null || value.length < 4 || value.length > 32) {
return 'fieldNicknameLengthLimit'.tr(args: [4.toString(), 32.toString()]);
}
return null;
},
autocorrect: false,
enableSuggestions: false,
controller: _nicknameController,
autofillHints: const [AutofillHints.nickname],
decoration: InputDecoration(
isDense: true,
border: const UnderlineInputBorder(),
labelText: 'fieldNickname'.tr(),
),
onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(),
),
const Gap(12),
TextFormField(
validator: (value) {
if (value == null || value.isEmpty) {
return 'fieldCannotBeEmpty'.tr();
}
if (!EmailValidator.validate(value)) {
return 'fieldEmailAddressMustBeValid'.tr();
}
return null;
},
autocorrect: false,
enableSuggestions: false,
controller: _emailController,
autofillHints: const [AutofillHints.email],
decoration: InputDecoration(
isDense: true,
border: const UnderlineInputBorder(),
labelText: 'fieldEmail'.tr(),
),
onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(),
),
const Gap(12),
TextFormField(
validator: (value) {
if (value == null || value.isEmpty) {
return 'fieldCannotBeEmpty'.tr();
}
return null;
},
obscureText: true,
autocorrect: false,
enableSuggestions: false,
autofillHints: const [AutofillHints.password],
controller: _passwordController,
decoration: InputDecoration(
isDense: true,
border: const UnderlineInputBorder(),
labelText: 'fieldPassword'.tr(),
),
onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(),
),
],
).padding(horizontal: 7),
),
const Gap(16),
Align(
alignment: Alignment.centerRight,
child: StyledWidget(
Container(
constraints: const BoxConstraints(maxWidth: 290),
child: Column(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Text(
'termAcceptNextWithAgree'.tr(),
textAlign: TextAlign.end,
style: Theme.of(context).textTheme.bodySmall!.copyWith(
color: Theme.of(context)
.colorScheme
.onSurface
.withAlpha((255 * 0.75).round()),
),
Text(
'screenAuthRegister',
style: const TextStyle(
fontSize: 28,
fontWeight: FontWeight.w900,
),
).tr().padding(left: 4, bottom: 16),
Form(
key: _formKey,
autovalidateMode: AutovalidateMode.onUserInteraction,
child: Column(
children: [
TextFormField(
validator: (value) {
if (value == null || value.length < 4 || value.length > 32) {
return 'fieldUsernameLengthLimit'.tr(args: [4.toString(), 32.toString()]);
}
if (!RegExp(r'^[a-zA-Z0-9_]+$').hasMatch(value)) {
return 'fieldUsernameAlphanumOnly'.tr();
}
return null;
},
autocorrect: false,
enableSuggestions: false,
controller: _usernameController,
autofillHints: const [AutofillHints.username],
decoration: InputDecoration(
isDense: true,
border: const UnderlineInputBorder(),
labelText: 'fieldUsername'.tr(),
),
Material(
color: Colors.transparent,
child: InkWell(
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Text('termAcceptLink'.tr()),
const Gap(4),
const Icon(Symbols.launch, size: 14),
],
onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(),
),
const Gap(12),
TextFormField(
validator: (value) {
if (value == null || value.length < 4 || value.length > 32) {
return 'fieldNicknameLengthLimit'.tr(args: [4.toString(), 32.toString()]);
}
return null;
},
autocorrect: false,
enableSuggestions: false,
controller: _nicknameController,
autofillHints: const [AutofillHints.nickname],
decoration: InputDecoration(
isDense: true,
border: const UnderlineInputBorder(),
labelText: 'fieldNickname'.tr(),
),
onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(),
),
const Gap(12),
TextFormField(
validator: (value) {
if (value == null || value.isEmpty) {
return 'fieldCannotBeEmpty'.tr();
}
if (!EmailValidator.validate(value)) {
return 'fieldEmailAddressMustBeValid'.tr();
}
return null;
},
autocorrect: false,
enableSuggestions: false,
controller: _emailController,
autofillHints: const [AutofillHints.email],
decoration: InputDecoration(
isDense: true,
border: const UnderlineInputBorder(),
labelText: 'fieldEmail'.tr(),
),
onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(),
),
const Gap(12),
TextFormField(
validator: (value) {
if (value == null || value.isEmpty) {
return 'fieldCannotBeEmpty'.tr();
}
return null;
},
obscureText: true,
autocorrect: false,
enableSuggestions: false,
autofillHints: const [AutofillHints.password],
controller: _passwordController,
decoration: InputDecoration(
isDense: true,
border: const UnderlineInputBorder(),
labelText: 'fieldPassword'.tr(),
),
onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(),
),
],
).padding(horizontal: 7),
),
const Gap(16),
Align(
alignment: Alignment.centerRight,
child: StyledWidget(
Container(
constraints: const BoxConstraints(maxWidth: 290),
child: Column(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Text(
'termAcceptNextWithAgree'.tr(),
textAlign: TextAlign.end,
style: Theme.of(context).textTheme.bodySmall!.copyWith(
color: Theme.of(context).colorScheme.onSurface.withAlpha((255 * 0.75).round()),
),
),
Material(
color: Colors.transparent,
child: InkWell(
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Text('termAcceptLink'.tr()),
const Gap(4),
const Icon(Symbols.launch, size: 14),
],
),
onTap: () {
launchUrlString('https://solsynth.dev/terms');
},
),
onTap: () {
launchUrlString('https://solsynth.dev/terms');
},
),
),
],
),
),
).padding(horizontal: 16),
),
Align(
alignment: Alignment.centerRight,
child: TextButton(
onPressed: () => _performAction(context),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Text('next').tr(),
const Icon(Symbols.chevron_right),
],
),
),
).padding(horizontal: 16),
),
Align(
alignment: Alignment.centerRight,
child: TextButton(
onPressed: () => _performAction(context),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Text('next').tr(),
const Icon(Symbols.chevron_right),
],
),
),
),
],
],
),
),
),
)).padding(all: 24).center();
)).padding(all: 24).center(),
);
}
}

View File

@ -13,6 +13,7 @@ import 'package:surface/widgets/account/account_select.dart';
import 'package:surface/widgets/app_bar_leading.dart';
import 'package:surface/widgets/dialog.dart';
import 'package:surface/widgets/loading_indicator.dart';
import 'package:surface/widgets/navigation/app_scaffold.dart';
import 'package:surface/widgets/unauthorized_hint.dart';
import 'package:uuid/uuid.dart';
@ -120,7 +121,7 @@ class _ChatScreenState extends State<ChatScreen> {
final ua = context.read<UserProvider>();
if (!ua.isAuthorized) {
return Scaffold(
return AppScaffold(
appBar: AppBar(
leading: AutoAppBarLeading(),
title: Text('screenChat').tr(),
@ -131,7 +132,7 @@ class _ChatScreenState extends State<ChatScreen> {
);
}
return Scaffold(
return AppScaffold(
appBar: AppBar(
leading: AutoAppBarLeading(),
title: Text('screenChat').tr(),
@ -195,22 +196,58 @@ class _ChatScreenState extends State<ChatScreen> {
children: [
LoadingIndicator(isActive: _isBusy),
Expanded(
child: RefreshIndicator(
onRefresh: () => Future.sync(() => _refreshChannels()),
child: ListView.builder(
itemCount: _channels?.length ?? 0,
itemBuilder: (context, idx) {
final channel = _channels![idx];
final lastMessage = _lastMessages?[channel.id];
child: MediaQuery.removePadding(
context: context,
removeTop: true,
child: RefreshIndicator(
onRefresh: () => Future.sync(() => _refreshChannels()),
child: ListView.builder(
itemCount: _channels?.length ?? 0,
itemBuilder: (context, idx) {
final channel = _channels![idx];
final lastMessage = _lastMessages?[channel.id];
if (channel.type == 1) {
final otherMember = channel.members?.cast<SnChannelMember?>().firstWhere(
(ele) => ele?.accountId != ua.user?.id,
orElse: () => null,
);
if (channel.type == 1) {
final otherMember = channel.members?.cast<SnChannelMember?>().firstWhere(
(ele) => ele?.accountId != ua.user?.id,
orElse: () => null,
);
return ListTile(
title: Text(ud.getAccountFromCache(otherMember?.accountId)?.nick ?? channel.name),
subtitle: lastMessage != null
? Text(
'${ud.getAccountFromCache(lastMessage.sender.accountId)?.nick}: ${lastMessage.body['text'] ?? 'Unable preview'}',
maxLines: 1,
overflow: TextOverflow.ellipsis,
)
: Text(
'channelDirectMessageDescription'.tr(args: [
'@${ud.getAccountFromCache(otherMember?.accountId)?.name}',
]),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
contentPadding: const EdgeInsets.symmetric(horizontal: 16),
leading: AccountImage(
content: ud.getAccountFromCache(otherMember?.accountId)?.avatar,
),
onTap: () {
GoRouter.of(context).pushNamed(
'chatRoom',
pathParameters: {
'scope': channel.realm?.alias ?? 'global',
'alias': channel.alias,
},
).then((value) {
if (mounted) _refreshChannels();
});
},
);
}
return ListTile(
title: Text(ud.getAccountFromCache(otherMember?.accountId)?.nick ?? channel.name),
title: Text(channel.name),
subtitle: lastMessage != null
? Text(
'${ud.getAccountFromCache(lastMessage.sender.accountId)?.nick}: ${lastMessage.body['text'] ?? 'Unable preview'}',
@ -218,15 +255,14 @@ class _ChatScreenState extends State<ChatScreen> {
overflow: TextOverflow.ellipsis,
)
: Text(
'channelDirectMessageDescription'.tr(args: [
'@${ud.getAccountFromCache(otherMember?.accountId)?.name}',
]),
channel.description,
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
contentPadding: const EdgeInsets.symmetric(horizontal: 16),
leading: AccountImage(
content: ud.getAccountFromCache(otherMember?.accountId)?.avatar,
content: null,
fallbackWidget: const Icon(Symbols.chat, size: 20),
),
onTap: () {
GoRouter.of(context).pushNamed(
@ -236,43 +272,12 @@ class _ChatScreenState extends State<ChatScreen> {
'alias': channel.alias,
},
).then((value) {
if (mounted) _refreshChannels();
if (value == true) _refreshChannels();
});
},
);
}
return ListTile(
title: Text(channel.name),
subtitle: lastMessage != null
? Text(
'${ud.getAccountFromCache(lastMessage.sender.accountId)?.nick}: ${lastMessage.body['text'] ?? 'Unable preview'}',
maxLines: 1,
overflow: TextOverflow.ellipsis,
)
: Text(
channel.description,
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
contentPadding: const EdgeInsets.symmetric(horizontal: 16),
leading: AccountImage(
content: null,
fallbackWidget: const Icon(Symbols.chat, size: 20),
),
onTap: () {
GoRouter.of(context).pushNamed(
'chatRoom',
pathParameters: {
'scope': channel.realm?.alias ?? 'global',
'alias': channel.alias,
},
).then((value) {
if (value == true) _refreshChannels();
});
},
);
},
},
),
),
),
),

View File

@ -9,6 +9,7 @@ import 'package:styled_widget/styled_widget.dart';
import 'package:surface/providers/chat_call.dart';
import 'package:surface/widgets/chat/call/call_controls.dart';
import 'package:surface/widgets/chat/call/call_participant.dart';
import 'package:surface/widgets/navigation/app_scaffold.dart';
class CallRoomScreen extends StatefulWidget {
final String scope;
@ -152,7 +153,7 @@ class _CallRoomScreenState extends State<CallRoomScreen> {
return ListenableBuilder(
listenable: call,
builder: (context, _) {
return Scaffold(
return AppScaffold(
appBar: AppBar(
title: RichText(
textAlign: TextAlign.center,

View File

@ -14,6 +14,7 @@ import 'package:surface/types/chat.dart';
import 'package:surface/widgets/account/account_image.dart';
import 'package:surface/widgets/dialog.dart';
import 'package:surface/widgets/loading_indicator.dart';
import 'package:surface/widgets/navigation/app_scaffold.dart';
import 'package:very_good_infinite_list/very_good_infinite_list.dart';
class ChannelDetailScreen extends StatefulWidget {
@ -189,7 +190,7 @@ class _ChannelDetailScreenState extends State<ChannelDetailScreen> {
final isOwned = ua.isAuthorized && _channel?.accountId == ua.user?.id;
return Scaffold(
return AppScaffold(
appBar: AppBar(
title: _channel != null ? Text(_channel!.name) : Text('loading').tr(),
),

View File

@ -12,6 +12,7 @@ import 'package:surface/types/realm.dart';
import 'package:surface/widgets/account/account_image.dart';
import 'package:surface/widgets/dialog.dart';
import 'package:surface/widgets/loading_indicator.dart';
import 'package:surface/widgets/navigation/app_scaffold.dart';
import 'package:uuid/uuid.dart';
class ChatManageScreen extends StatefulWidget {
@ -121,7 +122,7 @@ class _ChatManageScreenState extends State<ChatManageScreen> {
@override
Widget build(BuildContext context) {
return Scaffold(
return AppScaffold(
appBar: AppBar(
title: widget.editingChannelAlias != null
? Text('screenChatManage').tr()

View File

@ -20,6 +20,7 @@ import 'package:surface/widgets/chat/chat_message_input.dart';
import 'package:surface/widgets/chat/chat_typing_indicator.dart';
import 'package:surface/widgets/dialog.dart';
import 'package:surface/widgets/loading_indicator.dart';
import 'package:surface/widgets/navigation/app_scaffold.dart';
import 'package:very_good_infinite_list/very_good_infinite_list.dart';
import '../../providers/user_directory.dart';
@ -211,7 +212,7 @@ class _ChatRoomScreenState extends State<ChatRoomScreen> {
final call = context.watch<ChatCallProvider>();
final ud = context.read<UserDirectoryProvider>();
return Scaffold(
return AppScaffold(
appBar: AppBar(
title: Text(
_channel?.type == 1

View File

@ -13,6 +13,7 @@ import 'package:surface/screens/post/post_detail.dart';
import 'package:surface/types/post.dart';
import 'package:surface/widgets/app_bar_leading.dart';
import 'package:surface/widgets/dialog.dart';
import 'package:surface/widgets/navigation/app_scaffold.dart';
import 'package:surface/widgets/post/post_item.dart';
import 'package:very_good_infinite_list/very_good_infinite_list.dart';
@ -95,7 +96,7 @@ class _ExploreScreenState extends State<ExploreScreen> {
@override
Widget build(BuildContext context) {
return Scaffold(
return AppScaffold(
floatingActionButtonLocation: ExpandableFab.location,
floatingActionButton: ExpandableFab(
key: _fabKey,
@ -212,7 +213,7 @@ class _ExploreScreenState extends State<ExploreScreen> {
),
),
),
const SliverGap(8),
const SliverGap(12),
SliverInfiniteList(
itemCount: _posts.length,
isLoading: _isBusy,
@ -242,10 +243,10 @@ class _ExploreScreenState extends State<ExploreScreen> {
),
openColor: Colors.transparent,
openElevation: 0,
closedColor: Theme.of(context).colorScheme.surface,
closedColor: Theme.of(context).colorScheme.surfaceContainerLow.withOpacity(0.75),
transitionType: ContainerTransitionType.fade,
closedShape: const RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(8)),
borderRadius: BorderRadius.all(Radius.circular(16)),
),
),
);

View File

@ -11,6 +11,7 @@ import 'package:surface/widgets/account/account_image.dart';
import 'package:surface/widgets/app_bar_leading.dart';
import 'package:surface/widgets/dialog.dart';
import 'package:surface/widgets/loading_indicator.dart';
import 'package:surface/widgets/navigation/app_scaffold.dart';
import '../providers/userinfo.dart';
import '../widgets/unauthorized_hint.dart';
@ -180,7 +181,7 @@ class _FriendScreenState extends State<FriendScreen> {
final ua = context.read<UserProvider>();
if (!ua.isAuthorized) {
return Scaffold(
return AppScaffold(
appBar: AppBar(
leading: AutoAppBarLeading(),
title: Text('screenFriend').tr(),
@ -191,7 +192,7 @@ class _FriendScreenState extends State<FriendScreen> {
);
}
return Scaffold(
return AppScaffold(
appBar: AppBar(
leading: AutoAppBarLeading(),
title: Text('screenFriend').tr(),

View File

@ -25,6 +25,7 @@ import 'package:surface/types/check_in.dart';
import 'package:surface/types/post.dart';
import 'package:surface/widgets/app_bar_leading.dart';
import 'package:surface/widgets/dialog.dart';
import 'package:surface/widgets/navigation/app_scaffold.dart';
import 'package:surface/widgets/post/post_item.dart';
class HomeScreenDashEntry {
@ -67,7 +68,7 @@ class _HomeScreenState extends State<HomeScreen> {
@override
Widget build(BuildContext context) {
return Scaffold(
return AppScaffold(
appBar: AppBar(
leading: AutoAppBarLeading(),
title: Text("screenHome").tr(),
@ -387,6 +388,8 @@ class _HomeDashCheckInWidgetState extends State<_HomeDashCheckInWidget> {
Text(
'dailyCheckInNone',
style: Theme.of(context).textTheme.bodyLarge,
maxLines: 2,
overflow: TextOverflow.ellipsis,
).tr(),
],
)

View File

@ -14,6 +14,7 @@ import 'package:surface/widgets/app_bar_leading.dart';
import 'package:surface/widgets/dialog.dart';
import 'package:surface/widgets/loading_indicator.dart';
import 'package:surface/widgets/markdown_content.dart';
import 'package:surface/widgets/navigation/app_scaffold.dart';
import 'package:surface/widgets/post/post_item.dart';
import 'package:very_good_infinite_list/very_good_infinite_list.dart';
@ -137,7 +138,7 @@ class _NotificationScreenState extends State<NotificationScreen> {
final ua = context.read<UserProvider>();
if (!ua.isAuthorized) {
return Scaffold(
return AppScaffold(
appBar: AppBar(
leading: AutoAppBarLeading(),
title: Text('screenNotification').tr(),
@ -148,7 +149,7 @@ class _NotificationScreenState extends State<NotificationScreen> {
);
}
return Scaffold(
return AppScaffold(
appBar: AppBar(
leading: AutoAppBarLeading(),
title: Text('screenNotification').tr(),

View File

@ -14,6 +14,7 @@ import 'package:surface/types/post.dart';
import 'package:surface/widgets/dialog.dart';
import 'package:surface/widgets/loading_indicator.dart';
import 'package:surface/widgets/navigation/app_background.dart';
import 'package:surface/widgets/navigation/app_scaffold.dart';
import 'package:surface/widgets/post/post_comment_list.dart';
import 'package:surface/widgets/post/post_item.dart';
import 'package:surface/widgets/post/post_mini_editor.dart';
@ -67,7 +68,7 @@ class _PostDetailScreenState extends State<PostDetailScreen> {
return AppBackground(
isRoot: widget.onBack != null,
child: Scaffold(
child: AppScaffold(
appBar: AppBar(
leading: BackButton(
onPressed: () {

View File

@ -13,6 +13,7 @@ import 'package:surface/providers/sn_network.dart';
import 'package:surface/types/post.dart';
import 'package:surface/widgets/account/account_image.dart';
import 'package:surface/widgets/loading_indicator.dart';
import 'package:surface/widgets/navigation/app_scaffold.dart';
import 'package:surface/widgets/post/post_item.dart';
import 'package:surface/widgets/post/post_media_pending_list.dart';
import 'package:surface/widgets/post/post_meta_editor.dart';
@ -128,7 +129,7 @@ class _PostEditorScreenState extends State<PostEditorScreen> {
return ListenableBuilder(
listenable: _writeController,
builder: (context, _) {
return Scaffold(
return AppScaffold(
appBar: AppBar(
leading: BackButton(
onPressed: () {

View File

@ -8,6 +8,7 @@ import 'package:styled_widget/styled_widget.dart';
import 'package:surface/providers/post.dart';
import 'package:surface/types/post.dart';
import 'package:surface/widgets/dialog.dart';
import 'package:surface/widgets/navigation/app_scaffold.dart';
import 'package:surface/widgets/post/post_item.dart';
import 'package:surface/widgets/post/post_tags_field.dart';
import 'package:very_good_infinite_list/very_good_infinite_list.dart';
@ -119,7 +120,7 @@ class _PostSearchScreenState extends State<PostSearchScreen> {
),
];
return Scaffold(
return AppScaffold(
appBar: AppBar(
title: Text('screenPostSearch').tr(),
actions: [

View File

@ -17,6 +17,7 @@ import 'package:surface/types/post.dart';
import 'package:surface/types/realm.dart';
import 'package:surface/widgets/account/account_image.dart';
import 'package:surface/widgets/dialog.dart';
import 'package:surface/widgets/navigation/app_scaffold.dart';
import 'package:surface/widgets/post/post_item.dart';
import 'package:surface/widgets/universal_image.dart';
import 'package:very_good_infinite_list/very_good_infinite_list.dart';
@ -274,7 +275,7 @@ class _PostPublisherScreenState extends State<PostPublisherScreen> with SingleTi
final sn = context.read<SnNetworkProvider>();
return Scaffold(
return AppScaffold(
body: NestedScrollView(
controller: _scrollController,
headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) {

View File

@ -12,6 +12,7 @@ import 'package:surface/widgets/account/account_image.dart';
import 'package:surface/widgets/app_bar_leading.dart';
import 'package:surface/widgets/dialog.dart';
import 'package:surface/widgets/loading_indicator.dart';
import 'package:surface/widgets/navigation/app_scaffold.dart';
import 'package:surface/widgets/unauthorized_hint.dart';
import 'package:surface/widgets/universal_image.dart';
@ -83,7 +84,7 @@ class _RealmScreenState extends State<RealmScreen> {
final ua = context.read<UserProvider>();
if (!ua.isAuthorized) {
return Scaffold(
return AppScaffold(
appBar: AppBar(
leading: AutoAppBarLeading(),
title: Text('screenRealm').tr(),
@ -94,7 +95,7 @@ class _RealmScreenState extends State<RealmScreen> {
);
}
return Scaffold(
return AppScaffold(
appBar: AppBar(
leading: AutoAppBarLeading(),
title: Text('screenRealm').tr(),

View File

@ -18,6 +18,7 @@ import 'package:surface/types/realm.dart';
import 'package:surface/widgets/account/account_image.dart';
import 'package:surface/widgets/dialog.dart';
import 'package:surface/widgets/loading_indicator.dart';
import 'package:surface/widgets/navigation/app_scaffold.dart';
import 'package:surface/widgets/universal_image.dart';
import 'package:uuid/uuid.dart';
@ -179,7 +180,7 @@ class _RealmManageScreenState extends State<RealmManageScreen> {
Widget build(BuildContext context) {
final sn = context.read<SnNetworkProvider>();
return Scaffold(
return AppScaffold(
appBar: AppBar(
title: widget.editingRealmAlias != null
? Text('screenRealmManage').tr()

View File

@ -11,6 +11,7 @@ import 'package:surface/providers/userinfo.dart';
import 'package:surface/types/realm.dart';
import 'package:surface/widgets/account/account_image.dart';
import 'package:surface/widgets/dialog.dart';
import 'package:surface/widgets/navigation/app_scaffold.dart';
import 'package:very_good_infinite_list/very_good_infinite_list.dart';
import '../../types/post.dart';
@ -70,19 +71,11 @@ class _RealmDetailScreenState extends State<RealmDetailScreen> {
Widget build(BuildContext context) {
return DefaultTabController(
length: 3,
child: Scaffold(
child: AppScaffold(
body: NestedScrollView(
headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) {
// These are the slivers that show up in the "outer" scroll view.
return <Widget>[
SliverOverlapAbsorber(
// This widget takes the overlapping behavior of the SliverAppBar,
// and redirects it to the SliverOverlapInjector below. If it is
// missing, then it is possible for the nested "inner" scroll view
// below to end up under the SliverAppBar even when the inner
// scroll view thinks it has not been scrolled.
// This is not necessary if the "headerSliverBuilder" only builds
// widgets that do not overlap the next sliver.
handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context),
sliver: SliverAppBar(
title: Text(_realm?.name ?? 'loading'.tr()),

View File

@ -18,6 +18,7 @@ import 'package:surface/providers/sn_network.dart';
import 'package:surface/providers/theme.dart';
import 'package:surface/theme.dart';
import 'package:surface/widgets/dialog.dart';
import 'package:surface/widgets/navigation/app_scaffold.dart';
const Map<String, Color> kColorSchemes = {
'colorSchemeIndigo': Colors.indigo,
@ -68,6 +69,7 @@ class _SettingsScreenState extends State<SettingsScreen> {
final sn = context.read<SnNetworkProvider>();
return Scaffold(
backgroundColor: Colors.transparent,
body: SingleChildScrollView(
child: Column(
spacing: 16,
@ -255,6 +257,24 @@ class _SettingsScreenState extends State<SettingsScreen> {
),
],
),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('settingsFeatures').bold().fontSize(17).tr().padding(horizontal: 20, bottom: 4),
CheckboxListTile(
secondary: const Icon(Symbols.vibration),
contentPadding: const EdgeInsets.only(left: 24, right: 17),
title: Text('settingsNotifyWithHaptic').tr(),
subtitle: Text('settingsNotifyWithHapticDescription').tr(),
value: _prefs.getBool(kAppNotifyWithHaptic) ?? true,
onChanged: (value) {
setState(() {
_prefs.setBool(kAppNotifyWithHaptic, value ?? false);
});
},
),
],
),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [