♻️ Optimize the creator hub
This commit is contained in:
@@ -150,19 +150,15 @@ final routerProvider = Provider<GoRouter>((ref) {
|
|||||||
return EventCalanderScreen(name: name);
|
return EventCalanderScreen(name: name);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
ShellRoute(
|
|
||||||
builder:
|
|
||||||
(context, state, child) => CreatorHubShellScreen(child: child),
|
|
||||||
routes: [
|
|
||||||
GoRoute(
|
GoRoute(
|
||||||
name: 'creatorHub',
|
name: 'creatorHub',
|
||||||
path: '/creators',
|
path: '/creators',
|
||||||
builder: (context, state) => const CreatorHubScreen(),
|
builder: (context, state) => const CreatorHubScreen(),
|
||||||
),
|
routes: [
|
||||||
// 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);
|
||||||
|
@@ -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);
|
||||||
|
}
|
||||||
|
|
||||||
|
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: [
|
children: [
|
||||||
Flexible(flex: 2, child: const CreatorHubScreen(isAside: true)),
|
if (!hasPublishers) ...[
|
||||||
const VerticalDivider(width: 1),
|
const Icon(
|
||||||
Flexible(flex: 3, child: child),
|
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);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return AppBackground(isRoot: true, child: child);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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,227 +332,94 @@ class CreatorHubScreen extends HookConsumerWidget {
|
|||||||
publisherFeaturesProvider(currentPublisher.value?.name),
|
publisherFeaturesProvider(currentPublisher.value?.name),
|
||||||
);
|
);
|
||||||
|
|
||||||
return AppScaffold(
|
Widget buildNavigationWidget(bool isWide) {
|
||||||
appBar: AppBar(
|
final leftItems = [
|
||||||
leading: !isWide ? const PageBackButton() : null,
|
|
||||||
title: Text('creatorHub').tr(),
|
|
||||||
actions: [
|
|
||||||
DropdownButtonHideUnderline(
|
|
||||||
child: DropdownButton2<SnPublisher>(
|
|
||||||
alignment: Alignment.centerRight,
|
|
||||||
value: currentPublisher.value,
|
|
||||||
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) {
|
|
||||||
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),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
body: publisherStats.when(
|
|
||||||
data:
|
|
||||||
(stats) => SingleChildScrollView(
|
|
||||||
child:
|
|
||||||
currentPublisher.value == null
|
|
||||||
? Column(
|
|
||||||
children: [
|
|
||||||
const Gap(24),
|
|
||||||
const Icon(Symbols.info, size: 32).padding(bottom: 4),
|
|
||||||
Text(
|
|
||||||
'creatorHubUnselectedHint',
|
|
||||||
textAlign: TextAlign.center,
|
|
||||||
).tr(),
|
|
||||||
const Gap(24),
|
|
||||||
const Divider(height: 1),
|
|
||||||
...(publishers.value?.map(
|
|
||||||
(publisher) => ListTile(
|
|
||||||
leading: ProfilePictureWidget(
|
|
||||||
file: publisher.picture,
|
|
||||||
),
|
|
||||||
title: Text(publisher.nick),
|
|
||||||
subtitle: Text('@${publisher.name}'),
|
|
||||||
onTap: () {
|
|
||||||
currentPublisher.value = publisher;
|
|
||||||
},
|
|
||||||
),
|
|
||||||
) ??
|
|
||||||
[]),
|
|
||||||
ListTile(
|
ListTile(
|
||||||
leading: const CircleAvatar(
|
shape: RoundedRectangleBorder(
|
||||||
child: Icon(Symbols.mail),
|
borderRadius: const BorderRadius.all(Radius.circular(8)),
|
||||||
),
|
),
|
||||||
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(
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
),
|
|
||||||
],
|
|
||||||
)
|
|
||||||
: Column(
|
|
||||||
children: [
|
|
||||||
if (stats != null)
|
|
||||||
_PublisherStatsWidget(
|
|
||||||
stats: stats,
|
|
||||||
).padding(vertical: 12, horizontal: 12),
|
|
||||||
ListTile(
|
|
||||||
minTileHeight: 48,
|
minTileHeight: 48,
|
||||||
title: Text('stickers').tr(),
|
title: Text('stickers').tr(),
|
||||||
trailing: Icon(Symbols.chevron_right),
|
trailing: Icon(Symbols.chevron_right),
|
||||||
leading: const Icon(Symbols.ar_stickers),
|
leading: const Icon(Symbols.ar_stickers),
|
||||||
contentPadding: EdgeInsets.symmetric(
|
|
||||||
horizontal: 24,
|
|
||||||
),
|
|
||||||
onTap: () {
|
onTap: () {
|
||||||
context.pushNamed(
|
context.pushNamed(
|
||||||
'creatorStickers',
|
'creatorStickers',
|
||||||
pathParameters: {
|
pathParameters: {'name': currentPublisher.value!.name},
|
||||||
'name': currentPublisher.value!.name,
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
ListTile(
|
ListTile(
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: const BorderRadius.all(Radius.circular(8)),
|
||||||
|
),
|
||||||
minTileHeight: 48,
|
minTileHeight: 48,
|
||||||
title: Text('posts').tr(),
|
title: Text('posts').tr(),
|
||||||
trailing: Icon(Symbols.chevron_right),
|
trailing: Icon(Symbols.chevron_right),
|
||||||
leading: const Icon(Symbols.sticky_note_2),
|
leading: const Icon(Symbols.sticky_note_2),
|
||||||
contentPadding: EdgeInsets.symmetric(
|
|
||||||
horizontal: 24,
|
|
||||||
),
|
|
||||||
onTap: () {
|
onTap: () {
|
||||||
context.pushNamed(
|
context.pushNamed(
|
||||||
'creatorPosts',
|
'creatorPosts',
|
||||||
pathParameters: {
|
pathParameters: {'name': currentPublisher.value!.name},
|
||||||
'name': currentPublisher.value!.name,
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
ListTile(
|
ListTile(
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: const BorderRadius.all(Radius.circular(8)),
|
||||||
|
),
|
||||||
minTileHeight: 48,
|
minTileHeight: 48,
|
||||||
title: Text('polls').tr(),
|
title: Text('polls').tr(),
|
||||||
trailing: const Icon(Symbols.chevron_right),
|
trailing: const Icon(Symbols.chevron_right),
|
||||||
leading: const Icon(Symbols.poll),
|
leading: const Icon(Symbols.poll),
|
||||||
contentPadding: const EdgeInsets.symmetric(
|
|
||||||
horizontal: 24,
|
|
||||||
),
|
|
||||||
onTap: () {
|
onTap: () {
|
||||||
context.pushNamed(
|
context.pushNamed(
|
||||||
'creatorPolls',
|
'creatorPolls',
|
||||||
pathParameters: {
|
pathParameters: {'name': currentPublisher.value!.name},
|
||||||
'name': currentPublisher.value!.name,
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
ListTile(
|
ListTile(
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: const BorderRadius.all(Radius.circular(8)),
|
||||||
|
),
|
||||||
minTileHeight: 48,
|
minTileHeight: 48,
|
||||||
title: Text('publisherMembers').tr(),
|
title: Text('publisherMembers').tr(),
|
||||||
trailing: const Icon(Symbols.chevron_right),
|
trailing: const Icon(Symbols.chevron_right),
|
||||||
leading: const Icon(Symbols.group),
|
leading: const Icon(Symbols.group),
|
||||||
contentPadding: const EdgeInsets.symmetric(
|
|
||||||
horizontal: 24,
|
|
||||||
),
|
|
||||||
onTap: () {
|
onTap: () {
|
||||||
showModalBottomSheet(
|
showModalBottomSheet(
|
||||||
isScrollControlled: true,
|
isScrollControlled: true,
|
||||||
context: context,
|
context: context,
|
||||||
builder:
|
builder:
|
||||||
(context) => _PublisherMemberListSheet(
|
(context) => _PublisherMemberListSheet(
|
||||||
publisherUname:
|
publisherUname: currentPublisher.value!.name,
|
||||||
currentPublisher.value!.name,
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
];
|
||||||
|
|
||||||
|
final rightItems = [
|
||||||
ListTile(
|
ListTile(
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: const BorderRadius.all(Radius.circular(8)),
|
||||||
|
),
|
||||||
minTileHeight: 48,
|
minTileHeight: 48,
|
||||||
title: const Text('webFeeds').tr(),
|
title: const Text('webFeeds').tr(),
|
||||||
trailing: const Icon(Symbols.chevron_right),
|
trailing: const Icon(Symbols.chevron_right),
|
||||||
leading: const Icon(Symbols.rss_feed),
|
leading: const Icon(Symbols.rss_feed),
|
||||||
contentPadding: const EdgeInsets.symmetric(
|
|
||||||
horizontal: 24,
|
|
||||||
),
|
|
||||||
onTap: () {
|
onTap: () {
|
||||||
context.push(
|
context.push('/creators/${currentPublisher.value!.name}/feeds');
|
||||||
'/creators/${currentPublisher.value!.name}/feeds',
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
ExpansionTile(
|
ExpansionTile(
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: const BorderRadius.all(Radius.circular(8)),
|
||||||
|
),
|
||||||
title: Text('publisherFeatures').tr(),
|
title: Text('publisherFeatures').tr(),
|
||||||
leading: const Icon(Symbols.flag),
|
leading: const Icon(Symbols.flag),
|
||||||
tilePadding: EdgeInsets.symmetric(horizontal: 24),
|
tilePadding: const EdgeInsets.only(left: 16, right: 24),
|
||||||
minTileHeight: 48,
|
minTileHeight: 48,
|
||||||
children: [
|
children: [
|
||||||
...publisherFeatures.when(
|
...publisherFeatures.when(
|
||||||
@@ -437,28 +429,19 @@ class CreatorHubScreen extends HookConsumerWidget {
|
|||||||
'publisherFeature${entry.key.capitalizeEachWord()}';
|
'publisherFeature${entry.key.capitalizeEachWord()}';
|
||||||
return ListTile(
|
return ListTile(
|
||||||
minTileHeight: 48,
|
minTileHeight: 48,
|
||||||
contentPadding: EdgeInsets.symmetric(
|
contentPadding: const EdgeInsets.symmetric(horizontal: 16),
|
||||||
horizontal: 24,
|
|
||||||
),
|
|
||||||
leading: Icon(
|
leading: Icon(
|
||||||
Symbols.circle,
|
Symbols.circle,
|
||||||
color:
|
color: entry.value ? Colors.green : Colors.red,
|
||||||
entry.value
|
|
||||||
? Colors.green
|
|
||||||
: Colors.red,
|
|
||||||
fill: 1,
|
fill: 1,
|
||||||
size: 16,
|
size: 16,
|
||||||
).padding(left: 2, top: 4),
|
).padding(left: 2, top: 4),
|
||||||
title: Text(keyPrefix).tr(),
|
title: Text(keyPrefix).tr(),
|
||||||
subtitle: Column(
|
subtitle: Column(
|
||||||
crossAxisAlignment:
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
CrossAxisAlignment.start,
|
|
||||||
children: [
|
children: [
|
||||||
Text('${keyPrefix}Description').tr(),
|
Text('${keyPrefix}Description').tr(),
|
||||||
if (!entry.value)
|
if (!entry.value) Text('${keyPrefix}Hint').tr().bold(),
|
||||||
Text(
|
|
||||||
'${keyPrefix}Hint',
|
|
||||||
).tr().bold(),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
isThreeLine: true,
|
isThreeLine: true,
|
||||||
@@ -470,37 +453,131 @@ class CreatorHubScreen extends HookConsumerWidget {
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
Divider(height: 1).padding(vertical: 8),
|
|
||||||
ListTile(
|
ListTile(
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: const BorderRadius.all(Radius.circular(8)),
|
||||||
|
),
|
||||||
minTileHeight: 48,
|
minTileHeight: 48,
|
||||||
title: Text('editPublisher').tr(),
|
title: Text('editPublisher').tr(),
|
||||||
trailing: Icon(Symbols.chevron_right),
|
trailing: Icon(Symbols.chevron_right),
|
||||||
leading: const Icon(Symbols.edit),
|
leading: const Icon(Symbols.edit),
|
||||||
contentPadding: EdgeInsets.symmetric(
|
onTap: updatePublisher,
|
||||||
horizontal: 24,
|
|
||||||
),
|
|
||||||
onTap: () {
|
|
||||||
updatePublisher();
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
ListTile(
|
ListTile(
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: const BorderRadius.all(Radius.circular(8)),
|
||||||
|
),
|
||||||
minTileHeight: 48,
|
minTileHeight: 48,
|
||||||
title: Text('deletePublisher').tr(),
|
title: Text('deletePublisher').tr(),
|
||||||
trailing: Icon(Symbols.chevron_right),
|
trailing: Icon(Symbols.chevron_right),
|
||||||
leading: const Icon(Symbols.delete),
|
leading: const Icon(Symbols.delete),
|
||||||
contentPadding: EdgeInsets.symmetric(
|
onTap: deletePublisher,
|
||||||
horizontal: 24,
|
|
||||||
),
|
),
|
||||||
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(
|
||||||
|
isNoBackground: false,
|
||||||
|
appBar: AppBar(
|
||||||
|
leading: const PageBackButton(),
|
||||||
|
title: Text('creatorHub').tr(),
|
||||||
|
actions: [
|
||||||
|
if (!isWideScreen(context))
|
||||||
|
PublisherSelector(
|
||||||
|
currentPublisher: currentPublisher.value,
|
||||||
|
publishersMenu: publishersMenu,
|
||||||
|
onChanged: (value) {
|
||||||
|
currentPublisher.value = value;
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
const Gap(8),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
body: LayoutBuilder(
|
||||||
|
builder: (context, constraints) {
|
||||||
|
final isWide = isWideScreen(context);
|
||||||
|
final maxWidth = isWide ? 800.0 : double.infinity;
|
||||||
|
|
||||||
|
return Center(
|
||||||
|
child: ConstrainedBox(
|
||||||
|
constraints: BoxConstraints(maxWidth: maxWidth),
|
||||||
|
child: publisherStats.when(
|
||||||
|
data:
|
||||||
|
(stats) => SingleChildScrollView(
|
||||||
|
child:
|
||||||
|
currentPublisher.value == null
|
||||||
|
? ConstrainedBox(
|
||||||
|
constraints: BoxConstraints(maxWidth: 640),
|
||||||
|
child: _PublisherUnselectedWidget(
|
||||||
|
onPublisherSelected: (publisher) {
|
||||||
|
currentPublisher.value = publisher;
|
||||||
|
},
|
||||||
|
),
|
||||||
|
).center()
|
||||||
|
: isWide
|
||||||
|
? Column(
|
||||||
|
spacing: 8,
|
||||||
|
children: [
|
||||||
|
PublisherSelector(
|
||||||
|
currentPublisher: currentPublisher.value,
|
||||||
|
publishersMenu: publishersMenu,
|
||||||
|
onChanged: (value) {
|
||||||
|
currentPublisher.value = value;
|
||||||
|
},
|
||||||
|
),
|
||||||
|
if (stats != null)
|
||||||
|
_PublisherStatsWidget(
|
||||||
|
stats: stats,
|
||||||
|
).padding(horizontal: 12),
|
||||||
|
buildNavigationWidget(true),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
: Column(
|
||||||
|
spacing: 12,
|
||||||
|
children: [
|
||||||
|
if (stats != null)
|
||||||
|
_PublisherStatsWidget(
|
||||||
|
stats: stats,
|
||||||
|
).padding(horizontal: 16),
|
||||||
|
buildNavigationWidget(false),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
loading: () => const Center(child: CircularProgressIndicator()),
|
loading: () => const Center(child: CircularProgressIndicator()),
|
||||||
error: (_, _) => const SizedBox.shrink(),
|
error: (_, _) => const SizedBox.shrink(),
|
||||||
),
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -576,6 +653,18 @@ 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: Tooltip(
|
||||||
|
richMessage: TextSpan(
|
||||||
|
text: statLabel.tr(),
|
||||||
|
style: TextStyle(fontWeight: FontWeight.bold),
|
||||||
|
children: [
|
||||||
|
TextSpan(text: ' '),
|
||||||
|
TextSpan(
|
||||||
|
text: statValue,
|
||||||
|
style: TextStyle(fontWeight: FontWeight.normal),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
@@ -583,6 +672,8 @@ class _PublisherStatsWidget extends StatelessWidget {
|
|||||||
Text(
|
Text(
|
||||||
statValue,
|
statValue,
|
||||||
style: Theme.of(context).textTheme.headlineMedium,
|
style: Theme.of(context).textTheme.headlineMedium,
|
||||||
|
maxLines: 1,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
),
|
),
|
||||||
const Gap(4),
|
const Gap(4),
|
||||||
Text(
|
Text(
|
||||||
@@ -594,6 +685,7 @@ class _PublisherStatsWidget extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -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),
|
||||||
|
@@ -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),
|
||||||
|
@@ -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(),
|
||||||
|
@@ -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(),
|
||||||
),
|
),
|
||||||
|
@@ -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(),
|
||||||
|
@@ -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: [
|
||||||
|
@@ -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),
|
||||||
|
@@ -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: [
|
||||||
|
Reference in New Issue
Block a user