🐛 Fix some bugs in creator hub
This commit is contained in:
@@ -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) {
|
||||||
|
|||||||
@@ -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')),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -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(
|
||||||
|
|||||||
@@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -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(),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|||||||
Reference in New Issue
Block a user