💄 Optimization styles

This commit is contained in:
2025-08-04 18:20:13 +08:00
parent 1aa45dd9f1
commit ba269dbbb8
5 changed files with 171 additions and 188 deletions

View File

@@ -18,6 +18,7 @@ import 'package:island/widgets/account/status.dart';
import 'package:island/widgets/alert.dart'; import 'package:island/widgets/alert.dart';
import 'package:island/widgets/app_scaffold.dart'; import 'package:island/widgets/app_scaffold.dart';
import 'package:island/widgets/content/cloud_files.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:island/widgets/post/post_list.dart';
import 'package:material_symbols_icons/symbols.dart'; import 'package:material_symbols_icons/symbols.dart';
import 'package:palette_generator/palette_generator.dart'; import 'package:palette_generator/palette_generator.dart';
@@ -233,25 +234,36 @@ class PublisherProfileScreen extends HookConsumerWidget {
], ],
).padding(horizontal: 24, top: 24); ).padding(horizontal: 24, top: 24);
Widget publisherVerificationWidget(SnPublisher data) => Card( Widget publisherBadgesWidget(SnPublisher data) =>
margin: EdgeInsets.symmetric(horizontal: 8, vertical: 4), (badges.value?.isNotEmpty ?? false)
child: Column( ? Card(
children: [ child: BadgeList(
if (badges.value?.isNotEmpty ?? false) badges: badges.value!,
BadgeList(badges: badges.value!).padding(top: 16), ).padding(horizontal: 26, vertical: 20),
if (data.verification != null) ).padding(horizontal: 4)
VerificationStatusCard(mark: data.verification!), : const SizedBox.shrink();
],
),
).padding(top: 16);
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), margin: EdgeInsets.symmetric(horizontal: 8, vertical: 4),
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch, crossAxisAlignment: CrossAxisAlignment.stretch,
children: [ children: [
Text('bio').tr().bold().padding(bottom: 2), Text('bio').tr().bold().fontSize(15).padding(bottom: 8),
Text(data.bio.isEmpty ? 'descriptionNone'.tr() : data.bio), if (data.bio.isEmpty)
Text('descriptionNone').tr().italic()
else
MarkdownTextContent(
content: data.bio,
linesMargin: EdgeInsets.zero,
),
], ],
).padding(horizontal: 20, vertical: 16), ).padding(horizontal: 20, vertical: 16),
); );
@@ -325,8 +337,9 @@ class PublisherProfileScreen extends HookConsumerWidget {
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
publisherBasisWidget(data), publisherBasisWidget(data),
publisherBadgesWidget(data),
publisherVerificationWidget(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( SliverToBoxAdapter(
child: publisherVerificationWidget(data), child: publisherVerificationWidget(data),
), ),
SliverToBoxAdapter(child: publisherDetailWidget(data)), SliverToBoxAdapter(child: publisherBioWidget(data)),
SliverPostList(pubName: name), SliverPostList(pubName: name),
SliverGap(MediaQuery.of(context).padding.bottom + 16), SliverGap(MediaQuery.of(context).padding.bottom + 16),
], ],

View File

