refactor: share low-risk UI and tracker helpers
This commit is contained in:
@@ -18,6 +18,15 @@ bool flexibleBool(Object? v) => switch (v) {
|
||||
_ => false,
|
||||
};
|
||||
|
||||
/// Parse a value that may be [bool], [int] (0/1), or [String] ('1') to [bool].
|
||||
/// Returns `null` for `null` or unrecognised values.
|
||||
bool? flexibleBoolNullable(Object? v) => switch (v) {
|
||||
final bool b => b,
|
||||
final int n => n == 1,
|
||||
final String s => s == '1',
|
||||
_ => null,
|
||||
};
|
||||
|
||||
/// Parse a value that may be [double], [num], or [String] to [double].
|
||||
double? flexibleDouble(Object? v) => switch (v) {
|
||||
final num n => n.toDouble(),
|
||||
|
||||
@@ -35,6 +35,21 @@ void scrollToCurrentItem(ScrollController controller, GlobalKey firstItemKey, in
|
||||
});
|
||||
}
|
||||
|
||||
/// Owns the boilerplate for one-time initial scrolling in selectable lists.
|
||||
class InitialItemScrollController {
|
||||
final GlobalKey firstItemKey = GlobalKey();
|
||||
final ScrollController controller = ScrollController();
|
||||
bool _didInitialScroll = false;
|
||||
|
||||
void maybeScrollTo(int? selectedIndex) {
|
||||
if (_didInitialScroll || selectedIndex == null || selectedIndex <= 0) return;
|
||||
_didInitialScroll = true;
|
||||
scrollToCurrentItem(controller, firstItemKey, selectedIndex);
|
||||
}
|
||||
|
||||
void dispose() => controller.dispose();
|
||||
}
|
||||
|
||||
/// Scroll a horizontal list to center the item at the given index.
|
||||
///
|
||||
/// Assumes items are laid out with [leadingPadding] before the first item,
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
|
||||
import '../i18n/strings.g.dart';
|
||||
import '../services/update_service.dart';
|
||||
import '../widgets/dialog_action_button.dart';
|
||||
|
||||
Future<void> showUpdateAvailableDialog(
|
||||
BuildContext context,
|
||||
Map<String, dynamic> updateInfo, {
|
||||
required String title,
|
||||
required String dismissLabel,
|
||||
bool showSkipVersion = false,
|
||||
}) {
|
||||
return showDialog<void>(
|
||||
context: context,
|
||||
builder: (dialogContext) {
|
||||
final latestVersion = updateInfo['latestVersion'] as String;
|
||||
final releaseUrl = updateInfo['releaseUrl'] as String;
|
||||
|
||||
return AlertDialog(
|
||||
title: Text(title),
|
||||
content: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
t.update.versionAvailable(version: latestVersion),
|
||||
style: Theme.of(dialogContext).textTheme.titleMedium,
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
t.update.currentVersion(version: updateInfo['currentVersion']),
|
||||
style: Theme.of(dialogContext).textTheme.bodySmall,
|
||||
),
|
||||
],
|
||||
),
|
||||
actions: [
|
||||
DialogActionButton(onPressed: () => Navigator.pop(dialogContext), label: dismissLabel),
|
||||
if (showSkipVersion)
|
||||
DialogActionButton(
|
||||
onPressed: () async {
|
||||
await UpdateService.skipVersion(latestVersion);
|
||||
if (dialogContext.mounted) Navigator.pop(dialogContext);
|
||||
},
|
||||
label: t.update.skipVersion,
|
||||
),
|
||||
DialogActionButton(
|
||||
onPressed: () async {
|
||||
final url = Uri.parse(releaseUrl);
|
||||
if (await canLaunchUrl(url)) {
|
||||
await launchUrl(url, mode: LaunchMode.externalApplication);
|
||||
}
|
||||
if (dialogContext.mounted) Navigator.pop(dialogContext);
|
||||
},
|
||||
label: t.update.viewRelease,
|
||||
isPrimary: true,
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user