From 2986f29a995dae71b5e2c088017ddccc08f7191c Mon Sep 17 00:00:00 2001 From: wanghongenpin Date: Sun, 5 Oct 2025 23:49:15 +0800 Subject: [PATCH] sponsors --- lib/l10n/app_en.arb | 7 +- lib/l10n/app_localizations.dart | 30 +++++++ lib/l10n/app_localizations_en.dart | 16 ++++ lib/l10n/app_localizations_zh.dart | 33 ++++++++ lib/l10n/app_zh.arb | 7 +- lib/l10n/app_zh_Hant.arb | 8 +- lib/ui/desktop/setting/about.dart | 123 +++++++++++++++++++++++------ lib/ui/mobile/setting/ssl.dart | 10 ++- lib/ui/mobile/widgets/about.dart | 76 +++++++++++++++--- 9 files changed, 269 insertions(+), 41 deletions(-) diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index f0f3138..7ca94da 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -341,5 +341,10 @@ "automatic": "Automatic", "manual": "Manual", "certNotInstalled": "Certificate not installed", - "openNewWindow": "Open New Window" + "openNewWindow": "Open New Window", + "sponsorDonate": "Sponsor / Donate", + "sponsorSupport": "Support ongoing development", + "sponsorThanks": "Thank you for supporting this open-source project by choosing any of the following methods to help its long-term development.", + "sponsorAfdian": "AFDIAN", + "sponsorBuyMeCoffee": "Buy Me a Coffee" } \ No newline at end of file diff --git a/lib/l10n/app_localizations.dart b/lib/l10n/app_localizations.dart index db265bc..0f2660e 100644 --- a/lib/l10n/app_localizations.dart +++ b/lib/l10n/app_localizations.dart @@ -2003,6 +2003,36 @@ abstract class AppLocalizations { /// In en, this message translates to: /// **'Open New Window'** String get openNewWindow; + + /// No description provided for @sponsorDonate. + /// + /// In en, this message translates to: + /// **'Sponsor / Donate'** + String get sponsorDonate; + + /// No description provided for @sponsorSupport. + /// + /// In en, this message translates to: + /// **'Support ongoing development'** + String get sponsorSupport; + + /// No description provided for @sponsorThanks. + /// + /// In en, this message translates to: + /// **'Thank you for supporting this open-source project by choosing any of the following methods to help its long-term development.'** + String get sponsorThanks; + + /// No description provided for @sponsorAfdian. + /// + /// In en, this message translates to: + /// **'AFDIAN'** + String get sponsorAfdian; + + /// No description provided for @sponsorBuyMeCoffee. + /// + /// In en, this message translates to: + /// **'Buy Me a Coffee'** + String get sponsorBuyMeCoffee; } class _AppLocalizationsDelegate extends LocalizationsDelegate { diff --git a/lib/l10n/app_localizations_en.dart b/lib/l10n/app_localizations_en.dart index 8b35c00..213c246 100644 --- a/lib/l10n/app_localizations_en.dart +++ b/lib/l10n/app_localizations_en.dart @@ -990,4 +990,20 @@ class AppLocalizationsEn extends AppLocalizations { @override String get openNewWindow => 'Open New Window'; + + @override + String get sponsorDonate => 'Sponsor / Donate'; + + @override + String get sponsorSupport => 'Support ongoing development'; + + @override + String get sponsorThanks => + 'Thank you for supporting this open-source project by choosing any of the following methods to help its long-term development.'; + + @override + String get sponsorAfdian => 'AFDIAN'; + + @override + String get sponsorBuyMeCoffee => 'Buy Me a Coffee'; } diff --git a/lib/l10n/app_localizations_zh.dart b/lib/l10n/app_localizations_zh.dart index 1796729..0408028 100644 --- a/lib/l10n/app_localizations_zh.dart +++ b/lib/l10n/app_localizations_zh.dart @@ -978,6 +978,21 @@ class AppLocalizationsZh extends AppLocalizations { @override String get openNewWindow => '新窗口打开'; + + @override + String get sponsorDonate => '赞助 / 捐赠'; + + @override + String get sponsorSupport => '支持项目持续开发'; + + @override + String get sponsorThanks => '感谢支持开源项目,可选择以下任意方式,帮助项目长期发展'; + + @override + String get sponsorAfdian => '爱发电赞助'; + + @override + String get sponsorBuyMeCoffee => 'Buy Me a Coffee'; } /// The translations for Chinese, using the Han script (`zh_Hant`). @@ -1952,4 +1967,22 @@ class AppLocalizationsZhHant extends AppLocalizationsZh { @override String get certNotInstalled => '未安裝憑證'; + + @override + String get openNewWindow => '新視窗開啟'; + + @override + String get sponsorDonate => '贊助 / 捐贈'; + + @override + String get sponsorSupport => '支持項目持續開發'; + + @override + String get sponsorThanks => '感謝支持開源項目,可選擇以下任意方式,幫助項目長期發展'; + + @override + String get sponsorAfdian => '愛發電贊助'; + + @override + String get sponsorBuyMeCoffee => 'Buy Me a Coffee'; } diff --git a/lib/l10n/app_zh.arb b/lib/l10n/app_zh.arb index e8562f7..ef20a93 100644 --- a/lib/l10n/app_zh.arb +++ b/lib/l10n/app_zh.arb @@ -341,5 +341,10 @@ "manual": "手动安装", "certNotInstalled": "证书未安装", - "openNewWindow": "新窗口打开" + "openNewWindow": "新窗口打开", + "sponsorDonate": "赞助 / 捐赠", + "sponsorSupport": "支持项目持续开发", + "sponsorThanks": "感谢支持开源项目,可选择以下任意方式,帮助项目长期发展", + "sponsorAfdian": "爱发电赞助", + "sponsorBuyMeCoffee": "Buy Me a Coffee" } \ No newline at end of file diff --git a/lib/l10n/app_zh_Hant.arb b/lib/l10n/app_zh_Hant.arb index caf66e7..1f2183b 100644 --- a/lib/l10n/app_zh_Hant.arb +++ b/lib/l10n/app_zh_Hant.arb @@ -318,5 +318,11 @@ "automatic": "自動", "manual": "手動", - "certNotInstalled": "未安裝憑證" + "certNotInstalled": "未安裝憑證", + "openNewWindow": "新視窗開啟", + "sponsorDonate": "贊助 / 捐贈", + "sponsorSupport": "支持項目持續開發", + "sponsorThanks": "感謝支持開源項目,可選擇以下任意方式,幫助項目長期發展", + "sponsorAfdian": "愛發電贊助", + "sponsorBuyMeCoffee": "Buy Me a Coffee" } \ No newline at end of file diff --git a/lib/ui/desktop/setting/about.dart b/lib/ui/desktop/setting/about.dart index 726d169..7eb4136 100644 --- a/lib/ui/desktop/setting/about.dart +++ b/lib/ui/desktop/setting/about.dart @@ -1,3 +1,4 @@ +import 'dart:ui' show FontFeature; import 'package:flutter/material.dart'; import 'package:proxypin/l10n/app_localizations.dart'; import 'package:proxypin/ui/app_update/app_update_repository.dart'; @@ -29,54 +30,126 @@ class _AppUpdateStateChecking extends State { const Expanded(child: SizedBox()), Text(localizations.about, style: const TextStyle(fontSize: 18, fontWeight: FontWeight.w500)), const Expanded(child: SizedBox()), - Align(alignment: Alignment.topRight, child: CloseButton()) + const Align(alignment: Alignment.topRight, child: CloseButton()) ]), content: SizedBox( width: 360, child: Column( mainAxisSize: MainAxisSize.min, children: [ - const Text("ProxyPin", style: TextStyle(fontSize: 20)), + const Text("ProxyPin", style: TextStyle(fontSize: 20, fontWeight: FontWeight.w600, letterSpacing: .5)), const SizedBox(height: 10), Padding( padding: const EdgeInsets.only(left: 10, right: 10), - child: - Text(isCN ? "全平台开源免费抓包软件" : "Full platform open source free capture HTTP(S) traffic software")), - const SizedBox(height: 10), - Text("Version ${AppConfiguration.version}"), + child: Text(isCN ? "全平台开源免费抓包软件" : "Full platform open source free capture HTTP(S) traffic software", + textAlign: TextAlign.center, style: const TextStyle(height: 1.3))), const SizedBox(height: 10), + DecoratedBox( + decoration: BoxDecoration( + color: Theme.of(context).colorScheme.surfaceContainerHighest.withValues(alpha: 0.4), + borderRadius: BorderRadius.circular(8), + ), + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6), + child: Text("Version ${AppConfiguration.version}", + style: TextStyle( + fontWeight: FontWeight.w500, + color: Theme.of(context).colorScheme.primary, + fontFeatures: const [FontFeature.tabularFigures()])), + ), + ), + const SizedBox(height: 12), + Divider(height: 1, color: Theme.of(context).dividerColor.withValues(alpha: 0.4)), ListTile( - title: Text('GitHub'), - trailing: const Icon(Icons.open_in_new, size: 22), - onTap: () => launchUrl(Uri.parse(gitHub))), + dense: true, + title: const Text('GitHub'), + trailing: const Icon(Icons.open_in_new, size: 21), + onTap: () => _safeLaunch(Uri.parse(gitHub))), ListTile( + dense: true, title: Text(localizations.feedback), - trailing: const Icon(Icons.open_in_new, size: 22), - onTap: () => launchUrl(Uri.parse("$gitHub/issues"))), + trailing: const Icon(Icons.open_in_new, size: 21), + onTap: () => _safeLaunch(Uri.parse("$gitHub/issues"))), ListTile( + dense: true, title: Text(localizations.appUpdateCheckVersion), trailing: checkUpdating - ? const SizedBox(width: 22, height: 22, child: CircularProgressIndicator()) - : const Icon(Icons.sync, size: 22), + ? const SizedBox(width: 20, height: 20, child: CircularProgressIndicator(strokeWidth: 2)) + : const Icon(Icons.sync, size: 21), onTap: () async { - if (checkUpdating) { - return; - } - setState(() { - checkUpdating = true; - }); + if (checkUpdating) return; + setState(() => checkUpdating = true); await AppUpdateRepository.checkUpdate(context, canIgnore: false, showToast: true); - setState(() { - checkUpdating = false; - }); + if (mounted) setState(() => checkUpdating = false); }), ListTile( + dense: true, title: Text(isCN ? "下载地址" : "Download"), - trailing: const Icon(Icons.open_in_new, size: 22), - onTap: () => launchUrl( - Uri.parse(isCN ? "https://gitee.com/wanghongenpin/proxypin/releases" : "$gitHub/releases"))) + trailing: const Icon(Icons.open_in_new, size: 21), + onTap: () => _safeLaunch( + Uri.parse(isCN ? "https://gitee.com/wanghongenpin/proxypin/releases" : "$gitHub/releases"))), + ListTile( + dense: true, + title: Text(localizations.sponsorDonate), + subtitle: Text(localizations.sponsorSupport, style: const TextStyle(fontSize: 11)), + trailing: const Icon(Icons.favorite, color: Colors.redAccent, size: 21), + onTap: () => _showSponsorDialog(), + ), ], )), ); } + + Future _safeLaunch(Uri uri) async { + await launchUrl(uri, mode: LaunchMode.externalApplication); + } + + void _showSponsorDialog() { + bool isCN = Localizations.localeOf(context) == const Locale.fromSubtags(languageCode: 'zh'); + List sponsors = [ + ListTile( + onTap: () => _safeLaunch(Uri.parse("https://afdian.com/a/proxypin")), + contentPadding: EdgeInsets.zero, + leading: const Icon(Icons.favorite, color: Colors.pinkAccent), + title: Text(localizations.sponsorAfdian), + ) + ]; + + final coffee = ListTile( + contentPadding: EdgeInsets.zero, + leading: const Icon(Icons.coffee, color: Colors.brown), + title: Text('Buy Me a Coffee'), + onTap: () => _safeLaunch(Uri.parse("https://buymeacoffee.com/proxypin")), + ); + if (isCN) { + sponsors.add(coffee); + } else { + sponsors.insert(0, coffee); + } + + showDialog( + context: context, + builder: (ctx) { + return AlertDialog( + title: Text(localizations.sponsorDonate), + contentPadding: const EdgeInsets.only(left: 20, top: 10, right: 20, bottom: 10), + content: SizedBox( + width: 340, + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text(localizations.sponsorThanks, style: const TextStyle(height: 1.4)), + const SizedBox(height: 16), + ...sponsors + ], + ), + ), + actions: [ + TextButton(onPressed: () => Navigator.of(ctx).pop(), child: Text(localizations.close)), + ], + ); + }, + ); + } } diff --git a/lib/ui/mobile/setting/ssl.dart b/lib/ui/mobile/setting/ssl.dart index d3f5059..17ba87b 100644 --- a/lib/ui/mobile/setting/ssl.dart +++ b/lib/ui/mobile/setting/ssl.dart @@ -509,10 +509,14 @@ class IOSCertChecker { return AlertDialog( titlePadding: EdgeInsets.zero, contentPadding: EdgeInsets.zero, - constraints: const BoxConstraints(maxHeight: 185), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), - content: CertStatusCard( - installed: installed, trusted: trusted, margin: EdgeInsets.zero, proxyServer: ProxyServer.current!), + content: Container( + constraints: const BoxConstraints(maxHeight: 185), + child: CertStatusCard( + installed: installed, + trusted: trusted, + margin: EdgeInsets.zero, + proxyServer: ProxyServer.current!)), actions: [ TextButton( onPressed: () { diff --git a/lib/ui/mobile/widgets/about.dart b/lib/ui/mobile/widgets/about.dart index 8efb0c0..d61f1cd 100644 --- a/lib/ui/mobile/widgets/about.dart +++ b/lib/ui/mobile/widgets/about.dart @@ -37,9 +37,10 @@ class _AboutState extends State { @override Widget build(BuildContext context) { AppLocalizations localizations = AppLocalizations.of(context)!; - bool isCN = Localizations.localeOf(context) == const Locale.fromSubtags(languageCode: 'zh'); String gitHub = "https://github.com/wanghongenpin/proxypin"; + final String sponsorUrl = "https://github.com/sponsors/wanghongenpin"; + return Scaffold( appBar: AppBar(title: Text(localizations.about, style: const TextStyle(fontSize: 16)), centerTitle: true), body: ListView(padding: const EdgeInsets.all(12), children: [ @@ -49,8 +50,7 @@ class _AboutState extends State { Center( child: Padding( padding: const EdgeInsets.symmetric(horizontal: 10), - child: Text(isCN ? "全平台开源免费抓包软件" : "Full platform open source free capture HTTP(S) traffic software", - textAlign: TextAlign.center))), + child: Text(localizations.proxyPinSoftware, textAlign: TextAlign.center))), const SizedBox(height: 8), Center(child: Text("${localizations.version} ${AppConfiguration.version}")), const SizedBox(height: 12), @@ -65,14 +65,14 @@ class _AboutState extends State { title: const Text("GitHub"), trailing: const Icon(Icons.open_in_new, size: 22), onTap: () { - launchUrl(Uri.parse(gitHub), mode: LaunchMode.externalApplication); + _safeLaunch(Uri.parse(gitHub)); }), Divider(height: 0, thickness: 0.4, color: Theme.of(context).dividerColor.withValues(alpha: 0.22)), ListTile( title: Text(localizations.feedback), trailing: const Icon(Icons.open_in_new, size: 22), onTap: () { - launchUrl(Uri.parse("$gitHub/issues"), mode: LaunchMode.externalApplication); + _safeLaunch(Uri.parse("$gitHub/issues")); }), Divider(height: 0, thickness: 0.4, color: Theme.of(context).dividerColor.withValues(alpha: 0.22)), ListTile( @@ -88,15 +88,71 @@ class _AboutState extends State { }), Divider(height: 0, thickness: 0.4, color: Theme.of(context).dividerColor.withValues(alpha: 0.22)), ListTile( - title: Text(isCN ? "下载地址" : "Download"), + title: Text(localizations.download), trailing: const Icon(Icons.open_in_new, size: 22), onTap: () { - final url = isCN - ? "https://gitee.com/wanghongenpin/proxypin/releases" - : "$gitHub/releases"; - launchUrl(Uri.parse(url), mode: LaunchMode.externalApplication); + final url = "$gitHub/releases"; + _safeLaunch(Uri.parse(url)); }), + Divider(height: 0, thickness: 0.4, color: Theme.of(context).dividerColor.withValues(alpha: 0.22)), + // Sponsor / Donate entry + ListTile( + title: Text(localizations.sponsorDonate), + subtitle: Text(localizations.sponsorSupport, style: const TextStyle(fontSize: 12)), + trailing: const Icon(Icons.favorite, color: Colors.redAccent, size: 22), + onTap: () => _showSponsorDialog(localizations, sponsorUrl), + ), ])) ])); } + + Future _safeLaunch(Uri uri) async { + await launchUrl(uri, mode: LaunchMode.externalApplication); + } + + void _showSponsorDialog(AppLocalizations l10n, String sponsorUrl) { + bool isCN = Localizations.localeOf(context) == const Locale.fromSubtags(languageCode: 'zh'); + + List sponsors = [ + ListTile( + onTap: () => _safeLaunch(Uri.parse("https://afdian.com/a/proxypin")), + contentPadding: EdgeInsets.zero, + leading: const Icon(Icons.favorite, color: Colors.pinkAccent), + title: Text(l10n.sponsorAfdian), + ) + ]; + + final coffee = ListTile( + contentPadding: EdgeInsets.zero, + leading: const Icon(Icons.coffee, color: Colors.brown), + title: Text('Buy Me a Coffee'), + onTap: () => _safeLaunch(Uri.parse("https://buymeacoffee.com/proxypin")), + ); + if (isCN) { + sponsors.add(coffee); + } else { + sponsors.insert(0, coffee); + } + + showDialog( + context: context, + builder: (ctx) { + return AlertDialog( + title: Text(l10n.sponsorDonate), + contentPadding: const EdgeInsets.only(left: 20, top: 10, right: 20, bottom: 10), + content: SizedBox( + width: 340, + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [Text(l10n.sponsorThanks), const SizedBox(height: 12), ...sponsors], + ), + ), + actions: [ + TextButton(onPressed: () => Navigator.of(ctx).pop(), child: Text(l10n.close)), + ], + ); + }, + ); + } }