🐛 Fix some bugs in creator hub

This commit is contained in:
2025-12-06 21:26:00 +08:00
parent 7516e197fe
commit 9c370647dd
5 changed files with 430 additions and 439 deletions

View File

@@ -5,16 +5,14 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:island/models/webfeed.dart'; import 'package:island/models/webfeed.dart';
import 'package:island/pods/network.dart'; import 'package:island/pods/network.dart';
final webFeedListProvider = FutureProvider.family<List<SnWebFeed>, String>(( final webFeedListProvider = FutureProvider.autoDispose
ref, .family<List<SnWebFeed>, String>((ref, pubName) async {
pubName, final client = ref.watch(apiClientProvider);
) async { final response = await client.get('/sphere/publishers/$pubName/feeds');
final client = ref.watch(apiClientProvider); return (response.data as List)
final response = await client.get('/sphere/publishers/$pubName/feeds'); .map((json) => SnWebFeed.fromJson(json))
return (response.data as List) .toList();
.map((json) => SnWebFeed.fromJson(json)) });
.toList();
});
class WebFeedNotifier extends AsyncNotifier<SnWebFeed> { class WebFeedNotifier extends AsyncNotifier<SnWebFeed> {
final ({String pubName, String? feedId}) arg; final ({String pubName, String? feedId}) arg;
@@ -51,10 +49,9 @@ class WebFeedNotifier extends AsyncNotifier<SnWebFeed> {
final client = ref.read(apiClientProvider); final client = ref.read(apiClientProvider);
final url = '/sphere/publishers/${feed.publisherId}/feeds'; final url = '/sphere/publishers/${feed.publisherId}/feeds';
final response = final response = feed.id.isEmpty
feed.id.isEmpty ? await client.post(url, data: feed.toJson())
? await client.post(url, data: feed.toJson()) : await client.patch('$url/${feed.id}', data: feed.toJson());
: await client.patch('$url/${feed.id}', data: feed.toJson());
state = AsyncValue.data(SnWebFeed.fromJson(response.data)); state = AsyncValue.data(SnWebFeed.fromJson(response.data));
} catch (error, stackTrace) { } catch (error, stackTrace) {

View File

@@ -20,6 +20,7 @@ import 'package:island/widgets/navigation/fab_menu.dart';
import 'package:island/widgets/response.dart'; import 'package:island/widgets/response.dart';
import 'package:material_symbols_icons/symbols.dart'; import 'package:material_symbols_icons/symbols.dart';
import 'package:relative_time/relative_time.dart'; import 'package:relative_time/relative_time.dart';
import 'package:skeletonizer/skeletonizer.dart';
import 'package:styled_widget/styled_widget.dart'; import 'package:styled_widget/styled_widget.dart';
import 'package:super_sliver_list/super_sliver_list.dart'; import 'package:super_sliver_list/super_sliver_list.dart';
import 'package:island/pods/chat/chat_room.dart'; import 'package:island/pods/chat/chat_room.dart';
@@ -50,84 +51,124 @@ class ChatRoomListTile extends HookConsumerWidget {
if (validMembers.isNotEmpty) { if (validMembers.isNotEmpty) {
final userInfo = ref.watch(userInfoProvider); final userInfo = ref.watch(userInfoProvider);
if (userInfo.value != null) { if (userInfo.value != null) {
validMembers = validMembers = validMembers
validMembers .where((e) => e.accountId != userInfo.value!.id)
.where((e) => e.accountId != userInfo.value!.id) .toList();
.toList();
} }
} }
Widget buildSubtitle() { Widget buildSubtitle() {
if (subtitle != null) return subtitle!; if (subtitle != null) return subtitle!;
return summary.when( return AnimatedSwitcher(
data: (data) { duration: const Duration(milliseconds: 300),
if (data == null) { layoutBuilder: (currentChild, previousChildren) => Stack(
return isDirect && room.description == null alignment: Alignment.centerLeft,
? Text( children: [
validMembers.map((e) => '@${e.account.name}').join(', '), ...previousChildren,
maxLines: 1, if (currentChild != null) currentChild,
) ],
: Text(room.description ?? 'descriptionNone'.tr(), maxLines: 1); ),
} child: summary.when(
data: (data) => Container(
return Column( key: const ValueKey('data'),
crossAxisAlignment: CrossAxisAlignment.stretch, child: data == null
children: [ ? isDirect && room.description == null
if (data.unreadCount > 0) ? Text(
Text( validMembers
'unreadMessages'.plural(data.unreadCount), .map((e) => '@${e.account.name}')
style: Theme.of(context).textTheme.bodySmall?.copyWith( .join(', '),
color: Theme.of(context).colorScheme.primary, maxLines: 1,
)
: Text(
room.description ?? 'descriptionNone'.tr(),
maxLines: 1,
)
: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
if (data.unreadCount > 0)
Text(
'unreadMessages'.plural(data.unreadCount),
style: Theme.of(context).textTheme.bodySmall
?.copyWith(
color: Theme.of(context).colorScheme.primary,
),
),
if (data.lastMessage == null)
Text(
room.description ?? 'descriptionNone'.tr(),
maxLines: 1,
)
else
Row(
spacing: 4,
children: [
Badge(
label: Text(
data.lastMessage!.sender.account.nick,
),
textColor: Theme.of(
context,
).colorScheme.onPrimary,
backgroundColor: Theme.of(
context,
).colorScheme.primary,
),
Expanded(
child: Text(
(data.lastMessage!.content?.isNotEmpty ?? false)
? data.lastMessage!.content!
: 'messageNone'.tr(),
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: Theme.of(context).textTheme.bodySmall,
),
),
Align(
alignment: Alignment.centerRight,
child: Text(
RelativeTime(
context,
).format(data.lastMessage!.createdAt),
style: Theme.of(context).textTheme.bodySmall,
),
),
],
),
],
), ),
), ),
if (data.lastMessage == null) loading: () => Container(
Text(room.description ?? 'descriptionNone'.tr(), maxLines: 1) key: const ValueKey('loading'),
else child: Builder(
Row( builder: (context) {
spacing: 4, final seed = DateTime.now().microsecondsSinceEpoch;
children: [ final len = 4 + (seed % 17); // 4..20 inclusive
Badge( const chars = 'abcdefghijklmnopqrstuvwxyz0123456789';
label: Text(data.lastMessage!.sender.account.nick), var s = seed;
textColor: Theme.of(context).colorScheme.onPrimary, final buffer = StringBuffer();
backgroundColor: Theme.of(context).colorScheme.primary, for (var i = 0; i < len; i++) {
), s = (s * 1103515245 + 12345) & 0x7fffffff;
Expanded( buffer.write(chars[s % chars.length]);
child: Text( }
(data.lastMessage!.content?.isNotEmpty ?? false) return Skeletonizer(
? data.lastMessage!.content! enabled: true,
: 'messageNone'.tr(), child: Text(buffer.toString()),
maxLines: 1, );
overflow: TextOverflow.ellipsis, },
style: Theme.of(context).textTheme.bodySmall, ),
), ),
), error: (_, _) => Container(
Align( key: const ValueKey('error'),
alignment: Alignment.centerRight, child: isDirect && room.description == null
child: Text( ? Text(
RelativeTime( validMembers.map((e) => '@${e.account.name}').join(', '),
context, maxLines: 1,
).format(data.lastMessage!.createdAt), )
style: Theme.of(context).textTheme.bodySmall, : Text(room.description ?? 'descriptionNone'.tr(), maxLines: 1),
), ),
), ),
],
),
],
);
},
loading: () => const SizedBox.shrink(),
error:
(_, _) =>
isDirect && room.description == null
? Text(
validMembers.map((e) => '@${e.account.name}').join(', '),
maxLines: 1,
)
: Text(
room.description ?? 'descriptionNone'.tr(),
maxLines: 1,
),
); );
} }
@@ -149,17 +190,15 @@ class ChatRoomListTile extends HookConsumerWidget {
loading: () => false, loading: () => false,
error: (_, _) => false, error: (_, _) => false,
), ),
child: child: (isDirect && room.picture?.id == null)
(isDirect && room.picture?.id == null) ? SplitAvatarWidget(
? SplitAvatarWidget( filesId: validMembers
filesId: .map((e) => e.account.profile.picture?.id)
validMembers .toList(),
.map((e) => e.account.profile.picture?.id) )
.toList(), : room.picture?.id == null
) ? CircleAvatar(child: Text(room.name![0].toUpperCase()))
: room.picture?.id == null : ProfilePictureWidget(fileId: room.picture?.id),
? CircleAvatar(child: Text(room.name![0].toUpperCase()))
: ProfilePictureWidget(fileId: room.picture?.id),
), ),
title: Text(titleText), title: Text(titleText),
subtitle: buildSubtitle(), subtitle: buildSubtitle(),
@@ -199,74 +238,67 @@ class ChatListBodyWidget extends HookConsumerWidget {
builder: (context, ref, _) { builder: (context, ref, _) {
final summaryState = ref.watch(chatSummaryProvider); final summaryState = ref.watch(chatSummaryProvider);
return summaryState.maybeWhen( return summaryState.maybeWhen(
loading: loading: () => const LinearProgressIndicator(
() => const LinearProgressIndicator( minHeight: 2,
minHeight: 2, borderRadius: BorderRadius.zero,
borderRadius: BorderRadius.zero, ),
),
orElse: () => const SizedBox.shrink(), orElse: () => const SizedBox.shrink(),
); );
}, },
), ),
Expanded( Expanded(
child: chats.when( child: chats.when(
data: data: (items) => RefreshIndicator(
(items) => RefreshIndicator( onRefresh: () => Future.sync(() {
onRefresh: ref.invalidate(chatRoomJoinedProvider);
() => Future.sync(() { }),
ref.invalidate(chatRoomJoinedProvider); child: SuperListView.builder(
}), padding: EdgeInsets.only(bottom: 96),
child: SuperListView.builder( itemCount: items
padding: EdgeInsets.only(bottom: 96), .where(
itemCount: (item) =>
items selectedTab.value == 0 ||
.where( (selectedTab.value == 1 && item.type == 1) ||
(item) => (selectedTab.value == 2 && item.type != 1),
selectedTab.value == 0 || )
(selectedTab.value == 1 && item.type == 1) || .length,
(selectedTab.value == 2 && item.type != 1), itemBuilder: (context, index) {
) final filteredItems = items
.length, .where(
itemBuilder: (context, index) { (item) =>
final filteredItems = selectedTab.value == 0 ||
items (selectedTab.value == 1 && item.type == 1) ||
.where( (selectedTab.value == 2 && item.type != 1),
(item) => )
selectedTab.value == 0 || .toList();
(selectedTab.value == 1 && final item = filteredItems[index];
item.type == 1) || return ChatRoomListTile(
(selectedTab.value == 2 && item.type != 1), room: item,
) isDirect: item.type == 1,
.toList(); onTap: () {
final item = filteredItems[index]; if (isWideScreen(context)) {
return ChatRoomListTile( context.replaceNamed(
room: item, 'chatRoom',
isDirect: item.type == 1, pathParameters: {'id': item.id},
onTap: () { );
if (isWideScreen(context)) { } else {
context.replaceNamed( context.pushNamed(
'chatRoom', 'chatRoom',
pathParameters: {'id': item.id}, pathParameters: {'id': item.id},
); );
} else { }
context.pushNamed(
'chatRoom',
pathParameters: {'id': item.id},
);
}
},
);
}, },
), );
), },
),
),
loading: () => const Center(child: CircularProgressIndicator()), loading: () => const Center(child: CircularProgressIndicator()),
error: error: (error, stack) => ResponseErrorWidget(
(error, stack) => ResponseErrorWidget( error: error,
error: error, onRetry: () {
onRetry: () { ref.invalidate(chatRoomJoinedProvider);
ref.invalidate(chatRoomJoinedProvider); },
}, ),
),
), ),
), ),
], ],
@@ -552,53 +584,47 @@ class _ChatInvitesSheet extends HookConsumerWidget {
), ),
], ],
child: invites.when( child: invites.when(
data: data: (items) => items.isEmpty
(items) => ? Center(
items.isEmpty child: Text('invitesEmpty', textAlign: TextAlign.center).tr(),
? Center( )
child: : ListView.builder(
Text( shrinkWrap: true,
'invitesEmpty', itemCount: items.length,
textAlign: TextAlign.center, itemBuilder: (context, index) {
).tr(), final invite = items[index];
) return ChatRoomListTile(
: ListView.builder( room: invite.chatRoom!,
shrinkWrap: true, isDirect: invite.chatRoom!.type == 1,
itemCount: items.length, subtitle: Row(
itemBuilder: (context, index) { spacing: 6,
final invite = items[index]; children: [
return ChatRoomListTile( if (invite.chatRoom!.type == 1)
room: invite.chatRoom!, Badge(
isDirect: invite.chatRoom!.type == 1, label: const Text('directMessage').tr(),
subtitle: Row( backgroundColor: Theme.of(
spacing: 6, context,
children: [ ).colorScheme.primary,
if (invite.chatRoom!.type == 1) textColor: Theme.of(context).colorScheme.onPrimary,
Badge(
label: const Text('directMessage').tr(),
backgroundColor:
Theme.of(context).colorScheme.primary,
textColor:
Theme.of(context).colorScheme.onPrimary,
),
],
), ),
trailing: Row( ],
mainAxisSize: MainAxisSize.min,
children: [
IconButton(
icon: const Icon(Symbols.check),
onPressed: () => acceptInvite(invite),
),
IconButton(
icon: const Icon(Symbols.close),
onPressed: () => declineInvite(invite),
),
],
),
);
},
), ),
trailing: Row(
mainAxisSize: MainAxisSize.min,
children: [
IconButton(
icon: const Icon(Symbols.check),
onPressed: () => acceptInvite(invite),
),
IconButton(
icon: const Icon(Symbols.close),
onPressed: () => declineInvite(invite),
),
],
),
);
},
),
loading: () => const Center(child: CircularProgressIndicator()), loading: () => const Center(child: CircularProgressIndicator()),
error: (error, stack) => Center(child: Text('Error: $error')), error: (error, stack) => Center(child: Text('Error: $error')),
), ),

