chore: Follow up homeserver picker input

This commit is contained in:
Krille Fear 2022-12-25 10:50:41 +01:00
parent e04730c904
commit 135ed4fb17
3 changed files with 524 additions and 506 deletions

View file

@ -13,7 +13,6 @@ import 'package:fluffychat/pages/chat_list/stories_header.dart';
import 'package:fluffychat/widgets/avatar.dart'; import 'package:fluffychat/widgets/avatar.dart';
import 'package:fluffychat/widgets/profile_bottom_sheet.dart'; import 'package:fluffychat/widgets/profile_bottom_sheet.dart';
import 'package:fluffychat/widgets/public_room_bottom_sheet.dart'; import 'package:fluffychat/widgets/public_room_bottom_sheet.dart';
import '../../utils/stream_extension.dart';
import '../../widgets/connection_status_header.dart'; import '../../widgets/connection_status_header.dart';
import '../../widgets/matrix.dart'; import '../../widgets/matrix.dart';
@ -42,248 +41,237 @@ class ChatListViewBody extends StatelessWidget {
child: child, child: child,
); );
}, },
child: StreamBuilder( child: Builder(builder: (context) {
key: ValueKey(client.userID.toString() + if (controller.activeFilter == ActiveFilter.spaces &&
controller.activeFilter.toString() + !controller.isSearchMode) {
controller.activeSpaceId.toString()), return SpaceView(
stream: client.onSync.stream controller,
.where((s) => s.hasRoomUpdate) scrollController: controller.scrollController,
.rateLimit(const Duration(seconds: 1)), key: Key(controller.activeSpaceId ?? 'Spaces'),
builder: (context, _) { );
if (controller.activeFilter == ActiveFilter.spaces && }
!controller.isSearchMode) { if (controller.waitForFirstSync && client.prevBatch != null) {
return SpaceView( final rooms = controller.filteredRooms;
controller, final displayStoriesHeader = {
scrollController: controller.scrollController, ActiveFilter.allChats,
key: Key(controller.activeSpaceId ?? 'Spaces'), ActiveFilter.messages,
); }.contains(controller.activeFilter);
} return ListView.builder(
if (controller.waitForFirstSync && client.prevBatch != null) { controller: controller.scrollController,
final rooms = controller.filteredRooms; // add +1 space below in order to properly scroll below the spaces bar
final displayStoriesHeader = { itemCount: rooms.length + 1,
ActiveFilter.allChats, itemBuilder: (BuildContext context, int i) {
ActiveFilter.messages, if (i == 0) {
}.contains(controller.activeFilter); return Column(
return ListView.builder( mainAxisSize: MainAxisSize.min,
controller: controller.scrollController, children: [
// add +1 space below in order to properly scroll below the spaces bar if (roomSearchResult != null) ...[
itemCount: rooms.length + 1, SearchTitle(
itemBuilder: (BuildContext context, int i) { title: L10n.of(context)!.publicRooms,
if (i == 0) { icon: const Icon(Icons.explore_outlined),
return Column( ),
mainAxisSize: MainAxisSize.min, AnimatedContainer(
children: [ height: roomSearchResult.chunk.isEmpty ? 0 : 106,
if (roomSearchResult != null) ...[ duration: const Duration(milliseconds: 250),
SearchTitle( clipBehavior: Clip.hardEdge,
title: L10n.of(context)!.publicRooms, decoration: const BoxDecoration(),
icon: const Icon(Icons.explore_outlined), child: ListView.builder(
), scrollDirection: Axis.horizontal,
AnimatedContainer( itemCount: roomSearchResult.chunk.length,
height: roomSearchResult.chunk.isEmpty ? 0 : 106, itemBuilder: (context, i) => _SearchItem(
duration: const Duration(milliseconds: 250), title: roomSearchResult.chunk[i].name ??
clipBehavior: Clip.hardEdge, roomSearchResult
decoration: const BoxDecoration(), .chunk[i].canonicalAlias?.localpart ??
child: ListView.builder( L10n.of(context)!.group,
scrollDirection: Axis.horizontal, avatar: roomSearchResult.chunk[i].avatarUrl,
itemCount: roomSearchResult.chunk.length, onPressed: () => showModalBottomSheet(
itemBuilder: (context, i) => _SearchItem( context: context,
title: roomSearchResult.chunk[i].name ?? builder: (c) => PublicRoomBottomSheet(
roomSearchResult roomAlias:
.chunk[i].canonicalAlias?.localpart ?? roomSearchResult.chunk[i].canonicalAlias ??
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, roomSearchResult.chunk[i].roomId,
outerContext: context, outerContext: context,
chunk: roomSearchResult.chunk[i], 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,
),
),
),
),
),
],
if (controller.isSearchMode)
SearchTitle(
title: L10n.of(context)!.stories,
icon: const Icon(Icons.camera_alt_outlined),
),
if (displayStoriesHeader)
StoriesHeader(
key: const Key('stories_header'),
filter: controller.searchController.text,
),
const ConnectionStatusHeader(),
AnimatedContainer(
height: controller.isTorBrowser ? 64 : 0,
duration: const Duration(milliseconds: 300),
clipBehavior: Clip.hardEdge,
curve: Curves.bounceInOut,
decoration: const BoxDecoration(),
child: Material(
color: Theme.of(context).colorScheme.surface,
child: ListTile(
leading: const Icon(Icons.vpn_key),
title: Text(L10n.of(context)!.dehydrateTor),
subtitle:
Text(L10n.of(context)!.dehydrateTorLong),
trailing:
const Icon(Icons.chevron_right_outlined),
onTap: controller.dehydrate,
),
),
),
if (controller.isSearchMode)
SearchTitle(
title: L10n.of(context)!.chats,
icon: const Icon(Icons.chat_outlined),
),
if (rooms.isEmpty && !controller.isSearchMode)
Padding(
padding: const EdgeInsets.all(32.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const SizedBox(height: 32),
Image.asset(
'assets/start_chat.png',
),
Divider(
height: 1,
color: Theme.of(context)
.colorScheme
.onBackground,
),
const SizedBox(height: 32),
FloatingActionButton.extended(
backgroundColor:
Theme.of(context).colorScheme.primary,
foregroundColor:
Theme.of(context).colorScheme.onPrimary,
icon: const Icon(Icons.edit_outlined),
onPressed: () =>
VRouter.of(context).to('/newprivatechat'),
label: Text(L10n.of(context)!.startFirstChat),
),
],
),
),
],
);
}
i--;
if (!rooms[i].displayname.toLowerCase().contains(
controller.searchController.text.toLowerCase())) {
return Container();
}
return ChatListItem(
rooms[i],
key: Key('chat_list_item_${rooms[i].id}'),
selected: controller.selectedRoomIds.contains(rooms[i].id),
onTap: controller.selectMode == SelectMode.select
? () => controller.toggleSelection(rooms[i].id)
: null,
onLongPress: () => controller.toggleSelection(rooms[i].id),
activeChat: controller.activeChat == rooms[i].id,
);
},
);
}
const dummyChatCount = 5;
final titleColor =
Theme.of(context).textTheme.bodyText1!.color!.withAlpha(100);
final subtitleColor =
Theme.of(context).textTheme.bodyText1!.color!.withAlpha(50);
return ListView.builder(
key: const Key('dummychats'),
itemCount: dummyChatCount,
itemBuilder: (context, i) => Opacity(
opacity: (dummyChatCount - i) / dummyChatCount,
child: ListTile(
leading: CircleAvatar(
backgroundColor: titleColor,
child: CircularProgressIndicator(
strokeWidth: 1,
color: Theme.of(context).textTheme.bodyText1!.color,
),
),
title: Row(
children: [
Expanded(
child: Container(
height: 14,
decoration: BoxDecoration(
color: titleColor,
borderRadius: BorderRadius.circular(3),
),
),
),
const SizedBox(width: 36),
Container(
height: 14,
width: 14,
decoration: BoxDecoration(
color: subtitleColor,
borderRadius: BorderRadius.circular(14),
),
),
const SizedBox(width: 12),
Container(
height: 14,
width: 14,
decoration: BoxDecoration(
color: subtitleColor,
borderRadius: BorderRadius.circular(14),
), ),
), ),
], ],
), if (userSearchResult != null) ...[
subtitle: Container( SearchTitle(
decoration: BoxDecoration( title: L10n.of(context)!.users,
color: subtitleColor, icon: const Icon(Icons.group_outlined),
borderRadius: BorderRadius.circular(3), ),
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,
),
),
),
),
),
],
if (controller.isSearchMode)
SearchTitle(
title: L10n.of(context)!.stories,
icon: const Icon(Icons.camera_alt_outlined),
),
if (displayStoriesHeader)
StoriesHeader(
key: const Key('stories_header'),
filter: controller.searchController.text,
),
const ConnectionStatusHeader(),
AnimatedContainer(
height: controller.isTorBrowser ? 64 : 0,
duration: const Duration(milliseconds: 300),
clipBehavior: Clip.hardEdge,
curve: Curves.bounceInOut,
decoration: const BoxDecoration(),
child: Material(
color: Theme.of(context).colorScheme.surface,
child: ListTile(
leading: const Icon(Icons.vpn_key),
title: Text(L10n.of(context)!.dehydrateTor),
subtitle: Text(L10n.of(context)!.dehydrateTorLong),
trailing: const Icon(Icons.chevron_right_outlined),
onTap: controller.dehydrate,
),
),
), ),
height: 12, if (controller.isSearchMode)
margin: const EdgeInsets.only(right: 22), SearchTitle(
), title: L10n.of(context)!.chats,
icon: const Icon(Icons.chat_outlined),
),
if (rooms.isEmpty && !controller.isSearchMode)
Padding(
padding: const EdgeInsets.all(32.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const SizedBox(height: 32),
Image.asset(
'assets/start_chat.png',
),
Divider(
height: 1,
color: Theme.of(context).colorScheme.onBackground,
),
const SizedBox(height: 32),
FloatingActionButton.extended(
backgroundColor:
Theme.of(context).colorScheme.primary,
foregroundColor:
Theme.of(context).colorScheme.onPrimary,
icon: const Icon(Icons.edit_outlined),
onPressed: () =>
VRouter.of(context).to('/newprivatechat'),
label: Text(L10n.of(context)!.startFirstChat),
),
],
),
),
],
);
}
i--;
if (!rooms[i]
.displayname
.toLowerCase()
.contains(controller.searchController.text.toLowerCase())) {
return Container();
}
return ChatListItem(
rooms[i],
key: Key('chat_list_item_${rooms[i].id}'),
selected: controller.selectedRoomIds.contains(rooms[i].id),
onTap: controller.selectMode == SelectMode.select
? () => controller.toggleSelection(rooms[i].id)
: null,
onLongPress: () => controller.toggleSelection(rooms[i].id),
activeChat: controller.activeChat == rooms[i].id,
);
},
);
}
const dummyChatCount = 5;
final titleColor =
Theme.of(context).textTheme.bodyText1!.color!.withAlpha(100);
final subtitleColor =
Theme.of(context).textTheme.bodyText1!.color!.withAlpha(50);
return ListView.builder(
key: const Key('dummychats'),
itemCount: dummyChatCount,
itemBuilder: (context, i) => Opacity(
opacity: (dummyChatCount - i) / dummyChatCount,
child: ListTile(
leading: CircleAvatar(
backgroundColor: titleColor,
child: CircularProgressIndicator(
strokeWidth: 1,
color: Theme.of(context).textTheme.bodyText1!.color,
), ),
), ),
); title: Row(
}), children: [
Expanded(
child: Container(
height: 14,
decoration: BoxDecoration(
color: titleColor,
borderRadius: BorderRadius.circular(3),
),
),
),
const SizedBox(width: 36),
Container(
height: 14,
width: 14,
decoration: BoxDecoration(
color: subtitleColor,
borderRadius: BorderRadius.circular(14),
),
),
const SizedBox(width: 12),
Container(
height: 14,
width: 14,
decoration: BoxDecoration(
color: subtitleColor,
borderRadius: BorderRadius.circular(14),
),
),
],
),
subtitle: Container(
decoration: BoxDecoration(
color: subtitleColor,
borderRadius: BorderRadius.circular(3),
),
height: 12,
margin: const EdgeInsets.only(right: 22),
),
),
),
);
}),
); );
} }
} }

