feat(#1129): sponsor window on the first launch after an install or upgrade

SponsorPromptGate wraps the first screen and, once it has drawn, shows a
Support Offband dialog: Sponsor (opens offband.org/donate), Not now, and
Don't show again. It shows once per installed version and build, saved
before the dialog opens so a crash can't loop it. Hidden on iOS, as the
About screen's Donate link is (#1051). A link that fails to open shows the
About screen's persistent "Could not open the link".

Both keys are app-wide and classified in scoping_inventory.dart. English
strings only; untranslated.json regenerated.

Tests: 10 widget tests plus the wiring through the gate. Each of five guards
(iOS, Don't show again, same version, the gate's call, the failure message)
was removed in turn and a test failed. Full suite 2074 passed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
fix/1128-channel-key-history
diffwireauto 4 days ago committed by Strycher
parent 96ed46afd1
commit 65b91cffdf

@ -503,6 +503,11 @@
"settings_aboutWebsite": "Offband website",
"settings_aboutPlayStore": "Google Play",
"settings_aboutDonate": "Donate or sponsor",
"sponsorPrompt_title": "Support Offband",
"sponsorPrompt_body": "Offband MeshCore is free, open source, and built by an independent developer. If it's useful to you, sponsoring helps keep it maintained and improving. Thank you for using it.",
"sponsorPrompt_sponsor": "Sponsor",
"sponsorPrompt_notNow": "Not now",
"sponsorPrompt_dontShowAgain": "Don't show again",
"settings_aboutLinkFailed": "Could not open the link",
"settings_aboutOpenMeteoAttribution": "LOS elevation data: Open-Meteo (CC BY 4.0)",
"settings_aboutPresetsAttribution": "Regional radio presets: MeshCore's suggested settings, maintained by Liam Cottle (api.meshcore.nz), with Offband additions.",

@ -1738,6 +1738,36 @@ abstract class AppLocalizations {
/// **'Donate or sponsor'**
String get settings_aboutDonate;
/// No description provided for @sponsorPrompt_title.
///
/// In en, this message translates to:
/// **'Support Offband'**
String get sponsorPrompt_title;
/// No description provided for @sponsorPrompt_body.
///
/// In en, this message translates to:
/// **'Offband MeshCore is free, open source, and built by an independent developer. If it\'s useful to you, sponsoring helps keep it maintained and improving. Thank you for using it.'**
String get sponsorPrompt_body;
/// No description provided for @sponsorPrompt_sponsor.
///
/// In en, this message translates to:
/// **'Sponsor'**
String get sponsorPrompt_sponsor;
/// No description provided for @sponsorPrompt_notNow.
///
/// In en, this message translates to:
/// **'Not now'**
String get sponsorPrompt_notNow;
/// No description provided for @sponsorPrompt_dontShowAgain.
///
/// In en, this message translates to:
/// **'Don\'t show again'**
String get sponsorPrompt_dontShowAgain;
/// No description provided for @settings_aboutLinkFailed.
///
/// In en, this message translates to:

@ -965,6 +965,22 @@ class AppLocalizationsBg extends AppLocalizations {
@override
String get settings_aboutDonate => 'Donate or sponsor';
@override
String get sponsorPrompt_title => 'Support Offband';
@override
String get sponsorPrompt_body =>
'Offband MeshCore is free, open source, and built by an independent developer. If it\'s useful to you, sponsoring helps keep it maintained and improving. Thank you for using it.';
@override
String get sponsorPrompt_sponsor => 'Sponsor';
@override
String get sponsorPrompt_notNow => 'Not now';
@override
String get sponsorPrompt_dontShowAgain => 'Don\'t show again';
@override
String get settings_aboutLinkFailed => 'Could not open the link';

@ -960,6 +960,22 @@ class AppLocalizationsDe extends AppLocalizations {
@override
String get settings_aboutDonate => 'Donate or sponsor';
@override
String get sponsorPrompt_title => 'Support Offband';
@override
String get sponsorPrompt_body =>
'Offband MeshCore is free, open source, and built by an independent developer. If it\'s useful to you, sponsoring helps keep it maintained and improving. Thank you for using it.';
@override
String get sponsorPrompt_sponsor => 'Sponsor';
@override
String get sponsorPrompt_notNow => 'Not now';
@override
String get sponsorPrompt_dontShowAgain => 'Don\'t show again';
@override
String get settings_aboutLinkFailed => 'Could not open the link';

@ -951,6 +951,22 @@ class AppLocalizationsEn extends AppLocalizations {
@override
String get settings_aboutDonate => 'Donate or sponsor';
@override
String get sponsorPrompt_title => 'Support Offband';
@override
String get sponsorPrompt_body =>
'Offband MeshCore is free, open source, and built by an independent developer. If it\'s useful to you, sponsoring helps keep it maintained and improving. Thank you for using it.';
@override
String get sponsorPrompt_sponsor => 'Sponsor';
@override
String get sponsorPrompt_notNow => 'Not now';
@override
String get sponsorPrompt_dontShowAgain => 'Don\'t show again';
@override
String get settings_aboutLinkFailed => 'Could not open the link';

@ -961,6 +961,22 @@ class AppLocalizationsEs extends AppLocalizations {
@override
String get settings_aboutDonate => 'Donate or sponsor';
@override
String get sponsorPrompt_title => 'Support Offband';
@override
String get sponsorPrompt_body =>
'Offband MeshCore is free, open source, and built by an independent developer. If it\'s useful to you, sponsoring helps keep it maintained and improving. Thank you for using it.';
@override
String get sponsorPrompt_sponsor => 'Sponsor';
@override
String get sponsorPrompt_notNow => 'Not now';
@override
String get sponsorPrompt_dontShowAgain => 'Don\'t show again';
@override
String get settings_aboutLinkFailed => 'Could not open the link';

@ -965,6 +965,22 @@ class AppLocalizationsFr extends AppLocalizations {
@override
String get settings_aboutDonate => 'Donate or sponsor';
@override
String get sponsorPrompt_title => 'Support Offband';
@override
String get sponsorPrompt_body =>
'Offband MeshCore is free, open source, and built by an independent developer. If it\'s useful to you, sponsoring helps keep it maintained and improving. Thank you for using it.';
@override
String get sponsorPrompt_sponsor => 'Sponsor';
@override
String get sponsorPrompt_notNow => 'Not now';
@override
String get sponsorPrompt_dontShowAgain => 'Don\'t show again';
@override
String get settings_aboutLinkFailed => 'Could not open the link';

@ -963,6 +963,22 @@ class AppLocalizationsHu extends AppLocalizations {
@override
String get settings_aboutDonate => 'Donate or sponsor';
@override
String get sponsorPrompt_title => 'Support Offband';
@override
String get sponsorPrompt_body =>
'Offband MeshCore is free, open source, and built by an independent developer. If it\'s useful to you, sponsoring helps keep it maintained and improving. Thank you for using it.';
@override
String get sponsorPrompt_sponsor => 'Sponsor';
@override
String get sponsorPrompt_notNow => 'Not now';
@override
String get sponsorPrompt_dontShowAgain => 'Don\'t show again';
@override
String get settings_aboutLinkFailed => 'Could not open the link';

@ -963,6 +963,22 @@ class AppLocalizationsIt extends AppLocalizations {
@override
String get settings_aboutDonate => 'Donate or sponsor';
@override
String get sponsorPrompt_title => 'Support Offband';
@override
String get sponsorPrompt_body =>
'Offband MeshCore is free, open source, and built by an independent developer. If it\'s useful to you, sponsoring helps keep it maintained and improving. Thank you for using it.';
@override
String get sponsorPrompt_sponsor => 'Sponsor';
@override
String get sponsorPrompt_notNow => 'Not now';
@override
String get sponsorPrompt_dontShowAgain => 'Don\'t show again';
@override
String get settings_aboutLinkFailed => 'Could not open the link';

@ -936,6 +936,22 @@ class AppLocalizationsJa extends AppLocalizations {
@override
String get settings_aboutDonate => 'Donate or sponsor';
@override
String get sponsorPrompt_title => 'Support Offband';
@override
String get sponsorPrompt_body =>
'Offband MeshCore is free, open source, and built by an independent developer. If it\'s useful to you, sponsoring helps keep it maintained and improving. Thank you for using it.';
@override
String get sponsorPrompt_sponsor => 'Sponsor';
@override
String get sponsorPrompt_notNow => 'Not now';
@override
String get sponsorPrompt_dontShowAgain => 'Don\'t show again';
@override
String get settings_aboutLinkFailed => 'Could not open the link';

@ -936,6 +936,22 @@ class AppLocalizationsKo extends AppLocalizations {
@override
String get settings_aboutDonate => 'Donate or sponsor';
@override
String get sponsorPrompt_title => 'Support Offband';
@override
String get sponsorPrompt_body =>
'Offband MeshCore is free, open source, and built by an independent developer. If it\'s useful to you, sponsoring helps keep it maintained and improving. Thank you for using it.';
@override
String get sponsorPrompt_sponsor => 'Sponsor';
@override
String get sponsorPrompt_notNow => 'Not now';
@override
String get sponsorPrompt_dontShowAgain => 'Don\'t show again';
@override
String get settings_aboutLinkFailed => 'Could not open the link';

@ -957,6 +957,22 @@ class AppLocalizationsNl extends AppLocalizations {
@override
String get settings_aboutDonate => 'Donate or sponsor';
@override
String get sponsorPrompt_title => 'Support Offband';
@override
String get sponsorPrompt_body =>
'Offband MeshCore is free, open source, and built by an independent developer. If it\'s useful to you, sponsoring helps keep it maintained and improving. Thank you for using it.';
@override
String get sponsorPrompt_sponsor => 'Sponsor';
@override
String get sponsorPrompt_notNow => 'Not now';
@override
String get sponsorPrompt_dontShowAgain => 'Don\'t show again';
@override
String get settings_aboutLinkFailed => 'Could not open the link';

@ -965,6 +965,22 @@ class AppLocalizationsPl extends AppLocalizations {
@override
String get settings_aboutDonate => 'Donate or sponsor';
@override
String get sponsorPrompt_title => 'Support Offband';
@override
String get sponsorPrompt_body =>
'Offband MeshCore is free, open source, and built by an independent developer. If it\'s useful to you, sponsoring helps keep it maintained and improving. Thank you for using it.';
@override
String get sponsorPrompt_sponsor => 'Sponsor';
@override
String get sponsorPrompt_notNow => 'Not now';
@override
String get sponsorPrompt_dontShowAgain => 'Don\'t show again';
@override
String get settings_aboutLinkFailed => 'Could not open the link';

@ -963,6 +963,22 @@ class AppLocalizationsPt extends AppLocalizations {
@override
String get settings_aboutDonate => 'Donate or sponsor';
@override
String get sponsorPrompt_title => 'Support Offband';
@override
String get sponsorPrompt_body =>
'Offband MeshCore is free, open source, and built by an independent developer. If it\'s useful to you, sponsoring helps keep it maintained and improving. Thank you for using it.';
@override
String get sponsorPrompt_sponsor => 'Sponsor';
@override
String get sponsorPrompt_notNow => 'Not now';
@override
String get sponsorPrompt_dontShowAgain => 'Don\'t show again';
@override
String get settings_aboutLinkFailed => 'Could not open the link';

@ -962,6 +962,22 @@ class AppLocalizationsRu extends AppLocalizations {
@override
String get settings_aboutDonate => 'Donate or sponsor';
@override
String get sponsorPrompt_title => 'Support Offband';
@override
String get sponsorPrompt_body =>
'Offband MeshCore is free, open source, and built by an independent developer. If it\'s useful to you, sponsoring helps keep it maintained and improving. Thank you for using it.';
@override
String get sponsorPrompt_sponsor => 'Sponsor';
@override
String get sponsorPrompt_notNow => 'Not now';
@override
String get sponsorPrompt_dontShowAgain => 'Don\'t show again';
@override
String get settings_aboutLinkFailed => 'Could not open the link';

@ -957,6 +957,22 @@ class AppLocalizationsSk extends AppLocalizations {
@override
String get settings_aboutDonate => 'Donate or sponsor';
@override
String get sponsorPrompt_title => 'Support Offband';
@override
String get sponsorPrompt_body =>
'Offband MeshCore is free, open source, and built by an independent developer. If it\'s useful to you, sponsoring helps keep it maintained and improving. Thank you for using it.';
@override
String get sponsorPrompt_sponsor => 'Sponsor';
@override
String get sponsorPrompt_notNow => 'Not now';
@override
String get sponsorPrompt_dontShowAgain => 'Don\'t show again';
@override
String get settings_aboutLinkFailed => 'Could not open the link';

@ -954,6 +954,22 @@ class AppLocalizationsSl extends AppLocalizations {
@override
String get settings_aboutDonate => 'Donate or sponsor';
@override
String get sponsorPrompt_title => 'Support Offband';
@override
String get sponsorPrompt_body =>
'Offband MeshCore is free, open source, and built by an independent developer. If it\'s useful to you, sponsoring helps keep it maintained and improving. Thank you for using it.';
@override
String get sponsorPrompt_sponsor => 'Sponsor';
@override
String get sponsorPrompt_notNow => 'Not now';
@override
String get sponsorPrompt_dontShowAgain => 'Don\'t show again';
@override
String get settings_aboutLinkFailed => 'Could not open the link';

@ -952,6 +952,22 @@ class AppLocalizationsSv extends AppLocalizations {
@override
String get settings_aboutDonate => 'Donate or sponsor';
@override
String get sponsorPrompt_title => 'Support Offband';
@override
String get sponsorPrompt_body =>
'Offband MeshCore is free, open source, and built by an independent developer. If it\'s useful to you, sponsoring helps keep it maintained and improving. Thank you for using it.';
@override
String get sponsorPrompt_sponsor => 'Sponsor';
@override
String get sponsorPrompt_notNow => 'Not now';
@override
String get sponsorPrompt_dontShowAgain => 'Don\'t show again';
@override
String get settings_aboutLinkFailed => 'Could not open the link';

@ -960,6 +960,22 @@ class AppLocalizationsUk extends AppLocalizations {
@override
String get settings_aboutDonate => 'Donate or sponsor';
@override
String get sponsorPrompt_title => 'Support Offband';
@override
String get sponsorPrompt_body =>
'Offband MeshCore is free, open source, and built by an independent developer. If it\'s useful to you, sponsoring helps keep it maintained and improving. Thank you for using it.';
@override
String get sponsorPrompt_sponsor => 'Sponsor';
@override
String get sponsorPrompt_notNow => 'Not now';
@override
String get sponsorPrompt_dontShowAgain => 'Don\'t show again';
@override
String get settings_aboutLinkFailed => 'Could not open the link';

@ -928,6 +928,22 @@ class AppLocalizationsZh extends AppLocalizations {
@override
String get settings_aboutDonate => 'Donate or sponsor';
@override
String get sponsorPrompt_title => 'Support Offband';
@override
String get sponsorPrompt_body =>
'Offband MeshCore is free, open source, and built by an independent developer. If it\'s useful to you, sponsoring helps keep it maintained and improving. Thank you for using it.';
@override
String get sponsorPrompt_sponsor => 'Sponsor';
@override
String get sponsorPrompt_notNow => 'Not now';
@override
String get sponsorPrompt_dontShowAgain => 'Don\'t show again';
@override
String get settings_aboutLinkFailed => 'Could not open the link';

@ -43,6 +43,7 @@ import 'widgets/storage_unavailable_banner.dart';
import 'storage/prefs_manager.dart';
import 'utils/app_logger.dart';
import 'widgets/keep_screen_awake.dart';
import 'widgets/sponsor_prompt.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
@ -393,7 +394,7 @@ class MeshCoreApp extends StatelessWidget {
},
home: (PlatformInfo.isWeb && !PlatformInfo.isChrome)
? const ChromeRequiredScreen()
: const ScannerScreen(),
: const SponsorPromptGate(child: ScannerScreen()),
);
},
),

@ -275,6 +275,22 @@ const List<StorageSurface> storageSurfaces = [
'previously audited: surfaced by this inventory',
owner: 'lib/services/radio_preset_service.dart',
),
StorageSurface(
'sponsor_prompt_shown_for',
verdict: ScopingVerdict.globalOnPurpose,
reason:
'the app version the sponsor window last showed for (#1129). It '
'follows the installed app, not a radio',
owner: 'lib/widgets/sponsor_prompt.dart',
),
StorageSurface(
'sponsor_prompt_off',
verdict: ScopingVerdict.globalOnPurpose,
reason:
"the user's \"Don't show again\" for the sponsor window (#1129). A "
'choice about the app, not about a radio',
owner: 'lib/widgets/sponsor_prompt.dart',
),
StorageSurface(
'anr_last_exit_ts_ms',
verdict: ScopingVerdict.globalOnPurpose,

@ -0,0 +1,129 @@
import 'package:flutter/material.dart';
import 'package:package_info_plus/package_info_plus.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:url_launcher/url_launcher.dart';
import '../helpers/snack_bar_builder.dart';
import '../l10n/l10n.dart';
import '../storage/prefs_manager.dart';
import '../utils/app_logger.dart';
import '../utils/platform_info.dart';
enum _Choice { off, later, sponsor }
/// The sponsor window shown once on the first launch after an install or an
/// upgrade (#1129). App-wide, never per radio.
class SponsorPrompt {
static const String shownForKey = 'sponsor_prompt_shown_for';
static const String offKey = 'sponsor_prompt_off';
static final Uri sponsorUri = Uri.parse('https://offband.org/donate');
/// Shows the window unless it has already shown for [version], the user
/// turned it off, or this is iOS (hidden there like About's Donate, #1051).
/// The version is saved before the window opens, so a crash can't loop it.
static Future<void> maybeShow(
BuildContext context, {
String? version,
bool? isIOS,
SharedPreferences? prefs,
Future<bool> Function(Uri)? open,
}) async {
if (isIOS ?? PlatformInfo.isIOS) return;
final store = prefs ?? PrefsManager.instance;
if (store.getBool(offKey) ?? false) return;
final current = version ?? await _installedVersion();
if (current == null || store.getString(shownForKey) == current) return;
await store.setString(shownForKey, current);
if (!context.mounted) return;
final choice = await showDialog<_Choice>(
context: context,
builder: (_) => const _SponsorDialog(),
);
if (choice == _Choice.off) {
await store.setBool(offKey, true);
} else if (choice == _Choice.sponsor) {
if (await _open(open ?? _openExternally)) return;
if (context.mounted) {
showDismissibleSnackBar(
context,
content: Text(context.l10n.settings_aboutLinkFailed),
persist: true,
);
}
}
}
static Future<bool> _open(Future<bool> Function(Uri) open) async {
try {
return await open(sponsorUri);
} catch (e) {
appLogger.warn('Sponsor link did not open: $e', tag: 'Sponsor');
return false;
}
}
static Future<bool> _openExternally(Uri uri) =>
launchUrl(uri, mode: LaunchMode.externalApplication);
static Future<String?> _installedVersion() async {
try {
final info = await PackageInfo.fromPlatform();
return '${info.version}+${info.buildNumber}';
} catch (e) {
appLogger.error('Sponsor prompt: no app version: $e', tag: 'Sponsor');
return null;
}
}
}
/// Wraps the app's first screen and offers the sponsor window once it has
/// drawn.
class SponsorPromptGate extends StatefulWidget {
const SponsorPromptGate({super.key, required this.child});
final Widget child;
@override
State<SponsorPromptGate> createState() => _SponsorPromptGateState();
}
class _SponsorPromptGateState extends State<SponsorPromptGate> {
@override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) {
if (mounted) SponsorPrompt.maybeShow(context);
});
}
@override
Widget build(BuildContext context) => widget.child;
}
class _SponsorDialog extends StatelessWidget {
const _SponsorDialog();
@override
Widget build(BuildContext context) {
final l10n = context.l10n;
void choose(_Choice choice) => Navigator.of(context).pop(choice);
return AlertDialog(
title: Text(l10n.sponsorPrompt_title),
content: Text(l10n.sponsorPrompt_body),
actions: [
TextButton(
onPressed: () => choose(_Choice.off),
child: Text(l10n.sponsorPrompt_dontShowAgain),
),
TextButton(
onPressed: () => choose(_Choice.later),
child: Text(l10n.sponsorPrompt_notNow),
),
FilledButton(
onPressed: () => choose(_Choice.sponsor),
child: Text(l10n.sponsorPrompt_sponsor),
),
],
);
}
}

@ -0,0 +1,185 @@
// Sponsor prompt on the first launch after an install or upgrade (#1129).
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:meshcore_open/l10n/app_localizations.dart';
import 'package:meshcore_open/storage/prefs_manager.dart';
import 'package:meshcore_open/widgets/sponsor_prompt.dart';
import 'package:package_info_plus/package_info_plus.dart';
import 'package:shared_preferences/shared_preferences.dart';
Future<SharedPreferences> _prefs([Map<String, Object> values = const {}]) {
SharedPreferences.setMockInitialValues(values);
return SharedPreferences.getInstance();
}
/// Launches the app's first screen once: a button stands in for the scanner's
/// post-frame call.
Future<void> _launch(
WidgetTester tester,
SharedPreferences prefs, {
String version = '1.8.0+69',
bool isIOS = false,
Future<bool> Function(Uri)? open,
}) async {
await tester.pumpWidget(
MaterialApp(
localizationsDelegates: AppLocalizations.localizationsDelegates,
supportedLocales: AppLocalizations.supportedLocales,
home: Builder(
builder: (context) => Scaffold(
body: TextButton(
onPressed: () => SponsorPrompt.maybeShow(
context,
version: version,
isIOS: isIOS,
prefs: prefs,
open: open,
),
child: const Text('launch'),
),
),
),
),
);
await tester.tap(find.text('launch'));
await tester.pumpAndSettle();
}
final _title = find.text('Support Offband');
void main() {
testWidgets('a fresh install shows it, with all three choices', (
tester,
) async {
final prefs = await _prefs();
await _launch(tester, prefs);
expect(_title, findsOneWidget);
expect(find.text('Sponsor'), findsOneWidget);
expect(find.text('Not now'), findsOneWidget);
expect(find.text("Don't show again"), findsOneWidget);
// Saved while the window is still open, so a crash can't loop it.
expect(prefs.getString(SponsorPrompt.shownForKey), '1.8.0+69');
});
testWidgets('the same version does not show it again', (tester) async {
final prefs = await _prefs({SponsorPrompt.shownForKey: '1.8.0+69'});
await _launch(tester, prefs);
expect(_title, findsNothing);
});
testWidgets('an upgrade shows it once more', (tester) async {
final prefs = await _prefs({SponsorPrompt.shownForKey: '1.7.0+68'});
await _launch(tester, prefs);
expect(_title, findsOneWidget);
expect(prefs.getString(SponsorPrompt.shownForKey), '1.8.0+69');
});
testWidgets('Not now closes it and the next upgrade asks again', (
tester,
) async {
final prefs = await _prefs();
await _launch(tester, prefs);
await tester.tap(find.text('Not now'));
await tester.pumpAndSettle();
expect(_title, findsNothing);
expect(prefs.getBool(SponsorPrompt.offKey), isNull);
await _launch(tester, prefs, version: '1.8.1+70');
expect(_title, findsOneWidget);
});
testWidgets("Don't show again sticks across upgrades", (tester) async {
final prefs = await _prefs();
await _launch(tester, prefs);
await tester.tap(find.text("Don't show again"));
await tester.pumpAndSettle();
expect(_title, findsNothing);
expect(prefs.getBool(SponsorPrompt.offKey), isTrue);
await _launch(tester, prefs, version: '1.9.0+71');
expect(_title, findsNothing);
});
testWidgets('iOS never shows it and records nothing', (tester) async {
final prefs = await _prefs();
await _launch(tester, prefs, isIOS: true);
expect(_title, findsNothing);
expect(prefs.getString(SponsorPrompt.shownForKey), isNull);
});
testWidgets('the first screen offers it on its own after it draws', (
tester,
) async {
SharedPreferences.setMockInitialValues({});
PrefsManager.reset();
await PrefsManager.initialize();
addTearDown(PrefsManager.reset);
PackageInfo.setMockInitialValues(
appName: 'Offband MeshCore',
packageName: 'app.offband.meshcore',
version: '1.8.0',
buildNumber: '69',
buildSignature: '',
);
await tester.pumpWidget(
MaterialApp(
localizationsDelegates: AppLocalizations.localizationsDelegates,
supportedLocales: AppLocalizations.supportedLocales,
home: const SponsorPromptGate(child: Scaffold(body: Text('scanner'))),
),
);
await tester.pumpAndSettle();
expect(_title, findsOneWidget);
expect(
PrefsManager.instance.getString(SponsorPrompt.shownForKey),
'1.8.0+69',
);
});
testWidgets('Sponsor opens the donate page and says nothing more', (
tester,
) async {
final prefs = await _prefs();
final opened = <Uri>[];
await _launch(
tester,
prefs,
open: (uri) async {
opened.add(uri);
return true;
},
);
await tester.tap(find.text('Sponsor'));
await tester.pumpAndSettle();
expect(_title, findsNothing);
expect(opened, [SponsorPrompt.sponsorUri]);
expect(find.text('Could not open the link'), findsNothing);
expect(prefs.getBool(SponsorPrompt.offKey), isNull);
});
for (final failure in ['returns false', 'throws']) {
testWidgets('a Sponsor link that $failure says so and the message stays', (
tester,
) async {
final prefs = await _prefs();
await _launch(
tester,
prefs,
open: (uri) async {
if (failure == 'throws') throw Exception('no browser');
return false;
},
);
await tester.tap(find.text('Sponsor'));
await tester.pumpAndSettle();
expect(find.text('Could not open the link'), findsOneWidget);
await tester.pump(const Duration(seconds: 10));
expect(find.text('Could not open the link'), findsOneWidget);
});
}
test('Sponsor opens the donate page the About screen uses', () {
expect(SponsorPrompt.sponsorUri.toString(), 'https://offband.org/donate');
});
}

@ -112,6 +112,11 @@
"settings_aboutWebsite",
"settings_aboutPlayStore",
"settings_aboutDonate",
"sponsorPrompt_title",
"sponsorPrompt_body",
"sponsorPrompt_sponsor",
"sponsorPrompt_notNow",
"sponsorPrompt_dontShowAgain",
"settings_aboutLinkFailed",
"settings_aboutPresetsAttribution",
"settings_infoFirmware",
@ -501,6 +506,11 @@
"settings_aboutWebsite",
"settings_aboutPlayStore",
"settings_aboutDonate",
"sponsorPrompt_title",
"sponsorPrompt_body",
"sponsorPrompt_sponsor",
"sponsorPrompt_notNow",
"sponsorPrompt_dontShowAgain",
"settings_aboutLinkFailed",
"settings_aboutPresetsAttribution",
"settings_infoFirmware",
@ -890,6 +900,11 @@
"settings_aboutWebsite",
"settings_aboutPlayStore",
"settings_aboutDonate",
"sponsorPrompt_title",
"sponsorPrompt_body",
"sponsorPrompt_sponsor",
"sponsorPrompt_notNow",
"sponsorPrompt_dontShowAgain",
"settings_aboutLinkFailed",
"settings_aboutPresetsAttribution",
"settings_infoFirmware",
@ -1279,6 +1294,11 @@
"settings_aboutWebsite",
"settings_aboutPlayStore",
"settings_aboutDonate",
"sponsorPrompt_title",
"sponsorPrompt_body",
"sponsorPrompt_sponsor",
"sponsorPrompt_notNow",
"sponsorPrompt_dontShowAgain",
"settings_aboutLinkFailed",
"settings_aboutPresetsAttribution",
"settings_infoFirmware",
@ -1668,6 +1688,11 @@
"settings_aboutWebsite",
"settings_aboutPlayStore",
"settings_aboutDonate",
"sponsorPrompt_title",
"sponsorPrompt_body",
"sponsorPrompt_sponsor",
"sponsorPrompt_notNow",
"sponsorPrompt_dontShowAgain",
"settings_aboutLinkFailed",
"settings_aboutPresetsAttribution",
"settings_infoFirmware",
@ -2057,6 +2082,11 @@
"settings_aboutWebsite",
"settings_aboutPlayStore",
"settings_aboutDonate",
"sponsorPrompt_title",
"sponsorPrompt_body",
"sponsorPrompt_sponsor",
"sponsorPrompt_notNow",
"sponsorPrompt_dontShowAgain",
"settings_aboutLinkFailed",
"settings_aboutPresetsAttribution",
"settings_infoFirmware",
@ -2446,6 +2476,11 @@
"settings_aboutWebsite",
"settings_aboutPlayStore",
"settings_aboutDonate",
"sponsorPrompt_title",
"sponsorPrompt_body",
"sponsorPrompt_sponsor",
"sponsorPrompt_notNow",
"sponsorPrompt_dontShowAgain",
"settings_aboutLinkFailed",
"settings_aboutPresetsAttribution",
"settings_infoFirmware",
@ -2835,6 +2870,11 @@
"settings_aboutWebsite",
"settings_aboutPlayStore",
"settings_aboutDonate",
"sponsorPrompt_title",
"sponsorPrompt_body",
"sponsorPrompt_sponsor",
"sponsorPrompt_notNow",
"sponsorPrompt_dontShowAgain",
"settings_aboutLinkFailed",
"settings_aboutPresetsAttribution",
"settings_infoFirmware",
@ -3224,6 +3264,11 @@
"settings_aboutWebsite",
"settings_aboutPlayStore",
"settings_aboutDonate",
"sponsorPrompt_title",
"sponsorPrompt_body",
"sponsorPrompt_sponsor",
"sponsorPrompt_notNow",
"sponsorPrompt_dontShowAgain",
"settings_aboutLinkFailed",
"settings_aboutPresetsAttribution",
"settings_infoFirmware",
@ -3613,6 +3658,11 @@
"settings_aboutWebsite",
"settings_aboutPlayStore",
"settings_aboutDonate",
"sponsorPrompt_title",
"sponsorPrompt_body",
"sponsorPrompt_sponsor",
"sponsorPrompt_notNow",
"sponsorPrompt_dontShowAgain",
"settings_aboutLinkFailed",
"settings_aboutPresetsAttribution",
"settings_infoFirmware",
@ -4002,6 +4052,11 @@
"settings_aboutWebsite",
"settings_aboutPlayStore",
"settings_aboutDonate",
"sponsorPrompt_title",
"sponsorPrompt_body",
"sponsorPrompt_sponsor",
"sponsorPrompt_notNow",
"sponsorPrompt_dontShowAgain",
"settings_aboutLinkFailed",
"settings_aboutPresetsAttribution",
"settings_infoFirmware",
@ -4391,6 +4446,11 @@
"settings_aboutWebsite",
"settings_aboutPlayStore",
"settings_aboutDonate",
"sponsorPrompt_title",
"sponsorPrompt_body",
"sponsorPrompt_sponsor",
"sponsorPrompt_notNow",
"sponsorPrompt_dontShowAgain",
"settings_aboutLinkFailed",
"settings_aboutPresetsAttribution",
"settings_infoFirmware",
@ -4780,6 +4840,11 @@
"settings_aboutWebsite",
"settings_aboutPlayStore",
"settings_aboutDonate",
"sponsorPrompt_title",
"sponsorPrompt_body",
"sponsorPrompt_sponsor",
"sponsorPrompt_notNow",
"sponsorPrompt_dontShowAgain",
"settings_aboutLinkFailed",
"settings_aboutPresetsAttribution",
"settings_infoFirmware",
@ -5169,6 +5234,11 @@
"settings_aboutWebsite",
"settings_aboutPlayStore",
"settings_aboutDonate",
"sponsorPrompt_title",
"sponsorPrompt_body",
"sponsorPrompt_sponsor",
"sponsorPrompt_notNow",
"sponsorPrompt_dontShowAgain",
"settings_aboutLinkFailed",
"settings_aboutPresetsAttribution",
"settings_infoFirmware",
@ -5558,6 +5628,11 @@
"settings_aboutWebsite",
"settings_aboutPlayStore",
"settings_aboutDonate",
"sponsorPrompt_title",
"sponsorPrompt_body",
"sponsorPrompt_sponsor",
"sponsorPrompt_notNow",
"sponsorPrompt_dontShowAgain",
"settings_aboutLinkFailed",
"settings_aboutPresetsAttribution",
"settings_infoFirmware",
@ -5947,6 +6022,11 @@
"settings_aboutWebsite",
"settings_aboutPlayStore",
"settings_aboutDonate",
"sponsorPrompt_title",
"sponsorPrompt_body",
"sponsorPrompt_sponsor",
"sponsorPrompt_notNow",
"sponsorPrompt_dontShowAgain",
"settings_aboutLinkFailed",
"settings_aboutPresetsAttribution",
"settings_infoFirmware",
@ -6336,6 +6416,11 @@
"settings_aboutWebsite",
"settings_aboutPlayStore",
"settings_aboutDonate",
"sponsorPrompt_title",
"sponsorPrompt_body",
"sponsorPrompt_sponsor",
"sponsorPrompt_notNow",
"sponsorPrompt_dontShowAgain",
"settings_aboutLinkFailed",
"settings_aboutPresetsAttribution",
"settings_infoFirmware",

Loading…
Cancel
Save

Powered by TurnKey Linux.