diff --git a/lib/backend/services/settings_service.dart b/lib/backend/services/settings_service.dart index de8ec26..a7d9578 100644 --- a/lib/backend/services/settings_service.dart +++ b/lib/backend/services/settings_service.dart @@ -227,18 +227,44 @@ class SettingsService { Future> exportSettingsToJson() async { final result = await getSettings(); - return result.fold( - onSuccess: (settings) => Success(jsonEncode(settings.toJson())), - onError: Error.new, + if (result is Error) return Error(result.failure); + final settings = (result as Success).data; + final providers = []; + final repository = _providerRepository; + if (repository != null) { + final providersResult = await repository.getProviders(enabledOnly: false); + if (providersResult is Success>) { + providers.addAll(providersResult.data); + } + } + return Success( + const JsonEncoder.withIndent(' ').convert({ + 'schemaVersion': 2, + 'settings': settings.toJson(), + 'providers': providers.map((provider) => provider.toJson()).toList(), + }), ); } Future> importSettingsFromJson(String json) async { try { - final settings = - AppSettings.fromJson(jsonDecode(json) as Map); + final decoded = jsonDecode(json) as Map; + final settingsJson = + (decoded['settings'] as Map?)?.cast() ?? decoded; + final settings = AppSettings.fromJson(settingsJson); final result = await updateSettings(settings); if (result is Error) return result; + final repository = _providerRepository; + final providerItems = decoded['providers']; + if (repository != null && providerItems is List) { + for (final item in providerItems.whereType()) { + await repository.saveProvider( + ContentProviderConfig.fromJson( + Map.from(item), + ).copyWith(updatedAt: DateTime.now()), + ); + } + } await saveEnabledProviders(settings.enabledProviderIds); return result; } catch (error) { diff --git a/lib/features/post/presentation/post_details_screen.dart b/lib/features/post/presentation/post_details_screen.dart index 47c359b..c82815f 100644 --- a/lib/features/post/presentation/post_details_screen.dart +++ b/lib/features/post/presentation/post_details_screen.dart @@ -79,15 +79,13 @@ class PostDetailsScreen extends ConsumerWidget { : null; if (Responsive.isMobile(context)) { if (currentIndex >= 0 && feedPosts.length > 1) { - return PageView.builder( - controller: PageController(initialPage: currentIndex), - itemCount: feedPosts.length, - onPageChanged: (index) => - _replacePost(context, feedPosts[index]), - itemBuilder: (context, index) => _buildMobileDetails( + return _MobilePostPager( + posts: feedPosts, + initialIndex: currentIndex, + buildDetails: (context, post) => _buildMobileDetails( context, ref, - feedPosts[index], + post, settings, favoriteKeys, ), @@ -303,6 +301,77 @@ class PostDetailsScreen extends ConsumerWidget { } } +class _MobilePostPager extends StatefulWidget { + const _MobilePostPager({ + required this.posts, + required this.initialIndex, + required this.buildDetails, + }); + + final List posts; + final int initialIndex; + final Widget Function(BuildContext context, Post post) buildDetails; + + @override + State<_MobilePostPager> createState() => _MobilePostPagerState(); +} + +class _MobilePostPagerState extends State<_MobilePostPager> { + late final PageController _controller; + late int _currentIndex; + + @override + void initState() { + super.initState(); + _currentIndex = widget.initialIndex; + _controller = PageController(initialPage: widget.initialIndex); + } + + @override + void didUpdateWidget(covariant _MobilePostPager oldWidget) { + super.didUpdateWidget(oldWidget); + if (widget.initialIndex != _currentIndex && + widget.initialIndex >= 0 && + widget.initialIndex < widget.posts.length) { + _currentIndex = widget.initialIndex; + if (_controller.hasClients) { + _controller.jumpToPage(widget.initialIndex); + } + } + } + + @override + void dispose() { + _controller.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return PageView.builder( + controller: _controller, + itemCount: widget.posts.length, + onPageChanged: (index) { + _currentIndex = index; + final post = widget.posts[index]; + WidgetsBinding.instance.addPostFrameCallback((_) { + if (!mounted) return; + GoRouter.of(context).replace( + '/post/${post.providerId}/${post.id}', + extra: post, + ); + }); + }, + itemBuilder: (context, index) { + return KeyedSubtree( + key: ValueKey(widget.posts[index].cacheKey), + child: widget.buildDetails(context, widget.posts[index]), + ); + }, + ); + } +} + class _CommentsSection extends ConsumerStatefulWidget { const _CommentsSection({required this.post}); diff --git a/lib/features/settings/presentation/settings_screen.dart b/lib/features/settings/presentation/settings_screen.dart index 2c156c9..369d0af 100644 --- a/lib/features/settings/presentation/settings_screen.dart +++ b/lib/features/settings/presentation/settings_screen.dart @@ -107,13 +107,13 @@ class SettingsScreen extends ConsumerWidget { } }, icon: const Icon(Icons.upload_file_rounded), - label: const Text('Export settings JSON'), + label: const Text('Export settings and providers JSON'), ), const SizedBox(height: 12), FilledButton.tonalIcon( onPressed: () => _importDialog(context, ref), icon: const Icon(Icons.download_for_offline_rounded), - label: const Text('Import settings JSON'), + label: const Text('Import settings and providers JSON'), ), const SizedBox(height: 24), const Text('Version 0.1.0'),