💄 Improve UX

This commit is contained in:
LittleSheep 2024-04-25 21:33:53 +08:00
parent 3a2894b533
commit 0230ea5c79
12 changed files with 140 additions and 51 deletions

View File

@ -53,6 +53,9 @@
"chatNew": "New Chat", "chatNew": "New Chat",
"chatNewCreate": "Create a channel", "chatNewCreate": "Create a channel",
"chatNewJoin": "Join a exists channel", "chatNewJoin": "Join a exists channel",
"chatManage": "Manage Chat",
"chatMember": "Member",
"chatNotifySetting": "Notify Settings",
"chatChannelUsage": "Channel", "chatChannelUsage": "Channel",
"chatChannelUsageCaption": "Channel is place to talk with people, one or a lot.", "chatChannelUsageCaption": "Channel is place to talk with people, one or a lot.",
"chatChannelOrganize": "Organize a channel", "chatChannelOrganize": "Organize a channel",

View File

@ -51,6 +51,9 @@
"reactionAdded": "你的反应已被添加。", "reactionAdded": "你的反应已被添加。",
"reactionRemoved": "你的反应已被移除。", "reactionRemoved": "你的反应已被移除。",
"chatNew": "新聊天", "chatNew": "新聊天",
"chatManage": "管理聊天",
"chatMember": "成员",
"chatNotifySetting": "通知设定",
"chatNewCreate": "新建频道", "chatNewCreate": "新建频道",
"chatNewJoin": "加入已有频道", "chatNewJoin": "加入已有频道",
"chatChannelUsage": "频道", "chatChannelUsage": "频道",

View File

@ -1,3 +1,5 @@
import 'package:solian/models/account.dart';
class Channel { class Channel {
int id; int id;
DateTime createdAt; DateTime createdAt;
@ -7,9 +9,9 @@ class Channel {
String name; String name;
String description; String description;
dynamic members; dynamic members;
dynamic messages;
dynamic calls; dynamic calls;
int type; int type;
Account account;
int accountId; int accountId;
int realmId; int realmId;
@ -22,9 +24,9 @@ class Channel {
required this.name, required this.name,
required this.description, required this.description,
this.members, this.members,
this.messages,
this.calls, this.calls,
required this.type, required this.type,
required this.account,
required this.accountId, required this.accountId,
required this.realmId, required this.realmId,
}); });
@ -38,9 +40,9 @@ class Channel {
name: json["name"], name: json["name"],
description: json["description"], description: json["description"],
members: json["members"], members: json["members"],
messages: json["messages"],
calls: json["calls"], calls: json["calls"],
type: json["type"], type: json["type"],
account: Account.fromJson(json["account"]),
accountId: json["account_id"], accountId: json["account_id"],
realmId: json["realm_id"], realmId: json["realm_id"],
); );
@ -54,9 +56,9 @@ class Channel {
"name": name, "name": name,
"description": description, "description": description,
"members": members, "members": members,
"messages": messages,
"calls": calls, "calls": calls,
"type": type, "type": type,
"account": account,
"account_id": accountId, "account_id": accountId,
"realm_id": realmId, "realm_id": realmId,
}; };

View File