View File

@@ -44,11 +44,10 @@ class PollListNotifier extends AsyncNotifier<List<SnPollWithStats>>
queryParameters: queryParams, queryParameters: queryParams,
); );
totalCount = int.parse(response.headers.value('X-Total') ?? '0'); totalCount = int.parse(response.headers.value('X-Total') ?? '0');
final items = final items = response.data
response.data .map((json) => SnPollWithStats.fromJson(json))
.map((json) => SnPollWithStats.fromJson(json)) .cast<SnPollWithStats>()
.cast<SnPollWithStats>() .toList();
.toList();
return items; return items;
} }
@@ -91,6 +90,7 @@ class CreatorPollListScreen extends HookConsumerWidget {
body: ExtendedRefreshIndicator( body: ExtendedRefreshIndicator(
onRefresh: () => ref.refresh(pollListNotifierProvider(pubName).future), onRefresh: () => ref.refresh(pollListNotifierProvider(pubName).future),
child: PaginationList( child: PaginationList(
footerSkeletonMaxWidth: 640,
provider: pollListNotifierProvider(pubName), provider: pollListNotifierProvider(pubName),
notifier: pollListNotifierProvider(pubName).notifier, notifier: pollListNotifierProvider(pubName).notifier,
padding: const EdgeInsets.only(top: 12), padding: const EdgeInsets.only(top: 12),
@@ -119,10 +119,9 @@ class _CreatorPollItem extends HookConsumerWidget {
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
final theme = Theme.of(context); final theme = Theme.of(context);
final ended = pollWithStats.endedAt; final ended = pollWithStats.endedAt;
final endedText = final endedText = ended == null
ended == null ? 'No end'
? 'No end' : MaterialLocalizations.of(context).formatFullDate(ended);
: MaterialLocalizations.of(context).formatFullDate(ended);
return Card( return Card(
margin: const EdgeInsets.symmetric(horizontal: 12, vertical: 6), margin: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
@@ -152,78 +151,69 @@ class _CreatorPollItem extends HookConsumerWidget {
], ],
), ),
trailing: PopupMenuButton<String>( trailing: PopupMenuButton<String>(
itemBuilder: itemBuilder: (context) => [
(context) => [ PopupMenuItem(
PopupMenuItem( child: Row(
child: Row( children: [
children: [ const Icon(Symbols.edit),
const Icon(Symbols.edit), const Gap(16),
const Gap(16), Text('edit').tr(),
Text('edit').tr(), ],
),
onTap: () async {
final result = await showModalBottomSheet<SnPoll>(
context: context,
isScrollControlled: true,
isDismissible: false,
builder: (context) => PollEditorScreen(
initialPublisher: pubName,
initialPollId: pollWithStats.id,
),
);
if (result != null && context.mounted) {
ref.invalidate(pollListNotifierProvider(pubName));
}
},
),
PopupMenuItem(
child: Row(
children: [
const Icon(Symbols.delete, color: Colors.red),
const Gap(16),
Text('delete').tr().textColor(Colors.red),
],
),
onTap: () async {
final confirmed = await showDialog<bool>(
context: context,
builder: (context) => AlertDialog(
title: Text('Delete Poll'),
content: Text('Are you sure you want to delete this poll?'),
actions: [
TextButton(
onPressed: () => Navigator.of(context).pop(false),
child: Text('Cancel'),
),
TextButton(
onPressed: () => Navigator.of(context).pop(true),
child: Text('Delete'),
),
], ],
), ),
onTap: () async { );
final result = await showModalBottomSheet<SnPoll>( if (confirmed == true) {
context: context, try {
isScrollControlled: true, final client = ref.read(apiClientProvider);
isDismissible: false, await client.delete('/sphere/polls/${pollWithStats.id}');
builder: ref.invalidate(pollListNotifierProvider(pubName));
(context) => PollEditorScreen( showSnackBar('Poll deleted successfully');
initialPublisher: pubName, } catch (e) {
initialPollId: pollWithStats.id, showErrorAlert(e);
), }
); }
if (result != null && context.mounted) { },
ref.invalidate(pollListNotifierProvider(pubName)); ),
} ],
},
),
PopupMenuItem(
child: Row(
children: [
const Icon(Symbols.delete, color: Colors.red),
const Gap(16),
Text('delete').tr().textColor(Colors.red),
],
),
onTap: () async {
final confirmed = await showDialog<bool>(
context: context,
builder:
(context) => AlertDialog(
title: Text('Delete Poll'),
content: Text(
'Are you sure you want to delete this poll?',
),
actions: [
TextButton(
onPressed:
() => Navigator.of(context).pop(false),
child: Text('Cancel'),
),
TextButton(
onPressed:
() => Navigator.of(context).pop(true),
child: Text('Delete'),
),
],
),
);
if (confirmed == true) {
try {
final client = ref.read(apiClientProvider);
await client.delete(
'/sphere/polls/${pollWithStats.id}',
);
ref.invalidate(pollListNotifierProvider(pubName));
showSnackBar('Poll deleted successfully');
} catch (e) {
showErrorAlert(e);
}
}
},
),
],
), ),
onTap: () { onTap: () {
showModalBottomSheet( showModalBottomSheet(

View File

@@ -12,7 +12,6 @@ import 'package:island/widgets/alert.dart';
import 'package:island/widgets/app_scaffold.dart'; import 'package:island/widgets/app_scaffold.dart';
import 'package:island/widgets/paging/pagination_list.dart'; import 'package:island/widgets/paging/pagination_list.dart';
import 'package:material_symbols_icons/symbols.dart'; import 'package:material_symbols_icons/symbols.dart';
import 'package:island/widgets/extended_refresh_indicator.dart';
import 'package:styled_widget/styled_widget.dart'; import 'package:styled_widget/styled_widget.dart';
final siteListNotifierProvider = AsyncNotifierProvider.family.autoDispose( final siteListNotifierProvider = AsyncNotifierProvider.family.autoDispose(
@@ -38,11 +37,10 @@ class SiteListNotifier extends AsyncNotifier<List<SnPublicationSite>>
queryParameters: queryParams, queryParameters: queryParams,
); );
totalCount = int.parse(response.headers.value('X-Total') ?? '0'); totalCount = int.parse(response.headers.value('X-Total') ?? '0');
final items = final items = response.data
response.data .map((json) => SnPublicationSite.fromJson(json))
.map((json) => SnPublicationSite.fromJson(json)) .cast<SnPublicationSite>()
.cast<SnPublicationSite>() .toList();
.toList();
return items; return items;
} }
@@ -70,23 +68,17 @@ class CreatorSiteListScreen extends HookConsumerWidget {
onPressed: () => _createSite(context), onPressed: () => _createSite(context),
child: Icon(Icons.add), child: Icon(Icons.add),
), ),
body: ExtendedRefreshIndicator( body: PaginationList(
onRefresh: () => ref.refresh(siteListNotifierProvider(pubName).future), footerSkeletonMaxWidth: 640,
child: CustomScrollView( provider: siteListNotifierProvider(pubName),
slivers: [ notifier: siteListNotifierProvider(pubName).notifier,
const SliverGap(8), padding: const EdgeInsets.only(top: 12),
PaginationList( itemBuilder: (context, index, site) {
provider: siteListNotifierProvider(pubName), return ConstrainedBox(
notifier: siteListNotifierProvider(pubName).notifier, constraints: BoxConstraints(maxWidth: 640),
itemBuilder: (context, index, site) { child: _CreatorSiteItem(site: site, pubName: pubName),
return ConstrainedBox( ).center();
constraints: BoxConstraints(maxWidth: 640), },
child: _CreatorSiteItem(site: site, pubName: pubName),
).center();
},
),
],
),
), ),
); );
} }
@@ -148,73 +140,65 @@ class _CreatorSiteItem extends HookConsumerWidget {
), ),
), ),
PopupMenuButton<String>( PopupMenuButton<String>(
itemBuilder: itemBuilder: (context) => [
(context) => [ PopupMenuItem(
PopupMenuItem( child: Row(
child: Row( children: [
children: [ const Icon(Symbols.edit),
const Icon(Symbols.edit), const Gap(16),
const Gap(16), Text('edit').tr(),
Text('edit').tr(), ],
),
onTap: () {
showModalBottomSheet(
context: context,
isScrollControlled: true,
builder: (context) =>
SiteForm(pubName: pubName, siteSlug: site.slug),
);
},
),
PopupMenuItem(
child: Row(
children: [
const Icon(Symbols.delete, color: Colors.red),
const Gap(16),
Text('delete').tr().textColor(Colors.red),
],
),
onTap: () async {
final confirmed = await showDialog<bool>(
context: context,
builder: (context) => AlertDialog(
title: Text('deleteSite'.tr()),
content: Text('deleteSiteConfirm'.tr()),
actions: [
TextButton(
onPressed: () => Navigator.of(context).pop(false),
child: Text('cancel'.tr()),
),
TextButton(
onPressed: () => Navigator.of(context).pop(true),
child: Text('delete'.tr()),
),
], ],
), ),
onTap: () { );
showModalBottomSheet( if (confirmed == true) {
context: context, try {
isScrollControlled: true, final client = ref.read(apiClientProvider);
builder: await client.delete(
(context) => SiteForm( '/zone/sites/$pubName/${site.slug}',
pubName: pubName,
siteSlug: site.slug,
),
); );
}, ref.invalidate(siteListNotifierProvider(pubName));
), showSnackBar('siteDeletedSuccess'.tr());
PopupMenuItem( } catch (e) {
child: Row( showErrorAlert(e);
children: [ }
const Icon(Symbols.delete, color: Colors.red), }
const Gap(16), },
Text('delete').tr().textColor(Colors.red), ),
], ],
),
onTap: () async {
final confirmed = await showDialog<bool>(
context: context,
builder:
(context) => AlertDialog(
title: Text('deleteSite'.tr()),
content: Text('deleteSiteConfirm'.tr()),
actions: [
TextButton(
onPressed:
() =>
Navigator.of(context).pop(false),
child: Text('cancel'.tr()),
),
TextButton(
onPressed:
() => Navigator.of(context).pop(true),
child: Text('delete'.tr()),
),
],
),
);
if (confirmed == true) {
try {
final client = ref.read(apiClientProvider);
await client.delete(
'/zone/sites/$pubName/${site.slug}',
);
ref.invalidate(siteListNotifierProvider(pubName));
showSnackBar('siteDeletedSuccess'.tr());
} catch (e) {
showErrorAlert(e);
}
}
},
),
],
), ),
], ],
), ),