@@ -32,12 +32,12 @@ class BadgeItem extends StatelessWidget {
child: Container( child: Container(
padding: const EdgeInsets.all(4), padding: const EdgeInsets.all(4),
decoration: BoxDecoration( decoration: BoxDecoration(
color: (template?.color ?? Colors.blue).withOpacity(0.1), color: (template?.color ?? Colors.blue).withOpacity(0.2),
shape: BoxShape.circle, shape: BoxShape.circle,
), ),
child: Icon( child: Icon(
template?.icon ?? Icons.stars, template?.icon ?? Icons.stars,
color: template?.color ?? Colors.orange, color: template?.color ?? Colors.blue,
size: 20, size: 20,
), ),
), ),

View File

@@ -86,6 +86,7 @@ class AccountStatusCreationWidget extends HookConsumerWidget {
onTap: () { onTap: () {
showModalBottomSheet( showModalBottomSheet(
context: context, context: context,
isScrollControlled: true,
useRootNavigator: true, useRootNavigator: true,
builder: builder:
(context) => AccountStatusCreationSheet( (context) => AccountStatusCreationSheet(

View File

@@ -9,6 +9,7 @@ import 'package:island/pods/network.dart';
import 'package:island/pods/userinfo.dart'; import 'package:island/pods/userinfo.dart';
import 'package:island/widgets/account/status.dart'; import 'package:island/widgets/account/status.dart';
import 'package:island/widgets/alert.dart'; import 'package:island/widgets/alert.dart';
import 'package:island/widgets/content/sheet.dart';
import 'package:material_symbols_icons/symbols.dart'; import 'package:material_symbols_icons/symbols.dart';
class AccountStatusCreationSheet extends HookConsumerWidget { class AccountStatusCreationSheet extends HookConsumerWidget {
@@ -71,178 +72,145 @@ class AccountStatusCreationSheet extends HookConsumerWidget {
} }
} }
return Container( return SheetScaffold(
constraints: BoxConstraints( heightFactor: 0.6,
maxHeight: MediaQuery.of(context).size.height * 0.8, titleText:
), initialStatus == null ? 'statusCreate'.tr() : 'statusUpdate'.tr(),
child: Column( actions: [
children: [ TextButton.icon(
Padding( onPressed:
padding: EdgeInsets.only(top: 16, left: 20, right: 16, bottom: 12), submitting.value
child: Row( ? null
children: [ : () {
Text( submitStatus();
initialStatus == null },
? 'statusCreate'.tr() icon: const Icon(Symbols.upload),
: 'statusUpdate'.tr(), label: Text(initialStatus == null ? 'create' : 'update').tr(),
style: Theme.of(context).textTheme.headlineSmall?.copyWith( style: ButtonStyle(
fontWeight: FontWeight.w600, visualDensity: VisualDensity(
letterSpacing: -0.5, 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( onTapOutside:
onPressed: (_) => FocusManager.instance.primaryFocus?.unfocus(),
submitting.value ),
? null const SizedBox(height: 24),
: () { Text(
submitStatus(); 'statusAttitude'.tr(),
}, style: Theme.of(context).textTheme.titleMedium,
icon: const Icon(Symbols.upload), ),
label: Text(initialStatus == null ? 'create' : 'update').tr(), const SizedBox(height: 8),
style: ButtonStyle( SegmentedButton(
visualDensity: VisualDensity( segments: [
horizontal: VisualDensity.minimumDensity, ButtonSegment(
), value: 0,
foregroundColor: WidgetStatePropertyAll( icon: const Icon(Symbols.sentiment_satisfied),
Theme.of(context).colorScheme.onSurface, label: Text('attitudePositive'.tr()),
),
),
), ),
if (initialStatus != null) ButtonSegment(
IconButton( value: 1,
icon: const Icon(Symbols.delete), icon: const Icon(Symbols.sentiment_stressed),
onPressed: submitting.value ? null : () => clearStatus(), label: Text('attitudeNeutral'.tr()),
style: IconButton.styleFrom( ),
minimumSize: const Size(36, 36), ButtonSegment(
), value: 2,
), icon: const Icon(Symbols.sentiment_sad),
IconButton( label: Text('attitudeNegative'.tr()),
icon: const Icon(Symbols.close),
onPressed: () => Navigator.pop(context),
style: IconButton.styleFrom(minimumSize: const Size(36, 36)),
), ),
], ],
selected: {attitude.value},
onSelectionChanged: (Set<int> newSelection) {
attitude.value = newSelection.first;
},
), ),
), const Gap(12),
const Divider(height: 1), SwitchListTile(
Expanded( title: Text('statusInvisible'.tr()),
child: SingleChildScrollView( subtitle: Text('statusInvisibleDescription'.tr()),
padding: const EdgeInsets.symmetric(horizontal: 20), value: isInvisible.value,
child: Column( contentPadding: EdgeInsets.symmetric(horizontal: 8),
crossAxisAlignment: CrossAxisAlignment.start, onChanged: (bool value) {
children: [ isInvisible.value = value;
const Gap(24), },
TextField( ),
controller: labelController, SwitchListTile(
decoration: InputDecoration( title: Text('statusNotDisturb'.tr()),
labelText: 'statusLabel'.tr(), subtitle: Text('statusNotDisturbDescription'.tr()),
border: const OutlineInputBorder( value: isNotDisturb.value,
borderRadius: BorderRadius.all(Radius.circular(12)), contentPadding: EdgeInsets.symmetric(horizontal: 8),
), onChanged: (bool value) {
), isNotDisturb.value = value;
onTapOutside: },
(_) => FocusManager.instance.primaryFocus?.unfocus(), ),
), const SizedBox(height: 24),
const SizedBox(height: 24), Text(
Text( 'statusClearTime'.tr(),
'statusAttitude'.tr(), style: Theme.of(context).textTheme.titleMedium,
style: Theme.of(context).textTheme.titleMedium, ),
), const SizedBox(height: 8),
const SizedBox(height: 8), ListTile(
SegmentedButton( title: Text(
segments: [ clearedAt.value == null
ButtonSegment( ? 'statusNoAutoClear'.tr()
value: 0, : DateFormat.yMMMd().add_jm().format(clearedAt.value!),
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<int> 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),
],
), ),
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),
], ],
),
), ),
); );
} }

View File

@@ -670,11 +670,9 @@ class ComposeLogic {
// Send request // Send request
await client.request( await client.request(
endpoint, endpoint,
queryParameters: {'pub': state.currentPublisher.value?.name},
data: payload, data: payload,
options: Options( options: Options(method: isNewPost ? 'POST' : 'PATCH'),
headers: {'X-Pub': state.currentPublisher.value?.name},
method: isNewPost ? 'POST' : 'PATCH',
),
); );
// Delete draft after successful submission // Delete draft after successful submission