♻️ Refactor the profile and pub profile
This commit is contained in:
		| @@ -39,6 +39,407 @@ import 'package:url_launcher/url_launcher_string.dart'; | ||||
|  | ||||
| part 'profile.g.dart'; | ||||
|  | ||||
| class _AccountBasicInfo extends StatelessWidget { | ||||
|   final SnAccount data; | ||||
|   final String uname; | ||||
|   final AsyncValue<SnDeveloper?> accountDeveloper; | ||||
|  | ||||
|   const _AccountBasicInfo({ | ||||
|     required this.data, | ||||
|     required this.uname, | ||||
|     required this.accountDeveloper, | ||||
|   }); | ||||
|  | ||||
|   @override | ||||
|   Widget build(BuildContext context) { | ||||
|     return Padding( | ||||
|       padding: const EdgeInsets.fromLTRB(24, 24, 24, 8), | ||||
|       child: Row( | ||||
|         crossAxisAlignment: CrossAxisAlignment.start, | ||||
|         children: [ | ||||
|           ProfilePictureWidget(file: data.profile.picture, radius: 32), | ||||
|           const Gap(20), | ||||
|           Expanded( | ||||
|             child: Column( | ||||
|               crossAxisAlignment: CrossAxisAlignment.stretch, | ||||
|               children: [ | ||||
|                 Row( | ||||
|                   children: [ | ||||
|                     AccountName(account: data, style: TextStyle(fontSize: 20)), | ||||
|                     const Gap(6), | ||||
|                     Flexible( | ||||
|                       child: Text( | ||||
|                         '@${data.name}', | ||||
|                         maxLines: 1, | ||||
|                         overflow: TextOverflow.ellipsis, | ||||
|                       ).fontSize(14).opacity(0.85), | ||||
|                     ), | ||||
|                   ], | ||||
|                 ), | ||||
|                 if (accountDeveloper.value != null) | ||||
|                   Row( | ||||
|                     spacing: 7, | ||||
|                     children: [ | ||||
|                       const Icon(Symbols.smart_toy, size: 18), | ||||
|                       Text( | ||||
|                         'botAutomatedBy'.tr( | ||||
|                           args: [accountDeveloper.value!.publisher!.nick], | ||||
|                         ), | ||||
|                       ).fontSize(13), | ||||
|                     ], | ||||
|                   ).opacity(0.75), | ||||
|                 const Gap(4), | ||||
|                 AccountStatusWidget(uname: uname, padding: EdgeInsets.zero), | ||||
|               ], | ||||
|             ), | ||||
|           ), | ||||
|           IconButton( | ||||
|             onPressed: () { | ||||
|               SharePlus.instance.share( | ||||
|                 ShareParams( | ||||
|                   uri: Uri.parse('https://id.solian.app/@${data.name}'), | ||||
|                 ), | ||||
|               ); | ||||
|             }, | ||||
|             icon: const Icon(Symbols.share), | ||||
|           ), | ||||
|         ], | ||||
|       ), | ||||
|     ); | ||||
|   } | ||||
| } | ||||
|  | ||||
| class _AccountProfileBio extends StatelessWidget { | ||||
|   final SnAccount data; | ||||
|  | ||||
|   const _AccountProfileBio({required this.data}); | ||||
|  | ||||
|   @override | ||||
|   Widget build(BuildContext context) { | ||||
|     return Card( | ||||
|       child: Column( | ||||
|         crossAxisAlignment: CrossAxisAlignment.start, | ||||
|         children: [ | ||||
|           Text('bio').tr().bold().fontSize(15).padding(bottom: 8), | ||||
|           if (data.profile.bio.isEmpty) | ||||
|             Text('descriptionNone').tr().italic() | ||||
|           else | ||||
|             MarkdownTextContent( | ||||
|               content: data.profile.bio, | ||||
|               linesMargin: EdgeInsets.zero, | ||||
|             ), | ||||
|         ], | ||||
|       ).padding(horizontal: 24, vertical: 20), | ||||
|     ); | ||||
|   } | ||||
| } | ||||
|  | ||||
| class _AccountProfileDetail extends StatelessWidget { | ||||
|   final SnAccount data; | ||||
|  | ||||
|   const _AccountProfileDetail({required this.data}); | ||||
|  | ||||
|   List<Widget> _buildSubcolumn() { | ||||
|     return [ | ||||
|       Row( | ||||
|         spacing: 6, | ||||
|         children: [ | ||||
|           const Icon(Symbols.join, size: 17, fill: 1), | ||||
|           Text( | ||||
|             'joinedAt'.tr(args: [data.createdAt.formatCustom('yyyy-MM-dd')]), | ||||
|           ), | ||||
|         ], | ||||
|       ), | ||||
|       if (data.profile.birthday != null) | ||||
|         Row( | ||||
|           spacing: 6, | ||||
|           children: [ | ||||
|             const Icon(Symbols.cake, size: 17, fill: 1), | ||||
|             Text(data.profile.birthday!.formatCustom('yyyy-MM-dd')), | ||||
|             Text('·').bold(), | ||||
|             Text( | ||||
|               '${DateTime.now().difference(data.profile.birthday!).inDays ~/ 365} yrs old', | ||||
|             ), | ||||
|           ], | ||||
|         ), | ||||
|       if (data.profile.location.isNotEmpty) | ||||
|         Row( | ||||
|           spacing: 6, | ||||
|           children: [ | ||||
|             const Icon(Symbols.location_on, size: 17, fill: 1), | ||||
|             Text(data.profile.location), | ||||
|           ], | ||||
|         ), | ||||
|       if (data.profile.pronouns.isNotEmpty || data.profile.gender.isNotEmpty) | ||||
|         Row( | ||||
|           spacing: 6, | ||||
|           children: [ | ||||
|             const Icon(Symbols.person, size: 17, fill: 1), | ||||
|             Text( | ||||
|               data.profile.gender.isEmpty | ||||
|                   ? 'unspecified'.tr() | ||||
|                   : data.profile.gender, | ||||
|             ), | ||||
|             Text('·').bold(), | ||||
|             Text( | ||||
|               data.profile.pronouns.isEmpty | ||||
|                   ? 'unspecified'.tr() | ||||
|                   : data.profile.pronouns, | ||||
|             ), | ||||
|           ], | ||||
|         ), | ||||
|       if (data.profile.firstName.isNotEmpty || | ||||
|           data.profile.middleName.isNotEmpty || | ||||
|           data.profile.lastName.isNotEmpty) | ||||
|         Row( | ||||
|           spacing: 6, | ||||
|           children: [ | ||||
|             const Icon(Symbols.id_card, size: 17, fill: 1), | ||||
|             if (data.profile.firstName.isNotEmpty) Text(data.profile.firstName), | ||||
|             if (data.profile.middleName.isNotEmpty) | ||||
|               Text(data.profile.middleName), | ||||
|             if (data.profile.lastName.isNotEmpty) Text(data.profile.lastName), | ||||
|           ], | ||||
|         ), | ||||
|       Tooltip( | ||||
|         message: 'creditsStatus'.tr(), | ||||
|         child: Row( | ||||
|           spacing: 6, | ||||
|           children: [ | ||||
|             Icon(Symbols.star, size: 17, fill: 1).padding(right: 2), | ||||
|             Text('${data.profile.socialCredits.toStringAsFixed(2)} pts'), | ||||
|             Text('·').bold(), | ||||
|             switch (data.profile.socialCreditsLevel) { | ||||
|               -1 => Text('socialCreditsLevelPoor').tr(), | ||||
|               0 => Text('socialCreditsLevelNormal').tr(), | ||||
|               1 => Text('socialCreditsLevelGood').tr(), | ||||
|               2 => Text('socialCreditsLevelExcellent').tr(), | ||||
|               _ => Text('unknown').tr(), | ||||
|             }, | ||||
|           ], | ||||
|         ), | ||||
|       ), | ||||
|       InkWell( | ||||
|         child: Row( | ||||
|           spacing: 6, | ||||
|           children: [ | ||||
|             Icon(Symbols.fingerprint, size: 17, fill: 1).padding(right: 2), | ||||
|             Text(data.id), | ||||
|           ], | ||||
|         ), | ||||
|         onTap: () { | ||||
|           Clipboard.setData(ClipboardData(text: data.id)); | ||||
|         }, | ||||
|       ), | ||||
|     ]; | ||||
|   } | ||||
|  | ||||
|   @override | ||||
|   Widget build(BuildContext context) { | ||||
|     return Card( | ||||
|       child: Column( | ||||
|         crossAxisAlignment: CrossAxisAlignment.stretch, | ||||
|         spacing: 24, | ||||
|         children: [ | ||||
|           if (_buildSubcolumn().isNotEmpty) | ||||
|             Column( | ||||
|               crossAxisAlignment: CrossAxisAlignment.start, | ||||
|               spacing: 2, | ||||
|               children: _buildSubcolumn(), | ||||
|             ), | ||||
|           if (data.profile.timeZone.isNotEmpty && !kIsWeb) | ||||
|             Column( | ||||
|               crossAxisAlignment: CrossAxisAlignment.start, | ||||
|               children: [ | ||||
|                 Text('timeZone').tr().bold(), | ||||
|                 Row( | ||||
|                   crossAxisAlignment: CrossAxisAlignment.baseline, | ||||
|                   textBaseline: TextBaseline.alphabetic, | ||||
|                   spacing: 6, | ||||
|                   children: [ | ||||
|                     Text(data.profile.timeZone), | ||||
|                     Text( | ||||
|                       getTzInfo( | ||||
|                         data.profile.timeZone, | ||||
|                       ).$2.formatCustomGlobal('HH:mm'), | ||||
|                     ), | ||||
|                     Text( | ||||
|                       getTzInfo(data.profile.timeZone).$1.formatOffsetLocal(), | ||||
|                     ).fontSize(11), | ||||
|                     Text( | ||||
|                       'UTC${getTzInfo(data.profile.timeZone).$1.formatOffset()}', | ||||
|                     ).fontSize(11).opacity(0.75), | ||||
|                   ], | ||||
|                 ), | ||||
|               ], | ||||
|             ), | ||||
|         ], | ||||
|       ).padding(horizontal: 24, vertical: 16), | ||||
|     ); | ||||
|   } | ||||
| } | ||||
|  | ||||
| class _AccountProfileLinks extends StatelessWidget { | ||||
|   final SnAccount data; | ||||
|  | ||||
|   const _AccountProfileLinks({required this.data}); | ||||
|  | ||||
|   @override | ||||
|   Widget build(BuildContext context) { | ||||
|     return Card( | ||||
|       child: Column( | ||||
|         crossAxisAlignment: CrossAxisAlignment.start, | ||||
|         children: [ | ||||
|           Text('links').tr().bold().padding(horizontal: 24, top: 12, bottom: 4), | ||||
|           for (final link in data.profile.links) | ||||
|             ListTile( | ||||
|               title: Text(link.name.capitalizeEachWord()), | ||||
|               subtitle: Text(link.url), | ||||
|               contentPadding: EdgeInsets.symmetric(horizontal: 24), | ||||
|               trailing: const Icon(Symbols.chevron_right), | ||||
|               shape: RoundedRectangleBorder( | ||||
|                 borderRadius: const BorderRadius.all(Radius.circular(8)), | ||||
|               ), | ||||
|               onTap: () { | ||||
|                 if (!link.url.startsWith('http') && !link.url.contains('://')) { | ||||
|                   launchUrlString('https://${link.url}'); | ||||
|                 } else { | ||||
|                   launchUrlString(link.url); | ||||
|                 } | ||||
|               }, | ||||
|             ), | ||||
|         ], | ||||
|       ), | ||||
|     ); | ||||
|   } | ||||
| } | ||||
|  | ||||
| class _AccountAction extends StatelessWidget { | ||||
|   final SnAccount data; | ||||
|   final AsyncValue<SnRelationship?> accountRelationship; | ||||
|   final AsyncValue<SnChatRoom?> accountChat; | ||||
|   final VoidCallback relationshipAction; | ||||
|   final VoidCallback blockAction; | ||||
|   final VoidCallback directMessageAction; | ||||
|  | ||||
|   const _AccountAction({ | ||||
|     required this.data, | ||||
|     required this.accountRelationship, | ||||
|     required this.accountChat, | ||||
|     required this.relationshipAction, | ||||
|     required this.blockAction, | ||||
|     required this.directMessageAction, | ||||
|   }); | ||||
|  | ||||
|   @override | ||||
|   Widget build(BuildContext context) { | ||||
|     return Card( | ||||
|       child: Column( | ||||
|         children: [ | ||||
|           Row( | ||||
|             spacing: 8, | ||||
|             children: [ | ||||
|               if (accountRelationship.value == null || | ||||
|                   accountRelationship.value!.status > -100) | ||||
|                 Expanded( | ||||
|                   child: FilledButton.icon( | ||||
|                     style: ButtonStyle( | ||||
|                       backgroundColor: WidgetStatePropertyAll( | ||||
|                         accountRelationship.value == null | ||||
|                             ? null | ||||
|                             : Theme.of(context).colorScheme.secondary, | ||||
|                       ), | ||||
|                       foregroundColor: WidgetStatePropertyAll( | ||||
|                         accountRelationship.value == null | ||||
|                             ? null | ||||
|                             : Theme.of(context).colorScheme.onSecondary, | ||||
|                       ), | ||||
|                     ), | ||||
|                     onPressed: relationshipAction, | ||||
|                     label: | ||||
|                         Text( | ||||
|                           accountRelationship.value == null | ||||
|                               ? 'addFriendShort' | ||||
|                               : 'added', | ||||
|                         ).tr(), | ||||
|                     icon: | ||||
|                         accountRelationship.value == null | ||||
|                             ? const Icon(Symbols.person_add) | ||||
|                             : const Icon(Symbols.person_check), | ||||
|                   ), | ||||
|                 ), | ||||
|               if (accountRelationship.value == null || | ||||
|                   accountRelationship.value!.status <= -100) | ||||
|                 Expanded( | ||||
|                   child: FilledButton.icon( | ||||
|                     style: ButtonStyle( | ||||
|                       backgroundColor: WidgetStatePropertyAll( | ||||
|                         accountRelationship.value == null | ||||
|                             ? null | ||||
|                             : Theme.of(context).colorScheme.secondary, | ||||
|                       ), | ||||
|                       foregroundColor: WidgetStatePropertyAll( | ||||
|                         accountRelationship.value == null | ||||
|                             ? null | ||||
|                             : Theme.of(context).colorScheme.onSecondary, | ||||
|                       ), | ||||
|                     ), | ||||
|                     onPressed: blockAction, | ||||
|                     label: | ||||
|                         Text( | ||||
|                           accountRelationship.value == null | ||||
|                               ? 'blockUser' | ||||
|                               : 'unblockUser', | ||||
|                         ).tr(), | ||||
|                     icon: | ||||
|                         accountRelationship.value == null | ||||
|                             ? const Icon(Symbols.block) | ||||
|                             : const Icon(Symbols.person_cancel), | ||||
|                   ), | ||||
|                 ), | ||||
|             ], | ||||
|           ), | ||||
|           Row( | ||||
|             spacing: 8, | ||||
|             children: [ | ||||
|               Expanded( | ||||
|                 child: FilledButton.icon( | ||||
|                   onPressed: directMessageAction, | ||||
|                   icon: const Icon(Symbols.message), | ||||
|                   label: | ||||
|                       Text( | ||||
|                         accountChat.value == null | ||||
|                             ? 'createDirectMessage' | ||||
|                             : 'gotoDirectMessage', | ||||
|                         maxLines: 1, | ||||
|                       ).tr(), | ||||
|                 ), | ||||
|               ), | ||||
|               IconButton.filled( | ||||
|                 onPressed: () { | ||||
|                   showAbuseReportSheet( | ||||
|                     context, | ||||
|                     resourceIdentifier: 'account/${data.id}', | ||||
|                   ); | ||||
|                 }, | ||||
|                 icon: Icon( | ||||
|                   Symbols.flag, | ||||
|                   color: Theme.of(context).colorScheme.onError, | ||||
|                 ), | ||||
|                 style: ButtonStyle( | ||||
|                   backgroundColor: WidgetStatePropertyAll( | ||||
|                     Theme.of(context).colorScheme.error, | ||||
|                   ), | ||||
|                 ), | ||||
|               ), | ||||
|             ], | ||||
|           ), | ||||
|         ], | ||||
|       ).padding(horizontal: 16, vertical: 12), | ||||
|     ); | ||||
|   } | ||||
| } | ||||
|  | ||||
| @riverpod | ||||
| Future<SnAccount> account(Ref ref, String uname) async { | ||||
|   if (uname == 'me') { | ||||
| @@ -217,351 +618,12 @@ class AccountProfileScreen extends HookConsumerWidget { | ||||
|       } | ||||
|     } | ||||
|  | ||||
|     List<Widget> buildSubcolumn(SnAccount data) { | ||||
|       return [ | ||||
|         Row( | ||||
|           spacing: 6, | ||||
|           children: [ | ||||
|             const Icon(Symbols.join, size: 17, fill: 1), | ||||
|             Text( | ||||
|               'joinedAt'.tr(args: [data.createdAt.formatCustom('yyyy-MM-dd')]), | ||||
|             ), | ||||
|           ], | ||||
|         ), | ||||
|         if (data.profile.birthday != null) | ||||
|           Row( | ||||
|             spacing: 6, | ||||
|             children: [ | ||||
|               const Icon(Symbols.cake, size: 17, fill: 1), | ||||
|               Text(data.profile.birthday!.formatCustom('yyyy-MM-dd')), | ||||
|               Text('·').bold(), | ||||
|               Text( | ||||
|                 '${DateTime.now().difference(data.profile.birthday!).inDays ~/ 365} yrs old', | ||||
|               ), | ||||
|             ], | ||||
|           ), | ||||
|         if (data.profile.location.isNotEmpty) | ||||
|           Row( | ||||
|             spacing: 6, | ||||
|             children: [ | ||||
|               const Icon(Symbols.location_on, size: 17, fill: 1), | ||||
|               Text(data.profile.location), | ||||
|             ], | ||||
|           ), | ||||
|         if (data.profile.pronouns.isNotEmpty || data.profile.gender.isNotEmpty) | ||||
|           Row( | ||||
|             spacing: 6, | ||||
|             children: [ | ||||
|               const Icon(Symbols.person, size: 17, fill: 1), | ||||
|               Text( | ||||
|                 data.profile.gender.isEmpty | ||||
|                     ? 'unspecified'.tr() | ||||
|                     : data.profile.gender, | ||||
|               ), | ||||
|               Text('·').bold(), | ||||
|               Text( | ||||
|                 data.profile.pronouns.isEmpty | ||||
|                     ? 'unspecified'.tr() | ||||
|                     : data.profile.pronouns, | ||||
|               ), | ||||
|             ], | ||||
|           ), | ||||
|         if (data.profile.firstName.isNotEmpty || | ||||
|             data.profile.middleName.isNotEmpty || | ||||
|             data.profile.lastName.isNotEmpty) | ||||
|           Row( | ||||
|             spacing: 6, | ||||
|             children: [ | ||||
|               const Icon(Symbols.id_card, size: 17, fill: 1), | ||||
|               if (data.profile.firstName.isNotEmpty) | ||||
|                 Text(data.profile.firstName), | ||||
|               if (data.profile.middleName.isNotEmpty) | ||||
|                 Text(data.profile.middleName), | ||||
|               if (data.profile.lastName.isNotEmpty) Text(data.profile.lastName), | ||||
|             ], | ||||
|           ), | ||||
|         Tooltip( | ||||
|           message: 'creditsStatus'.tr(), | ||||
|           child: Row( | ||||
|             spacing: 6, | ||||
|             children: [ | ||||
|               Icon(Symbols.star, size: 17, fill: 1).padding(right: 2), | ||||
|               Text('${data.profile.socialCredits.toStringAsFixed(2)} pts'), | ||||
|               Text('·').bold(), | ||||
|               switch (data.profile.socialCreditsLevel) { | ||||
|                 -1 => Text('socialCreditsLevelPoor').tr(), | ||||
|                 0 => Text('socialCreditsLevelNormal').tr(), | ||||
|                 1 => Text('socialCreditsLevelGood').tr(), | ||||
|                 2 => Text('socialCreditsLevelExcellent').tr(), | ||||
|                 _ => Text('unknown').tr(), | ||||
|               }, | ||||
|             ], | ||||
|           ), | ||||
|         ), | ||||
|         InkWell( | ||||
|           child: Row( | ||||
|             spacing: 6, | ||||
|             children: [ | ||||
|               Icon(Symbols.fingerprint, size: 17, fill: 1).padding(right: 2), | ||||
|               Text(data.id), | ||||
|             ], | ||||
|           ), | ||||
|           onTap: () { | ||||
|             Clipboard.setData(ClipboardData(text: data.id)); | ||||
|           }, | ||||
|         ), | ||||
|       ]; | ||||
|     } | ||||
|  | ||||
|     final user = ref.watch(userInfoProvider); | ||||
|     final isCurrentUser = useMemoized( | ||||
|       () => user.value?.id == account.value?.id, | ||||
|       [user, account], | ||||
|     ); | ||||
|  | ||||
|     Widget accountBasicInfo(SnAccount data) => Padding( | ||||
|       padding: const EdgeInsets.fromLTRB(24, 24, 24, 8), | ||||
|       child: Row( | ||||
|         crossAxisAlignment: CrossAxisAlignment.start, | ||||
|         children: [ | ||||
|           ProfilePictureWidget(file: data.profile.picture, radius: 32), | ||||
|           const Gap(20), | ||||
|           Expanded( | ||||
|             child: Column( | ||||
|               crossAxisAlignment: CrossAxisAlignment.stretch, | ||||
|               children: [ | ||||
|                 Row( | ||||
|                   children: [ | ||||
|                     AccountName(account: data, style: TextStyle(fontSize: 20)), | ||||
|                     const Gap(6), | ||||
|                     Flexible( | ||||
|                       child: Text( | ||||
|                         '@${data.name}', | ||||
|                         maxLines: 1, | ||||
|                         overflow: TextOverflow.ellipsis, | ||||
|                       ).fontSize(14).opacity(0.85), | ||||
|                     ), | ||||
|                   ], | ||||
|                 ), | ||||
|                 if (accountDeveloper.value != null) | ||||
|                   Row( | ||||
|                     spacing: 7, | ||||
|                     children: [ | ||||
|                       const Icon(Symbols.smart_toy, size: 18), | ||||
|                       Text( | ||||
|                         'botAutomatedBy'.tr( | ||||
|                           args: [accountDeveloper.value!.publisher!.nick], | ||||
|                         ), | ||||
|                       ).fontSize(13), | ||||
|                     ], | ||||
|                   ).opacity(0.75), | ||||
|                 const Gap(4), | ||||
|                 AccountStatusWidget(uname: name, padding: EdgeInsets.zero), | ||||
|               ], | ||||
|             ), | ||||
|           ), | ||||
|           IconButton( | ||||
|             onPressed: () { | ||||
|               SharePlus.instance.share( | ||||
|                 ShareParams( | ||||
|                   uri: Uri.parse('https://id.solian.app/@${data.name}'), | ||||
|                 ), | ||||
|               ); | ||||
|             }, | ||||
|             icon: const Icon(Symbols.share), | ||||
|           ), | ||||
|         ], | ||||
|       ), | ||||
|     ); | ||||
|  | ||||
|     Widget accountProfileBio(SnAccount data) => Card( | ||||
|       child: Column( | ||||
|         crossAxisAlignment: CrossAxisAlignment.start, | ||||
|         children: [ | ||||
|           Text('bio').tr().bold().fontSize(15).padding(bottom: 8), | ||||
|           if (data.profile.bio.isEmpty) | ||||
|             Text('descriptionNone').tr().italic() | ||||
|           else | ||||
|             MarkdownTextContent( | ||||
|               content: data.profile.bio, | ||||
|               linesMargin: EdgeInsets.zero, | ||||
|             ), | ||||
|         ], | ||||
|       ).padding(horizontal: 24, vertical: 20), | ||||
|     ); | ||||
|  | ||||
|     Widget accountProfileDetail(SnAccount data) => Card( | ||||
|       child: Column( | ||||
|         crossAxisAlignment: CrossAxisAlignment.stretch, | ||||
|         spacing: 24, | ||||
|         children: [ | ||||
|           if (buildSubcolumn(data).isNotEmpty) | ||||
|             Column( | ||||
|               crossAxisAlignment: CrossAxisAlignment.start, | ||||
|               spacing: 2, | ||||
|               children: buildSubcolumn(data), | ||||
|             ), | ||||
|           if (data.profile.timeZone.isNotEmpty && !kIsWeb) | ||||
|             Column( | ||||
|               crossAxisAlignment: CrossAxisAlignment.start, | ||||
|               children: [ | ||||
|                 Text('timeZone').tr().bold(), | ||||
|                 Row( | ||||
|                   crossAxisAlignment: CrossAxisAlignment.baseline, | ||||
|                   textBaseline: TextBaseline.alphabetic, | ||||
|                   spacing: 6, | ||||
|                   children: [ | ||||
|                     Text(data.profile.timeZone), | ||||
|                     Text( | ||||
|                       getTzInfo( | ||||
|                         data.profile.timeZone, | ||||
|                       ).$2.formatCustomGlobal('HH:mm'), | ||||
|                     ), | ||||
|                     Text( | ||||
|                       getTzInfo(data.profile.timeZone).$1.formatOffsetLocal(), | ||||
|                     ).fontSize(11), | ||||
|                     Text( | ||||
|                       'UTC${getTzInfo(data.profile.timeZone).$1.formatOffset()}', | ||||
|                     ).fontSize(11).opacity(0.75), | ||||
|                   ], | ||||
|                 ), | ||||
|               ], | ||||
|             ), | ||||
|         ], | ||||
|       ).padding(horizontal: 24, vertical: 16), | ||||
|     ); | ||||
|  | ||||
|     Widget accountProfileLinks(SnAccount data) => Card( | ||||
|       child: Column( | ||||
|         crossAxisAlignment: CrossAxisAlignment.start, | ||||
|         children: [ | ||||
|           Text('links').tr().bold().padding(horizontal: 24, top: 12, bottom: 4), | ||||
|           for (final link in data.profile.links) | ||||
|             ListTile( | ||||
|               title: Text(link.name.capitalizeEachWord()), | ||||
|               subtitle: Text(link.url), | ||||
|               contentPadding: EdgeInsets.symmetric(horizontal: 24), | ||||
|               trailing: const Icon(Symbols.chevron_right), | ||||
|               shape: RoundedRectangleBorder( | ||||
|                 borderRadius: const BorderRadius.all(Radius.circular(8)), | ||||
|               ), | ||||
|               onTap: () { | ||||
|                 if (!link.url.startsWith('http') && !link.url.contains('://')) { | ||||
|                   launchUrlString('https://${link.url}'); | ||||
|                 } else { | ||||
|                   launchUrlString(link.url); | ||||
|                 } | ||||
|               }, | ||||
|             ), | ||||
|         ], | ||||
|       ), | ||||
|     ); | ||||
|  | ||||
|     Widget accountAction(SnAccount data) => Card( | ||||
|       child: Column( | ||||
|         children: [ | ||||
|           Row( | ||||
|             spacing: 8, | ||||
|             children: [ | ||||
|               if (accountRelationship.value == null || | ||||
|                   accountRelationship.value!.status > -100) | ||||
|                 Expanded( | ||||
|                   child: FilledButton.icon( | ||||
|                     style: ButtonStyle( | ||||
|                       backgroundColor: WidgetStatePropertyAll( | ||||
|                         accountRelationship.value == null | ||||
|                             ? null | ||||
|                             : Theme.of(context).colorScheme.secondary, | ||||
|                       ), | ||||
|                       foregroundColor: WidgetStatePropertyAll( | ||||
|                         accountRelationship.value == null | ||||
|                             ? null | ||||
|                             : Theme.of(context).colorScheme.onSecondary, | ||||
|                       ), | ||||
|                     ), | ||||
|                     onPressed: relationshipAction, | ||||
|                     label: | ||||
|                         Text( | ||||
|                           accountRelationship.value == null | ||||
|                               ? 'addFriendShort' | ||||
|                               : 'added', | ||||
|                         ).tr(), | ||||
|                     icon: | ||||
|                         accountRelationship.value == null | ||||
|                             ? const Icon(Symbols.person_add) | ||||
|                             : const Icon(Symbols.person_check), | ||||
|                   ), | ||||
|                 ), | ||||
|               if (accountRelationship.value == null || | ||||
|                   accountRelationship.value!.status <= -100) | ||||
|                 Expanded( | ||||
|                   child: FilledButton.icon( | ||||
|                     style: ButtonStyle( | ||||
|                       backgroundColor: WidgetStatePropertyAll( | ||||
|                         accountRelationship.value == null | ||||
|                             ? null | ||||
|                             : Theme.of(context).colorScheme.secondary, | ||||
|                       ), | ||||
|                       foregroundColor: WidgetStatePropertyAll( | ||||
|                         accountRelationship.value == null | ||||
|                             ? null | ||||
|                             : Theme.of(context).colorScheme.onSecondary, | ||||
|                       ), | ||||
|                     ), | ||||
|                     onPressed: blockAction, | ||||
|                     label: | ||||
|                         Text( | ||||
|                           accountRelationship.value == null | ||||
|                               ? 'blockUser' | ||||
|                               : 'unblockUser', | ||||
|                         ).tr(), | ||||
|                     icon: | ||||
|                         accountRelationship.value == null | ||||
|                             ? const Icon(Symbols.block) | ||||
|                             : const Icon(Symbols.person_cancel), | ||||
|                   ), | ||||
|                 ), | ||||
|             ], | ||||
|           ), | ||||
|           Row( | ||||
|             spacing: 8, | ||||
|             children: [ | ||||
|               Expanded( | ||||
|                 child: FilledButton.icon( | ||||
|                   onPressed: directMessageAction, | ||||
|                   icon: const Icon(Symbols.message), | ||||
|                   label: | ||||
|                       Text( | ||||
|                         accountChat.value == null | ||||
|                             ? 'createDirectMessage' | ||||
|                             : 'gotoDirectMessage', | ||||
|                         maxLines: 1, | ||||
|                       ).tr(), | ||||
|                 ), | ||||
|               ), | ||||
|               IconButton.filled( | ||||
|                 onPressed: () { | ||||
|                   showAbuseReportSheet( | ||||
|                     context, | ||||
|                     resourceIdentifier: 'account/${data.id}', | ||||
|                   ); | ||||
|                 }, | ||||
|                 icon: Icon( | ||||
|                   Symbols.flag, | ||||
|                   color: Theme.of(context).colorScheme.onError, | ||||
|                 ), | ||||
|                 style: ButtonStyle( | ||||
|                   backgroundColor: WidgetStatePropertyAll( | ||||
|                     Theme.of(context).colorScheme.error, | ||||
|                   ), | ||||
|                 ), | ||||
|               ), | ||||
|             ], | ||||
|           ), | ||||
|         ], | ||||
|       ).padding(horizontal: 16, vertical: 12), | ||||
|     ); | ||||
|  | ||||
|     return account.when( | ||||
|       data: | ||||
|           (data) => AppScaffold( | ||||
| @@ -613,7 +675,13 @@ class AccountProfileScreen extends HookConsumerWidget { | ||||
|                         Flexible( | ||||
|                           child: CustomScrollView( | ||||
|                             slivers: [ | ||||
|                               SliverToBoxAdapter(child: accountBasicInfo(data)), | ||||
|                               SliverToBoxAdapter( | ||||
|                                 child: _AccountBasicInfo( | ||||
|                                   data: data, | ||||
|                                   uname: name, | ||||
|                                   accountDeveloper: accountDeveloper, | ||||
|                                 ), | ||||
|                               ), | ||||
|                               if (data.badges.isNotEmpty) | ||||
|                                 SliverToBoxAdapter( | ||||
|                                   child: Card( | ||||
| @@ -642,14 +710,16 @@ class AccountProfileScreen extends HookConsumerWidget { | ||||
|                                 ).padding(horizontal: 4, top: 8), | ||||
|                               ), | ||||
|                               SliverToBoxAdapter( | ||||
|                                 child: accountProfileBio(data).padding(top: 4), | ||||
|                                 child: _AccountProfileBio( | ||||
|                                   data: data, | ||||
|                                 ).padding(top: 4), | ||||
|                               ), | ||||
|                               if (data.profile.links.isNotEmpty) | ||||
|                                 SliverToBoxAdapter( | ||||
|                                   child: accountProfileLinks(data), | ||||
|                                   child: _AccountProfileLinks(data: data), | ||||
|                                 ), | ||||
|                               SliverToBoxAdapter( | ||||
|                                 child: accountProfileDetail(data), | ||||
|                                 child: _AccountProfileDetail(data: data), | ||||
|                               ), | ||||
|                             ], | ||||
|                           ), | ||||
| @@ -659,7 +729,16 @@ class AccountProfileScreen extends HookConsumerWidget { | ||||
|                             slivers: [ | ||||
|                               SliverGap(24), | ||||
|                               if (user.value != null && !isCurrentUser) | ||||
|                                 SliverToBoxAdapter(child: accountAction(data)), | ||||
|                                 SliverToBoxAdapter( | ||||
|                                   child: _AccountAction( | ||||
|                                     data: data, | ||||
|                                     accountRelationship: accountRelationship, | ||||
|                                     accountChat: accountChat, | ||||
|                                     relationshipAction: relationshipAction, | ||||
|                                     blockAction: blockAction, | ||||
|                                     directMessageAction: directMessageAction, | ||||
|                                   ), | ||||
|                                 ), | ||||
|                               SliverToBoxAdapter( | ||||
|                                 child: Card( | ||||
|                                   child: FortuneGraphWidget( | ||||
| @@ -715,7 +794,13 @@ class AccountProfileScreen extends HookConsumerWidget { | ||||
|                             ], | ||||
|                           ), | ||||
|                         ), | ||||
|                         SliverToBoxAdapter(child: accountBasicInfo(data)), | ||||
|                         SliverToBoxAdapter( | ||||
|                           child: _AccountBasicInfo( | ||||
|                             data: data, | ||||
|                             uname: name, | ||||
|                             accountDeveloper: accountDeveloper, | ||||
|                           ), | ||||
|                         ), | ||||
|                         if (data.badges.isNotEmpty) | ||||
|                           SliverToBoxAdapter( | ||||
|                             child: Card( | ||||
| @@ -742,22 +827,31 @@ class AccountProfileScreen extends HookConsumerWidget { | ||||
|                           ), | ||||
|                         ), | ||||
|                         SliverToBoxAdapter( | ||||
|                           child: accountProfileBio(data).padding(horizontal: 4), | ||||
|                           child: _AccountProfileBio( | ||||
|                             data: data, | ||||
|                           ).padding(horizontal: 4), | ||||
|                         ), | ||||
|                         if (data.profile.links.isNotEmpty) | ||||
|                           SliverToBoxAdapter( | ||||
|                             child: accountProfileLinks( | ||||
|                               data, | ||||
|                             child: _AccountProfileLinks( | ||||
|                               data: data, | ||||
|                             ).padding(horizontal: 4), | ||||
|                           ), | ||||
|                         SliverToBoxAdapter( | ||||
|                           child: accountProfileDetail( | ||||
|                             data, | ||||
|                           child: _AccountProfileDetail( | ||||
|                             data: data, | ||||
|                           ).padding(horizontal: 4), | ||||
|                         ), | ||||
|                         if (user.value != null && !isCurrentUser) | ||||
|                           SliverToBoxAdapter( | ||||
|                             child: accountAction(data).padding(horizontal: 4), | ||||
|                             child: _AccountAction( | ||||
|                               data: data, | ||||
|                               accountRelationship: accountRelationship, | ||||
|                               accountChat: accountChat, | ||||
|                               relationshipAction: relationshipAction, | ||||
|                               blockAction: blockAction, | ||||
|                               directMessageAction: directMessageAction, | ||||
|                             ).padding(horizontal: 4), | ||||
|                           ), | ||||
|                         SliverToBoxAdapter( | ||||
|                           child: Card( | ||||
|   | ||||
| @@ -27,112 +27,24 @@ import 'package:styled_widget/styled_widget.dart'; | ||||
|  | ||||
| part 'pub_profile.g.dart'; | ||||
|  | ||||
| @riverpod | ||||
| Future<SnPublisher> publisher(Ref ref, String uname) async { | ||||
|   final apiClient = ref.watch(apiClientProvider); | ||||
|   final resp = await apiClient.get("/sphere/publishers/$uname"); | ||||
|   return SnPublisher.fromJson(resp.data); | ||||
| } | ||||
| class _PublisherBasisWidget extends StatelessWidget { | ||||
|   final SnPublisher data; | ||||
|   final AsyncValue<SnSubscriptionStatus> subStatus; | ||||
|   final ValueNotifier<bool> subscribing; | ||||
|   final VoidCallback subscribe; | ||||
|   final VoidCallback unsubscribe; | ||||
|  | ||||
| @riverpod | ||||
| Future<List<SnAccountBadge>> publisherBadges(Ref ref, String pubName) async { | ||||
|   final pub = await ref.watch(publisherProvider(pubName).future); | ||||
|   if (pub.type != 0 || pub.account == null) return []; | ||||
|   final apiClient = ref.watch(apiClientProvider); | ||||
|   final resp = await apiClient.get("/id/accounts/${pub.account!.name}/badges"); | ||||
|   return List<SnAccountBadge>.from( | ||||
|     resp.data.map((x) => SnAccountBadge.fromJson(x)), | ||||
|   ); | ||||
| } | ||||
|  | ||||
| @riverpod | ||||
| Future<SnSubscriptionStatus> publisherSubscriptionStatus( | ||||
|   Ref ref, | ||||
|   String pubName, | ||||
| ) async { | ||||
|   final apiClient = ref.watch(apiClientProvider); | ||||
|   final resp = await apiClient.get("/sphere/publishers/$pubName/subscription"); | ||||
|   return SnSubscriptionStatus.fromJson(resp.data); | ||||
| } | ||||
|  | ||||
| @riverpod | ||||
| Future<Color?> publisherAppbarForcegroundColor(Ref ref, String pubName) async { | ||||
|   try { | ||||
|     final publisher = await ref.watch(publisherProvider(pubName).future); | ||||
|     if (publisher.background == null) return null; | ||||
|     final palette = await PaletteGenerator.fromImageProvider( | ||||
|       CloudImageWidget.provider( | ||||
|         fileId: publisher.background!.id, | ||||
|         serverUrl: ref.watch(serverUrlProvider), | ||||
|       ), | ||||
|     ); | ||||
|     final dominantColor = palette.dominantColor?.color; | ||||
|     if (dominantColor == null) return null; | ||||
|     return dominantColor.computeLuminance() > 0.5 ? Colors.black : Colors.white; | ||||
|   } catch (_) { | ||||
|     return null; | ||||
|   } | ||||
| } | ||||
|  | ||||
| class PublisherProfileScreen extends HookConsumerWidget { | ||||
|   final String name; | ||||
|   const PublisherProfileScreen({super.key, required this.name}); | ||||
|  | ||||
|   @override | ||||
|   Widget build(BuildContext context, WidgetRef ref) { | ||||
|     final publisher = ref.watch(publisherProvider(name)); | ||||
|     final badges = ref.watch(publisherBadgesProvider(name)); | ||||
|     final subStatus = ref.watch(publisherSubscriptionStatusProvider(name)); | ||||
|     final appbarColor = ref.watch( | ||||
|       publisherAppbarForcegroundColorProvider(name), | ||||
|     ); | ||||
|  | ||||
|     final categoryTabController = useTabController(initialLength: 3); | ||||
|     final categoryTab = useState(0); | ||||
|     categoryTabController.addListener(() { | ||||
|       categoryTab.value = categoryTabController.index; | ||||
|   const _PublisherBasisWidget({ | ||||
|     required this.data, | ||||
|     required this.subStatus, | ||||
|     required this.subscribing, | ||||
|     required this.subscribe, | ||||
|     required this.unsubscribe, | ||||
|   }); | ||||
|  | ||||
|     final subscribing = useState(false); | ||||
|  | ||||
|     Future<void> subscribe() async { | ||||
|       final apiClient = ref.watch(apiClientProvider); | ||||
|       subscribing.value = true; | ||||
|       try { | ||||
|         await apiClient.post( | ||||
|           "/sphere/publishers/$name/subscribe", | ||||
|           data: {'tier': 0}, | ||||
|         ); | ||||
|         ref.invalidate(publisherSubscriptionStatusProvider(name)); | ||||
|         HapticFeedback.heavyImpact(); | ||||
|       } catch (err) { | ||||
|         showErrorAlert(err); | ||||
|       } finally { | ||||
|         subscribing.value = false; | ||||
|       } | ||||
|     } | ||||
|  | ||||
|     Future<void> unsubscribe() async { | ||||
|       final apiClient = ref.watch(apiClientProvider); | ||||
|       subscribing.value = true; | ||||
|       try { | ||||
|         await apiClient.post("/sphere/publishers/$name/unsubscribe"); | ||||
|         ref.invalidate(publisherSubscriptionStatusProvider(name)); | ||||
|         HapticFeedback.heavyImpact(); | ||||
|       } catch (err) { | ||||
|         showErrorAlert(err); | ||||
|       } finally { | ||||
|         subscribing.value = false; | ||||
|       } | ||||
|     } | ||||
|  | ||||
|     final appbarShadow = Shadow( | ||||
|       color: appbarColor.value?.invert ?? Colors.transparent, | ||||
|       blurRadius: 5.0, | ||||
|       offset: Offset(1.0, 1.0), | ||||
|     ); | ||||
|  | ||||
|     Widget publisherBasisWidget(SnPublisher data) => Row( | ||||
|   @override | ||||
|   Widget build(BuildContext context) { | ||||
|     return Row( | ||||
|       crossAxisAlignment: CrossAxisAlignment.start, | ||||
|       spacing: 20, | ||||
|       children: [ | ||||
| @@ -247,25 +159,51 @@ class PublisherProfileScreen extends HookConsumerWidget { | ||||
|         ), | ||||
|       ], | ||||
|     ).padding(horizontal: 24, top: 24); | ||||
|   } | ||||
| } | ||||
|  | ||||
|     Widget publisherBadgesWidget(SnPublisher data) => | ||||
|         (badges.value?.isNotEmpty ?? false) | ||||
| class _PublisherBadgesWidget extends StatelessWidget { | ||||
|   final SnPublisher data; | ||||
|   final AsyncValue<List<SnAccountBadge>> badges; | ||||
|  | ||||
|   const _PublisherBadgesWidget({required this.data, required this.badges}); | ||||
|  | ||||
|   @override | ||||
|   Widget build(BuildContext context) { | ||||
|     return (badges.value?.isNotEmpty ?? false) | ||||
|         ? Card( | ||||
|           child: BadgeList( | ||||
|             badges: badges.value!, | ||||
|           ).padding(horizontal: 26, vertical: 20), | ||||
|         ).padding(horizontal: 4) | ||||
|         : const SizedBox.shrink(); | ||||
|   } | ||||
| } | ||||
|  | ||||
|     Widget publisherVerificationWidget(SnPublisher data) => | ||||
|         (data.verification != null) | ||||
| class _PublisherVerificationWidget extends StatelessWidget { | ||||
|   final SnPublisher data; | ||||
|  | ||||
|   const _PublisherVerificationWidget({required this.data}); | ||||
|  | ||||
|   @override | ||||
|   Widget build(BuildContext context) { | ||||
|     return (data.verification != null) | ||||
|         ? Card( | ||||
|           margin: EdgeInsets.symmetric(horizontal: 8, vertical: 4), | ||||
|           child: VerificationStatusCard(mark: data.verification!), | ||||
|         ) | ||||
|         : const SizedBox.shrink(); | ||||
|   } | ||||
| } | ||||
|  | ||||
|     Widget publisherBioWidget(SnPublisher data) => Card( | ||||
| class _PublisherBioWidget extends StatelessWidget { | ||||
|   final SnPublisher data; | ||||
|  | ||||
|   const _PublisherBioWidget({required this.data}); | ||||
|  | ||||
|   @override | ||||
|   Widget build(BuildContext context) { | ||||
|     return Card( | ||||
|       margin: EdgeInsets.symmetric(horizontal: 8, vertical: 4), | ||||
|       child: Column( | ||||
|         crossAxisAlignment: CrossAxisAlignment.stretch, | ||||
| @@ -281,8 +219,17 @@ class PublisherProfileScreen extends HookConsumerWidget { | ||||
|         ], | ||||
|       ).padding(horizontal: 20, vertical: 16), | ||||
|     ); | ||||
|   } | ||||
| } | ||||
|  | ||||
|     Widget publisherCategoryTabWidget() => Card( | ||||
| class _PublisherCategoryTabWidget extends StatelessWidget { | ||||
|   final TabController categoryTabController; | ||||
|  | ||||
|   const _PublisherCategoryTabWidget({required this.categoryTabController}); | ||||
|  | ||||
|   @override | ||||
|   Widget build(BuildContext context) { | ||||
|     return Card( | ||||
|       margin: EdgeInsets.symmetric(horizontal: 8, vertical: 4), | ||||
|       child: TabBar( | ||||
|         controller: categoryTabController, | ||||
| @@ -295,6 +242,113 @@ class PublisherProfileScreen extends HookConsumerWidget { | ||||
|         ], | ||||
|       ), | ||||
|     ); | ||||
|   } | ||||
| } | ||||
|  | ||||
| @riverpod | ||||
| Future<SnPublisher> publisher(Ref ref, String uname) async { | ||||
|   final apiClient = ref.watch(apiClientProvider); | ||||
|   final resp = await apiClient.get("/sphere/publishers/$uname"); | ||||
|   return SnPublisher.fromJson(resp.data); | ||||
| } | ||||
|  | ||||
| @riverpod | ||||
| Future<List<SnAccountBadge>> publisherBadges(Ref ref, String pubName) async { | ||||
|   final pub = await ref.watch(publisherProvider(pubName).future); | ||||
|   if (pub.type != 0 || pub.account == null) return []; | ||||
|   final apiClient = ref.watch(apiClientProvider); | ||||
|   final resp = await apiClient.get("/id/accounts/${pub.account!.name}/badges"); | ||||
|   return List<SnAccountBadge>.from( | ||||
|     resp.data.map((x) => SnAccountBadge.fromJson(x)), | ||||
|   ); | ||||
| } | ||||
|  | ||||
| @riverpod | ||||
| Future<SnSubscriptionStatus> publisherSubscriptionStatus( | ||||
|   Ref ref, | ||||
|   String pubName, | ||||
| ) async { | ||||
|   final apiClient = ref.watch(apiClientProvider); | ||||
|   final resp = await apiClient.get("/sphere/publishers/$pubName/subscription"); | ||||
|   return SnSubscriptionStatus.fromJson(resp.data); | ||||
| } | ||||
|  | ||||
| @riverpod | ||||
| Future<Color?> publisherAppbarForcegroundColor(Ref ref, String pubName) async { | ||||
|   try { | ||||
|     final publisher = await ref.watch(publisherProvider(pubName).future); | ||||
|     if (publisher.background == null) return null; | ||||
|     final palette = await PaletteGenerator.fromImageProvider( | ||||
|       CloudImageWidget.provider( | ||||
|         fileId: publisher.background!.id, | ||||
|         serverUrl: ref.watch(serverUrlProvider), | ||||
|       ), | ||||
|     ); | ||||
|     final dominantColor = palette.dominantColor?.color; | ||||
|     if (dominantColor == null) return null; | ||||
|     return dominantColor.computeLuminance() > 0.5 ? Colors.black : Colors.white; | ||||
|   } catch (_) { | ||||
|     return null; | ||||
|   } | ||||
| } | ||||
|  | ||||
| class PublisherProfileScreen extends HookConsumerWidget { | ||||
|   final String name; | ||||
|   const PublisherProfileScreen({super.key, required this.name}); | ||||
|  | ||||
|   @override | ||||
|   Widget build(BuildContext context, WidgetRef ref) { | ||||
|     final publisher = ref.watch(publisherProvider(name)); | ||||
|     final badges = ref.watch(publisherBadgesProvider(name)); | ||||
|     final subStatus = ref.watch(publisherSubscriptionStatusProvider(name)); | ||||
|     final appbarColor = ref.watch( | ||||
|       publisherAppbarForcegroundColorProvider(name), | ||||
|     ); | ||||
|  | ||||
|     final categoryTabController = useTabController(initialLength: 3); | ||||
|     final categoryTab = useState(0); | ||||
|     categoryTabController.addListener(() { | ||||
|       categoryTab.value = categoryTabController.index; | ||||
|     }); | ||||
|  | ||||
|     final subscribing = useState(false); | ||||
|  | ||||
|     Future<void> subscribe() async { | ||||
|       final apiClient = ref.watch(apiClientProvider); | ||||
|       subscribing.value = true; | ||||
|       try { | ||||
|         await apiClient.post( | ||||
|           "/sphere/publishers/$name/subscribe", | ||||
|           data: {'tier': 0}, | ||||
|         ); | ||||
|         ref.invalidate(publisherSubscriptionStatusProvider(name)); | ||||
|         HapticFeedback.heavyImpact(); | ||||
|       } catch (err) { | ||||
|         showErrorAlert(err); | ||||
|       } finally { | ||||
|         subscribing.value = false; | ||||
|       } | ||||
|     } | ||||
|  | ||||
|     Future<void> unsubscribe() async { | ||||
|       final apiClient = ref.watch(apiClientProvider); | ||||
|       subscribing.value = true; | ||||
|       try { | ||||
|         await apiClient.post("/sphere/publishers/$name/unsubscribe"); | ||||
|         ref.invalidate(publisherSubscriptionStatusProvider(name)); | ||||
|         HapticFeedback.heavyImpact(); | ||||
|       } catch (err) { | ||||
|         showErrorAlert(err); | ||||
|       } finally { | ||||
|         subscribing.value = false; | ||||
|       } | ||||
|     } | ||||
|  | ||||
|     final appbarShadow = Shadow( | ||||
|       color: appbarColor.value?.invert ?? Colors.transparent, | ||||
|       blurRadius: 5.0, | ||||
|       offset: Offset(1.0, 1.0), | ||||
|     ); | ||||
|  | ||||
|     return publisher.when( | ||||
|       data: | ||||
| @@ -351,7 +405,9 @@ class PublisherProfileScreen extends HookConsumerWidget { | ||||
|                               SliverGap(16), | ||||
|                               SliverPostList(pubName: name, pinned: true), | ||||
|                               SliverToBoxAdapter( | ||||
|                                 child: publisherCategoryTabWidget(), | ||||
|                                 child: _PublisherCategoryTabWidget( | ||||
|                                   categoryTabController: categoryTabController, | ||||
|                                 ), | ||||
|                               ), | ||||
|                               SliverPostList( | ||||
|                                 key: ValueKey(categoryTab.value), | ||||
| @@ -377,10 +433,19 @@ class PublisherProfileScreen extends HookConsumerWidget { | ||||
|                               child: Column( | ||||
|                                 crossAxisAlignment: CrossAxisAlignment.stretch, | ||||
|                                 children: [ | ||||
|                                   publisherBasisWidget(data).padding(bottom: 8), | ||||
|                                   publisherBadgesWidget(data), | ||||
|                                   publisherVerificationWidget(data), | ||||
|                                   publisherBioWidget(data), | ||||
|                                   _PublisherBasisWidget( | ||||
|                                     data: data, | ||||
|                                     subStatus: subStatus, | ||||
|                                     subscribing: subscribing, | ||||
|                                     subscribe: subscribe, | ||||
|                                     unsubscribe: unsubscribe, | ||||
|                                   ).padding(bottom: 8), | ||||
|                                   _PublisherBadgesWidget( | ||||
|                                     data: data, | ||||
|                                     badges: badges, | ||||
|                                   ), | ||||
|                                   _PublisherVerificationWidget(data: data), | ||||
|                                   _PublisherBioWidget(data: data), | ||||
|                                 ], | ||||
|                               ), | ||||
|                             ), | ||||
| @@ -432,15 +497,32 @@ class PublisherProfileScreen extends HookConsumerWidget { | ||||
|                           ), | ||||
|                         ), | ||||
|                         SliverToBoxAdapter( | ||||
|                           child: publisherBasisWidget(data).padding(bottom: 8), | ||||
|                           child: _PublisherBasisWidget( | ||||
|                             data: data, | ||||
|                             subStatus: subStatus, | ||||
|                             subscribing: subscribing, | ||||
|                             subscribe: subscribe, | ||||
|                             unsubscribe: unsubscribe, | ||||
|                           ).padding(bottom: 8), | ||||
|                         ), | ||||
|                         SliverToBoxAdapter(child: publisherBadgesWidget(data)), | ||||
|                         SliverToBoxAdapter( | ||||
|                           child: publisherVerificationWidget(data), | ||||
|                           child: _PublisherBadgesWidget( | ||||
|                             data: data, | ||||
|                             badges: badges, | ||||
|                           ), | ||||
|                         ), | ||||
|                         SliverToBoxAdapter( | ||||
|                           child: _PublisherVerificationWidget(data: data), | ||||
|                         ), | ||||
|                         SliverToBoxAdapter( | ||||
|                           child: _PublisherBioWidget(data: data), | ||||
|                         ), | ||||
|                         SliverToBoxAdapter(child: publisherBioWidget(data)), | ||||
|                         SliverPostList(pubName: name, pinned: true), | ||||
|                         SliverToBoxAdapter(child: publisherCategoryTabWidget()), | ||||
|                         SliverToBoxAdapter( | ||||
|                           child: _PublisherCategoryTabWidget( | ||||
|                             categoryTabController: categoryTabController, | ||||
|                           ), | ||||
|                         ), | ||||
|                         SliverPostList( | ||||
|                           key: ValueKey(categoryTab.value), | ||||
|                           pubName: name, | ||||
|   | ||||
		Reference in New Issue
	
	Block a user