View File

@@ -41,11 +41,10 @@ class StickersScreen extends HookConsumerWidget {
showModalBottomSheet( showModalBottomSheet(
context: context, context: context,
isScrollControlled: true, isScrollControlled: true,
builder: builder: (context) => SheetScaffold(
(context) => SheetScaffold( titleText: 'createStickerPack'.tr(),
titleText: 'createStickerPack'.tr(), child: StickerPackForm(pubName: pubName),
child: StickerPackForm(pubName: pubName), ),
),
).then((value) { ).then((value) {
if (value != null) { if (value != null) {
ref.invalidate(stickerPacksProvider(pubName)); ref.invalidate(stickerPacksProvider(pubName));
@@ -54,24 +53,23 @@ class StickersScreen extends HookConsumerWidget {
}, },
child: const Icon(Symbols.add), child: const Icon(Symbols.add),
), ),
body: body: isWideScreen(context)
isWideScreen(context) ? Center(
? Center( child: ConstrainedBox(
child: ConstrainedBox( constraints: BoxConstraints(maxWidth: 640),
constraints: BoxConstraints(maxWidth: 640), child: Card(
child: Card( shape: RoundedRectangleBorder(
shape: RoundedRectangleBorder( borderRadius: const BorderRadius.only(
borderRadius: const BorderRadius.only( topLeft: Radius.circular(8),
topLeft: Radius.circular(8), topRight: Radius.circular(8),
topRight: Radius.circular(8),
),
), ),
margin: const EdgeInsets.only(top: 16),
child: content,
), ),
margin: const EdgeInsets.only(top: 16),
child: content,
), ),
) ),
: content, )
: content,
); );
} }
} }
@@ -83,6 +81,7 @@ class SliverStickerPacksList extends HookConsumerWidget {
@override @override
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
return PaginationList( return PaginationList(
padding: EdgeInsets.zero,
provider: stickerPacksProvider(pubName), provider: stickerPacksProvider(pubName),
notifier: stickerPacksProvider(pubName).notifier, notifier: stickerPacksProvider(pubName).notifier,
itemBuilder: (context, index, sticker) { itemBuilder: (context, index, sticker) {
@@ -97,40 +96,38 @@ class SliverStickerPacksList extends HookConsumerWidget {
showModalBottomSheet( showModalBottomSheet(
context: context, context: context,
isScrollControlled: true, isScrollControlled: true,
builder: builder: (context) => SheetScaffold(
(context) => SheetScaffold( titleText: sticker.name,
titleText: sticker.name, actions: [
actions: [ IconButton(
IconButton( icon: const Icon(Symbols.add_circle),
icon: const Icon(Symbols.add_circle), onPressed: () {
onPressed: () { final id = sticker.id;
final id = sticker.id; showModalBottomSheet(
showModalBottomSheet( context: context,
context: context, isScrollControlled: true,
isScrollControlled: true, builder: (context) => SheetScaffold(
builder: titleText: 'createSticker'.tr(),
(context) => SheetScaffold( child: StickerForm(packId: id),
titleText: 'createSticker'.tr(), ),
child: StickerForm(packId: id), ).then((value) {
), if (value != null) {
).then((value) { ref.invalidate(stickerPackContentProvider(id));
if (value != null) { }
ref.invalidate(stickerPackContentProvider(id)); });
} },
});
},
),
StickerPackActionMenu(
pubName: pubName,
packId: sticker.id,
iconShadow: Shadow(),
),
],
child: StickerPackDetailContent(
id: sticker.id,
pubName: pubName,
),
), ),
StickerPackActionMenu(
pubName: pubName,
packId: sticker.id,
iconShadow: Shadow(),
),
],
child: StickerPackDetailContent(
id: sticker.id,
pubName: pubName,
),
),
); );
}, },
); );
@@ -165,11 +162,10 @@ class StickerPacksNotifier extends AsyncNotifier<List<SnStickerPack>>
); );
totalCount = int.parse(response.headers.value('X-Total') ?? '0'); totalCount = int.parse(response.headers.value('X-Total') ?? '0');
final stickers = final stickers = response.data
response.data .map((e) => SnStickerPack.fromJson(e))
.map((e) => SnStickerPack.fromJson(e)) .cast<SnStickerPack>()
.cast<SnStickerPack>() .toList();
.toList();
return stickers; return stickers;
} catch (err) { } catch (err) {
@@ -262,10 +258,9 @@ class StickerPackForm extends HookConsumerWidget {
color: Theme.of(context).colorScheme.surfaceContainer, color: Theme.of(context).colorScheme.surfaceContainer,
borderRadius: BorderRadius.all(Radius.circular(8)), borderRadius: BorderRadius.all(Radius.circular(8)),
), ),
child: child: (icon.value?.isEmpty ?? true)
(icon.value?.isEmpty ?? true) ? const SizedBox.shrink()
? const SizedBox.shrink() : CloudImageWidget(fileId: icon.value!),
: CloudImageWidget(fileId: icon.value!),
), ),
), ),
), ),
@@ -273,10 +268,9 @@ class StickerPackForm extends HookConsumerWidget {
onPressed: () { onPressed: () {
showModalBottomSheet( showModalBottomSheet(
context: context, context: context,
builder: builder: (context) => CloudFilePicker(
(context) => CloudFilePicker( allowedTypes: {UniversalFileType.image},
allowedTypes: {UniversalFileType.image}, ),
),
).then((value) { ).then((value) {
if (value == null) return; if (value == null) return;
icon.value = value[0].id; icon.value = value[0].id;
@@ -300,8 +294,8 @@ class StickerPackForm extends HookConsumerWidget {
} }
return null; return null;
}, },
onTapOutside: onTapOutside: (_) =>
(_) => FocusManager.instance.primaryFocus?.unfocus(), FocusManager.instance.primaryFocus?.unfocus(),
), ),
TextFormField( TextFormField(
controller: descriptionController, controller: descriptionController,
@@ -314,8 +308,8 @@ class StickerPackForm extends HookConsumerWidget {
), ),
minLines: 3, minLines: 3,
maxLines: null, maxLines: null,
onTapOutside: onTapOutside: (_) =>
(_) => FocusManager.instance.primaryFocus?.unfocus(), FocusManager.instance.primaryFocus?.unfocus(),
), ),
TextFormField( TextFormField(
controller: prefixController, controller: prefixController,
@@ -332,8 +326,8 @@ class StickerPackForm extends HookConsumerWidget {
} }
return null; return null;
}, },
onTapOutside: onTapOutside: (_) =>
(_) => FocusManager.instance.primaryFocus?.unfocus(), FocusManager.instance.primaryFocus?.unfocus(),
), ),
], ],
), ),