fix: minor issues in room list

- allow to discard focus of search field
- properly circle the search field's progress indicator
- always keep search sections visible in order to workaround annoying
  behavior: When quickly searching for a chat and one is fast at
clicking on a room, it often happens that server side results just drop
in at this moment and one clicks at the wrong item -> with a static
height as now set, this no longer happens.

Signed-off-by: TheOneWithTheBraid <the-one@with-the-braid.cf>
This commit is contained in:
TheOneWithTheBraid 2022-12-25 13:08:51 +01:00
parent f9e4b9356a
commit 0a4f7c9d26
6 changed files with 115 additions and 92 deletions

View File

@ -1450,6 +1450,7 @@
"type": "text", "type": "text",
"placeholders": {} "placeholders": {}
}, },
"noSearchResult": "No matching search results.",
"moderator": "Moderator", "moderator": "Moderator",
"@moderator": { "@moderator": {
"type": "text", "type": "text",

View File

@ -55,6 +55,7 @@ enum ActiveFilter {
class ChatList extends StatefulWidget { class ChatList extends StatefulWidget {
static BuildContext? contextForVoip; static BuildContext? contextForVoip;
const ChatList({Key? key}) : super(key: key); const ChatList({Key? key}) : super(key: key);
@override @override
@ -235,12 +236,15 @@ class ChatListController extends State<ChatList>
_coolDown = Timer(const Duration(milliseconds: 500), _search); _coolDown = Timer(const Duration(milliseconds: 500), _search);
} }
void cancelSearch() => setState(() { void cancelSearch() {
searchController.clear(); setState(() {
isSearchMode = false; searchController.clear();
roomSearchResult = userSearchResult = null; isSearchMode = false;
isSearching = false; roomSearchResult = userSearchResult = null;
}); isSearching = false;
});
FocusManager.instance.primaryFocus?.unfocus();
}
bool isTorBrowser = false; bool isTorBrowser = false;

View File

@ -73,75 +73,81 @@ class ChatListViewBody extends StatelessWidget {
return Column( return Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
if (roomSearchResult != null) ...[ if (controller.isSearchMode) ...[
SearchTitle( SearchTitle(
title: L10n.of(context)!.publicRooms, title: L10n.of(context)!.publicRooms,
icon: const Icon(Icons.explore_outlined), icon: const Icon(Icons.explore_outlined),
), ),
AnimatedContainer( SizedBox(
height: roomSearchResult.chunk.isEmpty ? 0 : 106, height: 106,
duration: const Duration(milliseconds: 250), child: roomSearchResult == null ||
clipBehavior: Clip.hardEdge, roomSearchResult.chunk.isEmpty
decoration: const BoxDecoration(), ? Center(
child: ListView.builder( child:
scrollDirection: Axis.horizontal, Text(L10n.of(context)!.noSearchResult),
itemCount: roomSearchResult.chunk.length, )
itemBuilder: (context, i) => _SearchItem( : ListView.builder(
title: roomSearchResult.chunk[i].name ?? scrollDirection: Axis.horizontal,
roomSearchResult itemCount: roomSearchResult.chunk.length,
.chunk[i].canonicalAlias?.localpart ?? itemBuilder: (context, i) => _SearchItem(
L10n.of(context)!.group, title: roomSearchResult.chunk[i].name ??
avatar: roomSearchResult.chunk[i].avatarUrl, roomSearchResult.chunk[i]
onPressed: () => showModalBottomSheet( .canonicalAlias?.localpart ??
context: context, L10n.of(context)!.group,
builder: (c) => PublicRoomBottomSheet( avatar:
roomAlias: roomSearchResult roomSearchResult.chunk[i].avatarUrl,
.chunk[i].canonicalAlias ?? onPressed: () => showModalBottomSheet(
roomSearchResult.chunk[i].roomId, context: context,
outerContext: context, builder: (c) => PublicRoomBottomSheet(
chunk: roomSearchResult.chunk[i], roomAlias: roomSearchResult
.chunk[i].canonicalAlias ??
roomSearchResult.chunk[i].roomId,
outerContext: context,
chunk: roomSearchResult.chunk[i],
),
),
),
), ),
),
),
),
), ),
],
if (userSearchResult != null) ...[
SearchTitle( SearchTitle(
title: L10n.of(context)!.users, title: L10n.of(context)!.users,
icon: const Icon(Icons.group_outlined), icon: const Icon(Icons.group_outlined),
), ),
AnimatedContainer( SizedBox(
height: userSearchResult.results.isEmpty ? 0 : 106, height: 106,
duration: const Duration(milliseconds: 250), child: userSearchResult == null ||
clipBehavior: Clip.hardEdge, userSearchResult.results.isEmpty
decoration: const BoxDecoration(), ? Center(
child: ListView.builder( child:
scrollDirection: Axis.horizontal, Text(L10n.of(context)!.noSearchResult),
itemCount: userSearchResult.results.length, )
itemBuilder: (context, i) => _SearchItem( : ListView.builder(
title: scrollDirection: Axis.horizontal,
userSearchResult.results[i].displayName ?? itemCount: userSearchResult.results.length,
userSearchResult itemBuilder: (context, i) => _SearchItem(
.results[i].userId.localpart ?? title: userSearchResult
L10n.of(context)!.unknownDevice, .results[i].displayName ??
avatar: userSearchResult.results[i].avatarUrl, userSearchResult
onPressed: () => showModalBottomSheet( .results[i].userId.localpart ??
context: context, L10n.of(context)!.unknownDevice,
builder: (c) => ProfileBottomSheet( avatar:
userId: userSearchResult.results[i].userId, userSearchResult.results[i].avatarUrl,
outerContext: context, onPressed: () => showModalBottomSheet(
context: context,
builder: (c) => ProfileBottomSheet(
userId: userSearchResult
.results[i].userId,
outerContext: context,
),
),
),
), ),
),
),
),
), ),
],
if (controller.isSearchMode)
SearchTitle( SearchTitle(
title: L10n.of(context)!.stories, title: L10n.of(context)!.stories,
icon: const Icon(Icons.camera_alt_outlined), icon: const Icon(Icons.camera_alt_outlined),
), ),
],
if (displayStoriesHeader) if (displayStoriesHeader)
StoriesHeader( StoriesHeader(
key: const Key('stories_header'), key: const Key('stories_header'),
@ -319,6 +325,7 @@ class _SearchItem extends StatelessWidget {
title, title,
maxLines: 2, maxLines: 2,
textAlign: TextAlign.center, textAlign: TextAlign.center,
overflow: TextOverflow.ellipsis,
style: const TextStyle( style: const TextStyle(
fontSize: 12, fontSize: 12,
), ),

View File

@ -67,8 +67,18 @@ class ChatListHeader extends StatelessWidget implements PreferredSizeWidget {
), ),
suffixIcon: controller.isSearchMode suffixIcon: controller.isSearchMode
? controller.isSearching ? controller.isSearching
? const CircularProgressIndicator.adaptive( ? const Align(
strokeWidth: 2, alignment: Alignment.centerRight,
child: Padding(
padding: EdgeInsets.symmetric(
vertical: 8.0, horizontal: 12),
child: SizedBox.square(
dimension: 24,
child: CircularProgressIndicator.adaptive(
strokeWidth: 2,
),
),
),
) )
: TextButton( : TextButton(
onPressed: controller.setServer, onPressed: controller.setServer,

View File

@ -222,32 +222,37 @@ class ChatListView extends StatelessWidget {
), ),
], ],
Expanded( Expanded(
child: Scaffold( child: GestureDetector(
appBar: ChatListHeader(controller: controller), onTap: FocusManager.instance.primaryFocus?.unfocus,
body: ChatListViewBody(controller), excludeFromSemantics: true,
bottomNavigationBar: controller.displayNavigationBar behavior: HitTestBehavior.translucent,
? NavigationBar( child: Scaffold(
height: 64, appBar: ChatListHeader(controller: controller),
selectedIndex: controller.selectedIndex, body: ChatListViewBody(controller),
onDestinationSelected: bottomNavigationBar: controller.displayNavigationBar
controller.onDestinationSelected, ? NavigationBar(
destinations: getNavigationDestinations(context), height: 64,
) selectedIndex: controller.selectedIndex,
: null, onDestinationSelected:
floatingActionButton: controller.filteredRooms.isNotEmpty && controller.onDestinationSelected,
selectMode == SelectMode.normal destinations: getNavigationDestinations(context),
? KeyBoardShortcuts( )
keysToPress: { : null,
LogicalKeyboardKey.controlLeft, floatingActionButton: controller.filteredRooms.isNotEmpty &&
LogicalKeyboardKey.keyN selectMode == SelectMode.normal
}, ? KeyBoardShortcuts(
onKeysPressed: () => keysToPress: {
VRouter.of(context).to('/newprivatechat'), LogicalKeyboardKey.controlLeft,
helpLabel: L10n.of(context)!.newChat, LogicalKeyboardKey.keyN
child: StartChatFloatingActionButton( },
controller: controller), onKeysPressed: () =>
) VRouter.of(context).to('/newprivatechat'),
: null, helpLabel: L10n.of(context)!.newChat,
child: StartChatFloatingActionButton(
controller: controller),
)
: null,
),
), ),
), ),
], ],

View File

@ -19,6 +19,7 @@ enum ContextualRoomAction {
class StoriesHeader extends StatelessWidget { class StoriesHeader extends StatelessWidget {
final String filter; final String filter;
const StoriesHeader({required this.filter, Key? key}) : super(key: key); const StoriesHeader({required this.filter, Key? key}) : super(key: key);
void _addToStoryAction(BuildContext context) => void _addToStoryAction(BuildContext context) =>
@ -100,11 +101,6 @@ class StoriesHeader extends StatelessWidget {
onTap: () => _addToStoryAction(context), onTap: () => _addToStoryAction(context),
); );
} }
if (client.storiesRooms.isEmpty ||
!client.storiesRooms.any((room) =>
room.displayname.toLowerCase().contains(filter.toLowerCase()))) {
return Container();
}
final ownStoryRoom = client.storiesRooms final ownStoryRoom = client.storiesRooms
.firstWhereOrNull((r) => r.creatorId == client.userID); .firstWhereOrNull((r) => r.creatorId == client.userID);
final stories = [ final stories = [