lemmur-app-android/lib/stores/accounts_store.dart

204 lines
5.7 KiB
Dart
Raw Normal View History

2020-09-02 10:00:08 +02:00
import 'dart:convert';
2020-09-01 13:22:37 +02:00
import 'package:lemmy_api_client/lemmy_api_client.dart';
import 'package:mobx/mobx.dart';
import 'package:shared_preferences/shared_preferences.dart';
part 'accounts_store.g.dart';
class AccountsStore extends _AccountsStore with _$AccountsStore {}
abstract class _AccountsStore with Store {
ReactionDisposer _saveReactionDisposer;
_AccountsStore() {
2020-09-02 15:16:33 +02:00
// persistently save settings each time they are changed
2020-09-02 10:00:08 +02:00
_saveReactionDisposer = reaction(
(_) => [
users.forEach((k, submap) =>
MapEntry(k, submap.forEach((k2, v2) => MapEntry(k2, v2)))),
tokens.forEach((k, submap) =>
MapEntry(k, submap.forEach((k2, v2) => MapEntry(k2, v2)))),
2020-09-02 10:00:08 +02:00
_defaultAccount,
_defaultAccounts.asObservable(),
],
(_) => save(),
2020-09-02 10:00:08 +02:00
);
2020-09-01 13:22:37 +02:00
}
void dispose() {
_saveReactionDisposer();
}
void load() async {
2020-09-16 23:22:04 +02:00
final prefs = await SharedPreferences.getInstance();
nestedMapsCast<T>(String key, T f(Map<String, dynamic> json)) =>
ObservableMap.of(
(jsonDecode(prefs.getString(key) ?? '{}') as Map<String, dynamic>)
?.map(
(k, e) => MapEntry(
k,
ObservableMap.of(
(e as Map<String, dynamic>)?.map(
(k, e) => MapEntry(
k, e == null ? null : f(e as Map<String, dynamic>)),
),
),
),
),
);
2020-09-01 13:22:37 +02:00
// set saved settings or create defaults
users = nestedMapsCast('users', (json) => User.fromJson(json));
tokens = nestedMapsCast('tokens', (json) => Jwt(json['raw']));
2020-09-02 10:00:08 +02:00
_defaultAccount = prefs.getString('defaultAccount');
_defaultAccounts = ObservableMap.of(Map.castFrom(
jsonDecode(prefs.getString('defaultAccounts') ?? 'null') ?? {}));
2020-09-01 13:22:37 +02:00
}
void save() async {
2020-09-16 23:22:04 +02:00
final prefs = await SharedPreferences.getInstance();
2020-09-02 15:16:33 +02:00
2020-09-02 10:00:08 +02:00
await prefs.setString('defaultAccount', _defaultAccount);
await prefs.setString('defaultAccounts', jsonEncode(_defaultAccounts));
2020-09-02 15:16:33 +02:00
await prefs.setString('users', jsonEncode(users));
await prefs.setString('tokens', jsonEncode(tokens));
2020-09-01 13:22:37 +02:00
}
/// if path to tokens map exists, it exists for users as well
/// `users['instanceUrl']['username']`
@observable
ObservableMap<String, ObservableMap<String, User>> users;
/// if path to users map exists, it exists for tokens as well
/// `tokens['instanceUrl']['username']`
@observable
ObservableMap<String, ObservableMap<String, Jwt>> tokens;
/// default account for a given instance
/// map where keys are instanceUrls and values are usernames
@observable
ObservableMap<String, String> _defaultAccounts;
/// default account for the app
/// username@instanceUrl
@observable
String _defaultAccount;
@computed
User get defaultUser {
if (_defaultAccount == null) {
return null;
}
2020-09-16 23:22:04 +02:00
final userTag = _defaultAccount.split('@');
2020-09-01 13:22:37 +02:00
return users[userTag[1]][userTag[0]];
}
@computed
Jwt get defaultToken {
if (_defaultAccount == null) {
return null;
}
2020-09-16 23:22:04 +02:00
final userTag = _defaultAccount.split('@');
2020-09-01 13:22:37 +02:00
return tokens[userTag[1]][userTag[0]];
}
User defaultUserFor(String instanceUrl) => Computed(() {
if (isAnonymousFor(instanceUrl)) {
return null;
}
return users[instanceUrl][_defaultAccounts[instanceUrl]];
}).value;
Jwt defaultTokenFor(String instanceUrl) => Computed(() {
if (isAnonymousFor(instanceUrl)) {
return null;
}
return tokens[instanceUrl][_defaultAccounts[instanceUrl]];
}).value;
2020-09-01 13:22:37 +02:00
@action
void setDefaultAccount(String instanceUrl, String username) {
_defaultAccount = '$username@$instanceUrl';
}
@action
void setDefaultAccountFor(String instanceUrl, String username) {
_defaultAccounts[instanceUrl] = username;
}
bool isAnonymousFor(String instanceUrl) => Computed(() {
if (!users.containsKey(instanceUrl)) {
return true;
}
return users[instanceUrl].isEmpty;
}).value;
@computed
bool get hasNoAccount => users.values.every((e) => e.isEmpty);
2020-09-01 13:22:37 +02:00
/// adds a new account
/// if it's the first account ever the account is
/// set as default for the app
/// if it's the first account for an instance the account is
/// set as default for that instance
@action
Future<void> addAccount(
String instanceUrl,
String usernameOrEmail,
String password,
) async {
if (!users.containsKey(instanceUrl)) {
throw Exception('No such instance was added');
}
2020-09-16 23:22:04 +02:00
final lemmy = LemmyApi(instanceUrl).v1;
2020-09-01 13:22:37 +02:00
2020-09-16 23:22:04 +02:00
final token = await lemmy.login(
2020-09-01 13:22:37 +02:00
usernameOrEmail: usernameOrEmail,
password: password,
);
2020-09-16 23:22:04 +02:00
final userData =
2020-09-01 13:22:37 +02:00
await lemmy.getSite(auth: token.raw).then((value) => value.myUser);
// first account for this instance
if (users[instanceUrl].isEmpty) {
// first account ever
if (hasNoAccount) {
2020-09-01 13:22:37 +02:00
setDefaultAccount(instanceUrl, userData.name);
}
setDefaultAccountFor(instanceUrl, userData.name);
}
users[instanceUrl][userData.name] = userData;
tokens[instanceUrl][userData.name] = token;
}
2020-09-08 00:34:09 +02:00
/// adds a new instance with no accounts associated with it.
/// Additionally makes a test GET /site request to check if the instance exists
@action
2020-09-08 00:34:09 +02:00
Future<void> addInstance(String instanceUrl) async {
if (users.containsKey(instanceUrl)) {
throw Exception('This instance has already been added');
}
2020-09-08 00:34:09 +02:00
try {
await LemmyApi(instanceUrl).v1.getSite();
// ignore: avoid_catches_without_on_clauses
} catch (_) {
throw Exception('This instance seems to not exist');
}
users[instanceUrl] = ObservableMap();
tokens[instanceUrl] = ObservableMap();
}
// TODO: add a way of removing accounts/instances
2020-09-01 13:22:37 +02:00
}