lemmur-app-android/lib/pages/instance.dart

388 lines
13 KiB
Dart
Raw Normal View History

import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
2021-04-05 20:14:39 +02:00
import 'package:lemmy_api_client/v3.dart';
import 'package:url_launcher/url_launcher.dart' as ul;
import '../hooks/stores.dart';
2021-03-01 14:21:45 +01:00
import '../l10n/l10n.dart';
2021-02-09 15:12:13 +01:00
import '../util/extensions/spaced.dart';
import '../util/goto.dart';
2021-09-11 01:04:15 +02:00
import '../util/icons.dart';
2021-03-18 19:24:29 +01:00
import '../util/share.dart';
import '../util/text_color.dart';
2021-02-18 09:19:00 +01:00
import '../widgets/avatar.dart';
import '../widgets/bottom_modal.dart';
2021-10-21 14:40:28 +02:00
import '../widgets/cached_network_image.dart';
import '../widgets/fullscreenable_image.dart';
2020-10-26 18:47:49 +01:00
import '../widgets/info_table_popup.dart';
import '../widgets/markdown_text.dart';
2021-02-24 20:52:18 +01:00
import '../widgets/reveal_after_scroll.dart';
2020-09-29 10:27:36 +02:00
import '../widgets/sortable_infinite_list.dart';
import '../widgets/user_tile.dart';
2020-09-08 23:01:40 +02:00
import 'communities_list.dart';
import 'modlog/modlog.dart';
import 'users_list.dart';
2020-09-30 19:05:00 +02:00
/// Displays posts, comments, and general info about the given instance
class InstancePage extends HookWidget {
final String instanceHost;
final Future<FullSiteView> siteFuture;
2020-09-08 18:57:48 +02:00
final Future<List<CommunityView>> communitiesFuture;
InstancePage({required this.instanceHost})
: siteFuture = LemmyApiV3(instanceHost).run(const GetSite()),
2021-04-05 20:14:39 +02:00
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);
2020-09-29 10:53:40 +02:00
final accStore = useAccountsStore();
2021-02-24 20:52:18 +01:00
final scrollController = useScrollController();
if (!siteSnap.hasData || siteSnap.data!.siteView == null) {
return Scaffold(
2021-02-09 15:12:13 +01:00
appBar: AppBar(),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
if (siteSnap.hasError) ...[
2021-01-03 19:43:39 +01:00
const Icon(Icons.error),
Padding(
padding: const EdgeInsets.all(8),
child: Text('ERROR: ${siteSnap.error}'),
)
2021-04-11 11:12:42 +02:00
] 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!;
2021-03-20 15:50:49 +01:00
void _share() => share('https://$instanceHost', context: context);
2021-03-18 19:24:29 +01:00
2021-04-11 00:20:47 +02:00
void _openMoreMenu() {
2021-02-09 15:12:13 +01:00
showBottomModal(
context: context,
2021-02-09 15:12:13 +01:00
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}')
2021-03-10 08:34:30 +01:00
: ScaffoldMessenger.of(context).showSnackBar(
2021-02-09 15:12:13 +01:00
const SnackBar(content: Text("can't open in browser"))),
),
ListTile(
leading: const Icon(Icons.info_outline),
title: const Text('Nerd stuff'),
onTap: () {
2021-02-24 20:52:18 +01:00
showInfoTablePopup(context: context, table: site.toJson());
2021-02-09 15:12:13 +01:00
},
),
],
),
);
}
return Scaffold(
body: DefaultTabController(
length: 3,
child: NestedScrollView(
2021-02-24 20:52:18 +01:00
controller: scrollController,
2020-09-08 23:10:40 +02:00
headerSliverBuilder: (context, innerBoxIsScrolled) => <Widget>[
SliverAppBar(
2021-02-09 15:12:13 +01:00
expandedHeight: 250,
2020-09-08 23:10:40 +02:00
pinned: true,
backgroundColor: theme.cardColor,
2021-02-24 20:52:18 +01:00
title: RevealAfterScroll(
after: 150,
fade: true,
scrollController: scrollController,
child: Text(
siteView.site.name,
2021-02-24 20:52:18 +01:00
style: TextStyle(color: colorOnCard),
),
2020-09-08 23:10:40 +02:00
),
actions: [
IconButton(icon: Icon(shareIcon), onPressed: _share),
2021-04-11 00:20:47 +02:00
IconButton(icon: Icon(moreIcon), onPressed: _openMoreMenu),
2020-09-08 23:10:40 +02:00
],
flexibleSpace: FlexibleSpaceBar(
background: Stack(children: [
if (siteView.site.banner != null)
2020-09-11 21:18:45 +02:00
FullscreenableImage(
url: siteView.site.banner!,
child: CachedNetworkImage(
imageUrl: siteView.site.banner!,
2021-10-21 14:40:28 +02:00
errorBuilder: (_, ___) => const SizedBox.shrink(),
),
2020-09-11 21:18:45 +02:00
),
2020-09-08 23:10:40 +02:00
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!,
2021-10-21 14:40:28 +02:00
errorBuilder: (_, ___) =>
const Icon(Icons.warning),
),
),
2020-09-08 23:10:40 +02:00
),
Text(siteView.site.name,
2020-09-08 23:10:40 +02:00
style: theme.textTheme.headline6),
Text(instanceHost, style: theme.textTheme.caption)
2020-09-08 23:10:40 +02:00
],
),
),
),
2020-09-08 23:10:40 +02:00
]),
),
2021-02-09 15:12:13 +01:00
bottom: PreferredSize(
preferredSize: const TabBar(tabs: []).preferredSize,
child: Material(
color: theme.cardColor,
2021-03-01 14:21:45 +01:00
child: TabBar(
2021-02-09 15:12:13 +01:00
tabs: [
Tab(text: L10n.of(context).posts),
Tab(text: L10n.of(context).comments),
2021-03-01 14:21:45 +01:00
const Tab(text: 'About'),
2021-02-09 15:12:13 +01:00
],
),
),
),
2020-09-08 23:10:40 +02:00
),
],
body: TabBarView(
children: [
InfinitePostList(
fetcher: (page, batchSize, sort) =>
2021-04-05 20:14:39 +02:00
LemmyApiV3(instanceHost).run(GetPosts(
2021-01-24 20:01:55 +01:00
// TODO: switch between all and subscribed
type: PostListingType.all,
sort: sort,
limit: batchSize,
page: page,
2021-04-05 20:14:39 +02:00
savedOnly: false,
2021-04-11 18:27:22 +02:00
auth:
accStore.defaultUserDataFor(instanceHost)?.jwt.raw,
2021-01-24 20:01:55 +01:00
))),
2020-09-29 10:53:40 +02:00
InfiniteCommentList(
fetcher: (page, batchSize, sort) =>
2021-04-05 20:14:39 +02:00
LemmyApiV3(instanceHost).run(GetComments(
2021-01-24 20:01:55 +01:00
type: CommentListingType.all,
sort: sort,
limit: batchSize,
page: page,
2021-04-05 20:14:39 +02:00
savedOnly: false,
2021-04-11 18:27:22 +02:00
auth:
accStore.defaultUserDataFor(instanceHost)?.jwt.raw,
2021-01-24 20:01:55 +01:00
))),
_AboutTab(site,
communitiesFuture: communitiesFuture,
instanceHost: instanceHost),
],
),
),
),
);
}
}
2020-09-08 18:57:48 +02:00
class _AboutTab extends HookWidget {
final FullSiteView site;
2020-09-08 18:57:48 +02:00
final Future<List<CommunityView>> communitiesFuture;
final String instanceHost;
const _AboutTab(
this.site, {
required this.communitiesFuture,
required this.instanceHost,
});
2021-03-03 13:16:05 +01:00
void goToBannedUsers(BuildContext context) {
goTo(
2021-03-03 13:16:05 +01:00
context,
(_) => UsersListPage(
2021-03-03 13:16:05 +01:00
users: site.banned.reversed.toList(),
title: L10n.of(context).banned_users,
2021-03-03 13:16:05 +01:00
),
);
2020-09-08 19:52:09 +02:00
}
@override
Widget build(BuildContext context) {
2020-09-08 18:57:48 +02:00
final theme = Theme.of(context);
final commSnap = useFuture(communitiesFuture);
final accStore = useAccountsStore();
2020-09-08 18:57:48 +02:00
2020-09-08 23:01:40 +02:00
void goToCommunities() {
goTo(
context,
(_) => CommunitiesListPage(
2021-04-05 20:14:39 +02:00
fetcher: (page, batchSize, sortType) => LemmyApiV3(instanceHost).run(
ListCommunities(
type: PostListingType.local,
sort: sortType,
limit: batchSize,
page: page,
2021-04-11 18:27:22 +02:00
auth: accStore.defaultUserDataFor(instanceHost)?.jwt.raw,
),
),
title: 'Communities of ${site.siteView?.site.name}',
),
);
2020-09-08 23:01:40 +02:00
}
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(
2021-02-09 15:12:13 +01:00
height: 32,
child: ListView(
scrollDirection: Axis.horizontal,
2021-02-09 15:12:13 +01:00
padding: const EdgeInsets.symmetric(horizontal: 15),
children: [
2021-03-03 13:16:05 +01:00
Chip(
label: Text(L10n.of(context)
2021-03-03 13:16:05 +01:00
.number_of_users_online(site.online))),
2021-10-24 23:00:42 +02:00
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')),
2021-03-03 13:16:05 +01:00
Chip(
label: Text(L10n.of(context)
2021-04-11 00:20:47 +02:00
.number_of_users(siteView.counts.users))),
2021-02-09 15:12:13 +01:00
Chip(
label:
2021-04-11 00:20:47 +02:00
Text('${siteView.counts.communities} communities')),
Chip(label: Text('${siteView.counts.posts} posts')),
Chip(label: Text('${siteView.counts.comments} comments')),
2021-02-09 15:12:13 +01:00
].spaced(8),
),
),
2021-01-03 19:43:39 +01:00
const _Divider(),
2020-09-08 21:20:29 +02:00
ListTile(
title: Center(
child: Text(
'Trending communities:',
style: theme.textTheme.headline6?.copyWith(fontSize: 18),
2020-09-08 21:20:29 +02:00
),
),
2020-09-08 18:57:48 +02:00
),
if (commSnap.hasData)
for (final c in commSnap.data!)
ListTile(
onTap: () => goToCommunity.byId(
context, c.instanceHost, c.community.id),
title: Text(c.community.name),
2021-02-18 09:19:00 +01:00
leading: Avatar(url: c.community.icon),
)
2020-09-08 18:57:48 +02:00
else if (commSnap.hasError)
Padding(
2021-01-03 18:03:59 +01:00
padding: const EdgeInsets.all(8),
2020-09-08 18:57:48 +02:00
child: Text("Can't load communities, ${commSnap.error}"),
)
else
2021-01-03 19:43:39 +01:00
const Padding(
padding: EdgeInsets.symmetric(vertical: 10),
child: CircularProgressIndicator.adaptive(),
2020-09-08 18:57:48 +02:00
),
ListTile(
2021-01-03 19:43:39 +01:00
title: const Center(child: Text('See all')),
2020-09-08 19:52:09 +02:00
onTap: goToCommunities,
2020-09-08 18:57:48 +02:00
),
2021-01-03 19:43:39 +01:00
const _Divider(),
2020-09-08 19:14:05 +02:00
ListTile(
title: Center(
child: Text(
'Admins:',
style: theme.textTheme.headline6?.copyWith(fontSize: 18),
2020-09-08 19:14:05 +02:00
),
),
),
2021-01-31 14:38:47 +01:00
for (final u in site.admins)
PersonTile(
u.person,
expanded: true,
2021-01-31 14:38:47 +01:00
),
2021-01-03 19:43:39 +01:00
const _Divider(),
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),
2021-02-16 22:39:46 +01:00
),
),
],
),
),
);
}
}
class _Divider extends StatelessWidget {
2021-01-03 19:43:39 +01:00
const _Divider();
@override
2021-01-03 19:43:39 +01:00
Widget build(BuildContext context) => const Padding(
padding: EdgeInsets.symmetric(horizontal: 15, vertical: 10),
2020-09-08 23:10:40 +02:00
child: Divider(),
);
}