diff --git a/lib/i18n/app_en.arb b/lib/i18n/app_en.arb index f3d2b10..adc1d86 100644 --- a/lib/i18n/app_en.arb +++ b/lib/i18n/app_en.arb @@ -53,6 +53,9 @@ "chatNew": "New Chat", "chatNewCreate": "Create a channel", "chatNewJoin": "Join a exists channel", + "chatManage": "Manage Chat", + "chatMember": "Member", + "chatNotifySetting": "Notify Settings", "chatChannelUsage": "Channel", "chatChannelUsageCaption": "Channel is place to talk with people, one or a lot.", "chatChannelOrganize": "Organize a channel", diff --git a/lib/i18n/app_zh.arb b/lib/i18n/app_zh.arb index 01561ea..033ef4e 100644 --- a/lib/i18n/app_zh.arb +++ b/lib/i18n/app_zh.arb @@ -51,6 +51,9 @@ "reactionAdded": "你的反应已被添加。", "reactionRemoved": "你的反应已被移除。", "chatNew": "新聊天", + "chatManage": "管理聊天", + "chatMember": "成员", + "chatNotifySetting": "通知设定", "chatNewCreate": "新建频道", "chatNewJoin": "加入已有频道", "chatChannelUsage": "频道", diff --git a/lib/models/channel.dart b/lib/models/channel.dart index 1017307..edd6a9f 100644 --- a/lib/models/channel.dart +++ b/lib/models/channel.dart @@ -1,3 +1,5 @@ +import 'package:solian/models/account.dart'; + class Channel { int id; DateTime createdAt; @@ -7,9 +9,9 @@ class Channel { String name; String description; dynamic members; - dynamic messages; dynamic calls; int type; + Account account; int accountId; int realmId; @@ -22,9 +24,9 @@ class Channel { required this.name, required this.description, this.members, - this.messages, this.calls, required this.type, + required this.account, required this.accountId, required this.realmId, }); @@ -38,9 +40,9 @@ class Channel { name: json["name"], description: json["description"], members: json["members"], - messages: json["messages"], calls: json["calls"], type: json["type"], + account: Account.fromJson(json["account"]), accountId: json["account_id"], realmId: json["realm_id"], ); @@ -54,9 +56,9 @@ class Channel { "name": name, "description": description, "members": members, - "messages": messages, "calls": calls, "type": type, + "account": account, "account_id": accountId, "realm_id": realmId, }; diff --git a/lib/providers/chat.dart b/lib/providers/chat.dart index 1b69ef9..f00fcdd 100644 --- a/lib/providers/chat.dart +++ b/lib/providers/chat.dart @@ -13,7 +13,7 @@ class ChatProvider { await auth.refreshToken(); - var ori = getRequestUri('messaging', '/api/unified'); + var ori = getRequestUri('messaging', '/api/ws'); var uri = Uri( scheme: ori.scheme.replaceFirst('http', 'ws'), host: ori.host, diff --git a/lib/router.dart b/lib/router.dart index 69cce68..5ab471e 100644 --- a/lib/router.dart +++ b/lib/router.dart @@ -4,13 +4,14 @@ import 'package:solian/models/post.dart'; import 'package:solian/screens/account.dart'; import 'package:solian/screens/chat/chat.dart'; import 'package:solian/screens/chat/index.dart'; +import 'package:solian/screens/chat/manage.dart'; +import 'package:solian/screens/chat/channel/channel_editor.dart'; import 'package:solian/screens/explore.dart'; import 'package:solian/screens/notification.dart'; import 'package:solian/screens/posts/comment_editor.dart'; import 'package:solian/screens/posts/moment_editor.dart'; import 'package:solian/screens/posts/screen.dart'; import 'package:solian/screens/signin.dart'; -import 'package:solian/widgets/chat/channel_editor.dart'; final router = GoRouter( routes: [ @@ -27,13 +28,18 @@ final router = GoRouter( GoRoute( path: '/chat/create', name: 'chat.channel.editor', - builder: (context, state) => ChannelEditor(editing: state.extra as Channel?), + builder: (context, state) => ChannelEditorScreen(editing: state.extra as Channel?), ), GoRoute( path: '/chat/c/:channel', name: 'chat.channel', builder: (context, state) => ChatScreen(alias: state.pathParameters['channel'] as String), ), + GoRoute( + path: '/chat/c/:channel/manage', + name: 'chat.channel.manage', + builder: (context, state) => ChatManageScreen(channel: state.extra as Channel), + ), GoRoute( path: '/account', name: 'account', diff --git a/lib/widgets/chat/channel_editor.dart b/lib/screens/chat/channel/channel_editor.dart similarity index 96% rename from lib/widgets/chat/channel_editor.dart rename to lib/screens/chat/channel/channel_editor.dart index 3e6a488..fea7f69 100644 --- a/lib/widgets/chat/channel_editor.dart +++ b/lib/screens/chat/channel/channel_editor.dart @@ -12,16 +12,16 @@ import 'package:solian/widgets/indent_wrapper.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:uuid/uuid.dart'; -class ChannelEditor extends StatefulWidget { +class ChannelEditorScreen extends StatefulWidget { final Channel? editing; - const ChannelEditor({super.key, this.editing}); + const ChannelEditorScreen({super.key, this.editing}); @override - State createState() => _ChannelEditorState(); + State createState() => _ChannelEditorScreenState(); } -class _ChannelEditorState extends State { +class _ChannelEditorScreenState extends State { final _aliasController = TextEditingController(); final _nameController = TextEditingController(); final _descriptionController = TextEditingController(); diff --git a/lib/screens/chat/index.dart b/lib/screens/chat/index.dart index 32c8825..68179e3 100644 --- a/lib/screens/chat/index.dart +++ b/lib/screens/chat/index.dart @@ -9,6 +9,7 @@ import 'package:solian/utils/service_url.dart'; import 'package:solian/widgets/chat/chat_new.dart'; import 'package:solian/widgets/indent_wrapper.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; +import 'package:solian/widgets/notification_notifier.dart'; import 'package:solian/widgets/signin_required.dart'; class ChatIndexScreen extends StatefulWidget { @@ -63,6 +64,7 @@ class _ChatIndexScreenState extends State { return IndentWrapper( title: AppLocalizations.of(context)!.chat, + appBarActions: const [NotificationButton()], floatingActionButton: FutureBuilder( future: auth.isAuthorized(), builder: (context, snapshot) { diff --git a/lib/screens/chat/manage.dart b/lib/screens/chat/manage.dart new file mode 100644 index 0000000..2793b1f --- /dev/null +++ b/lib/screens/chat/manage.dart @@ -0,0 +1,96 @@ +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; +import 'package:solian/models/channel.dart'; +import 'package:solian/providers/auth.dart'; +import 'package:solian/router.dart'; +import 'package:solian/widgets/indent_wrapper.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; + +class ChatManageScreen extends StatefulWidget { + final Channel channel; + + const ChatManageScreen({super.key, required this.channel}); + + @override + State createState() => _ChatManageScreenState(); +} + +class _ChatManageScreenState extends State { + bool isOwned = false; + + @override + void initState() { + super.initState(); + + Future.delayed(Duration.zero, () async { + final auth = context.read(); + final prof = await auth.getProfiles(); + + setState(() { + isOwned = prof['id'] == widget.channel.account.externalId; + }); + }); + } + + @override + Widget build(BuildContext context) { + final authorizedItems = [ + ListTile( + leading: const Icon(Icons.settings), + title: Text(AppLocalizations.of(context)!.settings), + onTap: () async { + router.pushNamed('chat.channel.editor', extra: widget.channel).then((did) { + if (did == true) { + if (router.canPop()) router.pop(true); + } + }); + }, + ), + ]; + + return IndentWrapper( + title: AppLocalizations.of(context)!.chatManage, + hideDrawer: true, + noSafeArea: true, + child: Column( + children: [ + Padding( + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), + child: Row( + children: [ + const CircleAvatar( + radius: 24, + backgroundColor: Colors.teal, + child: Icon(Icons.tag, color: Colors.white), + ), + const SizedBox(width: 16), + Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ + Text(widget.channel.name, style: Theme.of(context).textTheme.bodyLarge), + Text(widget.channel.description, style: Theme.of(context).textTheme.bodySmall), + ]) + ], + ), + ), + const Divider(thickness: 0.3), + Expanded( + child: ListView( + children: [ + ListTile( + leading: const Icon(Icons.edit_notifications), + title: Text(AppLocalizations.of(context)!.chatNotifySetting), + onTap: () {}, + ), + ListTile( + leading: const Icon(Icons.supervisor_account), + title: Text(AppLocalizations.of(context)!.chatMember), + onTap: () {}, + ), + ...(isOwned ? authorizedItems : List.empty()), + ], + ), + ), + ], + ), + ); + } +} diff --git a/lib/screens/notification.dart b/lib/screens/notification.dart index b9b432c..8e2b8fb 100644 --- a/lib/screens/notification.dart +++ b/lib/screens/notification.dart @@ -23,6 +23,7 @@ class _NotificationScreenState extends State { return IndentWrapper( noSafeArea: true, + hideDrawer: true, title: AppLocalizations.of(context)!.notification, child: RefreshIndicator( onRefresh: () => nty.fetch(auth), diff --git a/lib/widgets/chat/channel_action.dart b/lib/widgets/chat/channel_action.dart index 2759b29..787873b 100644 --- a/lib/widgets/chat/channel_action.dart +++ b/lib/widgets/chat/channel_action.dart @@ -1,5 +1,4 @@ import 'package:flutter/material.dart'; -import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:solian/models/channel.dart'; import 'package:solian/router.dart'; @@ -13,41 +12,17 @@ class ChannelAction extends StatelessWidget { @override Widget build(BuildContext context) { - return Column( - children: [ - MenuAnchor( - menuChildren: [ - MenuItemButton( - child: Row( - children: [ - const Icon(Icons.settings), - const SizedBox(width: 12), - Text(AppLocalizations.of(context)!.settings), - ], - ), - onPressed: () { - router.pushNamed('chat.channel.editor', extra: channel).then((did) { - if(did == true) onUpdate(); - }); - }, - ), - ], - builder: (BuildContext context, MenuController controller, Widget? child) { - return IconButton( - onPressed: () { - if (controller.isOpen) { - controller.close(); - } else { - controller.open(); - } - }, - focusNode: _focusNode, - style: TextButton.styleFrom(shape: const CircleBorder()), - icon: const Icon(Icons.more_horiz), - ); - }, - ), - ], + return IconButton( + onPressed: () { + router.pushNamed( + 'chat.channel.manage', + extra: channel, + pathParameters: {'channel': channel.alias}, + ); + }, + focusNode: _focusNode, + style: TextButton.styleFrom(shape: const CircleBorder()), + icon: const Icon(Icons.more_horiz), ); } } diff --git a/lib/widgets/chat/maintainer.dart b/lib/widgets/chat/maintainer.dart index e0cba9d..c2a04fe 100644 --- a/lib/widgets/chat/maintainer.dart +++ b/lib/widgets/chat/maintainer.dart @@ -28,6 +28,8 @@ class ChatMaintainer extends StatefulWidget { class _ChatMaintainerState extends State { void connect() { + ScaffoldMessenger.of(context).clearSnackBars(); + final notify = ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text(AppLocalizations.of(context)!.connectingServer), @@ -73,8 +75,6 @@ class _ChatMaintainerState extends State { @override Widget build(BuildContext context) { - ScaffoldMessenger.of(context).clearSnackBars(); - return widget.child; } } diff --git a/lib/widgets/posts/reaction_action.dart b/lib/widgets/posts/reaction_action.dart index 3a1cba3..a79e8b9 100644 --- a/lib/widgets/posts/reaction_action.dart +++ b/lib/widgets/posts/reaction_action.dart @@ -1,6 +1,7 @@ import 'dart:convert'; import 'package:flutter/material.dart'; +import 'package:flutter_animate/flutter_animate.dart'; import 'package:provider/provider.dart'; import 'package:solian/models/reaction.dart'; import 'package:solian/providers/auth.dart'; @@ -106,7 +107,7 @@ class _ReactionActionPopupState extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [ Container( - padding: const EdgeInsets.only(left: 8, right: 8, top: 20), + padding: const EdgeInsets.only(left: 8, right: 8, top: 20, bottom: 12), child: Padding( padding: const EdgeInsets.symmetric( horizontal: 8, @@ -118,7 +119,7 @@ class _ReactionActionPopupState extends State { ), ), ), - _isSubmitting ? const LinearProgressIndicator() : Container(), + _isSubmitting ? const LinearProgressIndicator().animate().scaleX() : Container(), Expanded( child: ListView.builder( itemCount: reactions.length,