diff --git a/lib/screens/posts/pub_profile.dart b/lib/screens/posts/pub_profile.dart index a19f0e2..f422c31 100644 --- a/lib/screens/posts/pub_profile.dart +++ b/lib/screens/posts/pub_profile.dart @@ -18,6 +18,7 @@ import 'package:island/widgets/account/status.dart'; import 'package:island/widgets/alert.dart'; import 'package:island/widgets/app_scaffold.dart'; import 'package:island/widgets/content/cloud_files.dart'; +import 'package:island/widgets/content/markdown.dart'; import 'package:island/widgets/post/post_list.dart'; import 'package:material_symbols_icons/symbols.dart'; import 'package:palette_generator/palette_generator.dart'; @@ -233,25 +234,36 @@ class PublisherProfileScreen extends HookConsumerWidget { ], ).padding(horizontal: 24, top: 24); - Widget publisherVerificationWidget(SnPublisher data) => Card( - margin: EdgeInsets.symmetric(horizontal: 8, vertical: 4), - child: Column( - children: [ - if (badges.value?.isNotEmpty ?? false) - BadgeList(badges: badges.value!).padding(top: 16), - if (data.verification != null) - VerificationStatusCard(mark: data.verification!), - ], - ), - ).padding(top: 16); + Widget publisherBadgesWidget(SnPublisher data) => + (badges.value?.isNotEmpty ?? false) + ? Card( + child: BadgeList( + badges: badges.value!, + ).padding(horizontal: 26, vertical: 20), + ).padding(horizontal: 4) + : const SizedBox.shrink(); - Widget publisherDetailWidget(SnPublisher data) => Card( + Widget publisherVerificationWidget(SnPublisher data) => + (data.verification != null) + ? Card( + margin: EdgeInsets.symmetric(horizontal: 8, vertical: 4), + child: VerificationStatusCard(mark: data.verification!), + ) + : const SizedBox.shrink(); + + Widget publisherBioWidget(SnPublisher data) => Card( margin: EdgeInsets.symmetric(horizontal: 8, vertical: 4), child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ - Text('bio').tr().bold().padding(bottom: 2), - Text(data.bio.isEmpty ? 'descriptionNone'.tr() : data.bio), + Text('bio').tr().bold().fontSize(15).padding(bottom: 8), + if (data.bio.isEmpty) + Text('descriptionNone').tr().italic() + else + MarkdownTextContent( + content: data.bio, + linesMargin: EdgeInsets.zero, + ), ], ).padding(horizontal: 20, vertical: 16), ); @@ -325,8 +337,9 @@ class PublisherProfileScreen extends HookConsumerWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ publisherBasisWidget(data), + publisherBadgesWidget(data), publisherVerificationWidget(data), - publisherDetailWidget(data), + publisherBioWidget(data), ], ), ), @@ -377,11 +390,14 @@ class PublisherProfileScreen extends HookConsumerWidget { ], ), ), - SliverToBoxAdapter(child: publisherBasisWidget(data)), + SliverToBoxAdapter( + child: publisherBasisWidget(data).padding(bottom: 8), + ), + SliverToBoxAdapter(child: publisherBadgesWidget(data)), SliverToBoxAdapter( child: publisherVerificationWidget(data), ), - SliverToBoxAdapter(child: publisherDetailWidget(data)), + SliverToBoxAdapter(child: publisherBioWidget(data)), SliverPostList(pubName: name), SliverGap(MediaQuery.of(context).padding.bottom + 16), ], diff --git a/lib/widgets/account/badge.dart b/lib/widgets/account/badge.dart index 8fe5ccd..e3911e4 100644 --- a/lib/widgets/account/badge.dart +++ b/lib/widgets/account/badge.dart @@ -32,12 +32,12 @@ class BadgeItem extends StatelessWidget { child: Container( padding: const EdgeInsets.all(4), decoration: BoxDecoration( - color: (template?.color ?? Colors.blue).withOpacity(0.1), + color: (template?.color ?? Colors.blue).withOpacity(0.2), shape: BoxShape.circle, ), child: Icon( template?.icon ?? Icons.stars, - color: template?.color ?? Colors.orange, + color: template?.color ?? Colors.blue, size: 20, ), ), diff --git a/lib/widgets/account/status.dart b/lib/widgets/account/status.dart index e070aa3..92d30bb 100644 --- a/lib/widgets/account/status.dart +++ b/lib/widgets/account/status.dart @@ -86,6 +86,7 @@ class AccountStatusCreationWidget extends HookConsumerWidget { onTap: () { showModalBottomSheet( context: context, + isScrollControlled: true, useRootNavigator: true, builder: (context) => AccountStatusCreationSheet( diff --git a/lib/widgets/account/status_creation.dart b/lib/widgets/account/status_creation.dart index 3f5120a..551e7b7 100644 --- a/lib/widgets/account/status_creation.dart +++ b/lib/widgets/account/status_creation.dart @@ -9,6 +9,7 @@ import 'package:island/pods/network.dart'; import 'package:island/pods/userinfo.dart'; import 'package:island/widgets/account/status.dart'; import 'package:island/widgets/alert.dart'; +import 'package:island/widgets/content/sheet.dart'; import 'package:material_symbols_icons/symbols.dart'; class AccountStatusCreationSheet extends HookConsumerWidget { @@ -71,178 +72,145 @@ class AccountStatusCreationSheet extends HookConsumerWidget { } } - return Container( - constraints: BoxConstraints( - maxHeight: MediaQuery.of(context).size.height * 0.8, - ), - child: Column( - children: [ - Padding( - padding: EdgeInsets.only(top: 16, left: 20, right: 16, bottom: 12), - child: Row( - children: [ - Text( - initialStatus == null - ? 'statusCreate'.tr() - : 'statusUpdate'.tr(), - style: Theme.of(context).textTheme.headlineSmall?.copyWith( - fontWeight: FontWeight.w600, - letterSpacing: -0.5, - ), + return SheetScaffold( + heightFactor: 0.6, + titleText: + initialStatus == null ? 'statusCreate'.tr() : 'statusUpdate'.tr(), + actions: [ + TextButton.icon( + onPressed: + submitting.value + ? null + : () { + submitStatus(); + }, + icon: const Icon(Symbols.upload), + label: Text(initialStatus == null ? 'create' : 'update').tr(), + style: ButtonStyle( + visualDensity: VisualDensity( + horizontal: VisualDensity.minimumDensity, + ), + foregroundColor: WidgetStatePropertyAll( + Theme.of(context).colorScheme.onSurface, + ), + ), + ), + if (initialStatus != null) + IconButton( + icon: const Icon(Symbols.delete), + onPressed: submitting.value ? null : () => clearStatus(), + style: IconButton.styleFrom(minimumSize: const Size(36, 36)), + ), + ], + child: SingleChildScrollView( + padding: const EdgeInsets.symmetric(horizontal: 20), + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + const Gap(24), + TextField( + controller: labelController, + decoration: InputDecoration( + labelText: 'statusLabel'.tr(), + border: const OutlineInputBorder( + borderRadius: BorderRadius.all(Radius.circular(12)), ), - const Spacer(), - TextButton.icon( - onPressed: - submitting.value - ? null - : () { - submitStatus(); - }, - icon: const Icon(Symbols.upload), - label: Text(initialStatus == null ? 'create' : 'update').tr(), - style: ButtonStyle( - visualDensity: VisualDensity( - horizontal: VisualDensity.minimumDensity, - ), - foregroundColor: WidgetStatePropertyAll( - Theme.of(context).colorScheme.onSurface, - ), - ), + ), + onTapOutside: + (_) => FocusManager.instance.primaryFocus?.unfocus(), + ), + const SizedBox(height: 24), + Text( + 'statusAttitude'.tr(), + style: Theme.of(context).textTheme.titleMedium, + ), + const SizedBox(height: 8), + SegmentedButton( + segments: [ + ButtonSegment( + value: 0, + icon: const Icon(Symbols.sentiment_satisfied), + label: Text('attitudePositive'.tr()), ), - if (initialStatus != null) - IconButton( - icon: const Icon(Symbols.delete), - onPressed: submitting.value ? null : () => clearStatus(), - style: IconButton.styleFrom( - minimumSize: const Size(36, 36), - ), - ), - IconButton( - icon: const Icon(Symbols.close), - onPressed: () => Navigator.pop(context), - style: IconButton.styleFrom(minimumSize: const Size(36, 36)), + ButtonSegment( + value: 1, + icon: const Icon(Symbols.sentiment_stressed), + label: Text('attitudeNeutral'.tr()), + ), + ButtonSegment( + value: 2, + icon: const Icon(Symbols.sentiment_sad), + label: Text('attitudeNegative'.tr()), ), ], + selected: {attitude.value}, + onSelectionChanged: (Set newSelection) { + attitude.value = newSelection.first; + }, ), - ), - const Divider(height: 1), - Expanded( - child: SingleChildScrollView( - padding: const EdgeInsets.symmetric(horizontal: 20), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - const Gap(24), - TextField( - controller: labelController, - decoration: InputDecoration( - labelText: 'statusLabel'.tr(), - border: const OutlineInputBorder( - borderRadius: BorderRadius.all(Radius.circular(12)), - ), - ), - onTapOutside: - (_) => FocusManager.instance.primaryFocus?.unfocus(), - ), - const SizedBox(height: 24), - Text( - 'statusAttitude'.tr(), - style: Theme.of(context).textTheme.titleMedium, - ), - const SizedBox(height: 8), - SegmentedButton( - segments: [ - ButtonSegment( - value: 0, - icon: const Icon(Symbols.sentiment_satisfied), - label: Text('attitudePositive'.tr()), - ), - ButtonSegment( - value: 1, - icon: const Icon(Symbols.sentiment_stressed), - label: Text('attitudeNeutral'.tr()), - ), - ButtonSegment( - value: 2, - icon: const Icon(Symbols.sentiment_sad), - label: Text('attitudeNegative'.tr()), - ), - ], - selected: {attitude.value}, - onSelectionChanged: (Set newSelection) { - attitude.value = newSelection.first; - }, - ), - const Gap(12), - SwitchListTile( - title: Text('statusInvisible'.tr()), - subtitle: Text('statusInvisibleDescription'.tr()), - value: isInvisible.value, - contentPadding: EdgeInsets.symmetric(horizontal: 8), - onChanged: (bool value) { - isInvisible.value = value; - }, - ), - SwitchListTile( - title: Text('statusNotDisturb'.tr()), - subtitle: Text('statusNotDisturbDescription'.tr()), - value: isNotDisturb.value, - contentPadding: EdgeInsets.symmetric(horizontal: 8), - onChanged: (bool value) { - isNotDisturb.value = value; - }, - ), - const SizedBox(height: 24), - Text( - 'statusClearTime'.tr(), - style: Theme.of(context).textTheme.titleMedium, - ), - const SizedBox(height: 8), - ListTile( - title: Text( - clearedAt.value == null - ? 'statusNoAutoClear'.tr() - : DateFormat.yMMMd().add_jm().format( - clearedAt.value!, - ), - ), - trailing: const Icon(Symbols.schedule), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(8), - side: BorderSide( - color: Theme.of(context).colorScheme.outline, - ), - ), - onTap: () async { - final now = DateTime.now(); - final date = await showDatePicker( - context: context, - initialDate: now, - firstDate: now, - lastDate: now.add(const Duration(days: 365)), - ); - if (date == null) return; - if (!context.mounted) return; - final time = await showTimePicker( - context: context, - initialTime: TimeOfDay.now(), - ); - if (time == null) return; - clearedAt.value = DateTime( - date.year, - date.month, - date.day, - time.hour, - time.minute, - ); - }, - ), - Gap(MediaQuery.of(context).padding.bottom + 24), - ], + const Gap(12), + SwitchListTile( + title: Text('statusInvisible'.tr()), + subtitle: Text('statusInvisibleDescription'.tr()), + value: isInvisible.value, + contentPadding: EdgeInsets.symmetric(horizontal: 8), + onChanged: (bool value) { + isInvisible.value = value; + }, + ), + SwitchListTile( + title: Text('statusNotDisturb'.tr()), + subtitle: Text('statusNotDisturbDescription'.tr()), + value: isNotDisturb.value, + contentPadding: EdgeInsets.symmetric(horizontal: 8), + onChanged: (bool value) { + isNotDisturb.value = value; + }, + ), + const SizedBox(height: 24), + Text( + 'statusClearTime'.tr(), + style: Theme.of(context).textTheme.titleMedium, + ), + const SizedBox(height: 8), + ListTile( + title: Text( + clearedAt.value == null + ? 'statusNoAutoClear'.tr() + : DateFormat.yMMMd().add_jm().format(clearedAt.value!), ), + trailing: const Icon(Symbols.schedule), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8), + side: BorderSide(color: Theme.of(context).colorScheme.outline), + ), + onTap: () async { + final now = DateTime.now(); + final date = await showDatePicker( + context: context, + initialDate: now, + firstDate: now, + lastDate: now.add(const Duration(days: 365)), + ); + if (date == null) return; + if (!context.mounted) return; + final time = await showTimePicker( + context: context, + initialTime: TimeOfDay.now(), + ); + if (time == null) return; + clearedAt.value = DateTime( + date.year, + date.month, + date.day, + time.hour, + time.minute, + ); + }, ), - ), - ], + Gap(MediaQuery.of(context).padding.bottom + 24), + ], + ), ), ); } diff --git a/lib/widgets/post/compose_shared.dart b/lib/widgets/post/compose_shared.dart index fc260de..ab306ea 100644 --- a/lib/widgets/post/compose_shared.dart +++ b/lib/widgets/post/compose_shared.dart @@ -670,11 +670,9 @@ class ComposeLogic { // Send request await client.request( endpoint, + queryParameters: {'pub': state.currentPublisher.value?.name}, data: payload, - options: Options( - headers: {'X-Pub': state.currentPublisher.value?.name}, - method: isNewPost ? 'POST' : 'PATCH', - ), + options: Options(method: isNewPost ? 'POST' : 'PATCH'), ); // Delete draft after successful submission