diff --git a/lib/i18n/app_en.arb b/lib/i18n/app_en.arb index 26c23ef..6ba9f98 100644 --- a/lib/i18n/app_en.arb +++ b/lib/i18n/app_en.arb @@ -6,7 +6,11 @@ "signInCaption": "Sign in to create post, start a realm, message your friend and more!", "signUp": "Sign Up", "signUpCaption": "Create an account on Solarpass and then get the access of entire Solar Networks!", + "confirmation": "Confirmation", + "confirmCancel": "Not sure", + "confirmOkay": "OK", "edit": "Edit", + "delete": "Delete", "action": "Action", "report": "Report", "post": "Post", @@ -17,5 +21,6 @@ "pickPhoto": "Gallery photo", "newMoment": "Record a moment", "postIdentityNotify": "You will create this post as", - "postContentPlaceholder": "What's happened?!" + "postContentPlaceholder": "What's happened?!", + "postDeleteConfirm": "Are you sure you want to delete this post? This operation cannot be revert!" } diff --git a/lib/i18n/app_zh.arb b/lib/i18n/app_zh.arb index dff4136..b69e4ae 100644 --- a/lib/i18n/app_zh.arb +++ b/lib/i18n/app_zh.arb @@ -6,7 +6,11 @@ "signInCaption": "登陆以发表帖子、文章、创建领域、和你的朋友聊天,以及获取更多功能!", "signUp": "注册", "signUpCaption": "在 Solarpass 注册一个账号以获得整个 Solar Networks 的存取权!", + "confirmation": "确认", + "confirmCancel": "不太确定", + "confirmOkay": "确定", "edit": "编辑", + "delete": "删除", "action": "操作", "report": "举报", "post": "帖子", @@ -17,5 +21,6 @@ "pickPhoto": "相册照片", "newMoment": "记录时刻", "postIdentityNotify": "你将会以该身份发表本帖子", - "postContentPlaceholder": "发生什么事了?!" + "postContentPlaceholder": "发生什么事了?!", + "postDeleteConfirm": "你确定要删除这篇帖子吗?这意味着这个帖子将永远被我们丢弃在硬盘海中!该操作不可被反转!" } \ No newline at end of file diff --git a/lib/providers/auth.dart b/lib/providers/auth.dart index 66582da..0aac0cc 100755 --- a/lib/providers/auth.dart +++ b/lib/providers/auth.dart @@ -32,7 +32,7 @@ class AuthProvider { Future pickClient() async { if (await storage.containsKey(key: storageKey)) { try { - var credentials = + final credentials = oauth2.Credentials.fromJson((await storage.read(key: storageKey))!); client = oauth2.Client(credentials, identifier: clientId, secret: clientSecret); @@ -64,20 +64,14 @@ class AuthProvider { var authorizationUrl = grant.getAuthorizationUrl(redirectUrl, scopes: ["openid"]); - if (Platform.isAndroid || Platform.isIOS) { - // Use WebView to get authorization url - var responseUrl = await Navigator.of(context, rootNavigator: true).push( - MaterialPageRoute( - builder: (context) => AuthorizationScreen(authorizationUrl), - ), - ); + var responseUrl = await Navigator.of(context, rootNavigator: true).push( + MaterialPageRoute( + builder: (context) => AuthorizationScreen(authorizationUrl), + ), + ); - var responseUri = Uri.parse(responseUrl); - return await grant - .handleAuthorizationResponse(responseUri.queryParameters); - } else { - throw UnimplementedError("unsupported platform"); - } + var responseUri = Uri.parse(responseUrl); + return await grant.handleAuthorizationResponse(responseUri.queryParameters); } Future fetchProfiles() async { @@ -89,10 +83,11 @@ class AuthProvider { Future refreshToken() async { if (client != null) { - var credentials = await client?.credentials.refresh( + final credentials = await client?.credentials.refresh( identifier: clientId, secret: clientSecret, basicAuth: false); - - storage.write(key: storageKey, value: credentials!.toJson()); + client = oauth2.Client(credentials!, + identifier: clientId, secret: clientSecret); + storage.write(key: storageKey, value: credentials.toJson()); } } @@ -117,7 +112,6 @@ class AuthProvider { .add(const Duration(minutes: 3)) .isBefore(DateTime.now())) { await refreshToken(); - await pickClient(); lastRefreshedAt = DateTime.now(); } } diff --git a/lib/widgets/posts/item_action.dart b/lib/widgets/posts/item_action.dart index ffade9d..53221b0 100644 --- a/lib/widgets/posts/item_action.dart +++ b/lib/widgets/posts/item_action.dart @@ -4,6 +4,7 @@ import 'package:solian/models/post.dart'; import 'package:solian/providers/auth.dart'; import 'package:solian/router.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; +import 'package:solian/widgets/posts/item_deletion.dart'; class PostItemAction extends StatelessWidget { final Post item; @@ -16,7 +17,7 @@ class PostItemAction extends StatelessWidget { final auth = context.read(); return SizedBox( - height: 280, + height: 320, child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ @@ -29,43 +30,62 @@ class PostItemAction extends StatelessWidget { ), Expanded( child: FutureBuilder( - future: auth.getProfiles(), - builder: (context, snapshot) { - if (snapshot.hasData) { - final authorizedItems = [ - ListTile( - leading: const Icon(Icons.edit), - title: Text(AppLocalizations.of(context)!.edit), - onTap: () { - router - .pushNamed('posts.moments.editor', extra: item) - .then((did) { - if(did == true && onUpdate != null) { - onUpdate!(); - } - }); - }, - ) - ]; + future: auth.getProfiles(), + builder: (context, snapshot) { + print(snapshot); + if (snapshot.hasData) { + final authorizedItems = [ + ListTile( + leading: const Icon(Icons.edit), + title: Text(AppLocalizations.of(context)!.edit), + onTap: () { + router + .pushNamed('posts.moments.editor', extra: item) + .then((did) { + if (did == true && onUpdate != null) { + onUpdate!(); + } + }); + }, + ), + ListTile( + leading: const Icon(Icons.delete), + title: Text(AppLocalizations.of(context)!.delete), + onTap: () { + final dataset = '${item.modelType}s'; + showDialog( + context: context, + builder: (context) => ItemDeletionDialog( + item: item, + dataset: dataset, + onDelete: (did) { + if(did == true && onUpdate != null) onUpdate!(); + }, + ), + ); + }, + ) + ]; - return ListView( - children: [ - ...(snapshot.data['id'] == item.authorId - ? authorizedItems - : List.empty()), - ListTile( - leading: const Icon(Icons.report), - title: Text(AppLocalizations.of(context)!.report), - onTap: () {}, - ) - ], - ); - } else { - return const Center( - child: CircularProgressIndicator(), - ); - } - }), + return ListView( + children: [ + ...(snapshot.data['id'] == item.authorId + ? authorizedItems + : List.empty()), + ListTile( + leading: const Icon(Icons.report), + title: Text(AppLocalizations.of(context)!.report), + onTap: () {}, + ) + ], + ); + } else { + return const Center( + child: CircularProgressIndicator(), + ); + } + }, + ), ), ], ), diff --git a/lib/widgets/posts/item_deletion.dart b/lib/widgets/posts/item_deletion.dart new file mode 100644 index 0000000..8bcce57 --- /dev/null +++ b/lib/widgets/posts/item_deletion.dart @@ -0,0 +1,66 @@ +import 'dart:convert'; + +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; +import 'package:solian/models/post.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; +import 'package:solian/providers/auth.dart'; +import 'package:solian/utils/service_url.dart'; + +class ItemDeletionDialog extends StatefulWidget { + final Post item; + final String dataset; + final Function? onDelete; + + const ItemDeletionDialog({ + super.key, + required this.item, + required this.dataset, + this.onDelete, + }); + + @override + State createState() => _ItemDeletionDialogState(); +} + +class _ItemDeletionDialogState extends State { + bool _isSubmitting = false; + + void doDeletion(BuildContext context) async { + final auth = context.read(); + if (!await auth.isAuthorized()) return; + + final uri = + getRequestUri('interactive', '/api/p/moments/${widget.item.id}'); + + setState(() => _isSubmitting = true); + final res = await auth.client!.delete(uri); + if (res.statusCode != 200) { + var message = utf8.decode(res.bodyBytes); + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text("Something went wrong... $message")), + ); + setState(() => _isSubmitting = false); + } else { + Navigator.pop(context, true); + } + } + + @override + Widget build(BuildContext context) { + return AlertDialog( + title: Text(AppLocalizations.of(context)!.confirmation), + content: Text(AppLocalizations.of(context)!.postDeleteConfirm), + actions: [ + TextButton( + onPressed: _isSubmitting ? null : () => Navigator.pop(context, false), + child: Text(AppLocalizations.of(context)!.confirmCancel), + ), + TextButton( + onPressed: _isSubmitting ? null : () => doDeletion(context), + child: Text(AppLocalizations.of(context)!.confirmOkay), + ), + ], + ); + } +}