Focused realm linked with feed stream

This commit is contained in:
LittleSheep 2024-09-13 00:17:56 +08:00
parent 6daa04c208
commit dd01f964d4
5 changed files with 171 additions and 114 deletions

View File

@ -9,6 +9,7 @@ import 'package:solian/providers/last_read.dart';
class PostListController extends GetxController {
String? author;
String? realm;
/// The polling source modifier.
/// - `0`: default recommendations
@ -99,8 +100,10 @@ class PostListController extends GetxController {
final idx = <dynamic>{};
postList.retainWhere((x) => idx.add(x.id));
var lastId = postList.map((x) => x.id).reduce(max);
Get.find<LastReadProvider>().feedLastReadAt = lastId;
if (postList.isNotEmpty) {
var lastId = postList.map((x) => x.id).reduce(max);
Get.find<LastReadProvider>().feedLastReadAt = lastId;
}
return result;
}
@ -123,16 +126,21 @@ class PostListController extends GetxController {
resp = await provider.listRecommendations(
pageKey,
channel: 'shuffle',
realm: realm,
);
break;
case 1:
resp = await provider.listRecommendations(
pageKey,
channel: 'friends',
realm: realm,
);
break;
default:
resp = await provider.listRecommendations(pageKey);
resp = await provider.listRecommendations(
pageKey,
realm: realm,
);
break;
}
}

View File

