From d7e6fe2d8feff36823ff42026dc4b5c5958096e7 Mon Sep 17 00:00:00 2001 From: LittleSheep Date: Sun, 6 Oct 2024 23:06:33 +0800 Subject: [PATCH] :lipstick: More transparency --- lib/screens/about.dart | 239 ++++---- lib/screens/account.dart | 187 +++--- .../account/preferences/notifications.dart | 71 ++- lib/screens/account/profile_edit.dart | 300 +++++----- lib/screens/auth/signin.dart | 558 +++++++++--------- lib/screens/auth/signup.dart | 253 ++++---- lib/screens/settings.dart | 440 +++++++------- lib/theme.dart | 6 +- lib/widgets/markdown_text_content.dart | 6 +- 9 files changed, 1019 insertions(+), 1041 deletions(-) diff --git a/lib/screens/about.dart b/lib/screens/about.dart index 482e619..7c6760a 100644 --- a/lib/screens/about.dart +++ b/lib/screens/about.dart @@ -4,7 +4,6 @@ import 'package:get/get.dart'; import 'package:intl/intl.dart'; import 'package:package_info_plus/package_info_plus.dart'; import 'package:shared_preferences/shared_preferences.dart'; -import 'package:solian/widgets/root_container.dart'; import 'package:solian/widgets/sized_container.dart'; import 'package:url_launcher/url_launcher_string.dart'; @@ -16,132 +15,130 @@ class AboutScreen extends StatelessWidget { const denseButtonStyle = ButtonStyle(visualDensity: VisualDensity(vertical: -4)); - return RootContainer( - child: SizedBox( - width: double.infinity, - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - ClipRRect( - borderRadius: const BorderRadius.all(Radius.circular(16)), - child: Image.asset('assets/logo.png', width: 120, height: 120), - ), - const Gap(8), - Text( - 'Solian', - style: Theme.of(context).textTheme.headlineMedium, - ), - const Text( - 'The Solar Network', - style: TextStyle(fontWeight: FontWeight.bold, fontSize: 16), - ), - const Gap(8), - FutureBuilder( - future: PackageInfo.fromPlatform(), - builder: (context, snapshot) { - if (!snapshot.hasData) { - return const SizedBox.shrink(); - } + return SizedBox( + width: double.infinity, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + ClipRRect( + borderRadius: const BorderRadius.all(Radius.circular(16)), + child: Image.asset('assets/logo.png', width: 120, height: 120), + ), + const Gap(8), + Text( + 'Solian', + style: Theme.of(context).textTheme.headlineMedium, + ), + const Text( + 'The Solar Network', + style: TextStyle(fontWeight: FontWeight.bold, fontSize: 16), + ), + const Gap(8), + FutureBuilder( + future: PackageInfo.fromPlatform(), + builder: (context, snapshot) { + if (!snapshot.hasData) { + return const SizedBox.shrink(); + } - return Text( - 'v${snapshot.data!.version} · ${snapshot.data!.buildNumber}', - style: const TextStyle(fontFamily: 'monospace'), - ); - }, - ), - Text('Copyright © ${DateTime.now().year} Solsynth LLC'), - const Gap(16), - CenteredContainer( - maxWidth: 280, - child: Wrap( - spacing: 4, - runSpacing: 4, - alignment: WrapAlignment.center, - children: [ - TextButton( - style: denseButtonStyle, - child: Text('appDetails'.tr), - onPressed: () async { - final info = await PackageInfo.fromPlatform(); + return Text( + 'v${snapshot.data!.version} · ${snapshot.data!.buildNumber}', + style: const TextStyle(fontFamily: 'monospace'), + ); + }, + ), + Text('Copyright © ${DateTime.now().year} Solsynth LLC'), + const Gap(16), + CenteredContainer( + maxWidth: 280, + child: Wrap( + spacing: 4, + runSpacing: 4, + alignment: WrapAlignment.center, + children: [ + TextButton( + style: denseButtonStyle, + child: Text('appDetails'.tr), + onPressed: () async { + final info = await PackageInfo.fromPlatform(); - showAboutDialog( - context: context, - applicationVersion: - '${info.version} (${info.buildNumber})', - applicationLegalese: - 'The Solar Network App is an intuitive and open-source social network and computing platform. Experience the freedom of a user-friendly design that empowers you to create and connect with communities on your own terms. Embrace the future of social networking with a platform that prioritizes your independence and privacy.', - applicationIcon: ClipRRect( - borderRadius: - const BorderRadius.all(Radius.circular(16)), - child: Image.asset('assets/logo.png', - width: 60, height: 60), - ), - ); - }, - ), - TextButton( - style: denseButtonStyle, - child: Text('projectWebsite'.tr), - onPressed: () { - launchUrlString( - 'https://solsynth.dev/products/solar-network'); - }, - ), - TextButton( - style: denseButtonStyle, - child: Text('termRelated'.tr), - onPressed: () { - launchUrlString('https://solsynth.dev/terms'); - }, - ), - TextButton( - style: denseButtonStyle, - child: Text('serviceStatus'.tr), - onPressed: () { - launchUrlString('https://status.solsynth.dev'); - }, - ), - ], - ), + showAboutDialog( + context: context, + applicationVersion: + '${info.version} (${info.buildNumber})', + applicationLegalese: + 'The Solar Network App is an intuitive and open-source social network and computing platform. Experience the freedom of a user-friendly design that empowers you to create and connect with communities on your own terms. Embrace the future of social networking with a platform that prioritizes your independence and privacy.', + applicationIcon: ClipRRect( + borderRadius: + const BorderRadius.all(Radius.circular(16)), + child: Image.asset('assets/logo.png', + width: 60, height: 60), + ), + ); + }, + ), + TextButton( + style: denseButtonStyle, + child: Text('projectWebsite'.tr), + onPressed: () { + launchUrlString( + 'https://solsynth.dev/products/solar-network'); + }, + ), + TextButton( + style: denseButtonStyle, + child: Text('termRelated'.tr), + onPressed: () { + launchUrlString('https://solsynth.dev/terms'); + }, + ), + TextButton( + style: denseButtonStyle, + child: Text('serviceStatus'.tr), + onPressed: () { + launchUrlString('https://status.solsynth.dev'); + }, + ), + ], ), - const Gap(16), - const Text( - 'Open-sourced under AGPLv3', - style: TextStyle( + ), + const Gap(16), + const Text( + 'Open-sourced under AGPLv3', + style: TextStyle( + fontWeight: FontWeight.w300, + fontSize: 12, + ), + ), + FutureBuilder( + future: SharedPreferences.getInstance(), + builder: (context, snapshot) { + const textStyle = TextStyle( fontWeight: FontWeight.w300, fontSize: 12, - ), - ), - FutureBuilder( - future: SharedPreferences.getInstance(), - builder: (context, snapshot) { - const textStyle = TextStyle( - fontWeight: FontWeight.w300, - fontSize: 12, + ); + if (!snapshot.hasData || + !snapshot.data!.containsKey('first_boot_time')) { + return Text( + 'firstBootTime'.trParams({'time': 'unknown'.tr}), + style: textStyle, ); - if (!snapshot.hasData || - !snapshot.data!.containsKey('first_boot_time')) { - return Text( - 'firstBootTime'.trParams({'time': 'unknown'.tr}), - style: textStyle, - ); - } else { - return Text( - 'firstBootTime'.trParams({ - 'time': DateFormat('yyyy-MM-dd').format( - DateTime.tryParse( - snapshot.data!.getString('first_boot_time')!, - )?.toLocal() ?? - DateTime.now(), - ), - }), - style: textStyle, - ); - } - }, - ), - ], - ), + } else { + return Text( + 'firstBootTime'.trParams({ + 'time': DateFormat('yyyy-MM-dd').format( + DateTime.tryParse( + snapshot.data!.getString('first_boot_time')!, + )?.toLocal() ?? + DateTime.now(), + ), + }), + style: textStyle, + ); + } + }, + ), + ], ), ); } diff --git a/lib/screens/account.dart b/lib/screens/account.dart index 8da1143..e293680 100644 --- a/lib/screens/account.dart +++ b/lib/screens/account.dart @@ -7,7 +7,6 @@ import 'package:solian/providers/account_status.dart'; import 'package:solian/providers/relation.dart'; import 'package:solian/router.dart'; import 'package:solian/widgets/account/account_heading.dart'; -import 'package:solian/widgets/root_container.dart'; import 'package:solian/widgets/sized_container.dart'; import 'package:badges/badges.dart' as badges; @@ -50,112 +49,110 @@ class _AccountScreenState extends State { final AuthProvider auth = Get.find(); - return RootContainer( - child: SafeArea( - child: Obx(() { - if (auth.isAuthorized.isFalse) { - return Center( - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - _ActionCard( - icon: Icon( - Icons.login, - color: Theme.of(context).colorScheme.onPrimary, - ), - title: 'signin'.tr, - caption: 'signinCaption'.tr, - onTap: () { - AppRouter.instance.pushNamed('signin').then((val) async { - if (val == true) { - await auth.refreshUserProfile(); - } - }); - }, - ), - _ActionCard( - icon: Icon( - Icons.add, - color: Theme.of(context).colorScheme.onPrimary, - ), - title: 'signup'.tr, - caption: 'signupCaption'.tr, - onTap: () { - AppRouter.instance.pushNamed('signup').then((_) { - setState(() {}); - }); - }, - ), - const Gap(4), - TextButton( - style: const ButtonStyle( - visualDensity: VisualDensity( - horizontal: -4, - vertical: -2, - ), - ), - onPressed: () { - AppRouter.instance.pushNamed('settings'); - }, - child: Text('settings'.tr), - ), - ], - ), - ); - } - - return CenteredContainer( - child: ListView( + return SafeArea( + child: Obx(() { + if (auth.isAuthorized.isFalse) { + return Center( + child: Column( + mainAxisSize: MainAxisSize.min, children: [ - if (auth.userProfile.value != null) - const AccountHeading().paddingOnly(bottom: 8, top: 16), - ...(actionItems.map( - (x) => ListTile( - contentPadding: const EdgeInsets.symmetric(horizontal: 34), - leading: x.$1, - title: Text(x.$2), - onTap: () { - AppRouter.instance - .pushNamed(x.$3) - .then((_) => setState(() {})); - }, + _ActionCard( + icon: Icon( + Icons.login, + color: Theme.of(context).colorScheme.onPrimary, ), - )), - const Divider(thickness: 0.3, height: 1) - .paddingSymmetric(vertical: 4), - ListTile( - contentPadding: const EdgeInsets.symmetric(horizontal: 34), - leading: const Icon(Icons.settings), - title: Text('settings'.tr), + title: 'signin'.tr, + caption: 'signinCaption'.tr, onTap: () { - AppRouter.instance.pushNamed('settings'); + AppRouter.instance.pushNamed('signin').then((val) async { + if (val == true) { + await auth.refreshUserProfile(); + } + }); }, ), - if (auth.isAuthorized.value) - ListTile( - contentPadding: const EdgeInsets.symmetric(horizontal: 34), - leading: const Icon(Icons.edit_notifications), - title: Text('notificationPreferences'.tr), - onTap: () { - AppRouter.instance.pushNamed('notificationPreferences'); - }, + _ActionCard( + icon: Icon( + Icons.add, + color: Theme.of(context).colorScheme.onPrimary, ), - const Divider(thickness: 0.3, height: 1) - .paddingSymmetric(vertical: 4), - ListTile( - contentPadding: const EdgeInsets.symmetric(horizontal: 34), - leading: const Icon(Icons.logout), - title: Text('signout'.tr), + title: 'signup'.tr, + caption: 'signupCaption'.tr, onTap: () { - auth.signout(); - setState(() {}); + AppRouter.instance.pushNamed('signup').then((_) { + setState(() {}); + }); }, ), + const Gap(4), + TextButton( + style: const ButtonStyle( + visualDensity: VisualDensity( + horizontal: -4, + vertical: -2, + ), + ), + onPressed: () { + AppRouter.instance.pushNamed('settings'); + }, + child: Text('settings'.tr), + ), ], ), ); - }), - ), + } + + return CenteredContainer( + child: ListView( + children: [ + if (auth.userProfile.value != null) + const AccountHeading().paddingOnly(bottom: 8, top: 16), + ...(actionItems.map( + (x) => ListTile( + contentPadding: const EdgeInsets.symmetric(horizontal: 34), + leading: x.$1, + title: Text(x.$2), + onTap: () { + AppRouter.instance + .pushNamed(x.$3) + .then((_) => setState(() {})); + }, + ), + )), + const Divider(thickness: 0.3, height: 1) + .paddingSymmetric(vertical: 4), + ListTile( + contentPadding: const EdgeInsets.symmetric(horizontal: 34), + leading: const Icon(Icons.settings), + title: Text('settings'.tr), + onTap: () { + AppRouter.instance.pushNamed('settings'); + }, + ), + if (auth.isAuthorized.value) + ListTile( + contentPadding: const EdgeInsets.symmetric(horizontal: 34), + leading: const Icon(Icons.edit_notifications), + title: Text('notificationPreferences'.tr), + onTap: () { + AppRouter.instance.pushNamed('notificationPreferences'); + }, + ), + const Divider(thickness: 0.3, height: 1) + .paddingSymmetric(vertical: 4), + ListTile( + contentPadding: const EdgeInsets.symmetric(horizontal: 34), + leading: const Icon(Icons.logout), + title: Text('signout'.tr), + onTap: () { + auth.signout(); + setState(() {}); + }, + ), + ], + ), + ); + }), ); } } diff --git a/lib/screens/account/preferences/notifications.dart b/lib/screens/account/preferences/notifications.dart index e71d8ba..43e143c 100644 --- a/lib/screens/account/preferences/notifications.dart +++ b/lib/screens/account/preferences/notifications.dart @@ -6,7 +6,6 @@ import 'package:google_fonts/google_fonts.dart'; import 'package:solian/exceptions/request.dart'; import 'package:solian/exts.dart'; import 'package:solian/providers/auth.dart'; -import 'package:solian/widgets/root_container.dart'; class NotificationPreferencesScreen extends StatefulWidget { const NotificationPreferencesScreen({super.key}); @@ -75,44 +74,42 @@ class _NotificationPreferencesScreenState @override Widget build(BuildContext context) { - return RootContainer( - child: Column( - children: [ - if (_isBusy) const LinearProgressIndicator().animate().scaleX(), - ListTile( - tileColor: Theme.of(context).colorScheme.surfaceContainer, - contentPadding: const EdgeInsets.symmetric(horizontal: 24), - leading: const Icon(Icons.save), - title: Text('save'.tr), - enabled: !_isBusy, - onTap: () { - _savePreferences(); + return Column( + children: [ + if (_isBusy) const LinearProgressIndicator().animate().scaleX(), + ListTile( + tileColor: Theme.of(context).colorScheme.surfaceContainer, + contentPadding: const EdgeInsets.symmetric(horizontal: 24), + leading: const Icon(Icons.save), + title: Text('save'.tr), + enabled: !_isBusy, + onTap: () { + _savePreferences(); + }, + ), + Expanded( + child: ListView.builder( + itemCount: _topicMap.length, + itemBuilder: (context, index) { + final element = _topicMap.entries.elementAt(index); + return CheckboxListTile( + title: Text(element.value), + subtitle: Text( + element.key, + style: GoogleFonts.robotoMono(fontSize: 12), + ), + value: _config[element.key] ?? true, + contentPadding: const EdgeInsets.symmetric(horizontal: 24), + onChanged: (value) { + setState(() { + _config[element.key] = value ?? false; + }); + }, + ); }, ), - Expanded( - child: ListView.builder( - itemCount: _topicMap.length, - itemBuilder: (context, index) { - final element = _topicMap.entries.elementAt(index); - return CheckboxListTile( - title: Text(element.value), - subtitle: Text( - element.key, - style: GoogleFonts.robotoMono(fontSize: 12), - ), - value: _config[element.key] ?? true, - contentPadding: const EdgeInsets.symmetric(horizontal: 24), - onChanged: (value) { - setState(() { - _config[element.key] = value ?? false; - }); - }, - ); - }, - ), - ), - ], - ), + ), + ], ); } } diff --git a/lib/screens/account/profile_edit.dart b/lib/screens/account/profile_edit.dart index 2c7f57f..0b1711b 100644 --- a/lib/screens/account/profile_edit.dart +++ b/lib/screens/account/profile_edit.dart @@ -187,163 +187,161 @@ class _PersonalizeScreenState extends State { Widget build(BuildContext context) { const double padding = 32; - return RootContainer( - child: ListView( - children: [ - if (_isBusy) const LinearProgressIndicator().animate().scaleX(), - const Gap(24), - Stack( - children: [ - AccountAvatar(content: _avatar, radius: 40), - Positioned( - bottom: 0, - left: 40, - child: FloatingActionButton.small( - heroTag: const Key('avatar-editor'), - onPressed: () => _editImage('avatar'), - child: const Icon( - Icons.camera, - ), + return ListView( + children: [ + if (_isBusy) const LinearProgressIndicator().animate().scaleX(), + const Gap(24), + Stack( + children: [ + AccountAvatar(content: _avatar, radius: 40), + Positioned( + bottom: 0, + left: 40, + child: FloatingActionButton.small( + heroTag: const Key('avatar-editor'), + onPressed: () => _editImage('avatar'), + child: const Icon( + Icons.camera, ), ), - ], - ).paddingSymmetric(horizontal: padding), - const Gap(16), - Stack( - children: [ - ClipRRect( - borderRadius: const BorderRadius.all(Radius.circular(8)), - child: AspectRatio( - aspectRatio: 16 / 9, - child: Container( - color: Theme.of(context).colorScheme.surfaceContainerHigh, - child: _banner != null - ? Image.network( - ServiceFinder.buildUrl( - 'files', '/attachments/$_banner'), - fit: BoxFit.cover, - loadingBuilder: (BuildContext context, Widget child, - ImageChunkEvent? loadingProgress) { - if (loadingProgress == null) return child; - return Center( - child: CircularProgressIndicator( - value: loadingProgress.expectedTotalBytes != - null - ? loadingProgress.cumulativeBytesLoaded / - loadingProgress.expectedTotalBytes! - : null, - ), - ); - }, - ) - : Container(), - ), - ), - ), - Positioned( - bottom: 16, - right: 16, - child: FloatingActionButton( - heroTag: const Key('banner-editor'), - onPressed: () => _editImage('banner'), - child: const Icon( - Icons.camera_alt, - ), - ), - ), - ], - ).paddingSymmetric(horizontal: padding), - const Gap(24), - Row( - children: [ - Flexible( - flex: 1, - child: TextField( - readOnly: true, - controller: _usernameController, - decoration: InputDecoration( - border: const OutlineInputBorder(), - labelText: 'username'.tr, - prefixText: '@', - ), - ), - ), - const Gap(16), - Flexible( - flex: 1, - child: TextField( - controller: _nicknameController, - decoration: InputDecoration( - border: const OutlineInputBorder(), - labelText: 'nickname'.tr, - ), - ), - ), - ], - ).paddingSymmetric(horizontal: padding), - const Gap(16), - Row( - children: [ - Flexible( - flex: 1, - child: TextField( - controller: _firstNameController, - decoration: InputDecoration( - border: const OutlineInputBorder(), - labelText: 'firstName'.tr, - ), - ), - ), - const Gap(16), - Flexible( - flex: 1, - child: TextField( - controller: _lastNameController, - decoration: InputDecoration( - border: const OutlineInputBorder(), - labelText: 'lastName'.tr, - ), - ), - ), - ], - ).paddingSymmetric(horizontal: padding), - const Gap(16), - TextField( - controller: _descriptionController, - keyboardType: TextInputType.multiline, - maxLines: null, - minLines: 3, - decoration: InputDecoration( - border: const OutlineInputBorder(), - labelText: 'description'.tr, ), - ).paddingSymmetric(horizontal: padding), - const Gap(16), - TextField( - controller: _birthdayController, - readOnly: true, - decoration: InputDecoration( - border: const OutlineInputBorder(), - labelText: 'birthday'.tr, + ], + ).paddingSymmetric(horizontal: padding), + const Gap(16), + Stack( + children: [ + ClipRRect( + borderRadius: const BorderRadius.all(Radius.circular(8)), + child: AspectRatio( + aspectRatio: 16 / 9, + child: Container( + color: Theme.of(context).colorScheme.surfaceContainerHigh, + child: _banner != null + ? Image.network( + ServiceFinder.buildUrl( + 'files', '/attachments/$_banner'), + fit: BoxFit.cover, + loadingBuilder: (BuildContext context, Widget child, + ImageChunkEvent? loadingProgress) { + if (loadingProgress == null) return child; + return Center( + child: CircularProgressIndicator( + value: loadingProgress.expectedTotalBytes != + null + ? loadingProgress.cumulativeBytesLoaded / + loadingProgress.expectedTotalBytes! + : null, + ), + ); + }, + ) + : Container(), + ), + ), ), - onTap: () => _selectBirthday(), - ).paddingSymmetric(horizontal: padding), - const Gap(16), - Row( - mainAxisAlignment: MainAxisAlignment.end, - children: [ - TextButton( - onPressed: _isBusy ? null : () => _syncWidget(), - child: Text('reset'.tr), + Positioned( + bottom: 16, + right: 16, + child: FloatingActionButton( + heroTag: const Key('banner-editor'), + onPressed: () => _editImage('banner'), + child: const Icon( + Icons.camera_alt, + ), ), - ElevatedButton( - onPressed: _isBusy ? null : () => _editUserInfo(), - child: Text('apply'.tr), + ), + ], + ).paddingSymmetric(horizontal: padding), + const Gap(24), + Row( + children: [ + Flexible( + flex: 1, + child: TextField( + readOnly: true, + controller: _usernameController, + decoration: InputDecoration( + border: const OutlineInputBorder(), + labelText: 'username'.tr, + prefixText: '@', + ), ), - ], - ).paddingSymmetric(horizontal: padding), - ], - ), + ), + const Gap(16), + Flexible( + flex: 1, + child: TextField( + controller: _nicknameController, + decoration: InputDecoration( + border: const OutlineInputBorder(), + labelText: 'nickname'.tr, + ), + ), + ), + ], + ).paddingSymmetric(horizontal: padding), + const Gap(16), + Row( + children: [ + Flexible( + flex: 1, + child: TextField( + controller: _firstNameController, + decoration: InputDecoration( + border: const OutlineInputBorder(), + labelText: 'firstName'.tr, + ), + ), + ), + const Gap(16), + Flexible( + flex: 1, + child: TextField( + controller: _lastNameController, + decoration: InputDecoration( + border: const OutlineInputBorder(), + labelText: 'lastName'.tr, + ), + ), + ), + ], + ).paddingSymmetric(horizontal: padding), + const Gap(16), + TextField( + controller: _descriptionController, + keyboardType: TextInputType.multiline, + maxLines: null, + minLines: 3, + decoration: InputDecoration( + border: const OutlineInputBorder(), + labelText: 'description'.tr, + ), + ).paddingSymmetric(horizontal: padding), + const Gap(16), + TextField( + controller: _birthdayController, + readOnly: true, + decoration: InputDecoration( + border: const OutlineInputBorder(), + labelText: 'birthday'.tr, + ), + onTap: () => _selectBirthday(), + ).paddingSymmetric(horizontal: padding), + const Gap(16), + Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + TextButton( + onPressed: _isBusy ? null : () => _syncWidget(), + child: Text('reset'.tr), + ), + ElevatedButton( + onPressed: _isBusy ? null : () => _editUserInfo(), + child: Text('apply'.tr), + ), + ], + ).paddingSymmetric(horizontal: padding), + ], ); } diff --git a/lib/screens/auth/signin.dart b/lib/screens/auth/signin.dart index 3f4237c..2ff0db1 100644 --- a/lib/screens/auth/signin.dart +++ b/lib/screens/auth/signin.dart @@ -217,298 +217,288 @@ class _SignInScreenState extends State { @override Widget build(BuildContext context) { - return RootContainer( - child: CenteredContainer( - maxWidth: 360, - child: Theme( - data: Theme.of(context).copyWith(canvasColor: Colors.transparent), - child: PageTransitionSwitcher( - transitionBuilder: ( - Widget child, - Animation primaryAnimation, - Animation secondaryAnimation, - ) { - return SharedAxisTransition( - animation: primaryAnimation, - secondaryAnimation: secondaryAnimation, - transitionType: SharedAxisTransitionType.horizontal, - child: child, - ); - }, - child: switch (_period % 3) { - 1 => ListView( - shrinkWrap: true, - key: const ValueKey(1), - children: [ - Align( - alignment: Alignment.centerLeft, - child: ClipRRect( - borderRadius: - const BorderRadius.all(Radius.circular(8)), - child: Image.asset('assets/logo.png', - width: 64, height: 64), - ).paddingOnly(bottom: 8, left: 4), + return CenteredContainer( + maxWidth: 360, + child: Theme( + data: Theme.of(context).copyWith(canvasColor: Colors.transparent), + child: PageTransitionSwitcher( + transitionBuilder: ( + Widget child, + Animation primaryAnimation, + Animation secondaryAnimation, + ) { + return SharedAxisTransition( + animation: primaryAnimation, + secondaryAnimation: secondaryAnimation, + transitionType: SharedAxisTransitionType.horizontal, + child: child, + ); + }, + child: switch (_period % 3) { + 1 => ListView( + shrinkWrap: true, + key: const ValueKey(1), + children: [ + Align( + alignment: Alignment.centerLeft, + child: ClipRRect( + borderRadius: const BorderRadius.all(Radius.circular(8)), + child: + Image.asset('assets/logo.png', width: 64, height: 64), + ).paddingOnly(bottom: 8, left: 4), + ), + Text( + 'signinPickFactor'.tr, + style: const TextStyle( + fontSize: 28, + fontWeight: FontWeight.w900, ), - Text( - 'signinPickFactor'.tr, - style: const TextStyle( - fontSize: 28, - fontWeight: FontWeight.w900, - ), - ).paddingOnly(left: 4, bottom: 16), - Card( - margin: const EdgeInsets.symmetric(vertical: 4), - child: Column( - children: _factors - ?.map( - (x) => CheckboxListTile( - shape: const RoundedRectangleBorder( - borderRadius: BorderRadius.all( - Radius.circular(8), - ), + ).paddingOnly(left: 4, bottom: 16), + Card( + margin: const EdgeInsets.symmetric(vertical: 4), + child: Column( + children: _factors + ?.map( + (x) => CheckboxListTile( + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.all( + Radius.circular(8), ), - secondary: Icon( - _factorLabelMap[x.type]?.$2 ?? - Icons.question_mark, - ), - title: Text( - _factorLabelMap[x.type]?.$1 ?? - 'unknown'.tr, - ), - enabled: !_currentTicket!.factorTrail - .contains(x.id), - value: _factorPicked == x.id, - onChanged: (value) { - if (value == true) { - setState(() => _factorPicked = x.id); - } - }, ), - ) - .toList() ?? - List.empty(), - ), - ), - Text( - 'signinMultiFactor'.trParams( - {'n': _currentTicket!.stepRemain.toString()}, - ), - style: TextStyle(color: _unFocusColor, fontSize: 12), - ).paddingOnly(left: 16, right: 16), - const Gap(12), - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - TextButton( - onPressed: (_isBusy || _period > 1) - ? null - : () => _previousStep(), - style: TextButton.styleFrom( - foregroundColor: Colors.grey), - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - const Icon(Icons.chevron_left), - Text('prev'.tr), - ], - ), - ), - TextButton( - onPressed: - _isBusy ? null : () => _performGetFactorCode(), - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - Text('next'.tr), - const Icon(Icons.chevron_right), - ], - ), - ), - ], - ), - ], - ), - 2 => ListView( - key: const ValueKey(2), - shrinkWrap: true, - children: [ - Align( - alignment: Alignment.centerLeft, - child: ClipRRect( - borderRadius: - const BorderRadius.all(Radius.circular(8)), - child: Image.asset('assets/logo.png', - width: 64, height: 64), - ).paddingOnly(bottom: 8, left: 4), - ), - Text( - 'signinEnterPassword'.tr, - style: const TextStyle( - fontSize: 28, - fontWeight: FontWeight.w900, - ), - ).paddingOnly(left: 4, bottom: 16), - TextField( - autocorrect: false, - enableSuggestions: false, - controller: _passwordController, - obscureText: true, - autofillHints: [ - (_factorLabelMap[_factorPickedType]?.$3 ?? true) - ? AutofillHints.password - : AutofillHints.oneTimeCode - ], - decoration: InputDecoration( - isDense: true, - border: const OutlineInputBorder(), - labelText: - (_factorLabelMap[_factorPickedType]?.$3 ?? true) - ? 'passwordOneTime'.tr - : 'password'.tr, - helperText: - (_factorLabelMap[_factorPickedType]?.$3 ?? true) - ? 'passwordOneTimeInputHint'.tr - : 'passwordInputHint'.tr, - ), - onTapOutside: (_) => - FocusManager.instance.primaryFocus?.unfocus(), - onSubmitted: - _isBusy ? null : (_) => _performCheckTicket(), - ), - const Gap(12), - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - TextButton( - onPressed: _isBusy ? null : () => _previousStep(), - style: TextButton.styleFrom( - foregroundColor: Colors.grey), - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - const Icon(Icons.chevron_left), - Text('prev'.tr), - ], - ), - ), - TextButton( - onPressed: - _isBusy ? null : () => _performCheckTicket(), - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - Text('next'.tr), - const Icon(Icons.chevron_right), - ], - ), - ), - ], - ), - ], - ), - _ => ListView( - key: const ValueKey(0), - shrinkWrap: true, - children: [ - Align( - alignment: Alignment.centerLeft, - child: ClipRRect( - borderRadius: - const BorderRadius.all(Radius.circular(8)), - child: Image.asset('assets/logo.png', - width: 64, height: 64), - ).paddingOnly(bottom: 8, left: 4), - ), - Text( - 'signinGreeting'.tr, - style: const TextStyle( - fontSize: 28, - fontWeight: FontWeight.w900, - ), - ).paddingOnly(left: 4, bottom: 16), - TextField( - autocorrect: false, - enableSuggestions: false, - controller: _usernameController, - autofillHints: const [AutofillHints.username], - decoration: InputDecoration( - isDense: true, - border: const OutlineInputBorder(), - labelText: 'username'.tr, - helperText: 'usernameInputHint'.tr, - ), - onTapOutside: (_) => - FocusManager.instance.primaryFocus?.unfocus(), - onSubmitted: _isBusy ? null : (_) => _performNewTicket(), - ), - const Gap(12), - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - TextButton( - onPressed: - _isBusy ? null : () => _requestResetPassword(), - style: TextButton.styleFrom( - foregroundColor: Colors.grey), - child: Text('forgotPassword'.tr), - ), - TextButton( - onPressed: _isBusy ? null : () => _performNewTicket(), - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - Text('next'.tr), - const Icon(Icons.chevron_right), - ], - ), - ), - ], - ), - const Gap(12), - Align( - alignment: Alignment.centerRight, - child: 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 - .withOpacity(0.75), + secondary: Icon( + _factorLabelMap[x.type]?.$2 ?? + Icons.question_mark, ), - ), - Material( - color: Colors.transparent, - child: InkWell( - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - Text('termAcceptLink'.tr), - const Gap(4), - const Icon(Icons.launch, size: 14), - ], + title: Text( + _factorLabelMap[x.type]?.$1 ?? 'unknown'.tr, + ), + enabled: !_currentTicket!.factorTrail + .contains(x.id), + value: _factorPicked == x.id, + onChanged: (value) { + if (value == true) { + setState(() => _factorPicked = x.id); + } + }, ), - onTap: () { - launchUrlString('https://solsynth.dev/terms'); - }, - ), - ), + ) + .toList() ?? + List.empty(), + ), + ), + Text( + 'signinMultiFactor'.trParams( + {'n': _currentTicket!.stepRemain.toString()}, + ), + style: TextStyle(color: _unFocusColor, fontSize: 12), + ).paddingOnly(left: 16, right: 16), + const Gap(12), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + TextButton( + onPressed: (_isBusy || _period > 1) + ? null + : () => _previousStep(), + style: + TextButton.styleFrom(foregroundColor: Colors.grey), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + const Icon(Icons.chevron_left), + Text('prev'.tr), ], ), - ).paddingSymmetric(horizontal: 16), + ), + TextButton( + onPressed: + _isBusy ? null : () => _performGetFactorCode(), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Text('next'.tr), + const Icon(Icons.chevron_right), + ], + ), + ), + ], + ), + ], + ), + 2 => ListView( + key: const ValueKey(2), + shrinkWrap: true, + children: [ + Align( + alignment: Alignment.centerLeft, + child: ClipRRect( + borderRadius: const BorderRadius.all(Radius.circular(8)), + child: + Image.asset('assets/logo.png', width: 64, height: 64), + ).paddingOnly(bottom: 8, left: 4), + ), + Text( + 'signinEnterPassword'.tr, + style: const TextStyle( + fontSize: 28, + fontWeight: FontWeight.w900, ), - ], - ), - }, - ), + ).paddingOnly(left: 4, bottom: 16), + TextField( + autocorrect: false, + enableSuggestions: false, + controller: _passwordController, + obscureText: true, + autofillHints: [ + (_factorLabelMap[_factorPickedType]?.$3 ?? true) + ? AutofillHints.password + : AutofillHints.oneTimeCode + ], + decoration: InputDecoration( + isDense: true, + border: const OutlineInputBorder(), + labelText: + (_factorLabelMap[_factorPickedType]?.$3 ?? true) + ? 'passwordOneTime'.tr + : 'password'.tr, + helperText: + (_factorLabelMap[_factorPickedType]?.$3 ?? true) + ? 'passwordOneTimeInputHint'.tr + : 'passwordInputHint'.tr, + ), + onTapOutside: (_) => + FocusManager.instance.primaryFocus?.unfocus(), + onSubmitted: _isBusy ? null : (_) => _performCheckTicket(), + ), + const Gap(12), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + TextButton( + onPressed: _isBusy ? null : () => _previousStep(), + style: + TextButton.styleFrom(foregroundColor: Colors.grey), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + const Icon(Icons.chevron_left), + Text('prev'.tr), + ], + ), + ), + TextButton( + onPressed: _isBusy ? null : () => _performCheckTicket(), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Text('next'.tr), + const Icon(Icons.chevron_right), + ], + ), + ), + ], + ), + ], + ), + _ => ListView( + key: const ValueKey(0), + shrinkWrap: true, + children: [ + Align( + alignment: Alignment.centerLeft, + child: ClipRRect( + borderRadius: const BorderRadius.all(Radius.circular(8)), + child: + Image.asset('assets/logo.png', width: 64, height: 64), + ).paddingOnly(bottom: 8, left: 4), + ), + Text( + 'signinGreeting'.tr, + style: const TextStyle( + fontSize: 28, + fontWeight: FontWeight.w900, + ), + ).paddingOnly(left: 4, bottom: 16), + TextField( + autocorrect: false, + enableSuggestions: false, + controller: _usernameController, + autofillHints: const [AutofillHints.username], + decoration: InputDecoration( + isDense: true, + border: const OutlineInputBorder(), + labelText: 'username'.tr, + helperText: 'usernameInputHint'.tr, + ), + onTapOutside: (_) => + FocusManager.instance.primaryFocus?.unfocus(), + onSubmitted: _isBusy ? null : (_) => _performNewTicket(), + ), + const Gap(12), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + TextButton( + onPressed: + _isBusy ? null : () => _requestResetPassword(), + style: + TextButton.styleFrom(foregroundColor: Colors.grey), + child: Text('forgotPassword'.tr), + ), + TextButton( + onPressed: _isBusy ? null : () => _performNewTicket(), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Text('next'.tr), + const Icon(Icons.chevron_right), + ], + ), + ), + ], + ), + const Gap(12), + Align( + alignment: Alignment.centerRight, + child: 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 + .withOpacity(0.75), + ), + ), + Material( + color: Colors.transparent, + child: InkWell( + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Text('termAcceptLink'.tr), + const Gap(4), + const Icon(Icons.launch, size: 14), + ], + ), + onTap: () { + launchUrlString('https://solsynth.dev/terms'); + }, + ), + ), + ], + ), + ).paddingSymmetric(horizontal: 16), + ), + ], + ), + }, ), ).paddingAll(24), ); diff --git a/lib/screens/auth/signup.dart b/lib/screens/auth/signup.dart index afc3bc6..2915e08 100644 --- a/lib/screens/auth/signup.dart +++ b/lib/screens/auth/signup.dart @@ -3,7 +3,6 @@ import 'package:gap/gap.dart'; import 'package:get/get.dart'; import 'package:solian/exts.dart'; import 'package:solian/services.dart'; -import 'package:solian/widgets/root_container.dart'; import 'package:solian/widgets/sized_container.dart'; import 'package:url_launcher/url_launcher_string.dart'; @@ -66,147 +65,141 @@ class _SignUpScreenState extends State { @override Widget build(BuildContext context) { - return RootContainer( - child: CenteredContainer( - maxWidth: 360, - child: ListView( - shrinkWrap: true, - children: [ - Align( - alignment: Alignment.centerLeft, - child: ClipRRect( - borderRadius: const BorderRadius.all(Radius.circular(8)), - child: Image.asset('assets/logo.png', width: 64, height: 64), - ).paddingOnly(bottom: 8, left: 4), + return CenteredContainer( + maxWidth: 360, + child: ListView( + shrinkWrap: true, + children: [ + Align( + alignment: Alignment.centerLeft, + child: ClipRRect( + borderRadius: const BorderRadius.all(Radius.circular(8)), + child: Image.asset('assets/logo.png', width: 64, height: 64), + ).paddingOnly(bottom: 8, left: 4), + ), + Text( + 'signupGreeting'.tr, + style: const TextStyle( + fontSize: 28, + fontWeight: FontWeight.w900, ), - Text( - 'signupGreeting'.tr, - style: const TextStyle( - fontSize: 28, - fontWeight: FontWeight.w900, - ), - ).paddingOnly(left: 4, bottom: 16), - TextField( - autocorrect: false, - enableSuggestions: false, - controller: _usernameController, - autofillHints: const [AutofillHints.username], - decoration: InputDecoration( - isDense: true, - border: const OutlineInputBorder(), - labelText: 'username'.tr, - ), - onTapOutside: (_) => - FocusManager.instance.primaryFocus?.unfocus(), + ).paddingOnly(left: 4, bottom: 16), + TextField( + autocorrect: false, + enableSuggestions: false, + controller: _usernameController, + autofillHints: const [AutofillHints.username], + decoration: InputDecoration( + isDense: true, + border: const OutlineInputBorder(), + labelText: 'username'.tr, ), - const Gap(12), - TextField( - autocorrect: false, - enableSuggestions: false, - controller: _nicknameController, - autofillHints: const [AutofillHints.nickname], - decoration: InputDecoration( - isDense: true, - border: const OutlineInputBorder(), - labelText: 'nickname'.tr, - ), - onTapOutside: (_) => - FocusManager.instance.primaryFocus?.unfocus(), + onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(), + ), + const Gap(12), + TextField( + autocorrect: false, + enableSuggestions: false, + controller: _nicknameController, + autofillHints: const [AutofillHints.nickname], + decoration: InputDecoration( + isDense: true, + border: const OutlineInputBorder(), + labelText: 'nickname'.tr, ), - const Gap(12), - TextField( - autocorrect: false, - enableSuggestions: false, - controller: _emailController, - autofillHints: const [AutofillHints.email], - decoration: InputDecoration( - isDense: true, - border: const OutlineInputBorder(), - labelText: 'email'.tr, - ), - onTapOutside: (_) => - FocusManager.instance.primaryFocus?.unfocus(), + onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(), + ), + const Gap(12), + TextField( + autocorrect: false, + enableSuggestions: false, + controller: _emailController, + autofillHints: const [AutofillHints.email], + decoration: InputDecoration( + isDense: true, + border: const OutlineInputBorder(), + labelText: 'email'.tr, ), - const Gap(12), - TextField( - obscureText: true, - autocorrect: false, - enableSuggestions: false, - autofillHints: const [AutofillHints.password], - controller: _passwordController, - decoration: InputDecoration( - isDense: true, - border: const OutlineInputBorder(), - labelText: 'password'.tr, - ), - onTapOutside: (_) => - FocusManager.instance.primaryFocus?.unfocus(), - onSubmitted: (_) => _performAction(context), + onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(), + ), + const Gap(12), + TextField( + obscureText: true, + autocorrect: false, + enableSuggestions: false, + autofillHints: const [AutofillHints.password], + controller: _passwordController, + decoration: InputDecoration( + isDense: true, + border: const OutlineInputBorder(), + labelText: 'password'.tr, ), - const Gap(8), - CheckboxListTile( - value: _isTermAccepted, - title: Text( - 'termAccept'.tr, - style: const TextStyle(height: 1.2), - ).paddingOnly(bottom: 4), - shape: const RoundedRectangleBorder( - borderRadius: BorderRadius.all( - Radius.circular(8), - ), + onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(), + onSubmitted: (_) => _performAction(context), + ), + const Gap(8), + CheckboxListTile( + value: _isTermAccepted, + title: Text( + 'termAccept'.tr, + style: const TextStyle(height: 1.2), + ).paddingOnly(bottom: 4), + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.all( + Radius.circular(8), ), - subtitle: RichText( - text: TextSpan( - style: Theme.of(context).textTheme.bodySmall!.copyWith( - color: Theme.of(context) - .colorScheme - .onSurface - .withOpacity(0.75), - ), - children: [ - TextSpan(text: 'termAcceptDesc'.tr), - WidgetSpan( - child: Material( - color: Colors.transparent, - child: InkWell( - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - Text('termAcceptLink'.tr), - const Gap(4), - const Icon(Icons.launch, size: 14), - ], - ), - onTap: () { - launchUrlString('https://solsynth.dev/terms'); - }, + ), + subtitle: RichText( + text: TextSpan( + style: Theme.of(context).textTheme.bodySmall!.copyWith( + color: Theme.of(context) + .colorScheme + .onSurface + .withOpacity(0.75), + ), + children: [ + TextSpan(text: 'termAcceptDesc'.tr), + WidgetSpan( + child: Material( + color: Colors.transparent, + child: InkWell( + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Text('termAcceptLink'.tr), + const Gap(4), + const Icon(Icons.launch, size: 14), + ], ), + onTap: () { + launchUrlString('https://solsynth.dev/terms'); + }, ), ), - ], - ), + ), + ], ), - onChanged: (value) { - setState(() => _isTermAccepted = value ?? false); - }, ), - const Gap(16), - Align( - alignment: Alignment.centerRight, - child: TextButton( - onPressed: - !_isTermAccepted ? null : () => _performAction(context), - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - Text('next'.tr), - const Icon(Icons.chevron_right), - ], - ), + onChanged: (value) { + setState(() => _isTermAccepted = value ?? false); + }, + ), + const Gap(16), + Align( + alignment: Alignment.centerRight, + child: TextButton( + onPressed: + !_isTermAccepted ? null : () => _performAction(context), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Text('next'.tr), + const Icon(Icons.chevron_right), + ], ), - ) - ], - ), + ), + ) + ], ).paddingAll(24), ); } diff --git a/lib/screens/settings.dart b/lib/screens/settings.dart index 0e33bc3..bb3f577 100644 --- a/lib/screens/settings.dart +++ b/lib/screens/settings.dart @@ -19,7 +19,6 @@ import 'package:solian/providers/database/database.dart'; import 'package:solian/providers/theme_switcher.dart'; import 'package:solian/router.dart'; import 'package:solian/widgets/reports/abuse_report.dart'; -import 'package:solian/widgets/root_container.dart'; class SettingScreen extends StatefulWidget { const SettingScreen({super.key}); @@ -83,259 +82,258 @@ class _SettingScreenState extends State { @override Widget build(BuildContext context) { - return RootContainer( - child: ListView( - children: [ - _buildCaptionHeader('theme'.tr), - ListTile( - leading: const Icon(Icons.palette), - contentPadding: const EdgeInsets.symmetric(horizontal: 22), - title: Text('globalTheme'.tr), - trailing: DropdownButtonHideUnderline( - child: DropdownButton2( - isExpanded: true, - hint: Text( - 'theme'.tr, - style: TextStyle( - fontSize: 14, - color: Theme.of(context).hintColor, - ), + return ListView( + children: [ + _buildCaptionHeader('theme'.tr), + ListTile( + leading: const Icon(Icons.palette), + contentPadding: const EdgeInsets.symmetric(horizontal: 22), + title: Text('globalTheme'.tr), + trailing: DropdownButtonHideUnderline( + child: DropdownButton2( + isExpanded: true, + hint: Text( + 'theme'.tr, + style: TextStyle( + fontSize: 14, + color: Theme.of(context).hintColor, ), - items: _presentTheme - .map((SolianThemeData item) => - DropdownMenuItem( - value: item, - child: Row( - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - Icon(Icons.circle, color: item.seedColor), - const Gap(8), - Text( + ), + items: _presentTheme + .map((SolianThemeData item) => + DropdownMenuItem( + value: item, + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Icon(Icons.circle, color: item.seedColor), + const Gap(8), + Expanded( + child: Text( item.id.tr, + maxLines: 1, + overflow: TextOverflow.ellipsis, style: const TextStyle( fontSize: 14, ), ), - ], - ), - )) - .toList(), - value: (_prefs?.containsKey('global_theme') ?? false) - ? SolianThemeData.fromJson( - jsonDecode(_prefs!.getString('global_theme')!), - ) - : null, - onChanged: (SolianThemeData? value) { - context.read().setThemeData(value); - setState(() {}); - }, - buttonStyleData: const ButtonStyleData( - padding: EdgeInsets.symmetric(horizontal: 8), - height: 40, - width: 140, - ), - menuItemStyleData: const MenuItemStyleData( - height: 40, - ), + ), + ], + ), + )) + .toList(), + value: (_prefs?.containsKey('global_theme') ?? false) + ? SolianThemeData.fromJson( + jsonDecode(_prefs!.getString('global_theme')!), + ) + : null, + onChanged: (SolianThemeData? value) { + context.read().setThemeData(value); + setState(() {}); + }, + buttonStyleData: const ButtonStyleData( + padding: EdgeInsets.symmetric(horizontal: 8), + height: 40, + width: 140, + ), + menuItemStyleData: const MenuItemStyleData( + height: 40, ), ), ), - CheckboxListTile( - secondary: const Icon(Icons.military_tech), - contentPadding: const EdgeInsets.symmetric(horizontal: 22), - title: Text('agedTheme'.tr), - subtitle: Text('agedThemeDesc'.tr), - value: _prefs?.getBool('aged_theme') ?? false, - onChanged: (value) { - if (value != null) { - context.read().setAgedTheme(value); + ), + CheckboxListTile( + secondary: const Icon(Icons.military_tech), + contentPadding: const EdgeInsets.symmetric(horizontal: 22), + title: Text('agedTheme'.tr), + subtitle: Text('agedThemeDesc'.tr), + value: _prefs?.getBool('aged_theme') ?? false, + onChanged: (value) { + if (value != null) { + context.read().setAgedTheme(value); + } + setState(() {}); + }, + ), + if (!PlatformInfo.isWeb) + ListTile( + leading: const Icon(Icons.wallpaper), + contentPadding: const EdgeInsets.only(left: 22, right: 31), + title: Text('appBackgroundImage'.tr), + subtitle: Text('appBackgroundImageDesc'.tr), + trailing: File('$_docBasepath/app_background_image').existsSync() + ? const Icon(Icons.check_box) + : const Icon(Icons.check_box_outline_blank), + onTap: () async { + if (File('$_docBasepath/app_background_image').existsSync()) { + File('$_docBasepath/app_background_image').deleteSync(); + } else { + final image = await ImagePicker().pickImage( + source: ImageSource.gallery, + ); + if (image == null) return; + + await File(image.path) + .copy('$_docBasepath/app_background_image'); } + setState(() {}); }, ), - if (!PlatformInfo.isWeb) - ListTile( - leading: const Icon(Icons.wallpaper), - contentPadding: const EdgeInsets.only(left: 22, right: 31), - title: Text('appBackgroundImage'.tr), - subtitle: Text('appBackgroundImageDesc'.tr), - trailing: File('$_docBasepath/app_background_image').existsSync() - ? const Icon(Icons.check_box) - : const Icon(Icons.check_box_outline_blank), - onTap: () async { - if (File('$_docBasepath/app_background_image').existsSync()) { - File('$_docBasepath/app_background_image').deleteSync(); - } else { - final image = await ImagePicker().pickImage( - source: ImageSource.gallery, - ); - if (image == null) return; - - await File(image.path) - .copy('$_docBasepath/app_background_image'); - } - - setState(() {}); - }, - ), - _buildCaptionHeader('notification'.tr), - Tooltip( - message: 'settingsNotificationBgServiceDesc'.tr, - child: CheckboxListTile( - contentPadding: const EdgeInsets.symmetric(horizontal: 22), - secondary: const Icon(Icons.system_security_update_warning), - enabled: PlatformInfo.isAndroid, - title: Text('settingsNotificationBgService'.tr), - subtitle: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text('holdToSeeDetail'.tr), - Text( - 'needRestartToApply'.tr, - style: const TextStyle(fontWeight: FontWeight.bold), - ) - ], - ), - value: - _prefs?.getBool('service_background_notification') ?? false, - onChanged: (value) { - _prefs - ?.setBool('service_background_notification', value ?? false) - .then((_) { - setState(() {}); - }); - }, - ), - ), - _buildCaptionHeader('update'.tr), - CheckboxListTile( + _buildCaptionHeader('notification'.tr), + Tooltip( + message: 'settingsNotificationBgServiceDesc'.tr, + child: CheckboxListTile( contentPadding: const EdgeInsets.symmetric(horizontal: 22), - secondary: const Icon(Icons.sync_alt), - title: Text('updateCheckStrictly'.tr), - subtitle: Text('updateCheckStrictlyDesc'.tr), - value: _prefs?.getBool('check_update_strictly') ?? false, - onChanged: (value) { - _prefs - ?.setBool('check_update_strictly', value ?? false) - .then((_) { - setState(() {}); - }); - }, - ), - Obx(() { - final AuthProvider auth = Get.find(); - if (!auth.isAuthorized.value) return const SizedBox.shrink(); - return Column( + secondary: const Icon(Icons.system_security_update_warning), + enabled: PlatformInfo.isAndroid, + title: Text('settingsNotificationBgService'.tr), + subtitle: Column( + crossAxisAlignment: CrossAxisAlignment.start, children: [ - _buildCaptionHeader('account'.tr), - ListTile( - leading: const Icon(Icons.flag), - trailing: const Icon(Icons.chevron_right), - contentPadding: const EdgeInsets.symmetric(horizontal: 22), - title: Text('reportAbuse'.tr), - subtitle: Text('reportAbuseDesc'.tr), - onTap: () { - showDialog( - context: context, - builder: (context) => const AbuseReportDialog(), - ); - }, - ), - ListTile( - leading: const Icon(Icons.person_remove), - trailing: const Icon(Icons.chevron_right), - contentPadding: const EdgeInsets.symmetric(horizontal: 22), - title: Text('accountDeletion'.tr), - subtitle: Text('accountDeletionDesc'.tr), - onTap: () { - context - .showSlideToConfirmDialog( - 'accountDeletionConfirm'.tr, - 'accountDeletionConfirmDesc'.trParams({ - 'account': '@${auth.userProfile.value!['name']}', - }), - ) - .then((value) async { - if (value != true) return; - final client = await auth.configureClient('id'); - final resp = await client.post('/users/me/deletion', {}); - if (resp.statusCode != 200) { - context.showErrorDialog(RequestException(resp)); - } else { - context.showSnackbar('accountDeletionRequested'.tr); - } - }); - }, - ), + Text('holdToSeeDetail'.tr), + Text( + 'needRestartToApply'.tr, + style: const TextStyle(fontWeight: FontWeight.bold), + ) ], - ); - }), - _buildCaptionHeader('performance'.tr), - CheckboxListTile( - contentPadding: const EdgeInsets.symmetric(horizontal: 22), - secondary: const Icon(Icons.message), - title: Text('animatedMessageList'.tr), - subtitle: Text('animatedMessageListDesc'.tr), - value: _prefs?.getBool('non_animated_message_list') ?? false, + ), + value: _prefs?.getBool('service_background_notification') ?? false, onChanged: (value) { _prefs - ?.setBool('non_animated_message_list', value ?? false) + ?.setBool('service_background_notification', value ?? false) .then((_) { setState(() {}); }); }, ), - _buildCaptionHeader('more'.tr), - ListTile( - leading: const Icon(Icons.delete_sweep), - trailing: const Icon(Icons.chevron_right), - subtitle: FutureBuilder( - future: AppDatabase.getDatabaseSize(), - builder: (context, snapshot) { - if (!snapshot.hasData) { - return Text('localDatabaseSize'.trParams( - {'size': 'unknown'.tr}, - )); - } + ), + _buildCaptionHeader('update'.tr), + CheckboxListTile( + contentPadding: const EdgeInsets.symmetric(horizontal: 22), + secondary: const Icon(Icons.sync_alt), + title: Text('updateCheckStrictly'.tr), + subtitle: Text('updateCheckStrictlyDesc'.tr), + value: _prefs?.getBool('check_update_strictly') ?? false, + onChanged: (value) { + _prefs?.setBool('check_update_strictly', value ?? false).then((_) { + setState(() {}); + }); + }, + ), + Obx(() { + final AuthProvider auth = Get.find(); + if (!auth.isAuthorized.value) return const SizedBox.shrink(); + return Column( + children: [ + _buildCaptionHeader('account'.tr), + ListTile( + leading: const Icon(Icons.flag), + trailing: const Icon(Icons.chevron_right), + contentPadding: const EdgeInsets.symmetric(horizontal: 22), + title: Text('reportAbuse'.tr), + subtitle: Text('reportAbuseDesc'.tr), + onTap: () { + showDialog( + context: context, + builder: (context) => const AbuseReportDialog(), + ); + }, + ), + ListTile( + leading: const Icon(Icons.person_remove), + trailing: const Icon(Icons.chevron_right), + contentPadding: const EdgeInsets.symmetric(horizontal: 22), + title: Text('accountDeletion'.tr), + subtitle: Text('accountDeletionDesc'.tr), + onTap: () { + context + .showSlideToConfirmDialog( + 'accountDeletionConfirm'.tr, + 'accountDeletionConfirmDesc'.trParams({ + 'account': '@${auth.userProfile.value!['name']}', + }), + ) + .then((value) async { + if (value != true) return; + final client = await auth.configureClient('id'); + final resp = await client.post('/users/me/deletion', {}); + if (resp.statusCode != 200) { + context.showErrorDialog(RequestException(resp)); + } else { + context.showSnackbar('accountDeletionRequested'.tr); + } + }); + }, + ), + ], + ); + }), + _buildCaptionHeader('performance'.tr), + CheckboxListTile( + contentPadding: const EdgeInsets.symmetric(horizontal: 22), + secondary: const Icon(Icons.message), + title: Text('animatedMessageList'.tr), + subtitle: Text('animatedMessageListDesc'.tr), + value: _prefs?.getBool('non_animated_message_list') ?? false, + onChanged: (value) { + _prefs + ?.setBool('non_animated_message_list', value ?? false) + .then((_) { + setState(() {}); + }); + }, + ), + _buildCaptionHeader('more'.tr), + ListTile( + leading: const Icon(Icons.delete_sweep), + trailing: const Icon(Icons.chevron_right), + subtitle: FutureBuilder( + future: AppDatabase.getDatabaseSize(), + builder: (context, snapshot) { + if (!snapshot.hasData) { return Text('localDatabaseSize'.trParams( - {'size': snapshot.data!.formatBytes()}, + {'size': 'unknown'.tr}, )); - }, - ), - contentPadding: const EdgeInsets.symmetric(horizontal: 22), - title: Text('localDatabaseWipe'.tr), - onTap: () { - AppDatabase.removeDatabase().then((_) { - setState(() {}); - }); + } + return Text('localDatabaseSize'.trParams( + {'size': snapshot.data!.formatBytes()}, + )); }, ), - if (PlatformInfo.canRateTheApp) - ListTile( - leading: const Icon(Icons.star), - trailing: const Icon(Icons.chevron_right), - contentPadding: const EdgeInsets.symmetric(horizontal: 22), - title: Text('rateTheApp'.tr), - subtitle: Text('rateTheAppDesc'.tr), - onTap: () { - final inAppReview = InAppReview.instance; - - inAppReview.openStoreListing( - appStoreId: '6499032345', - ); - }, - ), + contentPadding: const EdgeInsets.symmetric(horizontal: 22), + title: Text('localDatabaseWipe'.tr), + onTap: () { + AppDatabase.removeDatabase().then((_) { + setState(() {}); + }); + }, + ), + if (PlatformInfo.canRateTheApp) ListTile( - leading: const Icon(Icons.info_outline), + leading: const Icon(Icons.star), trailing: const Icon(Icons.chevron_right), contentPadding: const EdgeInsets.symmetric(horizontal: 22), - title: Text('about'.tr), + title: Text('rateTheApp'.tr), + subtitle: Text('rateTheAppDesc'.tr), onTap: () { - AppRouter.instance.pushNamed('about'); + final inAppReview = InAppReview.instance; + + inAppReview.openStoreListing( + appStoreId: '6499032345', + ); }, ), - ], - ), + ListTile( + leading: const Icon(Icons.info_outline), + trailing: const Icon(Icons.chevron_right), + contentPadding: const EdgeInsets.symmetric(horizontal: 22), + title: Text('about'.tr), + onTap: () { + AppRouter.instance.pushNamed('about'); + }, + ), + ], ); } } diff --git a/lib/theme.dart b/lib/theme.dart index cd82c34..752c1e1 100644 --- a/lib/theme.dart +++ b/lib/theme.dart @@ -39,10 +39,13 @@ abstract class AppTheme { brightness: brightness, seedColor: seedColor ?? const Color.fromRGBO(154, 98, 91, 1), ), - scaffoldBackgroundColor: Colors.transparent, snackBarTheme: const SnackBarThemeData( behavior: SnackBarBehavior.floating, ), + scaffoldBackgroundColor: Colors.transparent, + appBarTheme: const AppBarTheme( + backgroundColor: Colors.transparent, + ), fontFamily: 'Comfortaa', fontFamilyFallback: [ 'NotoSansSC', @@ -74,6 +77,7 @@ abstract class AppTheme { behavior: SnackBarBehavior.floating, ), scaffoldBackgroundColor: Colors.transparent, + appBarTheme: const AppBarTheme(backgroundColor: Colors.transparent), fontFamily: data.fontFamily ?? 'Comfortaa', fontFamilyFallback: data.fontFamilyFallback ?? [ diff --git a/lib/widgets/markdown_text_content.dart b/lib/widgets/markdown_text_content.dart index 61b12d5..c0dfeac 100644 --- a/lib/widgets/markdown_text_content.dart +++ b/lib/widgets/markdown_text_content.dart @@ -43,6 +43,10 @@ class MarkdownTextContent extends StatelessWidget { if (isAutoWarp) { paragraph = paragraph.replaceAll('\n', '\\\n'); } + const charactersToTrim = '\\\n\t\r '; + final trimPattern = + RegExp('^[$charactersToTrim]+|[$charactersToTrim]+\$'); + paragraph = paragraph.trim().replaceAll(trimPattern, ''); // Matching stickers final stickerMatch = stickerRegex.allMatches(paragraph); @@ -184,7 +188,7 @@ class MarkdownTextContent extends StatelessWidget { ); if (idx < paragraphs.length - 1) { - contentWidgets.add(const Gap(4)); + contentWidgets.add(isAutoWarp ? const Gap(4) : const Gap(8)); } }