diff --git a/lib/business_logic/auth/oauth.dart b/lib/business_logic/auth/oauth.dart index 589a156..3609789 100644 --- a/lib/business_logic/auth/oauth.dart +++ b/lib/business_logic/auth/oauth.dart @@ -28,7 +28,7 @@ class App { redirectUri: json["redirect_uri"].toString(), clientId: json["client_id"].toString(), clientSecret: json["client_secret"].toString()); - global.settings!.saveApp(app); + global.settings!.identities[global.settings!.activeIdentity]!.saveApp(app); return app; } } @@ -37,7 +37,8 @@ Response readAuthcode(Request request) { Map params = request.url.queryParameters; if (params.containsKey("code") && params["code"] != null) { String code = params["code"].toString(); - global.settings!.saveAuthCode(code); + global.settings!.identities[global.settings!.activeIdentity]! + .saveAuthCode(code); } return Response(308, headers: {"Content-Type": "text/html; charset=UTF-8"}, @@ -53,7 +54,8 @@ Future handleFullOauth() async { return response.statusCode; } - await global.settings!.saveAuthCode(""); + await global.settings!.identities[global.settings!.activeIdentity]! + .saveAuthCode(""); var handler = const Pipeline().addHandler(readAuthcode); var server = await shelf_io.serve(handler, 'localhost', 1312); await pollCode(); @@ -69,13 +71,15 @@ Future pollCode() async { String code = ""; while (code == "") { await Future.delayed(const Duration(seconds: 3)); - code = global.settings!.authCode; + code = + global.settings!.identities[global.settings!.activeIdentity]!.authCode; } return code; } Future doOauthFlow() async { - String url = global.settings!.instanceUrl; + String url = + global.settings!.identities[global.settings!.activeIdentity]!.instanceUrl; try { http.Response response = await registerApp(url); openBrowserForAuthCode(url, App.fromJson(jsonDecode(response.body))); @@ -120,10 +124,11 @@ void openBrowserForAuthCode(String baseurl, App app) { } Future refreshToken() async { - final authCode = global.settings!.authCode; - final appId = global.settings!.clientId; - final clientSecret = global.settings!.clientSecret; - final baseurl = global.settings!.instanceUrl; + final activeId = global.settings!.activeIdentity; + final authCode = global.settings!.identities[activeId]!.authCode; + final appId = global.settings!.identities[activeId]!.clientId; + final clientSecret = global.settings!.identities[activeId]!.clientSecret; + final baseurl = global.settings!.identities[activeId]!.instanceUrl; Uri url = Uri.https(baseurl, "/oauth/token"); final response = await http.post( @@ -141,7 +146,8 @@ Future refreshToken() async { if (response.statusCode == 200) { final dec = jsonDecode(response.body); final accessToken = dec["access_token"]!; - await global.settings!.saveToken(accessToken); + await global.settings!.identities[global.settings!.activeIdentity]! + .saveToken(accessToken); } return response.statusCode; } diff --git a/lib/business_logic/notifications/notifs.dart b/lib/business_logic/notifications/notifs.dart new file mode 100644 index 0000000..040b7e5 --- /dev/null +++ b/lib/business_logic/notifications/notifs.dart @@ -0,0 +1 @@ +import '../../global.dart' as global; diff --git a/lib/business_logic/settings.dart b/lib/business_logic/settings.dart index 8f2332d..1081d63 100644 --- a/lib/business_logic/settings.dart +++ b/lib/business_logic/settings.dart @@ -2,21 +2,71 @@ import 'package:flutter/painting.dart'; import 'package:shared_preferences/shared_preferences.dart'; import '../business_logic/auth/oauth.dart' as oauth; -class Settings { +class AccountSettings { + final String identity; late String instanceUrl; static const String instanceUrlKey = "instance-url"; late String authCode; static const String authCodeKey = "authcode"; - late Locale locale; - static const localeKey = "active-locale"; late String clientSecret; static const clientSecretKey = "client-secret"; late String clientId; static const clientIdKey = "client-id"; late String token; static const tokenKey = "access-token"; + late SharedPreferences prefs; + + AccountSettings._create(this.identity); + + static Future 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 saveInstanceUrl(String url) async { + instanceUrl = url; + return await prefs.setString(identity + instanceUrlKey, url); + } + + Future saveAuthCode(String code) async { + authCode = code; + return await prefs.setString(identity + authCodeKey, code); + } + + Future saveApp(oauth.App app) async { + clientId = app.clientId; + clientSecret = clientSecret; + prefs.setString(identity + clientSecretKey, app.clientSecret); + prefs.setString(identity + clientIdKey, app.clientId); + } + + Future 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; static const batchSizeKey = "post-batch-size"; + late Map identities = {}; + static const identitiesKey = "identities"; + late String activeIdentity = ""; + static const activeIdentityKey = "active-identity"; late SharedPreferences prefs; Settings._create(); @@ -25,29 +75,31 @@ class Settings { Settings settings = Settings._create(); 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.clientSecret = settings.prefs.getString(clientSecretKey) ?? ""; - settings.clientId = settings.prefs.getString(clientIdKey) ?? ""; - settings.token = settings.prefs.getString(tokenKey) ?? ""; + settings.batchSize = settings.prefs.getInt(batchSizeKey) ?? 20; if (settings.batchSize < 5) { settings.batchSize = 5; } + List 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; } - Future saveInstanceUrl(String url) async { - instanceUrl = url; - return await prefs.setString(instanceUrlKey, url); - } + Future addNewIdentity(String key) async { + List a = identities.keys.toList(); + a.add(key); + identities.addAll({key: await AccountSettings.create(key)}); - Future saveAuthCode(String code) async { - authCode = code; - return await prefs.setString(authCodeKey, code); + return prefs.setStringList(identitiesKey, a); } Future saveLocale(String locale) async { @@ -55,16 +107,9 @@ class Settings { return await prefs.setString(localeKey, locale); } - Future saveApp(oauth.App app) async { - clientId = app.clientId; - clientSecret = clientSecret; - prefs.setString(clientSecretKey, app.clientSecret); - prefs.setString(clientIdKey, app.clientId); - } - - Future saveToken(String token) async { - this.token = token; - return await prefs.setString(tokenKey, token); + Future saveActiveIdentity(String identity) { + activeIdentity = identity; + return prefs.setString(activeIdentityKey, identity); } Future saveBatchSize(int size) async { diff --git a/lib/business_logic/timeline/timeline.dart b/lib/business_logic/timeline/timeline.dart index 17a556f..a614769 100644 --- a/lib/business_logic/timeline/timeline.dart +++ b/lib/business_logic/timeline/timeline.dart @@ -61,8 +61,9 @@ class PostModel implements Comparable { } Future getThread() async { - final token = global.settings!.token; - final baseUrl = global.settings!.instanceUrl; + final activeId = global.settings!.activeIdentity; + final token = global.settings!.identities[activeId]!.token; + final baseUrl = global.settings!.identities[activeId]!.instanceUrl; Map headers = {"Authorization": "Bearer $token"}; headers.addAll(global.defaultHeaders); String currentId = reblogId ?? id; @@ -72,10 +73,9 @@ class PostModel implements Comparable { path: "/api/v1/statuses/$currentId/context", ); http.Response response = await http.get(url, headers: headers); - if (response.statusCode != 200) { + if (response.statusCode != 200 || response.body.isEmpty) { return ThreadModel([this]); } - final json = jsonDecode(response.body); final List ancestorsJson = json["ancestors"]; @@ -98,15 +98,16 @@ class ThreadModel { } Future> getTimelineFromServer(String? index) async { + final activeId = global.settings!.activeIdentity; final limit = global.settings!.batchSize; - final token = global.settings!.token; + final token = global.settings!.identities[activeId]!.token; Map query = {"limit": limit.toString()}; if (index != null) { query.addAll({"max_id": index}); } - final baseUrl = global.settings!.instanceUrl; + final baseUrl = global.settings!.identities[activeId]!.instanceUrl; final url = Uri( scheme: "https", host: baseUrl, diff --git a/lib/business_logic/websocket.dart b/lib/business_logic/websocket.dart new file mode 100644 index 0000000..ad0c75f --- /dev/null +++ b/lib/business_logic/websocket.dart @@ -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; diff --git a/lib/main.dart b/lib/main.dart index 349a307..c7083c9 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -19,7 +19,7 @@ void main() async { activeLocale = global.settings!.locale; // check if all information is available - if (global.settings!.authCode == "") { + if (global.settings!.identities.isEmpty) { _initRoute = "/login"; } else { await oauth.refreshToken(); diff --git a/lib/pages/login.dart b/lib/pages/login.dart index 161c93f..9732336 100644 --- a/lib/pages/login.dart +++ b/lib/pages/login.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:localization/localization.dart'; import '../business_logic/auth/oauth.dart' as oauth; import '../global.dart' as global; +import '../business_logic/settings.dart' as settings; class Login extends StatefulWidget { const Login({Key? key}) : super(key: key); @@ -31,7 +32,7 @@ class LoginForm extends StatefulWidget { class _LoginFormState extends State { final formKey = GlobalKey(); - + String identity = ""; @override Widget build(BuildContext context) { return Form( @@ -45,7 +46,10 @@ class _LoginFormState extends State { Text("greeting".i18n(), style: Theme.of(context).textTheme.headline1), TextFormField( 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( labelText: "instance-url".i18n(), @@ -55,12 +59,22 @@ class _LoginFormState extends State { ), autofocus: true, ), + TextFormField( + onChanged: (value) { + identity = value; + }, + ), TextButton.icon( onPressed: () { bool isValid = formKey.currentState!.validate(); if (!isValid) { - ScaffoldMessenger.of(context).showSnackBar(SnackBar( - content: Text("login-failed-snackbar-text".i18n()))); + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text( + "login-failed-snackbar-text".i18n(), + ), + ), + ); } else { formKey.currentState?.save(); Navigator.push( diff --git a/lib/pages/settings/account.dart b/lib/pages/settings/account.dart index 682344e..7ed039b 100644 --- a/lib/pages/settings/account.dart +++ b/lib/pages/settings/account.dart @@ -20,12 +20,9 @@ class LogoutButton extends StatelessWidget { @override Widget build(BuildContext context) { - String url; - if (global.settings!.instanceUrl.isEmpty) { - url = "no-instance".i18n(); - } else { - url = global.settings!.instanceUrl; - } + String url = global + .settings!.identities[global.settings!.activeIdentity]!.instanceUrl; + return Row( children: [ Text(url), @@ -41,6 +38,6 @@ class LogoutButton extends StatelessWidget { } void logout() async { - await global.settings!.saveAuthCode(""); + //await global.settings!.saveAuthCode(""); exit(0); } diff --git a/lib/partials/main_scaffold.dart b/lib/partials/main_scaffold.dart index 3c514b2..8d143d3 100644 --- a/lib/partials/main_scaffold.dart +++ b/lib/partials/main_scaffold.dart @@ -5,6 +5,7 @@ import 'package:loris/pages/chat/chat.dart'; import 'package:loris/pages/notifications/notifications.dart'; import 'package:loris/pages/timeline/timeline.dart'; import 'package:loris/pages/settings/settings.dart'; +import '../business_logic/websocket.dart' as websocket; class MainScaffold extends StatefulWidget { const MainScaffold({Key? key}) : super(key: key); @@ -18,6 +19,7 @@ class _MainScaffoldState extends State { @override Widget build(BuildContext context) { + // websocket.connect(); final screens = [ const Timeline(), chat(context), diff --git a/lib/partials/post.dart b/lib/partials/post.dart index 031d084..0b44bb4 100644 --- a/lib/partials/post.dart +++ b/lib/partials/post.dart @@ -59,7 +59,9 @@ class DisplayName extends StatelessWidget { child: ProfilePic( url: account.avatar, )), - const SizedBox(width: 8), + const SizedBox( + width: 8, + ), Flexible( child: Column( mainAxisAlignment: MainAxisAlignment.spaceBetween, diff --git a/lib/themes/themes.dart b/lib/themes/themes.dart index cd1602e..2a05a8e 100644 --- a/lib/themes/themes.dart +++ b/lib/themes/themes.dart @@ -114,6 +114,7 @@ ThemeData getTheme(CustomColors colors) { fontWeight: FontWeight.w700, ), ), + tooltipTheme: TooltipThemeData(), ); } diff --git a/pubspec.lock b/pubspec.lock index 63d1604..eaef54c 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -43,6 +43,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.16.0" + crypto: + dependency: transitive + description: + name: crypto + url: "https://pub.dartlang.org" + source: hosted + version: "3.0.2" csslib: dependency: transitive description: @@ -404,6 +411,13 @@ packages: url: "https://pub.dartlang.org" source: hosted 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: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 17440f0..66a7a0e 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -43,6 +43,7 @@ dependencies: url_launcher: ^6.1.4 shelf: ^1.3.1 html: ^0.15.0 + web_socket_channel: ^2.2.0 dev_dependencies: flutter_test: