style: Use SliverList for chatlist

This commit is contained in:
Krille 2023-03-19 19:59:50 +01:00
parent 46e0e4b65e
commit 61c4d0e61f
7 changed files with 336 additions and 275 deletions

View file

@ -16,7 +16,7 @@ abstract class AppConfig {
static const double messageFontSize = 15.75; static const double messageFontSize = 15.75;
static const bool allowOtherHomeservers = true; static const bool allowOtherHomeservers = true;
static const bool enableRegistration = true; static const bool enableRegistration = true;
static const Color primaryColor = Color.fromARGB(255, 135, 103, 172); static const Color primaryColor = Color(0xFF5625BA);
static const Color primaryColorLight = Color(0xFFCCBDEA); static const Color primaryColorLight = Color(0xFFCCBDEA);
static const Color secondaryColor = Color(0xFF41a2bc); static const Color secondaryColor = Color(0xFF41a2bc);
static String _privacyUrl = static String _privacyUrl =

View file

@ -8,6 +8,7 @@ import 'package:fluffychat/pages/chat_list/chat_list.dart';
import 'package:fluffychat/pages/chat_list/chat_list_item.dart'; import 'package:fluffychat/pages/chat_list/chat_list_item.dart';
import 'package:fluffychat/pages/chat_list/search_title.dart'; import 'package:fluffychat/pages/chat_list/search_title.dart';
import 'package:fluffychat/pages/chat_list/space_view.dart'; import 'package:fluffychat/pages/chat_list/space_view.dart';
import 'package:fluffychat/pages/chat_list/start_chat_fab.dart';
import 'package:fluffychat/pages/chat_list/stories_header.dart'; import 'package:fluffychat/pages/chat_list/stories_header.dart';
import 'package:fluffychat/utils/adaptive_bottom_sheet.dart'; import 'package:fluffychat/utils/adaptive_bottom_sheet.dart';
import 'package:fluffychat/utils/matrix_sdk_extensions/client_stories_extension.dart'; import 'package:fluffychat/utils/matrix_sdk_extensions/client_stories_extension.dart';
@ -19,6 +20,7 @@ import 'package:fluffychat/widgets/public_room_bottom_sheet.dart';
import '../../config/themes.dart'; import '../../config/themes.dart';
import '../../widgets/connection_status_header.dart'; import '../../widgets/connection_status_header.dart';
import '../../widgets/matrix.dart'; import '../../widgets/matrix.dart';
import 'chat_list_header.dart';
class ChatListViewBody extends StatelessWidget { class ChatListViewBody extends StatelessWidget {
final ChatListController controller; final ChatListController controller;
@ -70,15 +72,13 @@ class ChatListViewBody extends StatelessWidget {
ActiveFilter.messages, ActiveFilter.messages,
}.contains(controller.activeFilter) && }.contains(controller.activeFilter) &&
client.storiesRooms.isNotEmpty; client.storiesRooms.isNotEmpty;
return ListView.builder( return CustomScrollView(
controller: controller.scrollController, controller: controller.scrollController,
// add +1 space below in order to properly scroll below the spaces bar slivers: [
itemCount: rooms.length + 1, ChatListHeader(controller: controller),
itemBuilder: (BuildContext context, int i) { SliverList(
if (i == 0) { delegate: SliverChildListDelegate(
return Column( [
mainAxisSize: MainAxisSize.min,
children: [
if (controller.isSearchMode) ...[ if (controller.isSearchMode) ...[
SearchTitle( SearchTitle(
title: L10n.of(context)!.publicRooms, title: L10n.of(context)!.publicRooms,
@ -187,7 +187,7 @@ class ChatListViewBody extends StatelessWidget {
title: L10n.of(context)!.chats, title: L10n.of(context)!.chats,
icon: const Icon(Icons.forum_outlined), icon: const Icon(Icons.forum_outlined),
), ),
if (rooms.isEmpty && !controller.isSearchMode) if (rooms.isEmpty && !controller.isSearchMode) ...[
Padding( Padding(
padding: const EdgeInsets.all(32.0), padding: const EdgeInsets.all(32.0),
child: Column( child: Column(
@ -201,12 +201,24 @@ class ChatListViewBody extends StatelessWidget {
], ],
), ),
), ),
Center(
child: StartChatFloatingActionButton(
activeFilter: controller.activeFilter,
roomsIsEmpty: true,
scrolledToTop: controller.scrolledToTop,
),
),
], ],
); ],
} ),
i--; ),
SliverList(
delegate: SliverChildBuilderDelegate(
(BuildContext context, int i) {
if (!rooms[i] if (!rooms[i]
.getLocalizedDisplayname(MatrixLocals(L10n.of(context)!)) .getLocalizedDisplayname(
MatrixLocals(L10n.of(context)!),
)
.toLowerCase() .toLowerCase()
.contains( .contains(
controller.searchController.text.toLowerCase(), controller.searchController.text.toLowerCase(),
@ -216,14 +228,20 @@ class ChatListViewBody extends StatelessWidget {
return ChatListItem( return ChatListItem(
rooms[i], rooms[i],
key: Key('chat_list_item_${rooms[i].id}'), key: Key('chat_list_item_${rooms[i].id}'),
selected: controller.selectedRoomIds.contains(rooms[i].id), selected:
controller.selectedRoomIds.contains(rooms[i].id),
onTap: controller.selectMode == SelectMode.select onTap: controller.selectMode == SelectMode.select
? () => controller.toggleSelection(rooms[i].id) ? () => controller.toggleSelection(rooms[i].id)
: null, : null,
onLongPress: () => controller.toggleSelection(rooms[i].id), onLongPress: () =>
controller.toggleSelection(rooms[i].id),
activeChat: controller.activeChat == rooms[i].id, activeChat: controller.activeChat == rooms[i].id,
); );
}, },
childCount: rooms.length,
),
),
],
); );
} }
const dummyChatCount = 5; const dummyChatCount = 5;

View file

@ -3,6 +3,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/l10n.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart';
import 'package:fluffychat/config/app_config.dart'; import 'package:fluffychat/config/app_config.dart';
import 'package:fluffychat/config/themes.dart';
import 'package:fluffychat/pages/chat_list/chat_list.dart'; import 'package:fluffychat/pages/chat_list/chat_list.dart';
import 'package:fluffychat/pages/chat_list/client_chooser_button.dart'; import 'package:fluffychat/pages/chat_list/client_chooser_button.dart';
import '../../widgets/matrix.dart'; import '../../widgets/matrix.dart';
@ -16,7 +17,12 @@ class ChatListHeader extends StatelessWidget implements PreferredSizeWidget {
Widget build(BuildContext context) { Widget build(BuildContext context) {
final selectMode = controller.selectMode; final selectMode = controller.selectMode;
return AppBar( return SliverAppBar(
floating: true,
pinned: FluffyThemes.isColumnMode(context),
elevation: 0,
scrolledUnderElevation: 0,
backgroundColor: Colors.transparent,
automaticallyImplyLeading: false, automaticallyImplyLeading: false,
leading: selectMode == SelectMode.normal leading: selectMode == SelectMode.normal
? null ? null
@ -38,15 +44,17 @@ class ChatListHeader extends StatelessWidget implements PreferredSizeWidget {
) )
: SizedBox( : SizedBox(
height: 44, height: 44,
child: Material(
elevation:
Theme.of(context).appBarTheme.scrolledUnderElevation ??
4,
shadowColor: Theme.of(context).appBarTheme.shadowColor,
borderRadius: BorderRadius.circular(AppConfig.borderRadius),
child: TextField( child: TextField(
controller: controller.searchController, controller: controller.searchController,
textInputAction: TextInputAction.search, textInputAction: TextInputAction.search,
onChanged: controller.onSearchEnter, onChanged: controller.onSearchEnter,
decoration: InputDecoration( decoration: InputDecoration(
fillColor: Theme.of(context)
.colorScheme
.secondaryContainer
.withAlpha(128),
border: UnderlineInputBorder( border: UnderlineInputBorder(
borderSide: BorderSide.none, borderSide: BorderSide.none,
borderRadius: borderRadius:
@ -59,11 +67,13 @@ class ChatListHeader extends StatelessWidget implements PreferredSizeWidget {
tooltip: L10n.of(context)!.cancel, tooltip: L10n.of(context)!.cancel,
icon: const Icon(Icons.close_outlined), icon: const Icon(Icons.close_outlined),
onPressed: controller.cancelSearch, onPressed: controller.cancelSearch,
color: Theme.of(context).colorScheme.onBackground, color:
Theme.of(context).colorScheme.onBackground,
) )
: Icon( : Icon(
Icons.search_outlined, Icons.search_outlined,
color: Theme.of(context).colorScheme.onBackground, color:
Theme.of(context).colorScheme.onBackground,
), ),
suffixIcon: controller.isSearchMode suffixIcon: controller.isSearchMode
? controller.isSearching ? controller.isSearching
@ -100,6 +110,7 @@ class ChatListHeader extends StatelessWidget implements PreferredSizeWidget {
), ),
), ),
), ),
),
actions: selectMode == SelectMode.share actions: selectMode == SelectMode.share
? [ ? [
Padding( Padding(

View file

@ -15,7 +15,6 @@ import 'package:fluffychat/widgets/avatar.dart';
import 'package:fluffychat/widgets/unread_rooms_badge.dart'; import 'package:fluffychat/widgets/unread_rooms_badge.dart';
import '../../widgets/matrix.dart'; import '../../widgets/matrix.dart';
import 'chat_list_body.dart'; import 'chat_list_body.dart';
import 'chat_list_header.dart';
import 'start_chat_fab.dart'; import 'start_chat_fab.dart';
class ChatListView extends StatelessWidget { class ChatListView extends StatelessWidget {
@ -46,12 +45,12 @@ class ChatListView extends StatelessWidget {
icon: UnreadRoomsBadge( icon: UnreadRoomsBadge(
badgePosition: badgePosition, badgePosition: badgePosition,
filter: controller.getRoomFilterByActiveFilter(ActiveFilter.groups), filter: controller.getRoomFilterByActiveFilter(ActiveFilter.groups),
child: const Icon(Icons.groups_outlined), child: const Icon(Icons.group_outlined),
), ),
selectedIcon: UnreadRoomsBadge( selectedIcon: UnreadRoomsBadge(
badgePosition: badgePosition, badgePosition: badgePosition,
filter: controller.getRoomFilterByActiveFilter(ActiveFilter.groups), filter: controller.getRoomFilterByActiveFilter(ActiveFilter.groups),
child: const Icon(Icons.groups), child: const Icon(Icons.group),
), ),
label: L10n.of(context)!.groups, label: L10n.of(context)!.groups,
), ),
@ -174,7 +173,6 @@ class ChatListView extends StatelessWidget {
excludeFromSemantics: true, excludeFromSemantics: true,
behavior: HitTestBehavior.translucent, behavior: HitTestBehavior.translucent,
child: Scaffold( child: Scaffold(
appBar: ChatListHeader(controller: controller),
body: ChatListViewBody(controller), body: ChatListViewBody(controller),
bottomNavigationBar: controller.displayNavigationBar bottomNavigationBar: controller.displayNavigationBar
? NavigationBar( ? NavigationBar(
@ -185,12 +183,7 @@ class ChatListView extends StatelessWidget {
destinations: getNavigationDestinations(context), destinations: getNavigationDestinations(context),
) )
: null, : null,
floatingActionButtonLocation: floatingActionButton: KeyBoardShortcuts(
controller.filteredRooms.isEmpty
? FloatingActionButtonLocation.centerFloat
: null,
floatingActionButton: selectMode == SelectMode.normal
? KeyBoardShortcuts(
keysToPress: { keysToPress: {
LogicalKeyboardKey.controlLeft, LogicalKeyboardKey.controlLeft,
LogicalKeyboardKey.keyN LogicalKeyboardKey.keyN
@ -198,11 +191,16 @@ class ChatListView extends StatelessWidget {
onKeysPressed: () => onKeysPressed: () =>
VRouter.of(context).to('/newprivatechat'), VRouter.of(context).to('/newprivatechat'),
helpLabel: L10n.of(context)!.newChat, helpLabel: L10n.of(context)!.newChat,
child: StartChatFloatingActionButton( child: selectMode == SelectMode.normal &&
controller: controller, controller.filteredRooms.isNotEmpty &&
), !controller.isSearchMode
? StartChatFloatingActionButton(
activeFilter: controller.activeFilter,
roomsIsEmpty: false,
scrolledToTop: controller.scrolledToTop,
) )
: null, : const SizedBox.shrink(),
),
), ),
), ),
), ),

View file

@ -14,6 +14,7 @@ import 'package:fluffychat/utils/matrix_sdk_extensions/matrix_locals.dart';
import 'package:fluffychat/widgets/avatar.dart'; import 'package:fluffychat/widgets/avatar.dart';
import '../../utils/localized_exception_extension.dart'; import '../../utils/localized_exception_extension.dart';
import '../../widgets/matrix.dart'; import '../../widgets/matrix.dart';
import 'chat_list_header.dart';
class SpaceView extends StatefulWidget { class SpaceView extends StatefulWidget {
final ChatListController controller; final ChatListController controller;
@ -154,10 +155,13 @@ class _SpaceViewState extends State<SpaceView> {
) )
.toList(); .toList();
return ListView.builder( return CustomScrollView(
itemCount: rootSpaces.length,
controller: widget.scrollController, controller: widget.scrollController,
itemBuilder: (context, i) { slivers: [
ChatListHeader(controller: widget.controller),
SliverList(
delegate: SliverChildBuilderDelegate(
(context, i) {
final rootSpace = rootSpaces[i]; final rootSpace = rootSpaces[i];
final displayname = rootSpace.getLocalizedDisplayname( final displayname = rootSpace.getLocalizedDisplayname(
MatrixLocals(L10n.of(context)!), MatrixLocals(L10n.of(context)!),
@ -179,11 +183,16 @@ class _SpaceViewState extends State<SpaceView> {
.numChats(rootSpace.spaceChildren.length.toString()), .numChats(rootSpace.spaceChildren.length.toString()),
), ),
onTap: () => widget.controller.setActiveSpace(rootSpace.id), onTap: () => widget.controller.setActiveSpace(rootSpace.id),
onLongPress: () => _onSpaceChildContextMenu(null, rootSpace), onLongPress: () =>
_onSpaceChildContextMenu(null, rootSpace),
trailing: const Icon(Icons.chevron_right_outlined), trailing: const Icon(Icons.chevron_right_outlined),
), ),
); );
}, },
childCount: rootSpaces.length,
),
),
],
); );
} }
return FutureBuilder<GetSpaceHierarchyResponse>( return FutureBuilder<GetSpaceHierarchyResponse>(
@ -208,7 +217,16 @@ class _SpaceViewState extends State<SpaceView> {
); );
} }
if (response == null) { if (response == null) {
return const Center(child: CircularProgressIndicator.adaptive()); return CustomScrollView(
slivers: [
ChatListHeader(controller: widget.controller),
const SliverFillRemaining(
child: Center(
child: CircularProgressIndicator.adaptive(),
),
),
],
);
} }
final parentSpace = allSpaces.firstWhereOrNull( final parentSpace = allSpaces.firstWhereOrNull(
(space) => (space) =>
@ -224,10 +242,13 @@ class _SpaceViewState extends State<SpaceView> {
return; return;
} }
}, },
child: ListView.builder( child: CustomScrollView(
itemCount: spaceChildren.length + 1 + (canLoadMore ? 1 : 0),
controller: widget.scrollController, controller: widget.scrollController,
itemBuilder: (context, i) { slivers: [
ChatListHeader(controller: widget.controller),
SliverList(
delegate: SliverChildBuilderDelegate(
(context, i) {
if (i == 0) { if (i == 0) {
return ListTile( return ListTile(
leading: BackButton( leading: BackButton(
@ -245,7 +266,8 @@ class _SpaceViewState extends State<SpaceView> {
icon: snapshot.connectionState != ConnectionState.done icon: snapshot.connectionState != ConnectionState.done
? const CircularProgressIndicator.adaptive() ? const CircularProgressIndicator.adaptive()
: const Icon(Icons.refresh_outlined), : const Icon(Icons.refresh_outlined),
onPressed: snapshot.connectionState != ConnectionState.done onPressed:
snapshot.connectionState != ConnectionState.done
? null ? null
: _refresh, : _refresh,
), ),
@ -267,17 +289,20 @@ class _SpaceViewState extends State<SpaceView> {
if (room != null && !room.isSpace) { if (room != null && !room.isSpace) {
return ChatListItem( return ChatListItem(
room, room,
onLongPress: () => _onSpaceChildContextMenu(spaceChild, room), onLongPress: () =>
_onSpaceChildContextMenu(spaceChild, room),
activeChat: widget.controller.activeChat == room.id, activeChat: widget.controller.activeChat == room.id,
); );
} }
final isSpace = spaceChild.roomType == 'm.space'; final isSpace = spaceChild.roomType == 'm.space';
final topic = final topic = spaceChild.topic?.isEmpty ?? true
spaceChild.topic?.isEmpty ?? true ? null : spaceChild.topic; ? null
: spaceChild.topic;
if (spaceChild.roomId == activeSpaceId) { if (spaceChild.roomId == activeSpaceId) {
return SearchTitle( return SearchTitle(
title: title: spaceChild.name ??
spaceChild.name ?? spaceChild.canonicalAlias ?? 'Space', spaceChild.canonicalAlias ??
'Space',
icon: Padding( icon: Padding(
padding: const EdgeInsets.symmetric(horizontal: 10.0), padding: const EdgeInsets.symmetric(horizontal: 10.0),
child: Avatar( child: Avatar(
@ -311,7 +336,8 @@ class _SpaceViewState extends State<SpaceView> {
spaceChild.canonicalAlias ?? spaceChild.canonicalAlias ??
L10n.of(context)!.chat, L10n.of(context)!.chat,
maxLines: 1, maxLines: 1,
style: const TextStyle(fontWeight: FontWeight.bold), style:
const TextStyle(fontWeight: FontWeight.bold),
), ),
), ),
if (!isSpace) ...[ if (!isSpace) ...[
@ -328,7 +354,8 @@ class _SpaceViewState extends State<SpaceView> {
], ],
), ),
onTap: () => _onJoinSpaceChild(spaceChild), onTap: () => _onJoinSpaceChild(spaceChild),
onLongPress: () => _onSpaceChildContextMenu(spaceChild, room), onLongPress: () =>
_onSpaceChildContextMenu(spaceChild, room),
subtitle: Text( subtitle: Text(
topic ?? topic ??
(isSpace (isSpace
@ -339,10 +366,15 @@ class _SpaceViewState extends State<SpaceView> {
color: Theme.of(context).colorScheme.onBackground, color: Theme.of(context).colorScheme.onBackground,
), ),
), ),
trailing: trailing: isSpace
isSpace ? const Icon(Icons.chevron_right_outlined) : null, ? const Icon(Icons.chevron_right_outlined)
: null,
); );
}, },
childCount: spaceChildren.length + 1 + (canLoadMore ? 1 : 0),
),
),
],
), ),
); );
}, },

View file

@ -7,13 +7,19 @@ import '../../config/themes.dart';
import 'chat_list.dart'; import 'chat_list.dart';
class StartChatFloatingActionButton extends StatelessWidget { class StartChatFloatingActionButton extends StatelessWidget {
final ChatListController controller; final ActiveFilter activeFilter;
final bool scrolledToTop;
final bool roomsIsEmpty;
const StartChatFloatingActionButton({Key? key, required this.controller}) const StartChatFloatingActionButton({
: super(key: key); Key? key,
required this.activeFilter,
required this.scrolledToTop,
required this.roomsIsEmpty,
}) : super(key: key);
void _onPressed(BuildContext context) { void _onPressed(BuildContext context) {
switch (controller.activeFilter) { switch (activeFilter) {
case ActiveFilter.allChats: case ActiveFilter.allChats:
case ActiveFilter.messages: case ActiveFilter.messages:
VRouter.of(context).to('/newprivatechat'); VRouter.of(context).to('/newprivatechat');
@ -28,10 +34,10 @@ class StartChatFloatingActionButton extends StatelessWidget {
} }
IconData get icon { IconData get icon {
switch (controller.activeFilter) { switch (activeFilter) {
case ActiveFilter.allChats: case ActiveFilter.allChats:
case ActiveFilter.messages: case ActiveFilter.messages:
return Icons.edit_outlined; return Icons.add_outlined;
case ActiveFilter.groups: case ActiveFilter.groups:
return Icons.group_add_outlined; return Icons.group_add_outlined;
case ActiveFilter.spaces: case ActiveFilter.spaces:
@ -40,10 +46,10 @@ class StartChatFloatingActionButton extends StatelessWidget {
} }
String getLabel(BuildContext context) { String getLabel(BuildContext context) {
switch (controller.activeFilter) { switch (activeFilter) {
case ActiveFilter.allChats: case ActiveFilter.allChats:
case ActiveFilter.messages: case ActiveFilter.messages:
return controller.filteredRooms.isEmpty return roomsIsEmpty
? L10n.of(context)!.startFirstChat ? L10n.of(context)!.startFirstChat
: L10n.of(context)!.newChat; : L10n.of(context)!.newChat;
case ActiveFilter.groups: case ActiveFilter.groups:
@ -58,15 +64,13 @@ class StartChatFloatingActionButton extends StatelessWidget {
return AnimatedContainer( return AnimatedContainer(
duration: FluffyThemes.animationDuration, duration: FluffyThemes.animationDuration,
curve: FluffyThemes.animationCurve, curve: FluffyThemes.animationCurve,
width: controller.filteredRooms.isEmpty width: roomsIsEmpty
? null ? null
: controller.scrolledToTop : scrolledToTop
? 144 ? 144
: 56, : 56,
child: controller.scrolledToTop child: scrolledToTop
? FloatingActionButton.extended( ? FloatingActionButton.extended(
backgroundColor: Theme.of(context).colorScheme.primary,
foregroundColor: Theme.of(context).colorScheme.onPrimary,
onPressed: () => _onPressed(context), onPressed: () => _onPressed(context),
icon: Icon(icon), icon: Icon(icon),
label: Text( label: Text(
@ -75,8 +79,6 @@ class StartChatFloatingActionButton extends StatelessWidget {
), ),
) )
: FloatingActionButton( : FloatingActionButton(
backgroundColor: Theme.of(context).colorScheme.primary,
foregroundColor: Theme.of(context).colorScheme.onPrimary,
onPressed: () => _onPressed(context), onPressed: () => _onPressed(context),
child: Icon(icon), child: Icon(icon),
), ),

View file

@ -47,11 +47,11 @@ class SettingsStyleController extends State<SettingsStyle> {
static final List<Color?> customColors = [ static final List<Color?> customColors = [
AppConfig.chatColor, AppConfig.chatColor,
Colors.blue.shade800, Colors.indigo,
Colors.green.shade800, Colors.green,
Colors.orange.shade700, Colors.orange,
Colors.pink.shade700, Colors.pink,
Colors.blueGrey.shade600, Colors.blueGrey,
null, null,
]; ];