feat: Allow loading of multiple clients in main.dart
This commit is contained in:
parent
fe4dd064d1
commit
ec68d15586
26 changed files with 840 additions and 159 deletions
|
|
@ -32,6 +32,7 @@ import 'send_location_dialog.dart';
|
|||
import 'sticker_picker_dialog.dart';
|
||||
import '../utils/matrix_sdk_extensions.dart/filtered_timeline_extension.dart';
|
||||
import '../utils/matrix_sdk_extensions.dart/matrix_file_extension.dart';
|
||||
import '../utils/account_bundles.dart';
|
||||
|
||||
class Chat extends StatefulWidget {
|
||||
final Widget sideView;
|
||||
|
|
@ -45,6 +46,8 @@ class Chat extends StatefulWidget {
|
|||
class ChatController extends State<Chat> {
|
||||
Room room;
|
||||
|
||||
Client sendingClient;
|
||||
|
||||
Timeline timeline;
|
||||
|
||||
MatrixState matrix;
|
||||
|
|
@ -222,6 +225,14 @@ class ChatController extends State<Chat> {
|
|||
|
||||
TextEditingController sendController = TextEditingController();
|
||||
|
||||
void setSendingClient(Client c) => setState(() {
|
||||
sendingClient = c;
|
||||
});
|
||||
|
||||
void setActiveClient(Client c) => setState(() {
|
||||
Matrix.of(context).setActiveClient(c);
|
||||
});
|
||||
|
||||
Future<void> send() async {
|
||||
if (sendController.text.trim().isEmpty) return;
|
||||
var parseCommands = true;
|
||||
|
|
@ -447,19 +458,51 @@ class ChatController extends State<Chat> {
|
|||
for (final event in selectedEvents) {
|
||||
await showFutureLoadingDialog(
|
||||
context: context,
|
||||
future: () =>
|
||||
event.status > 0 ? event.redactEvent() : event.remove());
|
||||
future: () async {
|
||||
if (event.status > 0) {
|
||||
if (event.canRedact) {
|
||||
await event.redactEvent();
|
||||
} else {
|
||||
final client = currentRoomBundle.firstWhere(
|
||||
(cl) => selectedEvents.first.senderId == cl.userID,
|
||||
orElse: () => null);
|
||||
if (client == null) {
|
||||
return;
|
||||
}
|
||||
final room = client.getRoomById(roomId);
|
||||
await Event.fromJson(event.toJson(), room).redactEvent();
|
||||
}
|
||||
} else {
|
||||
await event.remove();
|
||||
}
|
||||
});
|
||||
}
|
||||
setState(() => selectedEvents.clear());
|
||||
}
|
||||
|
||||
List<Client> get currentRoomBundle {
|
||||
final clients = matrix.currentBundle;
|
||||
clients.removeWhere((c) => c.getRoomById(roomId) == null);
|
||||
return clients;
|
||||
}
|
||||
|
||||
bool get canRedactSelectedEvents {
|
||||
final clients = matrix.currentBundle;
|
||||
for (final event in selectedEvents) {
|
||||
if (event.canRedact == false) return false;
|
||||
if (event.canRedact == false &&
|
||||
!(clients.any((cl) => event.senderId == cl.userID))) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool get canEditSelectedEvents {
|
||||
if (selectedEvents.length != 1 || selectedEvents.first.status < 1) {
|
||||
return false;
|
||||
}
|
||||
return currentRoomBundle
|
||||
.any((cl) => selectedEvents.first.senderId == cl.userID);
|
||||
}
|
||||
|
||||
void forwardEventsAction() async {
|
||||
if (selectedEvents.length == 1) {
|
||||
Matrix.of(context).shareContent = selectedEvents.first.content;
|
||||
|
|
@ -584,6 +627,13 @@ class ChatController extends State<Chat> {
|
|||
});
|
||||
|
||||
void editSelectedEventAction() {
|
||||
final client = currentRoomBundle.firstWhere(
|
||||
(cl) => selectedEvents.first.senderId == cl.userID,
|
||||
orElse: () => null);
|
||||
if (client == null) {
|
||||
return;
|
||||
}
|
||||
setSendingClient(client);
|
||||
setState(() {
|
||||
pendingText = sendController.text;
|
||||
editEvent = selectedEvents.first;
|
||||
|
|
@ -689,6 +739,19 @@ class ChatController extends State<Chat> {
|
|||
}
|
||||
|
||||
void onInputBarChanged(String text) {
|
||||
final clients = currentRoomBundle;
|
||||
for (final client in clients) {
|
||||
final prefix = client.sendPrefix;
|
||||
if ((prefix?.isNotEmpty ?? false) &&
|
||||
text.toLowerCase() == '${prefix.toLowerCase()} ') {
|
||||
setSendingClient(client);
|
||||
setState(() {
|
||||
inputText = '';
|
||||
sendController.text = '';
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
typingCoolDown?.cancel();
|
||||
typingCoolDown = Timer(Duration(seconds: 2), () {
|
||||
typingCoolDown = null;
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ import 'package:uni_links/uni_links.dart';
|
|||
import 'package:vrouter/vrouter.dart';
|
||||
import '../main.dart';
|
||||
import '../widgets/matrix.dart';
|
||||
import '../../utils/account_bundles.dart';
|
||||
import '../utils/matrix_sdk_extensions.dart/matrix_file_extension.dart';
|
||||
import '../utils/url_launcher.dart';
|
||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
||||
|
|
@ -409,6 +410,93 @@ class ChatListController extends State<ChatList> {
|
|||
}
|
||||
}
|
||||
|
||||
void setActiveClient(Client client) {
|
||||
VRouter.of(context).to('/rooms');
|
||||
setState(() {
|
||||
_activeSpaceId = null;
|
||||
selectedRoomIds.clear();
|
||||
Matrix.of(context).setActiveClient(client);
|
||||
});
|
||||
}
|
||||
|
||||
void setActiveBundle(String bundle) => setState(() {
|
||||
_activeSpaceId = null;
|
||||
selectedRoomIds.clear();
|
||||
Matrix.of(context).activeBundle = bundle;
|
||||
if (!Matrix.of(context)
|
||||
.currentBundle
|
||||
.any((client) => client == Matrix.of(context).client)) {
|
||||
Matrix.of(context)
|
||||
.setActiveClient(Matrix.of(context).currentBundle.first);
|
||||
}
|
||||
});
|
||||
|
||||
void editBundlesForAccount(String userId) async {
|
||||
final client = Matrix.of(context)
|
||||
.widget
|
||||
.clients[Matrix.of(context).getClientIndexByMatrixId(userId)];
|
||||
final action = await showConfirmationDialog<EditBundleAction>(
|
||||
context: context,
|
||||
title: L10n.of(context).editBundlesForAccount,
|
||||
actions: [
|
||||
AlertDialogAction(
|
||||
key: EditBundleAction.addToBundle,
|
||||
label: L10n.of(context).addToBundle,
|
||||
),
|
||||
if (Matrix.of(context).activeBundle != null)
|
||||
AlertDialogAction(
|
||||
key: EditBundleAction.removeFromBundle,
|
||||
label: L10n.of(context).removeFromBundle,
|
||||
),
|
||||
],
|
||||
);
|
||||
if (action == null) return;
|
||||
switch (action) {
|
||||
case EditBundleAction.addToBundle:
|
||||
final bundle = await showTextInputDialog(
|
||||
context: context,
|
||||
title: L10n.of(context).bundleName,
|
||||
textFields: [
|
||||
DialogTextField(hintText: L10n.of(context).bundleName)
|
||||
]);
|
||||
if (bundle.isEmpty && bundle.single.isEmpty) return;
|
||||
await showFutureLoadingDialog(
|
||||
context: context,
|
||||
future: () => client.setAccountBundle(bundle.single),
|
||||
);
|
||||
break;
|
||||
case EditBundleAction.removeFromBundle:
|
||||
await showFutureLoadingDialog(
|
||||
context: context,
|
||||
future: () =>
|
||||
client.removeFromAccountBundle(Matrix.of(context).activeBundle),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
bool get displayBundles =>
|
||||
Matrix.of(context).hasComplexBundles &&
|
||||
Matrix.of(context).accountBundles.keys.length > 1;
|
||||
|
||||
String get secureActiveBundle {
|
||||
if (Matrix.of(context).activeBundle == null ||
|
||||
!Matrix.of(context)
|
||||
.accountBundles
|
||||
.keys
|
||||
.contains(Matrix.of(context).activeBundle)) {
|
||||
return Matrix.of(context).accountBundles.keys.first;
|
||||
}
|
||||
return Matrix.of(context).activeBundle;
|
||||
}
|
||||
|
||||
void resetActiveBundle() {
|
||||
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
|
||||
setState(() {
|
||||
Matrix.of(context).activeBundle = null;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
Matrix.of(context).navigatorContext = context;
|
||||
|
|
@ -424,3 +512,5 @@ class ChatListController extends State<ChatList> {
|
|||
return ChatListView(this);
|
||||
}
|
||||
}
|
||||
|
||||
enum EditBundleAction { addToBundle, removeFromBundle }
|
||||
|
|
|
|||
|
|
@ -47,13 +47,13 @@ class HomeserverPickerController extends State<HomeserverPicker> {
|
|||
showFutureLoadingDialog(
|
||||
context: context,
|
||||
future: () async {
|
||||
if (Matrix.of(context).client.homeserver == null) {
|
||||
await Matrix.of(context).client.checkHomeserver(
|
||||
if (Matrix.of(context).getLoginClient().homeserver == null) {
|
||||
await Matrix.of(context).getLoginClient().checkHomeserver(
|
||||
await Store()
|
||||
.getItem(HomeserverPickerController.ssoHomeserverKey),
|
||||
);
|
||||
}
|
||||
await Matrix.of(context).client.login(
|
||||
await Matrix.of(context).getLoginClient().login(
|
||||
LoginType.mLoginToken,
|
||||
token: token,
|
||||
initialDeviceDisplayName: PlatformInfos.clientName,
|
||||
|
|
@ -117,7 +117,7 @@ class HomeserverPickerController extends State<HomeserverPicker> {
|
|||
isLoading = true;
|
||||
});
|
||||
final wellKnown =
|
||||
await Matrix.of(context).client.checkHomeserver(homeserver);
|
||||
await Matrix.of(context).getLoginClient().checkHomeserver(homeserver);
|
||||
|
||||
var jitsi = wellKnown?.additionalProperties
|
||||
?.tryGet<Map<String, dynamic>>('im.vector.riot.jitsi')
|
||||
|
|
@ -177,13 +177,13 @@ class HomeserverPickerController extends State<HomeserverPicker> {
|
|||
.any((flow) => flow['type'] == AuthenticationTypes.sso);
|
||||
|
||||
Future<Map<String, dynamic>> getLoginTypes() async {
|
||||
_rawLoginTypes ??= await Matrix.of(context).client.request(
|
||||
_rawLoginTypes ??= await Matrix.of(context).getLoginClient().request(
|
||||
RequestType.GET,
|
||||
'/client/r0/login',
|
||||
);
|
||||
if (registrationSupported == null) {
|
||||
try {
|
||||
await Matrix.of(context).client.register();
|
||||
await Matrix.of(context).getLoginClient().register();
|
||||
registrationSupported = true;
|
||||
} on MatrixException catch (e) {
|
||||
registrationSupported = e.requireAdditionalAuthentication ?? false;
|
||||
|
|
@ -200,14 +200,14 @@ class HomeserverPickerController extends State<HomeserverPicker> {
|
|||
if (kIsWeb) {
|
||||
// We store the homserver in the local storage instead of a redirect
|
||||
// parameter because of possible CSRF attacks.
|
||||
Store().setItem(
|
||||
ssoHomeserverKey, Matrix.of(context).client.homeserver.toString());
|
||||
Store().setItem(ssoHomeserverKey,
|
||||
Matrix.of(context).getLoginClient().homeserver.toString());
|
||||
}
|
||||
final redirectUrl = kIsWeb
|
||||
? AppConfig.webBaseUrl + '/#/'
|
||||
: AppConfig.appOpenUrlScheme.toLowerCase() + '://login';
|
||||
final url =
|
||||
'${Matrix.of(context).client.homeserver?.toString()}/_matrix/client/r0/login/sso/redirect/${Uri.encodeComponent(id)}?redirectUrl=${Uri.encodeQueryComponent(redirectUrl)}';
|
||||
'${Matrix.of(context).getLoginClient().homeserver?.toString()}/_matrix/client/r0/login/sso/redirect/${Uri.encodeComponent(id)}?redirectUrl=${Uri.encodeQueryComponent(redirectUrl)}';
|
||||
if (PlatformInfos.isMobile) {
|
||||
browser ??= ChromeSafariBrowser();
|
||||
browser.open(url: Uri.parse(url));
|
||||
|
|
@ -216,7 +216,7 @@ class HomeserverPickerController extends State<HomeserverPicker> {
|
|||
}
|
||||
}
|
||||
|
||||
void signUpAction() => VRouter.of(context).to('/signup');
|
||||
void signUpAction() => VRouter.of(context).to('signup');
|
||||
|
||||
bool _initialized = false;
|
||||
|
||||
|
|
|
|||
|
|
@ -64,7 +64,7 @@ class LoginController extends State<Login> {
|
|||
} else {
|
||||
identifier = AuthenticationUserIdentifier(user: username);
|
||||
}
|
||||
await matrix.client.login(LoginType.mLoginPassword,
|
||||
await matrix.getLoginClient().login(LoginType.mLoginPassword,
|
||||
identifier: identifier,
|
||||
// To stay compatible with older server versions
|
||||
// ignore: deprecated_member_use
|
||||
|
|
@ -98,12 +98,13 @@ class LoginController extends State<Login> {
|
|||
setState(() => usernameError = null);
|
||||
if (!userId.isValidMatrixId) return;
|
||||
try {
|
||||
final oldHomeserver = Matrix.of(context).client.homeserver;
|
||||
final oldHomeserver = Matrix.of(context).getLoginClient().homeserver;
|
||||
var newDomain = Uri.https(userId.domain, '');
|
||||
Matrix.of(context).client.homeserver = newDomain;
|
||||
Matrix.of(context).getLoginClient().homeserver = newDomain;
|
||||
DiscoveryInformation wellKnownInformation;
|
||||
try {
|
||||
wellKnownInformation = await Matrix.of(context).client.getWellknown();
|
||||
wellKnownInformation =
|
||||
await Matrix.of(context).getLoginClient().getWellknown();
|
||||
if (wellKnownInformation.mHomeserver?.baseUrl?.toString()?.isNotEmpty ??
|
||||
false) {
|
||||
newDomain = wellKnownInformation.mHomeserver.baseUrl;
|
||||
|
|
@ -120,8 +121,8 @@ class LoginController extends State<Login> {
|
|||
.checkHomeserver(newDomain)
|
||||
.catchError((e) => null),
|
||||
);
|
||||
if (Matrix.of(context).client.homeserver == null) {
|
||||
Matrix.of(context).client.homeserver = oldHomeserver;
|
||||
if (Matrix.of(context).getLoginClient().homeserver == null) {
|
||||
Matrix.of(context).getLoginClient().homeserver = oldHomeserver;
|
||||
// okay, the server we checked does not appear to be a matrix server
|
||||
Logs().v(
|
||||
'$newDomain is not running a homeserver, asking to use $oldHomeserver');
|
||||
|
|
@ -178,11 +179,12 @@ class LoginController extends State<Login> {
|
|||
Matrix.of(context).client.generateUniqueTransactionId();
|
||||
final response = await showFutureLoadingDialog(
|
||||
context: context,
|
||||
future: () => Matrix.of(context).client.requestTokenToResetPasswordEmail(
|
||||
clientSecret,
|
||||
input.single,
|
||||
sendAttempt++,
|
||||
),
|
||||
future: () =>
|
||||
Matrix.of(context).getLoginClient().requestTokenToResetPasswordEmail(
|
||||
clientSecret,
|
||||
input.single,
|
||||
sendAttempt++,
|
||||
),
|
||||
);
|
||||
if (response.error != null) return;
|
||||
final ok = await showOkAlertDialog(
|
||||
|
|
@ -211,7 +213,7 @@ class LoginController extends State<Login> {
|
|||
if (password == null) return;
|
||||
final success = await showFutureLoadingDialog(
|
||||
context: context,
|
||||
future: () => Matrix.of(context).client.changePassword(
|
||||
future: () => Matrix.of(context).getLoginClient().changePassword(
|
||||
password.single,
|
||||
auth: AuthenticationThreePidCreds(
|
||||
type: AuthenticationTypes.emailIdentity,
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import 'package:flutter/material.dart';
|
|||
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
||||
import 'package:future_loading_dialog/future_loading_dialog.dart';
|
||||
import 'package:matrix/matrix.dart';
|
||||
import 'package:vrouter/vrouter.dart';
|
||||
|
||||
class SettingsAccount extends StatefulWidget {
|
||||
const SettingsAccount({Key key}) : super(key: key);
|
||||
|
|
@ -144,6 +145,8 @@ class SettingsAccountController extends State<SettingsAccount> {
|
|||
);
|
||||
}
|
||||
|
||||
void addAccountAction() => VRouter.of(context).to('add');
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final client = Matrix.of(context).client;
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@ class SignupPageController extends State<SignupPage> {
|
|||
setState(() => loading = true);
|
||||
|
||||
try {
|
||||
final client = Matrix.of(context).client;
|
||||
final client = Matrix.of(context).getLoginClient();
|
||||
await client.uiaRequestBackground(
|
||||
(auth) => client.register(
|
||||
username: usernameController.text,
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
),
|
||||
|
|
|
|||
|
|
@ -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://', '')),
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
|
|
|
|||
|
|
@ -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(),
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue