diff --git a/lib/route.dart b/lib/route.dart index 051bd6a8..7d9cc73e 100644 --- a/lib/route.dart +++ b/lib/route.dart @@ -43,7 +43,6 @@ import 'package:island/screens/chat/search_messages.dart'; import 'package:island/screens/creators/hub.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/pack_detail.dart'; import 'package:island/screens/stickers/sticker_marketplace.dart'; import 'package:island/screens/stickers/pack_detail.dart'; import 'package:island/screens/discovery/feeds/feed_marketplace.dart'; @@ -232,49 +231,6 @@ final routerProvider = Provider((ref) { 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( name: 'creatorNew', path: 'new', diff --git a/lib/screens/creators/stickers/pack_detail.dart b/lib/screens/creators/stickers/pack_detail.dart index 54868fd6..c3bbdeeb 100644 --- a/lib/screens/creators/stickers/pack_detail.dart +++ b/lib/screens/creators/stickers/pack_detail.dart @@ -8,13 +8,14 @@ import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:gap/gap.dart'; import 'package:google_fonts/google_fonts.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:island/models/file.dart'; import 'package:island/models/sticker.dart'; import 'package:island/pods/network.dart'; import 'package:island/screens/creators/stickers/stickers.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_files.dart'; +import 'package:island/widgets/content/sheet.dart'; import 'package:material_symbols_icons/symbols.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; import 'package:styled_widget/styled_widget.dart'; @@ -33,13 +34,13 @@ Future> stickerPackContent(Ref ref, String packId) async { .toList(); } -class StickerPackDetailScreen extends HookConsumerWidget { +class StickerPackDetailContent extends HookConsumerWidget { final String id; final String pubName; - const StickerPackDetailScreen({ + const StickerPackDetailContent({ super.key, - required this.pubName, required this.id, + required this.pubName, }); @override @@ -67,194 +68,170 @@ class StickerPackDetailScreen extends HookConsumerWidget { } } - return AppScaffold( - 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: - (pack) => Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - Text(pack!.description), - Row( - spacing: 4, - children: [ - const Icon(Symbols.folder, size: 16), - Text( - '${packContent.value?.length ?? 0}/24', - style: GoogleFonts.robotoMono(), - ), - ], - ).opacity(0.85), - Row( - spacing: 4, - children: [ - const Icon(Symbols.sell, size: 16), - Text(pack.prefix, style: GoogleFonts.robotoMono()), - ], - ).opacity(0.85), - Row( - spacing: 4, - children: [ - const Icon(Symbols.tag, size: 16), - SelectableText( + return pack.when( + data: + (pack) => Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Text(pack!.description), + Row( + spacing: 4, + children: [ + const Icon(Symbols.folder, size: 16), + Text( + '${packContent.value?.length ?? 0}/24', + style: GoogleFonts.robotoMono(), + ), + ], + ).opacity(0.85), + Row( + spacing: 4, + children: [ + const Icon(Symbols.sell, size: 16), + Text(pack.prefix, style: GoogleFonts.robotoMono()), + ], + ).opacity(0.85), + Row( + spacing: 4, + children: [ + const Icon(Symbols.tag, size: 16), + Flexible( + child: SelectableText( pack.id, + maxLines: 1, style: GoogleFonts.robotoMono(), ), - ], - ).opacity(0.85), - ], - ).padding(horizontal: 24, vertical: 24), - const Divider(height: 1), - Expanded( - child: packContent.when( - data: - (stickers) => RefreshIndicator( - onRefresh: - () => ref.refresh( - stickerPackContentProvider(id).future, - ), - child: GridView.builder( - padding: const EdgeInsets.symmetric( - horizontal: 24, - vertical: 20, + ), + ], + ).opacity(0.85), + ], + ).padding(horizontal: 24, vertical: 24), + const Divider(height: 1), + Expanded( + child: packContent.when( + data: + (stickers) => RefreshIndicator( + onRefresh: + () => ref.refresh( + stickerPackContentProvider(id).future, ), - gridDelegate: - const SliverGridDelegateWithMaxCrossAxisExtent( - maxCrossAxisExtent: 48, - mainAxisSpacing: 8, - crossAxisSpacing: 8, - ), - itemCount: stickers.length, - itemBuilder: (context, index) { - final sticker = stickers[index]; - return ContextMenuWidget( - menuProvider: (_) { - return Menu( - children: [ - MenuAction( - title: 'stickerCopyPlaceholder'.tr(), - image: MenuImage.icon(Symbols.copy_all), - callback: () { - Clipboard.setData( - ClipboardData( - text: - ':${pack.prefix}${sticker.slug}:', - ), - ); - }, - ), - MenuSeparator(), - MenuAction( - title: 'edit'.tr(), - image: MenuImage.icon(Symbols.edit), - callback: () { - context - .pushNamed( - 'creatorStickerEdit', - pathParameters: { - 'name': pubName, - 'packId': id, - 'id': sticker.id, - }, - ) - .then((value) { - if (value != null) { - ref.invalidate( - stickerPackContentProvider( - id, - ), - ); - } - }); - }, - ), - MenuAction( - title: 'delete'.tr(), - image: MenuImage.icon(Symbols.delete), - callback: () { - deleteSticker(sticker); - }, - ), - ], - ); - }, - child: ClipRRect( - borderRadius: BorderRadius.all( - Radius.circular(8), - ), - child: Container( - decoration: BoxDecoration( - color: - Theme.of( - context, - ).colorScheme.surfaceContainer, - borderRadius: BorderRadius.all( - Radius.circular(8), - ), - ), - child: CloudImageWidget( - fileId: sticker.imageId, - ), - ), - ), - ); - }, + child: GridView.builder( + padding: const EdgeInsets.symmetric( + horizontal: 24, + vertical: 20, ), + gridDelegate: + const SliverGridDelegateWithMaxCrossAxisExtent( + maxCrossAxisExtent: 80, + mainAxisSpacing: 8, + crossAxisSpacing: 8, + ), + itemCount: stickers.length, + itemBuilder: (context, index) { + final sticker = stickers[index]; + return ContextMenuWidget( + menuProvider: (_) { + return Menu( + children: [ + MenuAction( + title: 'stickerCopyPlaceholder'.tr(), + image: MenuImage.icon(Symbols.copy_all), + callback: () { + Clipboard.setData( + ClipboardData( + text: + ':${pack.prefix}+${sticker.slug}:', + ), + ); + }, + ), + MenuSeparator(), + MenuAction( + title: 'edit'.tr(), + image: MenuImage.icon(Symbols.edit), + callback: () { + showModalBottomSheet( + context: context, + isScrollControlled: true, + builder: + (context) => SheetScaffold( + titleText: 'editSticker'.tr(), + child: StickerForm( + packId: id, + id: sticker.id, + ), + ), + ).then((value) { + if (value != null) { + ref.invalidate( + stickerPackContentProvider(id), + ); + } + }); + }, + ), + MenuAction( + title: 'delete'.tr(), + image: MenuImage.icon(Symbols.delete), + callback: () { + deleteSticker(sticker); + }, + ), + ], + ); + }, + child: ClipRRect( + borderRadius: BorderRadius.all( + Radius.circular(8), + ), + child: Container( + decoration: BoxDecoration( + color: + Theme.of( + context, + ).colorScheme.surfaceContainer, + borderRadius: BorderRadius.all( + Radius.circular(8), + ), + ), + child: CloudImageWidget( + fileId: sticker.imageId, + fit: BoxFit.contain, + ), + ), + ), + ); + }, ), - error: - (err, _) => - Text( - 'Error: $err', - ).textAlignment(TextAlign.center).center(), - loading: () => const CircularProgressIndicator().center(), - ), + ), + error: + (err, _) => + Text( + 'Error: $err', + ).textAlignment(TextAlign.center).center(), + loading: () => const CircularProgressIndicator().center(), ), - ], - ), - error: - (err, _) => - Text('Error: $err').textAlignment(TextAlign.center).center(), - loading: () => const CircularProgressIndicator().center(), - ), + ), + ], + ), + error: + (err, _) => + Text('Error: $err').textAlignment(TextAlign.center).center(), + loading: () => const CircularProgressIndicator().center(), ); } } -class _StickerPackActionMenu extends HookConsumerWidget { +class StickerPackActionMenu extends HookConsumerWidget { final String pubName; final String packId; final Shadow iconShadow; - const _StickerPackActionMenu({ + const StickerPackActionMenu({ + super.key, required this.pubName, required this.packId, required this.iconShadow, @@ -268,7 +245,22 @@ class _StickerPackActionMenu extends HookConsumerWidget { (context) => [ PopupMenuItem( 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( children: [ @@ -311,42 +303,10 @@ class _StickerPackActionMenu extends HookConsumerWidget { } } -@freezed -sealed class StickerWithPackQuery with _$StickerWithPackQuery { - const factory StickerWithPackQuery({ - required String packId, - required String id, - }) = _StickerWithPackQuery; -} - -@riverpod -Future 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 { +class StickerForm extends HookConsumerWidget { final String packId; final String? id; - const EditStickersScreen({super.key, required this.packId, required this.id}); + const StickerForm({super.key, required this.packId, this.id}); @override Widget build(BuildContext context, WidgetRef ref) { @@ -396,88 +356,102 @@ class EditStickersScreen extends HookConsumerWidget { } } - return AppScaffold( - isNoBackground: false, - appBar: AppBar( - title: Text(id == null ? 'createSticker' : 'editSticker').tr(), - ), - body: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - SizedBox( - height: 96, - width: 96, - child: ClipRRect( - borderRadius: BorderRadius.all(Radius.circular(8)), - child: Container( - decoration: BoxDecoration( - color: Theme.of(context).colorScheme.surfaceContainer, - borderRadius: BorderRadius.all(Radius.circular(8)), + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + spacing: 8, + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + SizedBox( + height: 80, + width: 80, + child: ClipRRect( + borderRadius: BorderRadius.all(Radius.circular(8)), + child: Container( + decoration: BoxDecoration( + color: Theme.of(context).colorScheme.surfaceContainer, + borderRadius: BorderRadius.all(Radius.circular(8)), + ), + child: + (image.value?.isEmpty ?? true) + ? const SizedBox.shrink() + : CloudImageWidget(fileId: image.value!), ), - child: - (image.value?.isEmpty ?? true) - ? const SizedBox.shrink() - : CloudImageWidget(fileId: image.value!), ), ), - ), - const Gap(16), - Form( - 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( - context: context, - builder: (context) => CloudFilePicker(), - ).then((value) { - if (value == null) return; - image.value = value[0].id; - imageController.text = image.value!; - }); - }, - borderRadius: BorderRadius.all(Radius.circular(8)), - child: const Icon( - Symbols.cloud_upload, - ).padding(horizontal: 4), - ), - ), - readOnly: true, - onTapOutside: - (_) => FocusManager.instance.primaryFocus?.unfocus(), - ), - TextFormField( - controller: slugController, - decoration: InputDecoration( - labelText: 'stickerSlug'.tr(), - helperText: 'stickerSlugHint'.tr(), - border: const UnderlineInputBorder(), - ), - onTapOutside: - (_) => FocusManager.instance.primaryFocus?.unfocus(), - ), - ], + IconButton.filledTonal( + onPressed: () { + showModalBottomSheet( + context: context, + builder: + (context) => CloudFilePicker( + allowedTypes: {UniversalFileType.image}, + ), + ).then((value) { + if (value == null) return; + image.value = value[0].id; + imageController.text = image.value!; + }); + }, + icon: const Icon(Symbols.cloud_upload), ), + ], + ), + const Gap(16), + Form( + key: formKey, + child: Column( + spacing: 12, + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + TextFormField( + controller: slugController, + decoration: InputDecoration( + labelText: 'stickerSlug'.tr(), + helperText: 'stickerSlugHint'.tr(), + border: const OutlineInputBorder( + borderRadius: BorderRadius.all(Radius.circular(12)), + ), + ), + onTapOutside: + (_) => FocusManager.instance.primaryFocus?.unfocus(), + ), + ], ), - const Gap(12), - Align( - alignment: Alignment.centerRight, - child: TextButton.icon( - onPressed: submitting.value ? null : submit, - icon: const Icon(Symbols.save), - label: Text(id == null ? 'create' : 'saveChanges').tr(), - ), + ), + const Gap(12), + Align( + alignment: Alignment.centerRight, + child: TextButton.icon( + onPressed: submitting.value ? null : submit, + icon: const Icon(Symbols.save), + label: Text(id == null ? 'create' : 'saveChanges').tr(), ), - ], - ).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 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); +} diff --git a/lib/screens/creators/stickers/stickers.dart b/lib/screens/creators/stickers/stickers.dart index 45115c0e..355968da 100644 --- a/lib/screens/creators/stickers/stickers.dart +++ b/lib/screens/creators/stickers/stickers.dart @@ -1,14 +1,16 @@ import 'package:dio/dio.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; -import 'package:go_router/go_router.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:gap/gap.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:island/models/sticker.dart'; import 'package:island/pods/network.dart'; +import 'package:island/services/responsive.dart'; import 'package:island/widgets/alert.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:riverpod_annotation/riverpod_annotation.dart'; import 'package:styled_widget/styled_widget.dart'; @@ -22,30 +24,50 @@ class StickersScreen extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { + final content = SliverStickerPacksList(pubName: pubName); + return AppScaffold( isNoBackground: false, appBar: AppBar( title: const Text('stickers').tr(), - actions: [ - IconButton( - onPressed: () { - context - .pushNamed( - 'creatorStickerPackNew', - pathParameters: {'name': pubName}, - ) - .then((value) { - if (value != null) { - ref.invalidate(stickerPacksNotifierProvider(pubName)); - } - }); - }, - icon: const Icon(Symbols.add_circle), - ), - const Gap(8), - ], + actions: [const Gap(8)], ), - body: SliverStickerPacksList(pubName: pubName), + floatingActionButton: FloatingActionButton( + onPressed: () { + showModalBottomSheet( + context: context, + isScrollControlled: true, + builder: + (context) => SheetScaffold( + titleText: 'createStickerPack'.tr(), + child: StickerPackForm(pubName: pubName), + ), + ).then((value) { + if (value != null) { + ref.invalidate(stickerPacksNotifierProvider(pubName)); + } + }); + }, + child: const Icon(Symbols.add), + ), + 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), + ), + ), + margin: const EdgeInsets.only(top: 16), + child: content, + ), + ), + ) + : content, ); } } @@ -71,13 +93,52 @@ class SliverStickerPacksList extends HookConsumerWidget { final sticker = data.items[index]; return ListTile( + shape: RoundedRectangleBorder( + borderRadius: const BorderRadius.all(Radius.circular(8)), + ), title: Text(sticker.name), subtitle: Text(sticker.description), trailing: const Icon(Symbols.chevron_right), onTap: () { - context.pushNamed( - 'creatorStickerPackDetail', - pathParameters: {'name': pubName, 'packId': sticker.id}, + showModalBottomSheet( + context: context, + 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 stickerPack(Ref ref, String? packId) async { return SnStickerPack.fromJson(resp.data); } -class NewStickerPacksScreen 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 { +class StickerPackForm extends HookConsumerWidget { final String pubName; final String? packId; - const EditStickerPacksScreen({super.key, required this.pubName, this.packId}); + const StickerPackForm({super.key, required this.pubName, this.packId}); @override Widget build(BuildContext context, WidgetRef ref) { @@ -188,7 +239,7 @@ class EditStickerPacksScreen extends HookConsumerWidget { options: Options(method: packId == null ? 'POST' : 'PATCH'), ); if (!context.mounted) return; - context.pop(SnStickerPack.fromJson(resp.data)); + Navigator.of(context).pop(SnStickerPack.fromJson(resp.data)); } catch (err) { showErrorAlert(err); } finally { @@ -196,77 +247,76 @@ class EditStickerPacksScreen extends HookConsumerWidget { } } - return AppScaffold( - isNoBackground: false, - appBar: AppBar( - title: - Text(packId == null ? 'createStickerPack' : 'editStickerPack').tr(), - ), - body: Column( - children: [ - Form( - key: formKey, - child: Column( - spacing: 8, - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - TextFormField( - controller: nameController, - decoration: InputDecoration( - labelText: 'name'.tr(), - border: const UnderlineInputBorder(), + return Column( + children: [ + Form( + key: formKey, + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + spacing: 16, + children: [ + TextFormField( + controller: nameController, + decoration: InputDecoration( + labelText: 'name'.tr(), + border: const OutlineInputBorder( + borderRadius: BorderRadius.all(Radius.circular(12)), ), - validator: (value) { - if (value == null || value.isEmpty) { - return 'fieldCannotBeEmpty'.tr(); - } - return null; - }, - onTapOutside: - (_) => FocusManager.instance.primaryFocus?.unfocus(), ), - TextFormField( - controller: descriptionController, - decoration: InputDecoration( - labelText: 'description'.tr(), - border: const UnderlineInputBorder(), - alignLabelWithHint: true, + validator: (value) { + if (value == null || value.isEmpty) { + return 'fieldCannotBeEmpty'.tr(); + } + return null; + }, + onTapOutside: + (_) => FocusManager.instance.primaryFocus?.unfocus(), + ), + TextFormField( + controller: descriptionController, + decoration: InputDecoration( + labelText: 'description'.tr(), + border: const OutlineInputBorder( + borderRadius: BorderRadius.all(Radius.circular(12)), ), - minLines: 3, - maxLines: null, - onTapOutside: - (_) => FocusManager.instance.primaryFocus?.unfocus(), + alignLabelWithHint: true, ), - TextFormField( - controller: prefixController, - decoration: InputDecoration( - labelText: 'stickerPackPrefix'.tr(), - border: const UnderlineInputBorder(), - helperText: 'deleteStickerHint'.tr(), + minLines: 3, + maxLines: null, + onTapOutside: + (_) => FocusManager.instance.primaryFocus?.unfocus(), + ), + TextFormField( + controller: prefixController, + decoration: InputDecoration( + labelText: 'stickerPackPrefix'.tr(), + border: const OutlineInputBorder( + borderRadius: BorderRadius.all(Radius.circular(12)), ), - validator: (value) { - if (value == null || value.isEmpty) { - return 'fieldCannotBeEmpty'.tr(); - } - return null; - }, - onTapOutside: - (_) => FocusManager.instance.primaryFocus?.unfocus(), + helperText: 'stickerPackPrefixHint'.tr(), ), - ], - ), + validator: (value) { + if (value == null || value.isEmpty) { + return 'fieldCannotBeEmpty'.tr(); + } + return null; + }, + onTapOutside: + (_) => FocusManager.instance.primaryFocus?.unfocus(), + ), + ], ), - const Gap(12), - Align( - alignment: Alignment.centerRight, - child: TextButton.icon( - onPressed: submitting.value ? null : submit, - icon: const Icon(Symbols.save), - label: Text(packId == null ? 'create'.tr() : 'saveChanges'.tr()), - ), + ), + const Gap(12), + Align( + alignment: Alignment.centerRight, + child: TextButton.icon( + onPressed: submitting.value ? null : submit, + icon: const Icon(Symbols.save), + label: Text(packId == null ? 'create'.tr() : 'saveChanges'.tr()), ), - ], - ).padding(horizontal: 24, vertical: 16), - ); + ), + ], + ).padding(horizontal: 24, vertical: 16); } } diff --git a/lib/widgets/content/cloud_file_picker.dart b/lib/widgets/content/cloud_file_picker.dart index fd37189b..d446e5ad 100644 --- a/lib/widgets/content/cloud_file_picker.dart +++ b/lib/widgets/content/cloud_file_picker.dart @@ -15,7 +15,16 @@ import 'package:styled_widget/styled_widget.dart'; class CloudFilePicker extends HookConsumerWidget { final bool allowMultiple; - const CloudFilePicker({super.key, this.allowMultiple = false}); + final Set allowedTypes; + const CloudFilePicker({ + super.key, + this.allowMultiple = false, + this.allowedTypes = const { + UniversalFileType.image, + UniversalFileType.video, + UniversalFileType.file, + }, + }); @override Widget build(BuildContext context, WidgetRef ref) { @@ -71,7 +80,7 @@ class CloudFilePicker extends HookConsumerWidget { void pickFile() async { showLoadingModal(context); - final result = await FilePickerIO().pickFiles( + final result = await FilePicker.platform.pickFiles( allowMultiple: allowMultiple, ); if (result == null) { @@ -80,9 +89,13 @@ class CloudFilePicker extends HookConsumerWidget { } final newFiles = - result.files - .map((e) => UniversalFile(data: e, type: UniversalFileType.file)) - .toList(); + result.files.map((e) { + final xfile = + e.bytes != null + ? XFile.fromData(e.bytes!, name: e.name) + : XFile(e.path!); + return UniversalFile(data: xfile, type: UniversalFileType.file); + }).toList(); if (!allowMultiple) { files.value = newFiles; @@ -99,23 +112,23 @@ class CloudFilePicker extends HookConsumerWidget { void pickImage() async { showLoadingModal(context); - final result = - allowMultiple - ? await ref.read(imagePickerProvider).pickMultiImage() - : [ - await ref - .read(imagePickerProvider) - .pickImage(source: ImageSource.gallery), - ]; - if (result.isEmpty) { + final result = await FilePicker.platform.pickFiles( + allowMultiple: allowMultiple, + type: FileType.image, + ); + if (result == null || result.files.isEmpty) { if (context.mounted) hideLoadingModal(context); return; } final newFiles = - result - .map((e) => UniversalFile(data: e, type: UniversalFileType.image)) - .toList(); + result.files.map((e) { + final xfile = + e.bytes != null + ? XFile.fromData(e.bytes!, name: e.name) + : XFile(e.path!); + return UniversalFile(data: xfile, type: UniversalFileType.image); + }).toList(); if (!allowMultiple) { files.value = newFiles; @@ -132,21 +145,26 @@ class CloudFilePicker extends HookConsumerWidget { void pickVideo() async { showLoadingModal(context); - final result = await ref - .read(imagePickerProvider) - .pickVideo(source: ImageSource.gallery); - if (result == null) { + final result = await FilePicker.platform.pickFiles( + allowMultiple: allowMultiple, + type: FileType.video, + ); + if (result == null || result.files.isEmpty) { if (context.mounted) hideLoadingModal(context); return; } - final newFile = UniversalFile( - data: result, - type: UniversalFileType.video, - ); + final newFiles = + result.files.map((e) { + 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) { - files.value = [newFile]; + files.value = newFiles; if (context.mounted) { hideLoadingModal(context); startUpload(); @@ -154,7 +172,7 @@ class CloudFilePicker extends HookConsumerWidget { return; } - files.value = [...files.value, newFile]; + files.value = [...files.value, ...newFiles]; if (context.mounted) hideLoadingModal(context); } @@ -252,30 +270,33 @@ class CloudFilePicker extends HookConsumerWidget { margin: EdgeInsets.zero, child: Column( children: [ - ListTile( - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(8), + if (allowedTypes.contains(UniversalFileType.image)) + ListTile( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8), + ), + leading: const Icon(Symbols.photo), + title: Text('addPhoto'.tr()), + onTap: () => pickImage(), ), - leading: const Icon(Symbols.photo), - title: Text('addPhoto'.tr()), - onTap: () => pickImage(), - ), - ListTile( - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(8), + if (allowedTypes.contains(UniversalFileType.video)) + ListTile( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8), + ), + leading: const Icon(Symbols.video_call), + title: Text('addVideo'.tr()), + onTap: () => pickVideo(), ), - leading: const Icon(Symbols.video_call), - title: Text('addVideo'.tr()), - onTap: () => pickVideo(), - ), - ListTile( - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(8), + if (allowedTypes.contains(UniversalFileType.file)) + ListTile( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8), + ), + leading: const Icon(Symbols.draft), + title: Text('addFile'.tr()), + onTap: () => pickFile(), ), - leading: const Icon(Symbols.draft), - title: Text('addFile'.tr()), - onTap: () => pickFile(), - ), ], ), ), diff --git a/lib/widgets/content/sheet.dart b/lib/widgets/content/sheet.dart index ee121ab5..65874ff7 100644 --- a/lib/widgets/content/sheet.dart +++ b/lib/widgets/content/sheet.dart @@ -30,6 +30,8 @@ class SheetScaffold extends StatelessWidget { fontWeight: FontWeight.w600, letterSpacing: -0.5, ), + maxLines: 1, + overflow: TextOverflow.ellipsis, ); return Container( @@ -43,7 +45,7 @@ class SheetScaffold extends StatelessWidget { padding: EdgeInsets.only(top: 16, left: 20, right: 16, bottom: 12), child: Row( children: [ - titleWidget, + Expanded(child: titleWidget), const Spacer(), ...actions, IconButton(