✨ Focused realm linked with feed stream
This commit is contained in:
parent
6daa04c208
commit
dd01f964d4
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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());
|
||||||
|
6
lib/providers/navigation.dart
Normal file
6
lib/providers/navigation.dart
Normal 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);
|
||||||
|
}
|
@ -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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user