♻️ Refactored cloud file picker, stickers
This commit is contained in:
@@ -43,7 +43,6 @@ import 'package:island/screens/chat/search_messages.dart';
|
|||||||
import 'package:island/screens/creators/hub.dart';
|
import 'package:island/screens/creators/hub.dart';
|
||||||
import 'package:island/screens/creators/posts/post_manage_list.dart';
|
import 'package:island/screens/creators/posts/post_manage_list.dart';
|
||||||
import 'package:island/screens/creators/stickers/stickers.dart';
|
import 'package:island/screens/creators/stickers/stickers.dart';
|
||||||
import 'package:island/screens/creators/stickers/pack_detail.dart';
|
|
||||||
import 'package:island/screens/stickers/sticker_marketplace.dart';
|
import 'package:island/screens/stickers/sticker_marketplace.dart';
|
||||||
import 'package:island/screens/stickers/pack_detail.dart';
|
import 'package:island/screens/stickers/pack_detail.dart';
|
||||||
import 'package:island/screens/discovery/feeds/feed_marketplace.dart';
|
import 'package:island/screens/discovery/feeds/feed_marketplace.dart';
|
||||||
@@ -232,49 +231,6 @@ final routerProvider = Provider<GoRouter>((ref) {
|
|||||||
return StickersScreen(pubName: name);
|
return StickersScreen(pubName: name);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
GoRoute(
|
|
||||||
name: 'creatorStickerPackNew',
|
|
||||||
path: ':name/stickers/new',
|
|
||||||
builder: (context, state) {
|
|
||||||
final name = state.pathParameters['name']!;
|
|
||||||
return NewStickerPacksScreen(pubName: name);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
GoRoute(
|
|
||||||
name: 'creatorStickerPackEdit',
|
|
||||||
path: ':name/stickers/:packId/edit',
|
|
||||||
builder: (context, state) {
|
|
||||||
final name = state.pathParameters['name']!;
|
|
||||||
final packId = state.pathParameters['packId']!;
|
|
||||||
return EditStickerPacksScreen(pubName: name, packId: packId);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
GoRoute(
|
|
||||||
name: 'creatorStickerPackDetail',
|
|
||||||
path: ':name/stickers/:packId',
|
|
||||||
builder: (context, state) {
|
|
||||||
final name = state.pathParameters['name']!;
|
|
||||||
final packId = state.pathParameters['packId']!;
|
|
||||||
return StickerPackDetailScreen(pubName: name, id: packId);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
GoRoute(
|
|
||||||
name: 'creatorStickerNew',
|
|
||||||
path: ':name/stickers/:packId/new',
|
|
||||||
builder: (context, state) {
|
|
||||||
final packId = state.pathParameters['packId']!;
|
|
||||||
return NewStickersScreen(packId: packId);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
GoRoute(
|
|
||||||
name: 'creatorStickerEdit',
|
|
||||||
path: ':name/stickers/:packId/:id/edit',
|
|
||||||
builder: (context, state) {
|
|
||||||
final packId = state.pathParameters['packId']!;
|
|
||||||
final id = state.pathParameters['id']!;
|
|
||||||
return EditStickersScreen(id: id, packId: packId);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
GoRoute(
|
GoRoute(
|
||||||
name: 'creatorNew',
|
name: 'creatorNew',
|
||||||
path: 'new',
|
path: 'new',
|
||||||
|
@@ -8,13 +8,14 @@ import 'package:freezed_annotation/freezed_annotation.dart';
|
|||||||
import 'package:gap/gap.dart';
|
import 'package:gap/gap.dart';
|
||||||
import 'package:google_fonts/google_fonts.dart';
|
import 'package:google_fonts/google_fonts.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
|
import 'package:island/models/file.dart';
|
||||||
import 'package:island/models/sticker.dart';
|
import 'package:island/models/sticker.dart';
|
||||||
import 'package:island/pods/network.dart';
|
import 'package:island/pods/network.dart';
|
||||||
import 'package:island/screens/creators/stickers/stickers.dart';
|
import 'package:island/screens/creators/stickers/stickers.dart';
|
||||||
import 'package:island/widgets/alert.dart';
|
import 'package:island/widgets/alert.dart';
|
||||||
import 'package:island/widgets/app_scaffold.dart';
|
|
||||||
import 'package:island/widgets/content/cloud_file_picker.dart';
|
import 'package:island/widgets/content/cloud_file_picker.dart';
|
||||||
import 'package:island/widgets/content/cloud_files.dart';
|
import 'package:island/widgets/content/cloud_files.dart';
|
||||||
|
import 'package:island/widgets/content/sheet.dart';
|
||||||
import 'package:material_symbols_icons/symbols.dart';
|
import 'package:material_symbols_icons/symbols.dart';
|
||||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||||
import 'package:styled_widget/styled_widget.dart';
|
import 'package:styled_widget/styled_widget.dart';
|
||||||
@@ -33,13 +34,13 @@ Future<List<SnSticker>> stickerPackContent(Ref ref, String packId) async {
|
|||||||
.toList();
|
.toList();
|
||||||
}
|
}
|
||||||
|
|
||||||
class StickerPackDetailScreen extends HookConsumerWidget {
|
class StickerPackDetailContent extends HookConsumerWidget {
|
||||||
final String id;
|
final String id;
|
||||||
final String pubName;
|
final String pubName;
|
||||||
const StickerPackDetailScreen({
|
const StickerPackDetailContent({
|
||||||
super.key,
|
super.key,
|
||||||
required this.pubName,
|
|
||||||
required this.id,
|
required this.id,
|
||||||
|
required this.pubName,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -67,35 +68,7 @@ class StickerPackDetailScreen extends HookConsumerWidget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return AppScaffold(
|
return pack.when(
|
||||||
isNoBackground: false,
|
|
||||||
appBar: AppBar(
|
|
||||||
title: Text(pack.value?.name ?? 'loading'.tr()),
|
|
||||||
actions: [
|
|
||||||
IconButton(
|
|
||||||
icon: const Icon(Symbols.add_circle),
|
|
||||||
onPressed: () {
|
|
||||||
context
|
|
||||||
.pushNamed(
|
|
||||||
'creatorStickerNew',
|
|
||||||
pathParameters: {'name': pubName, 'packId': id},
|
|
||||||
)
|
|
||||||
.then((value) {
|
|
||||||
if (value != null) {
|
|
||||||
ref.invalidate(stickerPackContentProvider(id));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
),
|
|
||||||
_StickerPackActionMenu(
|
|
||||||
pubName: pubName,
|
|
||||||
packId: id,
|
|
||||||
iconShadow: Shadow(),
|
|
||||||
),
|
|
||||||
const Gap(8),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
body: pack.when(
|
|
||||||
data:
|
data:
|
||||||
(pack) => Column(
|
(pack) => Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
@@ -125,10 +98,13 @@ class StickerPackDetailScreen extends HookConsumerWidget {
|
|||||||
spacing: 4,
|
spacing: 4,
|
||||||
children: [
|
children: [
|
||||||
const Icon(Symbols.tag, size: 16),
|
const Icon(Symbols.tag, size: 16),
|
||||||
SelectableText(
|
Flexible(
|
||||||
|
child: SelectableText(
|
||||||
pack.id,
|
pack.id,
|
||||||
|
maxLines: 1,
|
||||||
style: GoogleFonts.robotoMono(),
|
style: GoogleFonts.robotoMono(),
|
||||||
),
|
),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
).opacity(0.85),
|
).opacity(0.85),
|
||||||
],
|
],
|
||||||
@@ -149,7 +125,7 @@ class StickerPackDetailScreen extends HookConsumerWidget {
|
|||||||
),
|
),
|
||||||
gridDelegate:
|
gridDelegate:
|
||||||
const SliverGridDelegateWithMaxCrossAxisExtent(
|
const SliverGridDelegateWithMaxCrossAxisExtent(
|
||||||
maxCrossAxisExtent: 48,
|
maxCrossAxisExtent: 80,
|
||||||
mainAxisSpacing: 8,
|
mainAxisSpacing: 8,
|
||||||
crossAxisSpacing: 8,
|
crossAxisSpacing: 8,
|
||||||
),
|
),
|
||||||
@@ -167,7 +143,7 @@ class StickerPackDetailScreen extends HookConsumerWidget {
|
|||||||
Clipboard.setData(
|
Clipboard.setData(
|
||||||
ClipboardData(
|
ClipboardData(
|
||||||
text:
|
text:
|
||||||
':${pack.prefix}${sticker.slug}:',
|
':${pack.prefix}+${sticker.slug}:',
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
@@ -177,21 +153,21 @@ class StickerPackDetailScreen extends HookConsumerWidget {
|
|||||||
title: 'edit'.tr(),
|
title: 'edit'.tr(),
|
||||||
image: MenuImage.icon(Symbols.edit),
|
image: MenuImage.icon(Symbols.edit),
|
||||||
callback: () {
|
callback: () {
|
||||||
context
|
showModalBottomSheet(
|
||||||
.pushNamed(
|
context: context,
|
||||||
'creatorStickerEdit',
|
isScrollControlled: true,
|
||||||
pathParameters: {
|
builder:
|
||||||
'name': pubName,
|
(context) => SheetScaffold(
|
||||||
'packId': id,
|
titleText: 'editSticker'.tr(),
|
||||||
'id': sticker.id,
|
child: StickerForm(
|
||||||
},
|
packId: id,
|
||||||
)
|
id: sticker.id,
|
||||||
.then((value) {
|
),
|
||||||
|
),
|
||||||
|
).then((value) {
|
||||||
if (value != null) {
|
if (value != null) {
|
||||||
ref.invalidate(
|
ref.invalidate(
|
||||||
stickerPackContentProvider(
|
stickerPackContentProvider(id),
|
||||||
id,
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -223,6 +199,7 @@ class StickerPackDetailScreen extends HookConsumerWidget {
|
|||||||
),
|
),
|
||||||
child: CloudImageWidget(
|
child: CloudImageWidget(
|
||||||
fileId: sticker.imageId,
|
fileId: sticker.imageId,
|
||||||
|
fit: BoxFit.contain,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -244,17 +221,17 @@ class StickerPackDetailScreen extends HookConsumerWidget {
|
|||||||
(err, _) =>
|
(err, _) =>
|
||||||
Text('Error: $err').textAlignment(TextAlign.center).center(),
|
Text('Error: $err').textAlignment(TextAlign.center).center(),
|
||||||
loading: () => const CircularProgressIndicator().center(),
|
loading: () => const CircularProgressIndicator().center(),
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class _StickerPackActionMenu extends HookConsumerWidget {
|
class StickerPackActionMenu extends HookConsumerWidget {
|
||||||
final String pubName;
|
final String pubName;
|
||||||
final String packId;
|
final String packId;
|
||||||
final Shadow iconShadow;
|
final Shadow iconShadow;
|
||||||
|
|
||||||
const _StickerPackActionMenu({
|
const StickerPackActionMenu({
|
||||||
|
super.key,
|
||||||
required this.pubName,
|
required this.pubName,
|
||||||
required this.packId,
|
required this.packId,
|
||||||
required this.iconShadow,
|
required this.iconShadow,
|
||||||
@@ -268,7 +245,22 @@ class _StickerPackActionMenu extends HookConsumerWidget {
|
|||||||
(context) => [
|
(context) => [
|
||||||
PopupMenuItem(
|
PopupMenuItem(
|
||||||
onTap: () {
|
onTap: () {
|
||||||
context.push('/creators/$pubName/stickers/$packId/edit');
|
showModalBottomSheet(
|
||||||
|
context: context,
|
||||||
|
isScrollControlled: true,
|
||||||
|
builder:
|
||||||
|
(context) => SheetScaffold(
|
||||||
|
titleText: 'editStickerPack'.tr(),
|
||||||
|
child: StickerPackForm(
|
||||||
|
pubName: pubName,
|
||||||
|
packId: packId,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
).then((value) {
|
||||||
|
if (value != null) {
|
||||||
|
ref.invalidate(stickerPackProvider(packId));
|
||||||
|
}
|
||||||
|
});
|
||||||
},
|
},
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
@@ -311,42 +303,10 @@ class _StickerPackActionMenu extends HookConsumerWidget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@freezed
|
class StickerForm extends HookConsumerWidget {
|
||||||
sealed class StickerWithPackQuery with _$StickerWithPackQuery {
|
|
||||||
const factory StickerWithPackQuery({
|
|
||||||
required String packId,
|
|
||||||
required String id,
|
|
||||||
}) = _StickerWithPackQuery;
|
|
||||||
}
|
|
||||||
|
|
||||||
@riverpod
|
|
||||||
Future<SnSticker?> stickerPackSticker(
|
|
||||||
Ref ref,
|
|
||||||
StickerWithPackQuery? query,
|
|
||||||
) async {
|
|
||||||
if (query == null) return null;
|
|
||||||
final apiClient = ref.watch(apiClientProvider);
|
|
||||||
final resp = await apiClient.get(
|
|
||||||
'/sphere/stickers/${query.packId}/content/${query.id}',
|
|
||||||
);
|
|
||||||
if (resp.data == null) return null;
|
|
||||||
return SnSticker.fromJson(resp.data);
|
|
||||||
}
|
|
||||||
|
|
||||||
class NewStickersScreen extends StatelessWidget {
|
|
||||||
final String packId;
|
|
||||||
const NewStickersScreen({super.key, required this.packId});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return EditStickersScreen(packId: packId, id: null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class EditStickersScreen extends HookConsumerWidget {
|
|
||||||
final String packId;
|
final String packId;
|
||||||
final String? id;
|
final String? id;
|
||||||
const EditStickersScreen({super.key, required this.packId, required this.id});
|
const StickerForm({super.key, required this.packId, this.id});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
@@ -396,17 +356,16 @@ class EditStickersScreen extends HookConsumerWidget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return AppScaffold(
|
return Column(
|
||||||
isNoBackground: false,
|
|
||||||
appBar: AppBar(
|
|
||||||
title: Text(id == null ? 'createSticker' : 'editSticker').tr(),
|
|
||||||
),
|
|
||||||
body: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
spacing: 8,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.end,
|
||||||
children: [
|
children: [
|
||||||
SizedBox(
|
SizedBox(
|
||||||
height: 96,
|
height: 80,
|
||||||
width: 96,
|
width: 80,
|
||||||
child: ClipRRect(
|
child: ClipRRect(
|
||||||
borderRadius: BorderRadius.all(Radius.circular(8)),
|
borderRadius: BorderRadius.all(Radius.circular(8)),
|
||||||
child: Container(
|
child: Container(
|
||||||
@@ -421,45 +380,39 @@ class EditStickersScreen extends HookConsumerWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const Gap(16),
|
IconButton.filledTonal(
|
||||||
Form(
|
onPressed: () {
|
||||||
key: formKey,
|
|
||||||
child: Column(
|
|
||||||
spacing: 8,
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
|
||||||
children: [
|
|
||||||
TextFormField(
|
|
||||||
controller: imageController,
|
|
||||||
decoration: InputDecoration(
|
|
||||||
labelText: 'stickerImage'.tr(),
|
|
||||||
border: const UnderlineInputBorder(),
|
|
||||||
suffix: InkWell(
|
|
||||||
onTap: () {
|
|
||||||
showModalBottomSheet(
|
showModalBottomSheet(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (context) => CloudFilePicker(),
|
builder:
|
||||||
|
(context) => CloudFilePicker(
|
||||||
|
allowedTypes: {UniversalFileType.image},
|
||||||
|
),
|
||||||
).then((value) {
|
).then((value) {
|
||||||
if (value == null) return;
|
if (value == null) return;
|
||||||
image.value = value[0].id;
|
image.value = value[0].id;
|
||||||
imageController.text = image.value!;
|
imageController.text = image.value!;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
borderRadius: BorderRadius.all(Radius.circular(8)),
|
icon: const Icon(Symbols.cloud_upload),
|
||||||
child: const Icon(
|
|
||||||
Symbols.cloud_upload,
|
|
||||||
).padding(horizontal: 4),
|
|
||||||
),
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
readOnly: true,
|
const Gap(16),
|
||||||
onTapOutside:
|
Form(
|
||||||
(_) => FocusManager.instance.primaryFocus?.unfocus(),
|
key: formKey,
|
||||||
),
|
child: Column(
|
||||||
|
spacing: 12,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
|
children: [
|
||||||
TextFormField(
|
TextFormField(
|
||||||
controller: slugController,
|
controller: slugController,
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
labelText: 'stickerSlug'.tr(),
|
labelText: 'stickerSlug'.tr(),
|
||||||
helperText: 'stickerSlugHint'.tr(),
|
helperText: 'stickerSlugHint'.tr(),
|
||||||
border: const UnderlineInputBorder(),
|
border: const OutlineInputBorder(
|
||||||
|
borderRadius: BorderRadius.all(Radius.circular(12)),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
onTapOutside:
|
onTapOutside:
|
||||||
(_) => FocusManager.instance.primaryFocus?.unfocus(),
|
(_) => FocusManager.instance.primaryFocus?.unfocus(),
|
||||||
@@ -477,7 +430,28 @@ class EditStickersScreen extends HookConsumerWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
).padding(horizontal: 24, vertical: 24),
|
).padding(horizontal: 24, vertical: 16);
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
sealed class StickerWithPackQuery with _$StickerWithPackQuery {
|
||||||
|
const factory StickerWithPackQuery({
|
||||||
|
required String packId,
|
||||||
|
required String id,
|
||||||
|
}) = _StickerWithPackQuery;
|
||||||
|
}
|
||||||
|
|
||||||
|
@riverpod
|
||||||
|
Future<SnSticker?> stickerPackSticker(
|
||||||
|
Ref ref,
|
||||||
|
StickerWithPackQuery? query,
|
||||||
|
) async {
|
||||||
|
if (query == null) return null;
|
||||||
|
final apiClient = ref.watch(apiClientProvider);
|
||||||
|
final resp = await apiClient.get(
|
||||||
|
'/sphere/stickers/${query.packId}/content/${query.id}',
|
||||||
|
);
|
||||||
|
if (resp.data == null) return null;
|
||||||
|
return SnSticker.fromJson(resp.data);
|
||||||
|
}
|
||||||
|
@@ -1,14 +1,16 @@
|
|||||||
import 'package:dio/dio.dart';
|
import 'package:dio/dio.dart';
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:go_router/go_router.dart';
|
|
||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
import 'package:gap/gap.dart';
|
import 'package:gap/gap.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:island/models/sticker.dart';
|
import 'package:island/models/sticker.dart';
|
||||||
import 'package:island/pods/network.dart';
|
import 'package:island/pods/network.dart';
|
||||||
|
import 'package:island/services/responsive.dart';
|
||||||
import 'package:island/widgets/alert.dart';
|
import 'package:island/widgets/alert.dart';
|
||||||
import 'package:island/widgets/app_scaffold.dart';
|
import 'package:island/widgets/app_scaffold.dart';
|
||||||
|
import 'package:island/widgets/content/sheet.dart';
|
||||||
|
import 'package:island/screens/creators/stickers/pack_detail.dart';
|
||||||
import 'package:material_symbols_icons/symbols.dart';
|
import 'package:material_symbols_icons/symbols.dart';
|
||||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||||
import 'package:styled_widget/styled_widget.dart';
|
import 'package:styled_widget/styled_widget.dart';
|
||||||
@@ -22,30 +24,50 @@ class StickersScreen extends HookConsumerWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
|
final content = SliverStickerPacksList(pubName: pubName);
|
||||||
|
|
||||||
return AppScaffold(
|
return AppScaffold(
|
||||||
isNoBackground: false,
|
isNoBackground: false,
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
title: const Text('stickers').tr(),
|
title: const Text('stickers').tr(),
|
||||||
actions: [
|
actions: [const Gap(8)],
|
||||||
IconButton(
|
),
|
||||||
|
floatingActionButton: FloatingActionButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
context
|
showModalBottomSheet(
|
||||||
.pushNamed(
|
context: context,
|
||||||
'creatorStickerPackNew',
|
isScrollControlled: true,
|
||||||
pathParameters: {'name': pubName},
|
builder:
|
||||||
)
|
(context) => SheetScaffold(
|
||||||
.then((value) {
|
titleText: 'createStickerPack'.tr(),
|
||||||
|
child: StickerPackForm(pubName: pubName),
|
||||||
|
),
|
||||||
|
).then((value) {
|
||||||
if (value != null) {
|
if (value != null) {
|
||||||
ref.invalidate(stickerPacksNotifierProvider(pubName));
|
ref.invalidate(stickerPacksNotifierProvider(pubName));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
icon: const Icon(Symbols.add_circle),
|
child: const Icon(Symbols.add),
|
||||||
),
|
),
|
||||||
const Gap(8),
|
body:
|
||||||
],
|
isWideScreen(context)
|
||||||
|
? Center(
|
||||||
|
child: ConstrainedBox(
|
||||||
|
constraints: BoxConstraints(maxWidth: 640),
|
||||||
|
child: Card(
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: const BorderRadius.only(
|
||||||
|
topLeft: Radius.circular(8),
|
||||||
|
topRight: Radius.circular(8),
|
||||||
),
|
),
|
||||||
body: SliverStickerPacksList(pubName: pubName),
|
),
|
||||||
|
margin: const EdgeInsets.only(top: 16),
|
||||||
|
child: content,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: content,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -71,13 +93,52 @@ class SliverStickerPacksList extends HookConsumerWidget {
|
|||||||
|
|
||||||
final sticker = data.items[index];
|
final sticker = data.items[index];
|
||||||
return ListTile(
|
return ListTile(
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: const BorderRadius.all(Radius.circular(8)),
|
||||||
|
),
|
||||||
title: Text(sticker.name),
|
title: Text(sticker.name),
|
||||||
subtitle: Text(sticker.description),
|
subtitle: Text(sticker.description),
|
||||||
trailing: const Icon(Symbols.chevron_right),
|
trailing: const Icon(Symbols.chevron_right),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
context.pushNamed(
|
showModalBottomSheet(
|
||||||
'creatorStickerPackDetail',
|
context: context,
|
||||||
pathParameters: {'name': pubName, 'packId': sticker.id},
|
isScrollControlled: true,
|
||||||
|
builder:
|
||||||
|
(context) => SheetScaffold(
|
||||||
|
titleText: sticker.name,
|
||||||
|
actions: [
|
||||||
|
IconButton(
|
||||||
|
icon: const Icon(Symbols.add_circle),
|
||||||
|
onPressed: () {
|
||||||
|
final id = sticker.id;
|
||||||
|
showModalBottomSheet(
|
||||||
|
context: context,
|
||||||
|
isScrollControlled: true,
|
||||||
|
builder:
|
||||||
|
(context) => SheetScaffold(
|
||||||
|
titleText: 'createSticker'.tr(),
|
||||||
|
child: StickerForm(packId: id),
|
||||||
|
),
|
||||||
|
).then((value) {
|
||||||
|
if (value != null) {
|
||||||
|
ref.invalidate(
|
||||||
|
stickerPackContentProvider(id),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
),
|
||||||
|
StickerPackActionMenu(
|
||||||
|
pubName: pubName,
|
||||||
|
packId: sticker.id,
|
||||||
|
iconShadow: Shadow(),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
child: StickerPackDetailContent(
|
||||||
|
id: sticker.id,
|
||||||
|
pubName: pubName,
|
||||||
|
),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@@ -136,20 +197,10 @@ Future<SnStickerPack?> stickerPack(Ref ref, String? packId) async {
|
|||||||
return SnStickerPack.fromJson(resp.data);
|
return SnStickerPack.fromJson(resp.data);
|
||||||
}
|
}
|
||||||
|
|
||||||
class NewStickerPacksScreen extends HookConsumerWidget {
|
class StickerPackForm extends HookConsumerWidget {
|
||||||
final String pubName;
|
|
||||||
const NewStickerPacksScreen({super.key, required this.pubName});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
|
||||||
return EditStickerPacksScreen(pubName: pubName);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class EditStickerPacksScreen extends HookConsumerWidget {
|
|
||||||
final String pubName;
|
final String pubName;
|
||||||
final String? packId;
|
final String? packId;
|
||||||
const EditStickerPacksScreen({super.key, required this.pubName, this.packId});
|
const StickerPackForm({super.key, required this.pubName, this.packId});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
@@ -188,7 +239,7 @@ class EditStickerPacksScreen extends HookConsumerWidget {
|
|||||||
options: Options(method: packId == null ? 'POST' : 'PATCH'),
|
options: Options(method: packId == null ? 'POST' : 'PATCH'),
|
||||||
);
|
);
|
||||||
if (!context.mounted) return;
|
if (!context.mounted) return;
|
||||||
context.pop(SnStickerPack.fromJson(resp.data));
|
Navigator.of(context).pop(SnStickerPack.fromJson(resp.data));
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
showErrorAlert(err);
|
showErrorAlert(err);
|
||||||
} finally {
|
} finally {
|
||||||
@@ -196,25 +247,21 @@ class EditStickerPacksScreen extends HookConsumerWidget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return AppScaffold(
|
return Column(
|
||||||
isNoBackground: false,
|
|
||||||
appBar: AppBar(
|
|
||||||
title:
|
|
||||||
Text(packId == null ? 'createStickerPack' : 'editStickerPack').tr(),
|
|
||||||
),
|
|
||||||
body: Column(
|
|
||||||
children: [
|
children: [
|
||||||
Form(
|
Form(
|
||||||
key: formKey,
|
key: formKey,
|
||||||
child: Column(
|
child: Column(
|
||||||
spacing: 8,
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
|
spacing: 16,
|
||||||
children: [
|
children: [
|
||||||
TextFormField(
|
TextFormField(
|
||||||
controller: nameController,
|
controller: nameController,
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
labelText: 'name'.tr(),
|
labelText: 'name'.tr(),
|
||||||
border: const UnderlineInputBorder(),
|
border: const OutlineInputBorder(
|
||||||
|
borderRadius: BorderRadius.all(Radius.circular(12)),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
validator: (value) {
|
validator: (value) {
|
||||||
if (value == null || value.isEmpty) {
|
if (value == null || value.isEmpty) {
|
||||||
@@ -229,7 +276,9 @@ class EditStickerPacksScreen extends HookConsumerWidget {
|
|||||||
controller: descriptionController,
|
controller: descriptionController,
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
labelText: 'description'.tr(),
|
labelText: 'description'.tr(),
|
||||||
border: const UnderlineInputBorder(),
|
border: const OutlineInputBorder(
|
||||||
|
borderRadius: BorderRadius.all(Radius.circular(12)),
|
||||||
|
),
|
||||||
alignLabelWithHint: true,
|
alignLabelWithHint: true,
|
||||||
),
|
),
|
||||||
minLines: 3,
|
minLines: 3,
|
||||||
@@ -241,8 +290,10 @@ class EditStickerPacksScreen extends HookConsumerWidget {
|
|||||||
controller: prefixController,
|
controller: prefixController,
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
labelText: 'stickerPackPrefix'.tr(),
|
labelText: 'stickerPackPrefix'.tr(),
|
||||||
border: const UnderlineInputBorder(),
|
border: const OutlineInputBorder(
|
||||||
helperText: 'deleteStickerHint'.tr(),
|
borderRadius: BorderRadius.all(Radius.circular(12)),
|
||||||
|
),
|
||||||
|
helperText: 'stickerPackPrefixHint'.tr(),
|
||||||
),
|
),
|
||||||
validator: (value) {
|
validator: (value) {
|
||||||
if (value == null || value.isEmpty) {
|
if (value == null || value.isEmpty) {
|
||||||
@@ -266,7 +317,6 @@ class EditStickerPacksScreen extends HookConsumerWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
).padding(horizontal: 24, vertical: 16),
|
).padding(horizontal: 24, vertical: 16);
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -15,7 +15,16 @@ import 'package:styled_widget/styled_widget.dart';
|
|||||||
|
|
||||||
class CloudFilePicker extends HookConsumerWidget {
|
class CloudFilePicker extends HookConsumerWidget {
|
||||||
final bool allowMultiple;
|
final bool allowMultiple;
|
||||||
const CloudFilePicker({super.key, this.allowMultiple = false});
|
final Set<UniversalFileType> allowedTypes;
|
||||||
|
const CloudFilePicker({
|
||||||
|
super.key,
|
||||||
|
this.allowMultiple = false,
|
||||||
|
this.allowedTypes = const {
|
||||||
|
UniversalFileType.image,
|
||||||
|
UniversalFileType.video,
|
||||||
|
UniversalFileType.file,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
@@ -71,7 +80,7 @@ class CloudFilePicker extends HookConsumerWidget {
|
|||||||
|
|
||||||
void pickFile() async {
|
void pickFile() async {
|
||||||
showLoadingModal(context);
|
showLoadingModal(context);
|
||||||
final result = await FilePickerIO().pickFiles(
|
final result = await FilePicker.platform.pickFiles(
|
||||||
allowMultiple: allowMultiple,
|
allowMultiple: allowMultiple,
|
||||||
);
|
);
|
||||||
if (result == null) {
|
if (result == null) {
|
||||||
@@ -80,9 +89,13 @@ class CloudFilePicker extends HookConsumerWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
final newFiles =
|
final newFiles =
|
||||||
result.files
|
result.files.map((e) {
|
||||||
.map((e) => UniversalFile(data: e, type: UniversalFileType.file))
|
final xfile =
|
||||||
.toList();
|
e.bytes != null
|
||||||
|
? XFile.fromData(e.bytes!, name: e.name)
|
||||||
|
: XFile(e.path!);
|
||||||
|
return UniversalFile(data: xfile, type: UniversalFileType.file);
|
||||||
|
}).toList();
|
||||||
|
|
||||||
if (!allowMultiple) {
|
if (!allowMultiple) {
|
||||||
files.value = newFiles;
|
files.value = newFiles;
|
||||||
@@ -99,23 +112,23 @@ class CloudFilePicker extends HookConsumerWidget {
|
|||||||
|
|
||||||
void pickImage() async {
|
void pickImage() async {
|
||||||
showLoadingModal(context);
|
showLoadingModal(context);
|
||||||
final result =
|
final result = await FilePicker.platform.pickFiles(
|
||||||
allowMultiple
|
allowMultiple: allowMultiple,
|
||||||
? await ref.read(imagePickerProvider).pickMultiImage()
|
type: FileType.image,
|
||||||
: [
|
);
|
||||||
await ref
|
if (result == null || result.files.isEmpty) {
|
||||||
.read(imagePickerProvider)
|
|
||||||
.pickImage(source: ImageSource.gallery),
|
|
||||||
];
|
|
||||||
if (result.isEmpty) {
|
|
||||||
if (context.mounted) hideLoadingModal(context);
|
if (context.mounted) hideLoadingModal(context);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
final newFiles =
|
final newFiles =
|
||||||
result
|
result.files.map((e) {
|
||||||
.map((e) => UniversalFile(data: e, type: UniversalFileType.image))
|
final xfile =
|
||||||
.toList();
|
e.bytes != null
|
||||||
|
? XFile.fromData(e.bytes!, name: e.name)
|
||||||
|
: XFile(e.path!);
|
||||||
|
return UniversalFile(data: xfile, type: UniversalFileType.image);
|
||||||
|
}).toList();
|
||||||
|
|
||||||
if (!allowMultiple) {
|
if (!allowMultiple) {
|
||||||
files.value = newFiles;
|
files.value = newFiles;
|
||||||
@@ -132,21 +145,26 @@ class CloudFilePicker extends HookConsumerWidget {
|
|||||||
|
|
||||||
void pickVideo() async {
|
void pickVideo() async {
|
||||||
showLoadingModal(context);
|
showLoadingModal(context);
|
||||||
final result = await ref
|
final result = await FilePicker.platform.pickFiles(
|
||||||
.read(imagePickerProvider)
|
allowMultiple: allowMultiple,
|
||||||
.pickVideo(source: ImageSource.gallery);
|
type: FileType.video,
|
||||||
if (result == null) {
|
);
|
||||||
|
if (result == null || result.files.isEmpty) {
|
||||||
if (context.mounted) hideLoadingModal(context);
|
if (context.mounted) hideLoadingModal(context);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
final newFile = UniversalFile(
|
final newFiles =
|
||||||
data: result,
|
result.files.map((e) {
|
||||||
type: UniversalFileType.video,
|
final xfile =
|
||||||
);
|
e.bytes != null
|
||||||
|
? XFile.fromData(e.bytes!, name: e.name)
|
||||||
|
: XFile(e.path!);
|
||||||
|
return UniversalFile(data: xfile, type: UniversalFileType.video);
|
||||||
|
}).toList();
|
||||||
|
|
||||||
if (!allowMultiple) {
|
if (!allowMultiple) {
|
||||||
files.value = [newFile];
|
files.value = newFiles;
|
||||||
if (context.mounted) {
|
if (context.mounted) {
|
||||||
hideLoadingModal(context);
|
hideLoadingModal(context);
|
||||||
startUpload();
|
startUpload();
|
||||||
@@ -154,7 +172,7 @@ class CloudFilePicker extends HookConsumerWidget {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
files.value = [...files.value, newFile];
|
files.value = [...files.value, ...newFiles];
|
||||||
if (context.mounted) hideLoadingModal(context);
|
if (context.mounted) hideLoadingModal(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -252,6 +270,7 @@ class CloudFilePicker extends HookConsumerWidget {
|
|||||||
margin: EdgeInsets.zero,
|
margin: EdgeInsets.zero,
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
|
if (allowedTypes.contains(UniversalFileType.image))
|
||||||
ListTile(
|
ListTile(
|
||||||
shape: RoundedRectangleBorder(
|
shape: RoundedRectangleBorder(
|
||||||
borderRadius: BorderRadius.circular(8),
|
borderRadius: BorderRadius.circular(8),
|
||||||
@@ -260,6 +279,7 @@ class CloudFilePicker extends HookConsumerWidget {
|
|||||||
title: Text('addPhoto'.tr()),
|
title: Text('addPhoto'.tr()),
|
||||||
onTap: () => pickImage(),
|
onTap: () => pickImage(),
|
||||||
),
|
),
|
||||||
|
if (allowedTypes.contains(UniversalFileType.video))
|
||||||
ListTile(
|
ListTile(
|
||||||
shape: RoundedRectangleBorder(
|
shape: RoundedRectangleBorder(
|
||||||
borderRadius: BorderRadius.circular(8),
|
borderRadius: BorderRadius.circular(8),
|
||||||
@@ -268,6 +288,7 @@ class CloudFilePicker extends HookConsumerWidget {
|
|||||||
title: Text('addVideo'.tr()),
|
title: Text('addVideo'.tr()),
|
||||||
onTap: () => pickVideo(),
|
onTap: () => pickVideo(),
|
||||||
),
|
),
|
||||||
|
if (allowedTypes.contains(UniversalFileType.file))
|
||||||
ListTile(
|
ListTile(
|
||||||
shape: RoundedRectangleBorder(
|
shape: RoundedRectangleBorder(
|
||||||
borderRadius: BorderRadius.circular(8),
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
@@ -30,6 +30,8 @@ class SheetScaffold extends StatelessWidget {
|
|||||||
fontWeight: FontWeight.w600,
|
fontWeight: FontWeight.w600,
|
||||||
letterSpacing: -0.5,
|
letterSpacing: -0.5,
|
||||||
),
|
),
|
||||||
|
maxLines: 1,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
);
|
);
|
||||||
|
|
||||||
return Container(
|
return Container(
|
||||||
@@ -43,7 +45,7 @@ class SheetScaffold extends StatelessWidget {
|
|||||||
padding: EdgeInsets.only(top: 16, left: 20, right: 16, bottom: 12),
|
padding: EdgeInsets.only(top: 16, left: 20, right: 16, bottom: 12),
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
titleWidget,
|
Expanded(child: titleWidget),
|
||||||
const Spacer(),
|
const Spacer(),
|
||||||
...actions,
|
...actions,
|
||||||
IconButton(
|
IconButton(
|
||||||
|
Reference in New Issue
Block a user