View file

@ -4,11 +4,13 @@ import 'package:flutter/services.dart';
import 'package:badges/badges.dart'; import 'package:badges/badges.dart';
import 'package:flutter_gen/gen_l10n/l10n.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart';
import 'package:keyboard_shortcuts/keyboard_shortcuts.dart'; import 'package:keyboard_shortcuts/keyboard_shortcuts.dart';
import 'package:matrix/matrix.dart';
import 'package:vrouter/vrouter.dart'; import 'package:vrouter/vrouter.dart';
import 'package:fluffychat/config/app_config.dart'; import 'package:fluffychat/config/app_config.dart';
import 'package:fluffychat/config/themes.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/utils/stream_extension.dart';
import 'package:fluffychat/widgets/avatar.dart'; 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';
@ -103,155 +105,177 @@ class ChatListView extends StatelessWidget {
return; return;
} }
}, },
child: Row( child: StreamBuilder<Object>(
children: [ key: ValueKey(client.userID.toString() +
if (FluffyThemes.isColumnMode(context) && controller.activeFilter.toString() +
FluffyThemes.getDisplayNavigationRail(context)) ...[ controller.activeSpaceId.toString()),
Builder(builder: (context) { stream: client.onSync.stream
final allSpaces = client.rooms.where((room) => room.isSpace); .where((s) => s.hasRoomUpdate)
final rootSpaces = allSpaces .rateLimit(const Duration(seconds: 1)),
.where( builder: (context, snapshot) {
(space) => !allSpaces.any( return Row(
(parentSpace) => parentSpace.spaceChildren children: [
.any((child) => child.roomId == space.id), if (FluffyThemes.isColumnMode(context) &&
), FluffyThemes.getDisplayNavigationRail(context)) ...[
) Builder(builder: (context) {
.toList(); final allSpaces =
final destinations = getNavigationDestinations(context); client.rooms.where((room) => room.isSpace);
final rootSpaces = allSpaces
.where(
(space) => !allSpaces.any(
(parentSpace) => parentSpace.spaceChildren
.any((child) => child.roomId == space.id),
),
)
.toList();
final destinations = getNavigationDestinations(context);
return SizedBox( return SizedBox(
width: 64,
child: ListView.builder(
scrollDirection: Axis.vertical,
itemCount: rootSpaces.length + destinations.length,
itemBuilder: (context, i) {
if (i < destinations.length) {
final isSelected = i == controller.selectedIndex;
return Container(
height: 64,
width: 64,
decoration: BoxDecoration(
border: Border(
bottom: i == (destinations.length - 1)
? BorderSide(
width: 1,
color: Theme.of(context).dividerColor,
)
: BorderSide.none,
left: BorderSide(
color: isSelected
? Theme.of(context).colorScheme.primary
: Colors.transparent,
width: 4,
),
right: const BorderSide(
color: Colors.transparent,
width: 4,
),
),
),
alignment: Alignment.center,
child: IconButton(
color: isSelected
? Theme.of(context).colorScheme.secondary
: null,
icon: CircleAvatar(
backgroundColor: isSelected
? Theme.of(context).colorScheme.secondary
: Theme.of(context)
.colorScheme
.background,
foregroundColor: isSelected
? Theme.of(context)
.colorScheme
.onSecondary
: Theme.of(context)
.colorScheme
.onBackground,
child: i == controller.selectedIndex
? destinations[i].selectedIcon ??
destinations[i].icon
: destinations[i].icon),
tooltip: destinations[i].label,
onPressed: () =>
controller.onDestinationSelected(i),
),
);
}
i -= destinations.length;
final isSelected =
controller.activeFilter == ActiveFilter.spaces &&
rootSpaces[i].id == controller.activeSpaceId;
return Container(
height: 64,
width: 64, width: 64,
decoration: BoxDecoration( child: ListView.builder(
border: Border( scrollDirection: Axis.vertical,
left: BorderSide( itemCount: rootSpaces.length + destinations.length,
color: isSelected itemBuilder: (context, i) {
? Theme.of(context).colorScheme.secondary if (i < destinations.length) {
: Colors.transparent, final isSelected =
width: 4, i == controller.selectedIndex;
), return Container(
right: const BorderSide( height: 64,
color: Colors.transparent, width: 64,
width: 4, decoration: BoxDecoration(
), border: Border(
), bottom: i == (destinations.length - 1)
), ? BorderSide(
alignment: Alignment.center, width: 1,
child: IconButton( color: Theme.of(context)
tooltip: rootSpaces[i].displayname, .dividerColor,
icon: Avatar( )
mxContent: rootSpaces[i].avatar, : BorderSide.none,
name: rootSpaces[i].displayname, left: BorderSide(
size: 32, color: isSelected
fontSize: 12, ? Theme.of(context)
), .colorScheme
onPressed: () => .primary
controller.setActiveSpace(rootSpaces[i].id), : Colors.transparent,
width: 4,
),
right: const BorderSide(
color: Colors.transparent,
width: 4,
),
),
),
alignment: Alignment.center,
child: IconButton(
color: isSelected
? Theme.of(context)
.colorScheme
.secondary
: null,
icon: CircleAvatar(
backgroundColor: isSelected
? Theme.of(context)
.colorScheme
.secondary
: Theme.of(context)
.colorScheme
.background,
foregroundColor: isSelected
? Theme.of(context)
.colorScheme
.onSecondary
: Theme.of(context)
.colorScheme
.onBackground,
child: i == controller.selectedIndex
? destinations[i].selectedIcon ??
destinations[i].icon
: destinations[i].icon),
tooltip: destinations[i].label,
onPressed: () =>
controller.onDestinationSelected(i),
),
);
}
i -= destinations.length;
final isSelected = controller.activeFilter ==
ActiveFilter.spaces &&
rootSpaces[i].id == controller.activeSpaceId;
return Container(
height: 64,
width: 64,
decoration: BoxDecoration(
border: Border(
left: BorderSide(
color: isSelected
? Theme.of(context)
.colorScheme
.secondary
: Colors.transparent,
width: 4,
),
right: const BorderSide(
color: Colors.transparent,
width: 4,
),
),
),
alignment: Alignment.center,
child: IconButton(
tooltip: rootSpaces[i].displayname,
icon: Avatar(
mxContent: rootSpaces[i].avatar,
name: rootSpaces[i].displayname,
size: 32,
fontSize: 12,
),
onPressed: () => controller
.setActiveSpace(rootSpaces[i].id),
),
);
},
), ),
); );
}, }),
Container(
color: Theme.of(context).dividerColor,
width: 1,
),
],
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,
),
), ),
); ],
}), );
Container( }),
color: Theme.of(context).dividerColor,
width: 1,
),
],
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,
),
),
],
),
); );
}, },
); );

