Smooth mobile post swipes and export providers

This commit is contained in:
2026-05-23 12:27:20 +03:00
parent f9938e393d
commit 00ad16dc51
3 changed files with 109 additions and 14 deletions
+31 -5
View File
@@ -227,18 +227,44 @@ class SettingsService {
Future<Result<String>> exportSettingsToJson() async {
final result = await getSettings();
return result.fold(
onSuccess: (settings) => Success(jsonEncode(settings.toJson())),
onError: Error<String>.new,
if (result is Error<AppSettings>) return Error(result.failure);
final settings = (result as Success<AppSettings>).data;
final providers = <ContentProviderConfig>[];
final repository = _providerRepository;
if (repository != null) {
final providersResult = await repository.getProviders(enabledOnly: false);
if (providersResult is Success<List<ContentProviderConfig>>) {
providers.addAll(providersResult.data);
}
}
return Success(
const JsonEncoder.withIndent(' ').convert({
'schemaVersion': 2,
'settings': settings.toJson(),
'providers': providers.map((provider) => provider.toJson()).toList(),
}),
);
}
Future<Result<void>> importSettingsFromJson(String json) async {
try {
final settings =
AppSettings.fromJson(jsonDecode(json) as Map<String, dynamic>);
final decoded = jsonDecode(json) as Map<String, dynamic>;
final settingsJson =
(decoded['settings'] as Map?)?.cast<String, dynamic>() ?? decoded;
final settings = AppSettings.fromJson(settingsJson);
final result = await updateSettings(settings);
if (result is Error<void>) return result;
final repository = _providerRepository;
final providerItems = decoded['providers'];
if (repository != null && providerItems is List) {
for (final item in providerItems.whereType<Map>()) {
await repository.saveProvider(
ContentProviderConfig.fromJson(
Map<String, dynamic>.from(item),
).copyWith(updatedAt: DateTime.now()),
);
}
}
await saveEnabledProviders(settings.enabledProviderIds);
return result;
} catch (error) {
@@ -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<Post> 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});
@@ -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'),