Merge pull request #43 from krawieck/big-cleanup

This commit is contained in:
Filip Krawczyk 2020-09-17 14:33:20 +02:00 committed by GitHub
commit 87c14483c7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
23 changed files with 220 additions and 236 deletions

View File

@ -5,6 +5,7 @@ linter:
public_member_api_docs: false public_member_api_docs: false
prefer_expression_function_bodies: true prefer_expression_function_bodies: true
prefer_single_quotes: true prefer_single_quotes: true
prefer_final_locals: true
analyzer: analyzer:
exclude: exclude:
- '**/*.g.dart' - "**/*.g.dart"

View File

@ -21,7 +21,7 @@ class CommentTree {
static List<CommentTree> fromList(List<CommentView> comments) { static List<CommentTree> fromList(List<CommentView> comments) {
CommentTree gatherChildren(CommentTree parent) { CommentTree gatherChildren(CommentTree parent) {
for (var el in comments) { for (final el in comments) {
if (el.parentId == parent.comment.id) { if (el.parentId == parent.comment.id) {
parent.children.add(gatherChildren(CommentTree(el))); parent.children.add(gatherChildren(CommentTree(el)));
} }
@ -29,7 +29,7 @@ class CommentTree {
return parent; return parent;
} }
var parents = <CommentTree>[]; final parents = <CommentTree>[];
// first pass to get all the parents // first pass to get all the parents
for (var i = 0; i < comments.length; i++) { for (var i = 0; i < comments.length; i++) {
@ -38,7 +38,7 @@ class CommentTree {
} }
} }
var result = parents.map(gatherChildren).toList(); final result = parents.map(gatherChildren).toList();
return result; return result;
} }
@ -63,7 +63,7 @@ class CommentTree {
void _sort(int compare(CommentTree a, CommentTree b)) { void _sort(int compare(CommentTree a, CommentTree b)) {
children.sort(compare); children.sort(compare);
for (var el in children) { for (final el in children) {
el._sort(compare); el._sort(compare);
} }
} }

View File

@ -23,9 +23,9 @@ class DelayedLoading {
/// and loading is triggered after [delayDuration]. /// and loading is triggered after [delayDuration].
/// Everything can be reset with [.cancel()] /// Everything can be reset with [.cancel()]
DelayedLoading useDelayedLoading(Duration delayDuration) { DelayedLoading useDelayedLoading(Duration delayDuration) {
var loading = useState(false); final loading = useState(false);
var pending = useState(false); final pending = useState(false);
var timerHandle = useRef<Timer>(null); final timerHandle = useRef<Timer>(null);
return DelayedLoading( return DelayedLoading(
loading: loading.value, loading: loading.value,

8
lib/hooks/stores.dart Normal file
View File

@ -0,0 +1,8 @@
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:provider/provider.dart';
import '../stores/accounts_store.dart';
import '../stores/config_store.dart';
AccountsStore useAccountsStore() => useContext().watch<AccountsStore>();
ConfigStore useConfigStore() => useContext().watch<ConfigStore>();

View File

@ -1,17 +1,23 @@
import 'dart:async';
import 'package:flutter/material.dart'; 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:flutter_mobx/flutter_mobx.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'hooks/stores.dart';
import 'pages/profile_tab.dart';
import 'stores/accounts_store.dart'; import 'stores/accounts_store.dart';
import 'stores/config_store.dart'; import 'stores/config_store.dart';
Future<void> main() async { Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized(); WidgetsFlutterBinding.ensureInitialized();
var configStore = ConfigStore(); final configStore = ConfigStore();
await configStore.load(); await configStore.load();
var accountsStore = AccountsStore(); final accountsStore = AccountsStore();
await accountsStore.load(); await accountsStore.load();
runApp( runApp(
@ -31,71 +37,51 @@ Future<void> main() async {
); );
} }
class MyApp extends StatelessWidget { class MyApp extends HookWidget {
@override @override
Widget build(BuildContext context) => Observer( Widget build(BuildContext context) {
builder: (ctx) { final configStore = useConfigStore();
var maybeAmoledColor =
ctx.watch<ConfigStore>().amoledDarkMode ? Colors.black : null;
return MaterialApp( return Observer(
title: 'Flutter Demo', builder: (ctx) {
themeMode: ctx.watch<ConfigStore>().theme, final maybeAmoledColor =
darkTheme: ThemeData.dark().copyWith( configStore.amoledDarkMode ? Colors.black : null;
scaffoldBackgroundColor: maybeAmoledColor,
backgroundColor: maybeAmoledColor,
canvasColor: maybeAmoledColor,
cardColor: maybeAmoledColor,
splashColor: maybeAmoledColor,
),
theme: ThemeData(
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: MyHomePage(title: 'Flutter hello world'),
);
},
);
}
class MyHomePage extends StatefulWidget { return MaterialApp(
MyHomePage({Key key, this.title}) : super(key: key); title: 'Lemmur',
themeMode: configStore.theme,
final String title; darkTheme: ThemeData.dark().copyWith(
scaffoldBackgroundColor: maybeAmoledColor,
@override backgroundColor: maybeAmoledColor,
_MyHomePageState createState() => _MyHomePageState(); canvasColor: maybeAmoledColor,
} cardColor: maybeAmoledColor,
splashColor: maybeAmoledColor,
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;
void _incrementCounter() {
setState(() {
_counter++;
});
}
@override
Widget build(BuildContext context) => Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text('You have pushed the button this many times:'),
Text(
'$_counter',
style: Theme.of(context).textTheme.headline4,
),
],
), ),
), theme: ThemeData(
floatingActionButton: FloatingActionButton( visualDensity: VisualDensity.adaptivePlatformDensity,
onPressed: _incrementCounter, ),
tooltip: 'Increment', home: MyHomePage(),
child: Icon(Icons.add), );
), },
); );
}
}
class MyHomePage extends HookWidget {
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
useEffect(() {
Future.microtask(
() => SystemChrome.setSystemUIOverlayStyle(SystemUiOverlayStyle(
systemNavigationBarColor: theme.scaffoldBackgroundColor,
)),
);
return null;
}, [theme.scaffoldBackgroundColor]);
return UserProfileTab();
}
} }

View File

@ -5,10 +5,10 @@ import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:fuzzy/fuzzy.dart'; import 'package:fuzzy/fuzzy.dart';
import 'package:lemmy_api_client/lemmy_api_client.dart'; import 'package:lemmy_api_client/lemmy_api_client.dart';
import 'package:provider/provider.dart';
import '../hooks/delayed_loading.dart'; import '../hooks/delayed_loading.dart';
import '../stores/accounts_store.dart'; import '../hooks/memo_future.dart';
import '../hooks/stores.dart';
import '../util/extensions/iterators.dart'; import '../util/extensions/iterators.dart';
import '../util/text_color.dart'; import '../util/text_color.dart';
@ -17,23 +17,18 @@ class CommunitiesTab extends HookWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
var theme = Theme.of(context); final theme = Theme.of(context);
var filterController = useTextEditingController(); final filterController = useTextEditingController();
useValueListenable(filterController); useValueListenable(filterController);
var amountOfDisplayInstances = useMemoized(() { final accountsStore = useAccountsStore();
var accountsStore = context.watch<AccountsStore>();
return accountsStore.users.keys final amountOfDisplayInstances = useMemoized(() => accountsStore.users.keys
.where((e) => !accountsStore.isAnonymousFor(e)) .where((e) => !accountsStore.isAnonymousFor(e))
.length; .length);
}); final isCollapsed = useState(List.filled(amountOfDisplayInstances, false));
var isCollapsed = useState(List.filled(amountOfDisplayInstances, false));
// TODO: use useMemoFuture final instancesSnap = useMemoFuture(() {
var instancesFut = useMemoized(() { final futures = accountsStore.users.keys
var accountsStore = context.watch<AccountsStore>();
var futures = accountsStore.users.keys
.where((e) => !accountsStore.isAnonymousFor(e)) .where((e) => !accountsStore.isAnonymousFor(e))
.map( .map(
(instanceUrl) => (instanceUrl) =>
@ -43,10 +38,8 @@ class CommunitiesTab extends HookWidget {
return Future.wait(futures); return Future.wait(futures);
}); });
var communitiesFut = useMemoized(() { final communitiesSnap = useMemoFuture(() {
var accountsStore = context.watch<AccountsStore>(); final futures = accountsStore.users.keys
var futures = accountsStore.users.keys
.where((e) => !accountsStore.isAnonymousFor(e)) .where((e) => !accountsStore.isAnonymousFor(e))
.map( .map(
(instanceUrl) => LemmyApi(instanceUrl) (instanceUrl) => LemmyApi(instanceUrl)
@ -63,9 +56,6 @@ class CommunitiesTab extends HookWidget {
return Future.wait(futures); return Future.wait(futures);
}); });
var communitiesSnap = useFuture(communitiesFut);
var instancesSnap = useFuture(instancesFut);
if (communitiesSnap.hasError || instancesSnap.hasError) { if (communitiesSnap.hasError || instancesSnap.hasError) {
return Scaffold( return Scaffold(
appBar: AppBar(), appBar: AppBar(),
@ -93,12 +83,12 @@ class CommunitiesTab extends HookWidget {
); );
} }
var instances = instancesSnap.data; final instances = instancesSnap.data;
var communities = communitiesSnap.data final communities = communitiesSnap.data
..forEach( ..forEach(
(e) => e.sort((a, b) => a.communityName.compareTo(b.communityName))); (e) => e.sort((a, b) => a.communityName.compareTo(b.communityName)));
var filterIcon = () { final filterIcon = () {
if (filterController.text.isEmpty) { if (filterController.text.isEmpty) {
return Icon(Icons.filter_list); return Icon(Icons.filter_list);
} }
@ -113,7 +103,7 @@ class CommunitiesTab extends HookWidget {
}(); }();
filterCommunities(List<CommunityFollowerView> comm) { filterCommunities(List<CommunityFollowerView> comm) {
var matches = Fuzzy( final matches = Fuzzy(
comm.map((e) => e.communityName).toList(), comm.map((e) => e.communityName).toList(),
options: FuzzyOptions(threshold: 0.5), options: FuzzyOptions(threshold: 0.5),
).search(filterController.text).map((e) => e.item); ).search(filterController.text).map((e) => e.item);
@ -146,7 +136,7 @@ class CommunitiesTab extends HookWidget {
), ),
body: ListView( body: ListView(
children: [ children: [
for (var i in Iterable.generate(amountOfDisplayInstances)) for (final i in Iterable.generate(amountOfDisplayInstances))
Column( Column(
children: [ children: [
ListTile( ListTile(
@ -178,7 +168,7 @@ class CommunitiesTab extends HookWidget {
), ),
), ),
if (!isCollapsed.value[i]) if (!isCollapsed.value[i])
for (var comm in filterCommunities(communities[i])) for (final comm in filterCommunities(communities[i]))
Padding( Padding(
padding: const EdgeInsets.only(left: 17), padding: const EdgeInsets.only(left: 17),
child: ListTile( child: ListTile(
@ -236,9 +226,10 @@ class _CommunitySubscribeToggle extends HookWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
var theme = Theme.of(context); final theme = Theme.of(context);
var subbed = useState(true); final subbed = useState(true);
var delayed = useDelayedLoading(const Duration(milliseconds: 500)); final delayed = useDelayedLoading(const Duration(milliseconds: 500));
final accountsStore = useAccountsStore();
handleTap() async { handleTap() async {
delayed.start(); delayed.start();
@ -247,10 +238,7 @@ class _CommunitySubscribeToggle extends HookWidget {
await LemmyApi(instanceUrl).v1.followCommunity( await LemmyApi(instanceUrl).v1.followCommunity(
communityId: communityId, communityId: communityId,
follow: !subbed.value, follow: !subbed.value,
auth: context auth: accountsStore.defaultTokenFor(instanceUrl).raw,
.read<AccountsStore>()
.defaultTokenFor(instanceUrl)
.raw,
); );
subbed.value = !subbed.value; subbed.value = !subbed.value;
} on Exception catch (err) { } on Exception catch (err) {

View File

@ -1,5 +1,3 @@
import 'dart:async';
import 'package:cached_network_image/cached_network_image.dart'; import 'package:cached_network_image/cached_network_image.dart';
import 'package:esys_flutter_share/esys_flutter_share.dart'; import 'package:esys_flutter_share/esys_flutter_share.dart';
import 'package:flutter/gestures.dart'; import 'package:flutter/gestures.dart';
@ -7,11 +5,11 @@ import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:intl/intl.dart'; import 'package:intl/intl.dart';
import 'package:lemmy_api_client/lemmy_api_client.dart'; import 'package:lemmy_api_client/lemmy_api_client.dart';
import 'package:provider/provider.dart';
import 'package:url_launcher/url_launcher.dart' as ul; import 'package:url_launcher/url_launcher.dart' as ul;
import '../hooks/delayed_loading.dart';
import '../hooks/memo_future.dart'; import '../hooks/memo_future.dart';
import '../stores/accounts_store.dart'; import '../hooks/stores.dart';
import '../util/api_extensions.dart'; import '../util/api_extensions.dart';
import '../util/goto.dart'; import '../util/goto.dart';
import '../util/intl.dart'; import '../util/intl.dart';
@ -49,8 +47,10 @@ class CommunityPage extends HookWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final theme = Theme.of(context); final theme = Theme.of(context);
var fullCommunitySnap = useMemoFuture(() { final accountsStore = useAccountsStore();
final token = context.watch<AccountsStore>().defaultTokenFor(instanceUrl);
final fullCommunitySnap = useMemoFuture(() {
final token = accountsStore.defaultTokenFor(instanceUrl);
if (communityId != null) { if (communityId != null) {
return LemmyApi(instanceUrl).v1.getCommunity( return LemmyApi(instanceUrl).v1.getCommunity(
@ -535,13 +535,11 @@ class _FollowButton extends HookWidget {
final theme = Theme.of(context); final theme = Theme.of(context);
final isSubbed = useState(community.subscribed ?? false); final isSubbed = useState(community.subscribed ?? false);
final colorOnTopOfAccent = textColorBasedOnBackground(theme.accentColor); final token = useAccountsStore().defaultTokenFor(community.instanceUrl);
final token =
context.watch<AccountsStore>().defaultTokenFor(community.instanceUrl);
// TODO: use hook for handling spinner and pending final delayed = useDelayedLoading(const Duration(milliseconds: 500));
final showSpinner = useState(false);
final isPending = useState(false); final colorOnTopOfAccent = textColorBasedOnBackground(theme.accentColor);
subscribe() async { subscribe() async {
if (token == null) { if (token == null) {
@ -550,13 +548,10 @@ class _FollowButton extends HookWidget {
return; return;
} }
isPending.value = true; delayed.start();
var spinnerTimer =
Timer(Duration(milliseconds: 500), () => showSpinner.value = true);
final api = LemmyApi(community.instanceUrl).v1;
try { try {
await api.followCommunity( await LemmyApi(community.instanceUrl).v1.followCommunity(
communityId: community.id, communityId: community.id,
follow: !isSubbed.value, follow: !isSubbed.value,
auth: token?.raw); auth: token?.raw);
@ -574,17 +569,14 @@ class _FollowButton extends HookWidget {
)); ));
} }
// clean up delayed.cancel();
spinnerTimer.cancel();
isPending.value = false;
showSpinner.value = false;
} }
return Center( return Center(
child: SizedBox( child: SizedBox(
height: 27, height: 27,
width: 160, width: 160,
child: showSpinner.value child: delayed.loading
? RaisedButton( ? RaisedButton(
onPressed: null, onPressed: null,
child: SizedBox( child: SizedBox(
@ -598,7 +590,7 @@ class _FollowButton extends HookWidget {
) )
: RaisedButton.icon( : RaisedButton.icon(
padding: EdgeInsets.symmetric(vertical: 5, horizontal: 20), padding: EdgeInsets.symmetric(vertical: 5, horizontal: 20),
onPressed: isPending.value ? () {} : subscribe, onPressed: delayed.pending ? () {} : subscribe,
icon: isSubbed.value icon: isSubbed.value
? Icon(Icons.remove, size: 18, color: colorOnTopOfAccent) ? Icon(Icons.remove, size: 18, color: colorOnTopOfAccent)
: Icon(Icons.add, size: 18, color: colorOnTopOfAccent), : Icon(Icons.add, size: 18, color: colorOnTopOfAccent),

View File

@ -1,5 +1,6 @@
import 'package:cached_network_image/cached_network_image.dart'; import 'package:cached_network_image/cached_network_image.dart';
import 'package:esys_flutter_share/esys_flutter_share.dart'; import 'package:esys_flutter_share/esys_flutter_share.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:intl/intl.dart'; import 'package:intl/intl.dart';
@ -272,10 +273,11 @@ class _AboutTab extends HookWidget {
} }
void goToBannedUsers(BuildContext c) { void goToBannedUsers(BuildContext c) {
Navigator.of(c).push(MaterialPageRoute( goTo(
builder: (_) => UsersListPage( c,
(_) => UsersListPage(
users: site.banned.reversed.toList(), title: 'Banned users'), users: site.banned.reversed.toList(), title: 'Banned users'),
)); );
} }
@override @override
@ -284,11 +286,12 @@ class _AboutTab extends HookWidget {
final commSnap = useFuture(communitiesFuture); final commSnap = useFuture(communitiesFuture);
void goToCommunities() { void goToCommunities() {
Navigator.of(context).push(MaterialPageRoute( goTo(
builder: (_) => CommunitiesListPage( context,
(_) => CommunitiesListPage(
communities: commSnap.data, communities: commSnap.data,
title: 'Communities of ${site.site.name}'), title: 'Communities of ${site.site.name}'),
)); );
} }
return SingleChildScrollView( return SingleChildScrollView(

View File

@ -1,10 +1,11 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:flutter_mobx/flutter_mobx.dart'; import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:provider/provider.dart';
import '../stores/accounts_store.dart'; import '../hooks/stores.dart';
import '../util/api_extensions.dart'; import '../util/api_extensions.dart';
import '../util/goto.dart';
import '../widgets/bottom_modal.dart'; import '../widgets/bottom_modal.dart';
import '../widgets/user_profile.dart'; import '../widgets/user_profile.dart';
import 'settings.dart'; import 'settings.dart';
@ -14,11 +15,12 @@ class UserProfileTab extends HookWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
var theme = Theme.of(context); final theme = Theme.of(context);
final accountsStore = useAccountsStore();
return Observer( return Observer(
builder: (ctx) { builder: (ctx) {
if (ctx.watch<AccountsStore>().hasNoAccount) { if (accountsStore.hasNoAccount) {
return Scaffold( return Scaffold(
body: Center( body: Center(
child: Column( child: Column(
@ -27,8 +29,7 @@ class UserProfileTab extends HookWidget {
Text('No account was added.'), Text('No account was added.'),
FlatButton.icon( FlatButton.icon(
onPressed: () { onPressed: () {
Navigator.of(context).push(MaterialPageRoute( goTo(context, (_) => AccountsConfigPage());
builder: (_) => AccountsConfigPage()));
}, },
icon: Icon(Icons.add), icon: Icon(Icons.add),
label: Text('Add account'), label: Text('Add account'),
@ -39,7 +40,7 @@ class UserProfileTab extends HookWidget {
); );
} }
var user = ctx.watch<AccountsStore>().defaultUser; final user = accountsStore.defaultUser;
return Scaffold( return Scaffold(
extendBodyBehindAppBar: true, extendBodyBehindAppBar: true,
@ -68,12 +69,9 @@ class UserProfileTab extends HookWidget {
context: context, context: context,
backgroundColor: Colors.transparent, backgroundColor: Colors.transparent,
builder: (_) { builder: (_) {
var userTags = <String>[]; final userTags = <String>[];
ctx accountsStore.users.forEach((instanceUrl, value) {
.read<AccountsStore>()
.users
.forEach((instanceUrl, value) {
value.forEach((username, _) { value.forEach((username, _) {
userTags.add('$username@$instanceUrl'); userTags.add('$username@$instanceUrl');
}); });
@ -81,8 +79,8 @@ class UserProfileTab extends HookWidget {
return Observer( return Observer(
builder: (ctx) { builder: (ctx) {
var user = ctx.watch<AccountsStore>().defaultUser; final user = accountsStore.defaultUser;
var instanceUrl = user.instanceUrl; final instanceUrl = user.instanceUrl;
return BottomModal( return BottomModal(
title: 'account', title: 'account',
@ -94,8 +92,8 @@ class UserProfileTab extends HookWidget {
title: Text(tag), title: Text(tag),
groupValue: '${user.name}@$instanceUrl', groupValue: '${user.name}@$instanceUrl',
onChanged: (selected) { onChanged: (selected) {
var userTag = selected.split('@'); final userTag = selected.split('@');
ctx.read<AccountsStore>().setDefaultAccount( accountsStore.setDefaultAccount(
userTag[1], userTag[0]); userTag[1], userTag[0]);
Navigator.of(ctx).pop(); Navigator.of(ctx).pop();
}, },
@ -113,8 +111,7 @@ class UserProfileTab extends HookWidget {
IconButton( IconButton(
icon: Icon(Icons.settings), icon: Icon(Icons.settings),
onPressed: () { onPressed: () {
Navigator.of(context) goTo(context, (_) => SettingsPage());
.push(MaterialPageRoute(builder: (_) => SettingsPage()));
}, },
) )
], ],

View File

@ -1,15 +1,15 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:flutter_mobx/flutter_mobx.dart'; import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:provider/provider.dart';
import '../stores/accounts_store.dart'; import '../hooks/stores.dart';
import '../stores/config_store.dart'; import '../util/goto.dart';
class SettingsPage extends StatelessWidget { class SettingsPage extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
var theme = Theme.of(context); final theme = Theme.of(context);
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
@ -26,16 +26,14 @@ class SettingsPage extends StatelessWidget {
leading: Icon(Icons.person), leading: Icon(Icons.person),
title: Text('Accounts'), title: Text('Accounts'),
onTap: () { onTap: () {
Navigator.of(context).push( goTo(context, (_) => AccountsConfigPage());
MaterialPageRoute(builder: (_) => AccountsConfigPage()));
}, },
), ),
ListTile( ListTile(
leading: Icon(Icons.color_lens), leading: Icon(Icons.color_lens),
title: Text('Appearance'), title: Text('Appearance'),
onTap: () { onTap: () {
Navigator.of(context).push( goTo(context, (_) => AppearanceConfigPage());
MaterialPageRoute(builder: (_) => AppearanceConfigPage()));
}, },
) )
], ],
@ -45,10 +43,11 @@ class SettingsPage extends StatelessWidget {
} }
} }
class AppearanceConfigPage extends StatelessWidget { class AppearanceConfigPage extends HookWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
var theme = Theme.of(context); final theme = Theme.of(context);
final configStore = useConfigStore();
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
@ -66,16 +65,16 @@ class AppearanceConfigPage extends StatelessWidget {
RadioListTile<ThemeMode>( RadioListTile<ThemeMode>(
value: theme, value: theme,
title: Text(theme.toString().split('.')[1]), title: Text(theme.toString().split('.')[1]),
groupValue: ctx.watch<ConfigStore>().theme, groupValue: configStore.theme,
onChanged: (selected) { onChanged: (selected) {
ctx.read<ConfigStore>().theme = selected; configStore.theme = selected;
}, },
), ),
SwitchListTile( SwitchListTile(
title: Text('AMOLED dark mode'), title: Text('AMOLED dark mode'),
value: ctx.watch<ConfigStore>().amoledDarkMode, value: configStore.amoledDarkMode,
onChanged: (checked) { onChanged: (checked) {
ctx.read<ConfigStore>().amoledDarkMode = checked; configStore.amoledDarkMode = checked;
}) })
], ],
), ),
@ -89,7 +88,8 @@ class AccountsConfigPage extends HookWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
var theme = Theme.of(context); final theme = Theme.of(context);
final accountsStore = useAccountsStore();
return Scaffold( return Scaffold(
key: _scaffoldKey, key: _scaffoldKey,
@ -112,14 +112,13 @@ class AccountsConfigPage extends HookWidget {
), ),
body: Observer( body: Observer(
builder: (ctx) { builder: (ctx) {
var accountsStore = ctx.watch<AccountsStore>(); final theme = Theme.of(context);
var theme = Theme.of(context);
return ListView( return ListView(
children: [ children: [
for (var entry in accountsStore.users.entries) ...[ for (final entry in accountsStore.users.entries) ...[
_SectionHeading(entry.key), _SectionHeading(entry.key),
for (var username in entry.value.keys) ...[ for (final username in entry.value.keys) ...[
ListTile( ListTile(
trailing: trailing:
username == accountsStore.defaultUserFor(entry.key).name username == accountsStore.defaultUserFor(entry.key).name
@ -166,17 +165,16 @@ class _AccountsConfigAddInstanceDialog extends HookWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
var instanceController = useTextEditingController(); final instanceController = useTextEditingController();
useValueListenable(instanceController); useValueListenable(instanceController);
final accountsStore = useAccountsStore();
var loading = useState(false); final loading = useState(false);
handleOnAdd() async { handleOnAdd() async {
try { try {
loading.value = true; loading.value = true;
await context await accountsStore.addInstance(instanceController.text);
.read<AccountsStore>()
.addInstance(instanceController.text);
scaffoldKey.currentState.hideCurrentSnackBar(); scaffoldKey.currentState.hideCurrentSnackBar();
} on Exception catch (err) { } on Exception catch (err) {
scaffoldKey.currentState.showSnackBar(SnackBar( scaffoldKey.currentState.showSnackBar(SnackBar(
@ -222,21 +220,22 @@ class _AccountsConfigAddAccountDialog extends HookWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
var usernameController = useTextEditingController(); final usernameController = useTextEditingController();
var passwordController = useTextEditingController(); final passwordController = useTextEditingController();
useValueListenable(usernameController); useValueListenable(usernameController);
useValueListenable(passwordController); useValueListenable(passwordController);
final accountsStore = useAccountsStore();
var loading = useState(false); final loading = useState(false);
handleOnAdd() async { handleOnAdd() async {
try { try {
loading.value = true; loading.value = true;
await context.read<AccountsStore>().addAccount( await accountsStore.addAccount(
instanceUrl, instanceUrl,
usernameController.text, usernameController.text,
passwordController.text, passwordController.text,
); );
} on Exception catch (err) { } on Exception catch (err) {
scaffoldKey.currentState.showSnackBar(SnackBar( scaffoldKey.currentState.showSnackBar(SnackBar(
content: Text(err.toString()), content: Text(err.toString()),

View File

@ -30,9 +30,9 @@ class UserPage extends HookWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
var userViewSnap = useFuture(_userView); final userViewSnap = useFuture(_userView);
var body = () { final body = () {
if (userViewSnap.hasData) { if (userViewSnap.hasData) {
return UserProfile.fromUserView(userViewSnap.data); return UserProfile.fromUserView(userViewSnap.data);
} else if (userViewSnap.hasError) { } else if (userViewSnap.hasError) {

View File

@ -14,7 +14,6 @@ abstract class _AccountsStore with Store {
_AccountsStore() { _AccountsStore() {
// persistently save settings each time they are changed // persistently save settings each time they are changed
_saveReactionDisposer = reaction( _saveReactionDisposer = reaction(
// TODO: does not react to deep changes in users and tokens
(_) => [ (_) => [
users.forEach((k, submap) => users.forEach((k, submap) =>
MapEntry(k, submap.forEach((k2, v2) => MapEntry(k2, v2)))), MapEntry(k, submap.forEach((k2, v2) => MapEntry(k2, v2)))),
@ -32,7 +31,7 @@ abstract class _AccountsStore with Store {
} }
void load() async { void load() async {
var prefs = await SharedPreferences.getInstance(); final prefs = await SharedPreferences.getInstance();
nestedMapsCast<T>(String key, T f(Map<String, dynamic> json)) => nestedMapsCast<T>(String key, T f(Map<String, dynamic> json)) =>
ObservableMap.of( ObservableMap.of(
@ -59,7 +58,7 @@ abstract class _AccountsStore with Store {
} }
void save() async { void save() async {
var prefs = await SharedPreferences.getInstance(); final prefs = await SharedPreferences.getInstance();
await prefs.setString('defaultAccount', _defaultAccount); await prefs.setString('defaultAccount', _defaultAccount);
await prefs.setString('defaultAccounts', jsonEncode(_defaultAccounts)); await prefs.setString('defaultAccounts', jsonEncode(_defaultAccounts));
@ -93,7 +92,7 @@ abstract class _AccountsStore with Store {
return null; return null;
} }
var userTag = _defaultAccount.split('@'); final userTag = _defaultAccount.split('@');
return users[userTag[1]][userTag[0]]; return users[userTag[1]][userTag[0]];
} }
@ -103,7 +102,7 @@ abstract class _AccountsStore with Store {
return null; return null;
} }
var userTag = _defaultAccount.split('@'); final userTag = _defaultAccount.split('@');
return tokens[userTag[1]][userTag[0]]; return tokens[userTag[1]][userTag[0]];
} }
@ -159,13 +158,13 @@ abstract class _AccountsStore with Store {
throw Exception('No such instance was added'); throw Exception('No such instance was added');
} }
var lemmy = LemmyApi(instanceUrl).v1; final lemmy = LemmyApi(instanceUrl).v1;
var token = await lemmy.login( final token = await lemmy.login(
usernameOrEmail: usernameOrEmail, usernameOrEmail: usernameOrEmail,
password: password, password: password,
); );
var userData = final userData =
await lemmy.getSite(auth: token.raw).then((value) => value.myUser); await lemmy.getSite(auth: token.raw).then((value) => value.myUser);
// first account for this instance // first account for this instance

View File

@ -22,14 +22,14 @@ abstract class _ConfigStore with Store {
} }
void load() async { void load() async {
var prefs = await SharedPreferences.getInstance(); final prefs = await SharedPreferences.getInstance();
// load saved settings or create defaults // load saved settings or create defaults
theme = _themeModeFromString(prefs.getString('theme') ?? 'system'); theme = _themeModeFromString(prefs.getString('theme') ?? 'system');
amoledDarkMode = prefs.getBool('amoledDarkMode') ?? false; amoledDarkMode = prefs.getBool('amoledDarkMode') ?? false;
} }
void save() async { void save() async {
var prefs = await SharedPreferences.getInstance(); final prefs = await SharedPreferences.getInstance();
await prefs.setString('theme', describeEnum(theme)); await prefs.setString('theme', describeEnum(theme));
await prefs.setBool('amoledDarkMode', amoledDarkMode); await prefs.setBool('amoledDarkMode', amoledDarkMode);

View File

@ -1,3 +1,4 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:url_launcher/url_launcher.dart' as ul; import 'package:url_launcher/url_launcher.dart' as ul;
@ -7,6 +8,7 @@ import 'pages/full_post.dart';
import 'pages/instance.dart'; import 'pages/instance.dart';
import 'pages/user.dart'; import 'pages/user.dart';
import 'stores/accounts_store.dart'; import 'stores/accounts_store.dart';
import 'util/goto.dart';
Future<void> linkLauncher({ Future<void> linkLauncher({
@required BuildContext context, @required BuildContext context,
@ -14,7 +16,7 @@ Future<void> linkLauncher({
@required String instanceUrl, @required String instanceUrl,
}) async { }) async {
push(Widget Function() builder) { push(Widget Function() builder) {
Navigator.of(context).push(MaterialPageRoute(builder: (c) => builder())); goTo(context, (c) => builder());
} }
final instances = context.read<AccountsStore>().users.keys.toList(); final instances = context.read<AccountsStore>().users.keys.toList();

View File

@ -1,3 +1,4 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import '../pages/community.dart'; import '../pages/community.dart';
@ -5,41 +6,46 @@ import '../pages/full_post.dart';
import '../pages/instance.dart'; import '../pages/instance.dart';
import '../pages/user.dart'; import '../pages/user.dart';
void goToInstance(BuildContext context, String instanceUrl) => Future<dynamic> goTo(
Navigator.of(context).push(MaterialPageRoute( BuildContext context,
builder: (context) => InstancePage(instanceUrl: instanceUrl), Widget Function(BuildContext context) builder,
) =>
Navigator.of(context).push(CupertinoPageRoute(
builder: builder,
)); ));
void goToInstance(BuildContext context, String instanceUrl) =>
goTo(context, (context) => InstancePage(instanceUrl: instanceUrl));
// ignore: camel_case_types // ignore: camel_case_types
abstract class goToCommunity { abstract class goToCommunity {
/// Navigates to `CommunityPage` /// Navigates to `CommunityPage`
static void byId(BuildContext context, String instanceUrl, int communityId) => static void byId(BuildContext context, String instanceUrl, int communityId) =>
Navigator.of(context).push(MaterialPageRoute( goTo(
builder: (context) => CommunityPage.fromId( context,
(context) => CommunityPage.fromId(
instanceUrl: instanceUrl, communityId: communityId), instanceUrl: instanceUrl, communityId: communityId),
)); );
static void byName( static void byName(
BuildContext context, String instanceUrl, String communityName) => BuildContext context, String instanceUrl, String communityName) =>
Navigator.of(context).push(MaterialPageRoute( goTo(
builder: (context) => CommunityPage.fromName( context,
(context) => CommunityPage.fromName(
instanceUrl: instanceUrl, communityName: communityName), instanceUrl: instanceUrl, communityName: communityName),
)); );
} }
// ignore: camel_case_types // ignore: camel_case_types
abstract class goToUser { abstract class goToUser {
static void byId(BuildContext context, String instanceUrl, int userId) => static void byId(BuildContext context, String instanceUrl, int userId) =>
Navigator.of(context).push(MaterialPageRoute( goTo(context,
builder: (context) => (context) => UserPage(instanceUrl: instanceUrl, userId: userId));
UserPage(instanceUrl: instanceUrl, userId: userId)));
static void byName( static void byName(
BuildContext context, String instanceUrl, String userName) => BuildContext context, String instanceUrl, String userName) =>
throw UnimplementedError('need to create UserProfile constructor first'); throw UnimplementedError('need to create UserProfile constructor first');
} }
void goToPost(BuildContext context, String instanceUrl, int postId) => void goToPost(BuildContext context, String instanceUrl, int postId) => goTo(
Navigator.of(context).push(MaterialPageRoute( context, (context) => FullPostPage(instanceUrl: instanceUrl, id: postId));
builder: (context) =>
FullPostPage(instanceUrl: instanceUrl, id: postId)));

View File

@ -11,7 +11,7 @@ class Badge extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
var theme = Theme.of(context); final theme = Theme.of(context);
return Container( return Container(
height: 25, height: 25,

View File

@ -8,7 +8,7 @@ class BottomModal extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
var theme = Theme.of(context); final theme = Theme.of(context);
return SafeArea( return SafeArea(
child: Padding( child: Padding(

View File

@ -314,7 +314,7 @@ class Comment extends HookWidget {
: BorderSide.none, : BorderSide.none,
top: BorderSide(width: 0.2))), top: BorderSide(width: 0.2))),
), ),
for (var c in commentTree.children) for (final c in commentTree.children)
Comment( Comment(
c, c,
indent: indent + 1, indent: indent + 1,

View File

@ -34,9 +34,9 @@ class CommentSection extends HookWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
var sorting = useState(sortType); final sorting = useState(sortType);
var rawComms = useState(rawComments); final rawComms = useState(rawComments);
var comms = useState(comments); final comms = useState(comments);
void sortComments(CommentSortType sort) { void sortComments(CommentSortType sort) {
if (sort != sorting.value && sort != CommentSortType.chat) { if (sort != sorting.value && sort != CommentSortType.chat) {
@ -106,7 +106,8 @@ class CommentSection extends HookWidget {
postCreatorId: postCreatorId, postCreatorId: postCreatorId,
) )
else else
for (var com in comms.value) Comment(com, postCreatorId: postCreatorId), for (final com in comms.value)
Comment(com, postCreatorId: postCreatorId),
]); ]);
} }
} }

View File

@ -1,6 +1,8 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import '../pages/media_view.dart'; import '../pages/media_view.dart';
import '../util/goto.dart';
class FullscreenableImage extends StatelessWidget { class FullscreenableImage extends StatelessWidget {
final String url; final String url;
@ -13,9 +15,7 @@ class FullscreenableImage extends StatelessWidget {
}) : super(key: key); }) : super(key: key);
_onTap(BuildContext c) { _onTap(BuildContext c) {
Navigator.of(c).push(MaterialPageRoute( goTo(c, (context) => MediaViewPage(url));
builder: (context) => MediaViewPage(url),
));
} }
@override @override

View File

@ -1,5 +1,6 @@
import 'package:cached_network_image/cached_network_image.dart'; import 'package:cached_network_image/cached_network_image.dart';
import 'package:esys_flutter_share/esys_flutter_share.dart'; import 'package:esys_flutter_share/esys_flutter_share.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/gestures.dart'; import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:intl/intl.dart'; import 'package:intl/intl.dart';
@ -150,7 +151,7 @@ class Post extends StatelessWidget {
final urlDomain = () { final urlDomain = () {
if (post.url == null) return null; if (post.url == null) return null;
var url = post.url.split('/')[2]; final url = post.url.split('/')[2];
if (url.startsWith('www.')) return url.substring(4); if (url.startsWith('www.')) return url.substring(4);
return url; return url;
}(); }();
@ -425,9 +426,10 @@ class Post extends StatelessWidget {
child: InkWell( child: InkWell(
onTap: fullPost onTap: fullPost
? null ? null
: () => Navigator.of(context).push(MaterialPageRoute( : () => goTo(
builder: (context) => FullPostPage.fromPostView( context,
post))), //, instanceUrl, post.id), (context) =>
FullPostPage.fromPostView(post)), //, instanceUrl, post.id),
child: Column( child: Column(
children: [ children: [
info(), info(),

View File

@ -16,7 +16,7 @@ class PostListOptions extends HookWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
var sort = useState(defaultSort); final sort = useState(defaultSort);
void selectSortType(BuildContext context) { void selectSortType(BuildContext context) {
showModalBottomSheet( showModalBottomSheet(
@ -27,7 +27,7 @@ class PostListOptions extends HookWidget {
title: 'sort by', title: 'sort by',
child: Column( child: Column(
children: [ children: [
for (var x in SortType.values) for (final x in SortType.values)
RadioListTile<SortType>( RadioListTile<SortType>(
value: x, value: x,
groupValue: sort.value, groupValue: sort.value,

View File

@ -29,13 +29,13 @@ class UserProfile extends HookWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
var theme = Theme.of(context); final theme = Theme.of(context);
final colorOnTopOfAccentColor = final colorOnTopOfAccentColor =
textColorBasedOnBackground(theme.accentColor); textColorBasedOnBackground(theme.accentColor);
var userViewSnap = useFuture(_userView, preserveState: false); final userViewSnap = useFuture(_userView, preserveState: false);
Widget bio = () { final bio = () {
if (userViewSnap.hasData) { if (userViewSnap.hasData) {
if (userViewSnap.data.bio != null) { if (userViewSnap.data.bio != null) {
return Padding( return Padding(