@ -13,7 +13,7 @@ class ChatProvider {
await auth.refreshToken(); await auth.refreshToken();
var ori = getRequestUri('messaging', '/api/unified'); var ori = getRequestUri('messaging', '/api/ws');
var uri = Uri( var uri = Uri(
scheme: ori.scheme.replaceFirst('http', 'ws'), scheme: ori.scheme.replaceFirst('http', 'ws'),
host: ori.host, host: ori.host,

View File

@ -4,13 +4,14 @@ import 'package:solian/models/post.dart';
import 'package:solian/screens/account.dart'; import 'package:solian/screens/account.dart';
import 'package:solian/screens/chat/chat.dart'; import 'package:solian/screens/chat/chat.dart';
import 'package:solian/screens/chat/index.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/explore.dart';
import 'package:solian/screens/notification.dart'; import 'package:solian/screens/notification.dart';
import 'package:solian/screens/posts/comment_editor.dart'; import 'package:solian/screens/posts/comment_editor.dart';
import 'package:solian/screens/posts/moment_editor.dart'; import 'package:solian/screens/posts/moment_editor.dart';
import 'package:solian/screens/posts/screen.dart'; import 'package:solian/screens/posts/screen.dart';
import 'package:solian/screens/signin.dart'; import 'package:solian/screens/signin.dart';
import 'package:solian/widgets/chat/channel_editor.dart';
final router = GoRouter( final router = GoRouter(
routes: [ routes: [
@ -27,13 +28,18 @@ final router = GoRouter(
GoRoute( GoRoute(
path: '/chat/create', path: '/chat/create',
name: 'chat.channel.editor', name: 'chat.channel.editor',
builder: (context, state) => ChannelEditor(editing: state.extra as Channel?), builder: (context, state) => ChannelEditorScreen(editing: state.extra as Channel?),
), ),
GoRoute( GoRoute(
path: '/chat/c/:channel', path: '/chat/c/:channel',
name: 'chat.channel', name: 'chat.channel',
builder: (context, state) => ChatScreen(alias: state.pathParameters['channel'] as String), 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( GoRoute(
path: '/account', path: '/account',
name: 'account', name: 'account',

View File

@ -12,16 +12,16 @@ import 'package:solian/widgets/indent_wrapper.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:uuid/uuid.dart'; import 'package:uuid/uuid.dart';
class ChannelEditor extends StatefulWidget { class ChannelEditorScreen extends StatefulWidget {
final Channel? editing; final Channel? editing;
const ChannelEditor({super.key, this.editing}); const ChannelEditorScreen({super.key, this.editing});
@override @override
State<ChannelEditor> createState() => _ChannelEditorState(); State<ChannelEditorScreen> createState() => _ChannelEditorScreenState();
} }
class _ChannelEditorState extends State<ChannelEditor> { class _ChannelEditorScreenState extends State<ChannelEditorScreen> {
final _aliasController = TextEditingController(); final _aliasController = TextEditingController();
final _nameController = TextEditingController(); final _nameController = TextEditingController();
final _descriptionController = TextEditingController(); final _descriptionController = TextEditingController();

View File

@ -9,6 +9,7 @@ import 'package:solian/utils/service_url.dart';
import 'package:solian/widgets/chat/chat_new.dart'; import 'package:solian/widgets/chat/chat_new.dart';
import 'package:solian/widgets/indent_wrapper.dart'; import 'package:solian/widgets/indent_wrapper.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:solian/widgets/notification_notifier.dart';
import 'package:solian/widgets/signin_required.dart'; import 'package:solian/widgets/signin_required.dart';
class ChatIndexScreen extends StatefulWidget { class ChatIndexScreen extends StatefulWidget {
@ -63,6 +64,7 @@ class _ChatIndexScreenState extends State<ChatIndexScreen> {
return IndentWrapper( return IndentWrapper(
title: AppLocalizations.of(context)!.chat, title: AppLocalizations.of(context)!.chat,
appBarActions: const [NotificationButton()],
floatingActionButton: FutureBuilder( floatingActionButton: FutureBuilder(
future: auth.isAuthorized(), future: auth.isAuthorized(),
builder: (context, snapshot) { builder: (context, snapshot) {

View File

@ -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<ChatManageScreen> createState() => _ChatManageScreenState();
}
class _ChatManageScreenState extends State<ChatManageScreen> {
bool isOwned = false;
@override
void initState() {
super.initState();
Future.delayed(Duration.zero, () async {
final auth = context.read<AuthProvider>();
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()),
],
),
),
],
),
);
}
}

View File

@ -23,6 +23,7 @@ class _NotificationScreenState extends State<NotificationScreen> {
return IndentWrapper( return IndentWrapper(
noSafeArea: true, noSafeArea: true,
hideDrawer: true,
title: AppLocalizations.of(context)!.notification, title: AppLocalizations.of(context)!.notification,
child: RefreshIndicator( child: RefreshIndicator(
onRefresh: () => nty.fetch(auth), onRefresh: () => nty.fetch(auth),

View File

@ -1,5 +1,4 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:solian/models/channel.dart'; import 'package:solian/models/channel.dart';
import 'package:solian/router.dart'; import 'package:solian/router.dart';
@ -13,41 +12,17 @@ class ChannelAction extends StatelessWidget {
@override @override
Widget build(BuildContext context) { 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( return IconButton(
onPressed: () { onPressed: () {
if (controller.isOpen) { router.pushNamed(
controller.close(); 'chat.channel.manage',
} else { extra: channel,
controller.open(); pathParameters: {'channel': channel.alias},
} );
}, },
focusNode: _focusNode, focusNode: _focusNode,
style: TextButton.styleFrom(shape: const CircleBorder()), style: TextButton.styleFrom(shape: const CircleBorder()),
icon: const Icon(Icons.more_horiz), icon: const Icon(Icons.more_horiz),
); );
},
),
],
);
} }
} }

View File

@ -28,6 +28,8 @@ class ChatMaintainer extends StatefulWidget {
class _ChatMaintainerState extends State<ChatMaintainer> { class _ChatMaintainerState extends State<ChatMaintainer> {
void connect() { void connect() {
ScaffoldMessenger.of(context).clearSnackBars();
final notify = ScaffoldMessenger.of(context).showSnackBar( final notify = ScaffoldMessenger.of(context).showSnackBar(
SnackBar( SnackBar(
content: Text(AppLocalizations.of(context)!.connectingServer), content: Text(AppLocalizations.of(context)!.connectingServer),
@ -73,8 +75,6 @@ class _ChatMaintainerState extends State<ChatMaintainer> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
ScaffoldMessenger.of(context).clearSnackBars();
return widget.child; return widget.child;
} }
} }

View File

@ -1,6 +1,7 @@
import 'dart:convert'; import 'dart:convert';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_animate/flutter_animate.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:solian/models/reaction.dart'; import 'package:solian/models/reaction.dart';
import 'package:solian/providers/auth.dart'; import 'package:solian/providers/auth.dart';
@ -106,7 +107,7 @@ class _ReactionActionPopupState extends State<ReactionActionPopup> {
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Container( 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( child: Padding(
padding: const EdgeInsets.symmetric( padding: const EdgeInsets.symmetric(
horizontal: 8, horizontal: 8,
@ -118,7 +119,7 @@ class _ReactionActionPopupState extends State<ReactionActionPopup> {
), ),
), ),
), ),
_isSubmitting ? const LinearProgressIndicator() : Container(), _isSubmitting ? const LinearProgressIndicator().animate().scaleX() : Container(),
Expanded( Expanded(
child: ListView.builder( child: ListView.builder(
itemCount: reactions.length, itemCount: reactions.length,