Smooth mobile post swipes and export providers
This commit is contained in:
@@ -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'),
|
||||
|
||||
Reference in New Issue
Block a user