feat: persist last active tab and preserve per-tab navigation state
This commit is contained in:
@@ -0,0 +1,41 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../backend/models/post.dart';
|
||||
import '../features/post/presentation/post_details_screen.dart';
|
||||
import '../features/post/presentation/similar_posts_screen.dart';
|
||||
|
||||
class AppNavigator {
|
||||
const AppNavigator._();
|
||||
|
||||
static void openPost(
|
||||
BuildContext context, {
|
||||
required Post post,
|
||||
List<Post>? postsList,
|
||||
}) {
|
||||
Navigator.of(context).push(
|
||||
MaterialPageRoute<void>(
|
||||
builder: (ctx) => PostDetailsScreen(
|
||||
providerId: post.providerId,
|
||||
postId: post.id,
|
||||
initialPost: post,
|
||||
postsList: postsList,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
static void openSimilarPosts(
|
||||
BuildContext context, {
|
||||
required Post post,
|
||||
}) {
|
||||
Navigator.of(context).push(
|
||||
MaterialPageRoute<void>(
|
||||
builder: (ctx) => SimilarPostsScreen(
|
||||
providerId: post.providerId,
|
||||
postId: post.id,
|
||||
initialPost: post,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,3 @@
|
||||
const appDisplayVersion = '3.5.4';
|
||||
const appBuildNumber = 80;
|
||||
const appDisplayVersion = '3.5.5';
|
||||
const appBuildNumber = 81;
|
||||
const appReleaseChannel = 'Stable';
|
||||
|
||||
@@ -1,4 +1,13 @@
|
||||
const prismaChangelog = [
|
||||
PrismaChange(
|
||||
version: '3.5.5',
|
||||
title: 'Сохранение последней вкладки и состояния навигации между разделами',
|
||||
bullets: [
|
||||
'Сохранение последней вкладки: приложение запоминает активный раздел при выходе или закрытии и автоматически открывает его при следующем запуске.',
|
||||
'Независимая история и сохранение места: при переключении между вкладками состояние каждого раздела полностью сохраняется. Например, если открыть публикацию или карточку у артиста и переключиться в поиск или ленту, при возврате обратно вы останетесь ровно на том же посте.',
|
||||
'Бесшовные переходы между ветками навигации: улучшена обработка системной кнопки «Назад», переход по разделам стал еще быстрее и плавнее.',
|
||||
],
|
||||
),
|
||||
PrismaChange(
|
||||
version: '3.5.4',
|
||||
title: 'Исправление распознавания MP4-видео и отключение устаревших сервисов Kemono/Coomer',
|
||||
|
||||
+153
-98
@@ -21,116 +21,171 @@ import '../features/settings/presentation/settings_screen.dart';
|
||||
import '../features/viewed/presentation/viewed_screen.dart';
|
||||
import '../shared/widgets/app_shell.dart';
|
||||
|
||||
final branchNavKeys = List.generate(8, (_) => GlobalKey<NavigatorState>());
|
||||
|
||||
final appRouterProvider = Provider<GoRouter>((ref) {
|
||||
return GoRouter(
|
||||
initialLocation: '/',
|
||||
routes: [
|
||||
ShellRoute(
|
||||
builder: (context, state, child) => AppShell(child: child),
|
||||
StatefulShellRoute.indexedStack(
|
||||
builder: (context, state, navigationShell) => AppShell(
|
||||
navigationShell: navigationShell,
|
||||
child: navigationShell,
|
||||
),
|
||||
branches: [
|
||||
StatefulShellBranch(
|
||||
navigatorKey: branchNavKeys[0],
|
||||
routes: [
|
||||
GoRoute(
|
||||
path: '/',
|
||||
pageBuilder: (context, state) => _transitionPage(
|
||||
state,
|
||||
child: FeedScreen(initialQuery: state.uri.queryParameters['q']),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
StatefulShellBranch(
|
||||
navigatorKey: branchNavKeys[1],
|
||||
routes: [
|
||||
GoRoute(
|
||||
path: '/search',
|
||||
builder: (context, state) => const SearchScreen(),
|
||||
),
|
||||
],
|
||||
),
|
||||
StatefulShellBranch(
|
||||
navigatorKey: branchNavKeys[2],
|
||||
routes: [
|
||||
GoRoute(
|
||||
path: '/favorites',
|
||||
builder: (context, state) => const FavoritesScreen(),
|
||||
),
|
||||
],
|
||||
),
|
||||
StatefulShellBranch(
|
||||
navigatorKey: branchNavKeys[3],
|
||||
routes: [
|
||||
GoRoute(
|
||||
path: '/viewed',
|
||||
builder: (context, state) => const ViewedScreen(),
|
||||
),
|
||||
],
|
||||
),
|
||||
StatefulShellBranch(
|
||||
navigatorKey: branchNavKeys[4],
|
||||
routes: [
|
||||
GoRoute(
|
||||
path: '/collections',
|
||||
builder: (context, state) => const CollectionsScreen(),
|
||||
routes: [
|
||||
GoRoute(
|
||||
path: ':collectionId',
|
||||
builder: (context, state) => CollectionDetailsScreen(
|
||||
collectionId: state.pathParameters['collectionId']!,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
StatefulShellBranch(
|
||||
navigatorKey: branchNavKeys[5],
|
||||
routes: [
|
||||
GoRoute(
|
||||
path: '/artists',
|
||||
builder: (context, state) => const ArtistsScreen(),
|
||||
routes: [
|
||||
GoRoute(
|
||||
path: ':providerId/:service/:artistId',
|
||||
builder: (context, state) => ArtistPostsScreen(
|
||||
providerId: state.pathParameters['providerId']!,
|
||||
service: state.pathParameters['service']!,
|
||||
artistId: state.pathParameters['artistId']!,
|
||||
artistName: state.uri.queryParameters['name'] ??
|
||||
state.pathParameters['artistId']!,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
StatefulShellBranch(
|
||||
navigatorKey: branchNavKeys[6],
|
||||
routes: [
|
||||
GoRoute(
|
||||
path: '/providers',
|
||||
builder: (context, state) => const ProvidersScreen(),
|
||||
routes: [
|
||||
GoRoute(
|
||||
path: 'new',
|
||||
builder: (context, state) => ProviderFormScreen(
|
||||
initialConfig: state.extra is ContentProviderConfig
|
||||
? state.extra! as ContentProviderConfig
|
||||
: null,
|
||||
),
|
||||
),
|
||||
GoRoute(
|
||||
path: 'check',
|
||||
builder: (context, state) => const ProviderCheckScreen(),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
StatefulShellBranch(
|
||||
navigatorKey: branchNavKeys[7],
|
||||
routes: [
|
||||
GoRoute(
|
||||
path: '/settings',
|
||||
builder: (context, state) => const SettingsScreen(),
|
||||
routes: [
|
||||
GoRoute(
|
||||
path: 'hidden',
|
||||
builder: (context, state) => const HiddenPostsScreen(),
|
||||
),
|
||||
GoRoute(
|
||||
path: 'cache',
|
||||
builder: (context, state) => const CacheManagerScreen(),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
GoRoute(
|
||||
path: '/post/:providerId/:postId',
|
||||
builder: (context, state) {
|
||||
final extra = state.extra;
|
||||
final Post? initialPost;
|
||||
final List<Post>? postsList;
|
||||
if (extra is PostNavigationContext) {
|
||||
initialPost = extra.currentPost;
|
||||
postsList = extra.posts;
|
||||
} else if (extra is Post) {
|
||||
initialPost = extra;
|
||||
postsList = null;
|
||||
} else {
|
||||
initialPost = null;
|
||||
postsList = null;
|
||||
}
|
||||
return PostDetailsScreen(
|
||||
providerId: state.pathParameters['providerId']!,
|
||||
postId: state.pathParameters['postId']!,
|
||||
initialPost: initialPost,
|
||||
postsList: postsList,
|
||||
);
|
||||
},
|
||||
routes: [
|
||||
GoRoute(
|
||||
path: '/',
|
||||
pageBuilder: (context, state) => _transitionPage(
|
||||
state,
|
||||
child: FeedScreen(initialQuery: state.uri.queryParameters['q']),
|
||||
),
|
||||
),
|
||||
GoRoute(
|
||||
path: '/search',
|
||||
builder: (context, state) => const SearchScreen(),
|
||||
),
|
||||
GoRoute(
|
||||
path: '/post/:providerId/:postId',
|
||||
builder: (context, state) {
|
||||
final extra = state.extra;
|
||||
final Post? initialPost;
|
||||
final List<Post>? postsList;
|
||||
if (extra is PostNavigationContext) {
|
||||
initialPost = extra.currentPost;
|
||||
postsList = extra.posts;
|
||||
} else if (extra is Post) {
|
||||
initialPost = extra;
|
||||
postsList = null;
|
||||
} else {
|
||||
initialPost = null;
|
||||
postsList = null;
|
||||
}
|
||||
return PostDetailsScreen(
|
||||
providerId: state.pathParameters['providerId']!,
|
||||
postId: state.pathParameters['postId']!,
|
||||
initialPost: initialPost,
|
||||
postsList: postsList,
|
||||
);
|
||||
},
|
||||
),
|
||||
GoRoute(
|
||||
path: '/post/:providerId/:postId/similar',
|
||||
path: 'similar',
|
||||
builder: (context, state) => SimilarPostsScreen(
|
||||
providerId: state.pathParameters['providerId']!,
|
||||
postId: state.pathParameters['postId']!,
|
||||
initialPost: state.extra is Post ? state.extra! as Post : null,
|
||||
),
|
||||
),
|
||||
GoRoute(
|
||||
path: '/favorites',
|
||||
builder: (context, state) => const FavoritesScreen(),
|
||||
),
|
||||
GoRoute(
|
||||
path: '/viewed',
|
||||
builder: (context, state) => const ViewedScreen(),
|
||||
),
|
||||
GoRoute(
|
||||
path: '/collections',
|
||||
builder: (context, state) => const CollectionsScreen(),
|
||||
),
|
||||
GoRoute(
|
||||
path: '/collections/:collectionId',
|
||||
builder: (context, state) => CollectionDetailsScreen(
|
||||
collectionId: state.pathParameters['collectionId']!,
|
||||
),
|
||||
),
|
||||
GoRoute(
|
||||
path: '/artists',
|
||||
builder: (context, state) => const ArtistsScreen(),
|
||||
),
|
||||
GoRoute(
|
||||
path: '/artists/:providerId/:service/:artistId',
|
||||
builder: (context, state) => ArtistPostsScreen(
|
||||
providerId: state.pathParameters['providerId']!,
|
||||
service: state.pathParameters['service']!,
|
||||
artistId: state.pathParameters['artistId']!,
|
||||
artistName: state.uri.queryParameters['name'] ??
|
||||
state.pathParameters['artistId']!,
|
||||
),
|
||||
),
|
||||
GoRoute(
|
||||
path: '/providers',
|
||||
builder: (context, state) => const ProvidersScreen(),
|
||||
),
|
||||
GoRoute(
|
||||
path: '/providers/new',
|
||||
builder: (context, state) => ProviderFormScreen(
|
||||
initialConfig: state.extra is ContentProviderConfig
|
||||
? state.extra! as ContentProviderConfig
|
||||
: null,
|
||||
),
|
||||
),
|
||||
GoRoute(
|
||||
path: '/providers/check',
|
||||
builder: (context, state) => const ProviderCheckScreen(),
|
||||
),
|
||||
GoRoute(
|
||||
path: '/settings',
|
||||
builder: (context, state) => const SettingsScreen(),
|
||||
),
|
||||
GoRoute(
|
||||
path: '/settings/hidden',
|
||||
builder: (context, state) => const HiddenPostsScreen(),
|
||||
),
|
||||
GoRoute(
|
||||
path: '/settings/cache',
|
||||
builder: (context, state) => const CacheManagerScreen(),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
|
||||
Reference in New Issue
Block a user