feat: room search, room options sheet, .htaccess for E2EE
- Rooms screen: inline search filter with toggle — replaces the no-op button - Chat screen: room options sheet — member list, invite, leave room - rooms_repository: remove debug prints - web/.htaccess: COOP+COEP headers for SharedArrayBuffer / LiveKit E2EE worker (was on production but missing from source; adds it to build output automatically) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
// Version: 1.3.0 | Created: 2026-04-01 | Updated: 2026-04-11
|
||||
// Version: 1.4.0 | Created: 2026-04-01 | Updated: 2026-04-27
|
||||
// Main rooms list screen with bottom navigation.
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
@@ -22,6 +22,25 @@ class RoomsScreen extends ConsumerStatefulWidget {
|
||||
|
||||
class _RoomsScreenState extends ConsumerState<RoomsScreen> {
|
||||
int _selectedIndex = 0;
|
||||
bool _searchActive = false;
|
||||
String _searchQuery = '';
|
||||
final _searchController = TextEditingController();
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_searchController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void _toggleSearch() {
|
||||
setState(() {
|
||||
_searchActive = !_searchActive;
|
||||
if (!_searchActive) {
|
||||
_searchQuery = '';
|
||||
_searchController.clear();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
static const _destinations = [
|
||||
NavigationDestination(
|
||||
@@ -53,7 +72,7 @@ class _RoomsScreenState extends ConsumerState<RoomsScreen> {
|
||||
|
||||
Widget _buildBody() {
|
||||
return switch (_selectedIndex) {
|
||||
0 => const _RoomListBody(),
|
||||
0 => _RoomListBody(searchQuery: _searchQuery),
|
||||
1 => const SpacesScreen(embedded: true),
|
||||
2 => const ProfileScreen(embedded: true),
|
||||
3 => const ConferenceTab(),
|
||||
@@ -67,20 +86,29 @@ class _RoomsScreenState extends ConsumerState<RoomsScreen> {
|
||||
return Scaffold(
|
||||
appBar: _selectedIndex == 0
|
||||
? AppBar(
|
||||
title: const Text('M8Chat'),
|
||||
title: _searchActive
|
||||
? TextField(
|
||||
controller: _searchController,
|
||||
autofocus: true,
|
||||
decoration: const InputDecoration(
|
||||
hintText: 'Search rooms...',
|
||||
border: InputBorder.none,
|
||||
),
|
||||
onChanged: (v) => setState(() => _searchQuery = v),
|
||||
)
|
||||
: const Text('M8Chat'),
|
||||
actions: [
|
||||
IconButton(
|
||||
icon: const Icon(Icons.search),
|
||||
tooltip: 'Search rooms',
|
||||
onPressed: () {
|
||||
// Phase 2: room search
|
||||
},
|
||||
),
|
||||
IconButton(
|
||||
icon: const Icon(Icons.edit_square),
|
||||
tooltip: 'New message',
|
||||
onPressed: () => showUserSearchDialog(context),
|
||||
icon: Icon(_searchActive ? Icons.close : Icons.search),
|
||||
tooltip: _searchActive ? 'Cancel search' : 'Search rooms',
|
||||
onPressed: _toggleSearch,
|
||||
),
|
||||
if (!_searchActive)
|
||||
IconButton(
|
||||
icon: const Icon(Icons.edit_square),
|
||||
tooltip: 'New message',
|
||||
onPressed: () => showUserSearchDialog(context),
|
||||
),
|
||||
],
|
||||
)
|
||||
: null,
|
||||
@@ -96,7 +124,9 @@ class _RoomsScreenState extends ConsumerState<RoomsScreen> {
|
||||
}
|
||||
|
||||
class _RoomListBody extends ConsumerWidget {
|
||||
const _RoomListBody();
|
||||
const _RoomListBody({this.searchQuery = ''});
|
||||
|
||||
final String searchQuery;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
@@ -132,14 +162,39 @@ class _RoomListBody extends ConsumerWidget {
|
||||
),
|
||||
),
|
||||
data: (rooms) {
|
||||
if (rooms.isEmpty) {
|
||||
return const _EmptyRoomsState();
|
||||
final filtered = searchQuery.trim().isEmpty
|
||||
? rooms
|
||||
: rooms
|
||||
.where((r) => r.displayName
|
||||
.toLowerCase()
|
||||
.contains(searchQuery.trim().toLowerCase()))
|
||||
.toList();
|
||||
|
||||
if (filtered.isEmpty) {
|
||||
return searchQuery.isNotEmpty
|
||||
? Center(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(32),
|
||||
child: Text(
|
||||
'No rooms match "$searchQuery".',
|
||||
textAlign: TextAlign.center,
|
||||
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.onSurface
|
||||
.withAlpha(153),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
: const _EmptyRoomsState();
|
||||
}
|
||||
|
||||
return ListView.separated(
|
||||
itemCount: rooms.length,
|
||||
itemCount: filtered.length,
|
||||
separatorBuilder: (_, __) => const Divider(height: 1, indent: 72),
|
||||
itemBuilder: (context, index) {
|
||||
final room = rooms[index];
|
||||
final room = filtered[index];
|
||||
return RoomTile(
|
||||
room: room,
|
||||
onTap: () =>
|
||||
|
||||
Reference in New Issue
Block a user