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 { class PostListController extends GetxController {
String? author; String? author;
String? realm;
/// The polling source modifier. /// The polling source modifier.
/// - `0`: default recommendations /// - `0`: default recommendations
@ -99,8 +100,10 @@ class PostListController extends GetxController {
final idx = <dynamic>{}; final idx = <dynamic>{};
postList.retainWhere((x) => idx.add(x.id)); postList.retainWhere((x) => idx.add(x.id));
var lastId = postList.map((x) => x.id).reduce(max); if (postList.isNotEmpty) {
Get.find<LastReadProvider>().feedLastReadAt = lastId; var lastId = postList.map((x) => x.id).reduce(max);
Get.find<LastReadProvider>().feedLastReadAt = lastId;
}
return result; return result;
} }
@ -123,16 +126,21 @@ class PostListController extends GetxController {
resp = await provider.listRecommendations( resp = await provider.listRecommendations(
pageKey, pageKey,
channel: 'shuffle', channel: 'shuffle',
realm: realm,
); );
break; break;
case 1: case 1:
resp = await provider.listRecommendations( resp = await provider.listRecommendations(
pageKey, pageKey,
channel: 'friends', channel: 'friends',
realm: realm,
); );
break; break;
default: default:
resp = await provider.listRecommendations(pageKey); resp = await provider.listRecommendations(
pageKey,
realm: realm,
);
break; 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/daily_sign.dart';
import 'package:solian/providers/last_read.dart'; import 'package:solian/providers/last_read.dart';
import 'package:solian/providers/link_expander.dart'; import 'package:solian/providers/link_expander.dart';
import 'package:solian/providers/navigation.dart';
import 'package:solian/providers/stickers.dart'; import 'package:solian/providers/stickers.dart';
import 'package:solian/providers/theme_switcher.dart'; import 'package:solian/providers/theme_switcher.dart';
import 'package:solian/providers/websocket.dart'; import 'package:solian/providers/websocket.dart';
@ -123,6 +124,8 @@ class SolianApp extends StatelessWidget {
} }
void _initializeProviders(BuildContext context) async { void _initializeProviders(BuildContext context) async {
Get.put(NavigationStateProvider());
Get.lazyPut(() => AuthProvider()); Get.lazyPut(() => AuthProvider());
Get.lazyPut(() => RelationshipProvider()); Get.lazyPut(() => RelationshipProvider());
Get.lazyPut(() => PostProvider()); 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:flutter/material.dart';
import 'package:gap/gap.dart'; import 'package:gap/gap.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:solian/controllers/post_list_controller.dart'; import 'package:solian/controllers/post_list_controller.dart';
import 'package:solian/providers/auth.dart'; import 'package:solian/providers/auth.dart';
import 'package:solian/providers/navigation.dart';
import 'package:solian/router.dart'; import 'package:solian/router.dart';
import 'package:solian/screens/account/notification.dart'; import 'package:solian/screens/account/notification.dart';
import 'package:solian/theme.dart'; import 'package:solian/theme.dart';
@ -25,21 +28,34 @@ class _FeedScreenState extends State<FeedScreen>
late final PostListController _postController; late final PostListController _postController;
late final TabController _tabController; late final TabController _tabController;
List<StreamSubscription>? _subscriptions;
@override @override
void initState() { void initState() {
super.initState(); super.initState();
final navState = Get.find<NavigationStateProvider>();
_postController = PostListController(); _postController = PostListController();
_postController.realm = navState.focusedRealm.value?.alias;
_tabController = TabController(length: 3, vsync: this); _tabController = TabController(length: 3, vsync: this);
_tabController.addListener(() { _tabController.addListener(() {
if (_postController.mode.value == _tabController.index) return; if (_postController.mode.value == _tabController.index) return;
_postController.mode.value = _tabController.index; _postController.mode.value = _tabController.index;
_postController.reloadAllOver(); _postController.reloadAllOver();
}); });
_subscriptions = [
Get.find<NavigationStateProvider>().focusedRealm.listen((value) {
if (value?.alias != _postController.realm) {
_postController.realm = value?.alias;
_postController.reloadAllOver();
}
}),
];
} }
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final AuthProvider auth = Get.find(); final AuthProvider auth = Get.find();
final NavigationStateProvider navState = Get.find();
return Material( return Material(
color: Theme.of(context).colorScheme.surface, color: Theme.of(context).colorScheme.surface,
@ -96,37 +112,51 @@ class _FeedScreenState extends State<FeedScreen>
); );
} }
return TabBarView( return Column(
physics: const NeverScrollableScrollPhysics(),
controller: _tabController,
children: [ children: [
RefreshIndicator( if (navState.focusedRealm.value != null)
onRefresh: () => _postController.reloadAllOver(), MaterialBanner(
child: CustomScrollView(slivers: [ leading: const Icon(Icons.layers),
PostWarpedListWidget( content: Text(
controller: _postController.pagingController, 'Browsing in realm #${navState.focusedRealm.value!.alias}',
onUpdate: () => _postController.reloadAllOver(),
), ),
]), 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 @override
void dispose() { void dispose() {
_postController.dispose(); _postController.dispose();
if (_subscriptions != null) {
for (final subscription in _subscriptions!) {
subscription.cancel();
}
}
super.dispose(); super.dispose();
} }
} }

View File

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