View file

@ -17,144 +17,150 @@ class HomeserverPickerView extends StatelessWidget {
Widget build(BuildContext context) { Widget build(BuildContext context) {
final benchmarkResults = controller.benchmarkResults; final benchmarkResults = controller.benchmarkResults;
return LoginScaffold( return LoginScaffold(
body: Column( body: SafeArea(
children: [ child: Column(
HomeserverAppBar(controller: controller), children: [
// display a prominent banner to import session for TOR browser Padding(
// users. This feature is just some UX sugar as TOR users are padding: const EdgeInsets.all(12.0),
// usually forced to logout as TOR browser is non-persistent child: HomeserverAppBar(controller: controller),
AnimatedContainer( ),
height: controller.isTorBrowser ? 64 : 0, // display a prominent banner to import session for TOR browser
duration: const Duration(milliseconds: 300), // users. This feature is just some UX sugar as TOR users are
clipBehavior: Clip.hardEdge, // usually forced to logout as TOR browser is non-persistent
curve: Curves.bounceInOut, AnimatedContainer(
decoration: const BoxDecoration(), height: controller.isTorBrowser ? 64 : 0,
child: Material( duration: const Duration(milliseconds: 300),
clipBehavior: Clip.hardEdge, clipBehavior: Clip.hardEdge,
borderRadius: curve: Curves.bounceInOut,
const BorderRadius.vertical(bottom: Radius.circular(8)), decoration: const BoxDecoration(),
color: Theme.of(context).colorScheme.surface, child: Material(
child: ListTile( clipBehavior: Clip.hardEdge,
leading: const Icon(Icons.vpn_key), borderRadius:
title: Text(L10n.of(context)!.hydrateTor), const BorderRadius.vertical(bottom: Radius.circular(8)),
subtitle: Text(L10n.of(context)!.hydrateTorLong), color: Theme.of(context).colorScheme.surface,
trailing: const Icon(Icons.chevron_right_outlined), child: ListTile(
onTap: controller.restoreBackup, leading: const Icon(Icons.vpn_key),
title: Text(L10n.of(context)!.hydrateTor),
subtitle: Text(L10n.of(context)!.hydrateTorLong),
trailing: const Icon(Icons.chevron_right_outlined),
onTap: controller.restoreBackup,
),
), ),
), ),
), Expanded(
Expanded( child: ListView(
child: ListView(
children: [
if (controller.displayServerList)
Padding(
padding: const EdgeInsets.all(12.0),
child: Material(
borderRadius:
BorderRadius.circular(AppConfig.borderRadius),
color: Theme.of(context).colorScheme.onInverseSurface,
clipBehavior: Clip.hardEdge,
child: benchmarkResults == null
? const Center(
child: Padding(
padding: EdgeInsets.all(12.0),
child: CircularProgressIndicator.adaptive(),
))
: Column(
children: controller.filteredHomeservers
.map(
(server) => ListTile(
trailing: IconButton(
icon: const Icon(
Icons.info_outlined,
color: Colors.black,
),
onPressed: () =>
controller.showServerInfo(server),
),
onTap: () => controller.setServer(
server.homeserver.baseUrl.host),
title: Text(
server.homeserver.baseUrl.host,
style: const TextStyle(
color: Colors.black),
),
subtitle: Text(
server.homeserver.description ?? '',
style: TextStyle(
color: Colors.grey.shade700),
),
),
)
.toList(),
),
),
)
else ...[
Container(
alignment: Alignment.center,
height: 200,
child: Image.asset(
'assets/info-logo.png',
filterQuality: FilterQuality.medium,
),
),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16.0),
child: Center(
child: Text(
AppConfig.applicationWelcomeMessage ??
L10n.of(context)!.welcomeText,
textAlign: TextAlign.center,
style: const TextStyle(fontSize: 20),
),
),
),
],
],
),
),
SafeArea(
child: Container(
padding: const EdgeInsets.all(12),
width: double.infinity,
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [ children: [
TextButton( if (controller.displayServerList)
style: TextButton.styleFrom( Padding(
foregroundColor: padding: const EdgeInsets.all(12.0),
Theme.of(context).colorScheme.onSurfaceVariant, child: Material(
), borderRadius:
onPressed: controller.restoreBackup, BorderRadius.circular(AppConfig.borderRadius),
child: Text(L10n.of(context)!.hydrate), color: Theme.of(context).colorScheme.onInverseSurface,
), clipBehavior: Clip.hardEdge,
TextButton( child: benchmarkResults == null
onPressed: () => launch(AppConfig.privacyUrl), ? const Center(
child: Text(L10n.of(context)!.privacy), child: Padding(
), padding: EdgeInsets.all(12.0),
Hero( child: CircularProgressIndicator.adaptive(),
tag: 'loginButton', ))
child: ElevatedButton( : Column(
style: ElevatedButton.styleFrom( children: controller.filteredHomeservers
backgroundColor: Theme.of(context).colorScheme.primary, .map(
foregroundColor: (server) => ListTile(
Theme.of(context).colorScheme.onPrimary, trailing: IconButton(
icon: const Icon(
Icons.info_outlined,
color: Colors.black,
),
onPressed: () =>
controller.showServerInfo(server),
),
onTap: () => controller.setServer(
server.homeserver.baseUrl.host),
title: Text(
server.homeserver.baseUrl.host,
style: const TextStyle(
color: Colors.black),
),
subtitle: Text(
server.homeserver.description ?? '',
style: TextStyle(
color: Colors.grey.shade700),
),
),
)
.toList(),
),
),
)
else ...[
Container(
alignment: Alignment.center,
height: 200,
child: Image.asset(
'assets/info-logo.png',
filterQuality: FilterQuality.medium,
), ),
onPressed: controller.isLoading
? null
: controller.checkHomeserverAction,
child: controller.isLoading
? const LinearProgressIndicator()
: Text(L10n.of(context)!.connect),
), ),
), Padding(
padding: const EdgeInsets.symmetric(horizontal: 16.0),
child: Center(
child: Text(
AppConfig.applicationWelcomeMessage ??
L10n.of(context)!.welcomeText,
textAlign: TextAlign.center,
style: const TextStyle(fontSize: 20),
),
),
),
],
], ],
), ),
), ),
), SafeArea(
], child: Container(
padding: const EdgeInsets.all(12),
width: double.infinity,
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
TextButton(
style: TextButton.styleFrom(
foregroundColor:
Theme.of(context).colorScheme.onSurfaceVariant,
),
onPressed: controller.restoreBackup,
child: Text(L10n.of(context)!.hydrate),
),
TextButton(
onPressed: () => launch(AppConfig.privacyUrl),
child: Text(L10n.of(context)!.privacy),
),
Hero(
tag: 'loginButton',
child: ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor:
Theme.of(context).colorScheme.primary,
foregroundColor:
Theme.of(context).colorScheme.onPrimary,
),
onPressed: controller.isLoading
? null
: controller.checkHomeserverAction,
child: controller.isLoading
? const LinearProgressIndicator()
: Text(L10n.of(context)!.connect),
),
),
],
),
),
),
],
),
), ),
); );
} }