♻️ Optimize the creator hub

This commit is contained in:
2025-10-12 00:06:48 +08:00
parent c3f61467c8
commit c660a419e2
10 changed files with 437 additions and 337 deletions

View File

@@ -150,19 +150,15 @@ final routerProvider = Provider<GoRouter>((ref) {
return EventCalanderScreen(name: name); return EventCalanderScreen(name: name);
}, },
), ),
ShellRoute( GoRoute(
builder: name: 'creatorHub',
(context, state, child) => CreatorHubShellScreen(child: child), path: '/creators',
builder: (context, state) => const CreatorHubScreen(),
routes: [ routes: [
GoRoute(
name: 'creatorHub',
path: '/creators',
builder: (context, state) => const CreatorHubScreen(),
),
// Web Feed Routes // Web Feed Routes
GoRoute( GoRoute(
name: 'creatorFeeds', name: 'creatorFeeds',
path: '/creators/:name/feeds', path: ':name/feeds',
builder: (context, state) { builder: (context, state) {
final name = state.pathParameters['name']!; final name = state.pathParameters['name']!;
return WebFeedListScreen(pubName: name); return WebFeedListScreen(pubName: name);
@@ -191,7 +187,7 @@ final routerProvider = Provider<GoRouter>((ref) {
), ),
GoRoute( GoRoute(
name: 'creatorPosts', name: 'creatorPosts',
path: '/creators/:name/posts', path: ':name/posts',
builder: (context, state) { builder: (context, state) {
final name = state.pathParameters['name']!; final name = state.pathParameters['name']!;
return CreatorPostListScreen(pubName: name); return CreatorPostListScreen(pubName: name);
@@ -200,7 +196,7 @@ final routerProvider = Provider<GoRouter>((ref) {
// Poll list route // Poll list route
GoRoute( GoRoute(
name: 'creatorPolls', name: 'creatorPolls',
path: '/creators/:name/polls', path: ':name/polls',
builder: (context, state) { builder: (context, state) {
final name = state.pathParameters['name']!; final name = state.pathParameters['name']!;
return CreatorPollListScreen(pubName: name); return CreatorPollListScreen(pubName: name);
@@ -209,7 +205,7 @@ final routerProvider = Provider<GoRouter>((ref) {
// Poll routes // Poll routes
GoRoute( GoRoute(
name: 'creatorPollNew', name: 'creatorPollNew',
path: '/creators/:name/polls/new', path: ':name/polls/new',
builder: (context, state) { builder: (context, state) {
final name = state.pathParameters['name']!; final name = state.pathParameters['name']!;
// initialPollId left null for create; initialPublisher prefilled // initialPollId left null for create; initialPublisher prefilled
@@ -218,7 +214,7 @@ final routerProvider = Provider<GoRouter>((ref) {
), ),
GoRoute( GoRoute(
name: 'creatorPollEdit', name: 'creatorPollEdit',
path: '/creators/:name/polls/:id/edit', path: ':name/polls/:id/edit',
builder: (context, state) { builder: (context, state) {
final name = state.pathParameters['name']!; final name = state.pathParameters['name']!;
final id = state.pathParameters['id']!; final id = state.pathParameters['id']!;
@@ -230,7 +226,7 @@ final routerProvider = Provider<GoRouter>((ref) {
), ),
GoRoute( GoRoute(
name: 'creatorStickers', name: 'creatorStickers',
path: '/creators/:name/stickers', path: ':name/stickers',
builder: (context, state) { builder: (context, state) {
final name = state.pathParameters['name']!; final name = state.pathParameters['name']!;
return StickersScreen(pubName: name); return StickersScreen(pubName: name);
@@ -238,7 +234,7 @@ final routerProvider = Provider<GoRouter>((ref) {
), ),
GoRoute( GoRoute(
name: 'creatorStickerPackNew', name: 'creatorStickerPackNew',
path: '/creators/:name/stickers/new', path: ':name/stickers/new',
builder: (context, state) { builder: (context, state) {
final name = state.pathParameters['name']!; final name = state.pathParameters['name']!;
return NewStickerPacksScreen(pubName: name); return NewStickerPacksScreen(pubName: name);
@@ -246,7 +242,7 @@ final routerProvider = Provider<GoRouter>((ref) {
), ),
GoRoute( GoRoute(
name: 'creatorStickerPackEdit', name: 'creatorStickerPackEdit',
path: '/creators/:name/stickers/:packId/edit', path: ':name/stickers/:packId/edit',
builder: (context, state) { builder: (context, state) {
final name = state.pathParameters['name']!; final name = state.pathParameters['name']!;
final packId = state.pathParameters['packId']!; final packId = state.pathParameters['packId']!;
@@ -255,7 +251,7 @@ final routerProvider = Provider<GoRouter>((ref) {
), ),
GoRoute( GoRoute(
name: 'creatorStickerPackDetail', name: 'creatorStickerPackDetail',
path: '/creators/:name/stickers/:packId', path: ':name/stickers/:packId',
builder: (context, state) { builder: (context, state) {
final name = state.pathParameters['name']!; final name = state.pathParameters['name']!;
final packId = state.pathParameters['packId']!; final packId = state.pathParameters['packId']!;
@@ -264,7 +260,7 @@ final routerProvider = Provider<GoRouter>((ref) {
), ),
GoRoute( GoRoute(
name: 'creatorStickerNew', name: 'creatorStickerNew',
path: '/creators/:name/stickers/:packId/new', path: ':name/stickers/:packId/new',
builder: (context, state) { builder: (context, state) {
final packId = state.pathParameters['packId']!; final packId = state.pathParameters['packId']!;
return NewStickersScreen(packId: packId); return NewStickersScreen(packId: packId);
@@ -272,7 +268,7 @@ final routerProvider = Provider<GoRouter>((ref) {
), ),
GoRoute( GoRoute(
name: 'creatorStickerEdit', name: 'creatorStickerEdit',
path: '/creators/:name/stickers/:packId/:id/edit', path: ':name/stickers/:packId/:id/edit',
builder: (context, state) { builder: (context, state) {
final packId = state.pathParameters['packId']!; final packId = state.pathParameters['packId']!;
final id = state.pathParameters['id']!; final id = state.pathParameters['id']!;
@@ -281,12 +277,12 @@ final routerProvider = Provider<GoRouter>((ref) {
), ),
GoRoute( GoRoute(
name: 'creatorNew', name: 'creatorNew',
path: '/creators/new', path: 'new',
builder: (context, state) => const NewPublisherScreen(), builder: (context, state) => const NewPublisherScreen(),
), ),
GoRoute( GoRoute(
name: 'creatorEdit', name: 'creatorEdit',
path: '/creators/:name/edit', path: ':name/edit',
builder: (context, state) { builder: (context, state) {
final name = state.pathParameters['name']!; final name = state.pathParameters['name']!;
return EditPublisherScreen(name: name); return EditPublisherScreen(name: name);

View File

@@ -102,42 +102,167 @@ class PublisherMemberListNotifier extends _$PublisherMemberListNotifier
} }
} }
class CreatorHubShellScreen extends StatelessWidget { class PublisherSelector extends StatelessWidget {
final Widget child; final SnPublisher? currentPublisher;
const CreatorHubShellScreen({super.key, required this.child}); final List<DropdownMenuItem<SnPublisher>> publishersMenu;
final ValueChanged<SnPublisher?>? onChanged;
final bool isReadOnly;
const PublisherSelector({
required this.currentPublisher,
required this.publishersMenu,
this.onChanged,
this.isReadOnly = false,
});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final isWide = isWideScreen(context); if (isReadOnly || currentPublisher == null) {
if (isWide) { return ProfilePictureWidget(
return AppBackground( radius: 16,
isRoot: true, fileId: currentPublisher?.picture?.id,
child: Row( ).center().padding(right: 8);
children: [
Flexible(flex: 2, child: const CreatorHubScreen(isAside: true)),
const VerticalDivider(width: 1),
Flexible(flex: 3, child: child),
],
),
);
} }
return AppBackground(isRoot: true, child: child);
return DropdownButtonHideUnderline(
child: DropdownButton2<SnPublisher>(
value: currentPublisher,
hint: CircleAvatar(
radius: 16,
child: Icon(
Symbols.person,
color: Theme.of(
context,
).colorScheme.onSecondaryContainer.withOpacity(0.9),
fill: 1,
),
).center().padding(right: 8),
items: publishersMenu,
onChanged: onChanged,
selectedItemBuilder: (context) {
return publishersMenu
.map(
(e) => ProfilePictureWidget(
radius: 16,
fileId: e.value?.picture?.id,
).center().padding(right: 8),
)
.toList();
},
buttonStyleData: ButtonStyleData(
height: 40,
padding: const EdgeInsets.only(left: 14, right: 8),
decoration: BoxDecoration(borderRadius: BorderRadius.circular(20)),
),
dropdownStyleData: DropdownStyleData(
width: 320,
padding: const EdgeInsets.symmetric(vertical: 6),
decoration: BoxDecoration(borderRadius: BorderRadius.circular(4)),
),
menuItemStyleData: const MenuItemStyleData(
height: 64,
padding: EdgeInsets.only(left: 14, right: 14),
),
iconStyleData: IconStyleData(
icon: Icon(Icons.arrow_drop_down),
iconSize: 19,
iconEnabledColor: Theme.of(context).appBarTheme.foregroundColor!,
iconDisabledColor: Theme.of(context).appBarTheme.foregroundColor!,
),
),
);
}
}
class _PublisherUnselectedWidget extends HookConsumerWidget {
final ValueChanged<SnPublisher> onPublisherSelected;
const _PublisherUnselectedWidget({required this.onPublisherSelected});
@override
Widget build(BuildContext context, WidgetRef ref) {
final publishers = ref.watch(publishersManagedProvider);
final publisherInvites = ref.watch(publisherInvitesProvider);
final hasPublishers = publishers.value?.isNotEmpty ?? false;
return Card(
margin: const EdgeInsets.all(16),
child: Column(
children: [
if (!hasPublishers) ...[
const Icon(
Symbols.info,
fill: 1,
size: 32,
).padding(bottom: 6, top: 24),
Text(
'creatorHubUnselectedHint',
textAlign: TextAlign.center,
style: Theme.of(context).textTheme.bodyLarge,
).tr(),
const Gap(24),
],
if (hasPublishers)
...(publishers.value?.map(
(publisher) => ListTile(
shape: RoundedRectangleBorder(
borderRadius: const BorderRadius.all(Radius.circular(8)),
),
leading: ProfilePictureWidget(file: publisher.picture),
title: Text(publisher.nick),
subtitle: Text('@${publisher.name}'),
onTap: () => onPublisherSelected(publisher),
),
) ??
[]),
const Divider(height: 1),
ListTile(
shape: RoundedRectangleBorder(
borderRadius: const BorderRadius.all(Radius.circular(8)),
),
leading: const CircleAvatar(child: Icon(Symbols.mail)),
title: Text('publisherCollabInvitation').tr(),
subtitle: Text(
'publisherCollabInvitationCount',
).plural(publisherInvites.value?.length ?? 0),
trailing: const Icon(Symbols.chevron_right),
onTap: () {
showModalBottomSheet(
context: context,
isScrollControlled: true,
builder: (_) => const _PublisherInviteSheet(),
);
},
),
ListTile(
shape: RoundedRectangleBorder(
borderRadius: const BorderRadius.all(Radius.circular(8)),
),
leading: const CircleAvatar(child: Icon(Symbols.add)),
title: Text('createPublisher').tr(),
subtitle: Text('createPublisherHint').tr(),
trailing: const Icon(Symbols.chevron_right),
onTap: () {
context.pushNamed('creatorNew').then((value) {
if (value != null) {
ref.invalidate(publishersManagedProvider);
}
});
},
),
],
),
);
} }
} }
class CreatorHubScreen extends HookConsumerWidget { class CreatorHubScreen extends HookConsumerWidget {
final bool isAside; const CreatorHubScreen({super.key});
const CreatorHubScreen({super.key, this.isAside = false});
@override @override
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
final isWide = isWideScreen(context);
if (isWide && !isAside) {
return Container(color: Theme.of(context).colorScheme.surface);
}
final publishers = ref.watch(publishersManagedProvider); final publishers = ref.watch(publishersManagedProvider);
final publisherInvites = ref.watch(publisherInvitesProvider);
final currentPublisher = useState<SnPublisher?>( final currentPublisher = useState<SnPublisher?>(
publishers.value?.firstOrNull, publishers.value?.firstOrNull,
); );
@@ -207,299 +332,251 @@ class CreatorHubScreen extends HookConsumerWidget {
publisherFeaturesProvider(currentPublisher.value?.name), publisherFeaturesProvider(currentPublisher.value?.name),
); );
Widget buildNavigationWidget(bool isWide) {
final leftItems = [
ListTile(
shape: RoundedRectangleBorder(
borderRadius: const BorderRadius.all(Radius.circular(8)),
),
minTileHeight: 48,
title: Text('stickers').tr(),
trailing: Icon(Symbols.chevron_right),
leading: const Icon(Symbols.ar_stickers),
onTap: () {
context.pushNamed(
'creatorStickers',
pathParameters: {'name': currentPublisher.value!.name},
);
},
),
ListTile(
shape: RoundedRectangleBorder(
borderRadius: const BorderRadius.all(Radius.circular(8)),
),
minTileHeight: 48,
title: Text('posts').tr(),
trailing: Icon(Symbols.chevron_right),
leading: const Icon(Symbols.sticky_note_2),
onTap: () {
context.pushNamed(
'creatorPosts',
pathParameters: {'name': currentPublisher.value!.name},
);
},
),
ListTile(
shape: RoundedRectangleBorder(
borderRadius: const BorderRadius.all(Radius.circular(8)),
),
minTileHeight: 48,
title: Text('polls').tr(),
trailing: const Icon(Symbols.chevron_right),
leading: const Icon(Symbols.poll),
onTap: () {
context.pushNamed(
'creatorPolls',
pathParameters: {'name': currentPublisher.value!.name},
);
},
),
ListTile(
shape: RoundedRectangleBorder(
borderRadius: const BorderRadius.all(Radius.circular(8)),
),
minTileHeight: 48,
title: Text('publisherMembers').tr(),
trailing: const Icon(Symbols.chevron_right),
leading: const Icon(Symbols.group),
onTap: () {
showModalBottomSheet(
isScrollControlled: true,
context: context,
builder:
(context) => _PublisherMemberListSheet(
publisherUname: currentPublisher.value!.name,
),
);
},
),
];
final rightItems = [
ListTile(
shape: RoundedRectangleBorder(
borderRadius: const BorderRadius.all(Radius.circular(8)),
),
minTileHeight: 48,
title: const Text('webFeeds').tr(),
trailing: const Icon(Symbols.chevron_right),
leading: const Icon(Symbols.rss_feed),
onTap: () {
context.push('/creators/${currentPublisher.value!.name}/feeds');
},
),
ExpansionTile(
shape: RoundedRectangleBorder(
borderRadius: const BorderRadius.all(Radius.circular(8)),
),
title: Text('publisherFeatures').tr(),
leading: const Icon(Symbols.flag),
tilePadding: const EdgeInsets.only(left: 16, right: 24),
minTileHeight: 48,
children: [
...publisherFeatures.when(
data: (data) {
return data.entries.map((entry) {
final keyPrefix =
'publisherFeature${entry.key.capitalizeEachWord()}';
return ListTile(
minTileHeight: 48,
contentPadding: const EdgeInsets.symmetric(horizontal: 16),
leading: Icon(
Symbols.circle,
color: entry.value ? Colors.green : Colors.red,
fill: 1,
size: 16,
).padding(left: 2, top: 4),
title: Text(keyPrefix).tr(),
subtitle: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('${keyPrefix}Description').tr(),
if (!entry.value) Text('${keyPrefix}Hint').tr().bold(),
],
),
isThreeLine: true,
);
}).toList();
},
error: (_, _) => [],
loading: () => [],
),
],
),
ListTile(
shape: RoundedRectangleBorder(
borderRadius: const BorderRadius.all(Radius.circular(8)),
),
minTileHeight: 48,
title: Text('editPublisher').tr(),
trailing: Icon(Symbols.chevron_right),
leading: const Icon(Symbols.edit),
onTap: updatePublisher,
),
ListTile(
shape: RoundedRectangleBorder(
borderRadius: const BorderRadius.all(Radius.circular(8)),
),
minTileHeight: 48,
title: Text('deletePublisher').tr(),
trailing: Icon(Symbols.chevron_right),
leading: const Icon(Symbols.delete),
onTap: deletePublisher,
),
];
if (isWide) {
return Row(
crossAxisAlignment: CrossAxisAlignment.start,
spacing: 8,
children: [
Expanded(
child: Card(
margin: EdgeInsets.zero,
child: Column(children: leftItems),
),
),
Expanded(
child: Card(
margin: EdgeInsets.zero,
child: Column(children: rightItems),
),
),
],
).padding(horizontal: 12);
} else {
return Card(
margin: const EdgeInsets.symmetric(horizontal: 16),
child: Column(
children: [...leftItems, const Divider(height: 8), ...rightItems],
),
);
}
}
return AppScaffold( return AppScaffold(
isNoBackground: false,
appBar: AppBar( appBar: AppBar(
leading: !isWide ? const PageBackButton() : null, leading: const PageBackButton(),
title: Text('creatorHub').tr(), title: Text('creatorHub').tr(),
actions: [ actions: [
DropdownButtonHideUnderline( if (!isWideScreen(context))
child: DropdownButton2<SnPublisher>( PublisherSelector(
alignment: Alignment.centerRight, currentPublisher: currentPublisher.value,
value: currentPublisher.value, publishersMenu: publishersMenu,
hint: CircleAvatar(
radius: 16,
child: Icon(
Symbols.person,
color: Theme.of(
context,
).colorScheme.onSecondaryContainer.withOpacity(0.9),
fill: 1,
),
).center().padding(right: 8),
items: [...publishersMenu],
onChanged: (value) { onChanged: (value) {
currentPublisher.value = value; currentPublisher.value = value;
}, },
selectedItemBuilder: (context) {
return [
...publishersMenu.map(
(e) => ProfilePictureWidget(
radius: 16,
fileId: e.value?.picture?.id,
).center().padding(right: 8),
),
];
},
buttonStyleData: ButtonStyleData(
height: 40,
padding: const EdgeInsets.only(left: 14, right: 8),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(20),
),
),
dropdownStyleData: DropdownStyleData(
width: 320,
padding: const EdgeInsets.symmetric(vertical: 6),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(4),
),
),
menuItemStyleData: const MenuItemStyleData(
height: 64,
padding: EdgeInsets.only(left: 14, right: 14),
),
iconStyleData: IconStyleData(
icon: Icon(Icons.arrow_drop_down),
iconSize: 19,
iconEnabledColor:
Theme.of(context).appBarTheme.foregroundColor!,
iconDisabledColor:
Theme.of(context).appBarTheme.foregroundColor!,
),
), ),
),
const Gap(8), const Gap(8),
], ],
), ),
body: publisherStats.when( body: LayoutBuilder(
data: builder: (context, constraints) {
(stats) => SingleChildScrollView( final isWide = isWideScreen(context);
child: final maxWidth = isWide ? 800.0 : double.infinity;
currentPublisher.value == null
? Column( return Center(
children: [ child: ConstrainedBox(
const Gap(24), constraints: BoxConstraints(maxWidth: maxWidth),
const Icon(Symbols.info, size: 32).padding(bottom: 4), child: publisherStats.when(
Text( data:
'creatorHubUnselectedHint', (stats) => SingleChildScrollView(
textAlign: TextAlign.center, child:
).tr(), currentPublisher.value == null
const Gap(24), ? ConstrainedBox(
const Divider(height: 1), constraints: BoxConstraints(maxWidth: 640),
...(publishers.value?.map( child: _PublisherUnselectedWidget(
(publisher) => ListTile( onPublisherSelected: (publisher) {
leading: ProfilePictureWidget(
file: publisher.picture,
),
title: Text(publisher.nick),
subtitle: Text('@${publisher.name}'),
onTap: () {
currentPublisher.value = publisher; currentPublisher.value = publisher;
}, },
), ),
) ?? ).center()
[]), : isWide
ListTile( ? Column(
leading: const CircleAvatar( spacing: 8,
child: Icon(Symbols.mail), children: [
), PublisherSelector(
title: Text('publisherCollabInvitation').tr(), currentPublisher: currentPublisher.value,
subtitle: Text( publishersMenu: publishersMenu,
'publisherCollabInvitationCount', onChanged: (value) {
).plural(publisherInvites.value?.length ?? 0), currentPublisher.value = value;
trailing: const Icon(Symbols.chevron_right), },
onTap: () { ),
showModalBottomSheet( if (stats != null)
context: context, _PublisherStatsWidget(
isScrollControlled: true, stats: stats,
builder: (_) => const _PublisherInviteSheet(), ).padding(horizontal: 12),
); buildNavigationWidget(true),
}, ],
), )
ListTile( : Column(
leading: const CircleAvatar( spacing: 12,
child: Icon(Symbols.add), children: [
), if (stats != null)
title: Text('createPublisher').tr(), _PublisherStatsWidget(
subtitle: Text('createPublisherHint').tr(), stats: stats,
trailing: const Icon(Symbols.chevron_right), ).padding(horizontal: 16),
onTap: () { buildNavigationWidget(false),
context.pushNamed('creatorNew').then((value) { ],
if (value != null) {
ref.invalidate(publishersManagedProvider);
}
});
},
),
],
)
: Column(
children: [
if (stats != null)
_PublisherStatsWidget(
stats: stats,
).padding(vertical: 12, horizontal: 12),
ListTile(
minTileHeight: 48,
title: Text('stickers').tr(),
trailing: Icon(Symbols.chevron_right),
leading: const Icon(Symbols.ar_stickers),
contentPadding: EdgeInsets.symmetric(
horizontal: 24,
),
onTap: () {
context.pushNamed(
'creatorStickers',
pathParameters: {
'name': currentPublisher.value!.name,
},
);
},
),
ListTile(
minTileHeight: 48,
title: Text('posts').tr(),
trailing: Icon(Symbols.chevron_right),
leading: const Icon(Symbols.sticky_note_2),
contentPadding: EdgeInsets.symmetric(
horizontal: 24,
),
onTap: () {
context.pushNamed(
'creatorPosts',
pathParameters: {
'name': currentPublisher.value!.name,
},
);
},
),
ListTile(
minTileHeight: 48,
title: Text('polls').tr(),
trailing: const Icon(Symbols.chevron_right),
leading: const Icon(Symbols.poll),
contentPadding: const EdgeInsets.symmetric(
horizontal: 24,
),
onTap: () {
context.pushNamed(
'creatorPolls',
pathParameters: {
'name': currentPublisher.value!.name,
},
);
},
),
ListTile(
minTileHeight: 48,
title: Text('publisherMembers').tr(),
trailing: const Icon(Symbols.chevron_right),
leading: const Icon(Symbols.group),
contentPadding: const EdgeInsets.symmetric(
horizontal: 24,
),
onTap: () {
showModalBottomSheet(
isScrollControlled: true,
context: context,
builder:
(context) => _PublisherMemberListSheet(
publisherUname:
currentPublisher.value!.name,
),
);
},
),
ListTile(
minTileHeight: 48,
title: const Text('webFeeds').tr(),
trailing: const Icon(Symbols.chevron_right),
leading: const Icon(Symbols.rss_feed),
contentPadding: const EdgeInsets.symmetric(
horizontal: 24,
),
onTap: () {
context.push(
'/creators/${currentPublisher.value!.name}/feeds',
);
},
),
ExpansionTile(
title: Text('publisherFeatures').tr(),
leading: const Icon(Symbols.flag),
tilePadding: EdgeInsets.symmetric(horizontal: 24),
minTileHeight: 48,
children: [
...publisherFeatures.when(
data: (data) {
return data.entries.map((entry) {
final keyPrefix =
'publisherFeature${entry.key.capitalizeEachWord()}';
return ListTile(
minTileHeight: 48,
contentPadding: EdgeInsets.symmetric(
horizontal: 24,
),
leading: Icon(
Symbols.circle,
color:
entry.value
? Colors.green
: Colors.red,
fill: 1,
size: 16,
).padding(left: 2, top: 4),
title: Text(keyPrefix).tr(),
subtitle: Column(
crossAxisAlignment:
CrossAxisAlignment.start,
children: [
Text('${keyPrefix}Description').tr(),
if (!entry.value)
Text(
'${keyPrefix}Hint',
).tr().bold(),
],
),
isThreeLine: true,
);
}).toList();
},
error: (_, _) => [],
loading: () => [],
), ),
], ),
), loading: () => const Center(child: CircularProgressIndicator()),
Divider(height: 1).padding(vertical: 8), error: (_, _) => const SizedBox.shrink(),
ListTile( ),
minTileHeight: 48,
title: Text('editPublisher').tr(),
trailing: Icon(Symbols.chevron_right),
leading: const Icon(Symbols.edit),
contentPadding: EdgeInsets.symmetric(
horizontal: 24,
),
onTap: () {
updatePublisher();
},
),
ListTile(
minTileHeight: 48,
title: Text('deletePublisher').tr(),
trailing: Icon(Symbols.chevron_right),
leading: const Icon(Symbols.delete),
contentPadding: EdgeInsets.symmetric(
horizontal: 24,
),
onTap: () {
deletePublisher();
},
),
],
),
), ),
loading: () => const Center(child: CircularProgressIndicator()), );
error: (_, _) => const SizedBox.shrink(), },
), ),
); );
} }
@@ -576,21 +653,36 @@ class _PublisherStatsWidget extends StatelessWidget {
height: 100, height: 100,
child: Padding( child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 8), padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 8),
child: Column( child: Tooltip(
crossAxisAlignment: CrossAxisAlignment.stretch, richMessage: TextSpan(
mainAxisAlignment: MainAxisAlignment.center, text: statLabel.tr(),
children: [ style: TextStyle(fontWeight: FontWeight.bold),
Text( children: [
statValue, TextSpan(text: ' '),
style: Theme.of(context).textTheme.headlineMedium, TextSpan(
), text: statValue,
const Gap(4), style: TextStyle(fontWeight: FontWeight.normal),
Text( ),
statLabel, ],
maxLines: 1, ),
overflow: TextOverflow.ellipsis, child: Column(
).tr(), crossAxisAlignment: CrossAxisAlignment.stretch,
], mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
statValue,
style: Theme.of(context).textTheme.headlineMedium,
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
const Gap(4),
Text(
statLabel,
maxLines: 1,
overflow: TextOverflow.ellipsis,
).tr(),
],
),
), ),
), ),
), ),

View File

@@ -84,6 +84,7 @@ class CreatorPollListScreen extends HookConsumerWidget {
@override @override
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
return AppScaffold( return AppScaffold(
isNoBackground: false,
appBar: AppBar(title: const Text('Polls')), appBar: AppBar(title: const Text('Polls')),
floatingActionButton: FloatingActionButton( floatingActionButton: FloatingActionButton(
onPressed: () => _createPoll(context), onPressed: () => _createPoll(context),

View File

@@ -61,6 +61,7 @@ class CreatorPostListScreen extends HookConsumerWidget {
} }
return AppScaffold( return AppScaffold(
isNoBackground: false,
appBar: AppBar(title: Text('posts').tr()), appBar: AppBar(title: Text('posts').tr()),
body: CustomScrollView( body: CustomScrollView(
key: ValueKey(refreshKey.value), key: ValueKey(refreshKey.value),

View File

@@ -178,6 +178,7 @@ class EditPublisherScreen extends HookConsumerWidget {
} }
return AppScaffold( return AppScaffold(
isNoBackground: false,
appBar: AppBar( appBar: AppBar(
title: Text(name == null ? 'createPublisher' : 'editPublisher').tr(), title: Text(name == null ? 'createPublisher' : 'editPublisher').tr(),
leading: const PageBackButton(), leading: const PageBackButton(),

View File

@@ -68,6 +68,7 @@ class StickerPackDetailScreen extends HookConsumerWidget {
} }
return AppScaffold( return AppScaffold(
isNoBackground: false,
appBar: AppBar( appBar: AppBar(
title: Text(pack.value?.name ?? 'loading'.tr()), title: Text(pack.value?.name ?? 'loading'.tr()),
actions: [ actions: [
@@ -396,6 +397,7 @@ class EditStickersScreen extends HookConsumerWidget {
} }
return AppScaffold( return AppScaffold(
isNoBackground: false,
appBar: AppBar( appBar: AppBar(
title: Text(id == null ? 'createSticker' : 'editSticker').tr(), title: Text(id == null ? 'createSticker' : 'editSticker').tr(),
), ),

View File

@@ -23,6 +23,7 @@ class StickersScreen extends HookConsumerWidget {
@override @override
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
return AppScaffold( return AppScaffold(
isNoBackground: false,
appBar: AppBar( appBar: AppBar(
title: const Text('stickers').tr(), title: const Text('stickers').tr(),
actions: [ actions: [
@@ -196,6 +197,7 @@ class EditStickerPacksScreen extends HookConsumerWidget {
} }
return AppScaffold( return AppScaffold(
isNoBackground: false,
appBar: AppBar( appBar: AppBar(
title: title:
Text(packId == null ? 'createStickerPack' : 'editStickerPack').tr(), Text(packId == null ? 'createStickerPack' : 'editStickerPack').tr(),

View File

@@ -115,10 +115,12 @@ class WebFeedEditScreen extends HookConsumerWidget {
return feedAsync.when( return feedAsync.when(
loading: loading:
() => const AppScaffold( () => const AppScaffold(
isNoBackground: false,
body: Center(child: CircularProgressIndicator()), body: Center(child: CircularProgressIndicator()),
), ),
error: error:
(error, stack) => AppScaffold( (error, stack) => AppScaffold(
isNoBackground: false,
appBar: AppBar(title: const Text('Error')), appBar: AppBar(title: const Text('Error')),
body: Center(child: Text('Error: $error')), body: Center(child: Text('Error: $error')),
), ),
@@ -186,6 +188,7 @@ class WebFeedEditScreen extends HookConsumerWidget {
}, [pubName, feedId, ref, context]); }, [pubName, feedId, ref, context]);
return AppScaffold( return AppScaffold(
isNoBackground: false,
appBar: AppBar( appBar: AppBar(
title: Text(hasFeedId ? 'Edit Web Feed' : 'New Web Feed'), title: Text(hasFeedId ? 'Edit Web Feed' : 'New Web Feed'),
actions: [ actions: [

View File

@@ -17,6 +17,7 @@ class WebFeedListScreen extends ConsumerWidget {
final feedsAsync = ref.watch(webFeedListProvider(pubName)); final feedsAsync = ref.watch(webFeedListProvider(pubName));
return AppScaffold( return AppScaffold(
isNoBackground: false,
appBar: AppBar(title: const Text('Web Feeds')), appBar: AppBar(title: const Text('Web Feeds')),
floatingActionButton: FloatingActionButton( floatingActionButton: FloatingActionButton(
child: const Icon(Symbols.add), child: const Icon(Symbols.add),

View File

@@ -416,6 +416,7 @@ class PollEditorScreen extends ConsumerWidget {
} }
return AppScaffold( return AppScaffold(
isNoBackground: false,
appBar: AppBar( appBar: AppBar(
title: Text(model.id == null ? 'pollCreate'.tr() : 'pollEdit'.tr()), title: Text(model.id == null ? 'pollCreate'.tr() : 'pollEdit'.tr()),
actions: [ actions: [