From 7c2b8de9310bc3e955e8b6efc063a040984d3c7b Mon Sep 17 00:00:00 2001 From: LittleSheep Date: Fri, 28 Mar 2025 00:52:19 +0800 Subject: [PATCH] :sparkles: Desktop chat list :bento: Update launch sfx --- assets/audio/sfx/launch-done.mp3 | Bin 0 -> 6496 bytes assets/translations/en-US.json | 4 +- assets/translations/zh-CN.json | 4 +- lib/main.dart | 4 +- lib/providers/navigation.dart | 8 - lib/screens/chat.dart | 304 +++++++++++++++--- .../navigation/app_drawer_navigation.dart | 165 ---------- 7 files changed, 275 insertions(+), 214 deletions(-) create mode 100644 assets/audio/sfx/launch-done.mp3 diff --git a/assets/audio/sfx/launch-done.mp3 b/assets/audio/sfx/launch-done.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..1f456f7e5dd43abe47fc0a67aa6852c3f3de0861 GIT binary patch literal 6496 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 literal 0 HcmV?d00001 diff --git a/assets/translations/en-US.json b/assets/translations/en-US.json index 271a347..bb83128 100644 --- a/assets/translations/en-US.json +++ b/assets/translations/en-US.json @@ -939,5 +939,7 @@ "settingsSoundEffects": "Sound Effects", "settingsSoundEffectsDescription": "Enable the sound effects around the app.", "settingsResetMemorizedWindowSize": "Reset Window Size", - "settingsResetMemorizedWindowSizeDescription": "Reset the memorized window size, and set it to the default size." + "settingsResetMemorizedWindowSizeDescription": "Reset the memorized window size, and set it to the default size.", + "chatDirect": "Direct Messages", + "back": "返回" } diff --git a/assets/translations/zh-CN.json b/assets/translations/zh-CN.json index c33c6a2..c0fc6fc 100644 --- a/assets/translations/zh-CN.json +++ b/assets/translations/zh-CN.json @@ -936,5 +936,7 @@ "settingsSoundEffects": "声音效果", "settingsSoundEffectsDescription": "在一些场合下启用声音特效。", "settingsResetMemorizedWindowSize": "重置窗口大小", - "settingsResetMemorizedWindowSizeDescription": "重置记忆的窗口大小,以重新设置为默认大小。" + "settingsResetMemorizedWindowSizeDescription": "重置记忆的窗口大小,以重新设置为默认大小。", + "chatDirect": "私信", + "back": "返回" } diff --git a/lib/main.dart b/lib/main.dart index cde9f03..6c08bd8 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -396,8 +396,8 @@ class _AppSplashScreenState extends State<_AppSplashScreen> with TrayListener { final cfg = context.read(); if (!cfg.soundEffects) return; - final player = AudioPlayer(playerId: 'launch-intro-player'); - await player.play(AssetSource('audio/sfx/launch-intro.mp3'), volume: 0.5); + final player = AudioPlayer(playerId: 'launch-done-player'); + await player.play(AssetSource('audio/sfx/launch-done.mp3'), volume: 0.8); player.onPlayerComplete.listen((_) { player.dispose(); }); diff --git a/lib/providers/navigation.dart b/lib/providers/navigation.dart index eba39b9..faacbec 100644 --- a/lib/providers/navigation.dart +++ b/lib/providers/navigation.dart @@ -4,7 +4,6 @@ import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; import 'package:material_symbols_icons/symbols.dart'; import 'package:shared_preferences/shared_preferences.dart'; -import 'package:surface/types/realm.dart'; class AppNavListItem { final String title; @@ -135,11 +134,4 @@ class NavigationProvider extends ChangeNotifier { _currentIndex = idx; notifyListeners(); } - - SnRealm? focusedRealm; - - void setFocusedRealm(SnRealm? realm) { - focusedRealm = realm; - notifyListeners(); - } } diff --git a/lib/screens/chat.dart b/lib/screens/chat.dart index ed9fafe..70b668a 100644 --- a/lib/screens/chat.dart +++ b/lib/screens/chat.dart @@ -1,3 +1,5 @@ +import 'package:animations/animations.dart'; +import 'package:collection/collection.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter_expandable_fab/flutter_expandable_fab.dart'; @@ -6,11 +8,14 @@ import 'package:go_router/go_router.dart'; import 'package:google_fonts/google_fonts.dart'; import 'package:material_symbols_icons/symbols.dart'; import 'package:provider/provider.dart'; +import 'package:styled_widget/styled_widget.dart'; import 'package:surface/providers/channel.dart'; import 'package:surface/providers/sn_network.dart'; +import 'package:surface/providers/sn_realm.dart'; import 'package:surface/providers/user_directory.dart'; import 'package:surface/providers/userinfo.dart'; import 'package:surface/types/chat.dart'; +import 'package:surface/types/realm.dart'; import 'package:surface/widgets/account/account_image.dart'; import 'package:surface/widgets/account/account_select.dart'; import 'package:surface/widgets/app_bar_leading.dart'; @@ -18,6 +23,7 @@ import 'package:surface/widgets/dialog.dart'; import 'package:surface/widgets/loading_indicator.dart'; import 'package:surface/widgets/navigation/app_scaffold.dart'; import 'package:surface/widgets/unauthorized_hint.dart'; +import 'package:surface/widgets/universal_image.dart'; import 'package:uuid/uuid.dart'; class ChatScreen extends StatefulWidget { @@ -35,6 +41,7 @@ class _ChatScreenState extends State { List? _channels; Map? _lastMessages; Map? _unreadCounts; + Map? _unreadCountsGrouped; Future _fetchWhatsNew() async { final sn = context.read(); @@ -42,19 +49,48 @@ class _ChatScreenState extends State { if (resp.data == null) return; final List out = resp.data; setState(() { - _unreadCounts = {for (var v in out) v['channel_id']: v['count']}; + _unreadCounts ??= {}; + _unreadCountsGrouped ??= {}; + for (var v in out) { + _unreadCounts![v['channel_id']] = v['count']; + final channel = + _channels?.firstWhereOrNull((ele) => ele.id == v['channel_id']); + if (channel != null) { + if (channel.realmId != null) { + _unreadCountsGrouped![channel.realmId!] ??= 0; + _unreadCountsGrouped![channel.realmId!] = + (_unreadCountsGrouped![channel.realmId!]! + v['count']).toInt(); + } + if (channel.type == 1) { + _unreadCountsGrouped![0] ??= 0; + _unreadCountsGrouped![0] = + (_unreadCountsGrouped![0]! + v['count']).toInt(); + } + } + } }); } - void _refreshChannels({bool noRemote = false}) { + void _refreshChannels({bool withBoost = false, bool noRemote = false}) { + final ct = context.read(); final ua = context.read(); if (!ua.isAuthorized) { setState(() => _isBusy = false); return; } + if (!withBoost) { + if (!noRemote) { + ct.refreshAvailableChannels(); + } + } else { + setState(() { + _channels = ct.availableChannels; + }); + } + final chan = context.read(); - chan.fetchChannels(noRemote: noRemote).listen((channels) async { + chan.fetchChannels(noRemote: true).listen((channels) async { final lastMessages = await chan.getLastMessages(channels); _lastMessages = {for (final val in lastMessages) val.channelId: val}; channels.sort((a, b) { @@ -130,29 +166,49 @@ class _ChatScreenState extends State { @override void initState() { super.initState(); - _refreshChannels(); + _refreshChannels(withBoost: true); _fetchWhatsNew(); } void _onTapChannel(SnChannel channel) { setState(() => _unreadCounts?[channel.id] = 0); - GoRouter.of(context).pushReplacementNamed( - 'chatRoom', - pathParameters: { - 'scope': channel.realm?.alias ?? 'global', - 'alias': channel.alias, - }, - ).then((value) { - if (mounted) { - setState(() => _unreadCounts?[channel.id] = 0); - _refreshChannels(noRemote: true); - } - }); + if (ResponsiveScaffold.getIsExpand(context)) { + GoRouter.of(context).pushReplacementNamed( + 'chatRoom', + pathParameters: { + 'scope': channel.realm?.alias ?? 'global', + 'alias': channel.alias, + }, + ).then((value) { + if (mounted) { + setState(() => _unreadCounts?[channel.id] = 0); + _refreshChannels(noRemote: true); + } + }); + } else { + GoRouter.of(context).pushNamed( + 'chatRoom', + pathParameters: { + 'scope': channel.realm?.alias ?? 'global', + 'alias': channel.alias, + }, + ).then((value) { + if (mounted) { + setState(() => _unreadCounts?[channel.id] = 0); + _refreshChannels(noRemote: true); + } + }); + } } + SnRealm? _focusedRealm; + bool _isDirect = false; + @override Widget build(BuildContext context) { final ua = context.read(); + final sn = context.read(); + final rel = context.read(); if (!ua.isAuthorized) { return AppScaffold( @@ -235,34 +291,178 @@ class _ChatScreenState extends State { body: Column( children: [ LoadingIndicator(isActive: _isBusy), - Expanded( - child: MediaQuery.removePadding( - context: context, - removeTop: true, + if (_channels != null) + Expanded( child: RefreshIndicator( onRefresh: () => Future.wait([ Future.sync(() => _refreshChannels()), _fetchWhatsNew(), ]), - child: ListView.builder( - itemCount: _channels?.length ?? 0, - itemBuilder: (context, idx) { - final channel = _channels![idx]; - final lastMessage = _lastMessages?[channel.id]; + child: Builder(builder: (context) { + final scopeList = ListView( + key: const Key('realm-list-view'), + padding: EdgeInsets.zero, + children: [ + ListTile( + minTileHeight: 48, + leading: + const Icon(Symbols.inbox_text).padding(right: 4), + contentPadding: EdgeInsets.only(left: 24, right: 24), + title: Text('chatDirect').tr(), + trailing: Row( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + if (_unreadCountsGrouped?[0] != null && + (_unreadCountsGrouped?[0] ?? 0) > 0) + Badge( + label: Text( + _unreadCountsGrouped![0].toString(), + ), + ), + ], + ), + onTap: () { + setState(() => _isDirect = true); + }, + ), + ...rel.availableRealms.map((ele) { + return ListTile( + minTileHeight: 48, + contentPadding: EdgeInsets.only(left: 20, right: 24), + leading: AccountImage( + content: ele.avatar, + radius: 16, + ), + trailing: Row( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + if (_unreadCountsGrouped?[ele.id] != null && + (_unreadCountsGrouped?[ele.id] ?? 0) > 0) + Badge( + label: Text( + _unreadCountsGrouped![ele.id].toString(), + ), + ), + ], + ), + title: Text(ele.name), + onTap: () { + setState(() => _focusedRealm = ele); + }, + ); + }), + ], + ); - return _ChatChannelEntry( - channel: channel, - lastMessage: lastMessage, - unreadCount: _unreadCounts?[channel.id], - onTap: () { - _onTapChannel(channel); - }, - ); - }, - ), + final directChatList = ListView( + key: Key('direct-chat-list-view'), + padding: EdgeInsets.zero, + children: [ + ListTile( + minTileHeight: 48, + leading: const Icon(Symbols.arrow_left_alt), + contentPadding: EdgeInsets.only(left: 24), + title: Text('back').tr(), + onTap: () { + setState(() => _isDirect = false); + }, + ), + const Divider(height: 1), + ..._channels!.where((ele) => ele.type == 1).map( + (ele) { + return _ChatChannelEntry( + channel: ele, + unreadCount: _unreadCounts?[ele.id], + lastMessage: _lastMessages?[ele.id], + isCompact: true, + onTap: () => _onTapChannel(ele), + ); + }, + ) + ], + ); + + final realmScopedChatList = _focusedRealm == null + ? const SizedBox.shrink() + : ListView( + key: ValueKey(_focusedRealm), + padding: EdgeInsets.zero, + children: [ + if (_focusedRealm!.banner != null) + AspectRatio( + aspectRatio: 16 / 9, + child: AutoResizeUniversalImage( + sn.getAttachmentUrl( + _focusedRealm!.banner!, + ), + fit: BoxFit.cover, + ), + ), + ListTile( + minTileHeight: 48, + tileColor: Theme.of(context) + .colorScheme + .surfaceContainer, + leading: AccountImage( + content: _focusedRealm!.avatar, + radius: 16, + ), + contentPadding: EdgeInsets.only( + left: 20, + right: 16, + ), + trailing: IconButton( + icon: const Icon(Symbols.close), + padding: EdgeInsets.zero, + constraints: const BoxConstraints(), + visualDensity: VisualDensity.compact, + onPressed: () { + setState(() => _focusedRealm = null); + }, + ), + title: Text(_focusedRealm!.name), + ), + ...(_channels! + .where( + (ele) => ele.realmId == _focusedRealm?.id) + .map( + (ele) { + return _ChatChannelEntry( + channel: ele, + unreadCount: _unreadCounts?[ele.id], + lastMessage: _lastMessages?[ele.id], + onTap: () => _onTapChannel(ele), + isCompact: true, + ); + }, + )) + ], + ); + + return PageTransitionSwitcher( + duration: const Duration(milliseconds: 300), + transitionBuilder: (Widget child, + Animation primaryAnimation, + Animation secondaryAnimation) { + return SharedAxisTransition( + animation: primaryAnimation, + secondaryAnimation: secondaryAnimation, + fillColor: Colors.transparent, + transitionType: SharedAxisTransitionType.horizontal, + child: child, + ); + }, + child: (_focusedRealm == null && !_isDirect) + ? scopeList + : _isDirect + ? directChatList + : realmScopedChatList, + ); + }), ), ), - ), ], ), ); @@ -274,11 +474,13 @@ class _ChatChannelEntry extends StatelessWidget { final int? unreadCount; final SnChatMessage? lastMessage; final Function? onTap; + final bool isCompact; const _ChatChannelEntry({ required this.channel, this.unreadCount, this.lastMessage, this.onTap, + this.isCompact = false, }); @override @@ -297,6 +499,34 @@ class _ChatChannelEntry extends StatelessWidget { ? ud.getFromCache(otherMember.accountId)?.nick ?? channel.name : channel.name; + if (isCompact) { + return ListTile( + minTileHeight: 48, + contentPadding: + EdgeInsets.only(left: otherMember != null ? 20 : 24, right: 24), + leading: otherMember != null + ? AccountImage( + content: ud.getFromCache(otherMember.accountId)?.avatar, + radius: 16, + ) + : const Icon(Symbols.tag), + trailing: Row( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + if (unreadCount != null && (unreadCount ?? 0) > 0) + Badge( + label: Text(unreadCount.toString()), + ), + ], + ), + title: Text(title), + onTap: () { + onTap?.call(); + }, + ); + } + return ListTile( title: Row( children: [ @@ -359,7 +589,7 @@ class _ChatChannelEntry extends StatelessWidget { content: otherMember != null ? ud.getFromCache(otherMember.accountId)?.avatar : channel.realm?.avatar, - fallbackWidget: const Icon(Symbols.chat, size: 20), + fallbackWidget: const Icon(Symbols.tag, size: 20), ), onTap: () => onTap?.call(), ); diff --git a/lib/widgets/navigation/app_drawer_navigation.dart b/lib/widgets/navigation/app_drawer_navigation.dart index ab03f07..679ca37 100644 --- a/lib/widgets/navigation/app_drawer_navigation.dart +++ b/lib/widgets/navigation/app_drawer_navigation.dart @@ -1,6 +1,5 @@ import 'dart:io'; -import 'package:animations/animations.dart'; import 'package:bitsdojo_window/bitsdojo_window.dart'; import 'package:collection/collection.dart'; import 'package:easy_localization/easy_localization.dart'; @@ -11,13 +10,9 @@ import 'package:go_router/go_router.dart'; import 'package:material_symbols_icons/symbols.dart'; import 'package:provider/provider.dart'; import 'package:styled_widget/styled_widget.dart'; -import 'package:surface/providers/channel.dart'; import 'package:surface/providers/navigation.dart'; -import 'package:surface/providers/sn_network.dart'; -import 'package:surface/providers/sn_realm.dart'; import 'package:surface/providers/userinfo.dart'; import 'package:surface/widgets/account/account_image.dart'; -import 'package:surface/widgets/universal_image.dart'; import 'package:surface/widgets/version_label.dart'; class AppNavigationDrawer extends StatefulWidget { @@ -151,163 +146,3 @@ class _AppNavigationDrawerState extends State { ); } } - -class _DrawerContentList extends StatelessWidget { - const _DrawerContentList(); - - @override - Widget build(BuildContext context) { - final ct = context.read(); - final sn = context.read(); - final nav = context.watch(); - final rel = context.watch(); - - return PageTransitionSwitcher( - duration: const Duration(milliseconds: 300), - transitionBuilder: (Widget child, Animation primaryAnimation, - Animation secondaryAnimation) { - return SharedAxisTransition( - animation: primaryAnimation, - secondaryAnimation: secondaryAnimation, - fillColor: Colors.transparent, - transitionType: SharedAxisTransitionType.horizontal, - child: child, - ); - }, - child: nav.focusedRealm == null - ? ListView( - key: const Key('realm-list-view'), - padding: EdgeInsets.zero, - children: [ - Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text('Solar Network').bold(), - AppVersionLabel(), - ], - ).padding( - horizontal: 32, - vertical: 12, - ), - ...rel.availableRealms.map((ele) { - return ListTile( - minTileHeight: 48, - contentPadding: EdgeInsets.symmetric(horizontal: 24), - leading: AccountImage( - content: ele.avatar, - radius: 16, - ), - title: Text(ele.name), - onTap: () { - nav.setFocusedRealm(ele); - }, - ); - }), - ListTile( - minTileHeight: 48, - contentPadding: EdgeInsets.only(left: 28, right: 16), - leading: const Icon(Symbols.globe).padding(right: 4), - title: Text('screenRealmDiscovery').tr(), - onTap: () { - GoRouter.of(context).pushNamed('realmDiscovery'); - Scaffold.of(context).closeDrawer(); - }, - ), - ], - ) - : ListView( - key: ValueKey(nav.focusedRealm), - padding: EdgeInsets.zero, - children: [ - if (nav.focusedRealm!.banner != null) - AspectRatio( - aspectRatio: 16 / 9, - child: AutoResizeUniversalImage( - sn.getAttachmentUrl( - nav.focusedRealm!.banner!, - ), - fit: BoxFit.cover, - ), - ), - ListTile( - minTileHeight: 48, - tileColor: Theme.of(context).colorScheme.surfaceContainer, - contentPadding: EdgeInsets.only( - left: 24, - right: 16, - ), - leading: AccountImage( - content: nav.focusedRealm!.avatar, - radius: 16, - ), - trailing: IconButton( - icon: const Icon(Symbols.close), - padding: EdgeInsets.zero, - constraints: const BoxConstraints(), - visualDensity: VisualDensity.compact, - onPressed: () { - nav.setFocusedRealm(null); - }, - ), - title: Text(nav.focusedRealm!.name), - onTap: () { - GoRouter.of(context).goNamed( - 'realmDetail', - pathParameters: { - 'alias': nav.focusedRealm!.alias, - }, - ); - Scaffold.of(context).closeDrawer(); - }, - ), - ListTile( - minTileHeight: 48, - contentPadding: EdgeInsets.only( - left: 28, - right: 8, - ), - leading: const Icon(Symbols.globe), - title: Text('community').tr(), - onTap: () { - GoRouter.of(context).goNamed( - 'realmCommunity', - pathParameters: { - 'alias': nav.focusedRealm!.alias, - }, - ); - Scaffold.of(context).closeDrawer(); - }, - ), - if (ct.availableChannels - .where((ele) => ele.realmId == nav.focusedRealm?.id) - .isNotEmpty) - const Divider(height: 1), - ...(ct.availableChannels - .where((ele) => ele.realmId == nav.focusedRealm?.id) - .map((ele) { - return ListTile( - minTileHeight: 48, - contentPadding: EdgeInsets.only( - left: 28, - right: 8, - ), - leading: const Icon(Symbols.tag), - title: Text(ele.name), - onTap: () { - GoRouter.of(context).goNamed( - 'chatRoom', - pathParameters: { - 'scope': ele.realm?.alias ?? 'global', - 'alias': ele.alias, - }, - ); - Scaffold.of(context).closeDrawer(); - }, - ); - })) - ], - ), - ); - } -}