Merge pull request #79 from krawieck/experiment-change-notifier
This commit is contained in:
commit
7febc4b3bf
|
@ -1,20 +0,0 @@
|
|||
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
|
||||
/// Observes MobX observables in [fn] and returns the built value.
|
||||
/// When observable inside have changed, the hook rebuilds the value.
|
||||
/// The returned value can be ignored for a `useEffect(() { autorun(fn); }, [])`
|
||||
/// effect.
|
||||
T useObserved<T>(T Function() fn) {
|
||||
final returnValue = useState(useMemoized(fn));
|
||||
|
||||
useEffect(() {
|
||||
final disposer = autorun((_) {
|
||||
returnValue.value = fn();
|
||||
});
|
||||
|
||||
return disposer;
|
||||
}, []);
|
||||
|
||||
return returnValue.value;
|
||||
}
|
|
@ -5,4 +5,9 @@ import '../stores/accounts_store.dart';
|
|||
import '../stores/config_store.dart';
|
||||
|
||||
AccountsStore useAccountsStore() => useContext().watch<AccountsStore>();
|
||||
T useAccountsStoreSelect<T>(T selector(AccountsStore store)) =>
|
||||
useContext().select<AccountsStore, T>(selector);
|
||||
|
||||
ConfigStore useConfigStore() => useContext().watch<ConfigStore>();
|
||||
T useConfigStoreSelect<T>(T selector(ConfigStore store)) =>
|
||||
useContext().select<ConfigStore, T>(selector);
|
||||
|
|
|
@ -3,7 +3,6 @@ import 'dart:async';
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:flutter_mobx/flutter_mobx.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
import 'hooks/stores.dart';
|
||||
|
@ -26,13 +25,11 @@ Future<void> main() async {
|
|||
runApp(
|
||||
MultiProvider(
|
||||
providers: [
|
||||
Provider<ConfigStore>(
|
||||
create: (_) => configStore,
|
||||
dispose: (_, store) => store.dispose(),
|
||||
ChangeNotifierProvider.value(
|
||||
value: configStore,
|
||||
),
|
||||
Provider<AccountsStore>(
|
||||
create: (_) => accountsStore,
|
||||
dispose: (_, store) => store.dispose(),
|
||||
ChangeNotifierProvider.value(
|
||||
value: accountsStore,
|
||||
),
|
||||
],
|
||||
child: MyApp(),
|
||||
|
@ -44,28 +41,22 @@ class MyApp extends HookWidget {
|
|||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final configStore = useConfigStore();
|
||||
final maybeAmoledColor = configStore.amoledDarkMode ? Colors.black : null;
|
||||
|
||||
return Observer(
|
||||
builder: (ctx) {
|
||||
final maybeAmoledColor =
|
||||
configStore.amoledDarkMode ? Colors.black : null;
|
||||
|
||||
return MaterialApp(
|
||||
title: 'Lemmur',
|
||||
themeMode: configStore.theme,
|
||||
darkTheme: ThemeData.dark().copyWith(
|
||||
scaffoldBackgroundColor: maybeAmoledColor,
|
||||
backgroundColor: maybeAmoledColor,
|
||||
canvasColor: maybeAmoledColor,
|
||||
cardColor: maybeAmoledColor,
|
||||
splashColor: maybeAmoledColor,
|
||||
),
|
||||
theme: ThemeData(
|
||||
visualDensity: VisualDensity.adaptivePlatformDensity,
|
||||
),
|
||||
home: MyHomePage(),
|
||||
);
|
||||
},
|
||||
return MaterialApp(
|
||||
title: 'Lemmur',
|
||||
themeMode: configStore.theme,
|
||||
darkTheme: ThemeData.dark().copyWith(
|
||||
scaffoldBackgroundColor: maybeAmoledColor,
|
||||
backgroundColor: maybeAmoledColor,
|
||||
canvasColor: maybeAmoledColor,
|
||||
cardColor: maybeAmoledColor,
|
||||
splashColor: maybeAmoledColor,
|
||||
),
|
||||
theme: ThemeData(
|
||||
visualDensity: VisualDensity.adaptivePlatformDensity,
|
||||
),
|
||||
home: MyHomePage(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:flutter_mobx/flutter_mobx.dart';
|
||||
|
||||
import '../hooks/stores.dart';
|
||||
import '../util/goto.dart';
|
||||
|
@ -19,107 +18,101 @@ class UserProfileTab extends HookWidget {
|
|||
final theme = Theme.of(context);
|
||||
final accountsStore = useAccountsStore();
|
||||
|
||||
return Observer(
|
||||
builder: (ctx) {
|
||||
if (accountsStore.hasNoAccount) {
|
||||
return Scaffold(
|
||||
body: Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Text('No account was added.'),
|
||||
FlatButton.icon(
|
||||
onPressed: () {
|
||||
goTo(context, (_) => AccountsConfigPage());
|
||||
},
|
||||
icon: Icon(Icons.add),
|
||||
label: Text('Add account'),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return Scaffold(
|
||||
extendBodyBehindAppBar: true,
|
||||
// TODO: this is not visible in light mode when the sliver app bar
|
||||
// in UserProfile is folded
|
||||
appBar: AppBar(
|
||||
backgroundColor: Colors.transparent,
|
||||
shadowColor: Colors.transparent,
|
||||
centerTitle: true,
|
||||
title: FlatButton(
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Text(
|
||||
// TODO: fix overflow issues
|
||||
'@${accountsStore.defaultUsername}',
|
||||
style: theme.primaryTextTheme.headline6,
|
||||
overflow: TextOverflow.fade,
|
||||
),
|
||||
Icon(
|
||||
Icons.expand_more,
|
||||
color: theme.primaryIconTheme.color,
|
||||
),
|
||||
],
|
||||
),
|
||||
onPressed: () {
|
||||
showModalBottomSheet(
|
||||
context: context,
|
||||
backgroundColor: Colors.transparent,
|
||||
builder: (_) {
|
||||
final userTags = <String>[];
|
||||
|
||||
accountsStore.tokens.forEach((instanceUrl, value) {
|
||||
value.forEach((username, _) {
|
||||
userTags.add('$username@$instanceUrl');
|
||||
});
|
||||
});
|
||||
|
||||
return Observer(
|
||||
builder: (ctx) => BottomModal(
|
||||
title: 'account',
|
||||
child: Column(
|
||||
children: [
|
||||
for (final tag in userTags)
|
||||
RadioListTile<String>(
|
||||
value: tag,
|
||||
title: Text(tag),
|
||||
groupValue: '${accountsStore.defaultUsername}'
|
||||
'@${accountsStore.defaultInstanceUrl}',
|
||||
onChanged: (selected) {
|
||||
final userTag = selected.split('@');
|
||||
accountsStore.setDefaultAccount(
|
||||
userTag[1], userTag[0]);
|
||||
Navigator.of(ctx).pop();
|
||||
},
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
actions: [
|
||||
IconButton(
|
||||
icon: Icon(Icons.settings),
|
||||
if (accountsStore.hasNoAccount) {
|
||||
return Scaffold(
|
||||
body: Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Text('No account was added.'),
|
||||
FlatButton.icon(
|
||||
onPressed: () {
|
||||
goTo(context, (_) => SettingsPage());
|
||||
goTo(context, (_) => AccountsConfigPage());
|
||||
},
|
||||
icon: Icon(Icons.add),
|
||||
label: Text('Add account'),
|
||||
)
|
||||
],
|
||||
),
|
||||
body: UserProfile(
|
||||
userId: accountsStore.defaultToken.payload.id,
|
||||
instanceUrl: accountsStore.defaultInstanceUrl,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return Scaffold(
|
||||
extendBodyBehindAppBar: true,
|
||||
// TODO: this is not visible in light mode when the sliver app bar
|
||||
// in UserProfile is folded
|
||||
appBar: AppBar(
|
||||
backgroundColor: Colors.transparent,
|
||||
shadowColor: Colors.transparent,
|
||||
centerTitle: true,
|
||||
title: FlatButton(
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Text(
|
||||
// TODO: fix overflow issues
|
||||
'@${accountsStore.defaultUsername}',
|
||||
style: theme.primaryTextTheme.headline6,
|
||||
overflow: TextOverflow.fade,
|
||||
),
|
||||
Icon(
|
||||
Icons.expand_more,
|
||||
color: theme.primaryIconTheme.color,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
onPressed: () {
|
||||
showModalBottomSheet(
|
||||
context: context,
|
||||
backgroundColor: Colors.transparent,
|
||||
builder: (ctx) {
|
||||
final userTags = <String>[];
|
||||
|
||||
accountsStore.tokens.forEach((instanceUrl, value) {
|
||||
value.forEach((username, _) {
|
||||
userTags.add('$username@$instanceUrl');
|
||||
});
|
||||
});
|
||||
|
||||
return BottomModal(
|
||||
title: 'account',
|
||||
child: Column(
|
||||
children: [
|
||||
for (final tag in userTags)
|
||||
RadioListTile<String>(
|
||||
value: tag,
|
||||
title: Text(tag),
|
||||
groupValue: '${accountsStore.defaultUsername}'
|
||||
'@${accountsStore.defaultInstanceUrl}',
|
||||
onChanged: (selected) {
|
||||
final userTag = selected.split('@');
|
||||
accountsStore.setDefaultAccount(
|
||||
userTag[1], userTag[0]);
|
||||
Navigator.of(ctx).pop();
|
||||
},
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
actions: [
|
||||
IconButton(
|
||||
icon: Icon(Icons.settings),
|
||||
onPressed: () {
|
||||
goTo(context, (_) => SettingsPage());
|
||||
},
|
||||
)
|
||||
],
|
||||
),
|
||||
body: UserProfile(
|
||||
userId: accountsStore.defaultToken.payload.id,
|
||||
instanceUrl: accountsStore.defaultInstanceUrl,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:flutter_mobx/flutter_mobx.dart';
|
||||
import 'package:flutter_slidable/flutter_slidable.dart';
|
||||
import 'package:flutter_speed_dial/flutter_speed_dial.dart';
|
||||
|
||||
|
@ -67,27 +66,25 @@ class AppearanceConfigPage extends HookWidget {
|
|||
title: Text('Appearance', style: theme.textTheme.headline6),
|
||||
centerTitle: true,
|
||||
),
|
||||
body: Observer(
|
||||
builder: (ctx) => ListView(
|
||||
children: [
|
||||
_SectionHeading('Theme'),
|
||||
for (final theme in ThemeMode.values)
|
||||
RadioListTile<ThemeMode>(
|
||||
value: theme,
|
||||
title: Text(theme.toString().split('.')[1]),
|
||||
groupValue: configStore.theme,
|
||||
onChanged: (selected) {
|
||||
configStore.theme = selected;
|
||||
},
|
||||
),
|
||||
SwitchListTile(
|
||||
title: Text('AMOLED dark mode'),
|
||||
value: configStore.amoledDarkMode,
|
||||
onChanged: (checked) {
|
||||
configStore.amoledDarkMode = checked;
|
||||
})
|
||||
],
|
||||
),
|
||||
body: ListView(
|
||||
children: [
|
||||
_SectionHeading('Theme'),
|
||||
for (final theme in ThemeMode.values)
|
||||
RadioListTile<ThemeMode>(
|
||||
value: theme,
|
||||
title: Text(theme.toString().split('.')[1]),
|
||||
groupValue: configStore.theme,
|
||||
onChanged: (selected) {
|
||||
configStore.theme = selected;
|
||||
},
|
||||
),
|
||||
SwitchListTile(
|
||||
title: Text('AMOLED dark mode'),
|
||||
value: configStore.amoledDarkMode,
|
||||
onChanged: (checked) {
|
||||
configStore.amoledDarkMode = checked;
|
||||
})
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
@ -185,100 +182,92 @@ class AccountsConfigPage extends HookWidget {
|
|||
),
|
||||
],
|
||||
),
|
||||
body: Observer(
|
||||
builder: (ctx) {
|
||||
final theme = Theme.of(context);
|
||||
|
||||
return ListView(
|
||||
children: [
|
||||
if (accountsStore.tokens.isEmpty)
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 100),
|
||||
child: FlatButton.icon(
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
),
|
||||
onPressed: () => showCupertinoModalPopup(
|
||||
context: context,
|
||||
builder: (_) => AddInstancePage(),
|
||||
),
|
||||
icon: Icon(Icons.add),
|
||||
label: Text('Add instance')),
|
||||
),
|
||||
],
|
||||
),
|
||||
for (final entry in accountsStore.tokens.entries) ...[
|
||||
SizedBox(height: 40),
|
||||
Slidable(
|
||||
actionPane: SlidableBehindActionPane(),
|
||||
secondaryActions: [
|
||||
IconSlideAction(
|
||||
closeOnTap: true,
|
||||
onTap: () => removeInstanceDialog(entry.key),
|
||||
icon: Icons.delete_sweep,
|
||||
color: Colors.red,
|
||||
),
|
||||
],
|
||||
key: Key(entry.key),
|
||||
child: Container(
|
||||
color: theme.canvasColor,
|
||||
child: ListTile(
|
||||
dense: true,
|
||||
contentPadding: EdgeInsets.only(left: 0, top: 0),
|
||||
title: _SectionHeading(entry.key),
|
||||
),
|
||||
),
|
||||
),
|
||||
for (final username in entry.value.keys) ...[
|
||||
Slidable(
|
||||
actionPane: SlidableBehindActionPane(),
|
||||
key: Key('$username@${entry.key}'),
|
||||
secondaryActions: [
|
||||
IconSlideAction(
|
||||
closeOnTap: true,
|
||||
onTap: () => removeUserDialog(entry.key, username),
|
||||
icon: Icons.delete_sweep,
|
||||
color: Colors.red,
|
||||
body: ListView(
|
||||
children: [
|
||||
if (accountsStore.tokens.isEmpty)
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 100),
|
||||
child: FlatButton.icon(
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
),
|
||||
],
|
||||
child: Container(
|
||||
decoration: BoxDecoration(color: theme.canvasColor),
|
||||
child: ListTile(
|
||||
trailing: username ==
|
||||
accountsStore.defaultUsernameFor(entry.key)
|
||||
onPressed: () => showCupertinoModalPopup(
|
||||
context: context,
|
||||
builder: (_) => AddInstancePage(),
|
||||
),
|
||||
icon: Icon(Icons.add),
|
||||
label: Text('Add instance')),
|
||||
),
|
||||
],
|
||||
),
|
||||
for (final entry in accountsStore.tokens.entries) ...[
|
||||
SizedBox(height: 40),
|
||||
Slidable(
|
||||
actionPane: SlidableBehindActionPane(),
|
||||
secondaryActions: [
|
||||
IconSlideAction(
|
||||
closeOnTap: true,
|
||||
onTap: () => removeInstanceDialog(entry.key),
|
||||
icon: Icons.delete_sweep,
|
||||
color: Colors.red,
|
||||
),
|
||||
],
|
||||
key: Key(entry.key),
|
||||
child: Container(
|
||||
color: theme.canvasColor,
|
||||
child: ListTile(
|
||||
dense: true,
|
||||
contentPadding: EdgeInsets.only(left: 0, top: 0),
|
||||
title: _SectionHeading(entry.key),
|
||||
),
|
||||
),
|
||||
),
|
||||
for (final username in entry.value.keys) ...[
|
||||
Slidable(
|
||||
actionPane: SlidableBehindActionPane(),
|
||||
key: Key('$username@${entry.key}'),
|
||||
secondaryActions: [
|
||||
IconSlideAction(
|
||||
closeOnTap: true,
|
||||
onTap: () => removeUserDialog(entry.key, username),
|
||||
icon: Icons.delete_sweep,
|
||||
color: Colors.red,
|
||||
),
|
||||
],
|
||||
child: Container(
|
||||
decoration: BoxDecoration(color: theme.canvasColor),
|
||||
child: ListTile(
|
||||
trailing:
|
||||
username == accountsStore.defaultUsernameFor(entry.key)
|
||||
? Icon(
|
||||
Icons.check_circle_outline,
|
||||
color: theme.accentColor,
|
||||
)
|
||||
: null,
|
||||
title: Text(username),
|
||||
onLongPress: () {
|
||||
accountsStore.setDefaultAccountFor(
|
||||
entry.key, username);
|
||||
},
|
||||
onTap: () {}, // TODO: go to managing account
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
if (entry.value.keys.isEmpty)
|
||||
ListTile(
|
||||
leading: Icon(Icons.add),
|
||||
title: Text('Add account'),
|
||||
onTap: () {
|
||||
showCupertinoModalPopup(
|
||||
context: context,
|
||||
builder: (_) =>
|
||||
AddAccountPage(instanceUrl: entry.key));
|
||||
title: Text(username),
|
||||
onLongPress: () {
|
||||
accountsStore.setDefaultAccountFor(entry.key, username);
|
||||
},
|
||||
onTap: () {}, // TODO: go to managing account
|
||||
),
|
||||
]
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
if (entry.value.keys.isEmpty)
|
||||
ListTile(
|
||||
leading: Icon(Icons.add),
|
||||
title: Text('Add account'),
|
||||
onTap: () {
|
||||
showCupertinoModalPopup(
|
||||
context: context,
|
||||
builder: (_) => AddAccountPage(instanceUrl: entry.key));
|
||||
},
|
||||
),
|
||||
]
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,41 +1,70 @@
|
|||
import 'dart:collection';
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
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';
|
||||
import 'shared_pref_keys.dart';
|
||||
|
||||
/// Store that manages all accounts
|
||||
class AccountsStore extends _AccountsStore with _$AccountsStore {}
|
||||
class AccountsStore extends ChangeNotifier {
|
||||
/// Map containing JWT tokens of specific users.
|
||||
/// If a token is in this map, the user is considered logged in
|
||||
/// for that account.
|
||||
/// `tokens['instanceUrl']['username']`
|
||||
HashMap<String, HashMap<String, Jwt>> get tokens => _tokens;
|
||||
HashMap<String, HashMap<String, Jwt>> _tokens;
|
||||
|
||||
abstract class _AccountsStore with Store {
|
||||
ReactionDisposer _saveReactionDisposer;
|
||||
ReactionDisposer _pickDefaultsDisposer;
|
||||
/// default account for a given instance
|
||||
/// map where keys are instanceUrls and values are usernames
|
||||
HashMap<String, String> _defaultAccounts;
|
||||
|
||||
_AccountsStore() {
|
||||
// persistently save settings each time they are changed
|
||||
_saveReactionDisposer = reaction(
|
||||
(_) => [
|
||||
tokens.forEach((k, submap) =>
|
||||
MapEntry(k, submap.forEach((k2, v2) => MapEntry(k2, v2)))),
|
||||
_defaultAccount,
|
||||
_defaultAccounts.asObservable(),
|
||||
],
|
||||
(_) => save(),
|
||||
);
|
||||
/// default account for the app
|
||||
/// It is in a form of `username@instanceUrl`
|
||||
String _defaultAccount;
|
||||
|
||||
// automatically set new default accounts when accounts are added/removed
|
||||
_pickDefaultsDisposer = reaction(
|
||||
(_) => [
|
||||
tokens.forEach((k, submap) =>
|
||||
MapEntry(k, submap.forEach((k2, v2) => MapEntry(k2, v2)))),
|
||||
],
|
||||
(_) => _assignDefaultAccounts(),
|
||||
);
|
||||
Future<void> load() async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
|
||||
// I barely understand what I did. Long story short it casts a
|
||||
// raw json into a nested ObservableMap
|
||||
nestedMapsCast<T>(T f(Map<String, dynamic> json)) => HashMap.of(
|
||||
(jsonDecode(prefs.getString(SharedPrefKeys.tokens) ?? '{}')
|
||||
as Map<String, dynamic>)
|
||||
?.map(
|
||||
(k, e) => MapEntry(
|
||||
k,
|
||||
HashMap.of(
|
||||
(e as Map<String, dynamic>)?.map(
|
||||
(k, e) => MapEntry(
|
||||
k, e == null ? null : f(e as Map<String, dynamic>)),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
// set saved settings or create defaults
|
||||
_tokens = nestedMapsCast((json) => Jwt(json['raw']));
|
||||
_defaultAccount = prefs.getString(SharedPrefKeys.defaultAccount);
|
||||
_defaultAccounts = HashMap.of(Map.castFrom(
|
||||
jsonDecode(prefs.getString(SharedPrefKeys.defaultAccounts) ?? 'null') ??
|
||||
{}));
|
||||
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
@action
|
||||
Future<void> save() async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
|
||||
await prefs.setString(SharedPrefKeys.defaultAccount, _defaultAccount);
|
||||
await prefs.setString(
|
||||
SharedPrefKeys.defaultAccounts, jsonEncode(_defaultAccounts));
|
||||
await prefs.setString(SharedPrefKeys.tokens, jsonEncode(tokens));
|
||||
}
|
||||
|
||||
/// automatically sets default accounts
|
||||
void _assignDefaultAccounts() {
|
||||
// remove dangling defaults
|
||||
_defaultAccounts.entries.map((dft) {
|
||||
|
@ -80,65 +109,6 @@ abstract class _AccountsStore with Store {
|
|||
}
|
||||
}
|
||||
|
||||
void dispose() {
|
||||
_saveReactionDisposer();
|
||||
_pickDefaultsDisposer();
|
||||
}
|
||||
|
||||
void load() async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
|
||||
// I barely understand what I did. Long story short it casts a
|
||||
// raw json into a nested ObservableMap
|
||||
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>)),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
// set saved settings or create defaults
|
||||
tokens = nestedMapsCast('tokens', (json) => Jwt(json['raw']));
|
||||
_defaultAccount = prefs.getString('defaultAccount');
|
||||
_defaultAccounts = ObservableMap.of(Map.castFrom(
|
||||
jsonDecode(prefs.getString('defaultAccounts') ?? 'null') ?? {}));
|
||||
}
|
||||
|
||||
void save() async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
|
||||
await prefs.setString('defaultAccount', _defaultAccount);
|
||||
await prefs.setString('defaultAccounts', jsonEncode(_defaultAccounts));
|
||||
await prefs.setString('tokens', jsonEncode(tokens));
|
||||
}
|
||||
|
||||
/// Map containing JWT tokens of specific users.
|
||||
/// If a token is in this map, the user is considered logged in
|
||||
/// for that account.
|
||||
/// `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
|
||||
/// It is in a form of `username@instanceUrl`
|
||||
@observable
|
||||
String _defaultAccount;
|
||||
|
||||
@computed
|
||||
String get defaultUsername {
|
||||
if (_defaultAccount == null) {
|
||||
return null;
|
||||
|
@ -147,7 +117,6 @@ abstract class _AccountsStore with Store {
|
|||
return _defaultAccount.split('@')[0];
|
||||
}
|
||||
|
||||
@computed
|
||||
String get defaultInstanceUrl {
|
||||
if (_defaultAccount == null) {
|
||||
return null;
|
||||
|
@ -156,15 +125,14 @@ abstract class _AccountsStore with Store {
|
|||
return _defaultAccount.split('@')[1];
|
||||
}
|
||||
|
||||
String defaultUsernameFor(String instanceUrl) => Computed(() {
|
||||
if (isAnonymousFor(instanceUrl)) {
|
||||
return null;
|
||||
}
|
||||
String defaultUsernameFor(String instanceUrl) {
|
||||
if (isAnonymousFor(instanceUrl)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return _defaultAccounts[instanceUrl];
|
||||
}).value;
|
||||
return _defaultAccounts[instanceUrl];
|
||||
}
|
||||
|
||||
@computed
|
||||
Jwt get defaultToken {
|
||||
if (_defaultAccount == null) {
|
||||
return null;
|
||||
|
@ -174,44 +142,45 @@ abstract class _AccountsStore with Store {
|
|||
return tokens[userTag[1]][userTag[0]];
|
||||
}
|
||||
|
||||
Jwt defaultTokenFor(String instanceUrl) => Computed(() {
|
||||
if (isAnonymousFor(instanceUrl)) {
|
||||
return null;
|
||||
}
|
||||
Jwt defaultTokenFor(String instanceUrl) {
|
||||
if (isAnonymousFor(instanceUrl)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return tokens[instanceUrl][_defaultAccounts[instanceUrl]];
|
||||
}).value;
|
||||
return tokens[instanceUrl][_defaultAccounts[instanceUrl]];
|
||||
}
|
||||
|
||||
/// sets globally default account
|
||||
@action
|
||||
void setDefaultAccount(String instanceUrl, String username) {
|
||||
_defaultAccount = '$username@$instanceUrl';
|
||||
|
||||
notifyListeners();
|
||||
save();
|
||||
}
|
||||
|
||||
/// sets default account for given instance
|
||||
@action
|
||||
void setDefaultAccountFor(String instanceUrl, String username) {
|
||||
_defaultAccounts[instanceUrl] = username;
|
||||
|
||||
notifyListeners();
|
||||
save();
|
||||
}
|
||||
|
||||
/// An instance is considered anonymous if it was not
|
||||
/// added or there are no accounts assigned to it.
|
||||
bool isAnonymousFor(String instanceUrl) => Computed(() {
|
||||
if (!instances.contains(instanceUrl)) {
|
||||
return true;
|
||||
}
|
||||
bool isAnonymousFor(String instanceUrl) {
|
||||
if (!instances.contains(instanceUrl)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return tokens[instanceUrl].isEmpty;
|
||||
}).value;
|
||||
return tokens[instanceUrl].isEmpty;
|
||||
}
|
||||
|
||||
/// `true` if no added instance has an account assigned to it
|
||||
@computed
|
||||
bool get hasNoAccount => loggedInInstances.isEmpty;
|
||||
|
||||
@computed
|
||||
Iterable<String> get instances => tokens.keys;
|
||||
|
||||
@computed
|
||||
Iterable<String> get loggedInInstances =>
|
||||
instances.where((e) => !isAnonymousFor(e));
|
||||
|
||||
|
@ -220,7 +189,6 @@ abstract class _AccountsStore with Store {
|
|||
/// 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,
|
||||
|
@ -240,12 +208,15 @@ abstract class _AccountsStore with Store {
|
|||
await lemmy.getSite(auth: token.raw).then((value) => value.myUser);
|
||||
|
||||
tokens[instanceUrl][userData.name] = token;
|
||||
|
||||
_assignDefaultAccounts();
|
||||
notifyListeners();
|
||||
save();
|
||||
}
|
||||
|
||||
/// adds a new instance with no accounts associated with it.
|
||||
/// Additionally makes a test `GET /site` request to check if the instance exists.
|
||||
/// Check is skipped when [assumeValid] is `true`
|
||||
@action
|
||||
Future<void> addInstance(
|
||||
String instanceUrl, {
|
||||
bool assumeValid = false,
|
||||
|
@ -263,17 +234,27 @@ abstract class _AccountsStore with Store {
|
|||
}
|
||||
}
|
||||
|
||||
tokens[instanceUrl] = ObservableMap();
|
||||
tokens[instanceUrl] = HashMap();
|
||||
|
||||
_assignDefaultAccounts();
|
||||
notifyListeners();
|
||||
save();
|
||||
}
|
||||
|
||||
/// This also removes all accounts assigned to this instance
|
||||
@action
|
||||
void removeInstance(String instanceUrl) {
|
||||
tokens.remove(instanceUrl);
|
||||
|
||||
_assignDefaultAccounts();
|
||||
notifyListeners();
|
||||
save();
|
||||
}
|
||||
|
||||
@action
|
||||
void removeAccount(String instanceUrl, String username) {
|
||||
tokens[instanceUrl].remove(username);
|
||||
|
||||
_assignDefaultAccounts();
|
||||
notifyListeners();
|
||||
save();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,187 +0,0 @@
|
|||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'accounts_store.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// StoreGenerator
|
||||
// **************************************************************************
|
||||
|
||||
// ignore_for_file: non_constant_identifier_names, unnecessary_brace_in_string_interps, unnecessary_lambdas, prefer_expression_function_bodies, lines_longer_than_80_chars, avoid_as, avoid_annotating_with_dynamic
|
||||
|
||||
mixin _$AccountsStore on _AccountsStore, Store {
|
||||
Computed<String> _$defaultUsernameComputed;
|
||||
|
||||
@override
|
||||
String get defaultUsername => (_$defaultUsernameComputed ??= Computed<String>(
|
||||
() => super.defaultUsername,
|
||||
name: '_AccountsStore.defaultUsername'))
|
||||
.value;
|
||||
Computed<String> _$defaultInstanceUrlComputed;
|
||||
|
||||
@override
|
||||
String get defaultInstanceUrl => (_$defaultInstanceUrlComputed ??=
|
||||
Computed<String>(() => super.defaultInstanceUrl,
|
||||
name: '_AccountsStore.defaultInstanceUrl'))
|
||||
.value;
|
||||
Computed<Jwt> _$defaultTokenComputed;
|
||||
|
||||
@override
|
||||
Jwt get defaultToken =>
|
||||
(_$defaultTokenComputed ??= Computed<Jwt>(() => super.defaultToken,
|
||||
name: '_AccountsStore.defaultToken'))
|
||||
.value;
|
||||
Computed<bool> _$hasNoAccountComputed;
|
||||
|
||||
@override
|
||||
bool get hasNoAccount =>
|
||||
(_$hasNoAccountComputed ??= Computed<bool>(() => super.hasNoAccount,
|
||||
name: '_AccountsStore.hasNoAccount'))
|
||||
.value;
|
||||
Computed<Iterable<String>> _$instancesComputed;
|
||||
|
||||
@override
|
||||
Iterable<String> get instances =>
|
||||
(_$instancesComputed ??= Computed<Iterable<String>>(() => super.instances,
|
||||
name: '_AccountsStore.instances'))
|
||||
.value;
|
||||
Computed<Iterable<String>> _$loggedInInstancesComputed;
|
||||
|
||||
@override
|
||||
Iterable<String> get loggedInInstances => (_$loggedInInstancesComputed ??=
|
||||
Computed<Iterable<String>>(() => super.loggedInInstances,
|
||||
name: '_AccountsStore.loggedInInstances'))
|
||||
.value;
|
||||
|
||||
final _$tokensAtom = Atom(name: '_AccountsStore.tokens');
|
||||
|
||||
@override
|
||||
ObservableMap<String, ObservableMap<String, Jwt>> get tokens {
|
||||
_$tokensAtom.reportRead();
|
||||
return super.tokens;
|
||||
}
|
||||
|
||||
@override
|
||||
set tokens(ObservableMap<String, ObservableMap<String, Jwt>> value) {
|
||||
_$tokensAtom.reportWrite(value, super.tokens, () {
|
||||
super.tokens = value;
|
||||
});
|
||||
}
|
||||
|
||||
final _$_defaultAccountsAtom = Atom(name: '_AccountsStore._defaultAccounts');
|
||||
|
||||
@override
|
||||
ObservableMap<String, String> get _defaultAccounts {
|
||||
_$_defaultAccountsAtom.reportRead();
|
||||
return super._defaultAccounts;
|
||||
}
|
||||
|
||||
@override
|
||||
set _defaultAccounts(ObservableMap<String, String> value) {
|
||||
_$_defaultAccountsAtom.reportWrite(value, super._defaultAccounts, () {
|
||||
super._defaultAccounts = value;
|
||||
});
|
||||
}
|
||||
|
||||
final _$_defaultAccountAtom = Atom(name: '_AccountsStore._defaultAccount');
|
||||
|
||||
@override
|
||||
String get _defaultAccount {
|
||||
_$_defaultAccountAtom.reportRead();
|
||||
return super._defaultAccount;
|
||||
}
|
||||
|
||||
@override
|
||||
set _defaultAccount(String value) {
|
||||
_$_defaultAccountAtom.reportWrite(value, super._defaultAccount, () {
|
||||
super._defaultAccount = value;
|
||||
});
|
||||
}
|
||||
|
||||
final _$addAccountAsyncAction = AsyncAction('_AccountsStore.addAccount');
|
||||
|
||||
@override
|
||||
Future<void> addAccount(
|
||||
String instanceUrl, String usernameOrEmail, String password) {
|
||||
return _$addAccountAsyncAction
|
||||
.run(() => super.addAccount(instanceUrl, usernameOrEmail, password));
|
||||
}
|
||||
|
||||
final _$addInstanceAsyncAction = AsyncAction('_AccountsStore.addInstance');
|
||||
|
||||
@override
|
||||
Future<void> addInstance(String instanceUrl, {bool assumeValid = false}) {
|
||||
return _$addInstanceAsyncAction
|
||||
.run(() => super.addInstance(instanceUrl, assumeValid: assumeValid));
|
||||
}
|
||||
|
||||
final _$_AccountsStoreActionController =
|
||||
ActionController(name: '_AccountsStore');
|
||||
|
||||
@override
|
||||
void _assignDefaultAccounts() {
|
||||
final _$actionInfo = _$_AccountsStoreActionController.startAction(
|
||||
name: '_AccountsStore._assignDefaultAccounts');
|
||||
try {
|
||||
return super._assignDefaultAccounts();
|
||||
} finally {
|
||||
_$_AccountsStoreActionController.endAction(_$actionInfo);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void setDefaultAccount(String instanceUrl, String username) {
|
||||
final _$actionInfo = _$_AccountsStoreActionController.startAction(
|
||||
name: '_AccountsStore.setDefaultAccount');
|
||||
try {
|
||||
return super.setDefaultAccount(instanceUrl, username);
|
||||
} finally {
|
||||
_$_AccountsStoreActionController.endAction(_$actionInfo);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void setDefaultAccountFor(String instanceUrl, String username) {
|
||||
final _$actionInfo = _$_AccountsStoreActionController.startAction(
|
||||
name: '_AccountsStore.setDefaultAccountFor');
|
||||
try {
|
||||
return super.setDefaultAccountFor(instanceUrl, username);
|
||||
} finally {
|
||||
_$_AccountsStoreActionController.endAction(_$actionInfo);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void removeInstance(String instanceUrl) {
|
||||
final _$actionInfo = _$_AccountsStoreActionController.startAction(
|
||||
name: '_AccountsStore.removeInstance');
|
||||
try {
|
||||
return super.removeInstance(instanceUrl);
|
||||
} finally {
|
||||
_$_AccountsStoreActionController.endAction(_$actionInfo);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void removeAccount(String instanceUrl, String username) {
|
||||
final _$actionInfo = _$_AccountsStoreActionController.startAction(
|
||||
name: '_AccountsStore.removeAccount');
|
||||
try {
|
||||
return super.removeAccount(instanceUrl, username);
|
||||
} finally {
|
||||
_$_AccountsStoreActionController.endAction(_$actionInfo);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return '''
|
||||
tokens: ${tokens},
|
||||
defaultUsername: ${defaultUsername},
|
||||
defaultInstanceUrl: ${defaultInstanceUrl},
|
||||
defaultToken: ${defaultToken},
|
||||
hasNoAccount: ${hasNoAccount},
|
||||
instances: ${instances},
|
||||
loggedInInstances: ${loggedInInstances}
|
||||
''';
|
||||
}
|
||||
}
|
|
@ -1,46 +1,42 @@
|
|||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
|
||||
part 'config_store.g.dart';
|
||||
import 'shared_pref_keys.dart';
|
||||
|
||||
/// Store managing user-level configuration such as theme or language
|
||||
class ConfigStore extends _ConfigStore with _$ConfigStore {}
|
||||
|
||||
abstract class _ConfigStore with Store {
|
||||
ReactionDisposer _saveReactionDisposer;
|
||||
|
||||
_ConfigStore() {
|
||||
// persitently save settings each time they are changed
|
||||
_saveReactionDisposer = reaction((_) => [theme, amoledDarkMode], (_) {
|
||||
save();
|
||||
});
|
||||
class ConfigStore extends ChangeNotifier {
|
||||
ThemeMode _theme;
|
||||
ThemeMode get theme => _theme;
|
||||
set theme(ThemeMode theme) {
|
||||
_theme = theme;
|
||||
notifyListeners();
|
||||
save();
|
||||
}
|
||||
|
||||
void dispose() {
|
||||
_saveReactionDisposer();
|
||||
bool _amoledDarkMode;
|
||||
bool get amoledDarkMode => _amoledDarkMode;
|
||||
set amoledDarkMode(bool amoledDarkMode) {
|
||||
_amoledDarkMode = amoledDarkMode;
|
||||
notifyListeners();
|
||||
save();
|
||||
}
|
||||
|
||||
void load() async {
|
||||
Future<void> load() async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
// load saved settings or create defaults
|
||||
theme = _themeModeFromString(prefs.getString('theme') ?? 'system');
|
||||
amoledDarkMode = prefs.getBool('amoledDarkMode') ?? false;
|
||||
theme =
|
||||
_themeModeFromString(prefs.getString(SharedPrefKeys.theme) ?? 'system');
|
||||
amoledDarkMode = prefs.getBool(SharedPrefKeys.amoledDarkMode) ?? false;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
void save() async {
|
||||
Future<void> save() async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
|
||||
await prefs.setString('theme', describeEnum(theme));
|
||||
await prefs.setBool('amoledDarkMode', amoledDarkMode);
|
||||
await prefs.setString(SharedPrefKeys.theme, describeEnum(theme));
|
||||
await prefs.setBool(SharedPrefKeys.amoledDarkMode, amoledDarkMode);
|
||||
}
|
||||
|
||||
@observable
|
||||
ThemeMode theme;
|
||||
|
||||
@observable
|
||||
bool amoledDarkMode;
|
||||
}
|
||||
|
||||
/// converts string to ThemeMode
|
||||
|
|
|
@ -1,49 +0,0 @@
|
|||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'config_store.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// StoreGenerator
|
||||
// **************************************************************************
|
||||
|
||||
// ignore_for_file: non_constant_identifier_names, unnecessary_brace_in_string_interps, unnecessary_lambdas, prefer_expression_function_bodies, lines_longer_than_80_chars, avoid_as, avoid_annotating_with_dynamic
|
||||
|
||||
mixin _$ConfigStore on _ConfigStore, Store {
|
||||
final _$themeAtom = Atom(name: '_ConfigStore.theme');
|
||||
|
||||
@override
|
||||
ThemeMode get theme {
|
||||
_$themeAtom.reportRead();
|
||||
return super.theme;
|
||||
}
|
||||
|
||||
@override
|
||||
set theme(ThemeMode value) {
|
||||
_$themeAtom.reportWrite(value, super.theme, () {
|
||||
super.theme = value;
|
||||
});
|
||||
}
|
||||
|
||||
final _$amoledDarkModeAtom = Atom(name: '_ConfigStore.amoledDarkMode');
|
||||
|
||||
@override
|
||||
bool get amoledDarkMode {
|
||||
_$amoledDarkModeAtom.reportRead();
|
||||
return super.amoledDarkMode;
|
||||
}
|
||||
|
||||
@override
|
||||
set amoledDarkMode(bool value) {
|
||||
_$amoledDarkModeAtom.reportWrite(value, super.amoledDarkMode, () {
|
||||
super.amoledDarkMode = value;
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return '''
|
||||
theme: ${theme},
|
||||
amoledDarkMode: ${amoledDarkMode}
|
||||
''';
|
||||
}
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
/// Collection of string constants that are keys to SharedPreferences
|
||||
class SharedPrefKeys {
|
||||
static const tokens = 'tokens';
|
||||
static const defaultAccount = 'defaultAccount';
|
||||
static const defaultAccounts = 'defaultAccounts';
|
||||
static const theme = 'theme';
|
||||
static const amoledDarkMode = 'amoledDarkMode';
|
||||
}
|
288
pubspec.lock
288
pubspec.lock
|
@ -1,20 +1,6 @@
|
|||
# Generated by pub
|
||||
# See https://dart.dev/tools/pub/glossary#lockfile
|
||||
packages:
|
||||
_fe_analyzer_shared:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: _fe_analyzer_shared
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "6.0.0"
|
||||
analyzer:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: analyzer
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.39.14"
|
||||
args:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -36,62 +22,6 @@ packages:
|
|||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.1.0-nullsafety.1"
|
||||
build:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: build
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.3.0"
|
||||
build_config:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: build_config
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.4.2"
|
||||
build_daemon:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: build_daemon
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.1.4"
|
||||
build_resolvers:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: build_resolvers
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.3.11"
|
||||
build_runner:
|
||||
dependency: "direct dev"
|
||||
description:
|
||||
name: build_runner
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.10.1"
|
||||
build_runner_core:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: build_runner_core
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "6.0.1"
|
||||
built_collection:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: built_collection
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "4.3.2"
|
||||
built_value:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: built_value
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "7.1.0"
|
||||
cached_network_image:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
@ -113,20 +43,6 @@ packages:
|
|||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.2.0-nullsafety.1"
|
||||
checked_yaml:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: checked_yaml
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.0.2"
|
||||
cli_util:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: cli_util
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.1.4"
|
||||
clock:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -134,13 +50,6 @@ packages:
|
|||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.1.0-nullsafety.1"
|
||||
code_builder:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: code_builder
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "3.4.1"
|
||||
collection:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -162,13 +71,6 @@ packages:
|
|||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.1.5"
|
||||
csslib:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: csslib
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.16.2"
|
||||
cupertino_icons:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
@ -176,13 +78,6 @@ packages:
|
|||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.1.3"
|
||||
dart_style:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: dart_style
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.3.6"
|
||||
effective_dart:
|
||||
dependency: "direct dev"
|
||||
description:
|
||||
|
@ -218,13 +113,6 @@ packages:
|
|||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "5.2.1"
|
||||
fixnum:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: fixnum
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.10.11"
|
||||
flutter:
|
||||
dependency: "direct main"
|
||||
description: flutter
|
||||
|
@ -258,13 +146,6 @@ packages:
|
|||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.4.4"
|
||||
flutter_mobx:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: flutter_mobx
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.1.0+2"
|
||||
flutter_plugin_android_lifecycle:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -303,27 +184,6 @@ packages:
|
|||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.2.3"
|
||||
glob:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: glob
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.2.0"
|
||||
graphs:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: graphs
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.2.0"
|
||||
html:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: html
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.14.0+3"
|
||||
http:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -331,13 +191,6 @@ packages:
|
|||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.12.2"
|
||||
http_multi_server:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: http_multi_server
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.2.0"
|
||||
http_parser:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -366,20 +219,13 @@ packages:
|
|||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.16.1"
|
||||
io:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: io
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.3.4"
|
||||
js:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: js
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.6.2"
|
||||
version: "0.6.3-nullsafety.1"
|
||||
json_annotation:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -401,13 +247,6 @@ packages:
|
|||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.7.3"
|
||||
logging:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: logging
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.11.4"
|
||||
markdown:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
@ -428,28 +267,7 @@ packages:
|
|||
name: meta
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.3.0-nullsafety.3"
|
||||
mime:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: mime
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.9.7"
|
||||
mobx:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: mobx
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.2.1+2"
|
||||
mobx_codegen:
|
||||
dependency: "direct dev"
|
||||
description:
|
||||
name: mobx_codegen
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.1.0+1"
|
||||
version: "1.3.0-nullsafety.4"
|
||||
nested:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -457,20 +275,6 @@ packages:
|
|||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.0.4"
|
||||
node_interop:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: node_interop
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.1.1"
|
||||
node_io:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: node_io
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.1.1"
|
||||
octo_image:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -478,13 +282,6 @@ packages:
|
|||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.3.0"
|
||||
package_config:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: package_config
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.9.3"
|
||||
package_info:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
@ -569,13 +366,6 @@ packages:
|
|||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.0.2"
|
||||
pool:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: pool
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.4.0"
|
||||
process:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -597,20 +387,6 @@ packages:
|
|||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.4.4"
|
||||
pubspec_parse:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: pubspec_parse
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.1.5"
|
||||
quiver:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: quiver
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.1.3"
|
||||
rxdart:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -653,32 +429,11 @@ packages:
|
|||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.1.2+7"
|
||||
shelf:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shelf
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.7.9"
|
||||
shelf_web_socket:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shelf_web_socket
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.2.3"
|
||||
sky_engine:
|
||||
dependency: transitive
|
||||
description: flutter
|
||||
source: sdk
|
||||
version: "0.0.99"
|
||||
source_gen:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: source_gen
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.9.6"
|
||||
source_span:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -706,7 +461,7 @@ packages:
|
|||
name: stack_trace
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.10.0-nullsafety.1"
|
||||
version: "1.10.0-nullsafety.4"
|
||||
stream_channel:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -714,13 +469,6 @@ packages:
|
|||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.1.0-nullsafety.1"
|
||||
stream_transform:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: stream_transform
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.2.0"
|
||||
string_scanner:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -756,13 +504,6 @@ packages:
|
|||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.0.27"
|
||||
timing:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: timing
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.1.1+2"
|
||||
typed_data:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -826,20 +567,6 @@ packages:
|
|||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.1.0-nullsafety.3"
|
||||
watcher:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: watcher
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.9.7+15"
|
||||
web_socket_channel:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: web_socket_channel
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.1.0"
|
||||
win32:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -854,13 +581,6 @@ packages:
|
|||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.1.0"
|
||||
yaml:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: yaml
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.2.1"
|
||||
sdks:
|
||||
dart: ">=2.10.0-110 <2.11.0"
|
||||
dart: ">=2.10.0-110 <=2.11.0-242.0.dev"
|
||||
flutter: ">=1.20.0 <2.0.0"
|
||||
|
|
|
@ -38,8 +38,6 @@ dependencies:
|
|||
|
||||
# state management
|
||||
flutter_hooks: ^0.13.2
|
||||
mobx: ^1.2.1
|
||||
flutter_mobx: ^1.1.0
|
||||
provider: ^4.3.1
|
||||
|
||||
# utils
|
||||
|
@ -58,8 +56,6 @@ dev_dependencies:
|
|||
flutter_test:
|
||||
sdk: flutter
|
||||
effective_dart: ^1.0.0
|
||||
build_runner: ^1.10.0
|
||||
mobx_codegen: ^1.1.0
|
||||
# For information on the generic Dart part of this file, see the
|
||||
# following page: https://dart.dev/tools/pub/pubspec
|
||||
# The following section is specific to Flutter.
|
||||
|
|
Loading…
Reference in New Issue