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(
|
|
|
|
(_) => [
|
2020-09-15 01:01:52 +02:00
|
|
|
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(),
|
|
|
|
],
|
2020-09-15 01:01:52 +02:00
|
|
|
(_) => 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();
|
2020-09-15 01:01:52 +02:00
|
|
|
|
|
|
|
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
|
2020-09-15 01:01:52 +02:00
|
|
|
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 {
|
2020-09-16 21:51:08 +02:00
|
|
|
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 {
|
2020-09-16 21:51:08 +02:00
|
|
|
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]];
|
|
|
|
}
|
|
|
|
|
2020-09-16 21:51:08 +02:00
|
|
|
User defaultUserFor(String instanceUrl) => Computed(() {
|
|
|
|
if (isAnonymousFor(instanceUrl)) {
|
|
|
|
return null;
|
|
|
|
}
|
2020-09-02 01:35:30 +02:00
|
|
|
|
2020-09-16 21:51:08 +02:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2020-09-16 01:10:46 +02:00
|
|
|
bool isAnonymousFor(String instanceUrl) => Computed(() {
|
2020-09-16 21:51:08 +02:00
|
|
|
if (!users.containsKey(instanceUrl)) {
|
2020-09-16 01:10:46 +02:00
|
|
|
return true;
|
|
|
|
}
|
2020-09-16 21:51:08 +02:00
|
|
|
|
|
|
|
return users[instanceUrl].isEmpty;
|
2020-09-16 01:10:46 +02:00
|
|
|
}).value;
|
2020-09-07 23:43:23 +02:00
|
|
|
|
2020-09-08 21:44:52 +02:00
|
|
|
@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 {
|
2020-09-07 23:43:23 +02:00
|
|
|
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);
|
|
|
|
|
2020-09-07 23:43:23 +02:00
|
|
|
// first account for this instance
|
|
|
|
if (users[instanceUrl].isEmpty) {
|
|
|
|
// first account ever
|
2020-09-08 21:44:52 +02:00
|
|
|
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-07 23:43:23 +02:00
|
|
|
|
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
|
2020-09-07 23:43:23 +02:00
|
|
|
@action
|
2020-09-08 00:34:09 +02:00
|
|
|
Future<void> addInstance(String instanceUrl) async {
|
2020-09-07 23:43:23 +02:00
|
|
|
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');
|
|
|
|
}
|
|
|
|
|
2020-09-07 23:43:23 +02:00
|
|
|
users[instanceUrl] = ObservableMap();
|
|
|
|
tokens[instanceUrl] = ObservableMap();
|
|
|
|
}
|
|
|
|
|
|
|
|
// TODO: add a way of removing accounts/instances
|
2020-09-01 13:22:37 +02:00
|
|
|
}
|