-
Notifications
You must be signed in to change notification settings - Fork 16
Add sponsors and a one-time support prompt #122
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -16,6 +16,7 @@ import 'package:icarus/widgets/map_theme_settings_section.dart'; | |
| import 'package:icarus/widgets/settings_scope_card.dart'; | ||
| import 'package:icarus/widgets/text_editing_shortcut_scope.dart'; | ||
| import 'package:shadcn_ui/shadcn_ui.dart'; | ||
| import 'package:url_launcher/url_launcher.dart' show launchUrl; | ||
|
|
||
| enum _SettingsMode { | ||
| strategy, | ||
|
|
@@ -31,6 +32,7 @@ enum _SettingsSection { | |
| globalMapVisibility, | ||
| globalMapProfiles, | ||
| globalPrivacy, | ||
| globalSupport, | ||
| shortcuts, | ||
| } | ||
|
|
||
|
|
@@ -72,7 +74,10 @@ class _SettingsTabState extends ConsumerState<SettingsTab> { | |
| }; | ||
| final scopeValue = switch (_mode) { | ||
| _SettingsMode.strategy => activeStrategyName, | ||
| _SettingsMode.global => 'Defaults', | ||
| _SettingsMode.global => | ||
| _selectedSection == _SettingsSection.globalSupport | ||
| ? 'About & sponsors' | ||
| : 'Defaults', | ||
| _SettingsMode.shortcuts => 'Keybinds', | ||
| }; | ||
|
|
||
|
|
@@ -193,6 +198,7 @@ class _SettingsTabState extends ConsumerState<SettingsTab> { | |
| case _SettingsSection.globalMapVisibility: | ||
| case _SettingsSection.globalMapProfiles: | ||
| case _SettingsSection.globalPrivacy: | ||
| case _SettingsSection.globalSupport: | ||
| return _SettingsMode.global; | ||
| case _SettingsSection.shortcuts: | ||
| return _SettingsMode.shortcuts; | ||
|
|
@@ -562,6 +568,51 @@ class _GlobalSettingsSections extends ConsumerWidget { | |
| scope: MapThemeSettingsScope.global, | ||
| ), | ||
| ), | ||
| const SizedBox(height: 20), | ||
| const _SectionDivider(), | ||
| const SizedBox(height: 20), | ||
| SettingsScopeCard( | ||
| key: sectionKeys[_SettingsSection.globalSupport], | ||
| title: "Sponsors & support", | ||
| description: | ||
| "Icarus stays free through open-source programs and support from its users.", | ||
| child: Column( | ||
| children: [ | ||
| _SupportLinkTile( | ||
| icon: Icons.bug_report_outlined, | ||
| title: "Greptile", | ||
| description: | ||
| "AI code review through Greptile's Open Source Program.", | ||
| actionLabel: "View program", | ||
| onPressed: () { | ||
| launchUrl(Settings.greptileOpenSourceLink); | ||
| }, | ||
| ), | ||
| const _SettingsItemDivider(), | ||
| _SupportLinkTile( | ||
| icon: Icons.code_outlined, | ||
| title: "OpenAI", | ||
| description: | ||
| "Tooling and credits through Codex for Open Source.", | ||
| actionLabel: "View program", | ||
| onPressed: () { | ||
| launchUrl(Settings.openAICodexForOssLink); | ||
| }, | ||
| ), | ||
| const _SettingsItemDivider(), | ||
| _SupportLinkTile( | ||
| icon: Icons.local_cafe_outlined, | ||
| title: "Support Icarus", | ||
| description: | ||
| "If Icarus helps your team, you can help keep development going.", | ||
| actionLabel: "Buy me a coffee", | ||
| onPressed: () { | ||
| launchUrl(Settings.buyMeACoffeeLink); | ||
|
Comment on lines
+581
to
+610
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win 🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -euo pipefail
# Expect every launchUrl call to use result-aware handling.
if rg -nP '^\s*launchUrl\s*\(' lib/widgets/settings_tab.dart; then
echo "Found a fire-and-forget launchUrl call"
exit 1
fi
rg -n -C 5 'launchUrl\(' lib/widgets/settings_tab.dartRepository: SunkenInTime/icarus Length of output: 387 🏁 Script executed: #!/bin/bash
set -euo pipefail
echo "Relevant settings_tab.dart section:"
sed -n '560,625p' lib/widgets/settings_tab.dart
echo
echo "Import/dependency declarations:"
rg -n "url_launcher|launchUrl|Toast|showDialog|ErrorMessage|error" pubspec.yaml pubspec.lockRepository: SunkenInTime/icarus Length of output: 3225 🌐 Web query:
💡 Result: In the Citations:
Handle external-link failures before shipping.
🤖 Prompt for AI AgentsSource: Coding guidelines |
||
| }, | ||
| ), | ||
| ], | ||
| ), | ||
| ), | ||
| const SizedBox(height: 24), | ||
| ], | ||
| ); | ||
|
|
@@ -1218,6 +1269,12 @@ class _SettingsNavigationRail extends StatelessWidget { | |
| isSelected: selectedSection == _SettingsSection.shortcuts, | ||
| onTap: () => onSectionSelected(_SettingsSection.shortcuts), | ||
| ), | ||
| _SettingsNavItem( | ||
| icon: Icons.favorite_border_outlined, | ||
| label: "Sponsors", | ||
| isSelected: selectedSection == _SettingsSection.globalSupport, | ||
| onTap: () => onSectionSelected(_SettingsSection.globalSupport), | ||
| ), | ||
| ], | ||
| ), | ||
| ); | ||
|
|
@@ -1508,6 +1565,64 @@ class _SettingsToggleTile extends StatelessWidget { | |
| } | ||
| } | ||
|
|
||
| class _SupportLinkTile extends StatelessWidget { | ||
| const _SupportLinkTile({ | ||
| required this.icon, | ||
| required this.title, | ||
| required this.description, | ||
| required this.actionLabel, | ||
| required this.onPressed, | ||
| }); | ||
|
|
||
| final IconData icon; | ||
| final String title; | ||
| final String description; | ||
| final String actionLabel; | ||
| final VoidCallback onPressed; | ||
|
|
||
| @override | ||
| Widget build(BuildContext context) { | ||
| return Padding( | ||
| padding: const EdgeInsets.symmetric(vertical: 10), | ||
| child: Row( | ||
| crossAxisAlignment: CrossAxisAlignment.center, | ||
| children: [ | ||
| _SettingLeadingIcon( | ||
| icon: icon, | ||
| accentColor: Settings.tacticalVioletTheme.mutedForeground, | ||
| ), | ||
| const SizedBox(width: 10), | ||
| Expanded( | ||
| child: Column( | ||
| crossAxisAlignment: CrossAxisAlignment.start, | ||
| children: [ | ||
| Text( | ||
| title, | ||
| style: const TextStyle(fontWeight: FontWeight.w600), | ||
| ), | ||
| const SizedBox(height: 2), | ||
| Text( | ||
| description, | ||
| style: ShadTheme.of(context).textTheme.small.copyWith( | ||
| color: Settings.tacticalVioletTheme.mutedForeground, | ||
| height: 1.3, | ||
| ), | ||
| ), | ||
| ], | ||
| ), | ||
| ), | ||
| const SizedBox(width: 12), | ||
| ShadButton.secondary( | ||
| size: ShadButtonSize.sm, | ||
| onPressed: onPressed, | ||
| child: Text(actionLabel), | ||
| ), | ||
| ], | ||
| ), | ||
| ); | ||
| } | ||
| } | ||
|
|
||
| class _SettingLeadingIcon extends StatelessWidget { | ||
| const _SettingLeadingIcon({ | ||
| required this.icon, | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win
Move the persisted “shown” flag after the readiness check.
Line 110 writes
supportPromptShownKey = 1. Line 111 can then return beforeSettings.showToastruns. On a Windows launch with an uninitialized WebView, the prompt is not shown, but later launches return at Line 106 because the flag is already set.Move the readiness check before the write. Keep the write before the six-second delay.
Proposed fix
As per coding guidelines, “If a library write path is uncertain, fail loudly without saving rather than persisting potentially incorrect data.”
📝 Committable suggestion
🤖 Prompt for AI Agents
Source: Coding guidelines