@ -15,6 +15,7 @@ import 'package:solian/providers/attachment_uploader.dart';
import 'package:solian/providers/daily_sign.dart';
import 'package:solian/providers/last_read.dart';
import 'package:solian/providers/link_expander.dart';
import 'package:solian/providers/navigation.dart';
import 'package:solian/providers/stickers.dart';
import 'package:solian/providers/theme_switcher.dart';
import 'package:solian/providers/websocket.dart';
@ -123,6 +124,8 @@ class SolianApp extends StatelessWidget {
}
void _initializeProviders(BuildContext context) async {
Get.put(NavigationStateProvider());
Get.lazyPut(() => AuthProvider());
Get.lazyPut(() => RelationshipProvider());
Get.lazyPut(() => PostProvider());

View File

@ -0,0 +1,6 @@
import 'package:get/get.dart';
import 'package:solian/models/realm.dart';
class NavigationStateProvider extends GetxController {
final Rx<Realm?> focusedRealm = Rx(null);
}

View File

@ -1,8 +1,11 @@
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:gap/gap.dart';
import 'package:get/get.dart';
import 'package:solian/controllers/post_list_controller.dart';
import 'package:solian/providers/auth.dart';
import 'package:solian/providers/navigation.dart';
import 'package:solian/router.dart';
import 'package:solian/screens/account/notification.dart';
import 'package:solian/theme.dart';
@ -25,21 +28,34 @@ class _FeedScreenState extends State<FeedScreen>
late final PostListController _postController;
late final TabController _tabController;
List<StreamSubscription>? _subscriptions;
@override
void initState() {
super.initState();
final navState = Get.find<NavigationStateProvider>();
_postController = PostListController();
_postController.realm = navState.focusedRealm.value?.alias;
_tabController = TabController(length: 3, vsync: this);
_tabController.addListener(() {
if (_postController.mode.value == _tabController.index) return;
_postController.mode.value = _tabController.index;
_postController.reloadAllOver();
});
_subscriptions = [
Get.find<NavigationStateProvider>().focusedRealm.listen((value) {
if (value?.alias != _postController.realm) {
_postController.realm = value?.alias;
_postController.reloadAllOver();
}
}),
];
}
@override
Widget build(BuildContext context) {
final AuthProvider auth = Get.find();
final NavigationStateProvider navState = Get.find();
return Material(
color: Theme.of(context).colorScheme.surface,
@ -96,37 +112,51 @@ class _FeedScreenState extends State<FeedScreen>
);
}
return TabBarView(
physics: const NeverScrollableScrollPhysics(),
controller: _tabController,
return Column(
children: [
RefreshIndicator(
onRefresh: () => _postController.reloadAllOver(),
child: CustomScrollView(slivers: [
PostWarpedListWidget(
controller: _postController.pagingController,
onUpdate: () => _postController.reloadAllOver(),
if (navState.focusedRealm.value != null)
MaterialBanner(
leading: const Icon(Icons.layers),
content: Text(
'Browsing in realm #${navState.focusedRealm.value!.alias}',
),
]),
actions: const [SizedBox.shrink()],
),
Expanded(
child: TabBarView(
physics: const NeverScrollableScrollPhysics(),
controller: _tabController,
children: [
RefreshIndicator(
onRefresh: () => _postController.reloadAllOver(),
child: CustomScrollView(slivers: [
PostWarpedListWidget(
controller: _postController.pagingController,
onUpdate: () => _postController.reloadAllOver(),
),
]),
),
Obx(() {
if (auth.isAuthorized.value) {
return RefreshIndicator(
onRefresh: () => _postController.reloadAllOver(),
child: CustomScrollView(slivers: [
PostWarpedListWidget(
controller: _postController.pagingController,
onUpdate: () => _postController.reloadAllOver(),
),
]),
);
} else {
return SigninRequiredOverlay(
onSignedIn: () => _postController.reloadAllOver(),
);
}
}),
PostShuffleSwiper(controller: _postController),
],
),
),
Obx(() {
if (auth.isAuthorized.value) {
return RefreshIndicator(
onRefresh: () => _postController.reloadAllOver(),
child: CustomScrollView(slivers: [
PostWarpedListWidget(
controller: _postController.pagingController,
onUpdate: () => _postController.reloadAllOver(),
),
]),
);
} else {
return SigninRequiredOverlay(
onSignedIn: () => _postController.reloadAllOver(),
);
}
}),
PostShuffleSwiper(controller: _postController),
],
);
}),
@ -138,6 +168,11 @@ class _FeedScreenState extends State<FeedScreen>
@override
void dispose() {
_postController.dispose();
if (_subscriptions != null) {
for (final subscription in _subscriptions!) {
subscription.cancel();
}
}
super.dispose();
}
}

View File

@ -4,6 +4,7 @@ import 'package:solian/models/realm.dart';
import 'package:solian/providers/auth.dart';
import 'package:solian/providers/content/channel.dart';
import 'package:solian/providers/content/realm.dart';
import 'package:solian/providers/navigation.dart';
import 'package:solian/widgets/account/account_avatar.dart';
import 'package:solian/widgets/channel/channel_list.dart';
@ -21,8 +22,6 @@ class AppNavigationRegion extends StatefulWidget {
class _AppNavigationRegionState extends State<AppNavigationRegion>
with SingleTickerProviderStateMixin {
Realm? _focusedRealm;
bool _isTryingExit = false;
late final AnimationController _animationController = AnimationController(
@ -39,14 +38,18 @@ class _AppNavigationRegionState extends State<AppNavigationRegion>
void _focusRealm(Realm item) {
_animationController.animateTo(1).then((_) {
setState(() => _focusedRealm = item);
setState(
() => Get.find<NavigationStateProvider>().focusedRealm.value = item,
);
_animationController.animateTo(0);
});
}
void _unFocusRealm() {
_animationController.animateTo(1).then((_) {
setState(() => _focusedRealm = null);
setState(
() => Get.find<NavigationStateProvider>().focusedRealm.value = null,
);
_animationController.animateTo(0);
});
}
@ -58,6 +61,7 @@ class _AppNavigationRegionState extends State<AppNavigationRegion>
}
Widget _buildRealmFocusAvatar() {
final focusedRealm = Get.find<NavigationStateProvider>().focusedRealm.value;
return MouseRegion(
child: AnimatedSwitcher(
switchInCurve: Curves.fastOutSlowIn,
@ -83,7 +87,7 @@ class _AppNavigationRegionState extends State<AppNavigationRegion>
),
onTap: () => _unFocusRealm(),
)
: _buildEntryAvatar(_focusedRealm!),
: _buildEntryAvatar(focusedRealm!),
),
onEnter: (_) => setState(() => _isTryingExit = true),
onExit: (_) => setState(() => _isTryingExit = false),
@ -137,88 +141,89 @@ class _AppNavigationRegionState extends State<AppNavigationRegion>
final RealmProvider realms = Get.find();
final ChannelProvider channels = Get.find();
final AuthProvider auth = Get.find();
final NavigationStateProvider navState = Get.find();
return AnimatedBuilder(
animation: _animationController,
builder: (context, child) {
return SlideTransition(
position: _animationTween,
child: child,
);
},
child: _focusedRealm == null
? Obx(() {
if (widget.isCollapsed) {
return CustomScrollView(
slivers: [
const SliverPadding(padding: EdgeInsets.only(top: 8)),
SliverList.builder(
itemCount: realms.availableRealms.length,
itemBuilder: (context, index) {
final element = realms.availableRealms[index];
return Tooltip(
message: element.name,
child: _buildEntry(context, element),
);
},
),
],
);
}
return CustomScrollView(
slivers: [
const SliverPadding(padding: EdgeInsets.only(top: 8)),
SliverList.builder(
itemCount: realms.availableRealms.length,
itemBuilder: (context, index) {
final element = realms.availableRealms[index];
return _buildEntry(context, element);
},
),
const SliverPadding(padding: EdgeInsets.only(bottom: 8)),
],
);
})
: Column(
children: [
if (widget.isCollapsed)
Tooltip(
message: _focusedRealm!.name,
child: _buildRealmFocusAvatar().paddingSymmetric(
vertical: 8,
),
return Obx(
() => AnimatedBuilder(
animation: _animationController,
builder: (context, child) {
return SlideTransition(
position: _animationTween,
child: child,
);
},
child: navState.focusedRealm.value == null
? widget.isCollapsed
? CustomScrollView(
slivers: [
const SliverPadding(padding: EdgeInsets.only(top: 8)),
SliverList.builder(
itemCount: realms.availableRealms.length,
itemBuilder: (context, index) {
final element = realms.availableRealms[index];
return Tooltip(
message: element.name,
child: _buildEntry(context, element),
);
},
),
],
)
else
ListTile(
minTileHeight: 0,
tileColor:
Theme.of(context).colorScheme.surfaceContainerLow,
leading: _buildRealmFocusAvatar(),
contentPadding:
const EdgeInsets.symmetric(horizontal: 20, vertical: 8),
title: Text(_focusedRealm!.name),
subtitle: Text(
_focusedRealm!.description,
maxLines: 1,
overflow: TextOverflow.ellipsis,
: CustomScrollView(
slivers: [
const SliverPadding(padding: EdgeInsets.only(top: 8)),
SliverList.builder(
itemCount: realms.availableRealms.length,
itemBuilder: (context, index) {
final element = realms.availableRealms[index];
return _buildEntry(context, element);
},
),
const SliverPadding(padding: EdgeInsets.only(bottom: 8)),
],
)
: Column(
children: [
if (widget.isCollapsed)
Tooltip(
message: navState.focusedRealm.value!.name,
child: _buildRealmFocusAvatar().paddingSymmetric(
vertical: 8,
),
)
else
ListTile(
minTileHeight: 0,
tileColor:
Theme.of(context).colorScheme.surfaceContainerLow,
leading: _buildRealmFocusAvatar(),
contentPadding: const EdgeInsets.symmetric(
horizontal: 20, vertical: 8),
title: Text(navState.focusedRealm.value!.name),
subtitle: Text(
navState.focusedRealm.value!.description,
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
),
Expanded(
child: Obx(
() => ChannelListWidget(
channels: channels.availableChannels
.where(
(x) =>
x.realm?.id ==
navState.focusedRealm.value?.id,
)
.toList(),
selfId: auth.userProfile.value!['id'],
noCategory: true,
),
),
),
Expanded(
child: Obx(
() => ChannelListWidget(
channels: channels.availableChannels
.where(
(x) => x.realm?.externalId == _focusedRealm?.id,
)
.toList(),
selfId: auth.userProfile.value!['id'],
noCategory: true,
),
),
),
],
),
],
),
),
);
}
}