Merge pull request #96 from krawieck/new-lint-rules

This commit is contained in:
Filip Krawczyk 2021-01-04 00:35:50 +01:00 committed by GitHub
commit c3c6c05f7f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
36 changed files with 592 additions and 513 deletions

View File

@ -3,9 +3,58 @@ include: package:effective_dart/analysis_options.yaml
linter:
rules:
public_member_api_docs: false
prefer_expression_function_bodies: true
prefer_single_quotes: true
prefer_final_locals: true
prefer_expression_function_bodies: true
avoid_bool_literals_in_conditional_expressions: true
exhaustive_cases: true
prefer_for_elements_to_map_fromIterable: true
prefer_if_null_operators: true
prefer_is_not_operator: true
use_is_even_rather_than_modulo: true
unnecessary_string_escapes: true
use_full_hex_values_for_flutter_colors: true
sort_unnamed_constructors_first: true
use_raw_strings: true
unnecessary_string_interpolations: true
void_checks: true
unnecessary_null_in_if_null_operators: true
unnecessary_raw_strings: true
unnecessary_null_aware_assignments: true
unnecessary_parenthesis: true
prefer_if_elements_to_conditional_expressions: true
unawaited_futures: true
prefer_typing_uninitialized_variables: true
sized_box_for_whitespace: true
recursive_getters: true
prefer_int_literals: true
prefer_spread_collections: true
prefer_null_aware_operators: true
prefer_final_in_for_each: true
prefer_contains: true
prefer_constructors_over_static_methods: true
prefer_conditional_assignment: true
prefer_asserts_in_initializer_lists: true
parameter_assignments: true
avoid_unused_constructor_parameters: true
empty_catches: true
cascade_invocations: true
await_only_futures: true
avoid_void_async: true
avoid_unnecessary_containers: true
avoid_single_cascade_in_expression_statements: true
avoid_returning_null_for_void: true
avoid_redundant_argument_values: true
avoid_escaping_inner_quotes: true
sort_child_properties_last: true
prefer_const_constructors: true
prefer_const_declarations: true
prefer_const_literals_to_create_immutables: true
prefer_const_constructors_in_immutables: true
analyzer:
exclude:
- "**/*.g.dart"
strong-mode:
implicit-casts: false

View File

@ -7,22 +7,20 @@ import 'ref.dart';
class Debounce {
final bool loading;
final void Function() callback;
void call() => callback();
// void dispose() {}
final VoidCallback callback;
const Debounce({
@required this.loading,
@required this.callback,
});
void call() => callback();
}
/// will run `callback()` after debounce hook hasn't been called for the
/// specified `delayDuration`
Debounce useDebounce(
Future<Null> Function() callback, [
Future<void> Function() callback, [
Duration delayDuration = const Duration(seconds: 1),
]) {
final loading = useState(false);

View File

@ -8,8 +8,8 @@ import 'ref.dart';
class DelayedLoading {
final bool pending;
final bool loading;
final void Function() start;
final void Function() cancel;
final VoidCallback start;
final VoidCallback cancel;
const DelayedLoading({
@required this.pending,

View File

@ -11,14 +11,15 @@ import 'stores.dart';
/// Snackbar is rendered. If [any] is set to true, this check is performed for
/// all instances and if any of them have an account, the wrapped action will be
/// called with a null token.
Function(
Function(Jwt token) action, [
VoidCallback Function(
void Function(Jwt token) action, [
String message,
]) useLoggedInAction(String instanceHost, {bool any = false}) {
final context = useContext();
final store = useAccountsStore();
return (Function(Jwt token) action, [message]) {
return (action, [message]) {
if (any && store.hasNoAccount ||
!any && store.isAnonymousFor(instanceHost)) {
return () {

View File

@ -33,12 +33,14 @@ Future<void> main() async {
value: accountsStore,
),
],
child: MyApp(),
child: const MyApp(),
),
);
}
class MyApp extends HookWidget {
const MyApp();
@override
Widget build(BuildContext context) {
final configStore = useConfigStore();
@ -57,24 +59,26 @@ class MyApp extends HookWidget {
theme: ThemeData(
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: MyHomePage(),
home: const MyHomePage(),
);
}
}
class TemporarySearchTab extends HookWidget {
const TemporarySearchTab();
@override
Widget build(BuildContext context) {
final accStore = useAccountsStore();
return ListView(
children: [
ListTile(
const ListTile(
title: Center(
child: Text('🚧 this tab is still under construction 🚧\n'
'but you can open your instances in a browser '
' for missing functionality')),
),
Divider(),
const Divider(),
for (final inst in accStore.instances)
ListTile(
title: Text(inst),
@ -86,7 +90,9 @@ class TemporarySearchTab extends HookWidget {
}
class MyHomePage extends HookWidget {
final List<Widget> pages = [
const MyHomePage();
static const List<Widget> pages = [
HomeTab(),
CommunitiesTab(),
TemporarySearchTab(), // TODO: search tab
@ -126,20 +132,20 @@ class MyHomePage extends HookWidget {
index: currentTab.value,
children: pages,
),
floatingActionButton: CreatePostFab(),
floatingActionButton: const CreatePostFab(),
floatingActionButtonLocation: FloatingActionButtonLocation.centerDocked,
bottomNavigationBar: BottomAppBar(
shape: CircularNotchedRectangle(),
shape: const CircularNotchedRectangle(),
notchMargin: 7,
child: Container(
child: SizedBox(
height: 60,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
tabButton(Icons.home),
tabButton(Icons.list),
SizedBox.shrink(),
SizedBox.shrink(),
const SizedBox.shrink(),
const SizedBox.shrink(),
tabButton(Icons.search),
tabButton(Icons.person),
],

View File

@ -58,11 +58,11 @@ class AddAccountPage extends HookWidget {
title: Text(i),
),
ListTile(
leading: Padding(
padding: const EdgeInsets.all(8),
leading: const Padding(
padding: EdgeInsets.all(8),
child: Icon(Icons.add),
),
title: Text('Add instance'),
title: const Text('Add instance'),
onTap: () async {
final val = await showCupertinoModalPopup<String>(
context: context,
@ -99,13 +99,13 @@ class AddAccountPage extends HookWidget {
return Scaffold(
key: scaffoldKey,
appBar: AppBar(
leading: CloseButton(),
leading: const CloseButton(),
actionsIconTheme: theme.iconTheme,
iconTheme: theme.iconTheme,
textTheme: theme.textTheme,
brightness: theme.brightness,
centerTitle: true,
title: Text('Add account'),
title: const Text('Add account'),
backgroundColor: theme.canvasColor,
shadowColor: Colors.transparent,
),
@ -114,7 +114,7 @@ class AddAccountPage extends HookWidget {
child: ListView(
children: [
if (icon.value == null)
SizedBox(height: 150)
const SizedBox(height: 150)
else
SizedBox(
height: 150,
@ -122,22 +122,22 @@ class AddAccountPage extends HookWidget {
url: icon.value,
child: CachedNetworkImage(
imageUrl: icon.value,
errorWidget: (_, __, ___) => SizedBox.shrink(),
errorWidget: (_, __, ___) => const SizedBox.shrink(),
),
),
),
FlatButton(
onPressed: selectInstance,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(selectedInstance.value),
Icon(Icons.arrow_drop_down),
const Icon(Icons.arrow_drop_down),
],
),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10),
),
),
// TODO: add support for password managers
TextField(
@ -146,7 +146,7 @@ class AddAccountPage extends HookWidget {
decoration: InputDecoration(
isDense: true,
contentPadding:
EdgeInsets.symmetric(horizontal: 10, vertical: 10),
const EdgeInsets.symmetric(horizontal: 10, vertical: 10),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(10),
),
@ -160,7 +160,7 @@ class AddAccountPage extends HookWidget {
decoration: InputDecoration(
isDense: true,
contentPadding:
EdgeInsets.symmetric(horizontal: 10, vertical: 10),
const EdgeInsets.symmetric(horizontal: 10, vertical: 10),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(10),
),
@ -169,12 +169,18 @@ class AddAccountPage extends HookWidget {
),
RaisedButton(
color: theme.accentColor,
padding: EdgeInsets.all(0),
padding: const EdgeInsets.all(0),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10),
),
onPressed: usernameController.text.isEmpty ||
passwordController.text.isEmpty
? null
: loading.pending
? () {}
: handleOnAdd,
child: !loading.loading
? Text('Sign in')
? const Text('Sign in')
: SizedBox(
width: 20,
height: 20,
@ -183,21 +189,15 @@ class AddAccountPage extends HookWidget {
AlwaysStoppedAnimation<Color>(theme.canvasColor),
),
),
onPressed: usernameController.text.isEmpty ||
passwordController.text.isEmpty
? null
: loading.pending
? () {}
: handleOnAdd,
),
FlatButton(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10),
),
child: Text('Register'),
onPressed: () {
ul.launch('https://${selectedInstance.value}/login');
},
child: const Text('Register'),
),
],
),

View File

@ -68,10 +68,10 @@ class AddInstancePage extends HookWidget {
shadowColor: Colors.transparent,
iconTheme: theme.iconTheme,
centerTitle: true,
leading: CloseButton(),
leading: const CloseButton(),
actionsIconTheme: theme.iconTheme,
textTheme: theme.textTheme,
title: Text('Add instance'),
title: const Text('Add instance'),
),
body: ListView(
children: [
@ -79,26 +79,26 @@ class AddInstancePage extends HookWidget {
SizedBox(
height: 150,
child: FullscreenableImage(
url: icon.value,
child: CachedNetworkImage(
imageUrl: icon.value,
errorWidget: (_, __, ___) => SizedBox.shrink(),
errorWidget: (_, __, ___) => const SizedBox.shrink(),
),
url: icon.value,
))
else if (isSite.value == false)
SizedBox(
height: 150,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
children: const [
Icon(Icons.close, color: Colors.red),
Text('instance not found')
],
),
)
else
SizedBox(height: 150),
SizedBox(height: 15),
const SizedBox(height: 150),
const SizedBox(height: 15),
SizedBox(
height: 40,
child: Padding(
@ -117,7 +117,7 @@ class AddInstancePage extends HookWidget {
),
),
),
SizedBox(height: 5),
const SizedBox(height: 5),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 15),
child: SizedBox(
@ -127,8 +127,9 @@ class AddInstancePage extends HookWidget {
borderRadius: BorderRadius.circular(10),
),
color: theme.accentColor,
onPressed: isSite.value == true ? handleOnAdd : null,
child: !debounce.loading
? Text('Add')
? const Text('Add')
: SizedBox(
height: 20,
width: 20,
@ -137,7 +138,6 @@ class AddInstancePage extends HookWidget {
AlwaysStoppedAnimation<Color>(theme.canvasColor),
),
),
onPressed: isSite.value == true ? handleOnAdd : null,
),
),
),

View File

@ -35,7 +35,7 @@ class CommunitiesListPage extends StatelessWidget {
fetcher: fetcher,
builder: (community) => Column(
children: [
Divider(),
const Divider(),
ListTile(
title: Text(community.name),
subtitle: community.description != null
@ -61,9 +61,9 @@ class CommunitiesListPage extends StatelessWidget {
fit: BoxFit.cover, image: imageProvider),
),
),
errorWidget: (_, __, ___) => SizedBox(width: 50),
errorWidget: (_, __, ___) => const SizedBox(width: 50),
)
: SizedBox(width: 50),
: const SizedBox(width: 50),
),
],
),

View File

@ -16,7 +16,7 @@ import '../util/text_color.dart';
/// List of subscribed communities per instance
class CommunitiesTab extends HookWidget {
CommunitiesTab();
const CommunitiesTab();
@override
Widget build(BuildContext context) {
@ -64,9 +64,9 @@ class CommunitiesTab extends HookWidget {
body: Center(
child: Row(
children: [
Icon(Icons.error),
const Icon(Icons.error),
Padding(
padding: const EdgeInsets.all(8.0),
padding: const EdgeInsets.all(8),
child: Text(
communitiesSnap.error?.toString() ??
instancesSnap.error?.toString(),
@ -79,7 +79,7 @@ class CommunitiesTab extends HookWidget {
} else if (!communitiesSnap.hasData || !instancesSnap.hasData) {
return Scaffold(
appBar: AppBar(),
body: Center(
body: const Center(
child: CircularProgressIndicator(),
),
);
@ -92,7 +92,7 @@ class CommunitiesTab extends HookWidget {
final filterIcon = () {
if (filterController.text.isEmpty) {
return Icon(Icons.filter_list);
return const Icon(Icons.filter_list);
}
return IconButton(
@ -100,7 +100,7 @@ class CommunitiesTab extends HookWidget {
filterController.clear();
primaryFocus.unfocus();
},
icon: Icon(Icons.clear),
icon: const Icon(Icons.clear),
);
}();
@ -121,7 +121,7 @@ class CommunitiesTab extends HookWidget {
appBar: AppBar(
actions: [
IconButton(
icon: Icon(Icons.style),
icon: const Icon(Icons.style),
onPressed: () {}, // TODO: change styles?
),
],
@ -131,14 +131,14 @@ class CommunitiesTab extends HookWidget {
decoration: InputDecoration(
suffixIcon: filterIcon,
isDense: true,
border: OutlineInputBorder(),
border: const OutlineInputBorder(),
hintText: 'Filter', // TODO: hint with an filter icon
),
),
),
body: ListView(
children: [
for (final i in Iterable.generate(amountOfDisplayInstances))
for (var i = 0; i < amountOfDisplayInstances; i++)
Column(
children: [
ListTile(
@ -157,9 +157,10 @@ class CommunitiesTab extends HookWidget {
fit: BoxFit.cover, image: imageProvider),
),
),
errorWidget: (_, __, ___) => SizedBox(width: 50),
errorWidget: (_, __, ___) =>
const SizedBox(width: 50),
)
: SizedBox(width: 50),
: const SizedBox(width: 50),
title: Text(
instances[i].name,
style: theme.textTheme.headline6,
@ -201,11 +202,11 @@ class CommunitiesTab extends HookWidget {
),
),
errorWidget: (_, __, ___) =>
SizedBox(width: 30),
const SizedBox(width: 30),
)
else
SizedBox(width: 30),
SizedBox(width: 10),
const SizedBox(width: 30),
const SizedBox(width: 10),
Text(
'''!${comm.communityName}${comm.isLocal ? '' : '@${comm.originInstanceHost}'}''',
),
@ -229,7 +230,7 @@ class _CommunitySubscribeToggle extends HookWidget {
final int communityId;
final String instanceHost;
_CommunitySubscribeToggle(
const _CommunitySubscribeToggle(
{@required this.instanceHost, @required this.communityId})
: assert(instanceHost != null),
assert(communityId != null);
@ -238,7 +239,7 @@ class _CommunitySubscribeToggle extends HookWidget {
Widget build(BuildContext context) {
final theme = Theme.of(context);
final subbed = useState(true);
final delayed = useDelayedLoading(const Duration(milliseconds: 500));
final delayed = useDelayedLoading();
final accountsStore = useAccountsStore();
handleTap() async {
@ -271,7 +272,7 @@ class _CommunitySubscribeToggle extends HookWidget {
borderRadius: BorderRadius.circular(7),
),
child: delayed.loading
? Container(
? const SizedBox(
width: 20, height: 20, child: CircularProgressIndicator())
: Icon(
subbed.value ? Icons.done : Icons.add,

View File

@ -29,14 +29,14 @@ class CommunityPage extends HookWidget {
final String communityName;
final int communityId;
CommunityPage.fromName({
const CommunityPage.fromName({
@required this.communityName,
@required this.instanceHost,
}) : assert(communityName != null),
assert(instanceHost != null),
communityId = null,
_community = null;
CommunityPage.fromId({
const CommunityPage.fromId({
@required this.communityId,
@required this.instanceHost,
}) : assert(communityId != null),
@ -96,13 +96,13 @@ class CommunityPage extends HookWidget {
mainAxisAlignment: MainAxisAlignment.center,
children: [
if (fullCommunitySnap.hasError) ...[
Icon(Icons.error),
const Icon(Icons.error),
Padding(
padding: const EdgeInsets.all(8),
child: Text('ERROR: ${fullCommunitySnap.error}'),
)
] else
CircularProgressIndicator(semanticsLabel: 'loading')
const CircularProgressIndicator(semanticsLabel: 'loading')
],
),
),
@ -121,16 +121,16 @@ class CommunityPage extends HookWidget {
child: Column(
children: [
ListTile(
leading: Icon(Icons.open_in_browser),
title: Text('Open in browser'),
leading: const Icon(Icons.open_in_browser),
title: const Text('Open in browser'),
onTap: () async => await ul.canLaunch(community.actorId)
? ul.launch(community.actorId)
: Scaffold.of(context).showSnackBar(
SnackBar(content: Text("can't open in browser"))),
const SnackBar(content: Text("can't open in browser"))),
),
ListTile(
leading: Icon(Icons.info_outline),
title: Text('Nerd stuff'),
leading: const Icon(Icons.info_outline),
title: const Text('Nerd stuff'),
onTap: () {
showInfoTablePopup(context, {
'id': community.id,
@ -155,7 +155,6 @@ class CommunityPage extends HookWidget {
// TODO: change top section to be more flexible
SliverAppBar(
expandedHeight: 300,
floating: false,
pinned: true,
elevation: 0,
backgroundColor: theme.cardColor,
@ -164,7 +163,7 @@ class CommunityPage extends HookWidget {
title: Text('!${community.name}',
style: TextStyle(color: colorOnCard)),
actions: [
IconButton(icon: Icon(Icons.share), onPressed: _share),
IconButton(icon: const Icon(Icons.share), onPressed: _share),
IconButton(icon: Icon(moreIcon), onPressed: _openMoreMenu),
],
flexibleSpace: FlexibleSpaceBar(
@ -177,7 +176,7 @@ class CommunityPage extends HookWidget {
TabBar(
labelColor: theme.textTheme.bodyText1.color,
unselectedLabelColor: Colors.grey,
tabs: [
tabs: const [
Tab(text: 'Posts'),
Tab(text: 'Comments'),
Tab(text: 'About'),
@ -227,7 +226,7 @@ class _CommunityOverview extends StatelessWidget {
final CommunityView community;
final String instanceHost;
_CommunityOverview(
const _CommunityOverview(
this.community, {
@required this.instanceHost,
}) : assert(instanceHost != null),
@ -253,7 +252,7 @@ class _CommunityOverview extends StatelessWidget {
color: Colors.black.withOpacity(0.7), blurRadius: 3)
]),
),
Container(
SizedBox(
width: 83,
height: 83,
child: FullscreenableImage(
@ -269,7 +268,7 @@ class _CommunityOverview extends StatelessWidget {
),
),
),
errorWidget: (_, __, ___) => Icon(Icons.warning),
errorWidget: (_, __, ___) => const Icon(Icons.warning),
),
),
),
@ -283,48 +282,45 @@ class _CommunityOverview extends StatelessWidget {
url: community.banner,
child: CachedNetworkImage(
imageUrl: community.banner,
errorWidget: (_, __, ___) => SizedBox.shrink(),
errorWidget: (_, __, ___) => const SizedBox.shrink(),
),
),
SafeArea(
child: Padding(
padding: const EdgeInsets.only(top: 45),
child: Column(children: [
if (community.icon != null)
Center(
child: Padding(
padding: const EdgeInsets.only(top: 0),
child: Center(child: icon),
),
),
if (community.icon != null) icon,
// NAME
Center(
child: Padding(
padding: const EdgeInsets.only(top: 10),
child: RichText(
overflow: TextOverflow.ellipsis, // TODO: fix overflowing
text: TextSpan(
style: theme.textTheme.subtitle1.copyWith(shadows: [shadow]),
children: [
TextSpan(
text: '!',
style: TextStyle(fontWeight: FontWeight.w200)),
TextSpan(
text: community.name,
style: TextStyle(fontWeight: FontWeight.w600)),
TextSpan(
text: '@',
style: TextStyle(fontWeight: FontWeight.w200)),
TextSpan(
child: Padding(
padding: const EdgeInsets.only(top: 10),
child: RichText(
overflow: TextOverflow.ellipsis, // TODO: fix overflowing
text: TextSpan(
style:
theme.textTheme.subtitle1.copyWith(shadows: [shadow]),
children: [
const TextSpan(
text: '!',
style: TextStyle(fontWeight: FontWeight.w200)),
TextSpan(
text: community.name,
style: const TextStyle(fontWeight: FontWeight.w600)),
const TextSpan(
text: '@',
style: TextStyle(fontWeight: FontWeight.w200)),
TextSpan(
text: community.originInstanceHost,
style: TextStyle(fontWeight: FontWeight.w600),
style: const TextStyle(fontWeight: FontWeight.w600),
recognizer: TapGestureRecognizer()
..onTap = () => goToInstance(
context, community.originInstanceHost)),
],
context, community.originInstanceHost),
),
],
),
),
),
)),
),
// TITLE/MOTTO
Center(
child: Padding(
@ -345,21 +341,21 @@ class _CommunityOverview extends StatelessWidget {
padding: const EdgeInsets.only(top: 5),
child: Row(
children: [
Spacer(),
Padding(
padding: const EdgeInsets.only(right: 3),
const Spacer(),
const Padding(
padding: EdgeInsets.only(right: 3),
child: Icon(Icons.people, size: 20),
),
Text(compactNumber(community.numberOfSubscribers)),
Spacer(
const Spacer(
flex: 4,
),
Padding(
padding: const EdgeInsets.only(right: 3),
const Padding(
padding: EdgeInsets.only(right: 3),
child: Icon(Icons.record_voice_over, size: 20),
),
Text('xx'), // TODO: display online users
Spacer(),
const Text('xx'), // TODO: display online users
const Spacer(),
],
),
),
@ -375,10 +371,10 @@ class _CommunityOverview extends StatelessWidget {
}
class _SliverAppBarDelegate extends SliverPersistentHeaderDelegate {
_SliverAppBarDelegate(this._tabBar);
final TabBar _tabBar;
const _SliverAppBarDelegate(this._tabBar);
@override
double get minExtent => _tabBar.preferredSize.height;
@override
@ -388,7 +384,7 @@ class _SliverAppBarDelegate extends SliverPersistentHeaderDelegate {
Widget build(
BuildContext context, double shrinkOffset, bool overlapsContent) {
final theme = Theme.of(context);
return Container(child: _tabBar, color: theme.cardColor);
return Container(color: theme.cardColor, child: _tabBar);
}
@override
@ -418,7 +414,7 @@ class _AboutTab extends StatelessWidget {
final theme = Theme.of(context);
return ListView(
padding: EdgeInsets.only(top: 20),
padding: const EdgeInsets.only(top: 20),
children: [
if (community.description != null) ...[
Padding(
@ -426,7 +422,7 @@ class _AboutTab extends StatelessWidget {
child: MarkdownText(community.description,
instanceHost: community.instanceHost),
),
_Divider(),
const _Divider(),
],
SizedBox(
height: 25,
@ -434,8 +430,8 @@ class _AboutTab extends StatelessWidget {
scrollDirection: Axis.horizontal,
children: [
// TODO: consider using Chips
Padding(
padding: const EdgeInsets.only(left: 7),
const Padding(
padding: EdgeInsets.only(left: 7),
child: _Badge('X users online'),
),
_Badge(
@ -450,29 +446,29 @@ class _AboutTab extends StatelessWidget {
],
),
),
_Divider(),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 15, vertical: 0),
child: OutlineButton(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10),
),
child: Text('${community.categoryName}'),
onPressed: goToCategories,
),
),
_Divider(),
const _Divider(),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 15),
child: OutlineButton(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10),
),
child: Text('Modlog'),
onPressed: goToModlog,
onPressed: goToCategories,
child: Text(community.categoryName),
),
),
_Divider(),
const _Divider(),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 15),
child: OutlineButton(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10),
),
onPressed: goToModlog,
child: const Text('Modlog'),
),
),
const _Divider(),
if (moderators != null && moderators.isNotEmpty) ...[
Padding(
padding: const EdgeInsets.symmetric(horizontal: 15),
@ -493,7 +489,7 @@ class _Badge extends StatelessWidget {
final String text;
final bool noPad;
_Badge(this.text, {this.noPad = false});
const _Badge(this.text, {this.noPad = false});
@override
Widget build(BuildContext context) {
@ -513,9 +509,11 @@ class _Badge extends StatelessWidget {
}
class _Divider extends StatelessWidget {
const _Divider();
@override
Widget build(BuildContext context) => Padding(
padding: const EdgeInsets.symmetric(horizontal: 15, vertical: 10),
Widget build(BuildContext context) => const Padding(
padding: EdgeInsets.symmetric(horizontal: 15, vertical: 10),
child: Divider(),
);
}
@ -523,14 +521,14 @@ class _Divider extends StatelessWidget {
class _FollowButton extends HookWidget {
final CommunityView community;
_FollowButton(this.community);
const _FollowButton(this.community);
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
final isSubbed = useState(community.subscribed ?? false);
final delayed = useDelayedLoading(const Duration(milliseconds: 500));
final delayed = useDelayedLoading();
final loggedInAction = useLoggedInAction(community.instanceHost);
final colorOnTopOfAccent = textColorBasedOnBackground(theme.accentColor);
@ -548,8 +546,8 @@ class _FollowButton extends HookWidget {
Scaffold.of(context).showSnackBar(SnackBar(
content: Row(
children: [
Icon(Icons.warning),
SizedBox(width: 10),
const Icon(Icons.warning),
const SizedBox(width: 10),
Text("couldn't ${isSubbed.value ? 'un' : ''}sub :<"),
],
),
@ -566,17 +564,18 @@ class _FollowButton extends HookWidget {
child: delayed.loading
? RaisedButton(
onPressed: null,
child: SizedBox(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20),
),
child: const SizedBox(
height: 15,
width: 15,
child: CircularProgressIndicator(),
),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20),
),
)
: RaisedButton.icon(
padding: EdgeInsets.symmetric(vertical: 5, horizontal: 20),
padding:
const EdgeInsets.symmetric(vertical: 5, horizontal: 20),
onPressed: loggedInAction(delayed.pending ? (_) {} : subscribe),
icon: isSubbed.value
? Icon(Icons.remove, size: 18, color: colorOnTopOfAccent)

View File

@ -12,19 +12,22 @@ import '../hooks/stores.dart';
import '../util/extensions/spaced.dart';
import '../util/goto.dart';
import '../util/pictrs.dart';
import '../util/unawaited.dart';
import '../widgets/markdown_text.dart';
import 'full_post.dart';
/// Fab that triggers the [CreatePost] modal
class CreatePostFab extends HookWidget {
const CreatePostFab();
@override
Widget build(BuildContext context) {
final loggedInAction = useLoggedInAction(null, any: true);
return FloatingActionButton(
child: Icon(Icons.add),
onPressed: loggedInAction((_) => showCupertinoModalPopup(
context: context, builder: (_) => CreatePost())),
child: const Icon(Icons.add),
);
}
}
@ -89,8 +92,8 @@ class CreatePost extends HookWidget {
print(urlController.text);
// ignore: avoid_catches_without_on_clauses
} catch (e) {
scaffoldKey.currentState
.showSnackBar(SnackBar(content: Text('Failed to upload image')));
scaffoldKey.currentState.showSnackBar(
const SnackBar(content: Text('Failed to upload image')));
} finally {
imageUploadLoading.value = false;
}
@ -133,7 +136,7 @@ class CreatePost extends HookWidget {
child: DropdownButtonHideUnderline(
child: DropdownButton<String>(
value: selectedCommunity.value?.name,
hint: Text('Community'),
hint: const Text('Community'),
onChanged: (val) => selectedCommunity.value =
allCommunitiesSnap.data.firstWhere((e) => e.name == val),
items: allCommunitiesSnap.hasData
@ -143,7 +146,7 @@ class CreatePost extends HookWidget {
child: Text(e.name),
))
.toList()
: [
: const [
DropdownMenuItem(
value: '',
child: CircularProgressIndicator(),
@ -158,16 +161,16 @@ class CreatePost extends HookWidget {
child: TextField(
enabled: pictrsDeleteToken.value == null,
controller: urlController,
decoration: InputDecoration(
decoration: const InputDecoration(
border: OutlineInputBorder(),
labelText: 'URL',
suffixIcon: Icon(Icons.link)),
),
),
SizedBox(width: 5),
const SizedBox(width: 5),
IconButton(
icon: imageUploadLoading.value
? CircularProgressIndicator()
? const CircularProgressIndicator()
: Icon(pictrsDeleteToken.value == null
? Icons.add_photo_alternate
: Icons.close),
@ -182,8 +185,8 @@ class CreatePost extends HookWidget {
controller: titleController,
minLines: 1,
maxLines: 2,
decoration:
InputDecoration(border: OutlineInputBorder(), labelText: 'Title'),
decoration: const InputDecoration(
border: OutlineInputBorder(), labelText: 'Title'),
);
final body = IndexedStack(
@ -195,8 +198,8 @@ class CreatePost extends HookWidget {
maxLines: null,
minLines: 5,
textAlignVertical: TextAlignVertical.top,
decoration:
InputDecoration(border: OutlineInputBorder(), labelText: 'Body'),
decoration: const InputDecoration(
border: OutlineInputBorder(), labelText: 'Body'),
),
Padding(
padding: const EdgeInsets.all(16),
@ -210,7 +213,7 @@ class CreatePost extends HookWidget {
handleSubmit() async {
if (selectedCommunity.value == null || titleController.text.isEmpty) {
scaffoldKey.currentState.showSnackBar(SnackBar(
scaffoldKey.currentState.showSnackBar(const SnackBar(
content: Text('Choosing a community and a title is required'),
));
return;
@ -229,12 +232,12 @@ class CreatePost extends HookWidget {
name: titleController.text,
communityId: selectedCommunity.value.id,
auth: token.raw);
goToReplace(context, (_) => FullPostPage.fromPostView(res));
unawaited(goToReplace(context, (_) => FullPostPage.fromPostView(res)));
return;
// ignore: avoid_catches_without_on_clauses
} catch (e) {
scaffoldKey.currentState
.showSnackBar(SnackBar(content: Text('Failed to post')));
.showSnackBar(const SnackBar(content: Text('Failed to post')));
}
delayed.cancel();
}
@ -243,7 +246,7 @@ class CreatePost extends HookWidget {
key: scaffoldKey,
appBar: AppBar(
leading: IconButton(
icon: Icon(Icons.close),
icon: const Icon(Icons.close),
onPressed: Navigator.of(context).pop,
),
actions: [
@ -255,7 +258,7 @@ class CreatePost extends HookWidget {
),
body: SafeArea(
child: ListView(
padding: EdgeInsets.all(5),
padding: const EdgeInsets.all(5),
children: [
instanceDropdown,
communitiesDropdown,
@ -273,15 +276,15 @@ class CreatePost extends HookWidget {
value: nsfw.value,
onChanged: (val) => nsfw.value = val,
),
Text('NSFW')
const Text('NSFW')
],
),
),
FlatButton(
onPressed: delayed.pending ? () {} : handleSubmit,
child: delayed.loading
? CircularProgressIndicator()
: Text('post'),
? const CircularProgressIndicator()
: const Text('post'),
)
],
),

View File

@ -19,7 +19,7 @@ class FullPostPage extends HookWidget {
final String instanceHost;
final PostView post;
FullPostPage({@required this.id, @required this.instanceHost})
const FullPostPage({@required this.id, @required this.instanceHost})
: assert(id != null),
assert(instanceHost != null),
post = null;
@ -77,9 +77,9 @@ class FullPostPage extends HookWidget {
return Scaffold(
appBar: AppBar(
leading: BackButton(),
leading: const BackButton(),
actions: [
IconButton(icon: Icon(Icons.share), onPressed: sharePost),
IconButton(icon: const Icon(Icons.share), onPressed: sharePost),
SavePostButton(post),
IconButton(
icon: Icon(moreIcon),
@ -88,7 +88,7 @@ class FullPostPage extends HookWidget {
),
floatingActionButton: FloatingActionButton(
onPressed: loggedInAction((_) => comment()),
child: Icon(Icons.comment)),
child: const Icon(Icons.comment)),
body: ListView(
physics: const AlwaysScrollableScrollPhysics(),
children: [
@ -103,15 +103,15 @@ class FullPostPage extends HookWidget {
const EdgeInsets.symmetric(horizontal: 10, vertical: 30),
child: Column(
children: [
Icon(Icons.error),
const Icon(Icons.error),
Text('Error: ${fullPostSnap.error}')
],
),
)
else
Container(
child: Center(child: CircularProgressIndicator()),
const Padding(
padding: EdgeInsets.only(top: 40),
child: Center(child: CircularProgressIndicator()),
),
],
));

View File

@ -20,6 +20,8 @@ import 'inbox.dart';
/// First thing users sees when opening the app
/// Shows list of posts from all or just specific instances
class HomeTab extends HookWidget {
const HomeTab();
@override
Widget build(BuildContext context) {
// TODO: needs to be an observer? for accounts changes
@ -31,15 +33,13 @@ class HomeTab extends HookWidget {
final isc = useInfiniteScrollController();
final theme = Theme.of(context);
final instancesIcons = useMemoFuture(() async {
final map = <String, String>{};
final instances = accStore.instances.toList(growable: false);
final sites = await Future.wait(instances
.map((e) => LemmyApi(e).v1.getSite().catchError((e) => null)));
for (var i in Iterable.generate(sites.length)) {
map[instances[i]] = sites[i].site.icon;
}
return map;
return {
for (var i = 0; i < sites.length; i++) instances[i]: sites[i].site.icon
};
});
handleListChange() async {
@ -52,8 +52,8 @@ class HomeTab extends HookWidget {
return BottomModal(
child: Column(
children: [
SizedBox(height: 5),
ListTile(
const SizedBox(height: 5),
const ListTile(
title: Text('EVERYTHING'),
dense: true,
contentPadding: EdgeInsets.zero,
@ -62,20 +62,20 @@ class HomeTab extends HookWidget {
leading: SizedBox.shrink(),
),
ListTile(
title: Text('Subscribed'),
leading: SizedBox(width: 20, height: 20),
onTap: () => pop(
_SelectedList(listingType: PostListingType.subscribed)),
title: const Text('Subscribed'),
leading: const SizedBox(width: 20, height: 20),
onTap: () => pop(const _SelectedList(
listingType: PostListingType.subscribed)),
),
ListTile(
title: Text('All'),
leading: SizedBox(width: 20, height: 20),
onTap: () =>
pop(_SelectedList(listingType: PostListingType.all)),
title: const Text('All'),
leading: const SizedBox(width: 20, height: 20),
onTap: () => pop(
const _SelectedList(listingType: PostListingType.all)),
),
for (final instance in accStore.instances) ...[
Padding(
padding: const EdgeInsets.symmetric(horizontal: 10),
const Padding(
padding: EdgeInsets.symmetric(horizontal: 10),
child: Divider(),
),
ListTile(
@ -88,8 +88,8 @@ class HomeTab extends HookWidget {
onTap: () => goToInstance(context, instance),
dense: true,
contentPadding: EdgeInsets.zero,
visualDensity:
VisualDensity(vertical: VisualDensity.minimumDensity),
visualDensity: const VisualDensity(
vertical: VisualDensity.minimumDensity),
leading: (instancesIcons.hasData &&
instancesIcons.data[instance] != null)
? Padding(
@ -104,7 +104,7 @@ class HomeTab extends HookWidget {
),
),
)
: SizedBox(width: 30),
: const SizedBox(width: 30),
),
ListTile(
title: Text(
@ -123,15 +123,15 @@ class HomeTab extends HookWidget {
listingType: PostListingType.subscribed,
instanceHost: instance,
)),
leading: SizedBox(width: 20),
leading: const SizedBox(width: 20),
),
ListTile(
title: Text('All'),
title: const Text('All'),
onTap: () => pop(_SelectedList(
listingType: PostListingType.all,
instanceHost: instance,
)),
leading: SizedBox(width: 20),
leading: const SizedBox(width: 20),
),
]
],
@ -159,7 +159,7 @@ class HomeTab extends HookWidget {
return Scaffold(
body: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
children: const [
Center(child: Text('there needs to be at least one instance')),
],
),
@ -171,16 +171,16 @@ class HomeTab extends HookWidget {
appBar: AppBar(
actions: [
IconButton(
icon: Icon(Icons.notifications),
onPressed: () => goTo(context, (_) => InboxPage()),
icon: const Icon(Icons.notifications),
onPressed: () => goTo(context, (_) => const InboxPage()),
)
],
centerTitle: true,
title: TextButton(
style: TextButton.styleFrom(
shape: RoundedRectangleBorder(
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(10))),
padding: EdgeInsets.symmetric(horizontal: 15),
padding: const EdgeInsets.symmetric(horizontal: 15),
primary: theme.buttonColor,
textStyle: theme.primaryTextTheme.headline6,
),
@ -217,7 +217,8 @@ class InfiniteHomeList extends HookWidget {
final Function onStyleChange;
final InfiniteScrollController controller;
final _SelectedList selectedList;
InfiniteHomeList({
const InfiniteHomeList({
@required this.selectedList,
this.onStyleChange,
this.controller,
@ -265,14 +266,16 @@ class InfiniteHomeList extends HookWidget {
));
final posts = await Future.wait(futures);
final newPosts = <PostView>[];
for (final i
in Iterable.generate(posts.map((e) => e.length).reduce(max))) {
final longest = posts.map((e) => e.length).reduce(max);
for (var i = 0; i < longest; i++) {
for (final el in posts) {
if (el.elementAt(i) != null) {
newPosts.add(el[i]);
}
}
}
return newPosts;
}
@ -291,7 +294,6 @@ class InfiniteHomeList extends HookWidget {
children: [
PostListOptions(
onChange: changeSorting,
defaultSort: SortType.active,
styleButton: onStyleChange != null,
),
],
@ -299,7 +301,7 @@ class InfiniteHomeList extends HookWidget {
builder: (post) => Column(
children: [
Post(post),
SizedBox(height: 20),
const SizedBox(height: 20),
],
),
padding: EdgeInsets.zero,
@ -320,7 +322,8 @@ class InfiniteHomeList extends HookWidget {
class _SelectedList {
final String instanceHost;
final PostListingType listingType;
_SelectedList({
const _SelectedList({
@required this.listingType,
this.instanceHost,
});

View File

@ -2,6 +2,8 @@ import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
class InboxPage extends HookWidget {
const InboxPage();
@override
Widget build(BuildContext context) => Scaffold(
appBar: AppBar(),

View File

@ -52,13 +52,13 @@ class InstancePage extends HookWidget {
mainAxisAlignment: MainAxisAlignment.center,
children: [
if (siteSnap.hasError) ...[
Icon(Icons.error),
const Icon(Icons.error),
Padding(
padding: const EdgeInsets.all(8),
child: Text('ERROR: ${siteSnap.error}'),
)
] else
CircularProgressIndicator(semanticsLabel: 'loading')
const CircularProgressIndicator(semanticsLabel: 'loading')
],
),
),
@ -87,7 +87,6 @@ class InstancePage extends HookWidget {
SliverAppBar(
brightness: theme.brightness,
expandedHeight: 200,
floating: false,
pinned: true,
elevation: 0,
backgroundColor: theme.cardColor,
@ -97,7 +96,7 @@ class InstancePage extends HookWidget {
style: TextStyle(color: colorOnCard),
),
actions: [
IconButton(icon: Icon(Icons.share), onPressed: _share),
IconButton(icon: const Icon(Icons.share), onPressed: _share),
IconButton(
icon: Icon(moreIcon),
onPressed: () => _openMoreMenu(context)),
@ -109,7 +108,7 @@ class InstancePage extends HookWidget {
url: site.site.banner,
child: CachedNetworkImage(
imageUrl: site.site.banner,
errorWidget: (_, __, ___) => SizedBox.shrink(),
errorWidget: (_, __, ___) => const SizedBox.shrink(),
),
),
SafeArea(
@ -125,7 +124,7 @@ class InstancePage extends HookWidget {
height: 100,
imageUrl: site.site.icon,
errorWidget: (_, __, ___) =>
Icon(Icons.warning),
const Icon(Icons.warning),
),
),
),
@ -144,7 +143,7 @@ class InstancePage extends HookWidget {
TabBar(
labelColor: theme.textTheme.bodyText1.color,
unselectedLabelColor: Colors.grey,
tabs: [
tabs: const [
Tab(text: 'Posts'),
Tab(text: 'Comments'),
Tab(text: 'About'),
@ -187,10 +186,10 @@ class InstancePage extends HookWidget {
}
class _SliverAppBarDelegate extends SliverPersistentHeaderDelegate {
_SliverAppBarDelegate(this._tabBar);
final TabBar _tabBar;
const _SliverAppBarDelegate(this._tabBar);
@override
double get minExtent => _tabBar.preferredSize.height;
@override
@ -200,7 +199,7 @@ class _SliverAppBarDelegate extends SliverPersistentHeaderDelegate {
Widget build(
BuildContext context, double shrinkOffset, bool overlapsContent) {
final theme = Theme.of(context);
return Container(child: _tabBar, color: theme.cardColor);
return Container(color: theme.cardColor, child: _tabBar);
}
@override
@ -267,23 +266,23 @@ class _AboutTab extends HookWidget {
instanceHost: instanceHost,
),
),
_Divider(),
const _Divider(),
SizedBox(
height: 25,
child: ListView(
scrollDirection: Axis.horizontal,
children: [
SizedBox(width: 7),
_Badge('X users online'),
const SizedBox(width: 7),
const _Badge('X users online'),
_Badge('${site.site.numberOfUsers} users'),
_Badge('${site.site.numberOfCommunities} communities'),
_Badge('${site.site.numberOfPosts} posts'),
_Badge('${site.site.numberOfComments} comments'),
SizedBox(width: 15),
const SizedBox(width: 15),
],
),
),
_Divider(),
const _Divider(),
ListTile(
title: Center(
child: Text(
@ -303,7 +302,7 @@ class _AboutTab extends HookWidget {
width: 50,
imageUrl: e.icon,
errorWidget: (_, __, ___) =>
SizedBox(width: 50, height: 50),
const SizedBox(width: 50, height: 50),
imageBuilder: (context, imageProvider) => Container(
decoration: BoxDecoration(
shape: BoxShape.circle,
@ -313,23 +312,23 @@ class _AboutTab extends HookWidget {
),
),
))
: SizedBox(width: 50),
: const SizedBox(width: 50),
))
else if (commSnap.hasError)
Padding(
padding: const EdgeInsets.all(8.0),
padding: const EdgeInsets.all(8),
child: Text("Can't load communities, ${commSnap.error}"),
)
else
Padding(
padding: const EdgeInsets.symmetric(vertical: 10),
const Padding(
padding: EdgeInsets.symmetric(vertical: 10),
child: CircularProgressIndicator(),
),
ListTile(
title: Center(child: Text('See all')),
title: const Center(child: Text('See all')),
onTap: goToCommunities,
),
_Divider(),
const _Divider(),
ListTile(
title: Center(
child: Text(
@ -353,7 +352,7 @@ class _AboutTab extends HookWidget {
width: 50,
imageUrl: e.avatar,
errorWidget: (_, __, ___) =>
SizedBox(width: 50, height: 50),
const SizedBox(width: 50, height: 50),
imageBuilder: (context, imageProvider) => Container(
decoration: BoxDecoration(
shape: BoxShape.circle,
@ -361,18 +360,18 @@ class _AboutTab extends HookWidget {
fit: BoxFit.cover, image: imageProvider),
),
))
: SizedBox(width: 50),
: const SizedBox(width: 50),
)),
_Divider(),
const _Divider(),
ListTile(
title: Center(child: Text('Banned users')),
title: const Center(child: Text('Banned users')),
onTap: () => goToBannedUsers(context),
),
ListTile(
title: Center(child: Text('Modlog')),
title: const Center(child: Text('Modlog')),
onTap: goToModLog,
),
SizedBox(height: 20),
const SizedBox(height: 20),
],
),
),
@ -383,7 +382,7 @@ class _AboutTab extends HookWidget {
class _Badge extends StatelessWidget {
final String text;
_Badge(this.text);
const _Badge(this.text);
@override
Widget build(BuildContext context) {
@ -403,9 +402,11 @@ class _Badge extends StatelessWidget {
}
class _Divider extends StatelessWidget {
const _Divider();
@override
Widget build(BuildContext context) => Padding(
padding: const EdgeInsets.symmetric(horizontal: 15, vertical: 10),
Widget build(BuildContext context) => const Padding(
padding: EdgeInsets.symmetric(horizontal: 15, vertical: 10),
child: Divider(),
);
}

View File

@ -20,7 +20,7 @@ class MediaViewPage extends HookWidget {
final isZoomedOut = useState(true);
notImplemented() {
_key.currentState.showSnackBar(SnackBar(
_key.currentState.showSnackBar(const SnackBar(
content: Text("this feature hasn't been implemented yet 😰")));
}
@ -51,16 +51,16 @@ class MediaViewPage extends HookWidget {
child: Column(
children: [
ListTile(
leading: Icon(Icons.link),
title: Text('Share link'),
leading: const Icon(Icons.link),
title: const Text('Share link'),
onTap: () {
Navigator.of(context).pop();
Share.text('Share image url', url, 'text/plain');
},
),
ListTile(
leading: Icon(Icons.image),
title: Text('Share file'),
leading: const Icon(Icons.image),
title: const Text('Share file'),
onTap: () {
Navigator.of(context).pop();
notImplemented();
@ -81,15 +81,15 @@ class MediaViewPage extends HookWidget {
? AppBar(
backgroundColor: Colors.black38,
shadowColor: Colors.transparent,
leading: CloseButton(),
leading: const CloseButton(),
actions: [
IconButton(
icon: Icon(Icons.share),
icon: const Icon(Icons.share),
tooltip: 'share',
onPressed: share,
),
IconButton(
icon: Icon(Icons.file_download),
icon: const Icon(Icons.file_download),
tooltip: 'download',
onPressed: notImplemented,
),
@ -115,7 +115,7 @@ class MediaViewPage extends HookWidget {
imageProvider: CachedNetworkImageProvider(url),
heroAttributes: PhotoViewHeroAttributes(tag: url),
loadingBuilder: (context, event) =>
Center(child: CircularProgressIndicator()),
const Center(child: CircularProgressIndicator()),
),
),
);

View File

@ -11,7 +11,7 @@ import 'settings.dart';
/// Profile page for a logged in user. The difference between this and
/// UserPage is that here you have access to settings
class UserProfileTab extends HookWidget {
UserProfileTab();
const UserProfileTab();
@override
Widget build(BuildContext context) {
@ -20,9 +20,9 @@ class UserProfileTab extends HookWidget {
final actions = [
IconButton(
icon: Icon(Icons.settings),
icon: const Icon(Icons.settings),
onPressed: () {
goTo(context, (_) => SettingsPage());
goTo(context, (_) => const SettingsPage());
},
)
];
@ -39,13 +39,13 @@ class UserProfileTab extends HookWidget {
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text('No account was added.'),
const Text('No account was added.'),
FlatButton.icon(
onPressed: () {
goTo(context, (_) => AccountsConfigPage());
},
icon: Icon(Icons.add),
label: Text('Add account'),
icon: const Icon(Icons.add),
label: const Text('Add account'),
)
],
),
@ -62,22 +62,6 @@ class UserProfileTab extends HookWidget {
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,
@ -114,6 +98,22 @@ class UserProfileTab extends HookWidget {
},
);
},
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,
),
],
),
),
actions: actions,
),

View File

@ -13,6 +13,8 @@ import 'add_instance.dart';
/// Page with a list of different settings sections
class SettingsPage extends StatelessWidget {
const SettingsPage();
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
@ -26,26 +28,24 @@ class SettingsPage extends StatelessWidget {
title: Text('Settings', style: theme.textTheme.headline6),
centerTitle: true,
),
body: Container(
child: ListView(
children: [
ListTile(
leading: Icon(Icons.person),
title: Text('Accounts'),
onTap: () {
goTo(context, (_) => AccountsConfigPage());
},
),
ListTile(
leading: Icon(Icons.color_lens),
title: Text('Appearance'),
onTap: () {
goTo(context, (_) => AppearanceConfigPage());
},
),
AboutTile()
],
),
body: ListView(
children: [
ListTile(
leading: const Icon(Icons.person),
title: const Text('Accounts'),
onTap: () {
goTo(context, (_) => AccountsConfigPage());
},
),
ListTile(
leading: const Icon(Icons.color_lens),
title: const Text('Appearance'),
onTap: () {
goTo(context, (_) => const AppearanceConfigPage());
},
),
const AboutTile()
],
),
);
}
@ -53,6 +53,8 @@ class SettingsPage extends StatelessWidget {
/// Settings for theme color, AMOLED switch
class AppearanceConfigPage extends HookWidget {
const AppearanceConfigPage();
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
@ -69,7 +71,7 @@ class AppearanceConfigPage extends HookWidget {
),
body: ListView(
children: [
_SectionHeading('Theme'),
const _SectionHeading('Theme'),
for (final theme in ThemeMode.values)
RadioListTile<ThemeMode>(
value: theme,
@ -80,7 +82,7 @@ class AppearanceConfigPage extends HookWidget {
},
),
SwitchListTile(
title: Text('AMOLED dark mode'),
title: const Text('AMOLED dark mode'),
value: configStore.amoledDarkMode,
onChanged: (checked) {
configStore.amoledDarkMode = checked;
@ -104,16 +106,16 @@ class AccountsConfigPage extends HookWidget {
if (await showDialog<bool>(
context: context,
builder: (context) => AlertDialog(
title: Text('Remove instance?'),
title: const Text('Remove instance?'),
content: Text('Are you sure you want to remove $instanceHost?'),
actions: [
FlatButton(
child: Text('no'),
onPressed: () => Navigator.of(context).pop(false),
child: const Text('no'),
),
FlatButton(
child: Text('yes'),
onPressed: () => Navigator.of(context).pop(true),
child: const Text('yes'),
),
],
),
@ -127,17 +129,17 @@ class AccountsConfigPage extends HookWidget {
if (await showDialog<bool>(
context: context,
builder: (context) => AlertDialog(
title: Text('Remove user?'),
title: const Text('Remove user?'),
content: Text(
'Are you sure you want to remove $username@$instanceHost?'),
actions: [
FlatButton(
child: Text('no'),
onPressed: () => Navigator.of(context).pop(false),
child: const Text('no'),
),
FlatButton(
child: Text('yes'),
onPressed: () => Navigator.of(context).pop(true),
child: const Text('yes'),
),
],
),
@ -159,14 +161,12 @@ class AccountsConfigPage extends HookWidget {
),
floatingActionButton: SpeedDial(
animatedIcon: AnimatedIcons.menu_close, // TODO: change to + => x
closeManually: false,
curve: Curves.bounceIn,
tooltip: 'Add account or instance',
child: Icon(Icons.add),
overlayColor: theme.canvasColor,
children: [
SpeedDialChild(
child: Icon(Icons.person_add),
child: const Icon(Icons.person_add),
label: 'Add account',
labelBackgroundColor: theme.canvasColor,
onTap: () => showCupertinoModalPopup(
@ -175,13 +175,14 @@ class AccountsConfigPage extends HookWidget {
AddAccountPage(instanceHost: accountsStore.instances.last)),
),
SpeedDialChild(
child: Icon(Icons.dns),
child: const Icon(Icons.dns),
labelBackgroundColor: theme.canvasColor,
label: 'Add instance',
onTap: () => showCupertinoModalPopup(
context: context, builder: (_) => AddInstancePage()),
),
],
child: const Icon(Icons.add),
),
body: ListView(
children: [
@ -199,18 +200,17 @@ class AccountsConfigPage extends HookWidget {
context: context,
builder: (_) => AddInstancePage(),
),
icon: Icon(Icons.add),
label: Text('Add instance')),
icon: const Icon(Icons.add),
label: const Text('Add instance')),
),
],
),
for (final entry in accountsStore.tokens.entries) ...[
SizedBox(height: 40),
const SizedBox(height: 40),
Slidable(
actionPane: SlidableBehindActionPane(),
actionPane: const SlidableBehindActionPane(),
secondaryActions: [
IconSlideAction(
closeOnTap: true,
onTap: () => removeInstanceDialog(entry.key),
icon: Icons.delete_sweep,
color: Colors.red,
@ -221,18 +221,17 @@ class AccountsConfigPage extends HookWidget {
color: theme.canvasColor,
child: ListTile(
dense: true,
contentPadding: EdgeInsets.only(left: 0, top: 0),
contentPadding: EdgeInsets.zero,
title: _SectionHeading(entry.key),
),
),
),
for (final username in entry.value.keys) ...[
Slidable(
actionPane: SlidableBehindActionPane(),
actionPane: const SlidableBehindActionPane(),
key: Key('$username@${entry.key}'),
secondaryActions: [
IconSlideAction(
closeOnTap: true,
onTap: () => removeUserDialog(entry.key, username),
icon: Icons.delete_sweep,
color: Colors.red,
@ -259,8 +258,8 @@ class AccountsConfigPage extends HookWidget {
],
if (entry.value.keys.isEmpty)
ListTile(
leading: Icon(Icons.add),
title: Text('Add account'),
leading: const Icon(Icons.add),
title: const Text('Add account'),
onTap: () {
showCupertinoModalPopup(
context: context,
@ -283,9 +282,9 @@ class _SectionHeading extends StatelessWidget {
Widget build(BuildContext context) {
final theme = Theme.of(context);
return Padding(
padding: const EdgeInsets.only(left: 20),
child: Text(text.toUpperCase(),
style: theme.textTheme.subtitle2.copyWith(color: theme.accentColor)),
padding: EdgeInsets.only(left: 20, top: 0),
);
}
}

View File

@ -32,9 +32,9 @@ class UserPage extends HookWidget {
if (userDetailsSnap.hasData) {
return UserProfile.fromUserDetails(userDetailsSnap.data);
} else if (userDetailsSnap.hasError) {
return Center(child: Text('Could not find that user.'));
return const Center(child: Text('Could not find that user.'));
} else {
return Center(child: CircularProgressIndicator());
return const Center(child: CircularProgressIndicator());
}
}();
@ -46,11 +46,11 @@ class UserPage extends HookWidget {
actions: [
if (userDetailsSnap.hasData) ...[
IconButton(
icon: Icon(Icons.email),
icon: const Icon(Icons.email),
onPressed: () {}, // TODO: go to messaging page
),
IconButton(
icon: Icon(Icons.share),
icon: const Icon(Icons.share),
onPressed: () => Share.text('Share user',
userDetailsSnap.data.user.actorId, 'text/plain'),
)

View File

@ -52,7 +52,7 @@ class UsersListPage extends StatelessWidget {
width: 50,
imageUrl: users[i].avatar,
errorWidget: (_, __, ___) =>
SizedBox(height: 50, width: 50),
const SizedBox(height: 50, width: 50),
imageBuilder: (context, imageProvider) => Container(
decoration: BoxDecoration(
shape: BoxShape.circle,
@ -60,7 +60,7 @@ class UsersListPage extends StatelessWidget {
fit: BoxFit.cover, image: imageProvider),
),
))
: SizedBox(width: 50),
: const SizedBox(width: 50),
),
itemCount: users.length,
));

View File

@ -5,6 +5,7 @@ import 'package:flutter/foundation.dart';
import 'package:lemmy_api_client/lemmy_api_client.dart';
import 'package:shared_preferences/shared_preferences.dart';
import '../util/unawaited.dart';
import 'shared_pref_keys.dart';
/// Store that manages all accounts
@ -46,11 +47,13 @@ class AccountsStore extends ChangeNotifier {
);
// set saved settings or create defaults
_tokens = nestedMapsCast((json) => Jwt(json['raw']));
_tokens = nestedMapsCast((json) => Jwt(json['raw'] as String));
_defaultAccount = prefs.getString(SharedPrefKeys.defaultAccount);
_defaultAccounts = HashMap.of(Map.castFrom(
jsonDecode(prefs.getString(SharedPrefKeys.defaultAccounts) ?? 'null') ??
{}));
jsonDecode(prefs.getString(SharedPrefKeys.defaultAccounts) ?? 'null')
as Map<dynamic, dynamic> ??
{},
));
notifyListeners();
}
@ -211,7 +214,7 @@ class AccountsStore extends ChangeNotifier {
_assignDefaultAccounts();
notifyListeners();
save();
unawaited(save());
}
/// adds a new instance with no accounts associated with it.
@ -238,7 +241,7 @@ class AccountsStore extends ChangeNotifier {
_assignDefaultAccounts();
notifyListeners();
save();
unawaited(save());
}
/// This also removes all accounts assigned to this instance

View File

@ -1,14 +1,16 @@
/// Strips protocol, 'www.', and trailing '/' from [url] aka. cleans it up
String cleanUpUrl(String url) {
if (url.startsWith('https://')) {
url = url.substring(8);
var newUrl = url;
if (newUrl.startsWith('https://')) {
newUrl = newUrl.substring(8);
}
if (url.startsWith('www.')) {
url = url.substring(4);
if (newUrl.startsWith('www.')) {
newUrl = newUrl.substring(4);
}
if (url.endsWith('/')) {
url = url.substring(0, url.length - 1);
if (newUrl.endsWith('/')) {
newUrl = newUrl.substring(0, newUrl.length - 1);
}
return url;
return newUrl;
}

3
lib/util/unawaited.dart Normal file
View File

@ -0,0 +1,3 @@
/// Explicitely indicate to the `unawaited_futures` lint
/// that the future is not awaited for on purpose
void unawaited<T>(Future<T> future) {}

View File

@ -10,6 +10,8 @@ import 'bottom_modal.dart';
/// Title that opens a dialog with information about Lemmur.
/// Licenses, changelog, version etc.
class AboutTile extends HookWidget {
const AboutTile();
@override
Widget build(BuildContext context) {
final packageInfoSnap = useMemoFuture(PackageInfo.fromPlatform);
@ -26,11 +28,11 @@ class AboutTile extends HookWidget {
// TODO: add app icon
return AboutListTile(
icon: Icon(Icons.info),
icon: const Icon(Icons.info),
aboutBoxChildren: [
FlatButton.icon(
icon: Icon(Icons.subject),
label: Text('changelog'),
icon: const Icon(Icons.subject),
label: const Text('changelog'),
onPressed: () => showModalBottomSheet(
context: context,
builder: (_) => BottomModal(
@ -39,13 +41,13 @@ class AboutTile extends HookWidget {
),
),
FlatButton.icon(
icon: Icon(Icons.code),
label: Text('source code'),
icon: const Icon(Icons.code),
label: const Text('source code'),
onPressed: () => openInBrowser('https://github.com/krawieck/lemmur'),
),
FlatButton.icon(
icon: Icon(Icons.monetization_on),
label: Text('support development'),
icon: const Icon(Icons.monetization_on),
label: const Text('support development'),
onPressed: () {
showDialog(
context: context,
@ -54,14 +56,14 @@ class AboutTile extends HookWidget {
mainAxisSize: MainAxisSize.min,
children: [
FlatButton(
child: Text('Patreon'),
onPressed: () =>
openInBrowser('https://patreon.com/lemmur'),
child: const Text('Patreon'),
),
FlatButton(
child: Text('Buy Me a Coffee'),
onPressed: () =>
openInBrowser('https://buymeacoff.ee/lemmur'),
child: const Text('Buy Me a Coffee'),
),
],
),

View File

@ -5,7 +5,7 @@ class Badge extends StatelessWidget {
final Widget child;
final BorderRadiusGeometry borderRadius;
Badge({
const Badge({
@required this.child,
this.borderRadius = const BorderRadius.all(Radius.circular(10)),
});

View File

@ -5,7 +5,7 @@ class BottomModal extends StatelessWidget {
final Widget child;
final String title;
BottomModal({@required this.child, this.title});
const BottomModal({@required this.child, this.title});
@override
Widget build(BuildContext context) {
@ -13,13 +13,13 @@ class BottomModal extends StatelessWidget {
return SafeArea(
child: Padding(
padding: const EdgeInsets.all(8.0),
padding: const EdgeInsets.all(8),
child: SingleChildScrollView(
child: Container(
padding: title != null ? const EdgeInsets.only(top: 10) : null,
decoration: BoxDecoration(
color: theme.scaffoldBackgroundColor,
borderRadius: BorderRadius.all(const Radius.circular(10.0)),
borderRadius: const BorderRadius.all(Radius.circular(10)),
),
child: Column(
mainAxisSize: MainAxisSize.min,
@ -34,7 +34,7 @@ class BottomModal extends StatelessWidget {
textAlign: TextAlign.left,
),
),
Divider(
const Divider(
indent: 20,
endIndent: 20,
)

View File

@ -52,8 +52,7 @@ class Comment extends HookWidget {
'upvotes': com.upvotes,
'downvotes': com.downvotes,
'score': com.score,
'% of upvotes':
'${(100 * (com.upvotes / (com.upvotes + com.downvotes)))}%',
'% of upvotes': '${100 * (com.upvotes / (com.upvotes + com.downvotes))}%',
'hotRank': com.hotRank,
'hotRankActive': com.hotRankActive,
'published': com.published,
@ -92,7 +91,7 @@ class Comment extends HookWidget {
// ignore: avoid_catches_without_on_clauses
} catch (e) {
Scaffold.of(context).showSnackBar(
SnackBar(content: Text('Failed to delete/restore comment')));
const SnackBar(content: Text('Failed to delete/restore comment')));
return;
}
delayedDeletion.cancel();
@ -109,22 +108,22 @@ class Comment extends HookWidget {
child: Column(
children: [
ListTile(
leading: Icon(Icons.open_in_browser),
title: Text('Open in browser'),
leading: const Icon(Icons.open_in_browser),
title: const Text('Open in browser'),
onTap: () async => await ul.canLaunch(com.apId)
? ul.launch(com.apId)
: Scaffold.of(context).showSnackBar(
SnackBar(content: Text("can't open in browser"))),
const SnackBar(content: Text("can't open in browser"))),
),
ListTile(
leading: Icon(Icons.share),
title: Text('Share url'),
leading: const Icon(Icons.share),
title: const Text('Share url'),
onTap: () =>
Share.text('Share comment url', com.apId, 'text/plain'),
),
ListTile(
leading: Icon(Icons.share),
title: Text('Share text'),
leading: const Icon(Icons.share),
title: const Text('Share text'),
onTap: () =>
Share.text('Share comment text', com.content, 'text/plain'),
),
@ -153,8 +152,8 @@ class Comment extends HookWidget {
onTap: loggedInAction(handleDelete),
),
ListTile(
leading: Icon(Icons.info_outline),
title: Text('Nerd stuff'),
leading: const Icon(Icons.info_outline),
title: const Text('Nerd stuff'),
onTap: () => _showCommentInfo(context),
),
],
@ -184,7 +183,7 @@ class Comment extends HookWidget {
// ignore: avoid_catches_without_on_clauses
} catch (e) {
Scaffold.of(context)
.showSnackBar(SnackBar(content: Text('voting failed :(')));
.showSnackBar(const SnackBar(content: Text('voting failed :(')));
return;
}
delayedVoting.cancel();
@ -208,14 +207,14 @@ class Comment extends HookWidget {
final body = () {
if (isDeleted.value) {
return Flexible(
return const Flexible(
child: Text(
'comment deleted by creator',
style: TextStyle(fontStyle: FontStyle.italic),
),
);
} else if (comment.removed) {
return Flexible(
return const Flexible(
child: Text(
'comment deleted by moderator',
style: TextStyle(fontStyle: FontStyle.italic),
@ -250,7 +249,7 @@ class Comment extends HookWidget {
}();
final actions = collapsed.value
? SizedBox.shrink()
? const SizedBox.shrink()
: Row(children: [
if (selectable.value && !isDeleted.value && !comment.removed)
_CommentAction(
@ -259,10 +258,11 @@ class Comment extends HookWidget {
onPressed: () {
Clipboard.setData(
ClipboardData(text: commentTree.comment.content))
.then((_) => Scaffold.of(context).showSnackBar(SnackBar(
content: Text('comment copied to clipboard'))));
.then((_) => Scaffold.of(context).showSnackBar(
const SnackBar(
content: Text('comment copied to clipboard'))));
}),
Spacer(),
const Spacer(),
_CommentAction(
icon: Icons.more_horiz,
onPressed: () => _openMoreMenu(context),
@ -303,6 +303,15 @@ class Comment extends HookWidget {
child: Column(
children: [
Container(
padding: const EdgeInsets.all(10),
margin: EdgeInsets.only(left: indent > 1 ? (indent - 1) * 5.0 : 0),
decoration: BoxDecoration(
border: Border(
left: indent > 0
? BorderSide(
color: colors[indent % colors.length], width: 5)
: BorderSide.none,
top: const BorderSide(width: 0.2))),
child: Column(
children: [
Row(children: [
@ -325,27 +334,27 @@ class Comment extends HookWidget {
),
),
),
errorWidget: (_, __, ___) => SizedBox.shrink(),
errorWidget: (_, __, ___) => const SizedBox.shrink(),
),
),
),
InkWell(
onTap: () => goToUser.byId(
context, comment.instanceHost, comment.creatorId),
child: Text(username,
style: TextStyle(
color: Theme.of(context).accentColor,
)),
onTap: () => goToUser.byId(
context, comment.instanceHost, comment.creatorId),
),
if (isOP) _CommentTag('OP', Theme.of(context).accentColor),
if (comment.banned) _CommentTag('BANNED', Colors.red),
if (comment.banned) const _CommentTag('BANNED', Colors.red),
if (comment.bannedFromCommunity)
_CommentTag('BANNED FROM COMMUNITY', Colors.red),
Spacer(),
const _CommentTag('BANNED FROM COMMUNITY', Colors.red),
const Spacer(),
if (collapsed.value && commentTree.children.length > 0) ...[
_CommentTag('+${commentTree.children.length}',
Theme.of(context).accentColor),
SizedBox(width: 7),
const SizedBox(width: 7),
],
InkWell(
onTap: () => _showCommentInfo(context),
@ -353,32 +362,23 @@ class Comment extends HookWidget {
children: [
if (delayedVoting.loading)
SizedBox.fromSize(
size: Size.square(16),
child: CircularProgressIndicator())
size: const Size.square(16),
child: const CircularProgressIndicator())
else
Text(compactNumber(comment.score +
(wasVoted ? 0 : myVote.value.value))),
Text(' · '),
const Text(' · '),
Text(timeago.format(comment.published)),
],
),
)
]),
SizedBox(height: 10),
const SizedBox(height: 10),
Row(children: [body]),
SizedBox(height: 5),
const SizedBox(height: 5),
actions,
],
),
padding: EdgeInsets.all(10),
margin: EdgeInsets.only(left: indent > 1 ? (indent - 1) * 5.0 : 0),
decoration: BoxDecoration(
border: Border(
left: indent > 0
? BorderSide(
color: colors[indent % colors.length], width: 5)
: BorderSide.none,
top: BorderSide(width: 0.2))),
),
if (!collapsed.value)
for (final c in newReplies.value.followedBy(commentTree.children))
@ -396,13 +396,13 @@ class Comment extends HookWidget {
class _SaveComment extends HookWidget {
final CommentView comment;
_SaveComment(this.comment);
const _SaveComment(this.comment);
@override
Widget build(BuildContext context) {
final loggedInAction = useLoggedInAction(comment.instanceHost);
final isSaved = useState(comment.saved ?? false);
final delayed = useDelayedLoading(const Duration(milliseconds: 500));
final delayed = useDelayedLoading();
handleSave(Jwt token) async {
final api = LemmyApi(comment.instanceHost).v1;
@ -415,7 +415,7 @@ class _SaveComment extends HookWidget {
// ignore: avoid_catches_without_on_clauses
} catch (e) {
Scaffold.of(context)
.showSnackBar(SnackBar(content: Text('saving failed :(')));
.showSnackBar(const SnackBar(content: Text('saving failed :(')));
}
delayed.cancel();
}
@ -440,10 +440,10 @@ class _CommentTag extends StatelessWidget {
padding: const EdgeInsets.only(left: 5),
child: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(5)),
borderRadius: const BorderRadius.all(Radius.circular(5)),
color: bgColor,
),
padding: EdgeInsets.symmetric(horizontal: 3, vertical: 2),
padding: const EdgeInsets.symmetric(horizontal: 3, vertical: 2),
child: Text(text,
style: TextStyle(
color: textColorBasedOnBackground(bgColor),
@ -456,7 +456,7 @@ class _CommentTag extends StatelessWidget {
class _CommentAction extends StatelessWidget {
final IconData icon;
final void Function() onPressed;
final VoidCallback onPressed;
final String tooltip;
final bool loading;
final Color iconColor;
@ -472,10 +472,11 @@ class _CommentAction extends StatelessWidget {
@override
Widget build(BuildContext context) => IconButton(
constraints: BoxConstraints.tight(Size(32, 26)),
constraints: BoxConstraints.tight(const Size(32, 26)),
icon: loading
? SizedBox.fromSize(
size: Size.square(22), child: CircularProgressIndicator())
size: const Size.square(22),
child: const CircularProgressIndicator())
: Icon(
icon,
color: iconColor ??
@ -485,6 +486,6 @@ class _CommentAction extends StatelessWidget {
onPressed: onPressed,
iconSize: 22,
tooltip: tooltip,
padding: EdgeInsets.all(0),
padding: const EdgeInsets.all(0),
);
}

View File

@ -60,10 +60,10 @@ class CommentSection extends HookWidget {
children: [
for (final e in sortPairs.entries)
ListTile(
leading: Icon(e.value[0]),
title: Text(e.value[1]),
leading: Icon(e.value[0] as IconData),
title: Text(e.value[1] as String),
trailing: sorting.value == e.key
? Icon(Icons.check)
? const Icon(Icons.check)
: null,
onTap: () {
Navigator.of(context).pop();
@ -79,18 +79,18 @@ class CommentSection extends HookWidget {
),
child: Row(
children: [
Text(sortPairs[sorting.value][1]),
Icon(Icons.arrow_drop_down),
Text(sortPairs[sorting.value][1] as String),
const Icon(Icons.arrow_drop_down),
],
),
),
Spacer(),
const Spacer(),
],
),
),
// sorting menu goes here
if (comments.isEmpty)
Padding(
const Padding(
padding: EdgeInsets.symmetric(vertical: 50),
child: Text(
'no comments yet',
@ -105,7 +105,7 @@ class CommentSection extends HookWidget {
)
else
for (final com in comments) Comment(com, postCreatorId: postCreatorId),
SizedBox(height: 50),
const SizedBox(height: 50),
]);
}
}

View File

@ -4,7 +4,7 @@ import 'package:flutter_hooks/flutter_hooks.dart';
import '../hooks/ref.dart';
class InfiniteScrollController {
Function() clear;
VoidCallback clear;
InfiniteScrollController() {
usedBeforeCreation() => throw Exception(
@ -28,7 +28,7 @@ class InfiniteScroll<T> extends HookWidget {
final Widget prepend;
final EdgeInsetsGeometry padding;
InfiniteScroll({
const InfiniteScroll({
this.batchSize = 10,
this.prepend = const SizedBox.shrink(),
this.padding,
@ -75,7 +75,7 @@ class InfiniteScroll<T> extends HookWidget {
if (i == data.value.length) {
// if there are no more, skip
if (!hasMore.current) {
return SizedBox.shrink();
return const SizedBox.shrink();
}
// if it's already fetching more, skip

View File

@ -12,7 +12,7 @@ class MarkdownText extends StatelessWidget {
final String text;
final bool selectable;
MarkdownText(this.text,
const MarkdownText(this.text,
{@required this.instanceHost, this.selectable = false})
: assert(instanceHost != null);
@ -26,7 +26,7 @@ class MarkdownText extends StatelessWidget {
.catchError((e) => Scaffold.of(context).showSnackBar(SnackBar(
content: Row(
children: [
Icon(Icons.warning),
const Icon(Icons.warning),
Text("couldn't open link, ${e.toString()}"),
],
),
@ -38,7 +38,7 @@ class MarkdownText extends StatelessWidget {
imageUrl: uri.toString(),
errorWidget: (context, url, error) => Row(
children: [
Icon(Icons.warning),
const Icon(Icons.warning),
Text("couldn't load image, ${error.toString()}")
],
),

View File

@ -64,16 +64,16 @@ class Post extends HookWidget {
child: Column(
children: [
ListTile(
leading: Icon(Icons.open_in_browser),
title: Text('Open in browser'),
leading: const Icon(Icons.open_in_browser),
title: const Text('Open in browser'),
onTap: () async => await ul.canLaunch(post.apId)
? ul.launch(post.apId)
: Scaffold.of(context).showSnackBar(
SnackBar(content: Text("can't open in browser"))),
const SnackBar(content: Text("can't open in browser"))),
),
ListTile(
leading: Icon(Icons.info_outline),
title: Text('Nerd stuff'),
leading: const Icon(Icons.info_outline),
title: const Text('Nerd stuff'),
onTap: () {
showInfoTablePopup(context, {
'id': post.id,
@ -151,6 +151,7 @@ class Post extends HookWidget {
],
),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(children: [
RichText(
@ -160,21 +161,23 @@ class Post extends HookWidget {
fontSize: 15,
color: theme.textTheme.bodyText1.color),
children: [
TextSpan(
const TextSpan(
text: '!',
style: TextStyle(fontWeight: FontWeight.w300)),
TextSpan(
text: post.communityName,
style: TextStyle(fontWeight: FontWeight.w600),
style:
const TextStyle(fontWeight: FontWeight.w600),
recognizer: TapGestureRecognizer()
..onTap = () => goToCommunity.byId(
context, instanceHost, post.communityId)),
TextSpan(
const TextSpan(
text: '@',
style: TextStyle(fontWeight: FontWeight.w300)),
TextSpan(
text: post.originInstanceHost,
style: TextStyle(fontWeight: FontWeight.w600),
style:
const TextStyle(fontWeight: FontWeight.w600),
recognizer: TapGestureRecognizer()
..onTap = () => goToInstance(
context, post.originInstanceHost)),
@ -190,13 +193,14 @@ class Post extends HookWidget {
fontSize: 13,
color: theme.textTheme.bodyText1.color),
children: [
TextSpan(
const TextSpan(
text: 'by',
style: TextStyle(fontWeight: FontWeight.w300)),
TextSpan(
text:
''' ${post.creatorPreferredUsername ?? post.creatorName}''',
style: TextStyle(fontWeight: FontWeight.w600),
style:
const TextStyle(fontWeight: FontWeight.w600),
recognizer: TapGestureRecognizer()
..onTap = () => goToUser.byId(
context, post.instanceHost, post.creatorId),
@ -204,32 +208,32 @@ class Post extends HookWidget {
TextSpan(
text:
''' · ${timeago.format(post.published, locale: 'en_short')}'''),
if (post.locked) TextSpan(text: ' · 🔒'),
if (post.stickied) TextSpan(text: ' · 📌'),
if (post.nsfw) TextSpan(text: ' · '),
if (post.locked) const TextSpan(text: ' · 🔒'),
if (post.stickied) const TextSpan(text: ' · 📌'),
if (post.nsfw) const TextSpan(text: ' · '),
if (post.nsfw)
TextSpan(
const TextSpan(
text: 'NSFW',
style: TextStyle(color: Colors.red)),
if (urlDomain != null)
TextSpan(text: ' · $urlDomain'),
if (post.removed) TextSpan(text: ' · REMOVED'),
if (post.deleted) TextSpan(text: ' · DELETED'),
if (post.removed)
const TextSpan(text: ' · REMOVED'),
if (post.deleted)
const TextSpan(text: ' · DELETED'),
],
))
]),
],
crossAxisAlignment: CrossAxisAlignment.start,
),
Spacer(),
const Spacer(),
if (!fullPost)
Column(
children: [
IconButton(
onPressed: () => showMoreMenu(context, post),
icon: Icon(moreIcon),
iconSize: 24,
padding: EdgeInsets.all(0),
padding: const EdgeInsets.all(0),
visualDensity: VisualDensity.compact,
)
],
@ -246,29 +250,31 @@ class Post extends HookWidget {
Expanded(
flex: 100,
child: Text(
'${post.name}',
post.name,
textAlign: TextAlign.left,
softWrap: true,
style: TextStyle(fontSize: 18, fontWeight: FontWeight.w600),
style: const TextStyle(
fontSize: 18, fontWeight: FontWeight.w600),
),
),
if (whatType(post.url) == MediaType.other &&
post.thumbnailUrl != null) ...[
Spacer(),
const Spacer(),
InkWell(
onTap: _openLink,
child: Stack(children: [
ClipRRect(
borderRadius: BorderRadius.circular(20),
child: CachedNetworkImage(
imageUrl: post.thumbnailUrl,
width: 70,
height: 70,
fit: BoxFit.cover,
errorWidget: (context, url, error) =>
Text(error.toString()),
)),
Positioned(
borderRadius: BorderRadius.circular(20),
child: CachedNetworkImage(
imageUrl: post.thumbnailUrl,
width: 70,
height: 70,
fit: BoxFit.cover,
errorWidget: (context, url, error) =>
Text(error.toString()),
),
),
const Positioned(
top: 8,
right: 8,
child: Icon(
@ -294,7 +300,6 @@ class Post extends HookWidget {
child: Container(
decoration: BoxDecoration(
border: Border.all(
width: 1,
color: Theme.of(context).iconTheme.color.withAlpha(170)),
borderRadius: BorderRadius.circular(5)),
child: Padding(
@ -302,15 +307,15 @@ class Post extends HookWidget {
child: Column(
children: [
Row(children: [
Spacer(),
const Spacer(),
Text('$urlDomain ',
style: theme.textTheme.caption
.apply(fontStyle: FontStyle.italic)),
Icon(Icons.launch, size: 12),
const Icon(Icons.launch, size: 12),
]),
Row(children: [
Flexible(
child: Text('${post.embedTitle ?? ''}',
child: Text(post.embedTitle ?? '',
style: theme.textTheme.subtitle1
.apply(fontWeightDelta: 2)))
]),
@ -335,7 +340,7 @@ class Post extends HookWidget {
url: post.url,
child: CachedNetworkImage(
imageUrl: post.url,
errorWidget: (_, __, ___) => Icon(Icons.warning),
errorWidget: (_, __, ___) => const Icon(Icons.warning),
progressIndicatorBuilder: (context, url, progress) =>
CircularProgressIndicator(value: progress.progress),
),
@ -347,7 +352,7 @@ class Post extends HookWidget {
padding: const EdgeInsets.fromLTRB(10, 5, 10, 5),
child: Row(
children: [
Icon(Icons.comment),
const Icon(Icons.comment),
Expanded(
flex: 999,
child: Text(
@ -356,10 +361,10 @@ class Post extends HookWidget {
softWrap: false,
),
),
Spacer(),
const Spacer(),
if (!fullPost)
IconButton(
icon: Icon(Icons.share),
icon: const Icon(Icons.share),
onPressed: () => Share.text('Share post url', post.apId,
'text/plain')), // TODO: find a way to mark it as url
if (!fullPost) SavePostButton(post),
@ -370,9 +375,9 @@ class Post extends HookWidget {
return Container(
decoration: BoxDecoration(
boxShadow: [BoxShadow(blurRadius: 10, color: Colors.black54)],
boxShadow: const [BoxShadow(blurRadius: 10, color: Colors.black54)],
color: theme.cardColor,
borderRadius: BorderRadius.all(Radius.circular(20)),
borderRadius: const BorderRadius.all(Radius.circular(20)),
),
child: InkWell(
onTap: fullPost
@ -412,7 +417,7 @@ class _Voting extends HookWidget {
Widget build(BuildContext context) {
final theme = Theme.of(context);
final myVote = useState(post.myVote ?? VoteType.none);
final loading = useDelayedLoading(Duration(milliseconds: 500));
final loading = useDelayedLoading();
final loggedInAction = useLoggedInAction(post.instanceHost);
vote(VoteType vote, Jwt token) async {
@ -426,7 +431,7 @@ class _Voting extends HookWidget {
// ignore: avoid_catches_without_on_clauses
} catch (e) {
Scaffold.of(context)
.showSnackBar(SnackBar(content: Text('voting failed :(')));
.showSnackBar(const SnackBar(content: Text('voting failed :(')));
return;
}
loading.cancel();
@ -446,7 +451,8 @@ class _Voting extends HookWidget {
),
)),
if (loading.loading)
SizedBox(child: CircularProgressIndicator(), width: 20, height: 20)
const SizedBox(
width: 20, height: 20, child: CircularProgressIndicator())
else
Text(NumberFormat.compact()
.format(post.score + (wasVoted ? 0 : myVote.value.value))),

View File

@ -11,7 +11,7 @@ class PostListOptions extends HookWidget {
final SortType defaultSort;
final bool styleButton;
PostListOptions({
const PostListOptions({
@required this.onChange,
this.styleButton = true,
this.defaultSort = SortType.active,
@ -60,14 +60,14 @@ class PostListOptions extends HookWidget {
children: <Widget>[
Text(sort.value.value),
const SizedBox(width: 8),
Icon(Icons.arrow_drop_down),
const Icon(Icons.arrow_drop_down),
],
),
),
Spacer(),
const Spacer(),
if (styleButton)
IconButton(
icon: Icon(Icons.view_stream),
icon: const Icon(Icons.view_stream),
// TODO: create compact post and dropdown for selecting
onPressed: () => print('TBD'),
),

View File

@ -10,13 +10,13 @@ import '../hooks/logged_in_action.dart';
class SavePostButton extends HookWidget {
final PostView post;
SavePostButton(this.post);
const SavePostButton(this.post);
@override
Widget build(BuildContext context) {
final isSaved = useState(post.saved ?? false);
final savedIcon = isSaved.value ? Icons.bookmark : Icons.bookmark_border;
final loading = useDelayedLoading(Duration(milliseconds: 500));
final loading = useDelayedLoading();
final loggedInAction = useLoggedInAction(post.instanceHost);
savePost(Jwt token) async {
@ -30,7 +30,7 @@ class SavePostButton extends HookWidget {
// ignore: avoid_catches_without_on_clauses
} catch (e) {
Scaffold.of(context)
.showSnackBar(SnackBar(content: Text('saving failed :(')));
.showSnackBar(const SnackBar(content: Text('saving failed :(')));
}
loading.cancel();
}

View File

@ -16,7 +16,7 @@ class SortableInfiniteList<T> extends HookWidget {
final Widget Function(T) builder;
final Function onStyleChange;
SortableInfiniteList({
const SortableInfiniteList({
@required this.fetcher,
@required this.builder,
this.onStyleChange,
@ -36,7 +36,6 @@ class SortableInfiniteList<T> extends HookWidget {
return InfiniteScroll<T>(
prepend: PostListOptions(
onChange: changeSorting,
defaultSort: SortType.active,
styleButton: onStyleChange != null,
),
builder: builder,
@ -52,14 +51,14 @@ class InfinitePostList extends StatelessWidget {
final Future<List<PostView>> Function(
int page, int batchSize, SortType sortType) fetcher;
InfinitePostList({@required this.fetcher}) : assert(fetcher != null);
const InfinitePostList({@required this.fetcher}) : assert(fetcher != null);
Widget build(BuildContext context) => SortableInfiniteList<PostView>(
onStyleChange: () {},
builder: (post) => Column(
children: [
Post(post),
SizedBox(height: 20),
const SizedBox(height: 20),
],
),
fetcher: fetcher,
@ -70,7 +69,7 @@ class InfiniteCommentList extends StatelessWidget {
final Future<List<CommentView>> Function(
int page, int batchSize, SortType sortType) fetcher;
InfiniteCommentList({@required this.fetcher}) : assert(fetcher != null);
const InfiniteCommentList({@required this.fetcher}) : assert(fetcher != null);
Widget build(BuildContext context) => SortableInfiniteList<CommentView>(
builder: (comment) => Comment(

View File

@ -38,7 +38,7 @@ class UserProfile extends HookWidget {
} else if (userDetailsSnap.hasError) {
return Center(
child: Row(mainAxisAlignment: MainAxisAlignment.center, children: [
Icon(Icons.error),
const Icon(Icons.error),
Padding(
padding: const EdgeInsets.all(8),
child: Text('ERROR: ${userDetailsSnap.error}'),
@ -66,7 +66,7 @@ class UserProfile extends HookWidget {
FlexibleSpaceBar(background: _UserOverview(userView)),
bottom: TabBar(
labelColor: theme.textTheme.bodyText1.color,
tabs: [
tabs: const [
Tab(text: 'Posts'),
Tab(text: 'Comments'),
Tab(text: 'About'),
@ -131,7 +131,7 @@ class _UserOverview extends HookWidget {
url: userView.banner,
child: CachedNetworkImage(
imageUrl: userView.banner,
errorWidget: (_, __, ___) => SizedBox.shrink(),
errorWidget: (_, __, ___) => const SizedBox.shrink(),
),
)
else
@ -142,7 +142,7 @@ class _UserOverview extends HookWidget {
),
Container(
height: 200,
decoration: BoxDecoration(
decoration: const BoxDecoration(
gradient: LinearGradient(
begin: FractionalOffset.topCenter,
end: FractionalOffset.bottomCenter,
@ -161,7 +161,7 @@ class _UserOverview extends HookWidget {
height: double.infinity,
child: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.only(
borderRadius: const BorderRadius.only(
topRight: Radius.circular(40),
topLeft: Radius.circular(40),
),
@ -181,19 +181,19 @@ class _UserOverview extends HookWidget {
child: Container(
// clipBehavior: Clip.antiAlias,
decoration: BoxDecoration(
boxShadow: [
boxShadow: const [
BoxShadow(blurRadius: 6, color: Colors.black54)
],
borderRadius: BorderRadius.all(Radius.circular(15)),
borderRadius: const BorderRadius.all(Radius.circular(15)),
border: Border.all(color: Colors.white, width: 3),
),
child: ClipRRect(
borderRadius: BorderRadius.all(Radius.circular(12)),
borderRadius: const BorderRadius.all(Radius.circular(12)),
child: FullscreenableImage(
url: userView.avatar,
child: CachedNetworkImage(
imageUrl: userView.avatar,
errorWidget: (_, __, ___) => SizedBox.shrink(),
errorWidget: (_, __, ___) => const SizedBox.shrink(),
),
),
),
@ -201,7 +201,7 @@ class _UserOverview extends HookWidget {
),
Padding(
padding: userView.avatar != null
? const EdgeInsets.only(top: 8.0)
? const EdgeInsets.only(top: 8)
: const EdgeInsets.only(top: 70),
child: Padding(
padding:
@ -213,7 +213,7 @@ class _UserOverview extends HookWidget {
),
),
Padding(
padding: const EdgeInsets.only(top: 4.0),
padding: const EdgeInsets.only(top: 4),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
@ -225,7 +225,7 @@ class _UserOverview extends HookWidget {
onTap: () =>
goToInstance(context, userView.originInstanceHost),
child: Text(
'${userView.originInstanceHost}',
userView.originInstanceHost,
style: theme.textTheme.caption,
),
)
@ -246,7 +246,7 @@ class _UserOverview extends HookWidget {
color: colorOnTopOfAccentColor,
),
Padding(
padding: const EdgeInsets.only(left: 4.0),
padding: const EdgeInsets.only(left: 4),
child: Text(
'${compactNumber(userView.numberOfPosts)}'
' Post${pluralS(userView.numberOfPosts)}',
@ -257,7 +257,7 @@ class _UserOverview extends HookWidget {
),
),
Padding(
padding: const EdgeInsets.only(left: 16.0),
padding: const EdgeInsets.only(left: 16),
child: Badge(
child: Row(
children: [
@ -267,7 +267,7 @@ class _UserOverview extends HookWidget {
color: colorOnTopOfAccentColor,
),
Padding(
padding: const EdgeInsets.only(left: 4.0),
padding: const EdgeInsets.only(left: 4),
child: Text(
'${compactNumber(userView.numberOfComments)}'
' Comment${pluralS(userView.numberOfComments)}',
@ -294,12 +294,12 @@ class _UserOverview extends HookWidget {
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
const Icon(
Icons.cake,
size: 13,
),
Padding(
padding: const EdgeInsets.only(left: 4.0),
padding: const EdgeInsets.only(left: 4),
child: Text(
DateFormat('MMM dd, yyyy').format(userView.published),
style: theme.textTheme.bodyText1,
@ -337,7 +337,7 @@ class _AboutTab extends HookWidget {
final divider = Padding(
padding: EdgeInsets.symmetric(
horizontal: wallPadding.horizontal / 2, vertical: 10),
child: Divider(),
child: const Divider(),
);
communityTile(String name, String icon, int id) => ListTile(
@ -349,7 +349,8 @@ class _AboutTab extends HookWidget {
height: 40,
width: 40,
imageUrl: icon,
errorWidget: (_, __, ___) => SizedBox(width: 40, height: 40),
errorWidget: (_, __, ___) =>
const SizedBox(width: 40, height: 40),
imageBuilder: (context, imageProvider) => Container(
decoration: BoxDecoration(
shape: BoxShape.circle,
@ -359,17 +360,17 @@ class _AboutTab extends HookWidget {
),
),
))
: SizedBox(width: 40),
: const SizedBox(width: 40),
);
return ListView(
padding: EdgeInsets.symmetric(vertical: 20),
padding: const EdgeInsets.symmetric(vertical: 20),
children: [
if (isOwnedAccount)
ListTile(
title: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
children: const [
Icon(Icons.edit),
SizedBox(width: 10),
Text('edit profile'),
@ -415,8 +416,8 @@ class _AboutTab extends HookWidget {
communityTile(
comm.communityName, comm.communityIcon, comm.communityId)
else
Padding(
padding: const EdgeInsets.only(top: 8),
const Padding(
padding: EdgeInsets.only(top: 8),
child: Center(
child: Text(
'this user does not subscribe to any community',

View File

@ -41,9 +41,9 @@ class WriteComment extends HookWidget {
children: [
Text(
post.name,
style: TextStyle(fontSize: 15, fontWeight: FontWeight.w600),
style: const TextStyle(fontSize: 15, fontWeight: FontWeight.w600),
),
SizedBox(height: 4),
const SizedBox(height: 4),
body,
],
);
@ -68,8 +68,8 @@ class WriteComment extends HookWidget {
// ignore: avoid_catches_without_on_clauses
} catch (e) {
print(e);
scaffoldKey.currentState
.showSnackBar(SnackBar(content: Text('Failed to post comment')));
scaffoldKey.currentState.showSnackBar(
const SnackBar(content: Text('Failed to post comment')));
}
delayed.cancel();
}
@ -78,7 +78,7 @@ class WriteComment extends HookWidget {
key: scaffoldKey,
appBar: AppBar(
leading: IconButton(
icon: Icon(Icons.close),
icon: const Icon(Icons.close),
onPressed: Navigator.of(context).pop,
),
actions: [
@ -98,7 +98,7 @@ class WriteComment extends HookWidget {
child: preview,
),
),
Divider(),
const Divider(),
IndexedStack(
index: showFancy.value ? 1 : 0,
children: [
@ -108,7 +108,7 @@ class WriteComment extends HookWidget {
minLines: 5,
maxLines: null,
textAlignVertical: TextAlignVertical.top,
decoration: InputDecoration(border: OutlineInputBorder()),
decoration: const InputDecoration(border: OutlineInputBorder()),
),
Padding(
padding: const EdgeInsets.all(16),
@ -125,8 +125,8 @@ class WriteComment extends HookWidget {
FlatButton(
onPressed: delayed.pending ? () {} : handleSubmit,
child: delayed.loading
? CircularProgressIndicator()
: Text('post'),
? const CircularProgressIndicator()
: const Text('post'),
)
],
),