Add Device Settings page

This commit is contained in:
Christian Pauly 2020-02-19 16:23:13 +01:00
parent 9a52e93862
commit fff216c46c
11 changed files with 310 additions and 10 deletions

View File

@ -14,6 +14,7 @@ class SimpleDialogs {
String labelText,
String prefixText,
String suffixText,
bool password = false,
bool multiLine = false,
}) async {
final TextEditingController controller = TextEditingController();
@ -31,6 +32,7 @@ class SimpleDialogs {
},
minLines: multiLine ? 3 : 1,
maxLines: multiLine ? 3 : 1,
obscureText: password,
textInputAction: multiLine ? TextInputAction.newline : null,
decoration: InputDecoration(
hintText: hintText,

View File

@ -60,16 +60,30 @@ class MatrixState extends State<Matrix> {
BuildContext _loadingDialogContext;
Future<dynamic> tryRequestWithLoadingDialog(Future<dynamic> request) async {
Future<dynamic> tryRequestWithLoadingDialog(Future<dynamic> request,
{Function(MatrixException) onAdditionalAuth}) async {
showLoadingDialog(context);
final dynamic = await tryRequestWithErrorToast(request);
final dynamic = await tryRequestWithErrorToast(request,
onAdditionalAuth: onAdditionalAuth);
hideLoadingDialog();
return dynamic;
}
Future<dynamic> tryRequestWithErrorToast(Future<dynamic> request) async {
Future<dynamic> tryRequestWithErrorToast(Future<dynamic> request,
{Function(MatrixException) onAdditionalAuth}) async {
try {
return await request;
} on MatrixException catch (exception) {
if (exception.requireAdditionalAuthentication &&
onAdditionalAuth != null) {
return await tryRequestWithErrorToast(onAdditionalAuth(exception));
} else {
Toast.show(
exception.errorMessage,
context,
duration: Toast.LENGTH_LONG,
);
}
} catch (exception) {
Toast.show(
exception.toString(),
@ -302,6 +316,17 @@ class MatrixState extends State<Matrix> {
}
}
Map<String, dynamic> getAuthByPassword(String password, String session) => {
"type": "m.login.password",
"identifier": {
"type": "m.id.user",
"user": client.userID,
},
"user": client.userID,
"password": password,
"session": session,
};
@override
void initState() {
if (widget.client == null) {

View File

@ -256,6 +256,8 @@ class I18n {
String get deleteMessage => Intl.message("Delete message");
String get devices => Intl.message("Devices");
String get discardPicture => Intl.message("Discard picture");
String get displaynameHasBeenChanged =>
@ -324,6 +326,8 @@ class I18n {
String get homeserverIsNotCompatible =>
Intl.message("Homeserver is not compatible");
String get id => Intl.message("ID");
String get inviteContact => Intl.message("Invite contact");
String inviteContactToGroup(String groupName) => Intl.message(
@ -382,6 +386,8 @@ class I18n {
args: [username],
);
String get lastSeenIp => Intl.message("Last seen IP");
String get license => Intl.message("License");
String get loadingPleaseWait => Intl.message("Loading... Please wait");
@ -473,12 +479,16 @@ class I18n {
args: [username],
);
String get removeAllOtherDevices => Intl.message("Remove all other devices");
String removedBy(String username) => Intl.message(
"Removed by $username",
name: "removedBy",
args: [username],
);
String get removeDevice => Intl.message("Remove device");
String get removeExile => Intl.message("Remove exile");
String get revokeAllPermissions => Intl.message("Revoke all permissions");
@ -621,6 +631,8 @@ class I18n {
String get unmuteChat => Intl.message('Unmute chat');
String get unknownDevice => Intl.message("Unknown device");
String unknownEvent(String type) => Intl.message(
"Unknown event '$type'",
name: "unknownEvent",

View File

@ -340,6 +340,11 @@
"type": "text",
"placeholders": {}
},
"Devices": "Geräte",
"@Devices": {
"type": "text",
"placeholders": {}
},
"Discard picture": "Bild verwerfen",
"@Discard picture": {
"type": "text",
@ -472,6 +477,11 @@
"type": "text",
"placeholders": {}
},
"ID": "ID",
"@ID": {
"type": "text",
"placeholders": {}
},
"Invite contact": "Kontakt einladen",
"@Invite contact": {
"type": "text",
@ -565,6 +575,11 @@
"username": {}
}
},
"Last seen IP": "Zuletzt bekannte IP",
"@Last seen IP": {
"type": "text",
"placeholders": {}
},
"License": "Lizenz",
"@License": {
"type": "text",
@ -735,6 +750,11 @@
"username": {}
}
},
"Remove all other devices": "Alle anderen Geräte entfernen",
"@Remove all other devices": {
"type": "text",
"placeholders": {}
},
"removedBy": "Entfernt von {username}",
"@removedBy": {
"type": "text",
@ -742,6 +762,11 @@
"username": {}
}
},
"Remove device": "Gerät entfernen",
"@Remove device": {
"type": "text",
"placeholders": {}
},
"Remove exile": "Verbannung aufheben",
"@Remove exile": {
"type": "text",
@ -991,6 +1016,11 @@
"type": "text",
"placeholders": {}
},
"Unknown device": "Unbekanntes Gerät",
"@Unknown device": {
"type": "text",
"placeholders": {}
},
"unknownEvent": "Unbekanntes Event '{type}'",
"@unknownEvent": {
"type": "text",

View File

@ -1,5 +1,5 @@
{
"@@last_modified": "2020-02-16T12:36:34.703154",
"@@last_modified": "2020-02-19T16:20:13.752724",
"About": "About",
"@About": {
"type": "text",
@ -340,6 +340,11 @@
"type": "text",
"placeholders": {}
},
"Devices": "Devices",
"@Devices": {
"type": "text",
"placeholders": {}
},
"Discard picture": "Discard picture",
"@Discard picture": {
"type": "text",
@ -472,6 +477,11 @@
"type": "text",
"placeholders": {}
},
"ID": "ID",
"@ID": {
"type": "text",
"placeholders": {}
},
"Invite contact": "Invite contact",
"@Invite contact": {
"type": "text",
@ -565,6 +575,11 @@
"username": {}
}
},
"Last seen IP": "Last seen IP",
"@Last seen IP": {
"type": "text",
"placeholders": {}
},
"License": "License",
"@License": {
"type": "text",
@ -735,6 +750,11 @@
"username": {}
}
},
"Remove all other devices": "Remove all other devices",
"@Remove all other devices": {
"type": "text",
"placeholders": {}
},
"removedBy": "Removed by {username}",
"@removedBy": {
"type": "text",
@ -742,6 +762,11 @@
"username": {}
}
},
"Remove device": "Remove device",
"@Remove device": {
"type": "text",
"placeholders": {}
},
"Remove exile": "Remove exile",
"@Remove exile": {
"type": "text",
@ -991,6 +1016,11 @@
"type": "text",
"placeholders": {}
},
"Unknown device": "Unknown device",
"@Unknown device": {
"type": "text",
"placeholders": {}
},
"unknownEvent": "Unknown event '{type}'",
"@unknownEvent": {
"type": "text",

View File

@ -170,6 +170,7 @@ class MessageLookup extends MessageLookupByLibrary {
"Dark" : MessageLookupByLibrary.simpleMessage("Dunkel"),
"Delete" : MessageLookupByLibrary.simpleMessage("Löschen"),
"Delete message" : MessageLookupByLibrary.simpleMessage("Nachricht löschen"),
"Devices" : MessageLookupByLibrary.simpleMessage("Geräte"),
"Discard picture" : MessageLookupByLibrary.simpleMessage("Bild verwerfen"),
"Displayname has been changed" : MessageLookupByLibrary.simpleMessage("Anzeigename wurde geändert"),
"Donate" : MessageLookupByLibrary.simpleMessage("Spenden"),
@ -193,11 +194,13 @@ class MessageLookup extends MessageLookupByLibrary {
"Guests can join" : MessageLookupByLibrary.simpleMessage("Gäste dürfen beitreten"),
"Help" : MessageLookupByLibrary.simpleMessage("Hilfe"),
"Homeserver is not compatible" : MessageLookupByLibrary.simpleMessage("Homeserver ist nicht kompatibel"),
"ID" : MessageLookupByLibrary.simpleMessage("ID"),
"Invite contact" : MessageLookupByLibrary.simpleMessage("Kontakt einladen"),
"Invited" : MessageLookupByLibrary.simpleMessage("Eingeladen"),
"Invited users only" : MessageLookupByLibrary.simpleMessage("Nur eingeladene Benutzer"),
"It seems that you have no google services on your phone. That\'s a good decision for your privacy! To receive push notifications in FluffyChat we recommend using microG: https://microg.org/" : MessageLookupByLibrary.simpleMessage("Es sieht so aus als hättest du keine Google Dienste auf deinem Gerät. Das ist eine gute Entscheidung für deine Privatsphäre. Um Push Benachrichtigungen in FluffyChat zu erhalten, empfehlen wir die Verwendung von microG: https://microg.org/"),
"Kick from chat" : MessageLookupByLibrary.simpleMessage("Aus dem Chat hinauswerfen"),
"Last seen IP" : MessageLookupByLibrary.simpleMessage("Zuletzt bekannte IP"),
"Leave" : MessageLookupByLibrary.simpleMessage("Verlassen"),
"Left the chat" : MessageLookupByLibrary.simpleMessage("Hat den Chat verlassen"),
"License" : MessageLookupByLibrary.simpleMessage("Lizenz"),
@ -228,6 +231,8 @@ class MessageLookup extends MessageLookupByLibrary {
"Please enter your username" : MessageLookupByLibrary.simpleMessage("Bitte deinen Benutzernamen eingeben"),
"Rejoin" : MessageLookupByLibrary.simpleMessage("Wieder beitreten"),
"Remove" : MessageLookupByLibrary.simpleMessage("Entfernen"),
"Remove all other devices" : MessageLookupByLibrary.simpleMessage("Alle anderen Geräte entfernen"),
"Remove device" : MessageLookupByLibrary.simpleMessage("Gerät entfernen"),
"Remove exile" : MessageLookupByLibrary.simpleMessage("Verbannung aufheben"),
"Remove message" : MessageLookupByLibrary.simpleMessage("Nachricht entfernen"),
"Reply" : MessageLookupByLibrary.simpleMessage("Antworten"),
@ -253,6 +258,7 @@ class MessageLookup extends MessageLookupByLibrary {
"Thursday" : MessageLookupByLibrary.simpleMessage("Donnerstag"),
"Try to send again" : MessageLookupByLibrary.simpleMessage("Nochmal versuchen zu senden"),
"Tuesday" : MessageLookupByLibrary.simpleMessage("Tuesday"),
"Unknown device" : MessageLookupByLibrary.simpleMessage("Unbekanntes Gerät"),
"Unmute chat" : MessageLookupByLibrary.simpleMessage("Stumm aus"),
"Use Amoled compatible colors?" : MessageLookupByLibrary.simpleMessage("Amoled optimierte Farben verwenden?"),
"Username" : MessageLookupByLibrary.simpleMessage("Benutzername"),

View File

@ -170,6 +170,7 @@ class MessageLookup extends MessageLookupByLibrary {
"Dark" : MessageLookupByLibrary.simpleMessage("Dark"),
"Delete" : MessageLookupByLibrary.simpleMessage("Delete"),
"Delete message" : MessageLookupByLibrary.simpleMessage("Delete message"),
"Devices" : MessageLookupByLibrary.simpleMessage("Devices"),
"Discard picture" : MessageLookupByLibrary.simpleMessage("Discard picture"),
"Displayname has been changed" : MessageLookupByLibrary.simpleMessage("Displayname has been changed"),
"Donate" : MessageLookupByLibrary.simpleMessage("Donate"),
@ -193,11 +194,13 @@ class MessageLookup extends MessageLookupByLibrary {
"Guests can join" : MessageLookupByLibrary.simpleMessage("Guests can join"),
"Help" : MessageLookupByLibrary.simpleMessage("Help"),
"Homeserver is not compatible" : MessageLookupByLibrary.simpleMessage("Homeserver is not compatible"),
"ID" : MessageLookupByLibrary.simpleMessage("ID"),
"Invite contact" : MessageLookupByLibrary.simpleMessage("Invite contact"),
"Invited" : MessageLookupByLibrary.simpleMessage("Invited"),
"Invited users only" : MessageLookupByLibrary.simpleMessage("Invited users only"),
"It seems that you have no google services on your phone. That\'s a good decision for your privacy! To receive push notifications in FluffyChat we recommend using microG: https://microg.org/" : MessageLookupByLibrary.simpleMessage("It seems that you have no google services on your phone. That\'s a good decision for your privacy! To receive push notifications in FluffyChat we recommend using microG: https://microg.org/"),
"Kick from chat" : MessageLookupByLibrary.simpleMessage("Kick from chat"),
"Last seen IP" : MessageLookupByLibrary.simpleMessage("Last seen IP"),
"Leave" : MessageLookupByLibrary.simpleMessage("Leave"),
"Left the chat" : MessageLookupByLibrary.simpleMessage("Left the chat"),
"License" : MessageLookupByLibrary.simpleMessage("License"),
@ -228,6 +231,8 @@ class MessageLookup extends MessageLookupByLibrary {
"Please enter your username" : MessageLookupByLibrary.simpleMessage("Please enter your username"),
"Rejoin" : MessageLookupByLibrary.simpleMessage("Rejoin"),
"Remove" : MessageLookupByLibrary.simpleMessage("Remove"),
"Remove all other devices" : MessageLookupByLibrary.simpleMessage("Remove all other devices"),
"Remove device" : MessageLookupByLibrary.simpleMessage("Remove device"),
"Remove exile" : MessageLookupByLibrary.simpleMessage("Remove exile"),
"Remove message" : MessageLookupByLibrary.simpleMessage("Remove message"),
"Reply" : MessageLookupByLibrary.simpleMessage("Reply"),
@ -253,6 +258,7 @@ class MessageLookup extends MessageLookupByLibrary {
"Thursday" : MessageLookupByLibrary.simpleMessage("Thursday"),
"Try to send again" : MessageLookupByLibrary.simpleMessage("Try to send again"),
"Tuesday" : MessageLookupByLibrary.simpleMessage("Tuesday"),
"Unknown device" : MessageLookupByLibrary.simpleMessage("Unknown device"),
"Unmute chat" : MessageLookupByLibrary.simpleMessage("Unmute chat"),
"Use Amoled compatible colors?" : MessageLookupByLibrary.simpleMessage("Use Amoled compatible colors?"),
"Username" : MessageLookupByLibrary.simpleMessage("Username"),

View File

@ -1,6 +1,7 @@
import 'dart:io';
import 'package:famedlysdk/famedlysdk.dart';
import 'package:fluffychat/views/settings_devices.dart';
import 'package:flutter/material.dart';
import 'package:image_picker/image_picker.dart';
import 'package:toast/toast.dart';
@ -156,7 +157,16 @@ class _SettingsState extends State<Settings> {
),
),
),
Divider(thickness: 1),
ListTile(
trailing: Icon(Icons.devices_other),
title: Text(I18n.of(context).devices),
onTap: () async => await Navigator.of(context).push(
AppRoute.defaultRoute(
context,
DevicesSettingsView(),
),
),
),
ListTile(
trailing: Icon(Icons.exit_to_app),
title: Text(I18n.of(context).logout),

View File

@ -0,0 +1,179 @@
import 'package:famedlysdk/famedlysdk.dart';
import 'package:fluffychat/components/dialogs/simple_dialogs.dart';
import 'package:flutter/material.dart';
import '../utils/date_time_extension.dart';
import '../components/adaptive_page_layout.dart';
import '../components/matrix.dart';
import '../i18n/i18n.dart';
import 'chat_list.dart';
class DevicesSettingsView extends StatelessWidget {
@override
Widget build(BuildContext context) {
return AdaptivePageLayout(
primaryPage: FocusPage.SECOND,
firstScaffold: ChatList(),
secondScaffold: DevicesSettings(),
);
}
}
class DevicesSettings extends StatefulWidget {
@override
DevicesSettingsState createState() => DevicesSettingsState();
}
class DevicesSettingsState extends State<DevicesSettings> {
List<UserDevice> devices;
Future<bool> _loadUserDevices(BuildContext context) async {
if (devices != null) return true;
devices = await Matrix.of(context).client.requestUserDevices();
return true;
}
void reload() => setState(() => devices = null);
void _removeDevicesAction(
BuildContext context, List<UserDevice> devices) async {
if (await SimpleDialogs(context).askConfirmation() == false) return;
MatrixState matrix = Matrix.of(context);
List<String> deviceIds = [];
for (UserDevice userDevice in devices) {
deviceIds.add(userDevice.deviceId);
}
final success = await matrix
.tryRequestWithLoadingDialog(matrix.client.deleteDevices(deviceIds),
onAdditionalAuth: (MatrixException exception) async {
final String password = await SimpleDialogs(context).enterText(
titleText: I18n.of(context).pleaseEnterYourPassword,
labelText: I18n.of(context).pleaseEnterYourPassword,
hintText: "******",
password: true);
if (password == null) return;
await matrix.client.deleteDevices(deviceIds,
auth: matrix.getAuthByPassword(password, exception.session));
return;
});
if (success != false) {
reload();
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text(I18n.of(context).devices)),
body: FutureBuilder<bool>(
future: _loadUserDevices(context),
builder: (BuildContext context, snapshot) {
if (snapshot.hasError) {
return Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Icon(Icons.error),
Text(snapshot.error.toString()),
],
),
);
}
if (!snapshot.hasData || this.devices == null) {
return Center(child: CircularProgressIndicator());
}
Function isOwnDevice = (UserDevice userDevice) =>
userDevice.deviceId == Matrix.of(context).client.deviceID;
final List<UserDevice> devices = List<UserDevice>.from(this.devices);
UserDevice thisDevice =
devices.firstWhere(isOwnDevice, orElse: () => null);
devices.removeWhere(isOwnDevice);
return Column(
children: <Widget>[
if (thisDevice != null)
UserDeviceListItem(
thisDevice,
remove: (d) => _removeDevicesAction(context, [d]),
),
Divider(height: 1),
if (devices.isNotEmpty)
ListTile(
title: Text(
I18n.of(context).removeAllOtherDevices,
style: TextStyle(color: Colors.red),
),
trailing: Icon(Icons.delete_outline),
onTap: () => _removeDevicesAction(context, devices),
),
Divider(height: 1),
Expanded(
child: devices.isEmpty
? Center(
child: Icon(
Icons.devices_other,
size: 60,
color: Theme.of(context).secondaryHeaderColor,
),
)
: ListView.separated(
separatorBuilder: (BuildContext context, int i) =>
Divider(height: 1),
itemCount: devices.length,
itemBuilder: (BuildContext context, int i) =>
UserDeviceListItem(
devices[i],
remove: (d) => _removeDevicesAction(context, [d]),
),
),
),
],
);
},
),
);
}
}
class UserDeviceListItem extends StatelessWidget {
final UserDevice userDevice;
final Function remove;
const UserDeviceListItem(this.userDevice, {this.remove, Key key})
: super(key: key);
@override
Widget build(BuildContext context) {
return PopupMenuButton(
onSelected: (String action) {
if (action == "remove" && this.remove != null) {
remove(userDevice);
}
},
itemBuilder: (BuildContext context) => [
PopupMenuItem<String>(
value: "remove",
child: Text(I18n.of(context).removeDevice,
style: TextStyle(color: Colors.red)),
),
],
child: ListTile(
contentPadding: EdgeInsets.all(16.0),
title: Row(
children: <Widget>[
Text((userDevice.displayName?.isNotEmpty ?? false)
? userDevice.displayName
: I18n.of(context).unknownDevice),
Spacer(),
Text(userDevice.lastSeenTs.localizedTimeShort(context)),
],
),
subtitle: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text("${I18n.of(context).id}: ${userDevice.deviceId}"),
Text("${I18n.of(context).lastSeenIp}: ${userDevice.lastSeenIp}"),
],
),
),
);
}
}

View File

@ -124,8 +124,8 @@ packages:
dependency: "direct main"
description:
path: "."
ref: "083dd8eb295f0c6624e78d54f2ffeaa30de5de41"
resolved-ref: "083dd8eb295f0c6624e78d54f2ffeaa30de5de41"
ref: "6dd3b879b6cfcf1c7da9dedfa62626237afa6d9a"
resolved-ref: "6dd3b879b6cfcf1c7da9dedfa62626237afa6d9a"
url: "https://gitlab.com/famedly/famedlysdk.git"
source: git
version: "0.0.1"
@ -377,8 +377,8 @@ packages:
dependency: transitive
description:
path: "."
ref: "09eb49dbdb1ad9ed71c6bf74562250ecd3d4198b"
resolved-ref: "09eb49dbdb1ad9ed71c6bf74562250ecd3d4198b"
ref: "307dc133867eb5bf80d4f5c7412e58621dfca3cf"
resolved-ref: "307dc133867eb5bf80d4f5c7412e58621dfca3cf"
url: "https://gitlab.com/famedly/libraries/dart-olm.git"
source: git
version: "0.0.0"

View File

@ -27,7 +27,7 @@ dependencies:
famedlysdk:
git:
url: https://gitlab.com/famedly/famedlysdk.git
ref: ce1fd3ecd86999c0397208a707e359ec956ff52a
ref: 6dd3b879b6cfcf1c7da9dedfa62626237afa6d9a
localstorage: ^3.0.1+4
bubble: ^1.1.9+1