From e221016c8deb392d3ccd81f464c7584a71067914 Mon Sep 17 00:00:00 2001 From: LittleSheep Date: Sun, 23 Jun 2024 02:25:45 +0800 Subject: [PATCH] :sparkles: Better chat screen --- lib/screens/chat.dart | 192 ++++++++++++++++---------- lib/translations.dart | 2 + lib/widgets/channel/channel_list.dart | 17 +-- 3 files changed, 130 insertions(+), 81 deletions(-) diff --git a/lib/screens/chat.dart b/lib/screens/chat.dart index d212b40..5cabea1 100644 --- a/lib/screens/chat.dart +++ b/lib/screens/chat.dart @@ -1,5 +1,4 @@ import 'package:flutter/material.dart'; -import 'package:flutter_animate/flutter_animate.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import 'package:get/get.dart'; import 'package:solian/models/channel.dart'; @@ -80,82 +79,135 @@ class _ChatScreenState extends State { ); } - return RefreshIndicator( - onRefresh: () => getChannels(), - child: CustomScrollView( - slivers: [ - SliverAppBar( - title: AppBarTitle('chat'.tr), - centerTitle: false, - floating: true, - titleSpacing: SolianTheme.titleSpacing(context), - toolbarHeight: SolianTheme.toolbarHeight(context), - actions: [ - const BackgroundStateWidget(), - const NotificationButton(), - PopupMenuButton( - icon: const Icon(Icons.add_circle), - itemBuilder: (BuildContext context) => [ - PopupMenuItem( - child: ListTile( - title: Text('channelOrganizeCommon'.tr), - leading: const Icon(Icons.tag), - contentPadding: - const EdgeInsets.symmetric(horizontal: 8), - ), - onTap: () { - AppRouter.instance - .pushNamed('channelOrganizing') - .then( - (value) { - if (value != null) getChannels(); + final prefixSlivers = [ + Obx(() { + if (call.current.value != null) { + return const SliverToBoxAdapter( + child: ChatCallCurrentIndicator(), + ); + } else { + return const SliverToBoxAdapter(); + } + }), + ]; + + return DefaultTabController( + length: 2, + child: NestedScrollView( + headerSliverBuilder: (context, innerBoxIsScrolled) { + return [ + SliverOverlapAbsorber( + handle: NestedScrollView.sliverOverlapAbsorberHandleFor( + context), + sliver: SliverAppBar( + title: AppBarTitle('chat'.tr), + centerTitle: true, + floating: true, + titleSpacing: SolianTheme.titleSpacing(context), + toolbarHeight: SolianTheme.toolbarHeight(context), + actions: [ + const BackgroundStateWidget(), + const NotificationButton(), + PopupMenuButton( + icon: const Icon(Icons.add_circle), + itemBuilder: (BuildContext context) => [ + PopupMenuItem( + child: ListTile( + title: Text('channelOrganizeCommon'.tr), + leading: const Icon(Icons.tag), + contentPadding: + const EdgeInsets.symmetric(horizontal: 8), + ), + onTap: () { + AppRouter.instance + .pushNamed('channelOrganizing') + .then( + (value) { + if (value != null) getChannels(); + }, + ); }, - ); - }, - ), - PopupMenuItem( - child: ListTile( - title: Text('channelOrganizeDirect'.tr), - leading: const FaIcon( - FontAwesomeIcons.userGroup, - size: 16, ), - contentPadding: - const EdgeInsets.symmetric(horizontal: 8), - ), - onTap: () { - final ChannelProvider provider = Get.find(); - provider - .createDirectChannel(context, 'global') - .then((resp) { - if (resp != null) { - getChannels(); - } - }); - }, + PopupMenuItem( + child: ListTile( + title: Text('channelOrganizeDirect'.tr), + leading: const FaIcon( + FontAwesomeIcons.userGroup, + size: 16, + ), + contentPadding: + const EdgeInsets.symmetric(horizontal: 8), + ), + onTap: () { + final ChannelProvider provider = Get.find(); + provider + .createDirectChannel(context, 'global') + .then((resp) { + if (resp != null) { + getChannels(); + } + }); + }, + ), + ], + ), + SizedBox( + width: SolianTheme.isLargeScreen(context) ? 8 : 16, ), ], + bottom: TabBar( + tabs: [ + Tab( + icon: const Icon(Icons.tag), + text: 'channels'.tr, + ), + Tab( + icon: const Icon(Icons.chat), + text: 'channelCategoryDirect'.tr, + ), + ], + ), ), - SizedBox( - width: SolianTheme.isLargeScreen(context) ? 8 : 16, + ), + ]; + }, + body: Builder(builder: (context) { + if (_isBusy) { + return const Center(child: CircularProgressIndicator()); + } + + return TabBarView( + children: [ + RefreshIndicator( + onRefresh: () => getChannels(), + child: CustomScrollView( + slivers: [ + ...prefixSlivers, + ChannelListWidget( + channels: + _channels.where((x) => x.type == 0).toList(), + selfId: _accountId ?? 0, + ), + ], + ), + ), + RefreshIndicator( + onRefresh: () => getChannels(), + child: CustomScrollView( + slivers: [ + ...prefixSlivers, + ChannelListWidget( + channels: + _channels.where((x) => x.type == 1).toList(), + selfId: _accountId ?? 0, + noCategory: true, + ), + ], + ), ), ], - ), - Obx(() { - if (call.current.value != null) { - return const SliverToBoxAdapter( - child: ChatCallCurrentIndicator(), - ); - } else { - return const SliverToBoxAdapter(); - } - }), - if (_isBusy) - SliverToBoxAdapter( - child: const LinearProgressIndicator().animate().scaleX(), - ), - ChannelListWidget(channels: _channels, selfId: _accountId ?? 0), - ], + ); + }), ), ); }, diff --git a/lib/translations.dart b/lib/translations.dart index cf88721..fb78b7d 100644 --- a/lib/translations.dart +++ b/lib/translations.dart @@ -127,6 +127,7 @@ class SolianMessages extends Translations { 'realmDeletionConfirm': 'Confirm realm deletion', 'realmDeletionConfirmCaption': 'Are you sure to delete realm @realm? This action cannot be undone!', + 'channels': 'Channels', 'channelNew': 'Create a new channel', 'channelNewInRealmHint': 'Create channel in realm @realm', 'channelOrganizing': 'Organize a channel', @@ -328,6 +329,7 @@ class SolianMessages extends Translations { 'realmLeaveConfirmCaption': '你确认要离开领域 @realm 吗?你在该领域发表的内容不会被删除。', 'realmDeletionConfirm': '确认删除领域', 'realmDeletionConfirmCaption': '你确定要删除领域 @realm 嘛?该操作不可撤销。', + 'channels': '频道', 'channelNew': '创建新频道', 'channelNewInRealmHint': '在领域 @realm 里创建新频道', 'channelOrganizing': '组织频道', diff --git a/lib/widgets/channel/channel_list.dart b/lib/widgets/channel/channel_list.dart index d698113..5cae153 100644 --- a/lib/widgets/channel/channel_list.dart +++ b/lib/widgets/channel/channel_list.dart @@ -23,13 +23,11 @@ class ChannelListWidget extends StatefulWidget { class _ChannelListWidgetState extends State { final List _globalChannels = List.empty(growable: true); - final List _directMessages = List.empty(growable: true); final Map> _inRealms = {}; void mapChannels() { _inRealms.clear(); _globalChannels.clear(); - _directMessages.clear(); if (widget.noCategory) { _globalChannels.addAll(widget.channels); @@ -42,8 +40,6 @@ class _ChannelListWidgetState extends State { _inRealms[channel.realm!.alias] = List.empty(growable: true); } _inRealms[channel.realm!.alias]!.add(channel); - } else if (channel.type == 1) { - _directMessages.add(channel); } else { _globalChannels.add(channel); } @@ -56,6 +52,12 @@ class _ChannelListWidgetState extends State { super.didUpdateWidget(oldWidget); } + @override + void initState() { + mapChannels(); + super.initState(); + } + Widget buildItem(Channel element) { if (element.type == 1) { final otherside = element.members! @@ -125,13 +127,6 @@ class _ChannelListWidgetState extends State { return SliverList.list( children: [ ..._globalChannels.map((e) => buildItem(e)), - if (_directMessages.isNotEmpty) - ExpansionTile( - tilePadding: const EdgeInsets.symmetric(horizontal: 24), - title: Text('channelCategoryDirect'.tr), - subtitle: Text('channelCategoryDirectHint'.tr), - children: _directMessages.map((e) => buildItem(e)).toList(), - ), ..._inRealms.entries.map((element) { return ExpansionTile( tilePadding: const EdgeInsets.symmetric(horizontal: 24),