From a0fe3f918ee3ce4cdc03ddf069568f4d47b0c4d8 Mon Sep 17 00:00:00 2001 From: LittleSheep Date: Tue, 9 Jul 2024 21:23:38 +0800 Subject: [PATCH] :sparkles: Post draft --- lib/models/post.dart | 4 + lib/router.dart | 26 +- lib/screens/articles/article_publish.dart | 247 ++++++++++++++++++ lib/screens/feed.dart | 16 +- lib/screens/feed/draft_box.dart | 33 +++ .../{feed_search.dart => feed/search.dart} | 0 lib/screens/posts/post_publish.dart | 72 ++--- lib/screens/realms/realm_view.dart | 4 +- lib/translations.dart | 17 +- .../navigation/app_navigation_bottom_bar.dart | 3 +- lib/widgets/posts/post_action.dart | 12 +- lib/widgets/posts/post_item.dart | 6 +- pubspec.lock | 20 +- pubspec.yaml | 1 + 14 files changed, 396 insertions(+), 65 deletions(-) create mode 100644 lib/screens/articles/article_publish.dart create mode 100644 lib/screens/feed/draft_box.dart rename lib/screens/{feed_search.dart => feed/search.dart} (100%) diff --git a/lib/models/post.dart b/lib/models/post.dart index eb520a3..0a85414 100755 --- a/lib/models/post.dart +++ b/lib/models/post.dart @@ -20,6 +20,7 @@ class Post { Post? repostTo; Realm? realm; DateTime? publishedAt; + bool? isDraft; int authorId; Account author; int replyCount; @@ -44,6 +45,7 @@ class Post { required this.repostTo, required this.realm, required this.publishedAt, + required this.isDraft, required this.authorId, required this.author, required this.replyCount, @@ -80,6 +82,7 @@ class Post { publishedAt: json['published_at'] != null ? DateTime.parse(json['published_at']) : null, + isDraft: json['is_draft'], authorId: json['author_id'], author: Account.fromJson(json['author']), replyCount: json['reply_count'], @@ -112,6 +115,7 @@ class Post { 'repost_to': repostTo?.toJson(), 'realm': realm?.toJson(), 'published_at': publishedAt?.toIso8601String(), + 'is_draft': isDraft, 'author_id': authorId, 'author': author.toJson(), 'reply_count': replyCount, diff --git a/lib/router.dart b/lib/router.dart index 82ea7e7..ebdc4f2 100644 --- a/lib/router.dart +++ b/lib/router.dart @@ -4,12 +4,14 @@ import 'package:solian/screens/about.dart'; import 'package:solian/screens/account.dart'; import 'package:solian/screens/account/friend.dart'; import 'package:solian/screens/account/personalize.dart'; +import 'package:solian/screens/articles/article_publish.dart'; import 'package:solian/screens/channel/channel_chat.dart'; import 'package:solian/screens/channel/channel_detail.dart'; import 'package:solian/screens/channel/channel_organize.dart'; import 'package:solian/screens/chat.dart'; -import 'package:solian/screens/feed_search.dart'; +import 'package:solian/screens/feed/search.dart'; import 'package:solian/screens/posts/post_detail.dart'; +import 'package:solian/screens/feed/draft_box.dart'; import 'package:solian/screens/realms.dart'; import 'package:solian/screens/realms/realm_detail.dart'; import 'package:solian/screens/realms/realm_organize.dart'; @@ -59,6 +61,11 @@ abstract class AppRouter { ), ), ), + GoRoute( + path: '/drafts', + name: 'draftBox', + builder: (context, state) => const DraftBoxScreen(), + ), GoRoute( path: '/posts/view/:alias', name: 'postDetail', @@ -71,10 +78,10 @@ abstract class AppRouter { ), GoRoute( path: '/posts/publish', - name: 'postPublishing', + name: 'postCreate', builder: (context, state) { - final arguments = state.extra as PostPublishingArguments?; - return PostPublishingScreen( + final arguments = state.extra as PostPublishArguments?; + return PostPublishScreen( edit: arguments?.edit, reply: arguments?.reply, repost: arguments?.repost, @@ -82,6 +89,17 @@ abstract class AppRouter { ); }, ), + GoRoute( + path: '/articles/publish', + name: 'articleCreate', + builder: (context, state) { + final arguments = state.extra as ArticlePublishArguments?; + return ArticlePublishScreen( + edit: arguments?.edit, + realm: arguments?.realm, + ); + }, + ) ], ); diff --git a/lib/screens/articles/article_publish.dart b/lib/screens/articles/article_publish.dart new file mode 100644 index 0000000..7724581 --- /dev/null +++ b/lib/screens/articles/article_publish.dart @@ -0,0 +1,247 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_animate/flutter_animate.dart'; +import 'package:get/get.dart'; +import 'package:solian/exts.dart'; +import 'package:solian/models/post.dart'; +import 'package:solian/models/realm.dart'; +import 'package:solian/providers/auth.dart'; +import 'package:solian/router.dart'; +import 'package:solian/theme.dart'; +import 'package:solian/widgets/app_bar_title.dart'; +import 'package:solian/widgets/attachments/attachment_publish.dart'; +import 'package:solian/widgets/posts/tags_field.dart'; +import 'package:solian/widgets/prev_page.dart'; +import 'package:textfield_tags/textfield_tags.dart'; + +class ArticlePublishArguments { + final Post? edit; + final Realm? realm; + + ArticlePublishArguments({this.edit, this.realm}); +} + +class ArticlePublishScreen extends StatefulWidget { + final Post? edit; + final Realm? realm; + + const ArticlePublishScreen({ + super.key, + this.edit, + this.realm, + }); + + @override + State createState() => _ArticlePublishScreenState(); +} + +class _ArticlePublishScreenState extends State { + final _titleController = TextEditingController(); + final _descriptionController = TextEditingController(); + final _contentController = TextEditingController(); + final _tagsController = StringTagController(); + + bool _isBusy = false; + + List _attachments = List.empty(); + + void showAttachments() { + showModalBottomSheet( + context: context, + isScrollControlled: true, + builder: (context) => AttachmentPublishPopup( + usage: 'i.attachment', + current: _attachments, + onUpdate: (value) => _attachments = value, + ), + ); + } + + void applyPost() async { + final AuthProvider auth = Get.find(); + if (!await auth.isAuthorized) return; + if (_contentController.value.text.isEmpty) return; + + setState(() => _isBusy = true); + + final client = auth.configureClient('interactive'); + + final payload = { + 'title': _titleController.value.text, + 'description': _descriptionController.value.text, + 'content': _contentController.value.text, + 'tags': _tagsController.getTags?.map((x) => {'alias': x}).toList() ?? + List.empty(), + 'attachments': _attachments, + if (widget.edit != null) 'alias': widget.edit!.alias, + if (widget.realm != null) 'realm': widget.realm!.alias, + }; + + Response resp; + if (widget.edit != null) { + resp = await client.put('/api/articles/${widget.edit!.id}', payload); + } else { + resp = await client.post('/api/articles', payload); + } + if (resp.statusCode != 200) { + context.showErrorDialog(resp.bodyString); + } else { + AppRouter.instance.pop(resp.body); + } + + setState(() => _isBusy = false); + } + + void syncWidget() { + if (widget.edit != null) { + _contentController.text = widget.edit!.content; + _attachments = widget.edit!.attachments ?? List.empty(); + } + } + + void cancelAction() { + AppRouter.instance.pop(); + } + + @override + void initState() { + syncWidget(); + super.initState(); + } + + @override + Widget build(BuildContext context) { + final notifyBannerActions = [ + TextButton( + onPressed: cancelAction, + child: Text('cancel'.tr), + ) + ]; + + return Material( + color: Theme.of(context).colorScheme.surface, + child: Scaffold( + appBar: AppBar( + title: AppBarTitle('articlePublish'.tr), + centerTitle: false, + toolbarHeight: SolianTheme.toolbarHeight(context), + leading: const PrevPageButton(), + actions: [ + TextButton( + onPressed: _isBusy ? null : () => applyPost(), + child: Text('postAction'.tr.toUpperCase()), + ) + ], + ), + body: SafeArea( + top: false, + child: Stack( + children: [ + ListView( + children: [ + if (_isBusy) + const LinearProgressIndicator().animate().scaleX(), + if (widget.edit != null) + MaterialBanner( + leading: const Icon(Icons.edit), + leadingPadding: + const EdgeInsets.only(left: 10, right: 20), + dividerColor: Colors.transparent, + content: Text('postEditingNotify'.tr), + actions: notifyBannerActions, + ), + if (widget.realm != null) + MaterialBanner( + leading: const Icon(Icons.group), + leadingPadding: + const EdgeInsets.only(left: 10, right: 20), + dividerColor: Colors.transparent, + content: Text( + 'postInRealmNotify' + .trParams({'realm': '#${widget.realm!.alias}'}), + ), + actions: notifyBannerActions, + ), + const Divider(thickness: 0.3, height: 0.3) + .paddingOnly(bottom: 6), + Container( + padding: + const EdgeInsets.symmetric(horizontal: 16, vertical: 8), + child: TextField( + maxLines: null, + autofocus: true, + autocorrect: true, + keyboardType: TextInputType.multiline, + controller: _titleController, + decoration: InputDecoration.collapsed( + hintText: 'articleTitlePlaceholder'.tr, + ), + onTapOutside: (_) => + FocusManager.instance.primaryFocus?.unfocus(), + ), + ), + const Divider(thickness: 0.3, height: 0.3) + .paddingSymmetric(vertical: 6), + Container( + padding: + const EdgeInsets.symmetric(horizontal: 16, vertical: 8), + child: TextField( + maxLines: null, + autofocus: true, + autocorrect: true, + keyboardType: TextInputType.multiline, + controller: _contentController, + decoration: InputDecoration.collapsed( + hintText: 'articleContentPlaceholder'.tr, + ), + onTapOutside: (_) => + FocusManager.instance.primaryFocus?.unfocus(), + ), + ), + ], + ), + Positioned( + bottom: 0, + left: 0, + right: 0, + child: Column( + children: [ + TagsField( + initialTags: + widget.edit?.tags?.map((x) => x.alias).toList(), + tagsController: _tagsController, + hintText: 'postTagsPlaceholder'.tr, + ), + const Divider(thickness: 0.3, height: 0.3), + SizedBox( + height: 56, + child: Row( + children: [ + TextButton( + style: TextButton.styleFrom( + shape: const CircleBorder(), + ), + child: const Icon(Icons.camera_alt), + onPressed: () => showAttachments(), + ) + ], + ), + ), + ], + ), + ), + ], + ), + ), + ), + ); + } + + @override + void dispose() { + _titleController.dispose(); + _descriptionController.dispose(); + _contentController.dispose(); + _tagsController.dispose(); + super.dispose(); + } +} diff --git a/lib/screens/feed.dart b/lib/screens/feed.dart index e630778..e761123 100644 --- a/lib/screens/feed.dart +++ b/lib/screens/feed.dart @@ -101,7 +101,7 @@ class FeedCreationButton extends StatelessWidget { builder: (context, snapshot) { if (snapshot.hasData && snapshot.data == true) { return PopupMenuButton( - icon: const Icon(Icons.add_circle), + icon: const Icon(Icons.edit_square), itemBuilder: (BuildContext context) => [ PopupMenuItem( child: ListTile( @@ -110,7 +110,7 @@ class FeedCreationButton extends StatelessWidget { contentPadding: const EdgeInsets.symmetric(horizontal: 8), ), onTap: () { - AppRouter.instance.pushNamed('postPublishing').then((val) { + AppRouter.instance.pushNamed('postCreate').then((val) { if (val != null && onCreated != null) { onCreated!(); } @@ -123,7 +123,13 @@ class FeedCreationButton extends StatelessWidget { leading: const Icon(Icons.newspaper), contentPadding: const EdgeInsets.symmetric(horizontal: 8), ), - onTap: () {}, + onTap: () { + AppRouter.instance.pushNamed('articleCreate').then((val) { + if (val != null && onCreated != null) { + onCreated!(); + } + }); + }, ), PopupMenuItem( child: ListTile( @@ -131,7 +137,9 @@ class FeedCreationButton extends StatelessWidget { leading: const Icon(Icons.drafts), contentPadding: const EdgeInsets.symmetric(horizontal: 8), ), - onTap: () {}, + onTap: () { + AppRouter.instance.goNamed('draftBox'); + }, ), ], ); diff --git a/lib/screens/feed/draft_box.dart b/lib/screens/feed/draft_box.dart new file mode 100644 index 0000000..c3bab6a --- /dev/null +++ b/lib/screens/feed/draft_box.dart @@ -0,0 +1,33 @@ +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:solian/screens/feed.dart'; +import 'package:solian/theme.dart'; +import 'package:solian/widgets/app_bar_title.dart'; +import 'package:solian/widgets/prev_page.dart'; + +class DraftBoxScreen extends StatelessWidget { + const DraftBoxScreen({super.key}); + + @override + Widget build(BuildContext context) { + return Material( + color: Theme.of(context).colorScheme.surface, + child: Scaffold( + appBar: AppBar( + title: AppBarTitle('draftBox'.tr), + centerTitle: false, + toolbarHeight: SolianTheme.toolbarHeight(context), + leading: const PrevPageButton(), + actions: [ + FeedCreationButton( + onCreated: () {}, + ), + SizedBox( + width: SolianTheme.isLargeScreen(context) ? 8 : 16, + ), + ], + ), + ), + ); + } +} diff --git a/lib/screens/feed_search.dart b/lib/screens/feed/search.dart similarity index 100% rename from lib/screens/feed_search.dart rename to lib/screens/feed/search.dart diff --git a/lib/screens/posts/post_publish.dart b/lib/screens/posts/post_publish.dart index 3880ef0..48ea528 100644 --- a/lib/screens/posts/post_publish.dart +++ b/lib/screens/posts/post_publish.dart @@ -8,30 +8,30 @@ import 'package:solian/models/realm.dart'; import 'package:solian/providers/auth.dart'; import 'package:solian/router.dart'; import 'package:solian/theme.dart'; -import 'package:solian/widgets/account/account_avatar.dart'; import 'package:solian/widgets/app_bar_title.dart'; import 'package:solian/widgets/attachments/attachment_publish.dart'; import 'package:solian/widgets/posts/post_item.dart'; import 'package:solian/widgets/posts/tags_field.dart'; import 'package:solian/widgets/prev_page.dart'; import 'package:textfield_tags/textfield_tags.dart'; +import 'package:badges/badges.dart' as badges; -class PostPublishingArguments { +class PostPublishArguments { final Post? edit; final Post? reply; final Post? repost; final Realm? realm; - PostPublishingArguments({this.edit, this.reply, this.repost, this.realm}); + PostPublishArguments({this.edit, this.reply, this.repost, this.realm}); } -class PostPublishingScreen extends StatefulWidget { +class PostPublishScreen extends StatefulWidget { final Post? edit; final Post? reply; final Post? repost; final Realm? realm; - const PostPublishingScreen({ + const PostPublishScreen({ super.key, this.edit, this.reply, @@ -40,10 +40,10 @@ class PostPublishingScreen extends StatefulWidget { }); @override - State createState() => _PostPublishingScreenState(); + State createState() => _PostPublishScreenState(); } -class _PostPublishingScreenState extends State { +class _PostPublishScreenState extends State { final _contentController = TextEditingController(); final _tagsController = StringTagController(); @@ -51,6 +51,8 @@ class _PostPublishingScreenState extends State { List _attachments = List.empty(); + bool _isDraft = false; + void showAttachments() { showModalBottomSheet( context: context, @@ -77,6 +79,7 @@ class _PostPublishingScreenState extends State { 'tags': _tagsController.getTags?.map((x) => {'alias': x}).toList() ?? List.empty(), 'attachments': _attachments, + 'is_draft': _isDraft, if (widget.edit != null) 'alias': widget.edit!.alias, if (widget.reply != null) 'reply_to': widget.reply!.id, if (widget.repost != null) 'repost_to': widget.repost!.id, @@ -102,6 +105,7 @@ class _PostPublishingScreenState extends State { if (widget.edit != null) { _contentController.text = widget.edit!.content; _attachments = widget.edit!.attachments ?? List.empty(); + _isDraft = widget.edit!.isDraft ?? false; } } @@ -117,8 +121,6 @@ class _PostPublishingScreenState extends State { @override Widget build(BuildContext context) { - final AuthProvider auth = Get.find(); - final notifyBannerActions = [ TextButton( onPressed: cancelAction, @@ -130,14 +132,18 @@ class _PostPublishingScreenState extends State { color: Theme.of(context).colorScheme.surface, child: Scaffold( appBar: AppBar( - title: AppBarTitle('postPublishing'.tr), + title: AppBarTitle('postPublish'.tr), centerTitle: false, toolbarHeight: SolianTheme.toolbarHeight(context), leading: const PrevPageButton(), actions: [ TextButton( onPressed: _isBusy ? null : () => applyPost(), - child: Text('postAction'.tr.toUpperCase()), + child: Text( + _isDraft + ? 'draftSave'.tr.toUpperCase() + : 'postAction'.tr.toUpperCase(), + ), ) ], ), @@ -194,22 +200,6 @@ class _PostPublishingScreenState extends State { ).paddingOnly(bottom: 8), ], ), - FutureBuilder( - future: auth.getProfile(), - builder: (context, snapshot) { - if (snapshot.hasData) { - return ListTile( - leading: AccountAvatar( - content: snapshot.data?.body!['avatar'], - radius: 22), - title: Text(snapshot.data?.body!['nick']), - subtitle: Text('postIdentityNotify'.tr), - ); - } else { - return Container(); - } - }, - ), if (widget.realm != null) MaterialBanner( leading: const Icon(Icons.group), @@ -247,6 +237,7 @@ class _PostPublishingScreenState extends State { left: 0, right: 0, child: Column( + crossAxisAlignment: CrossAxisAlignment.start, children: [ TagsField( initialTags: @@ -257,17 +248,30 @@ class _PostPublishingScreenState extends State { const Divider(thickness: 0.3, height: 0.3), SizedBox( height: 56, - child: Row( + child: ListView( + scrollDirection: Axis.horizontal, children: [ - TextButton( - style: TextButton.styleFrom( - shape: const CircleBorder(), + IconButton( + icon: _isDraft + ? const Icon(Icons.drive_file_rename_outline) + : const Icon(Icons.public), + color: _isDraft + ? Colors.grey.shade600 + : Colors.green.shade700, + onPressed: () { + setState(() => _isDraft = !_isDraft); + }, + ), + badges.Badge( + badgeContent: Text(_attachments.length.toString()), + showBadge: _attachments.isNotEmpty, + child: IconButton( + icon: const Icon(Icons.camera_alt), + onPressed: () => showAttachments(), ), - child: const Icon(Icons.camera_alt), - onPressed: () => showAttachments(), ) ], - ), + ).paddingSymmetric(horizontal: 6, vertical: 8), ), ], ), diff --git a/lib/screens/realms/realm_view.dart b/lib/screens/realms/realm_view.dart index 0f42954..fa91dbb 100644 --- a/lib/screens/realms/realm_view.dart +++ b/lib/screens/realms/realm_view.dart @@ -210,8 +210,8 @@ class _RealmPostListWidgetState extends State { onTap: () { AppRouter.instance .pushNamed( - 'postPublishing', - extra: PostPublishingArguments(realm: widget.realm), + 'postCreate', + extra: PostPublishArguments(realm: widget.realm), ) .then((value) { if (value != null) _pagingController.refresh(); diff --git a/lib/translations.dart b/lib/translations.dart index a0be185..3c9b982 100644 --- a/lib/translations.dart +++ b/lib/translations.dart @@ -10,6 +10,9 @@ class SolianMessages extends Translations { 'next': 'Next', 'reset': 'Reset', 'page': 'Page', + 'draft': 'Draft', + 'draftSave': 'Save', + 'draftBox': 'Draft Box', 'feed': 'Feed', 'feedSearch': 'Search Feed', 'feedSearchWithTag': 'Searching with tag #@key', @@ -91,7 +94,10 @@ class SolianMessages extends Translations { 'postInRealm': 'In realm @realm', 'postDetail': 'Post', 'postReplies': 'Replies', - 'postPublishing': 'Post a post', + 'postPublish': 'Post a post', + 'articlePublish': 'Write an article', + 'articleTitlePlaceholder': 'Title', + 'articleContentPlaceholder': 'Content', 'postIdentityNotify': 'You will post this post as', 'postContentPlaceholder': 'What\'s happened?!', 'postTagsPlaceholder': 'Tags', @@ -270,6 +276,9 @@ class SolianMessages extends Translations { 'edit': '编辑', 'delete': '删除', 'page': '页面', + 'draft': '草稿', + 'draftSave': '存为草稿', + 'draftBox': '草稿箱', 'feed': '资讯', 'feedSearch': '搜索资讯', 'feedSearchWithTag': '检索带有 #@key 标签的资讯', @@ -325,8 +334,8 @@ class SolianMessages extends Translations { 'notifyAllRead': '已读所有通知', 'notifyEmpty': '通知箱为空', 'notifyEmptyCaption': '看起来最近没发生什么呢', - 'postCreate': '发表帖子', - 'articleCreate': '发表文章', + 'postCreate': '发个帖子', + 'articleCreate': '撰写文章', 'draftBoxOpen': '打开草稿箱', 'postNew': '创建新帖子', 'postNewInRealmHint': '在领域 @realm 里发表新帖子', @@ -335,7 +344,7 @@ class SolianMessages extends Translations { 'postInRealm': '发表于 @realm', 'postDetail': '帖子详情', 'postReplies': '帖子回复', - 'postPublishing': '发表帖子', + 'postPublish': '编辑帖子', 'postIdentityNotify': '你将会以本身份发表帖子', 'postContentPlaceholder': '发生什么事了?!', 'postTagsPlaceholder': '标签', diff --git a/lib/widgets/navigation/app_navigation_bottom_bar.dart b/lib/widgets/navigation/app_navigation_bottom_bar.dart index 4c4bd91..5282901 100644 --- a/lib/widgets/navigation/app_navigation_bottom_bar.dart +++ b/lib/widgets/navigation/app_navigation_bottom_bar.dart @@ -29,8 +29,7 @@ class _AppNavigationBottomBarState extends State { showUnselectedLabels: false, onTap: (idx) { setState(() => _selectedIndex = idx); - AppRouter.instance - .pushReplacementNamed(AppNavigation.destinations[idx].page); + AppRouter.instance.goNamed(AppNavigation.destinations[idx].page); }, ); } diff --git a/lib/widgets/posts/post_action.dart b/lib/widgets/posts/post_action.dart index 7cbbe72..199d1d6 100644 --- a/lib/widgets/posts/post_action.dart +++ b/lib/widgets/posts/post_action.dart @@ -72,8 +72,8 @@ class _PostActionState extends State { title: Text('reply'.tr), onTap: () async { final value = await AppRouter.instance.pushNamed( - 'postPublishing', - extra: PostPublishingArguments(reply: widget.item), + 'postCreate', + extra: PostPublishArguments(reply: widget.item), ); if (value != null) { Navigator.pop(context, true); @@ -86,8 +86,8 @@ class _PostActionState extends State { title: Text('repost'.tr), onTap: () async { final value = await AppRouter.instance.pushNamed( - 'postPublishing', - extra: PostPublishingArguments(repost: widget.item), + 'postCreate', + extra: PostPublishArguments(repost: widget.item), ); if (value != null) { Navigator.pop(context, true); @@ -104,8 +104,8 @@ class _PostActionState extends State { title: Text('edit'.tr), onTap: () async { final value = await AppRouter.instance.pushNamed( - 'postPublishing', - extra: PostPublishingArguments(edit: widget.item), + 'postCreate', + extra: PostPublishArguments(edit: widget.item), ); if (value != null) { Navigator.pop(context, true); diff --git a/lib/widgets/posts/post_item.dart b/lib/widgets/posts/post_item.dart index b42232a..bfb017d 100644 --- a/lib/widgets/posts/post_item.dart +++ b/lib/widgets/posts/post_item.dart @@ -133,7 +133,7 @@ class _PostItemState extends State { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: widgets, - ); + ).paddingOnly(top: 4); } } @@ -222,7 +222,7 @@ class _PostItemState extends State { top: 2, bottom: hasAttachment ? 4 : 0, ), - buildFooter().paddingOnly(left: 16, top: 2), + buildFooter().paddingOnly(left: 16), AttachmentList( parentId: widget.overrideAttachmentParent ?? widget.item.alias, attachmentsId: item.attachments ?? List.empty(), @@ -284,7 +284,7 @@ class _PostItemState extends State { ); }, ), - buildFooter().paddingOnly(left: 12, top: 2), + buildFooter().paddingOnly(left: 12), ], ), ) diff --git a/pubspec.lock b/pubspec.lock index 9e0e1bc..1e5e8c5 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -49,6 +49,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.11.0" + badges: + dependency: "direct main" + description: + name: badges + sha256: a7b6bbd60dce418df0db3058b53f9d083c22cdb5132a052145dc267494df0b84 + url: "https://pub.dev" + source: hosted + version: "3.1.2" boolean_selector: dependency: transitive description: @@ -522,10 +530,10 @@ packages: dependency: "direct main" description: name: flutter_local_notifications - sha256: ced76d337f54de33d7d9f06092137b4ac2da5079e00cee8a11a1794ffc7c61c6 + sha256: "0a9068149f0225e81642b03562e99776106edbd967816ee68bc16310d457c60e" url: "https://pub.dev" source: hosted - version: "17.2.1" + version: "17.2.1+1" flutter_local_notifications_linux: dependency: transitive description: @@ -1076,10 +1084,10 @@ packages: dependency: transitive description: name: path_provider_android - sha256: bca87b0165ffd7cdb9cad8edd22d18d2201e886d9a9f19b4fb3452ea7df3a72a + sha256: "30c5aa827a6ae95ce2853cdc5fe3971daaac00f6f081c419c013f7f57bff2f5e" url: "https://pub.dev" source: hosted - version: "2.2.6" + version: "2.2.7" path_provider_foundation: dependency: transitive description: @@ -1108,10 +1116,10 @@ packages: dependency: transitive description: name: path_provider_windows - sha256: "8bc9f22eee8690981c22aa7fc602f5c85b497a6fb2ceb35ee5a5e5ed85ad8170" + sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7 url: "https://pub.dev" source: hosted - version: "2.2.1" + version: "2.3.0" permission_handler: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index e5578a7..51cc803 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -54,6 +54,7 @@ dependencies: mutex: ^3.1.0 pasteboard: ^0.2.0 desktop_drop: ^0.4.4 + badges: ^3.1.2 dev_dependencies: flutter_test: