Migrate instance page to mobx + l10 strings (#316)
This commit is contained in:
parent
56bba4d6af
commit
88608ea9e1
|
@ -34,7 +34,7 @@
|
|||
"L10n string": {
|
||||
"scope": "dart",
|
||||
"prefix": "l10n",
|
||||
"body": ["L10n.of(context)!.$0"]
|
||||
"body": ["L10n.of(context).$0"]
|
||||
},
|
||||
"Mobx store": {
|
||||
"prefix": "mobxstore",
|
||||
|
|
|
@ -1,3 +1,9 @@
|
|||
## Unreleased
|
||||
|
||||
### Added
|
||||
|
||||
- You can now add an instance from the three dots menu on the instance page
|
||||
|
||||
## v0.8.0 - 2022-01-14
|
||||
|
||||
### Added
|
||||
|
|
|
@ -215,7 +215,10 @@
|
|||
"number_of_users_online": "{formattedCount,plural, =1{{formattedCount} user online} other{{formattedCount} users online}}",
|
||||
"@number_of_users_online": {
|
||||
"placeholders": {
|
||||
"formattedCount": {}
|
||||
"formattedCount": {
|
||||
"type": "int",
|
||||
"format": "compact"
|
||||
}
|
||||
}
|
||||
},
|
||||
"number_of_comments": "{formattedCount,plural, =1{{formattedCount} comment} other{{formattedCount} comments}}",
|
||||
|
@ -239,13 +242,28 @@
|
|||
"number_of_subscribers": "{formattedCount,plural, =1{{formattedCount} subscriber} other{{formattedCount} subscribers}}",
|
||||
"@number_of_subscribers": {
|
||||
"placeholders": {
|
||||
"formattedCount": {}
|
||||
"formattedCount": {
|
||||
"type": "int",
|
||||
"format": "compact"
|
||||
}
|
||||
}
|
||||
},
|
||||
"number_of_users": "{formattedCount,plural, =1{{formattedCount} user} other{{formattedCount} users}}",
|
||||
"@number_of_users": {
|
||||
"placeholders": {
|
||||
"formattedCount": {}
|
||||
"formattedCount": {
|
||||
"type": "int",
|
||||
"format": "compact"
|
||||
}
|
||||
}
|
||||
},
|
||||
"number_of_communities": "{formattedCount,plural, =1{{formattedCount} community} other{{formattedCount} communities}}",
|
||||
"@number_of_communities": {
|
||||
"placeholders": {
|
||||
"formattedCount": {
|
||||
"type": "int",
|
||||
"format": "compact"
|
||||
}
|
||||
}
|
||||
},
|
||||
"unsubscribe": "unsubscribe",
|
||||
|
@ -291,5 +309,41 @@
|
|||
"show_bot_accounts": "Show Bot Accounts",
|
||||
"@show_bot_accounts": {},
|
||||
"show_read_posts": "Show Read Posts",
|
||||
"@show_read_posts": {}
|
||||
"@show_read_posts": {},
|
||||
"site_not_set_up": "This site has not yet been set up",
|
||||
"@site_not_set_up": {},
|
||||
"nerd_stuff": "Nerd stuff",
|
||||
"@nerd_stuff": {},
|
||||
"open_in_browser": "Open in browser",
|
||||
"@open_in_browser": {},
|
||||
"cannot_open_in_browser": "Can't open in browser",
|
||||
"@cannot_open_in_browser": {},
|
||||
"about": "About",
|
||||
"@about": {},
|
||||
"see_all": "See all",
|
||||
"@see_all": {},
|
||||
"admins": "Admins",
|
||||
"@admins": {},
|
||||
"trending_communities": "Trending communities",
|
||||
"@trending_communities": {},
|
||||
"communities_of_instance": "Communities of {instance}",
|
||||
"@communities_of_instance": {
|
||||
"placeholders": {
|
||||
"instance": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"day": "day",
|
||||
"@day": {},
|
||||
"week": "week",
|
||||
"@week": {},
|
||||
"month": "month",
|
||||
"@month": {},
|
||||
"six_months": "6 months",
|
||||
"@six_months": {},
|
||||
"add_instance": "Add instance",
|
||||
"@add_instance": {},
|
||||
"instance_added": "Instance successfully added",
|
||||
"@instance_added": {}
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -4,6 +4,5 @@ template-arb-file: intl_en.arb
|
|||
output-localization-file: l10n.dart
|
||||
preferred-supported-locales: [en]
|
||||
output-class: L10n
|
||||
untranslated-messages-file: assets/l10n/untranslated.json
|
||||
synthetic-package: false
|
||||
nullable-getter: false
|
||||
|
|
|
@ -9,11 +9,7 @@ import '../widgets/sortable_infinite_list.dart';
|
|||
/// Infinite list of Communities fetched by the given fetcher
|
||||
class CommunitiesListPage extends StatelessWidget {
|
||||
final String title;
|
||||
final Future<List<CommunityView>> Function(
|
||||
int page,
|
||||
int batchSize,
|
||||
SortType sortType,
|
||||
) fetcher;
|
||||
final FetcherWithSorting<CommunityView> fetcher;
|
||||
|
||||
const CommunitiesListPage({Key? key, required this.fetcher, this.title = ''})
|
||||
: super(key: key);
|
||||
|
@ -21,6 +17,7 @@ class CommunitiesListPage extends StatelessWidget {
|
|||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = Theme.of(context);
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
backgroundColor: theme.cardColor,
|
||||
|
|
|
@ -15,6 +15,7 @@ import '../util/goto.dart';
|
|||
import '../util/text_color.dart';
|
||||
import '../widgets/avatar.dart';
|
||||
import '../widgets/pull_to_refresh.dart';
|
||||
import 'instance/instance.dart';
|
||||
|
||||
/// List of subscribed communities per instance
|
||||
class CommunitiesTab extends HookWidget {
|
||||
|
@ -171,8 +172,11 @@ class CommunitiesTab extends HookWidget {
|
|||
Column(
|
||||
children: [
|
||||
ListTile(
|
||||
onTap: () => goToInstance(context,
|
||||
accountsStore.loggedInInstances.elementAt(i)),
|
||||
onTap: () => Navigator.of(context).push(
|
||||
InstancePage.route(
|
||||
accountsStore.loggedInInstances.elementAt(i),
|
||||
),
|
||||
),
|
||||
onLongPress: () => toggleCollapse(i),
|
||||
leading: Avatar(
|
||||
url: instances[i].icon,
|
||||
|
|
|
@ -4,10 +4,10 @@ import 'package:lemmy_api_client/v3.dart';
|
|||
|
||||
import '../../l10n/l10n.dart';
|
||||
import '../../util/extensions/api.dart';
|
||||
import '../../util/goto.dart';
|
||||
import '../../widgets/avatar.dart';
|
||||
import '../../widgets/cached_network_image.dart';
|
||||
import '../../widgets/fullscreenable_image.dart';
|
||||
import '../instance/instance.dart';
|
||||
import 'community_follow_button.dart';
|
||||
|
||||
class CommunityOverview extends StatelessWidget {
|
||||
|
@ -93,11 +93,12 @@ class CommunityOverview extends StatelessWidget {
|
|||
text: community.community.originInstanceHost,
|
||||
style: const TextStyle(fontWeight: FontWeight.w600),
|
||||
recognizer: TapGestureRecognizer()
|
||||
..onTap = () => goToInstance(
|
||||
context,
|
||||
..onTap = () => Navigator.of(context).push(
|
||||
InstancePage.route(
|
||||
community.community.originInstanceHost,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
|
|
@ -15,6 +15,7 @@ import '../widgets/cached_network_image.dart';
|
|||
import '../widgets/infinite_scroll.dart';
|
||||
import '../widgets/sortable_infinite_list.dart';
|
||||
import 'inbox.dart';
|
||||
import 'instance/instance.dart';
|
||||
import 'settings/add_account_page.dart';
|
||||
|
||||
/// First thing users sees when opening the app
|
||||
|
@ -128,7 +129,9 @@ class HomeTab extends HookWidget {
|
|||
color:
|
||||
theme.textTheme.bodyText1?.color?.withOpacity(0.7)),
|
||||
),
|
||||
onTap: () => goToInstance(context, instance),
|
||||
onTap: () => Navigator.of(context).push(
|
||||
InstancePage.route(instance),
|
||||
),
|
||||
dense: true,
|
||||
contentPadding: EdgeInsets.zero,
|
||||
visualDensity: const VisualDensity(
|
||||
|
|
|
@ -1,387 +0,0 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:lemmy_api_client/v3.dart';
|
||||
import 'package:url_launcher/url_launcher.dart' as ul;
|
||||
|
||||
import '../hooks/stores.dart';
|
||||
import '../l10n/l10n.dart';
|
||||
import '../util/extensions/spaced.dart';
|
||||
import '../util/goto.dart';
|
||||
import '../util/icons.dart';
|
||||
import '../util/share.dart';
|
||||
import '../util/text_color.dart';
|
||||
import '../widgets/avatar.dart';
|
||||
import '../widgets/bottom_modal.dart';
|
||||
import '../widgets/cached_network_image.dart';
|
||||
import '../widgets/fullscreenable_image.dart';
|
||||
import '../widgets/info_table_popup.dart';
|
||||
import '../widgets/markdown_text.dart';
|
||||
import '../widgets/reveal_after_scroll.dart';
|
||||
import '../widgets/sortable_infinite_list.dart';
|
||||
import '../widgets/user_tile.dart';
|
||||
import 'communities_list.dart';
|
||||
import 'modlog/modlog.dart';
|
||||
|
||||
/// Displays posts, comments, and general info about the given instance
|
||||
class InstancePage extends HookWidget {
|
||||
final String instanceHost;
|
||||
final Future<FullSiteView> siteFuture;
|
||||
final Future<List<CommunityView>> communitiesFuture;
|
||||
|
||||
InstancePage({required this.instanceHost})
|
||||
: siteFuture = LemmyApiV3(instanceHost).run(const GetSite()),
|
||||
communitiesFuture = LemmyApiV3(instanceHost).run(const ListCommunities(
|
||||
type: PostListingType.local, sort: SortType.hot, limit: 6));
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = Theme.of(context);
|
||||
final siteSnap = useFuture(siteFuture);
|
||||
final colorOnCard = textColorBasedOnBackground(theme.cardColor);
|
||||
final accStore = useAccountsStore();
|
||||
final scrollController = useScrollController();
|
||||
|
||||
if (!siteSnap.hasData || siteSnap.data!.siteView == null) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(),
|
||||
body: Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
if (siteSnap.hasError) ...[
|
||||
const Icon(Icons.error),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(8),
|
||||
child: Text('ERROR: ${siteSnap.error}'),
|
||||
)
|
||||
] else if (siteSnap.hasData && siteSnap.data!.siteView == null)
|
||||
const Text('ERROR')
|
||||
else
|
||||
const CircularProgressIndicator.adaptive(
|
||||
semanticsLabel: 'loading')
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
final site = siteSnap.data!;
|
||||
final siteView = site.siteView!;
|
||||
|
||||
void _share() => share('https://$instanceHost', context: context);
|
||||
|
||||
void _openMoreMenu() {
|
||||
showBottomModal(
|
||||
context: context,
|
||||
builder: (context) => Column(
|
||||
children: [
|
||||
ListTile(
|
||||
leading: const Icon(Icons.open_in_browser),
|
||||
title: const Text('Open in browser'),
|
||||
onTap: () async => await ul
|
||||
.canLaunch('https://${site.instanceHost}')
|
||||
? ul.launch('https://${site.instanceHost}')
|
||||
: ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(content: Text("can't open in browser"))),
|
||||
),
|
||||
ListTile(
|
||||
leading: const Icon(Icons.info_outline),
|
||||
title: const Text('Nerd stuff'),
|
||||
onTap: () {
|
||||
showInfoTablePopup(context: context, table: site.toJson());
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return Scaffold(
|
||||
body: DefaultTabController(
|
||||
length: 3,
|
||||
child: NestedScrollView(
|
||||
controller: scrollController,
|
||||
headerSliverBuilder: (context, innerBoxIsScrolled) => <Widget>[
|
||||
SliverAppBar(
|
||||
expandedHeight: 250,
|
||||
pinned: true,
|
||||
backgroundColor: theme.cardColor,
|
||||
title: RevealAfterScroll(
|
||||
after: 150,
|
||||
fade: true,
|
||||
scrollController: scrollController,
|
||||
child: Text(
|
||||
siteView.site.name,
|
||||
style: TextStyle(color: colorOnCard),
|
||||
),
|
||||
),
|
||||
actions: [
|
||||
IconButton(icon: Icon(shareIcon), onPressed: _share),
|
||||
IconButton(icon: Icon(moreIcon), onPressed: _openMoreMenu),
|
||||
],
|
||||
flexibleSpace: FlexibleSpaceBar(
|
||||
background: Stack(children: [
|
||||
if (siteView.site.banner != null)
|
||||
FullscreenableImage(
|
||||
url: siteView.site.banner!,
|
||||
child: CachedNetworkImage(
|
||||
imageUrl: siteView.site.banner!,
|
||||
errorBuilder: (_, ___) => const SizedBox.shrink(),
|
||||
),
|
||||
),
|
||||
SafeArea(
|
||||
child: Center(
|
||||
child: Column(
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 40),
|
||||
child: siteView.site.icon == null
|
||||
? const SizedBox(height: 100, width: 100)
|
||||
: FullscreenableImage(
|
||||
url: siteView.site.icon!,
|
||||
child: CachedNetworkImage(
|
||||
width: 100,
|
||||
height: 100,
|
||||
imageUrl: siteView.site.icon!,
|
||||
errorBuilder: (_, ___) =>
|
||||
const Icon(Icons.warning),
|
||||
),
|
||||
),
|
||||
),
|
||||
Text(siteView.site.name,
|
||||
style: theme.textTheme.headline6),
|
||||
Text(instanceHost, style: theme.textTheme.caption)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
]),
|
||||
),
|
||||
bottom: PreferredSize(
|
||||
preferredSize: const TabBar(tabs: []).preferredSize,
|
||||
child: Material(
|
||||
color: theme.cardColor,
|
||||
child: TabBar(
|
||||
tabs: [
|
||||
Tab(text: L10n.of(context).posts),
|
||||
Tab(text: L10n.of(context).comments),
|
||||
const Tab(text: 'About'),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
body: TabBarView(
|
||||
children: [
|
||||
InfinitePostList(
|
||||
fetcher: (page, batchSize, sort) =>
|
||||
LemmyApiV3(instanceHost).run(GetPosts(
|
||||
// TODO: switch between all and subscribed
|
||||
type: PostListingType.all,
|
||||
sort: sort,
|
||||
limit: batchSize,
|
||||
page: page,
|
||||
savedOnly: false,
|
||||
auth:
|
||||
accStore.defaultUserDataFor(instanceHost)?.jwt.raw,
|
||||
))),
|
||||
InfiniteCommentList(
|
||||
fetcher: (page, batchSize, sort) =>
|
||||
LemmyApiV3(instanceHost).run(GetComments(
|
||||
type: CommentListingType.all,
|
||||
sort: sort,
|
||||
limit: batchSize,
|
||||
page: page,
|
||||
savedOnly: false,
|
||||
auth:
|
||||
accStore.defaultUserDataFor(instanceHost)?.jwt.raw,
|
||||
))),
|
||||
_AboutTab(site,
|
||||
communitiesFuture: communitiesFuture,
|
||||
instanceHost: instanceHost),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _AboutTab extends HookWidget {
|
||||
final FullSiteView site;
|
||||
final Future<List<CommunityView>> communitiesFuture;
|
||||
final String instanceHost;
|
||||
|
||||
const _AboutTab(
|
||||
this.site, {
|
||||
required this.communitiesFuture,
|
||||
required this.instanceHost,
|
||||
});
|
||||
|
||||
// void goToBannedUsers(BuildContext context) {
|
||||
// goTo(
|
||||
// context,
|
||||
// (_) => UsersListPage(
|
||||
// users: site.banned.reversed.toList(),
|
||||
// title: L10n.of(context).banned_users,
|
||||
// ),
|
||||
// );
|
||||
// }
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = Theme.of(context);
|
||||
final commSnap = useFuture(communitiesFuture);
|
||||
final accStore = useAccountsStore();
|
||||
|
||||
void goToCommunities() {
|
||||
goTo(
|
||||
context,
|
||||
(_) => CommunitiesListPage(
|
||||
fetcher: (page, batchSize, sortType) => LemmyApiV3(instanceHost).run(
|
||||
ListCommunities(
|
||||
type: PostListingType.local,
|
||||
sort: sortType,
|
||||
limit: batchSize,
|
||||
page: page,
|
||||
auth: accStore.defaultUserDataFor(instanceHost)?.jwt.raw,
|
||||
),
|
||||
),
|
||||
title: 'Communities of ${site.siteView?.site.name}',
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
final siteView = site.siteView;
|
||||
|
||||
if (siteView == null) {
|
||||
return const SingleChildScrollView(
|
||||
child: Center(
|
||||
child: Padding(
|
||||
padding: EdgeInsets.all(16),
|
||||
child: Text('error'),
|
||||
)));
|
||||
}
|
||||
|
||||
return SingleChildScrollView(
|
||||
child: SafeArea(
|
||||
top: false,
|
||||
child: Column(
|
||||
children: [
|
||||
if (siteView.site.description != null) ...[
|
||||
Padding(
|
||||
padding:
|
||||
const EdgeInsets.symmetric(horizontal: 15, vertical: 15),
|
||||
child: MarkdownText(
|
||||
siteView.site.description!,
|
||||
instanceHost: instanceHost,
|
||||
),
|
||||
),
|
||||
const _Divider(),
|
||||
],
|
||||
SizedBox(
|
||||
height: 32,
|
||||
child: ListView(
|
||||
scrollDirection: Axis.horizontal,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 15),
|
||||
children: [
|
||||
Chip(
|
||||
label: Text(L10n.of(context)
|
||||
.number_of_users_online(site.online))),
|
||||
Chip(
|
||||
label: Text(
|
||||
'${siteView.counts.usersActiveDay} users / day')),
|
||||
Chip(
|
||||
label: Text(
|
||||
'${siteView.counts.usersActiveWeek} users / week')),
|
||||
Chip(
|
||||
label: Text(
|
||||
'${siteView.counts.usersActiveMonth} users / month')),
|
||||
Chip(
|
||||
label: Text(
|
||||
'${siteView.counts.usersActiveHalfYear} users / 6 months')),
|
||||
Chip(
|
||||
label: Text(L10n.of(context)
|
||||
.number_of_users(siteView.counts.users))),
|
||||
Chip(
|
||||
label:
|
||||
Text('${siteView.counts.communities} communities')),
|
||||
Chip(label: Text('${siteView.counts.posts} posts')),
|
||||
Chip(label: Text('${siteView.counts.comments} comments')),
|
||||
].spaced(8),
|
||||
),
|
||||
),
|
||||
const _Divider(),
|
||||
ListTile(
|
||||
title: Center(
|
||||
child: Text(
|
||||
'Trending communities:',
|
||||
style: theme.textTheme.headline6?.copyWith(fontSize: 18),
|
||||
),
|
||||
),
|
||||
),
|
||||
if (commSnap.hasData)
|
||||
for (final c in commSnap.data!)
|
||||
ListTile(
|
||||
onTap: () => goToCommunity.byId(
|
||||
context, c.instanceHost, c.community.id),
|
||||
title: Text(c.community.name),
|
||||
leading: Avatar(url: c.community.icon),
|
||||
)
|
||||
else if (commSnap.hasError)
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(8),
|
||||
child: Text("Can't load communities, ${commSnap.error}"),
|
||||
)
|
||||
else
|
||||
const Padding(
|
||||
padding: EdgeInsets.symmetric(vertical: 10),
|
||||
child: CircularProgressIndicator.adaptive(),
|
||||
),
|
||||
ListTile(
|
||||
title: const Center(child: Text('See all')),
|
||||
onTap: goToCommunities,
|
||||
),
|
||||
const _Divider(),
|
||||
ListTile(
|
||||
title: Center(
|
||||
child: Text(
|
||||
'Admins:',
|
||||
style: theme.textTheme.headline6?.copyWith(fontSize: 18),
|
||||
),
|
||||
),
|
||||
),
|
||||
for (final u in site.admins)
|
||||
PersonTile(
|
||||
u.person,
|
||||
expanded: true,
|
||||
),
|
||||
const _Divider(),
|
||||
// TODO: transition to new API
|
||||
// ListTile(
|
||||
// title: Center(child: Text(L10n.of(context).banned_users)),
|
||||
// onTap: () => goToBannedUsers(context),
|
||||
// ),
|
||||
ListTile(
|
||||
title: Center(child: Text(L10n.of(context).modlog)),
|
||||
onTap: () => Navigator.of(context).push(
|
||||
ModlogPage.forInstanceRoute(instanceHost),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _Divider extends StatelessWidget {
|
||||
const _Divider();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => const Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 15, vertical: 10),
|
||||
child: Divider(),
|
||||
);
|
||||
}
|
|
@ -0,0 +1,204 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:lemmy_api_client/v3.dart';
|
||||
|
||||
import '../../l10n/l10n.dart';
|
||||
import '../../util/extensions/context.dart';
|
||||
import '../../util/icons.dart';
|
||||
import '../../util/mobx_provider.dart';
|
||||
import '../../util/observer_consumers.dart';
|
||||
import '../../util/share.dart';
|
||||
import '../../util/text_color.dart';
|
||||
import '../../widgets/cached_network_image.dart';
|
||||
import '../../widgets/failed_to_load.dart';
|
||||
import '../../widgets/fullscreenable_image.dart';
|
||||
import '../../widgets/reveal_after_scroll.dart';
|
||||
import '../../widgets/sortable_infinite_list.dart';
|
||||
import 'instance_about_tab.dart';
|
||||
import 'instance_more_menu.dart';
|
||||
import 'instance_store.dart';
|
||||
|
||||
/// Displays posts, comments, and general info about the given instance
|
||||
class InstancePage extends HookWidget {
|
||||
const InstancePage();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = Theme.of(context);
|
||||
final colorOnCard = textColorBasedOnBackground(theme.cardColor);
|
||||
final scrollController = useScrollController();
|
||||
|
||||
return ObserverBuilder<InstanceStore>(
|
||||
builder: (context, store) {
|
||||
final instanceUrl = 'https://${store.instanceHost}';
|
||||
|
||||
return store.siteState.map(
|
||||
loading: () => Scaffold(
|
||||
appBar: AppBar(),
|
||||
body: const Center(child: CircularProgressIndicator.adaptive()),
|
||||
),
|
||||
error: (errorTerm) => Scaffold(
|
||||
appBar: AppBar(),
|
||||
body: Center(
|
||||
child: FailedToLoad(
|
||||
refresh: () => store.fetch(
|
||||
context.defaultJwt(store.instanceHost),
|
||||
),
|
||||
message: errorTerm.tr(context),
|
||||
),
|
||||
),
|
||||
),
|
||||
data: (site) {
|
||||
final siteView = site.siteView;
|
||||
|
||||
if (siteView == null) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(),
|
||||
body: Center(child: Text(L10n.of(context).site_not_set_up)),
|
||||
);
|
||||
}
|
||||
|
||||
return Scaffold(
|
||||
body: DefaultTabController(
|
||||
length: 3,
|
||||
child: NestedScrollView(
|
||||
controller: scrollController,
|
||||
headerSliverBuilder: (context, innerBoxIsScrolled) => [
|
||||
SliverAppBar(
|
||||
expandedHeight: 250,
|
||||
pinned: true,
|
||||
backgroundColor: theme.cardColor,
|
||||
title: RevealAfterScroll(
|
||||
after: 150,
|
||||
fade: true,
|
||||
scrollController: scrollController,
|
||||
child: Text(
|
||||
siteView.site.name,
|
||||
style: TextStyle(color: colorOnCard),
|
||||
),
|
||||
),
|
||||
actions: [
|
||||
IconButton(
|
||||
icon: Icon(shareIcon),
|
||||
onPressed: () => share(instanceUrl, context: context),
|
||||
),
|
||||
IconButton(
|
||||
icon: Icon(moreIcon),
|
||||
onPressed: () => InstanceMoreMenu.open(context, site),
|
||||
),
|
||||
],
|
||||
flexibleSpace: FlexibleSpaceBar(
|
||||
background: Stack(
|
||||
children: [
|
||||
if (siteView.site.banner != null)
|
||||
FullscreenableImage(
|
||||
url: siteView.site.banner!,
|
||||
child: CachedNetworkImage(
|
||||
imageUrl: siteView.site.banner!,
|
||||
errorBuilder: (_, ___) => const SizedBox(),
|
||||
),
|
||||
),
|
||||
SafeArea(
|
||||
child: Center(
|
||||
child: Column(
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 40),
|
||||
child: siteView.site.icon == null
|
||||
? const SizedBox(
|
||||
height: 100,
|
||||
width: 100,
|
||||
)
|
||||
: FullscreenableImage(
|
||||
url: siteView.site.icon!,
|
||||
child: CachedNetworkImage(
|
||||
width: 100,
|
||||
height: 100,
|
||||
imageUrl: siteView.site.icon!,
|
||||
errorBuilder: (_, ___) =>
|
||||
const Icon(Icons.warning),
|
||||
),
|
||||
),
|
||||
),
|
||||
Text(
|
||||
siteView.site.name,
|
||||
style: theme.textTheme.headline6,
|
||||
),
|
||||
Text(
|
||||
store.instanceHost,
|
||||
style: theme.textTheme.caption,
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
bottom: PreferredSize(
|
||||
preferredSize: const TabBar(tabs: []).preferredSize,
|
||||
child: Material(
|
||||
color: theme.cardColor,
|
||||
child: TabBar(
|
||||
tabs: [
|
||||
Tab(text: L10n.of(context).posts),
|
||||
Tab(text: L10n.of(context).comments),
|
||||
Tab(text: L10n.of(context).about),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
body: TabBarView(
|
||||
children: [
|
||||
InfinitePostList(
|
||||
fetcher: (page, batchSize, sort) =>
|
||||
LemmyApiV3(store.instanceHost).run(GetPosts(
|
||||
// TODO: switch between all and subscribed
|
||||
type: PostListingType.all,
|
||||
sort: sort,
|
||||
limit: batchSize,
|
||||
page: page,
|
||||
savedOnly: false,
|
||||
auth: context.defaultJwt(store.instanceHost)?.raw,
|
||||
)),
|
||||
),
|
||||
InfiniteCommentList(
|
||||
fetcher: (page, batchSize, sort) =>
|
||||
LemmyApiV3(store.instanceHost).run(GetComments(
|
||||
type: CommentListingType.all,
|
||||
sort: sort,
|
||||
limit: batchSize,
|
||||
page: page,
|
||||
savedOnly: false,
|
||||
auth: context.defaultJwt(store.instanceHost)?.raw,
|
||||
)),
|
||||
),
|
||||
InstanceAboutTab(
|
||||
site: site,
|
||||
siteView: siteView,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
static Route route(String instanceHost) {
|
||||
return MaterialPageRoute(
|
||||
builder: (context) {
|
||||
return MobxProvider(
|
||||
create: (context) => InstanceStore(instanceHost)
|
||||
..fetch(context.defaultJwt(instanceHost)),
|
||||
child: const InstancePage(),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,206 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:lemmy_api_client/v3.dart';
|
||||
|
||||
import '../../l10n/l10n.dart';
|
||||
import '../../util/extensions/context.dart';
|
||||
import '../../util/extensions/spaced.dart';
|
||||
import '../../util/goto.dart';
|
||||
import '../../util/observer_consumers.dart';
|
||||
import '../../widgets/avatar.dart';
|
||||
import '../../widgets/failed_to_load.dart';
|
||||
import '../../widgets/markdown_text.dart';
|
||||
import '../../widgets/pull_to_refresh.dart';
|
||||
import '../../widgets/user_tile.dart';
|
||||
import '../communities_list.dart';
|
||||
import '../community/community.dart';
|
||||
import '../modlog/modlog.dart';
|
||||
import 'instance_store.dart';
|
||||
|
||||
class InstanceAboutTab extends HookWidget {
|
||||
final FullSiteView site;
|
||||
final SiteView siteView;
|
||||
|
||||
const InstanceAboutTab({required this.site, required this.siteView});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = Theme.of(context);
|
||||
final l10n = L10n.of(context);
|
||||
|
||||
void goToCommunities() {
|
||||
goTo(
|
||||
context,
|
||||
(_) => CommunitiesListPage(
|
||||
fetcher: (page, batchSize, sortType) =>
|
||||
LemmyApiV3(site.instanceHost).run(
|
||||
ListCommunities(
|
||||
type: PostListingType.local,
|
||||
sort: sortType,
|
||||
limit: batchSize,
|
||||
page: page,
|
||||
auth: context.defaultJwt(site.instanceHost)?.raw,
|
||||
),
|
||||
),
|
||||
title: l10n.communities_of_instance(siteView.site.name),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return PullToRefresh(
|
||||
onRefresh: () => context
|
||||
.read<InstanceStore>()
|
||||
.fetch(context.defaultJwt(site.instanceHost), refresh: true),
|
||||
child: SingleChildScrollView(
|
||||
child: SafeArea(
|
||||
top: false,
|
||||
child: Column(
|
||||
children: [
|
||||
if (siteView.site.description != null) ...[
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 15,
|
||||
vertical: 15,
|
||||
),
|
||||
child: MarkdownText(
|
||||
siteView.site.description!,
|
||||
instanceHost: site.instanceHost,
|
||||
),
|
||||
),
|
||||
const _Divider(),
|
||||
],
|
||||
SizedBox(
|
||||
height: 32,
|
||||
child: ListView(
|
||||
scrollDirection: Axis.horizontal,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 15),
|
||||
children: [
|
||||
Chip(
|
||||
label: Text(
|
||||
l10n.number_of_users_online(site.online),
|
||||
),
|
||||
),
|
||||
Chip(
|
||||
label: Text(
|
||||
'${l10n.number_of_users(siteView.counts.usersActiveDay)} / ${l10n.day}',
|
||||
),
|
||||
),
|
||||
Chip(
|
||||
label: Text(
|
||||
'${l10n.number_of_users(siteView.counts.usersActiveWeek)} / ${l10n.week}',
|
||||
),
|
||||
),
|
||||
Chip(
|
||||
label: Text(
|
||||
'${l10n.number_of_users(siteView.counts.usersActiveMonth)} / ${l10n.month}',
|
||||
),
|
||||
),
|
||||
Chip(
|
||||
label: Text(
|
||||
'${l10n.number_of_users(siteView.counts.usersActiveHalfYear)} / ${l10n.six_months}',
|
||||
),
|
||||
),
|
||||
Chip(
|
||||
label: Text(
|
||||
l10n.number_of_users(siteView.counts.users),
|
||||
),
|
||||
),
|
||||
Chip(
|
||||
label: Text(
|
||||
l10n.number_of_communities(siteView.counts.communities),
|
||||
),
|
||||
),
|
||||
Chip(
|
||||
label: Text(
|
||||
l10n.number_of_posts(siteView.counts.posts),
|
||||
),
|
||||
),
|
||||
Chip(
|
||||
label: Text(
|
||||
l10n.number_of_comments(siteView.counts.comments),
|
||||
),
|
||||
),
|
||||
].spaced(8),
|
||||
),
|
||||
),
|
||||
const _Divider(),
|
||||
ListTile(
|
||||
title: Center(
|
||||
child: Text(
|
||||
l10n.trending_communities,
|
||||
style: theme.textTheme.headline6?.copyWith(fontSize: 18),
|
||||
),
|
||||
),
|
||||
),
|
||||
ObserverBuilder<InstanceStore>(
|
||||
builder: (context, store) => store.communitiesState.map(
|
||||
loading: () => const Padding(
|
||||
padding: EdgeInsets.symmetric(vertical: 10),
|
||||
child: CircularProgressIndicator.adaptive(),
|
||||
),
|
||||
error: (errorTerm) => FailedToLoad(
|
||||
refresh: () => store.fetchCommunites(
|
||||
context.defaultJwt(store.instanceHost)),
|
||||
message: errorTerm.tr(context),
|
||||
),
|
||||
data: (communities) => Column(
|
||||
children: [
|
||||
for (final c in communities)
|
||||
ListTile(
|
||||
onTap: () => Navigator.of(context).push(
|
||||
CommunityPage.fromIdRoute(
|
||||
store.instanceHost,
|
||||
c.community.id,
|
||||
),
|
||||
),
|
||||
title: Text(c.community.name),
|
||||
leading: Avatar(url: c.community.icon),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
ListTile(
|
||||
title: Center(child: Text(l10n.see_all)),
|
||||
onTap: goToCommunities,
|
||||
),
|
||||
const _Divider(),
|
||||
ListTile(
|
||||
title: Center(
|
||||
child: Text(
|
||||
l10n.admins,
|
||||
style: theme.textTheme.headline6?.copyWith(fontSize: 18),
|
||||
),
|
||||
),
|
||||
),
|
||||
for (final u in site.admins)
|
||||
PersonTile(
|
||||
u.person,
|
||||
expanded: true,
|
||||
),
|
||||
const _Divider(),
|
||||
ListTile(
|
||||
title: Center(child: Text(l10n.modlog)),
|
||||
onTap: () => Navigator.of(context).push(
|
||||
ModlogPage.forInstanceRoute(site.instanceHost),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _Divider extends StatelessWidget {
|
||||
const _Divider();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return const Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 15, vertical: 10),
|
||||
child: Divider(),
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,69 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:lemmy_api_client/v3.dart';
|
||||
import 'package:url_launcher/url_launcher.dart' as ul;
|
||||
|
||||
import '../../l10n/l10n.dart';
|
||||
import '../../stores/accounts_store.dart';
|
||||
import '../../util/observer_consumers.dart';
|
||||
import '../../widgets/bottom_modal.dart';
|
||||
import '../../widgets/info_table_popup.dart';
|
||||
|
||||
class InstanceMoreMenu extends StatelessWidget {
|
||||
final FullSiteView site;
|
||||
|
||||
const InstanceMoreMenu({Key? key, required this.site}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final instanceUrl = 'https://${site.instanceHost}';
|
||||
final accountsStore = context.watch<AccountsStore>();
|
||||
|
||||
return Column(
|
||||
children: [
|
||||
if (!accountsStore.instances.contains(site.instanceHost))
|
||||
ListTile(
|
||||
leading: const Icon(Icons.add),
|
||||
title: Text(L10n.of(context).add_instance),
|
||||
onTap: () {
|
||||
accountsStore.addInstance(site.instanceHost, assumeValid: true);
|
||||
Navigator.of(context).pop();
|
||||
ScaffoldMessenger.of(context)
|
||||
..hideCurrentSnackBar()
|
||||
..showSnackBar(
|
||||
SnackBar(content: Text(L10n.of(context).instance_added)),
|
||||
);
|
||||
},
|
||||
),
|
||||
ListTile(
|
||||
leading: const Icon(Icons.open_in_browser),
|
||||
title: Text(L10n.of(context).open_in_browser),
|
||||
onTap: () async {
|
||||
if (await ul.canLaunch(instanceUrl)) {
|
||||
await ul.launch(instanceUrl);
|
||||
} else {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(L10n.of(context).cannot_open_in_browser),
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
ListTile(
|
||||
leading: const Icon(Icons.info_outline),
|
||||
title: Text(L10n.of(context).nerd_stuff),
|
||||
onTap: () {
|
||||
showInfoTablePopup(context: context, table: site.toJson());
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
static void open(BuildContext context, FullSiteView site) {
|
||||
showBottomModal(
|
||||
context: context,
|
||||
builder: (context) => InstanceMoreMenu(site: site),
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,43 @@
|
|||
import 'package:lemmy_api_client/v3.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
|
||||
import '../../util/async_store.dart';
|
||||
|
||||
part 'instance_store.g.dart';
|
||||
|
||||
class InstanceStore = _InstanceStore with _$InstanceStore;
|
||||
|
||||
abstract class _InstanceStore with Store {
|
||||
final String instanceHost;
|
||||
|
||||
_InstanceStore(this.instanceHost);
|
||||
|
||||
final siteState = AsyncStore<FullSiteView>();
|
||||
final communitiesState = AsyncStore<List<CommunityView>>();
|
||||
|
||||
@action
|
||||
Future<void> fetch(Jwt? token, {bool refresh = false}) async {
|
||||
await Future.wait([
|
||||
siteState.runLemmy(
|
||||
instanceHost,
|
||||
GetSite(auth: token?.raw),
|
||||
refresh: refresh,
|
||||
),
|
||||
fetchCommunites(token, refresh: refresh),
|
||||
]);
|
||||
}
|
||||
|
||||
@action
|
||||
Future<void> fetchCommunites(Jwt? token, {bool refresh = false}) async {
|
||||
await communitiesState.runLemmy(
|
||||
instanceHost,
|
||||
ListCommunities(
|
||||
type: PostListingType.local,
|
||||
sort: SortType.hot,
|
||||
limit: 6,
|
||||
auth: token?.raw,
|
||||
),
|
||||
refresh: refresh,
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'instance_store.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// StoreGenerator
|
||||
// **************************************************************************
|
||||
|
||||
// ignore_for_file: non_constant_identifier_names, unnecessary_brace_in_string_interps, unnecessary_lambdas, prefer_expression_function_bodies, lines_longer_than_80_chars, avoid_as, avoid_annotating_with_dynamic
|
||||
|
||||
mixin _$InstanceStore on _InstanceStore, Store {
|
||||
final _$fetchAsyncAction = AsyncAction('_InstanceStore.fetch');
|
||||
|
||||
@override
|
||||
Future<void> fetch(Jwt? token, {bool refresh = false}) {
|
||||
return _$fetchAsyncAction.run(() => super.fetch(token, refresh: refresh));
|
||||
}
|
||||
|
||||
final _$fetchCommunitesAsyncAction =
|
||||
AsyncAction('_InstanceStore.fetchCommunites');
|
||||
|
||||
@override
|
||||
Future<void> fetchCommunites(Jwt? token, {bool refresh = false}) {
|
||||
return _$fetchCommunitesAsyncAction
|
||||
.run(() => super.fetchCommunites(token, refresh: refresh));
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return '''
|
||||
|
||||
''';
|
||||
}
|
||||
}
|
|
@ -4,29 +4,35 @@ import 'package:lemmy_api_client/v3.dart';
|
|||
import '../util/extensions/api.dart';
|
||||
import '../util/goto.dart';
|
||||
import '../widgets/avatar.dart';
|
||||
import '../widgets/infinite_scroll.dart';
|
||||
import '../widgets/markdown_text.dart';
|
||||
|
||||
/// Infinite list of Users fetched by the given fetcher
|
||||
class UsersListPage extends StatelessWidget {
|
||||
final String title;
|
||||
final List<PersonViewSafe> users;
|
||||
final Fetcher<PersonViewSafe> fetcher;
|
||||
|
||||
const UsersListPage({Key? key, required this.users, this.title = ''})
|
||||
const UsersListPage({Key? key, required this.fetcher, this.title = ''})
|
||||
: super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = Theme.of(context);
|
||||
|
||||
// TODO: change to infinite scroll
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
backgroundColor: theme.cardColor,
|
||||
title: Text(title),
|
||||
),
|
||||
body: ListView.builder(
|
||||
itemBuilder: (context, i) => UsersListItem(user: users[i]),
|
||||
itemCount: users.length,
|
||||
body: InfiniteScroll<PersonViewSafe>(
|
||||
fetcher: fetcher,
|
||||
itemBuilder: (user) => Column(
|
||||
children: [
|
||||
const Divider(),
|
||||
UsersListItem(user: user),
|
||||
],
|
||||
),
|
||||
uniqueProp: (user) => user.person.actorId,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
@ -38,7 +44,8 @@ class UsersListItem extends StatelessWidget {
|
|||
const UsersListItem({Key? key, required this.user}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => ListTile(
|
||||
Widget build(BuildContext context) {
|
||||
return ListTile(
|
||||
title: Text(user.person.originPreferredName),
|
||||
subtitle: user.person.bio != null
|
||||
? Opacity(
|
||||
|
@ -53,3 +60,4 @@ class UsersListItem extends StatelessWidget {
|
|||
leading: Avatar(url: user.person.avatar),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,7 +3,7 @@ import 'package:provider/provider.dart';
|
|||
import 'package:url_launcher/url_launcher.dart' as ul;
|
||||
|
||||
import 'pages/community/community.dart';
|
||||
import 'pages/instance.dart';
|
||||
import 'pages/instance/instance.dart';
|
||||
import 'pages/media_view.dart';
|
||||
import 'pages/user.dart';
|
||||
import 'stores/accounts_store.dart';
|
||||
|
@ -48,7 +48,7 @@ Future<void> linkLauncher({
|
|||
|
||||
if (matchedInstance != null && instances.any((e) => e == match?.group(1))) {
|
||||
if (rest == null || rest.isEmpty || rest == '/') {
|
||||
return push(() => InstancePage(instanceHost: matchedInstance));
|
||||
return Navigator.of(context).push<void>(InstancePage.route(instanceHost));
|
||||
}
|
||||
final split = rest.split('/');
|
||||
switch (split[1]) {
|
||||
|
|
|
@ -76,8 +76,10 @@ abstract class _AsyncStore<T> with Store {
|
|||
bool refresh = false,
|
||||
}) async {
|
||||
try {
|
||||
return await run(() => LemmyApiV3(instanceHost).run(query),
|
||||
refresh: refresh);
|
||||
return await run(
|
||||
() => LemmyApiV3(instanceHost).run(query),
|
||||
refresh: refresh,
|
||||
);
|
||||
} on LemmyApiException catch (err) {
|
||||
final data = refresh ? asyncState.mapOrNull(data: (data) => data) : null;
|
||||
if (data != null) {
|
||||
|
|
|
@ -52,3 +52,14 @@ extension UserPreferredNames on PersonSafe {
|
|||
extension CommentLink on Comment {
|
||||
String get link => 'https://$instanceHost/post/$postId/comment/$id';
|
||||
}
|
||||
|
||||
// inspired by https://github.com/LemmyNet/lemmy-ui/blob/66c846ededef8c0afd5aaadca4aaedcbaeab3ee6/src/shared/utils.ts#L533
|
||||
extension PersonSafeCakeDay on PersonSafe {
|
||||
bool get isCakeDay {
|
||||
final now = DateTime.now().toUtc();
|
||||
|
||||
return now.day == published.day &&
|
||||
now.month == published.month &&
|
||||
now.year != published.year;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,12 +0,0 @@
|
|||
import 'package:lemmy_api_client/v3.dart';
|
||||
|
||||
// inspired by https://github.com/LemmyNet/lemmy-ui/blob/66c846ededef8c0afd5aaadca4aaedcbaeab3ee6/src/shared/utils.ts#L533
|
||||
extension PersonSafeCakeDay on PersonSafe {
|
||||
bool get isCakeDay {
|
||||
final now = DateTime.now().toUtc();
|
||||
|
||||
return now.day == published.day &&
|
||||
now.month == published.month &&
|
||||
now.year != published.year;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:lemmy_api_client/v3.dart';
|
||||
|
||||
import '../../stores/accounts_store.dart';
|
||||
import '../observer_consumers.dart';
|
||||
|
||||
extension BuildContextExtensions on BuildContext {
|
||||
/// Get default [Jwt] for an instance
|
||||
Jwt? defaultJwt(String instanceHost) =>
|
||||
read<AccountsStore>().defaultUserDataFor(instanceHost)?.jwt;
|
||||
}
|
|
@ -3,7 +3,6 @@ import 'package:lemmy_api_client/v3.dart';
|
|||
|
||||
import '../pages/community/community.dart';
|
||||
import '../pages/full_post/full_post.dart';
|
||||
import '../pages/instance.dart';
|
||||
import '../pages/media_view.dart';
|
||||
import '../pages/user.dart';
|
||||
|
||||
|
@ -25,9 +24,6 @@ Future<dynamic> goToReplace(
|
|||
builder: builder,
|
||||
));
|
||||
|
||||
void goToInstance(BuildContext context, String instanceHost) =>
|
||||
goTo(context, (context) => InstancePage(instanceHost: instanceHost));
|
||||
|
||||
// ignore: camel_case_types
|
||||
abstract class goToCommunity {
|
||||
/// Navigates to `CommunityPage`
|
||||
|
|
|
@ -9,7 +9,6 @@ import '../../l10n/l10n.dart';
|
|||
import '../../stores/config_store.dart';
|
||||
import '../../util/async_store_listener.dart';
|
||||
import '../../util/extensions/api.dart';
|
||||
import '../../util/extensions/cake_day.dart';
|
||||
import '../../util/goto.dart';
|
||||
import '../../util/mobx_provider.dart';
|
||||
import '../../util/observer_consumers.dart';
|
||||
|
|
|
@ -17,6 +17,8 @@ class InfiniteScrollController {
|
|||
}
|
||||
}
|
||||
|
||||
typedef Fetcher<T> = Future<List<T>> Function(int page, int batchSize);
|
||||
|
||||
/// `ListView.builder` with asynchronous data fetching and no `itemCount`
|
||||
class InfiniteScroll<T> extends HookWidget {
|
||||
/// How many items should be fetched per call
|
||||
|
@ -31,7 +33,7 @@ class InfiniteScroll<T> extends HookWidget {
|
|||
/// Fetches data to be displayed. It is important to respect `batchSize`,
|
||||
/// if the returned list has less than `batchSize` then the InfiniteScroll
|
||||
/// is considered finished
|
||||
final Future<List<T>> Function(int page, int batchSize) fetcher;
|
||||
final Fetcher<T> fetcher;
|
||||
|
||||
final InfiniteScrollController? controller;
|
||||
|
||||
|
|
|
@ -3,6 +3,7 @@ import 'package:flutter/material.dart';
|
|||
|
||||
import '../../l10n/l10n.dart';
|
||||
import '../../pages/community/community.dart';
|
||||
import '../../pages/instance/instance.dart';
|
||||
import '../../util/extensions/api.dart';
|
||||
import '../../util/goto.dart';
|
||||
import '../../util/observer_consumers.dart';
|
||||
|
@ -67,11 +68,12 @@ class PostInfoSection extends StatelessWidget {
|
|||
text: post.post.originInstanceHost,
|
||||
style: const TextStyle(fontWeight: FontWeight.w600),
|
||||
recognizer: TapGestureRecognizer()
|
||||
..onTap = () => goToInstance(
|
||||
context,
|
||||
..onTap = () => Navigator.of(context).push(
|
||||
InstancePage.route(
|
||||
post.post.originInstanceHost,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
|
|
@ -6,9 +6,9 @@ import 'package:lemmy_api_client/v3.dart';
|
|||
import '../hooks/memo_future.dart';
|
||||
import '../hooks/stores.dart';
|
||||
import '../l10n/l10n.dart';
|
||||
import '../pages/instance/instance.dart';
|
||||
import '../pages/manage_account.dart';
|
||||
import '../util/extensions/api.dart';
|
||||
import '../util/extensions/cake_day.dart';
|
||||
import '../util/goto.dart';
|
||||
import '../util/text_color.dart';
|
||||
import 'avatar.dart';
|
||||
|
@ -226,8 +226,11 @@ class _UserOverview extends HookWidget {
|
|||
style: theme.textTheme.caption,
|
||||
),
|
||||
InkWell(
|
||||
onTap: () => goToInstance(
|
||||
context, userView.person.originInstanceHost),
|
||||
onTap: () => Navigator.of(context).push(
|
||||
InstancePage.route(
|
||||
userView.person.originInstanceHost,
|
||||
),
|
||||
),
|
||||
child: Text(
|
||||
userView.person.originInstanceHost,
|
||||
style: theme.textTheme.caption,
|
||||
|
|
Loading…
Reference in New Issue