refactor for multiple accounts

This commit is contained in:
zoe 2022-08-13 18:14:12 +02:00
parent 495dc1c29a
commit 43b5908488
13 changed files with 145 additions and 55 deletions

View File

@ -28,7 +28,7 @@ class App {
redirectUri: json["redirect_uri"].toString(), redirectUri: json["redirect_uri"].toString(),
clientId: json["client_id"].toString(), clientId: json["client_id"].toString(),
clientSecret: json["client_secret"].toString()); clientSecret: json["client_secret"].toString());
global.settings!.saveApp(app); global.settings!.identities[global.settings!.activeIdentity]!.saveApp(app);
return app; return app;
} }
} }
@ -37,7 +37,8 @@ Response readAuthcode(Request request) {
Map<String, String> params = request.url.queryParameters; Map<String, String> params = request.url.queryParameters;
if (params.containsKey("code") && params["code"] != null) { if (params.containsKey("code") && params["code"] != null) {
String code = params["code"].toString(); String code = params["code"].toString();
global.settings!.saveAuthCode(code); global.settings!.identities[global.settings!.activeIdentity]!
.saveAuthCode(code);
} }
return Response(308, return Response(308,
headers: {"Content-Type": "text/html; charset=UTF-8"}, headers: {"Content-Type": "text/html; charset=UTF-8"},
@ -53,7 +54,8 @@ Future<int> handleFullOauth() async {
return response.statusCode; return response.statusCode;
} }
await global.settings!.saveAuthCode(""); await global.settings!.identities[global.settings!.activeIdentity]!
.saveAuthCode("");
var handler = const Pipeline().addHandler(readAuthcode); var handler = const Pipeline().addHandler(readAuthcode);
var server = await shelf_io.serve(handler, 'localhost', 1312); var server = await shelf_io.serve(handler, 'localhost', 1312);
await pollCode(); await pollCode();
@ -69,13 +71,15 @@ Future<String> pollCode() async {
String code = ""; String code = "";
while (code == "") { while (code == "") {
await Future.delayed(const Duration(seconds: 3)); await Future.delayed(const Duration(seconds: 3));
code = global.settings!.authCode; code =
global.settings!.identities[global.settings!.activeIdentity]!.authCode;
} }
return code; return code;
} }
Future<http.Response> doOauthFlow() async { Future<http.Response> doOauthFlow() async {
String url = global.settings!.instanceUrl; String url =
global.settings!.identities[global.settings!.activeIdentity]!.instanceUrl;
try { try {
http.Response response = await registerApp(url); http.Response response = await registerApp(url);
openBrowserForAuthCode(url, App.fromJson(jsonDecode(response.body))); openBrowserForAuthCode(url, App.fromJson(jsonDecode(response.body)));
@ -120,10 +124,11 @@ void openBrowserForAuthCode(String baseurl, App app) {
} }
Future<int> refreshToken() async { Future<int> refreshToken() async {
final authCode = global.settings!.authCode; final activeId = global.settings!.activeIdentity;
final appId = global.settings!.clientId; final authCode = global.settings!.identities[activeId]!.authCode;
final clientSecret = global.settings!.clientSecret; final appId = global.settings!.identities[activeId]!.clientId;
final baseurl = global.settings!.instanceUrl; final clientSecret = global.settings!.identities[activeId]!.clientSecret;
final baseurl = global.settings!.identities[activeId]!.instanceUrl;
Uri url = Uri.https(baseurl, "/oauth/token"); Uri url = Uri.https(baseurl, "/oauth/token");
final response = await http.post( final response = await http.post(
@ -141,7 +146,8 @@ Future<int> refreshToken() async {
if (response.statusCode == 200) { if (response.statusCode == 200) {
final dec = jsonDecode(response.body); final dec = jsonDecode(response.body);
final accessToken = dec["access_token"]!; final accessToken = dec["access_token"]!;
await global.settings!.saveToken(accessToken); await global.settings!.identities[global.settings!.activeIdentity]!
.saveToken(accessToken);
} }
return response.statusCode; return response.statusCode;
} }

View File

@ -0,0 +1 @@
import '../../global.dart' as global;

View File

@ -2,21 +2,71 @@ import 'package:flutter/painting.dart';
import 'package:shared_preferences/shared_preferences.dart'; import 'package:shared_preferences/shared_preferences.dart';
import '../business_logic/auth/oauth.dart' as oauth; import '../business_logic/auth/oauth.dart' as oauth;
class Settings { class AccountSettings {
final String identity;
late String instanceUrl; late String instanceUrl;
static const String instanceUrlKey = "instance-url"; static const String instanceUrlKey = "instance-url";
late String authCode; late String authCode;
static const String authCodeKey = "authcode"; static const String authCodeKey = "authcode";
late Locale locale;
static const localeKey = "active-locale";
late String clientSecret; late String clientSecret;
static const clientSecretKey = "client-secret"; static const clientSecretKey = "client-secret";
late String clientId; late String clientId;
static const clientIdKey = "client-id"; static const clientIdKey = "client-id";
late String token; late String token;
static const tokenKey = "access-token"; static const tokenKey = "access-token";
late SharedPreferences prefs;
AccountSettings._create(this.identity);
static Future<AccountSettings> create(String identity) async {
AccountSettings settings = AccountSettings._create(identity);
settings.prefs = await SharedPreferences.getInstance();
settings.clientSecret =
settings.prefs.getString(settings.identity + clientSecretKey) ?? "";
settings.clientId =
settings.prefs.getString(settings.identity + clientIdKey) ?? "";
settings.token =
settings.prefs.getString(settings.identity + tokenKey) ?? "";
settings.instanceUrl =
settings.prefs.getString(settings.identity + instanceUrlKey) ??
"example.com";
settings.authCode =
settings.prefs.getString(settings.identity + authCodeKey) ?? "";
return settings;
}
Future<bool> saveInstanceUrl(String url) async {
instanceUrl = url;
return await prefs.setString(identity + instanceUrlKey, url);
}
Future<bool> saveAuthCode(String code) async {
authCode = code;
return await prefs.setString(identity + authCodeKey, code);
}
Future<void> saveApp(oauth.App app) async {
clientId = app.clientId;
clientSecret = clientSecret;
prefs.setString(identity + clientSecretKey, app.clientSecret);
prefs.setString(identity + clientIdKey, app.clientId);
}
Future<bool> saveToken(String token) async {
this.token = token;
return await prefs.setString(identity + tokenKey, token);
}
}
class Settings {
late Locale locale;
static const localeKey = "active-locale";
late int batchSize; late int batchSize;
static const batchSizeKey = "post-batch-size"; static const batchSizeKey = "post-batch-size";
late Map<String, AccountSettings> identities = {};
static const identitiesKey = "identities";
late String activeIdentity = "";
static const activeIdentityKey = "active-identity";
late SharedPreferences prefs; late SharedPreferences prefs;
Settings._create(); Settings._create();
@ -25,29 +75,31 @@ class Settings {
Settings settings = Settings._create(); Settings settings = Settings._create();
settings.prefs = await SharedPreferences.getInstance(); settings.prefs = await SharedPreferences.getInstance();
settings.instanceUrl =
settings.prefs.getString(instanceUrlKey) ?? "example.com";
settings.authCode = settings.prefs.getString(authCodeKey) ?? "";
settings.locale = Locale(settings.prefs.getString(localeKey) ?? "en"); settings.locale = Locale(settings.prefs.getString(localeKey) ?? "en");
settings.clientSecret = settings.prefs.getString(clientSecretKey) ?? "";
settings.clientId = settings.prefs.getString(clientIdKey) ?? "";
settings.token = settings.prefs.getString(tokenKey) ?? "";
settings.batchSize = settings.prefs.getInt(batchSizeKey) ?? 20; settings.batchSize = settings.prefs.getInt(batchSizeKey) ?? 20;
if (settings.batchSize < 5) { if (settings.batchSize < 5) {
settings.batchSize = 5; settings.batchSize = 5;
} }
List<String> identityList =
settings.prefs.getStringList(identitiesKey) ?? [];
settings.identities = {};
for (int i = 0; i < identityList.length; i++) {
settings.identities.addAll(
{identityList[i]: await AccountSettings.create(identityList[i])});
}
settings.activeIdentity = settings.prefs.getString(activeIdentityKey) ?? "";
return settings; return settings;
} }
Future<bool> saveInstanceUrl(String url) async { Future<bool> addNewIdentity(String key) async {
instanceUrl = url; List<String> a = identities.keys.toList();
return await prefs.setString(instanceUrlKey, url); a.add(key);
} identities.addAll({key: await AccountSettings.create(key)});
Future<bool> saveAuthCode(String code) async { return prefs.setStringList(identitiesKey, a);
authCode = code;
return await prefs.setString(authCodeKey, code);
} }
Future<bool> saveLocale(String locale) async { Future<bool> saveLocale(String locale) async {
@ -55,16 +107,9 @@ class Settings {
return await prefs.setString(localeKey, locale); return await prefs.setString(localeKey, locale);
} }
Future<void> saveApp(oauth.App app) async { Future<bool> saveActiveIdentity(String identity) {
clientId = app.clientId; activeIdentity = identity;
clientSecret = clientSecret; return prefs.setString(activeIdentityKey, identity);
prefs.setString(clientSecretKey, app.clientSecret);
prefs.setString(clientIdKey, app.clientId);
}
Future<bool> saveToken(String token) async {
this.token = token;
return await prefs.setString(tokenKey, token);
} }
Future<bool> saveBatchSize(int size) async { Future<bool> saveBatchSize(int size) async {

View File

@ -61,8 +61,9 @@ class PostModel implements Comparable {
} }
Future<ThreadModel> getThread() async { Future<ThreadModel> getThread() async {
final token = global.settings!.token; final activeId = global.settings!.activeIdentity;
final baseUrl = global.settings!.instanceUrl; final token = global.settings!.identities[activeId]!.token;
final baseUrl = global.settings!.identities[activeId]!.instanceUrl;
Map<String, String> headers = {"Authorization": "Bearer $token"}; Map<String, String> headers = {"Authorization": "Bearer $token"};
headers.addAll(global.defaultHeaders); headers.addAll(global.defaultHeaders);
String currentId = reblogId ?? id; String currentId = reblogId ?? id;
@ -72,10 +73,9 @@ class PostModel implements Comparable {
path: "/api/v1/statuses/$currentId/context", path: "/api/v1/statuses/$currentId/context",
); );
http.Response response = await http.get(url, headers: headers); http.Response response = await http.get(url, headers: headers);
if (response.statusCode != 200) { if (response.statusCode != 200 || response.body.isEmpty) {
return ThreadModel([this]); return ThreadModel([this]);
} }
final json = jsonDecode(response.body); final json = jsonDecode(response.body);
final List<dynamic> ancestorsJson = json["ancestors"]; final List<dynamic> ancestorsJson = json["ancestors"];
@ -98,15 +98,16 @@ class ThreadModel {
} }
Future<List<ThreadModel>> getTimelineFromServer(String? index) async { Future<List<ThreadModel>> getTimelineFromServer(String? index) async {
final activeId = global.settings!.activeIdentity;
final limit = global.settings!.batchSize; final limit = global.settings!.batchSize;
final token = global.settings!.token; final token = global.settings!.identities[activeId]!.token;
Map<String, String> query = {"limit": limit.toString()}; Map<String, String> query = {"limit": limit.toString()};
if (index != null) { if (index != null) {
query.addAll({"max_id": index}); query.addAll({"max_id": index});
} }
final baseUrl = global.settings!.instanceUrl; final baseUrl = global.settings!.identities[activeId]!.instanceUrl;
final url = Uri( final url = Uri(
scheme: "https", scheme: "https",
host: baseUrl, host: baseUrl,

View File

@ -0,0 +1,6 @@
import 'package:web_socket_channel/status.dart' as status;
import 'package:web_socket_channel/web_socket_channel.dart';
import '../global.dart' as global;
WebSocketChannel? channel;
bool connected = false;

View File

@ -19,7 +19,7 @@ void main() async {
activeLocale = global.settings!.locale; activeLocale = global.settings!.locale;
// check if all information is available // check if all information is available
if (global.settings!.authCode == "") { if (global.settings!.identities.isEmpty) {
_initRoute = "/login"; _initRoute = "/login";
} else { } else {
await oauth.refreshToken(); await oauth.refreshToken();

View File

@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
import 'package:localization/localization.dart'; import 'package:localization/localization.dart';
import '../business_logic/auth/oauth.dart' as oauth; import '../business_logic/auth/oauth.dart' as oauth;
import '../global.dart' as global; import '../global.dart' as global;
import '../business_logic/settings.dart' as settings;
class Login extends StatefulWidget { class Login extends StatefulWidget {
const Login({Key? key}) : super(key: key); const Login({Key? key}) : super(key: key);
@ -31,7 +32,7 @@ class LoginForm extends StatefulWidget {
class _LoginFormState extends State<LoginForm> { class _LoginFormState extends State<LoginForm> {
final formKey = GlobalKey<FormState>(); final formKey = GlobalKey<FormState>();
String identity = "";
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Form( return Form(
@ -45,7 +46,10 @@ class _LoginFormState extends State<LoginForm> {
Text("greeting".i18n(), style: Theme.of(context).textTheme.headline1), Text("greeting".i18n(), style: Theme.of(context).textTheme.headline1),
TextFormField( TextFormField(
onSaved: (value) async { onSaved: (value) async {
await global.settings!.saveInstanceUrl(value!); await global.settings!.addNewIdentity(identity);
await global.settings!.saveActiveIdentity(identity);
await global.settings!.identities[identity]!
.saveInstanceUrl(identity);
}, },
decoration: InputDecoration( decoration: InputDecoration(
labelText: "instance-url".i18n(), labelText: "instance-url".i18n(),
@ -55,12 +59,22 @@ class _LoginFormState extends State<LoginForm> {
), ),
autofocus: true, autofocus: true,
), ),
TextFormField(
onChanged: (value) {
identity = value;
},
),
TextButton.icon( TextButton.icon(
onPressed: () { onPressed: () {
bool isValid = formKey.currentState!.validate(); bool isValid = formKey.currentState!.validate();
if (!isValid) { if (!isValid) {
ScaffoldMessenger.of(context).showSnackBar(SnackBar( ScaffoldMessenger.of(context).showSnackBar(
content: Text("login-failed-snackbar-text".i18n()))); SnackBar(
content: Text(
"login-failed-snackbar-text".i18n(),
),
),
);
} else { } else {
formKey.currentState?.save(); formKey.currentState?.save();
Navigator.push( Navigator.push(

View File

@ -20,12 +20,9 @@ class LogoutButton extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
String url; String url = global
if (global.settings!.instanceUrl.isEmpty) { .settings!.identities[global.settings!.activeIdentity]!.instanceUrl;
url = "no-instance".i18n();
} else {
url = global.settings!.instanceUrl;
}
return Row( return Row(
children: [ children: [
Text(url), Text(url),
@ -41,6 +38,6 @@ class LogoutButton extends StatelessWidget {
} }
void logout() async { void logout() async {
await global.settings!.saveAuthCode(""); //await global.settings!.saveAuthCode("");
exit(0); exit(0);
} }

View File

@ -5,6 +5,7 @@ import 'package:loris/pages/chat/chat.dart';
import 'package:loris/pages/notifications/notifications.dart'; import 'package:loris/pages/notifications/notifications.dart';
import 'package:loris/pages/timeline/timeline.dart'; import 'package:loris/pages/timeline/timeline.dart';
import 'package:loris/pages/settings/settings.dart'; import 'package:loris/pages/settings/settings.dart';
import '../business_logic/websocket.dart' as websocket;
class MainScaffold extends StatefulWidget { class MainScaffold extends StatefulWidget {
const MainScaffold({Key? key}) : super(key: key); const MainScaffold({Key? key}) : super(key: key);
@ -18,6 +19,7 @@ class _MainScaffoldState extends State<MainScaffold> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
// websocket.connect();
final screens = [ final screens = [
const Timeline(), const Timeline(),
chat(context), chat(context),

View File

@ -59,7 +59,9 @@ class DisplayName extends StatelessWidget {
child: ProfilePic( child: ProfilePic(
url: account.avatar, url: account.avatar,
)), )),
const SizedBox(width: 8), const SizedBox(
width: 8,
),
Flexible( Flexible(
child: Column( child: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,

View File

@ -114,6 +114,7 @@ ThemeData getTheme(CustomColors colors) {
fontWeight: FontWeight.w700, fontWeight: FontWeight.w700,
), ),
), ),
tooltipTheme: TooltipThemeData(),
); );
} }

View File

@ -43,6 +43,13 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.16.0" version: "1.16.0"
crypto:
dependency: transitive
description:
name: crypto
url: "https://pub.dartlang.org"
source: hosted
version: "3.0.2"
csslib: csslib:
dependency: transitive dependency: transitive
description: description:
@ -404,6 +411,13 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.1.2" version: "2.1.2"
web_socket_channel:
dependency: "direct main"
description:
name: web_socket_channel
url: "https://pub.dartlang.org"
source: hosted
version: "2.2.0"
win32: win32:
dependency: transitive dependency: transitive
description: description:

View File

@ -43,6 +43,7 @@ dependencies:
url_launcher: ^6.1.4 url_launcher: ^6.1.4
shelf: ^1.3.1 shelf: ^1.3.1
html: ^0.15.0 html: ^0.15.0
web_socket_channel: ^2.2.0
dev_dependencies: dev_dependencies:
flutter_test: flutter_test: