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",
"placeholders": {}
},
"noSearchResult": "No matching search results.",
"moderator": "Moderator",
"@moderator": {
"type": "text",

View File

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

View File

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

View File

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

View File

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

View File

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