feat: Allow loading of multiple clients in main.dart

This commit is contained in:
Krille Fear 2021-09-19 11:48:23 +00:00
commit ec68d15586
26 changed files with 840 additions and 159 deletions

View file

@ -1,4 +1,9 @@
import 'dart:math';
import 'package:async/async.dart';
import 'package:fluffychat/config/themes.dart';
import 'package:fluffychat/widgets/avatar.dart';
import 'package:flutter/widgets.dart';
import 'package:matrix/matrix.dart';
import 'package:fluffychat/pages/chat_list.dart';
import 'package:fluffychat/widgets/connection_status_header.dart';
@ -9,6 +14,7 @@ import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:vrouter/vrouter.dart';
import '../../widgets/matrix.dart';
import '../../utils/account_bundles.dart';
import 'package:flutter_gen/gen_l10n/l10n.dart';
import '../../utils/stream_extension.dart';
@ -17,6 +23,59 @@ class ChatListView extends StatelessWidget {
const ChatListView(this.controller, {Key key}) : super(key: key);
List<BottomNavigationBarItem> getBottomBarItems(BuildContext context) {
final displayClients = Matrix.of(context).currentBundle;
if (displayClients.isEmpty) {
displayClients.addAll(Matrix.of(context).widget.clients);
controller.resetActiveBundle();
}
final items = displayClients.map((client) {
return BottomNavigationBarItem(
label: client.userID,
icon: FutureBuilder<Profile>(
future: client.ownProfile,
builder: (context, snapshot) {
return InkWell(
borderRadius: BorderRadius.circular(32),
onTap: () => controller.setActiveClient(client),
onLongPress: () =>
controller.editBundlesForAccount(client.userID),
child: Avatar(
snapshot.data?.avatarUrl,
snapshot.data?.displayName ?? client.userID.localpart,
size: 32,
),
);
}),
);
}).toList();
if (controller.displayBundles && false) {
items.insert(
0,
BottomNavigationBarItem(
label: 'Bundles',
icon: PopupMenuButton(
icon: Icon(
Icons.menu,
color: Theme.of(context).textTheme.bodyText1.color,
),
onSelected: controller.setActiveBundle,
itemBuilder: (context) => Matrix.of(context)
.accountBundles
.keys
.map(
(bundle) => PopupMenuItem(
value: bundle,
child: Text(bundle),
),
)
.toList(),
)));
}
return items;
}
@override
Widget build(BuildContext context) {
return StreamBuilder<Object>(
@ -216,12 +275,98 @@ class ChatListView extends StatelessWidget {
child: Icon(CupertinoIcons.chat_bubble),
)
: null,
bottomNavigationBar: Matrix.of(context).isMultiAccount
? StreamBuilder(
stream: StreamGroup.merge(Matrix.of(context)
.widget
.clients
.map((client) => client.onSync.stream.where((s) =>
s.accountData != null &&
s.accountData
.any((e) => e.type == accountBundlesType)))),
builder: (context, _) => Material(
color: Theme.of(context)
.bottomNavigationBarTheme
.backgroundColor,
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Divider(height: 1),
Builder(builder: (context) {
final items = getBottomBarItems(context);
if (items.length == 1) {
return Padding(
padding: const EdgeInsets.all(7.0),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
items.single.icon,
Text(items.single.label),
],
),
);
}
return SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: SizedBox(
width: max(
FluffyThemes.isColumnMode(context)
? FluffyThemes.columnWidth
: MediaQuery.of(context).size.width,
Matrix.of(context).widget.clients.length *
84.0,
),
child: BottomNavigationBar(
elevation: 0,
onTap: (i) => controller.setActiveClient(
Matrix.of(context).currentBundle[i]),
currentIndex: Matrix.of(context)
.currentBundle
.indexWhere(
(client) =>
client ==
Matrix.of(context).client,
),
showUnselectedLabels: false,
showSelectedLabels: true,
type: BottomNavigationBarType.shifting,
selectedItemColor:
Theme.of(context).primaryColor,
items: items,
),
),
);
}),
if (controller.displayBundles)
Padding(
padding: const EdgeInsets.symmetric(
vertical: 4.0,
horizontal: 12,
),
child: SizedBox(
width: double.infinity,
child: CupertinoSlidingSegmentedControl(
groupValue: controller.secureActiveBundle,
onValueChanged: controller.setActiveBundle,
children: Map.fromEntries(Matrix.of(context)
.accountBundles
.keys
.map((bundle) =>
MapEntry(bundle, Text(bundle)))),
),
),
),
],
),
),
)
: null,
drawer: controller.spaces.isEmpty
? null
: Drawer(
child: SafeArea(
child: ListView.builder(
itemCount: controller.spaces.length + 1,
itemCount: controller.spaces.length,
itemBuilder: (context, i) {
if (i == 0) {
return ListTile(

View file

@ -37,9 +37,10 @@ class ChatView extends StatelessWidget {
@override
Widget build(BuildContext context) {
controller.matrix = Matrix.of(context);
controller.matrix ??= Matrix.of(context);
final client = controller.matrix.client;
controller.room ??= client.getRoomById(controller.roomId);
controller.sendingClient ??= client;
controller.room = controller.sendingClient.getRoomById(controller.roomId);
if (controller.room == null) {
return Scaffold(
appBar: AppBar(
@ -147,10 +148,7 @@ class ChatView extends StatelessWidget {
: Text(controller.selectedEvents.length.toString()),
actions: controller.selectMode
? <Widget>[
if (controller.selectedEvents.length == 1 &&
controller.selectedEvents.first.status > 0 &&
controller.selectedEvents.first.senderId ==
client.userID)
if (controller.canEditSelectedEvents)
IconButton(
icon: Icon(Icons.edit_outlined),
tooltip: L10n.of(context).edit,
@ -680,6 +678,14 @@ class ChatView extends StatelessWidget {
alignment: Alignment.center,
child: EncryptionButton(controller.room),
),
if (controller.matrix.isMultiAccount &&
controller.matrix.currentBundle.length >
1)
Container(
height: 56,
alignment: Alignment.center,
child: _ChatAccountPicker(controller),
),
Expanded(
child: Padding(
padding: const EdgeInsets.symmetric(
@ -792,3 +798,58 @@ class _EditContent extends StatelessWidget {
);
}
}
class _ChatAccountPicker extends StatelessWidget {
final ChatController controller;
const _ChatAccountPicker(this.controller, {Key key}) : super(key: key);
void _popupMenuButtonSelected(String mxid) {
final client = controller.matrix.currentBundle
.firstWhere((cl) => cl.userID == mxid, orElse: () => null);
if (client == null) {
Logs().w('Attempted to switch to a non-existing client $mxid');
return;
}
controller.setSendingClient(client);
}
@override
Widget build(BuildContext context) {
controller.matrix ??= Matrix.of(context);
final clients = controller.currentRoomBundle;
return Padding(
padding: const EdgeInsets.all(8.0),
child: FutureBuilder<Profile>(
future: controller.sendingClient.ownProfile,
builder: (context, snapshot) => PopupMenuButton<String>(
onSelected: _popupMenuButtonSelected,
itemBuilder: (BuildContext context) => clients
.map((client) => PopupMenuItem<String>(
value: client.userID,
child: FutureBuilder<Profile>(
future: client.ownProfile,
builder: (context, snapshot) => ListTile(
leading: Avatar(
snapshot.data?.avatarUrl,
snapshot.data?.displayName ?? client.userID.localpart,
size: 20,
),
title:
Text(snapshot.data?.displayName ?? client.userID),
contentPadding: EdgeInsets.all(0),
),
),
))
.toList(),
child: Avatar(
snapshot.data?.avatarUrl,
snapshot.data?.displayName ??
controller.matrix.client.userID.localpart,
size: 20,
),
),
),
);
}
}

View file

@ -100,7 +100,8 @@ class HomeserverPickerView extends StatelessWidget {
imageUrl: Uri.parse(
identityProvider.icon)
.getDownloadLink(
Matrix.of(context).client)
Matrix.of(context)
.getLoginClient())
.toString(),
width: 24,
height: 24,
@ -128,7 +129,7 @@ class HomeserverPickerView extends StatelessWidget {
Expanded(
child: _LoginButton(
onPressed: () =>
VRouter.of(context).to('/login'),
VRouter.of(context).to('login'),
icon: Icon(Icons.login_outlined),
labelText: L10n.of(context).login,
),

View file

@ -19,7 +19,7 @@ class LoginView extends StatelessWidget {
elevation: 0,
title: Text(
L10n.of(context).logInTo(Matrix.of(context)
.client
.getLoginClient()
.homeserver
.toString()
.replaceFirst('https://', '')),

View file

@ -20,6 +20,13 @@ class SettingsAccountView extends StatelessWidget {
withScrolling: true,
child: Column(
children: [
ListTile(
trailing: Icon(Icons.add_box_outlined),
title: Text(L10n.of(context).addAccount),
subtitle: Text(L10n.of(context).enableMultiAccounts),
onTap: controller.addAccountAction,
),
Divider(height: 1),
ListTile(
trailing: Icon(Icons.edit_outlined),
title: Text(L10n.of(context).editDisplayname),
@ -38,6 +45,7 @@ class SettingsAccountView extends StatelessWidget {
title: Text(L10n.of(context).devices),
onTap: () => VRouter.of(context).to('devices'),
),
Divider(height: 1),
ListTile(
trailing: Icon(Icons.exit_to_app_outlined),
title: Text(L10n.of(context).logout),

View file

@ -38,7 +38,7 @@ class SignupPageView extends StatelessWidget {
labelText: L10n.of(context).username,
prefixText: '@',
suffixText:
':${Matrix.of(context).client.homeserver.host}'),
':${Matrix.of(context).getLoginClient().homeserver.host}'),
),
),
Divider(),