Prepare RuleGel 1.0
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
||||
import '../../../app/app.dart';
|
||||
import '../../../backend/backend.dart';
|
||||
import '../../../core/utils/result.dart';
|
||||
import 'feed_state.dart';
|
||||
@@ -153,6 +154,22 @@ class FeedController extends AsyncNotifier<FeedState> {
|
||||
await refresh();
|
||||
}
|
||||
|
||||
Future<void> hidePost(Post post) async {
|
||||
final current = state.value;
|
||||
if (current == null) return;
|
||||
final result =
|
||||
await ref.read(settingsServiceProvider).hidePostKey(post.cacheKey);
|
||||
if (result is Error<void>) return;
|
||||
state = AsyncData(
|
||||
current.copyWith(
|
||||
posts: current.posts
|
||||
.where((item) => item.cacheKey != post.cacheKey)
|
||||
.toList(growable: false),
|
||||
),
|
||||
);
|
||||
ref.invalidate(appSettingsProvider);
|
||||
}
|
||||
|
||||
Future<void> saveSession({double? scrollOffset}) async {
|
||||
final current = state.value;
|
||||
if (current == null) return;
|
||||
|
||||
@@ -249,6 +249,7 @@ class _FeedScreenState extends ConsumerState<FeedScreen> {
|
||||
_toggleFavorite(ref, post, favoriteKeys),
|
||||
onAddToCollection: (post) =>
|
||||
_addToCollection(context, ref, post),
|
||||
onHide: (post) => _hidePost(context, ref, post),
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -400,6 +401,30 @@ class _FeedScreenState extends ConsumerState<FeedScreen> {
|
||||
ref.invalidate(favoriteKeysProvider);
|
||||
ref.invalidate(favoritesControllerProvider);
|
||||
}
|
||||
|
||||
Future<void> _hidePost(
|
||||
BuildContext context,
|
||||
WidgetRef ref,
|
||||
Post post,
|
||||
) async {
|
||||
await ref.read(feedControllerProvider.notifier).hidePost(post);
|
||||
if (!context.mounted) return;
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: const Text('Post hidden locally'),
|
||||
action: SnackBarAction(
|
||||
label: 'Undo',
|
||||
onPressed: () async {
|
||||
await ref
|
||||
.read(settingsServiceProvider)
|
||||
.unhidePostKey(post.cacheKey);
|
||||
ref.invalidate(appSettingsProvider);
|
||||
ref.read(feedControllerProvider.notifier).refresh();
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _RefreshIntent extends Intent {
|
||||
|
||||
@@ -218,6 +218,7 @@ class PostDetailsScreen extends ConsumerWidget {
|
||||
onCopy: () =>
|
||||
Clipboard.setData(ClipboardData(text: post.fileUrl)),
|
||||
onSimilar: () => _openSimilar(context, ref, post),
|
||||
onHide: () => _hidePost(context, ref, post),
|
||||
onDownload: settings.allowDownloads
|
||||
? () => _download(context, ref, post)
|
||||
: null,
|
||||
@@ -300,6 +301,7 @@ class PostDetailsScreen extends ConsumerWidget {
|
||||
onOpen: () => launchUrl(Uri.parse(post.fileUrl)),
|
||||
onCopy: () => Clipboard.setData(ClipboardData(text: post.fileUrl)),
|
||||
onSimilar: () => _openSimilar(context, ref, post),
|
||||
onHide: () => _hidePost(context, ref, post),
|
||||
onDownload: settings.allowDownloads
|
||||
? () => _download(context, ref, post)
|
||||
: null,
|
||||
@@ -442,6 +444,21 @@ class PostDetailsScreen extends ConsumerWidget {
|
||||
ref.invalidate(favoritesControllerProvider);
|
||||
}
|
||||
|
||||
Future<void> _hidePost(
|
||||
BuildContext context,
|
||||
WidgetRef ref,
|
||||
Post post,
|
||||
) async {
|
||||
await ref.read(settingsServiceProvider).hidePostKey(post.cacheKey);
|
||||
ref.invalidate(appSettingsProvider);
|
||||
ref.read(feedControllerProvider.notifier).refresh();
|
||||
if (!context.mounted) return;
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(content: Text('Post hidden locally')),
|
||||
);
|
||||
_close(context);
|
||||
}
|
||||
|
||||
void _openPost(BuildContext context, Post post) {
|
||||
_replacePost(context, post);
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ class PostActionBar extends StatelessWidget {
|
||||
required this.isFavorite,
|
||||
this.onDownload,
|
||||
this.onSimilar,
|
||||
this.onHide,
|
||||
super.key,
|
||||
});
|
||||
|
||||
@@ -19,6 +20,7 @@ class PostActionBar extends StatelessWidget {
|
||||
final bool isFavorite;
|
||||
final VoidCallback? onDownload;
|
||||
final VoidCallback? onSimilar;
|
||||
final VoidCallback? onHide;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@@ -60,6 +62,12 @@ class PostActionBar extends StatelessWidget {
|
||||
onPressed: onDownload,
|
||||
icon: const Icon(Icons.download_rounded),
|
||||
),
|
||||
if (onHide != null)
|
||||
IconButton.filledTonal(
|
||||
tooltip: 'Hide post locally',
|
||||
onPressed: onHide,
|
||||
icon: const Icon(Icons.visibility_off_rounded),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
||||
import '../../../app/app.dart';
|
||||
import '../../../app/app_version.dart';
|
||||
import '../../../backend/backend.dart';
|
||||
import '../../../core/utils/logger.dart';
|
||||
import '../../../core/utils/result.dart';
|
||||
|
||||
final settingsControllerProvider =
|
||||
@@ -30,6 +32,34 @@ class SettingsController extends AsyncNotifier<AppSettings> {
|
||||
ref.invalidate(viewedKeysProvider);
|
||||
}
|
||||
|
||||
Future<void> clearHiddenPosts() async {
|
||||
await ref.read(settingsServiceProvider).clearHiddenPosts();
|
||||
ref.invalidate(appSettingsProvider);
|
||||
ref.invalidateSelf();
|
||||
}
|
||||
|
||||
Future<String> diagnosticsReport() async {
|
||||
final result =
|
||||
await ref.read(settingsServiceProvider).buildDiagnosticsReport(
|
||||
appVersion: appDisplayVersion,
|
||||
buildNumber: appBuildNumber,
|
||||
);
|
||||
return result is Success<String> ? result.data : '{}';
|
||||
}
|
||||
|
||||
Future<String> diagnosticLogs() async {
|
||||
final settings = state.value ?? AppSettings.defaults;
|
||||
final persistent = settings.diagnosticLogLines;
|
||||
final runtime = AppLogger.lines;
|
||||
return [...persistent, ...runtime].join('\n');
|
||||
}
|
||||
|
||||
Future<void> clearDiagnosticLogs() async {
|
||||
AppLogger.clear();
|
||||
await ref.read(settingsServiceProvider).clearDiagnosticLogs();
|
||||
ref.invalidateSelf();
|
||||
}
|
||||
|
||||
Future<String> exportJson() async {
|
||||
final result =
|
||||
await ref.read(settingsServiceProvider).exportSettingsToJson();
|
||||
|
||||
@@ -5,6 +5,7 @@ import 'package:go_router/go_router.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
|
||||
import '../../../app/app_version.dart';
|
||||
import '../../../app/changelog.dart';
|
||||
import '../../../backend/backend.dart';
|
||||
import '../../../core/utils/result.dart';
|
||||
import '../../../shared/widgets/adaptive_scaffold.dart';
|
||||
@@ -151,6 +152,15 @@ class SettingsScreen extends ConsumerWidget {
|
||||
label: const Text('Clear cache'),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
FilledButton.tonalIcon(
|
||||
onPressed: () => ref
|
||||
.read(settingsControllerProvider.notifier)
|
||||
.clearHiddenPosts(),
|
||||
icon: const Icon(Icons.visibility_off_rounded),
|
||||
label: Text(
|
||||
'Clear hidden posts (${settings.hiddenPostKeys.length})'),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
FilledButton.tonalIcon(
|
||||
onPressed: () => ref
|
||||
.read(settingsControllerProvider.notifier)
|
||||
@@ -186,8 +196,54 @@ class SettingsScreen extends ConsumerWidget {
|
||||
icon: const Icon(Icons.system_update_alt_rounded),
|
||||
label: const Text('Check for updates'),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
FilledButton.tonalIcon(
|
||||
onPressed: () async {
|
||||
final report = await ref
|
||||
.read(settingsControllerProvider.notifier)
|
||||
.diagnosticsReport();
|
||||
await Clipboard.setData(ClipboardData(text: report));
|
||||
if (context.mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(content: Text('Diagnostics copied')),
|
||||
);
|
||||
}
|
||||
},
|
||||
icon: const Icon(Icons.bug_report_rounded),
|
||||
label: const Text('Copy diagnostics report'),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
FilledButton.tonalIcon(
|
||||
onPressed: () async {
|
||||
final logs = await ref
|
||||
.read(settingsControllerProvider.notifier)
|
||||
.diagnosticLogs();
|
||||
await Clipboard.setData(ClipboardData(text: logs));
|
||||
if (context.mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(content: Text('Diagnostic logs copied')),
|
||||
);
|
||||
}
|
||||
},
|
||||
icon: const Icon(Icons.receipt_long_rounded),
|
||||
label: const Text('Copy local logs'),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
FilledButton.tonalIcon(
|
||||
onPressed: () => ref
|
||||
.read(settingsControllerProvider.notifier)
|
||||
.clearDiagnosticLogs(),
|
||||
icon: const Icon(Icons.delete_sweep_rounded),
|
||||
label: const Text('Clear local logs'),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
FilledButton.tonalIcon(
|
||||
onPressed: () => _showChangelog(context),
|
||||
icon: const Icon(Icons.new_releases_rounded),
|
||||
label: const Text('What changed'),
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
const Text('Version $appDisplayVersion'),
|
||||
const Text('Version $appDisplayVersion ($appBuildNumber)'),
|
||||
],
|
||||
),
|
||||
),
|
||||
@@ -305,6 +361,45 @@ class SettingsScreen extends ConsumerWidget {
|
||||
);
|
||||
ref.invalidate(settingsControllerProvider);
|
||||
}
|
||||
|
||||
Future<void> _showChangelog(BuildContext context) {
|
||||
return showDialog<void>(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
title: const Text('RuleGel changelog'),
|
||||
content: ConstrainedBox(
|
||||
constraints: const BoxConstraints(maxWidth: 560),
|
||||
child: SingleChildScrollView(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
for (final change in ruleGelChangelog) ...[
|
||||
Text(
|
||||
'${change.version} - ${change.title}',
|
||||
style: Theme.of(context).textTheme.titleMedium,
|
||||
),
|
||||
const SizedBox(height: 6),
|
||||
for (final bullet in change.bullets)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(left: 8, bottom: 4),
|
||||
child: Text('- $bullet'),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(context),
|
||||
child: const Text('Close'),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _TagListEditor extends StatefulWidget {
|
||||
|
||||
Reference in New Issue
Block a user