From 6b001eaebdfb4b8ab037ec3d13a3d9694fe421ae Mon Sep 17 00:00:00 2001 From: atavism Date: Mon, 18 Mar 2024 01:42:38 -0700 Subject: [PATCH 01/49] do not pass initialize flag when running Lantern --- .github/workflows/release.yml | 5 ++--- installer-resources-lantern/windows/lantern.nsi | 6 +----- 2 files changed, 3 insertions(+), 8 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 0f074f25d..28543f897 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -2,7 +2,7 @@ name: Publish releases on: push: - branches: [ main ] + branches: [ atavism/windows-installer-updates ] tags: - '*' @@ -15,8 +15,7 @@ env: S3_BUCKET: lantern jobs: set-version: - runs-on: - group: large-runners + runs-on: ubuntu-latest outputs: version: ${{ steps.set-version.outputs.version }} prefix: ${{ steps.set-version.outputs.prefix }} diff --git a/installer-resources-lantern/windows/lantern.nsi b/installer-resources-lantern/windows/lantern.nsi index 8dcc03d50..eb7a898a2 100755 --- a/installer-resources-lantern/windows/lantern.nsi +++ b/installer-resources-lantern/windows/lantern.nsi @@ -103,11 +103,7 @@ Section "Lantern" "$\"$INSTDIR\${APP_NAME}$\" -clear-proxy-settings" # Initialize Lantern to a point of having at lease one working proxy. - ExecWait '"$INSTDIR\${APP_NAME}" "-initialize" "-timeout" "30s"' $0 - - ${If} $0 != 0 - DetailPrint "Timed out initialize Lantern, continue anyway" - ${EndIf} + ExecWait '"$INSTDIR\${APP_NAME}"' $0 SectionEnd # end default section From 39a1e394137bd1469106761d121b42d9880ac6f1 Mon Sep 17 00:00:00 2001 From: atavism Date: Mon, 18 Mar 2024 03:00:44 -0700 Subject: [PATCH 02/49] do not pass initialize flag when running Lantern --- installer-resources-lantern/windows/lantern.nsi | 3 --- 1 file changed, 3 deletions(-) diff --git a/installer-resources-lantern/windows/lantern.nsi b/installer-resources-lantern/windows/lantern.nsi index eb7a898a2..5713343e0 100755 --- a/installer-resources-lantern/windows/lantern.nsi +++ b/installer-resources-lantern/windows/lantern.nsi @@ -102,9 +102,6 @@ Section WriteRegStr HKCU "Software\Microsoft\Windows\CurrentVersion\Run" \ "Lantern" "$\"$INSTDIR\${APP_NAME}$\" -clear-proxy-settings" - # Initialize Lantern to a point of having at lease one working proxy. - ExecWait '"$INSTDIR\${APP_NAME}"' $0 - SectionEnd # end default section From 99ee35a5eae986c42e0ac6c7670a7815de00ca22 Mon Sep 17 00:00:00 2001 From: atavism Date: Mon, 18 Mar 2024 03:30:32 -0700 Subject: [PATCH 03/49] remove tray manager references in vpn switch widget --- lib/vpn/vpn_switch.dart | 8 -------- 1 file changed, 8 deletions(-) diff --git a/lib/vpn/vpn_switch.dart b/lib/vpn/vpn_switch.dart index 2c68eaac7..db71adc30 100644 --- a/lib/vpn/vpn_switch.dart +++ b/lib/vpn/vpn_switch.dart @@ -19,17 +19,11 @@ class _VPNSwitchState extends State with TrayListener { @override void initState() { - if (isDesktop()) { - trayManager.addListener(this); - } super.initState(); } @override void dispose() { - if (isDesktop()) { - trayManager.removeListener(this); - } super.dispose(); } @@ -38,7 +32,6 @@ class _VPNSwitchState extends State with TrayListener { Future vpnProcessForDesktop() async { bool isConnected = vpnStatus == 'connected'; - String path = systemTrayIcon(!isConnected); if (isConnected) { sysProxyOff(); await setupMenu(false); @@ -46,7 +39,6 @@ class _VPNSwitchState extends State with TrayListener { sysProxyOn(); await setupMenu(true); } - await trayManager.setIcon(path); } Future vpnProcessForMobile( From 1d0b91b3bc92e5dae55d7f894f93f4ae686add78 Mon Sep 17 00:00:00 2001 From: atavism Date: Mon, 18 Mar 2024 04:46:50 -0700 Subject: [PATCH 04/49] remove tray manager references in vpn switch widget --- .github/workflows/release.yml | 2 +- lib/common/ui/app_webview.dart | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 28543f897..6703c18ed 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -2,7 +2,7 @@ name: Publish releases on: push: - branches: [ atavism/windows-installer-updates ] + branches: [ main ] tags: - '*' diff --git a/lib/common/ui/app_webview.dart b/lib/common/ui/app_webview.dart index a6aee6266..c98784388 100644 --- a/lib/common/ui/app_webview.dart +++ b/lib/common/ui/app_webview.dart @@ -48,8 +48,8 @@ class AppBrowser extends InAppBrowser { final InAppBrowserClassSettings settings = InAppBrowserClassSettings( browserSettings: InAppBrowserSettings(hideUrlBar: true), - webViewSettings: InAppWebViewSettings( - javaScriptEnabled: true, isInspectable: kDebugMode)); + /*webViewSettings: InAppWebViewSettings( + javaScriptEnabled: true, isInspectable: kDebugMode)*/); AppBrowser(); From e07e01185cf2be1d1230437de561dd1a38eac040 Mon Sep 17 00:00:00 2001 From: atavism Date: Mon, 18 Mar 2024 05:01:23 -0700 Subject: [PATCH 05/49] use windows webview --- lib/plans/checkout.dart | 12 ++++++++++-- pubspec.lock | 9 +++++++++ pubspec.yaml | 4 ++++ windows/flutter/generated_plugin_registrant.cc | 3 +++ windows/flutter/generated_plugins.cmake | 1 + 5 files changed, 27 insertions(+), 2 deletions(-) diff --git a/lib/plans/checkout.dart b/lib/plans/checkout.dart index f48c03a44..2aab6f728 100644 --- a/lib/plans/checkout.dart +++ b/lib/plans/checkout.dart @@ -1,3 +1,4 @@ +import 'package:flutter_windows_webview/flutter_windows_webview.dart'; import 'package:email_validator/email_validator.dart'; import 'package:lantern/common/common.dart'; import 'package:lantern/common/common_desktop.dart'; @@ -25,7 +26,6 @@ class _CheckoutState extends State with SingleTickerProviderStateMixin { bool showMoreOptions = false; bool showContinueButton = false; - final browser = AppBrowser(); final emailFieldKey = GlobalKey(); late final emailController = CustomTextEditingController( formKey: emailFieldKey, @@ -161,9 +161,17 @@ class _CheckoutState extends State "stripe", os, ); - if (!Platform.isMacOS) { + if (!Platform.isMacOS && !Platform.isWindows) { await context.pushRoute(AppWebview(url: redirectUrl)); + } else if (Platform.isWindows) { + final webview = FlutterWindowsWebview(); + webview.launchWebview(redirectUrl, WebviewOptions( + onNavigation: (url) { + return false; + } + )); } else { + final browser = AppBrowser(); await browser.openUrl(redirectUrl, () async { final res = await ffiProUser(); if (!widget.isPro && res == "true") { diff --git a/pubspec.lock b/pubspec.lock index 94e1743a2..8d0249c2e 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -754,6 +754,15 @@ packages: description: flutter source: sdk version: "0.0.0" + flutter_windows_webview: + dependency: "direct main" + description: + path: "." + ref: master + resolved-ref: "096d24d88454819b9f8a94af14a130cb372fa5ae" + url: "https://github.com/MiaoMint/flutter_windows_webview" + source: git + version: "0.0.1" fluttertoast: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index c2ae32bb2..068bb12a8 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -93,6 +93,10 @@ dependencies: url_launcher: ^6.1.12 share_plus: ^7.1.0 flutter_inappwebview: ^6.0.0 + flutter_windows_webview: + git: + url: https://github.com/MiaoMint/flutter_windows_webview + ref: master # Desktop window_manager: ^0.3.8 diff --git a/windows/flutter/generated_plugin_registrant.cc b/windows/flutter/generated_plugin_registrant.cc index d10899353..9582b198e 100644 --- a/windows/flutter/generated_plugin_registrant.cc +++ b/windows/flutter/generated_plugin_registrant.cc @@ -9,6 +9,7 @@ #include #include #include +#include #include #include #include @@ -24,6 +25,8 @@ void RegisterPlugins(flutter::PluginRegistry* registry) { registry->GetRegistrarForPlugin("AudioplayersWindowsPlugin")); EmojiPickerFlutterPluginCApiRegisterWithRegistrar( registry->GetRegistrarForPlugin("EmojiPickerFlutterPluginCApi")); + FlutterWindowsWebviewPluginCApiRegisterWithRegistrar( + registry->GetRegistrarForPlugin("FlutterWindowsWebviewPluginCApi")); PermissionHandlerWindowsPluginRegisterWithRegistrar( registry->GetRegistrarForPlugin("PermissionHandlerWindowsPlugin")); ScreenRetrieverPluginRegisterWithRegistrar( diff --git a/windows/flutter/generated_plugins.cmake b/windows/flutter/generated_plugins.cmake index 5d0ef0b62..ad1e63721 100644 --- a/windows/flutter/generated_plugins.cmake +++ b/windows/flutter/generated_plugins.cmake @@ -6,6 +6,7 @@ list(APPEND FLUTTER_PLUGIN_LIST app_links audioplayers_windows emoji_picker_flutter + flutter_windows_webview permission_handler_windows screen_retriever sentry_flutter From c775cc924e4117b6fbb8266a345704cf8c6e1ee3 Mon Sep 17 00:00:00 2001 From: atavism Date: Mon, 18 Mar 2024 05:26:02 -0700 Subject: [PATCH 06/49] update windows webview --- lib/common/ui/app_webview.dart | 31 ++++++++++++++++++------------- lib/plans/checkout.dart | 25 ++++++++++--------------- 2 files changed, 28 insertions(+), 28 deletions(-) diff --git a/lib/common/ui/app_webview.dart b/lib/common/ui/app_webview.dart index c98784388..1327a1044 100644 --- a/lib/common/ui/app_webview.dart +++ b/lib/common/ui/app_webview.dart @@ -1,4 +1,5 @@ import 'package:flutter_inappwebview/flutter_inappwebview.dart'; +import 'package:flutter_windows_webview/flutter_windows_webview.dart'; import 'package:lantern/common/common.dart'; import 'package:lantern/common/common_desktop.dart'; import 'package:lantern/plans/utils.dart'; @@ -17,7 +18,6 @@ class AppWebView extends StatefulWidget { } class _AppWebViewState extends State { - final InAppWebViewSettings settings = InAppWebViewSettings( isInspectable: false, javaScriptEnabled: true, @@ -29,7 +29,6 @@ class _AppWebViewState extends State { preferredContentMode: UserPreferredContentMode.MOBILE, ); - @override Widget build(BuildContext context) { return BaseScreen( @@ -43,14 +42,8 @@ class _AppWebViewState extends State { } class AppBrowser extends InAppBrowser { - Future Function()? _onLoadStop; - final InAppBrowserClassSettings settings = InAppBrowserClassSettings( - browserSettings: InAppBrowserSettings(hideUrlBar: true), - /*webViewSettings: InAppWebViewSettings( - javaScriptEnabled: true, isInspectable: kDebugMode)*/); - AppBrowser(); @override @@ -69,11 +62,6 @@ class AppBrowser extends InAppBrowser { this._onLoadStop?.call(); } - Future openUrl(String url, Future Function() cb) async { - this._onLoadStop = cb; - await this.openUrlRequest(urlRequest: URLRequest(url: WebUri(url)), settings: settings); - } - @override void onReceivedError(WebResourceRequest request, WebResourceError error) { print("Can't load ${request.url}.. Error: ${error.description}"); @@ -88,4 +76,21 @@ class AppBrowser extends InAppBrowser { void onExit() { print("Browser closed"); } + + static Future launchMacWebview( + String url, Future Function() cb) async { + final AppBrowser browser = AppBrowser(cb); + browser._onLoadStop = cb; + final settings = InAppBrowserClassSettings( + browserSettings: InAppBrowserSettings(hideUrlBar: true), + webViewSettings: InAppWebViewSettings( + javaScriptEnabled: true, isInspectable: kDebugMode)); + await browser.openUrlRequest( + urlRequest: URLRequest(url: WebUri(url)), settings: settings); + } + + static Future launchWindowsWebview( + String url, Future Function() cb) async { + FlutterWindowsWebview().launchWebview(url); + } } diff --git a/lib/plans/checkout.dart b/lib/plans/checkout.dart index 2aab6f728..4d1d037fb 100644 --- a/lib/plans/checkout.dart +++ b/lib/plans/checkout.dart @@ -1,4 +1,3 @@ -import 'package:flutter_windows_webview/flutter_windows_webview.dart'; import 'package:email_validator/email_validator.dart'; import 'package:lantern/common/common.dart'; import 'package:lantern/common/common_desktop.dart'; @@ -149,6 +148,14 @@ class _CheckoutState extends State return widgets; } + Future checkProUser() async { + final res = await ffiProUser(); + if (!widget.isPro && res == "true") { + // show success dialog if user becomes Pro during browser session + showSuccessDialog(context, widget.isPro); + } + } + Future resolvePaymentRoute() async { switch (selectedPaymentProvider!) { case Providers.stripe: @@ -164,21 +171,9 @@ class _CheckoutState extends State if (!Platform.isMacOS && !Platform.isWindows) { await context.pushRoute(AppWebview(url: redirectUrl)); } else if (Platform.isWindows) { - final webview = FlutterWindowsWebview(); - webview.launchWebview(redirectUrl, WebviewOptions( - onNavigation: (url) { - return false; - } - )); + await AppBrowser.launchWindowsWebview(redirectUrl, checkProUser); } else { - final browser = AppBrowser(); - await browser.openUrl(redirectUrl, () async { - final res = await ffiProUser(); - if (!widget.isPro && res == "true") { - // show success dialog if user becomes Pro during browser session - showSuccessDialog(context, widget.isPro); - } - }); + await AppBrowser.launchMacWebview(redirectUrl, checkProUser); } return; } From d8081967f96b7d17e795dd2c3c7df8894567e2cd Mon Sep 17 00:00:00 2001 From: atavism Date: Mon, 18 Mar 2024 05:27:32 -0700 Subject: [PATCH 07/49] update windows webview --- lib/common/ui/app_webview.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/common/ui/app_webview.dart b/lib/common/ui/app_webview.dart index 1327a1044..fee7e217a 100644 --- a/lib/common/ui/app_webview.dart +++ b/lib/common/ui/app_webview.dart @@ -79,7 +79,7 @@ class AppBrowser extends InAppBrowser { static Future launchMacWebview( String url, Future Function() cb) async { - final AppBrowser browser = AppBrowser(cb); + final AppBrowser browser = AppBrowser(); browser._onLoadStop = cb; final settings = InAppBrowserClassSettings( browserSettings: InAppBrowserSettings(hideUrlBar: true), From 289604c1f8bd0d6a9e8b93944d24a8425df39199 Mon Sep 17 00:00:00 2001 From: atavism Date: Mon, 18 Mar 2024 05:37:31 -0700 Subject: [PATCH 08/49] windows webview updates --- lib/common/ui/app_webview.dart | 18 ++++++++---------- lib/plans/checkout.dart | 9 ++++++--- 2 files changed, 14 insertions(+), 13 deletions(-) diff --git a/lib/common/ui/app_webview.dart b/lib/common/ui/app_webview.dart index fee7e217a..f8e08df3e 100644 --- a/lib/common/ui/app_webview.dart +++ b/lib/common/ui/app_webview.dart @@ -42,9 +42,11 @@ class _AppWebViewState extends State { } class AppBrowser extends InAppBrowser { - Future Function()? _onLoadStop; + final Future Function()? onClose; - AppBrowser(); + AppBrowser({ + required this.onClose, + }); @override Future onBrowserCreated() async { @@ -59,7 +61,7 @@ class AppBrowser extends InAppBrowser { @override Future onLoadStop(url) async { print("Stopped displaying $url"); - this._onLoadStop?.call(); + this.onClose?.call(); } @override @@ -77,20 +79,16 @@ class AppBrowser extends InAppBrowser { print("Browser closed"); } - static Future launchMacWebview( - String url, Future Function() cb) async { - final AppBrowser browser = AppBrowser(); - browser._onLoadStop = cb; + Future openMacWebview(String url) async { final settings = InAppBrowserClassSettings( browserSettings: InAppBrowserSettings(hideUrlBar: true), webViewSettings: InAppWebViewSettings( javaScriptEnabled: true, isInspectable: kDebugMode)); - await browser.openUrlRequest( + await this.openUrlRequest( urlRequest: URLRequest(url: WebUri(url)), settings: settings); } - static Future launchWindowsWebview( - String url, Future Function() cb) async { + Future openWindowsWebview(String url) async { FlutterWindowsWebview().launchWebview(url); } } diff --git a/lib/plans/checkout.dart b/lib/plans/checkout.dart index 4d1d037fb..169430ef8 100644 --- a/lib/plans/checkout.dart +++ b/lib/plans/checkout.dart @@ -170,10 +170,13 @@ class _CheckoutState extends State ); if (!Platform.isMacOS && !Platform.isWindows) { await context.pushRoute(AppWebview(url: redirectUrl)); - } else if (Platform.isWindows) { - await AppBrowser.launchWindowsWebview(redirectUrl, checkProUser); } else { - await AppBrowser.launchMacWebview(redirectUrl, checkProUser); + final browser = AppBrowser(onClose: checkProUser); + if (Platform.isWindows) { + await browser.openWindowsWebview(redirectUrl); + } else { + await browser.openMacWebview(redirectUrl); + } } return; } From 368cc36bfe3df281fe152c2a4854f2fd36898086 Mon Sep 17 00:00:00 2001 From: atavism Date: Mon, 18 Mar 2024 05:41:54 -0700 Subject: [PATCH 09/49] windows webview updates --- lib/plans/checkout.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/plans/checkout.dart b/lib/plans/checkout.dart index 169430ef8..e09dddcf8 100644 --- a/lib/plans/checkout.dart +++ b/lib/plans/checkout.dart @@ -171,10 +171,10 @@ class _CheckoutState extends State if (!Platform.isMacOS && !Platform.isWindows) { await context.pushRoute(AppWebview(url: redirectUrl)); } else { - final browser = AppBrowser(onClose: checkProUser); if (Platform.isWindows) { - await browser.openWindowsWebview(redirectUrl); + await AppBrowser.openWindowsWebview(redirectUrl); } else { + final browser = AppBrowser(onClose: checkProUser); await browser.openMacWebview(redirectUrl); } } From 312c0cf356d8031c827b07f3b5e1bdc9c7ecfddc Mon Sep 17 00:00:00 2001 From: atavism Date: Mon, 18 Mar 2024 05:43:42 -0700 Subject: [PATCH 10/49] windows webview updates --- lib/common/ui/app_webview.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/common/ui/app_webview.dart b/lib/common/ui/app_webview.dart index f8e08df3e..916ce766d 100644 --- a/lib/common/ui/app_webview.dart +++ b/lib/common/ui/app_webview.dart @@ -88,7 +88,7 @@ class AppBrowser extends InAppBrowser { urlRequest: URLRequest(url: WebUri(url)), settings: settings); } - Future openWindowsWebview(String url) async { + static Future openWindowsWebview(String url) async { FlutterWindowsWebview().launchWebview(url); } } From ca8e7b75c3e2986e008c1dc7fd991b796697be9f Mon Sep 17 00:00:00 2001 From: atavism Date: Mon, 18 Mar 2024 05:48:48 -0700 Subject: [PATCH 11/49] updates to use flutter_windows_webview --- lib/common/ui/app_webview.dart | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/common/ui/app_webview.dart b/lib/common/ui/app_webview.dart index 916ce766d..a3c95c98d 100644 --- a/lib/common/ui/app_webview.dart +++ b/lib/common/ui/app_webview.dart @@ -43,6 +43,10 @@ class _AppWebViewState extends State { class AppBrowser extends InAppBrowser { final Future Function()? onClose; + final InAppBrowserClassSettings settings = InAppBrowserClassSettings( + browserSettings: InAppBrowserSettings(hideUrlBar: true), + webViewSettings: InAppWebViewSettings( + javaScriptEnabled: true, isInspectable: kDebugMode)); AppBrowser({ required this.onClose, @@ -80,10 +84,6 @@ class AppBrowser extends InAppBrowser { } Future openMacWebview(String url) async { - final settings = InAppBrowserClassSettings( - browserSettings: InAppBrowserSettings(hideUrlBar: true), - webViewSettings: InAppWebViewSettings( - javaScriptEnabled: true, isInspectable: kDebugMode)); await this.openUrlRequest( urlRequest: URLRequest(url: WebUri(url)), settings: settings); } From 1ba42629a039ba1988e70fe2aed88003102a9bc4 Mon Sep 17 00:00:00 2001 From: atavism Date: Mon, 18 Mar 2024 06:15:59 -0700 Subject: [PATCH 12/49] update CI branch --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 6703c18ed..8d52c9b8d 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -2,7 +2,7 @@ name: Publish releases on: push: - branches: [ main ] + branches: [ atavism/windows-webview-updates ] tags: - '*' From a2335d1fce390bcb3e21736e24d54f05a4f7982c Mon Sep 17 00:00:00 2001 From: atavism Date: Tue, 19 Mar 2024 05:48:04 -0700 Subject: [PATCH 13/49] use wgh136/flutter_windows_webview --- pubspec.lock | 2 +- pubspec.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pubspec.lock b/pubspec.lock index 8d0249c2e..0e68fe80d 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -760,7 +760,7 @@ packages: path: "." ref: master resolved-ref: "096d24d88454819b9f8a94af14a130cb372fa5ae" - url: "https://github.com/MiaoMint/flutter_windows_webview" + url: "https://github.com/wgh136/flutter_windows_webview" source: git version: "0.0.1" fluttertoast: diff --git a/pubspec.yaml b/pubspec.yaml index 068bb12a8..12fd52c1c 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -95,7 +95,7 @@ dependencies: flutter_inappwebview: ^6.0.0 flutter_windows_webview: git: - url: https://github.com/MiaoMint/flutter_windows_webview + url: https://github.com/wgh136/flutter_windows_webview ref: master # Desktop From 482b1534b7990211692e4efd814fa5014d5b779a Mon Sep 17 00:00:00 2001 From: atavism Date: Tue, 19 Mar 2024 05:58:13 -0700 Subject: [PATCH 14/49] use subosito/flutter-action v2.12.0 --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 8d52c9b8d..413493442 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -129,7 +129,7 @@ jobs: run: git lfs pull # Install Flutter - - uses: subosito/flutter-action@v2 + - uses: subosito/flutter-action@v2.12.0 with: channel: "stable" - run: flutter --version From d6943f4bdd37d9e505ca57415db827afd554beb3 Mon Sep 17 00:00:00 2001 From: atavism Date: Tue, 19 Mar 2024 05:59:01 -0700 Subject: [PATCH 15/49] use subosito/flutter-action v2.12.0 --- .github/workflows/build-darwin.yml | 2 +- .github/workflows/build-windows.yml | 2 +- .github/workflows/release.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build-darwin.yml b/.github/workflows/build-darwin.yml index 1bd2099ed..cab67d65d 100644 --- a/.github/workflows/build-darwin.yml +++ b/.github/workflows/build-darwin.yml @@ -84,7 +84,7 @@ jobs: liblantern.dylib # Install Flutter - - uses: subosito/flutter-action@v2 + - uses: subosito/flutter-action with: channel: "stable" diff --git a/.github/workflows/build-windows.yml b/.github/workflows/build-windows.yml index 5ac98ce17..603bb3835 100644 --- a/.github/workflows/build-windows.yml +++ b/.github/workflows/build-windows.yml @@ -128,7 +128,7 @@ jobs: lfs: true # Install Flutter - - uses: subosito/flutter-action@v2 + - uses: subosito/flutter-action@v2.12.0 with: channel: "stable" diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 413493442..8d52c9b8d 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -129,7 +129,7 @@ jobs: run: git lfs pull # Install Flutter - - uses: subosito/flutter-action@v2.12.0 + - uses: subosito/flutter-action@v2 with: channel: "stable" - run: flutter --version From 219991a7c85e45e2b1915655799cbd588008ebb3 Mon Sep 17 00:00:00 2001 From: atavism Date: Tue, 19 Mar 2024 06:06:05 -0700 Subject: [PATCH 16/49] use subosito/flutter-action v2.12.0 --- .github/workflows/build-darwin.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-darwin.yml b/.github/workflows/build-darwin.yml index cab67d65d..1bd2099ed 100644 --- a/.github/workflows/build-darwin.yml +++ b/.github/workflows/build-darwin.yml @@ -84,7 +84,7 @@ jobs: liblantern.dylib # Install Flutter - - uses: subosito/flutter-action + - uses: subosito/flutter-action@v2 with: channel: "stable" From 50f269dce156085513bfa366a30219b253b5ec4f Mon Sep 17 00:00:00 2001 From: atavism Date: Tue, 19 Mar 2024 07:45:20 -0700 Subject: [PATCH 17/49] Add expectedMonthlyPrice to plan json --- desktop/lib.go | 24 ++++++++-- lib/common/session_model.dart | 21 ++++----- lib/vpn/protos_shared/vpn.pb.dart | 64 +++++++++++++++------------ lib/vpn/protos_shared/vpn.pbjson.dart | 38 +++++++++++----- protos_shared/vpn.proto | 11 ++--- 5 files changed, 100 insertions(+), 58 deletions(-) diff --git a/desktop/lib.go b/desktop/lib.go index 6bef10e55..e2425c2ff 100644 --- a/desktop/lib.go +++ b/desktop/lib.go @@ -24,6 +24,7 @@ import ( "github.com/getlantern/flashlight/v7/pro/client" "github.com/getlantern/golog" "github.com/getlantern/i18n" + "github.com/getlantern/jibber_jabber" "github.com/getlantern/lantern-client/desktop/app" "github.com/getlantern/lantern-client/desktop/autoupdate" "github.com/getlantern/lantern-client/internalsdk/protos" @@ -35,6 +36,10 @@ import ( import "C" +const ( + defaultLocale = "en-US" +) + var ( log = golog.LoggerFor("lantern-desktop.main") a *app.App @@ -476,6 +481,18 @@ func runApp(a *app.App) { a.Run(true) } +// useOSLocale detect OS locale for current user and let i18n to use it +func useOSLocale() (string, error) { + userLocale, err := jibber_jabber.DetectIETF() + if err != nil || userLocale == "C" { + log.Debugf("Ignoring OS locale and using default") + userLocale = defaultLocale + } + log.Debugf("Using OS locale of current user: %v", userLocale) + a.SetLanguage(userLocale) + return userLocale, nil +} + func i18nInit(a *app.App) { i18n.SetMessagesFunc(func(filename string) ([]byte, error) { return a.GetTranslations(filename) @@ -485,10 +502,11 @@ func i18nInit(a *app.App) { if _, err := i18n.SetLocale(locale); err != nil { log.Debugf("i18n.SetLocale(%s) failed, fallback to OS default: %q", locale, err) - // On startup GetLanguage will return '', as the browser has not set the language yet. - // We use the OS locale instead and make sure the language is populated. - if locale, err := i18n.UseOSLocale(); err != nil { + // On startup GetLanguage will return '' We use the OS locale instead and make sure the language is + // populated. + if locale, err := useOSLocale(); err != nil { log.Debugf("i18n.UseOSLocale: %q", err) + a.SetLanguage(defaultLocale) } else { a.SetLanguage(locale) } diff --git a/lib/common/session_model.dart b/lib/common/session_model.dart index f62800761..870d542c3 100644 --- a/lib/common/session_model.dart +++ b/lib/common/session_model.dart @@ -420,20 +420,21 @@ class SessionModel extends Model { Plan planFromJson(Map item) { final formatCurrency = new NumberFormat.simpleCurrency(); - var id = item['id']; - var plan = Plan(); - plan.id = id; - plan.description = item["description"]; - plan.oneMonthCost = formatCurrency - .format(item["expectedMonthlyPrice"]["usd"] / 100) + print("plan is $item"); + final res = jsonEncode(item); + final plan = Plan.create()..mergeFromProto3Json(jsonDecode(res)); + final usdPrice = item["usdPrice"] as int; + if (plan.expectedMonthlyPrice["usd"] != null) { + var monthlyPrice = plan.expectedMonthlyPrice["usd"]!.toInt(); + plan.oneMonthCost = formatCurrency + .format(monthlyPrice / 100) .toString(); - plan.totalCost = formatCurrency.format(item["usdPrice"] / 100).toString(); + } + plan.totalCost = formatCurrency.format(usdPrice / 100).toString(); plan.totalCostBilledOneTime = - formatCurrency.format(item["usdPrice"] / 100).toString() + + formatCurrency.format(usdPrice / 100).toString() + ' ' + 'billed_one_time'.i18n; - plan.bestValue = item["bestValue"] ?? false; - plan.usdPrice = Int64(item["usdPrice"]); return plan; } diff --git a/lib/vpn/protos_shared/vpn.pb.dart b/lib/vpn/protos_shared/vpn.pb.dart index f5dc9b23f..d5d775a75 100644 --- a/lib/vpn/protos_shared/vpn.pb.dart +++ b/lib/vpn/protos_shared/vpn.pb.dart @@ -405,6 +405,7 @@ class Plan extends $pb.GeneratedMessage { $core.bool? bestValue, $fixnum.Int64? usdPrice, $core.Map<$core.String, $fixnum.Int64>? price, + $core.Map<$core.String, $fixnum.Int64>? expectedMonthlyPrice, $core.String? totalCostBilledOneTime, $core.String? oneMonthCost, $core.String? totalCost, @@ -427,6 +428,9 @@ class Plan extends $pb.GeneratedMessage { if (price != null) { $result.price.addAll(price); } + if (expectedMonthlyPrice != null) { + $result.expectedMonthlyPrice.addAll(expectedMonthlyPrice); + } if (totalCostBilledOneTime != null) { $result.totalCostBilledOneTime = totalCostBilledOneTime; } @@ -454,11 +458,12 @@ class Plan extends $pb.GeneratedMessage { ..aOB(3, _omitFieldNames ? '' : 'bestValue', protoName: 'bestValue') ..aInt64(4, _omitFieldNames ? '' : 'usdPrice', protoName: 'usdPrice') ..m<$core.String, $fixnum.Int64>(5, _omitFieldNames ? '' : 'price', entryClassName: 'Plan.PriceEntry', keyFieldType: $pb.PbFieldType.OS, valueFieldType: $pb.PbFieldType.O6) - ..aOS(6, _omitFieldNames ? '' : 'totalCostBilledOneTime', protoName: 'totalCostBilledOneTime') - ..aOS(7, _omitFieldNames ? '' : 'oneMonthCost', protoName: 'oneMonthCost') - ..aOS(8, _omitFieldNames ? '' : 'totalCost', protoName: 'totalCost') - ..aOS(9, _omitFieldNames ? '' : 'formattedBonus', protoName: 'formattedBonus') - ..aOS(10, _omitFieldNames ? '' : 'renewalText', protoName: 'renewalText') + ..m<$core.String, $fixnum.Int64>(6, _omitFieldNames ? '' : 'expectedMonthlyPrice', protoName: 'expectedMonthlyPrice', entryClassName: 'Plan.ExpectedMonthlyPriceEntry', keyFieldType: $pb.PbFieldType.OS, valueFieldType: $pb.PbFieldType.O6) + ..aOS(7, _omitFieldNames ? '' : 'totalCostBilledOneTime', protoName: 'totalCostBilledOneTime') + ..aOS(8, _omitFieldNames ? '' : 'oneMonthCost', protoName: 'oneMonthCost') + ..aOS(9, _omitFieldNames ? '' : 'totalCost', protoName: 'totalCost') + ..aOS(10, _omitFieldNames ? '' : 'formattedBonus', protoName: 'formattedBonus') + ..aOS(11, _omitFieldNames ? '' : 'renewalText', protoName: 'renewalText') ..hasRequiredFields = false ; @@ -523,49 +528,52 @@ class Plan extends $pb.GeneratedMessage { $core.Map<$core.String, $fixnum.Int64> get price => $_getMap(4); @$pb.TagNumber(6) - $core.String get totalCostBilledOneTime => $_getSZ(5); - @$pb.TagNumber(6) - set totalCostBilledOneTime($core.String v) { $_setString(5, v); } - @$pb.TagNumber(6) - $core.bool hasTotalCostBilledOneTime() => $_has(5); - @$pb.TagNumber(6) - void clearTotalCostBilledOneTime() => clearField(6); + $core.Map<$core.String, $fixnum.Int64> get expectedMonthlyPrice => $_getMap(5); @$pb.TagNumber(7) - $core.String get oneMonthCost => $_getSZ(6); + $core.String get totalCostBilledOneTime => $_getSZ(6); @$pb.TagNumber(7) - set oneMonthCost($core.String v) { $_setString(6, v); } + set totalCostBilledOneTime($core.String v) { $_setString(6, v); } @$pb.TagNumber(7) - $core.bool hasOneMonthCost() => $_has(6); + $core.bool hasTotalCostBilledOneTime() => $_has(6); @$pb.TagNumber(7) - void clearOneMonthCost() => clearField(7); + void clearTotalCostBilledOneTime() => clearField(7); @$pb.TagNumber(8) - $core.String get totalCost => $_getSZ(7); + $core.String get oneMonthCost => $_getSZ(7); @$pb.TagNumber(8) - set totalCost($core.String v) { $_setString(7, v); } + set oneMonthCost($core.String v) { $_setString(7, v); } @$pb.TagNumber(8) - $core.bool hasTotalCost() => $_has(7); + $core.bool hasOneMonthCost() => $_has(7); @$pb.TagNumber(8) - void clearTotalCost() => clearField(8); + void clearOneMonthCost() => clearField(8); @$pb.TagNumber(9) - $core.String get formattedBonus => $_getSZ(8); + $core.String get totalCost => $_getSZ(8); @$pb.TagNumber(9) - set formattedBonus($core.String v) { $_setString(8, v); } + set totalCost($core.String v) { $_setString(8, v); } @$pb.TagNumber(9) - $core.bool hasFormattedBonus() => $_has(8); + $core.bool hasTotalCost() => $_has(8); @$pb.TagNumber(9) - void clearFormattedBonus() => clearField(9); + void clearTotalCost() => clearField(9); @$pb.TagNumber(10) - $core.String get renewalText => $_getSZ(9); + $core.String get formattedBonus => $_getSZ(9); @$pb.TagNumber(10) - set renewalText($core.String v) { $_setString(9, v); } + set formattedBonus($core.String v) { $_setString(9, v); } @$pb.TagNumber(10) - $core.bool hasRenewalText() => $_has(9); + $core.bool hasFormattedBonus() => $_has(9); @$pb.TagNumber(10) - void clearRenewalText() => clearField(10); + void clearFormattedBonus() => clearField(10); + + @$pb.TagNumber(11) + $core.String get renewalText => $_getSZ(10); + @$pb.TagNumber(11) + set renewalText($core.String v) { $_setString(10, v); } + @$pb.TagNumber(11) + $core.bool hasRenewalText() => $_has(10); + @$pb.TagNumber(11) + void clearRenewalText() => clearField(11); } class PaymentProviders extends $pb.GeneratedMessage { diff --git a/lib/vpn/protos_shared/vpn.pbjson.dart b/lib/vpn/protos_shared/vpn.pbjson.dart index a3e645287..f3e5254e1 100644 --- a/lib/vpn/protos_shared/vpn.pbjson.dart +++ b/lib/vpn/protos_shared/vpn.pbjson.dart @@ -98,13 +98,14 @@ const Plan$json = { {'1': 'bestValue', '3': 3, '4': 1, '5': 8, '10': 'bestValue'}, {'1': 'usdPrice', '3': 4, '4': 1, '5': 3, '10': 'usdPrice'}, {'1': 'price', '3': 5, '4': 3, '5': 11, '6': '.Plan.PriceEntry', '10': 'price'}, - {'1': 'totalCostBilledOneTime', '3': 6, '4': 1, '5': 9, '10': 'totalCostBilledOneTime'}, - {'1': 'oneMonthCost', '3': 7, '4': 1, '5': 9, '10': 'oneMonthCost'}, - {'1': 'totalCost', '3': 8, '4': 1, '5': 9, '10': 'totalCost'}, - {'1': 'formattedBonus', '3': 9, '4': 1, '5': 9, '10': 'formattedBonus'}, - {'1': 'renewalText', '3': 10, '4': 1, '5': 9, '10': 'renewalText'}, + {'1': 'expectedMonthlyPrice', '3': 6, '4': 3, '5': 11, '6': '.Plan.ExpectedMonthlyPriceEntry', '10': 'expectedMonthlyPrice'}, + {'1': 'totalCostBilledOneTime', '3': 7, '4': 1, '5': 9, '10': 'totalCostBilledOneTime'}, + {'1': 'oneMonthCost', '3': 8, '4': 1, '5': 9, '10': 'oneMonthCost'}, + {'1': 'totalCost', '3': 9, '4': 1, '5': 9, '10': 'totalCost'}, + {'1': 'formattedBonus', '3': 10, '4': 1, '5': 9, '10': 'formattedBonus'}, + {'1': 'renewalText', '3': 11, '4': 1, '5': 9, '10': 'renewalText'}, ], - '3': [Plan_PriceEntry$json], + '3': [Plan_PriceEntry$json, Plan_ExpectedMonthlyPriceEntry$json], }; @$core.Deprecated('Use planDescriptor instead') @@ -117,16 +118,29 @@ const Plan_PriceEntry$json = { '7': {'7': true}, }; +@$core.Deprecated('Use planDescriptor instead') +const Plan_ExpectedMonthlyPriceEntry$json = { + '1': 'ExpectedMonthlyPriceEntry', + '2': [ + {'1': 'key', '3': 1, '4': 1, '5': 9, '10': 'key'}, + {'1': 'value', '3': 2, '4': 1, '5': 3, '10': 'value'}, + ], + '7': {'7': true}, +}; + /// Descriptor for `Plan`. Decode as a `google.protobuf.DescriptorProto`. final $typed_data.Uint8List planDescriptor = $convert.base64Decode( 'CgRQbGFuEg4KAmlkGAEgASgJUgJpZBIgCgtkZXNjcmlwdGlvbhgCIAEoCVILZGVzY3JpcHRpb2' '4SHAoJYmVzdFZhbHVlGAMgASgIUgliZXN0VmFsdWUSGgoIdXNkUHJpY2UYBCABKANSCHVzZFBy' - 'aWNlEiYKBXByaWNlGAUgAygLMhAuUGxhbi5QcmljZUVudHJ5UgVwcmljZRI2ChZ0b3RhbENvc3' - 'RCaWxsZWRPbmVUaW1lGAYgASgJUhZ0b3RhbENvc3RCaWxsZWRPbmVUaW1lEiIKDG9uZU1vbnRo' - 'Q29zdBgHIAEoCVIMb25lTW9udGhDb3N0EhwKCXRvdGFsQ29zdBgIIAEoCVIJdG90YWxDb3N0Ei' - 'YKDmZvcm1hdHRlZEJvbnVzGAkgASgJUg5mb3JtYXR0ZWRCb251cxIgCgtyZW5ld2FsVGV4dBgK' - 'IAEoCVILcmVuZXdhbFRleHQaOAoKUHJpY2VFbnRyeRIQCgNrZXkYASABKAlSA2tleRIUCgV2YW' - 'x1ZRgCIAEoA1IFdmFsdWU6AjgB'); + 'aWNlEiYKBXByaWNlGAUgAygLMhAuUGxhbi5QcmljZUVudHJ5UgVwcmljZRJTChRleHBlY3RlZE' + '1vbnRobHlQcmljZRgGIAMoCzIfLlBsYW4uRXhwZWN0ZWRNb250aGx5UHJpY2VFbnRyeVIUZXhw' + 'ZWN0ZWRNb250aGx5UHJpY2USNgoWdG90YWxDb3N0QmlsbGVkT25lVGltZRgHIAEoCVIWdG90YW' + 'xDb3N0QmlsbGVkT25lVGltZRIiCgxvbmVNb250aENvc3QYCCABKAlSDG9uZU1vbnRoQ29zdBIc' + 'Cgl0b3RhbENvc3QYCSABKAlSCXRvdGFsQ29zdBImCg5mb3JtYXR0ZWRCb251cxgKIAEoCVIOZm' + '9ybWF0dGVkQm9udXMSIAoLcmVuZXdhbFRleHQYCyABKAlSC3JlbmV3YWxUZXh0GjgKClByaWNl' + 'RW50cnkSEAoDa2V5GAEgASgJUgNrZXkSFAoFdmFsdWUYAiABKANSBXZhbHVlOgI4ARpHChlFeH' + 'BlY3RlZE1vbnRobHlQcmljZUVudHJ5EhAKA2tleRgBIAEoCVIDa2V5EhQKBXZhbHVlGAIgASgD' + 'UgV2YWx1ZToCOAE='); @$core.Deprecated('Use paymentProvidersDescriptor instead') const PaymentProviders$json = { diff --git a/protos_shared/vpn.proto b/protos_shared/vpn.proto index 2ed58a660..bade11566 100644 --- a/protos_shared/vpn.proto +++ b/protos_shared/vpn.proto @@ -38,11 +38,12 @@ message Plan { bool bestValue = 3; int64 usdPrice = 4; map price = 5; - string totalCostBilledOneTime = 6; - string oneMonthCost = 7; - string totalCost = 8; - string formattedBonus = 9; - string renewalText = 10; + map expectedMonthlyPrice = 6; + string totalCostBilledOneTime = 7; + string oneMonthCost = 8; + string totalCost = 9; + string formattedBonus = 10; + string renewalText = 11; } message PaymentProviders { From 4d10af0993bd47e72e940f3efadc903a1d5d34a2 Mon Sep 17 00:00:00 2001 From: atavism Date: Tue, 19 Mar 2024 07:45:42 -0700 Subject: [PATCH 18/49] update branch name --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index c4423595a..5ccdcd733 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -2,7 +2,7 @@ name: Publish releases on: push: - branches: [ atavism/windows-webview-updates ] + branches: [ atavism/locale-plans-updates ] tags: - '*' From a56f3ed7837534281fde224d98782d2d4fe23c05 Mon Sep 17 00:00:00 2001 From: atavism Date: Tue, 19 Mar 2024 08:08:28 -0700 Subject: [PATCH 19/49] update planfromJson --- lib/common/session_model.dart | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/lib/common/session_model.dart b/lib/common/session_model.dart index 870d542c3..03c2fd7ce 100644 --- a/lib/common/session_model.dart +++ b/lib/common/session_model.dart @@ -419,20 +419,21 @@ class SessionModel extends Model { } Plan planFromJson(Map item) { - final formatCurrency = new NumberFormat.simpleCurrency(); print("plan is $item"); + final formatCurrency = NumberFormat.simpleCurrency(); + final currency = formatCurrency.currencyName != null ? formatCurrency.currencyName!.toLowerCase() : "usd"; final res = jsonEncode(item); final plan = Plan.create()..mergeFromProto3Json(jsonDecode(res)); - final usdPrice = item["usdPrice"] as int; - if (plan.expectedMonthlyPrice["usd"] != null) { - var monthlyPrice = plan.expectedMonthlyPrice["usd"]!.toInt(); + final price = plan.price[currency] as Int64; + if (plan.expectedMonthlyPrice[currency] != null) { + var monthlyPrice = plan.expectedMonthlyPrice[currency]!.toInt(); plan.oneMonthCost = formatCurrency .format(monthlyPrice / 100) .toString(); } - plan.totalCost = formatCurrency.format(usdPrice / 100).toString(); + plan.totalCost = formatCurrency.format(price.toInt() / 100).toString(); plan.totalCostBilledOneTime = - formatCurrency.format(usdPrice / 100).toString() + + formatCurrency.format(price.toInt() / 100).toString() + ' ' + 'billed_one_time'.i18n; return plan; From 7c6f2da2af8e73df6e9dfb5e9c6ed9f0f2267984 Mon Sep 17 00:00:00 2001 From: atavism Date: Tue, 19 Mar 2024 09:03:50 -0700 Subject: [PATCH 20/49] use cannot_sign_in instead of cannot_login --- lib/account/report_issue.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/account/report_issue.dart b/lib/account/report_issue.dart index da6f8c43e..759aa985c 100644 --- a/lib/account/report_issue.dart +++ b/lib/account/report_issue.dart @@ -141,7 +141,7 @@ class _ReportIssueState extends State { items: [ 'cannot_access_blocked_sites'.i18n, 'cannot_complete_purchase'.i18n, - 'cannot_login'.i18n, + 'cannot_sign_in'.i18n, 'loading_spinner_spins_endlessly'.i18n, 'slow'.i18n, 'cannot_link_devices'.i18n, From 61235811ac8d417ea410883e51006f2cc2ac0e3e Mon Sep 17 00:00:00 2001 From: atavism Date: Tue, 19 Mar 2024 09:30:00 -0700 Subject: [PATCH 21/49] fix issue with blank pro account screen --- desktop/lib.go | 10 ++++++++++ lib/common/session_model.dart | 27 +++++++++++++++++++-------- lib/ffi.dart | 2 ++ lib/plans/checkout.dart | 29 +++++++++++++++++++++-------- liblantern.h | 1 + 5 files changed, 53 insertions(+), 16 deletions(-) diff --git a/desktop/lib.go b/desktop/lib.go index e2425c2ff..7a33e9894 100644 --- a/desktop/lib.go +++ b/desktop/lib.go @@ -174,6 +174,16 @@ func paymentMethods() *C.char { return C.CString(string(b)) } +//export devices +func devices() *C.char { + resp, err := proClient.UserData(userConfig()) + if err != nil { + return sendError(err) + } + b, _ := json.Marshal(resp.User.Devices) + return C.CString(string(b)) +} + //export userData func userData() *C.char { resp, err := proClient.UserData(userConfig()) diff --git a/lib/common/session_model.dart b/lib/common/session_model.dart index 03c2fd7ce..ff57dac0b 100644 --- a/lib/common/session_model.dart +++ b/lib/common/session_model.dart @@ -224,21 +224,33 @@ class SessionModel extends Model { return subscribedSingleValueBuilder('deviceid', builder: builder); } return ffiValueBuilder( - 'referral', + 'deviceid', ffiReferral, defaultValue: '', builder: builder, ); } + Device deviceFromJson(Map item) { + final res = jsonEncode(item); + return Device.create()..mergeFromProto3Json(jsonDecode(res)); + } + Widget devices(ValueWidgetBuilder builder) { - return subscribedSingleValueBuilder( - 'devices', + if (isMobile()) { + return subscribedSingleValueBuilder( + 'devices', + builder: builder, + deserialize: (Uint8List serialized) { + return Devices.fromBuffer(serialized); + }, + ); + } + return ffiValueBuilder( + '/devices/', + ffiDevices, builder: builder, - deserialize: (Uint8List serialized) { - return Devices.fromBuffer(serialized); - }, - ); + ); } Future setProxyAll(bool on) async { @@ -419,7 +431,6 @@ class SessionModel extends Model { } Plan planFromJson(Map item) { - print("plan is $item"); final formatCurrency = NumberFormat.simpleCurrency(); final currency = formatCurrency.currencyName != null ? formatCurrency.currencyName!.toLowerCase() : "usd"; final res = jsonEncode(item); diff --git a/lib/ffi.dart b/lib/ffi.dart index feea87f02..34e4f959b 100644 --- a/lib/ffi.dart +++ b/lib/ffi.dart @@ -40,6 +40,8 @@ Future ffiUserData() async { return User.create()..mergeFromProto3Json(jsonDecode(res)); } +Pointer ffiDevices() => _bindings.devices().cast(); + Pointer ffiDevelopmentMode() => _bindings.developmentMode().cast(); Pointer ffiAcceptedTermsVersion() => diff --git a/lib/plans/checkout.dart b/lib/plans/checkout.dart index e09dddcf8..f0f48d9dd 100644 --- a/lib/plans/checkout.dart +++ b/lib/plans/checkout.dart @@ -163,11 +163,11 @@ class _CheckoutState extends State if (isDesktop()) { String os = Platform.operatingSystem; final redirectUrl = await sessionModel.paymentRedirect( - widget.plan.id, - emailController.text, - "stripe", - os, - ); + widget.plan.id, + emailController.text, + "stripe", + os, + ); if (!Platform.isMacOS && !Platform.isWindows) { await context.pushRoute(AppWebview(url: redirectUrl)); } else { @@ -230,7 +230,8 @@ class _CheckoutState extends State } bool enableContinueButton() { - final isEmailValid = !emailController.value.text.isEmpty && emailFieldKey.currentState!.validate(); + final isEmailValid = !emailController.value.text.isEmpty && + emailFieldKey.currentState!.validate(); if (!isRefCodeFieldShowing || refCodeController.text.isEmpty) { return isEmailValid; } @@ -287,6 +288,10 @@ class _CheckoutState extends State autovalidateMode: widget.isPro ? AutovalidateMode.always : AutovalidateMode.disabled, + contentPadding: const EdgeInsetsDirectional.only( + top: 8.0, + bottom: 8.0, + ), label: 'email'.i18n, keyboardType: TextInputType.emailAddress, prefixIcon: const CAssetImage(path: ImagePaths.email), @@ -312,9 +317,15 @@ class _CheckoutState extends State child: CTextField( controller: refCodeController, autovalidateMode: AutovalidateMode.disabled, + contentPadding: + const EdgeInsetsDirectional.only( + top: 8.0, + bottom: 8.0, + ), onChanged: (text) { setState(() { - showContinueButton = enableContinueButton(); + showContinueButton = + enableContinueButton(); }); }, textCapitalization: @@ -371,7 +382,9 @@ class _CheckoutState extends State width: MediaQuery.of(context).size.width, child: Column( crossAxisAlignment: CrossAxisAlignment.start, - children: Platform.isAndroid ? paymentOptions(paymentMethods) : desktopPaymentOptions(), + children: Platform.isAndroid + ? paymentOptions(paymentMethods) + : desktopPaymentOptions(), ), ), // * Price summary, unused pro time disclaimer, Continue button diff --git a/liblantern.h b/liblantern.h index 0201ce494..2d828ea34 100644 --- a/liblantern.h +++ b/liblantern.h @@ -82,6 +82,7 @@ extern char* websocketAddr(); extern void setSelectTab(char* ttab); extern char* plans(); extern char* paymentMethods(); +extern char* devices(); extern char* userData(); extern char* serverInfo(); extern char* emailAddress(); From 084871dd01ac5ed73e7e470eff1bc41deec43bef Mon Sep 17 00:00:00 2001 From: atavism Date: Tue, 19 Mar 2024 10:14:15 -0700 Subject: [PATCH 22/49] Add devices and expiryDate --- desktop/lib.go | 17 ++++++++++++++++- lib/common/ffi_subscriber.dart | 2 +- lib/common/model.dart | 4 ++-- lib/common/session_model.dart | 28 +++++++++++++++------------- lib/ffi.dart | 2 ++ liblantern.h | 1 + 6 files changed, 37 insertions(+), 17 deletions(-) diff --git a/desktop/lib.go b/desktop/lib.go index 7a33e9894..aa930ed9a 100644 --- a/desktop/lib.go +++ b/desktop/lib.go @@ -184,6 +184,17 @@ func devices() *C.char { return C.CString(string(b)) } +//export expiryDate +func expiryDate() *C.char { + resp, err := proClient.UserData(userConfig()) + if err != nil { + return sendError(err) + } + tm := time.Unix(resp.User.Expiration, 0) + exp := tm.Format("01/02/2006") + return C.CString(string(exp)) +} + //export userData func userData() *C.char { resp, err := proClient.UserData(userConfig()) @@ -211,7 +222,11 @@ func serverInfo() *C.char { //export emailAddress func emailAddress() *C.char { - return C.CString("") + resp, err := proClient.UserData(userConfig()) + if err != nil { + return sendError(err) + } + return C.CString(resp.User.Email) } //export emailExists diff --git a/lib/common/ffi_subscriber.dart b/lib/common/ffi_subscriber.dart index 924f023e7..912160359 100644 --- a/lib/common/ffi_subscriber.dart +++ b/lib/common/ffi_subscriber.dart @@ -21,7 +21,7 @@ class FfiValueNotifier extends SubscribedNotifier { void Function(void Function(T?) setValue)? onChanges, WebSocketChannel? channel, T Function(Uint8List serialized)? deserialize, - T Function(Map json)? fromJsonModel, + T Function(dynamic json)? fromJsonModel, }) : super(defaultValue, removeFromCache) { if (onChanges != null) { onChanges((newValue) { diff --git a/lib/common/model.dart b/lib/common/model.dart index 647c63c8c..cc8e702cd 100644 --- a/lib/common/model.dart +++ b/lib/common/model.dart @@ -63,7 +63,7 @@ abstract class Model { void Function(void Function(T?) setValue)? onChanges, WebSocketChannel? channel, T Function(Uint8List serialized)? deserialize, - T Function(Map json)? fromJsonModel, + T Function(dynamic json)? fromJsonModel, }) { var notifier = ffiValueNotifier( ffiFunction, @@ -102,7 +102,7 @@ abstract class Model { void Function(void Function(T?) setValue)? onChanges, WebSocketChannel? channel, T Function(Uint8List serialized)? deserialize, - T Function(Map json)? fromJsonModel, + T Function(dynamic json)? fromJsonModel, }) { return FfiValueNotifier( ffiFunction, diff --git a/lib/common/session_model.dart b/lib/common/session_model.dart index ff57dac0b..6d4ba9ec4 100644 --- a/lib/common/session_model.dart +++ b/lib/common/session_model.dart @@ -1,9 +1,9 @@ import 'package:fixnum/fixnum.dart'; import 'package:intl/intl.dart'; import 'package:lantern/replica/common.dart'; - import 'common.dart'; import 'common_desktop.dart'; +import 'package:intl/intl.dart'; final sessionModel = SessionModel(); @@ -197,8 +197,8 @@ class SessionModel extends Model { ); } return ffiValueBuilder( - 'emailAddress', - ffiEmailAddress, + 'expirydatestr', + ffiExpiryDate, defaultValue: '', builder: builder, ); @@ -231,9 +231,12 @@ class SessionModel extends Model { ); } - Device deviceFromJson(Map item) { - final res = jsonEncode(item); - return Device.create()..mergeFromProto3Json(jsonDecode(res)); + Devices devicesFromJson(dynamic item) { + final items = item as List; + if (items.length == 0) return Devices.create(); + + final res = jsonEncode(items); + return Devices.create()..mergeFromProto3Json(jsonDecode(res)); } Widget devices(ValueWidgetBuilder builder) { @@ -247,8 +250,10 @@ class SessionModel extends Model { ); } return ffiValueBuilder( - '/devices/', + 'devices', ffiDevices, + fromJsonModel: devicesFromJson, + defaultValue: null, builder: builder, ); } @@ -563,12 +568,9 @@ class SessionModel extends Model { 'serverInfo', ffiServerInfo, builder: builder, - fromJsonModel: (Map json) { - var info = ServerInfo(); - info.city = json['city']; - info.country = json['country']; - info.countryCode = json['countryCode']; - return info; + fromJsonModel: (dynamic json) { + final res = jsonEncode(json); + return ServerInfo.create()..mergeFromProto3Json(jsonDecode(res)); }, deserialize: (Uint8List serialized) { return ServerInfo.fromBuffer(serialized); diff --git a/lib/ffi.dart b/lib/ffi.dart index 34e4f959b..2b32a3906 100644 --- a/lib/ffi.dart +++ b/lib/ffi.dart @@ -68,6 +68,8 @@ Pointer ffiPaymentMethods() => _bindings.paymentMethods().cast(); Pointer ffiDeviceLinkingCode() => _bindings.deviceLinkingCode().cast(); +Pointer ffiExpiryDate() => _bindings.expiryDate().cast(); + Pointer ffiSplitTunneling() => _bindings.splitTunneling().cast(); Pointer ffiChatMe() => _bindings.chatMe().cast(); diff --git a/liblantern.h b/liblantern.h index 2d828ea34..c6f00dedc 100644 --- a/liblantern.h +++ b/liblantern.h @@ -83,6 +83,7 @@ extern void setSelectTab(char* ttab); extern char* plans(); extern char* paymentMethods(); extern char* devices(); +extern char* expiryDate(); extern char* userData(); extern char* serverInfo(); extern char* emailAddress(); From 117001e0bb90e9064ca043d9ba53973754429023 Mon Sep 17 00:00:00 2001 From: atavism Date: Tue, 19 Mar 2024 10:14:35 -0700 Subject: [PATCH 23/49] update branch name --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 5ccdcd733..e6b514db2 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -2,7 +2,7 @@ name: Publish releases on: push: - branches: [ atavism/locale-plans-updates ] + branches: [ atavism/pro-account-screen ] tags: - '*' From 85582859b12b1fd99d6b7d7852da0d8d39a19763 Mon Sep 17 00:00:00 2001 From: atavism Date: Tue, 19 Mar 2024 10:17:01 -0700 Subject: [PATCH 24/49] Add report_description --- assets/locales/en.po | 3 +++ 1 file changed, 3 insertions(+) diff --git a/assets/locales/en.po b/assets/locales/en.po index 87c7bf504..2a639cf9a 100644 --- a/assets/locales/en.po +++ b/assets/locales/en.po @@ -248,6 +248,9 @@ msgstr "Lantern Pro Email" msgid "lantern_desktop" msgstr "Lantern Desktop" +msgid "report_description" +msgstr "Report Description" + msgid "report_issue" msgstr "Report Issue" From a1dba705104d315594908f14a8ebd3384243169e Mon Sep 17 00:00:00 2001 From: atavism Date: Tue, 19 Mar 2024 10:24:57 -0700 Subject: [PATCH 25/49] Add report_description --- assets/locales/en.po | 2 +- lib/account/report_issue.dart | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/assets/locales/en.po b/assets/locales/en.po index 2a639cf9a..59d51887c 100644 --- a/assets/locales/en.po +++ b/assets/locales/en.po @@ -249,7 +249,7 @@ msgid "lantern_desktop" msgstr "Lantern Desktop" msgid "report_description" -msgstr "Report Description" +msgstr "Issue Description" msgid "report_issue" msgstr "Report Issue" diff --git a/lib/account/report_issue.dart b/lib/account/report_issue.dart index 759aa985c..981a4a675 100644 --- a/lib/account/report_issue.dart +++ b/lib/account/report_issue.dart @@ -158,7 +158,7 @@ class _ReportIssueState extends State { Form( key: descFieldKey, child: CTextField( - tooltipMessage: AppKeys.reportDescription, + tooltipMessage: 'report_description'.i18n, controller: descController, contentPadding: const EdgeInsetsDirectional.all(8.0), label: '', From 2620791018721c612a3c2183ee7d53d9722b7156 Mon Sep 17 00:00:00 2001 From: atavism Date: Tue, 19 Mar 2024 18:42:58 -0700 Subject: [PATCH 26/49] save email address and pass currency to paymentRedirect --- .gitignore | 2 + Makefile | 1 + desktop/app/app.go | 8 +++ desktop/app/settings.go | 12 ++++ desktop/lib.go | 20 ++++-- lib/common/session_model.dart | 2 + lib/ffi.dart | 4 +- lib/plans/checkout.dart | 43 ++++++++----- liblantern.h | 116 ---------------------------------- 9 files changed, 66 insertions(+), 142 deletions(-) delete mode 100644 liblantern.h diff --git a/.gitignore b/.gitignore index 9f3e92038..8a9a164dd 100644 --- a/.gitignore +++ b/.gitignore @@ -80,6 +80,8 @@ android/app/libs/liblantern-all.aar lantern-installer.dmg liblantern.h liblantern.dylib +liblantern_arm64.dylib +liblantern_amd64.dylib lib/generated_bindings.dart *.env diff --git a/Makefile b/Makefile index 95eecb264..e85250bf4 100644 --- a/Makefile +++ b/Makefile @@ -564,6 +564,7 @@ darwin: darwin-arm64 ${DESKTOP_LIB_NAME}_amd64.dylib \ -output ${DARWIN_LIB_NAME} install_name_tool -id "@rpath/${DARWIN_LIB_NAME}" ${DARWIN_LIB_NAME} + rm ${DESKTOP_LIB_NAME}_arm64.h && mv ${DESKTOP_LIB_NAME}_amd64.h ${DESKTOP_LIB_NAME}.h $(INSTALLER_NAME).dmg: require-version require-appdmg require-retry require-magick @echo "Generating distribution package for darwin/amd64..." && \ diff --git a/desktop/app/app.go b/desktop/app/app.go index cd9feaec0..85e95708a 100644 --- a/desktop/app/app.go +++ b/desktop/app/app.go @@ -169,6 +169,14 @@ func (app *App) SetSelectedTab(selectedTab Tab) { app.selectedTab = selectedTab } +func (app *App) EmailAddress() string { + return app.settings.GetEmailAddress() +} + +func (app *App) SetEmailAddress(emailAddress string) { + app.settings.SetEmailAddress(emailAddress) +} + // Run starts the app. func (app *App) Run(isMain bool) { golog.OnFatal(app.exitOnFatal) diff --git a/desktop/app/settings.go b/desktop/app/settings.go index 1c8d724bd..a54030e7b 100644 --- a/desktop/app/settings.go +++ b/desktop/app/settings.go @@ -39,6 +39,7 @@ const ( SNLocalHTTPToken SettingName = "localHTTPToken" SNDeviceID SettingName = "deviceID" + SNEmailAddress SettingName = "emailAddress" SNUserID SettingName = "userID" SNUserToken SettingName = "userToken" SNMigratedDeviceIDForUserID SettingName = "migratedDeviceIDForUserID" @@ -80,6 +81,7 @@ var settingMeta = map[SettingName]struct { SNLocalHTTPToken: {stString, true, true}, // SNDeviceID: intentionally omit, to avoid setting it from UI + SNEmailAddress: {stString, true, true}, SNUserID: {stNumber, true, true}, SNUserToken: {stString, true, true}, SNMigratedDeviceIDForUserID: {stNumber, true, true}, @@ -485,6 +487,16 @@ func (s *Settings) GetAddr() string { return s.getString(SNAddr) } +// GetEmailAddress gets the email address of pro users. +func (s *Settings) GetEmailAddress() string { + return s.getString(SNEmailAddress) +} + +// SetEmailAddress locally stores the email address of a pro user +func (s *Settings) SetEmailAddress(email string) { + s.setVal(SNEmailAddress, email) +} + // GetUIAddr returns the address of the UI, stored across runs to avoid a // different port on each run, which breaks things like local storage in the UI. func (s *Settings) GetUIAddr() string { diff --git a/desktop/lib.go b/desktop/lib.go index aa930ed9a..20537b766 100644 --- a/desktop/lib.go +++ b/desktop/lib.go @@ -10,6 +10,7 @@ import ( "runtime" "runtime/debug" "strconv" + "strings" "syscall" "time" @@ -222,11 +223,16 @@ func serverInfo() *C.char { //export emailAddress func emailAddress() *C.char { - resp, err := proClient.UserData(userConfig()) - if err != nil { - return sendError(err) + emailAddress := a.EmailAddress() + if emailAddress == "" { + resp, err := proClient.UserData(userConfig()) + if err != nil { + return sendError(err) + } + emailAddress = resp.User.Email + a.SetEmailAddress(emailAddress) } - return C.CString(resp.User.Email) + return C.CString(emailAddress) } //export emailExists @@ -268,7 +274,7 @@ func lang() *C.char { lang := a.Settings().GetLanguage() if lang == "" { // Default language is English - lang = "en-US" + lang = defaultLocale } return C.CString(lang) } @@ -331,12 +337,12 @@ func deviceLinkingCode() *C.char { } //export paymentRedirect -func paymentRedirect(planID, provider, email, deviceName *C.char) *C.char { +func paymentRedirect(planID, currency, provider, email, deviceName *C.char) *C.char { country := a.Settings().GetCountry() resp, err := proClient.PaymentRedirect(userConfig(), &client.PaymentRedirectRequest{ Plan: C.GoString(planID), Provider: C.GoString(provider), - Currency: "USD", + Currency: strings.ToUpper(C.GoString(currency)), Email: C.GoString(email), DeviceName: C.GoString(deviceName), CountryCode: country, diff --git a/lib/common/session_model.dart b/lib/common/session_model.dart index 6d4ba9ec4..ce00ee594 100644 --- a/lib/common/session_model.dart +++ b/lib/common/session_model.dart @@ -653,12 +653,14 @@ class SessionModel extends Model { Future paymentRedirect( String planID, + String currency, String email, String provider, String deviceName, ) async { final resp = await ffiPaymentRedirect( planID.toNativeUtf8(), + currency.toNativeUtf8(), provider.toNativeUtf8(), email.toNativeUtf8(), deviceName.toNativeUtf8()); diff --git a/lib/ffi.dart b/lib/ffi.dart index 2b32a3906..e90dad594 100644 --- a/lib/ffi.dart +++ b/lib/ffi.dart @@ -85,8 +85,8 @@ Pointer ffiPurchase(planID, email, cardNumber, expDate, cvc) => Pointer ffiReportIssue(email, issueType, description) => _bindings.reportIssue(email, issueType, description).cast(); -Pointer ffiPaymentRedirect(planID, provider, email, deviceName) => - _bindings.paymentRedirect(planID, provider, email, deviceName).cast(); +Pointer ffiPaymentRedirect(planID, currency, provider, email, deviceName) => + _bindings.paymentRedirect(planID, currency, provider, email, deviceName).cast(); const String _libName = 'liblantern'; diff --git a/lib/plans/checkout.dart b/lib/plans/checkout.dart index f0f48d9dd..397504bec 100644 --- a/lib/plans/checkout.dart +++ b/lib/plans/checkout.dart @@ -5,6 +5,7 @@ import 'package:lantern/common/ui/app_webview.dart'; import 'package:lantern/plans/payment_provider.dart'; import 'package:lantern/plans/plan_details.dart'; import 'package:lantern/plans/utils.dart'; +import 'package:intl/intl.dart'; @RoutePage(name: 'Checkout') class Checkout extends StatefulWidget { @@ -156,28 +157,36 @@ class _CheckoutState extends State } } + Future openDesktopWebview() async { + String os = Platform.operatingSystem; + Locale locale = Localizations.localeOf(context); + final format = NumberFormat.simpleCurrency(locale: locale.toString()); + final currencyName = format.currencyName ?? "USD"; + final redirectUrl = await sessionModel.paymentRedirect( + widget.plan.id, + currencyName, + emailController.text, + "stripe", + os, + ); + if (!Platform.isMacOS && !Platform.isWindows) { + await context.pushRoute(AppWebview(url: redirectUrl)); + } else { + if (Platform.isWindows) { + await AppBrowser.openWindowsWebview(redirectUrl); + } else { + final browser = AppBrowser(onClose: checkProUser); + await browser.openMacWebview(redirectUrl); + } + } + } + Future resolvePaymentRoute() async { switch (selectedPaymentProvider!) { case Providers.stripe: // * Stripe selected if (isDesktop()) { - String os = Platform.operatingSystem; - final redirectUrl = await sessionModel.paymentRedirect( - widget.plan.id, - emailController.text, - "stripe", - os, - ); - if (!Platform.isMacOS && !Platform.isWindows) { - await context.pushRoute(AppWebview(url: redirectUrl)); - } else { - if (Platform.isWindows) { - await AppBrowser.openWindowsWebview(redirectUrl); - } else { - final browser = AppBrowser(onClose: checkProUser); - await browser.openMacWebview(redirectUrl); - } - } + await openDesktopWebview(); return; } await context.pushRoute( diff --git a/liblantern.h b/liblantern.h deleted file mode 100644 index c6f00dedc..000000000 --- a/liblantern.h +++ /dev/null @@ -1,116 +0,0 @@ -/* Code generated by cmd/cgo; DO NOT EDIT. */ - -/* package command-line-arguments */ - - -#line 1 "cgo-builtin-export-prolog" - -#include - -#ifndef GO_CGO_EXPORT_PROLOGUE_H -#define GO_CGO_EXPORT_PROLOGUE_H - -#ifndef GO_CGO_GOSTRING_TYPEDEF -typedef struct { const char *p; ptrdiff_t n; } _GoString_; -#endif - -#endif - -/* Start of preamble from import "C" comments. */ - - - - -/* End of preamble from import "C" comments. */ - - -/* Start of boilerplate cgo prologue. */ -#line 1 "cgo-gcc-export-header-prolog" - -#ifndef GO_CGO_PROLOGUE_H -#define GO_CGO_PROLOGUE_H - -typedef signed char GoInt8; -typedef unsigned char GoUint8; -typedef short GoInt16; -typedef unsigned short GoUint16; -typedef int GoInt32; -typedef unsigned int GoUint32; -typedef long long GoInt64; -typedef unsigned long long GoUint64; -typedef GoInt64 GoInt; -typedef GoUint64 GoUint; -typedef size_t GoUintptr; -typedef float GoFloat32; -typedef double GoFloat64; -#ifdef _MSC_VER -#include -typedef _Fcomplex GoComplex64; -typedef _Dcomplex GoComplex128; -#else -typedef float _Complex GoComplex64; -typedef double _Complex GoComplex128; -#endif - -/* - static assertion to make sure the file is being used on architecture - at least with matching size of GoInt. -*/ -typedef char _check_for_64_bit_pointer_matching_GoInt[sizeof(void*)==64/8 ? 1:-1]; - -#ifndef GO_CGO_GOSTRING_TYPEDEF -typedef _GoString_ GoString; -#endif -typedef void *GoMap; -typedef void *GoChan; -typedef struct { void *t; void *v; } GoInterface; -typedef struct { void *data; GoInt len; GoInt cap; } GoSlice; - -#endif - -/* End of boilerplate cgo prologue. */ - -#ifdef __cplusplus -extern "C" { -#endif - -extern void start(); -extern void sysProxyOn(); -extern void sysProxyOff(); -extern char* selectedTab(); -extern char* websocketAddr(); -extern void setSelectTab(char* ttab); -extern char* plans(); -extern char* paymentMethods(); -extern char* devices(); -extern char* expiryDate(); -extern char* userData(); -extern char* serverInfo(); -extern char* emailAddress(); -extern char* emailExists(char* email); -extern char* referral(); -extern char* chatEnabled(); -extern char* playVersion(); -extern char* storeVersion(); -extern char* lang(); -extern void setSelectLang(char* lang); -extern char* country(); -extern char* sdkVersion(); -extern char* vpnStatus(); -extern char* hasSucceedingProxy(); -extern char* onBoardingStatus(); -extern char* acceptedTermsVersion(); -extern char* proUser(); -extern char* deviceLinkingCode(); -extern char* paymentRedirect(char* planID, char* provider, char* email, char* deviceName); -extern char* developmentMode(); -extern char* splitTunneling(); -extern char* chatMe(); -extern char* replicaAddr(); -extern char* reportIssue(char* email, char* issueType, char* description); -extern char* checkUpdates(); -extern char* purchase(GoString planID, GoString email, GoString cardNumber, GoString expDate, GoString cvc); - -#ifdef __cplusplus -} -#endif From 322da455b3db0bc875afc8de5d7321b1698fe4d2 Mon Sep 17 00:00:00 2001 From: atavism Date: Tue, 19 Mar 2024 18:45:55 -0700 Subject: [PATCH 27/49] dart format --- lib/ffi.dart | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/lib/ffi.dart b/lib/ffi.dart index e90dad594..da59ebc59 100644 --- a/lib/ffi.dart +++ b/lib/ffi.dart @@ -49,7 +49,8 @@ Pointer ffiAcceptedTermsVersion() => Pointer ffiEmailAddress() => _bindings.emailAddress().cast(); -Pointer ffiEmailExists(email) => _bindings.emailExists(email).cast(); +Pointer ffiEmailExists(email) => + _bindings.emailExists(email).cast(); Pointer ffiReferral() => _bindings.referral().cast(); @@ -85,8 +86,11 @@ Pointer ffiPurchase(planID, email, cardNumber, expDate, cvc) => Pointer ffiReportIssue(email, issueType, description) => _bindings.reportIssue(email, issueType, description).cast(); -Pointer ffiPaymentRedirect(planID, currency, provider, email, deviceName) => - _bindings.paymentRedirect(planID, currency, provider, email, deviceName).cast(); +Pointer ffiPaymentRedirect( + planID, currency, provider, email, deviceName) => + _bindings + .paymentRedirect(planID, currency, provider, email, deviceName) + .cast(); const String _libName = 'liblantern'; From ef82f53a63ecd0b4307d76128dee7db74feb2cd5 Mon Sep 17 00:00:00 2001 From: atavism Date: Wed, 20 Mar 2024 09:00:19 -0700 Subject: [PATCH 28/49] use switch statement for desktop os opening webview --- lib/plans/checkout.dart | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/lib/plans/checkout.dart b/lib/plans/checkout.dart index 3fec4c230..35d0a449f 100644 --- a/lib/plans/checkout.dart +++ b/lib/plans/checkout.dart @@ -169,15 +169,16 @@ class _CheckoutState extends State "stripe", os, ); - if (!Platform.isMacOS && !Platform.isWindows) { - await context.pushRoute(AppWebview(url: redirectUrl)); - } else { - if (Platform.isWindows) { + switch (Platform.operatingSystem) { + case 'windows': await AppBrowser.openWindowsWebview(redirectUrl); - } else { + break; + case 'macos': final browser = AppBrowser(onClose: checkProUser); await browser.openMacWebview(redirectUrl); - } + break; + default: + await context.pushRoute(AppWebview(url: redirectUrl)); } } From 82909d8709a03ca17296041ed40843171f5c3d13 Mon Sep 17 00:00:00 2001 From: atavism Date: Wed, 20 Mar 2024 21:16:57 -0700 Subject: [PATCH 29/49] update user is pro check --- desktop/lib.go | 2 +- lib/common/common_desktop.dart | 4 ++++ lib/common/session_model.dart | 19 +++++++++++++++++++ lib/ffi.dart | 2 +- lib/home.dart | 2 +- lib/vpn/vpn_model.dart | 8 +++----- 6 files changed, 29 insertions(+), 8 deletions(-) diff --git a/desktop/lib.go b/desktop/lib.go index 20537b766..333b2e12f 100644 --- a/desktop/lib.go +++ b/desktop/lib.go @@ -410,7 +410,7 @@ func reportIssue(email, issueType, description *C.char) *C.char { uc := userConfig() subscriptionLevel := "free" - if a.IsPro() { + if isProUser, ok := a.IsProUser(); ok && isProUser { subscriptionLevel = "pro" } diff --git a/lib/common/common_desktop.dart b/lib/common/common_desktop.dart index 8a994c22f..fb3f88e96 100644 --- a/lib/common/common_desktop.dart +++ b/lib/common/common_desktop.dart @@ -3,6 +3,8 @@ import 'package:tray_manager/tray_manager.dart'; import 'package:lantern/ffi.dart'; import 'package:ffi/ffi.dart'; import 'dart:ffi'; +import 'package:web_socket_channel/io.dart'; +import 'package:web_socket_channel/web_socket_channel.dart'; export 'dart:convert'; export 'dart:ffi'; // For FFI @@ -10,6 +12,8 @@ export 'dart:ffi'; // For FFI export 'package:ffi/ffi.dart'; export 'package:ffi/src/utf8.dart'; export 'package:lantern/ffi.dart'; +export 'package:web_socket_channel/io.dart'; +export 'package:web_socket_channel/web_socket_channel.dart'; // Include resources here just for desktop compatibility or use diff --git a/lib/common/session_model.dart b/lib/common/session_model.dart index ce00ee594..8a5791ab8 100644 --- a/lib/common/session_model.dart +++ b/lib/common/session_model.dart @@ -77,9 +77,28 @@ class SessionModel extends Model { if (isMobile()) { return subscribedSingleValueBuilder('prouser', builder: builder); } + final channel = WebSocketChannel.connect( + Uri.parse("ws://" + websocketAddr() + '/data'), + ); + return ffiValueBuilder( 'prouser', defaultValue: false, + channel: channel, + onChanges: (setValue) { + /// Listen for all incoming data + channel.stream.listen( + (data) { + final parsedJson = json.decode(data); + if (parsedJson["type"] == "pro") { + final userStatus = parsedJson["message"]["userStatus"]; + final isProUser = userStatus != null && userStatus.toString() == "active"; + setValue(isProUser); + } + }, + onError: (error) => print(error), + ); + }, ffiProUser, builder: builder, ); diff --git a/lib/ffi.dart b/lib/ffi.dart index da59ebc59..692b93aa2 100644 --- a/lib/ffi.dart +++ b/lib/ffi.dart @@ -16,7 +16,7 @@ void setLang(lang) => _bindings.setSelectLang(lang); String websocketAddr() => _bindings.websocketAddr().cast().toDartString(); -Pointer vpnStatus() => _bindings.vpnStatus().cast(); +Pointer ffiVpnStatus() => _bindings.vpnStatus().cast(); Pointer ffiSelectedTab() => _bindings.selectedTab().cast(); diff --git a/lib/home.dart b/lib/home.dart index 8b1fa8050..fb7a3aef3 100644 --- a/lib/home.dart +++ b/lib/home.dart @@ -133,7 +133,7 @@ class _HomePageState extends State with TrayListener, WindowListener { case 'exit': SystemChannels.platform.invokeMethod('SystemNavigator.pop'); case 'status': - bool isConnected = await vpnStatus() == "connected"; + bool isConnected = await ffiVpnStatus() == "connected"; if (isConnected) { sysProxyOff(); await setupMenu(false); diff --git a/lib/vpn/vpn_model.dart b/lib/vpn/vpn_model.dart index 9f59b7c91..71475312a 100644 --- a/lib/vpn/vpn_model.dart +++ b/lib/vpn/vpn_model.dart @@ -1,7 +1,5 @@ import 'package:lantern/vpn/vpn.dart'; -import 'package:lantern/common/common_desktop.dart' as desktop; -import 'package:web_socket_channel/io.dart'; -import 'package:web_socket_channel/web_socket_channel.dart'; +import 'package:lantern/common/common_desktop.dart'; final vpnModel = VpnModel(); @@ -30,7 +28,7 @@ class VpnModel extends Model { ); } final channel = WebSocketChannel.connect( - Uri.parse("ws://" + desktop.websocketAddr() + '/data'), + Uri.parse("ws://" + websocketAddr() + '/data'), ); return ffiValueBuilder( 'vpnStatus', @@ -50,7 +48,7 @@ class VpnModel extends Model { onError: (error) => print(error), ); }, - desktop.vpnStatus, + ffiVpnStatus, builder: builder, ); } From 961b1edc8177d22997891505dd6973f770cf0f85 Mon Sep 17 00:00:00 2001 From: atavism Date: Thu, 21 Mar 2024 12:32:57 -0700 Subject: [PATCH 30/49] add websocket service --- lib/common/common_desktop.dart | 3 ++- lib/common/session_model.dart | 17 ++++++---------- lib/custom_bottom_bar.dart | 2 ++ lib/home.dart | 2 +- lib/vpn/vpn_model.dart | 36 ++++++++++------------------------ lib/vpn/vpn_status.dart | 3 ++- lib/vpn/vpn_tab.dart | 29 ++++++++++++++++++++++++--- 7 files changed, 49 insertions(+), 43 deletions(-) diff --git a/lib/common/common_desktop.dart b/lib/common/common_desktop.dart index fb3f88e96..c90b2dd2c 100644 --- a/lib/common/common_desktop.dart +++ b/lib/common/common_desktop.dart @@ -3,15 +3,16 @@ import 'package:tray_manager/tray_manager.dart'; import 'package:lantern/ffi.dart'; import 'package:ffi/ffi.dart'; import 'dart:ffi'; +import 'package:lantern/common/ui/websocket.dart'; import 'package:web_socket_channel/io.dart'; import 'package:web_socket_channel/web_socket_channel.dart'; export 'dart:convert'; export 'dart:ffi'; // For FFI - export 'package:ffi/ffi.dart'; export 'package:ffi/src/utf8.dart'; export 'package:lantern/ffi.dart'; +export 'package:lantern/common/ui/websocket.dart'; export 'package:web_socket_channel/io.dart'; export 'package:web_socket_channel/web_socket_channel.dart'; diff --git a/lib/common/session_model.dart b/lib/common/session_model.dart index 8a5791ab8..1baf762f4 100644 --- a/lib/common/session_model.dart +++ b/lib/common/session_model.dart @@ -73,25 +73,20 @@ class SessionModel extends Model { late ValueNotifier proxyAvailable; late ValueNotifier country; - Widget proUser(ValueWidgetBuilder builder) { + Widget proUser(ValueWidgetBuilder builder, [WebsocketImpl? websocket]) { if (isMobile()) { return subscribedSingleValueBuilder('prouser', builder: builder); } - final channel = WebSocketChannel.connect( - Uri.parse("ws://" + websocketAddr() + '/data'), - ); - return ffiValueBuilder( 'prouser', defaultValue: false, - channel: channel, onChanges: (setValue) { + if (websocket == null) return; /// Listen for all incoming data - channel.stream.listen( - (data) { - final parsedJson = json.decode(data); - if (parsedJson["type"] == "pro") { - final userStatus = parsedJson["message"]["userStatus"]; + websocket.messageStream.listen( + (json) { + if (json["type"] == "pro") { + final userStatus = json["message"]["userStatus"]; final isProUser = userStatus != null && userStatus.toString() == "active"; setValue(isProUser); } diff --git a/lib/custom_bottom_bar.dart b/lib/custom_bottom_bar.dart index 34c114c6a..d0642781f 100644 --- a/lib/custom_bottom_bar.dart +++ b/lib/custom_bottom_bar.dart @@ -1,3 +1,4 @@ +import 'package:lantern/common/common_desktop.dart'; import 'package:lantern/custom_bottom_item.dart'; import 'package:lantern/messaging/messaging.dart'; import 'package:lantern/replica/common.dart'; @@ -177,6 +178,7 @@ class CustomBottomBar extends StatelessWidget { : indicatorRed, ), ), + WebsocketImpl(), ), ), label: '', diff --git a/lib/home.dart b/lib/home.dart index fb7a3aef3..17cc23653 100644 --- a/lib/home.dart +++ b/lib/home.dart @@ -259,7 +259,7 @@ class _HomePageState extends State with TrayListener, WindowListener { ? Chats() : Welcome(); case TAB_VPN: - return const VPNTab(); + return VPNTab(); case TAB_REPLICA: return ReplicaTab(); case TAB_ACCOUNT: diff --git a/lib/vpn/vpn_model.dart b/lib/vpn/vpn_model.dart index 71475312a..fdde3e6aa 100644 --- a/lib/vpn/vpn_model.dart +++ b/lib/vpn/vpn_model.dart @@ -20,31 +20,27 @@ class VpnModel extends Model { }); } - Widget vpnStatus(ValueWidgetBuilder builder) { + Future handleWebSocketMessage(Map data, Function setValue) async { + if (data["type"] != "vpnstatus") return; + final updated = data["message"]["connected"]; + final isConnected = updated != null && updated.toString() == "true"; + setValue(isConnected ? "connected" : "disconnected"); + } + + Widget vpnStatus(ValueWidgetBuilder builder, [WebsocketImpl? websocket]) { if (isMobile()) { return subscribedSingleValueBuilder( '/vpn_status', builder: builder, ); } - final channel = WebSocketChannel.connect( - Uri.parse("ws://" + websocketAddr() + '/data'), - ); return ffiValueBuilder( 'vpnStatus', defaultValue: '', - channel: channel, onChanges: (setValue) { /// Listen for all incoming data - channel.stream.listen( - (data) { - final parsedJson = json.decode(data); - if (parsedJson["type"] == "vpnstatus") { - final updated = parsedJson["message"]["connected"]; - final isConnected = updated != null && updated.toString() == "true"; - setValue(isConnected ? "connected" : "disconnected"); - } - }, + websocket?.messageStream.listen( + (json) => handleWebSocketMessage(json, setValue), onError: (error) => print(error), ); }, @@ -58,18 +54,6 @@ class VpnModel extends Model { return vpnStatus == 'connected'; } - //This method has moved to Session model - // Due to go model changes - // Widget serverInfo(ValueWidgetBuilder builder) { - // return subscribedSingleValueBuilder( - // '/server_info', - // builder: builder, - // deserialize: (Uint8List serialized) { - // return ServerInfo.fromBuffer(serialized); - // }, - // ); - // } - Widget bandwidth(ValueWidgetBuilder builder) { return subscribedSingleValueBuilder( '/bandwidth', diff --git a/lib/vpn/vpn_status.dart b/lib/vpn/vpn_status.dart index 5f843fe37..73632a44e 100644 --- a/lib/vpn/vpn_status.dart +++ b/lib/vpn/vpn_status.dart @@ -1,4 +1,5 @@ import 'package:lantern/vpn/vpn.dart'; +import 'package:lantern/common/common_desktop.dart'; class VPNStatus extends StatelessWidget { @override @@ -43,6 +44,6 @@ class VPNStatus extends StatelessWidget { ), ], ); - }); + }, WebsocketImpl()); } } diff --git a/lib/vpn/vpn_tab.dart b/lib/vpn/vpn_tab.dart index 140839577..4ea9142f1 100644 --- a/lib/vpn/vpn_tab.dart +++ b/lib/vpn/vpn_tab.dart @@ -1,4 +1,5 @@ import 'package:lantern/account/split_tunneling.dart'; +import 'package:lantern/common/common_desktop.dart'; import 'package:lantern/messaging/messaging.dart'; import 'package:lantern/vpn/vpn.dart'; import 'vpn_bandwidth.dart'; @@ -7,8 +8,30 @@ import 'vpn_server_location.dart'; import 'vpn_status.dart'; import 'vpn_switch.dart'; -class VPNTab extends StatelessWidget { - const VPNTab({Key? key}) : super(key: key); +class VPNTab extends StatefulWidget { + VPNTab({Key? key}) : super(key: key); + + @override + _VPNTabState createState() => _VPNTabState(); +} + +class _VPNTabState extends State { + WebsocketImpl? websocket; + + @override + void initState() { + if (isDesktop()) { + setState(() => websocket = WebsocketImpl()); + websocket?.connect(); + } + super.initState(); + } + + @override + void dispose() { + websocket?.close(); + super.dispose(); + } @override Widget build(BuildContext context) { @@ -56,6 +79,6 @@ class VPNTab extends StatelessWidget { ], ), ); - }); + }, websocket); } } From d5d72fc2ab6f40fe80aa1376cf88e3e4733d370d Mon Sep 17 00:00:00 2001 From: atavism Date: Thu, 21 Mar 2024 12:33:03 -0700 Subject: [PATCH 31/49] add websocket service --- lib/common/ui/websocket.dart | 79 ++++++++++++++++++++++++++++++++++++ 1 file changed, 79 insertions(+) create mode 100644 lib/common/ui/websocket.dart diff --git a/lib/common/ui/websocket.dart b/lib/common/ui/websocket.dart new file mode 100644 index 000000000..710784608 --- /dev/null +++ b/lib/common/ui/websocket.dart @@ -0,0 +1,79 @@ +import 'dart:async'; +import 'dart:convert'; +import 'package:flutter/foundation.dart'; +import 'package:lantern/common/common_desktop.dart'; + +abstract class WebsocketService { + Stream> get messageStream; + Future connect(); + Future close(); + void send(String event, Map data); +} + +class WebsocketImpl implements WebsocketService { + + static final WebsocketImpl _websocket = WebsocketImpl._internal(); + + factory WebsocketImpl() { + return _websocket; + } + + WebsocketImpl._internal(); + + StreamController> streamController = StreamController>.broadcast(); + WebSocketChannel? _channel; + bool _isConnected = false; + + @override + Stream> get messageStream => streamController.stream; + + @override + Future connect() async { + _channel = WebSocketChannel.connect( + Uri.parse("ws://" + websocketAddr() + '/data'), + ); + + _channel!.stream.listen( + (message) async { + final Map json = jsonDecode(message ?? {}); + streamController.add(json); + }, + onDone: () async { + await close(); + }, + onError: (error) => _handleError(error), + ); + + _isConnected = true; + + print("Websocket connected"); + } + + void _handleError(dynamic error) { + if (error is WebSocketChannelException) { + close(); + return; + } + + print('Websocket error: $error'); + } + + @override + Future close() async { + if (_channel != null && _channel?.sink != null) { + await _channel?.sink.close(); + } + _isConnected = false; + } + + @override + void send(String event, Map data) { + if (_channel != null && _channel?.sink != null) { + data['type'] = event; + + _channel!.sink.add(jsonEncode(data)); + } else { + print('Websocket is not connected'); + } + } +} \ No newline at end of file From c00ced486163672888eb1a1351df9ac5f7fa5f14 Mon Sep 17 00:00:00 2001 From: atavism Date: Thu, 21 Mar 2024 12:41:27 -0700 Subject: [PATCH 32/49] clean-ups, add comments --- lib/common/ui/websocket.dart | 14 ++++++++------ lib/vpn/vpn_tab.dart | 2 +- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/lib/common/ui/websocket.dart b/lib/common/ui/websocket.dart index 710784608..059208cfc 100644 --- a/lib/common/ui/websocket.dart +++ b/lib/common/ui/websocket.dart @@ -5,7 +5,7 @@ import 'package:lantern/common/common_desktop.dart'; abstract class WebsocketService { Stream> get messageStream; - Future connect(); + Future connect(Uri uri); Future close(); void send(String event, Map data); } @@ -20,18 +20,19 @@ class WebsocketImpl implements WebsocketService { WebsocketImpl._internal(); + // streamController is used to control the websocket stream channel StreamController> streamController = StreamController>.broadcast(); + // _channel is a stream channel that communicates over a WebSocket. WebSocketChannel? _channel; bool _isConnected = false; @override Stream> get messageStream => streamController.stream; + // Creates a new Websocket connection @override - Future connect() async { - _channel = WebSocketChannel.connect( - Uri.parse("ws://" + websocketAddr() + '/data'), - ); + Future connect(Uri uri) async { + _channel = WebSocketChannel.connect(uri); _channel!.stream.listen( (message) async { @@ -58,6 +59,7 @@ class WebsocketImpl implements WebsocketService { print('Websocket error: $error'); } + // Close sink for sending values and websocket connection @override Future close() async { if (_channel != null && _channel?.sink != null) { @@ -66,11 +68,11 @@ class WebsocketImpl implements WebsocketService { _isConnected = false; } + // Send data over a websocket channel @override void send(String event, Map data) { if (_channel != null && _channel?.sink != null) { data['type'] = event; - _channel!.sink.add(jsonEncode(data)); } else { print('Websocket is not connected'); diff --git a/lib/vpn/vpn_tab.dart b/lib/vpn/vpn_tab.dart index 4ea9142f1..8acf3e39b 100644 --- a/lib/vpn/vpn_tab.dart +++ b/lib/vpn/vpn_tab.dart @@ -22,7 +22,7 @@ class _VPNTabState extends State { void initState() { if (isDesktop()) { setState(() => websocket = WebsocketImpl()); - websocket?.connect(); + websocket?.connect(Uri.parse("ws://" + websocketAddr() + '/data')); } super.initState(); } From 7d43b8f026ad9705dd25bc58aeee10f360e217ab Mon Sep 17 00:00:00 2001 From: atavism Date: Thu, 21 Mar 2024 12:44:16 -0700 Subject: [PATCH 33/49] apply formatting --- lib/common/ui/websocket.dart | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/common/ui/websocket.dart b/lib/common/ui/websocket.dart index 059208cfc..b61824273 100644 --- a/lib/common/ui/websocket.dart +++ b/lib/common/ui/websocket.dart @@ -11,7 +11,6 @@ abstract class WebsocketService { } class WebsocketImpl implements WebsocketService { - static final WebsocketImpl _websocket = WebsocketImpl._internal(); factory WebsocketImpl() { @@ -21,7 +20,8 @@ class WebsocketImpl implements WebsocketService { WebsocketImpl._internal(); // streamController is used to control the websocket stream channel - StreamController> streamController = StreamController>.broadcast(); + StreamController> streamController = + StreamController>.broadcast(); // _channel is a stream channel that communicates over a WebSocket. WebSocketChannel? _channel; bool _isConnected = false; @@ -47,7 +47,7 @@ class WebsocketImpl implements WebsocketService { _isConnected = true; - print("Websocket connected"); + print("Websocket connected"); } void _handleError(dynamic error) { @@ -78,4 +78,4 @@ class WebsocketImpl implements WebsocketService { print('Websocket is not connected'); } } -} \ No newline at end of file +} From c901d474be736c1937983f1b61bd566b55d21270 Mon Sep 17 00:00:00 2001 From: Jigar-f Date: Fri, 22 Mar 2024 18:22:44 +0530 Subject: [PATCH 34/49] Added socket to account menu and --- lib/account/account.dart | 129 +++++++++++++++++++++++---------------- lib/vpn/vpn_tab.dart | 22 ++++--- 2 files changed, 92 insertions(+), 59 deletions(-) diff --git a/lib/account/account.dart b/lib/account/account.dart index 6431f3d90..d5a9475d1 100644 --- a/lib/account/account.dart +++ b/lib/account/account.dart @@ -1,9 +1,35 @@ import 'package:lantern/common/common.dart'; import 'package:lantern/messaging/messaging_model.dart'; +import '../common/common_desktop.dart'; + @RoutePage(name: 'Account') -class AccountMenu extends StatelessWidget { - const AccountMenu({Key? key}) : super(key: key); +class AccountMenu extends StatefulWidget { + const AccountMenu({Key? key}) : super(key: key); + + @override + State createState() => _AccountMenuState(); +} + +class _AccountMenuState extends State { + WebsocketImpl websocket = WebsocketImpl(); + + @override + void initState() { + if (isDesktop()) { + connectDesktopSocket(); + } + super.initState(); + } + + void connectDesktopSocket() { + try { + websocket.connect(Uri.parse('ws://${websocketAddr()}/data')); + appLogger.i("Socket connected"); + } catch (e) { + appLogger.e('Error connecting to socket: $e'); + } + } Future authorizeDeviceForPro(BuildContext context) async => await context.pushRoute(AuthorizePro()); @@ -25,30 +51,30 @@ class AccountMenu extends StatelessWidget { List freeItems(BuildContext context, SessionModel sessionModel) { return [ - if (Platform.isAndroid) messagingModel.getOnBoardingStatus( - (context, hasBeenOnboarded, child) => hasBeenOnboarded == true - ? messagingModel.getCopiedRecoveryStatus( - ( - BuildContext context, - bool hasCopiedRecoveryKey, - Widget? child, - ) => - ListItemFactory.settingsItem( - icon: ImagePaths.account, - content: 'account_management'.i18n, - onTap: () async => - await context.pushRoute(AccountManagement(isPro: false)), - trailingArray: [ - if (!hasCopiedRecoveryKey) - const CAssetImage( - path: ImagePaths.badge, - ), - ], - ), - ) - : const SizedBox(), - ), if (Platform.isAndroid) + messagingModel.getOnBoardingStatus( + (context, hasBeenOnboarded, child) => hasBeenOnboarded == true + ? messagingModel.getCopiedRecoveryStatus( + ( + BuildContext context, + bool hasCopiedRecoveryKey, + Widget? child, + ) => + ListItemFactory.settingsItem( + icon: ImagePaths.account, + content: 'account_management'.i18n, + onTap: () async => await context + .pushRoute(AccountManagement(isPro: false)), + trailingArray: [ + if (!hasCopiedRecoveryKey) + const CAssetImage( + path: ImagePaths.badge, + ), + ], + ), + ) + : const SizedBox(), + ), ListItemFactory.settingsItem( key: AppKeys.upgrade_lantern_pro, icon: ImagePaths.pro_icon_black, @@ -79,23 +105,21 @@ class AccountMenu extends StatelessWidget { return [ messagingModel.getOnBoardingStatus( (context, hasBeenOnboarded, child) => - messagingModel.getCopiedRecoveryStatus( - (BuildContext context, bool hasCopiedRecoveryKey, Widget? child) => - ListItemFactory.settingsItem( - key: AppKeys.account_management, - icon: ImagePaths.account, - content: 'account_management'.i18n, - onTap: () async => await context - .pushRoute(AccountManagement(isPro: true)), - trailingArray: [ - if (!hasCopiedRecoveryKey && hasBeenOnboarded == true) - const CAssetImage( - path: ImagePaths.badge, - ), - ], - ) - - ), + messagingModel.getCopiedRecoveryStatus((BuildContext context, + bool hasCopiedRecoveryKey, Widget? child) => + ListItemFactory.settingsItem( + key: AppKeys.account_management, + icon: ImagePaths.account, + content: 'account_management'.i18n, + onTap: () async => + await context.pushRoute(AccountManagement(isPro: true)), + trailingArray: [ + if (!hasCopiedRecoveryKey && hasBeenOnboarded == true) + const CAssetImage( + path: ImagePaths.badge, + ), + ], + )), ), ListItemFactory.settingsItem( icon: ImagePaths.star, @@ -116,13 +140,14 @@ class AccountMenu extends StatelessWidget { List commonItems(BuildContext context) { return [ - if (Platform.isAndroid) ListItemFactory.settingsItem( - icon: ImagePaths.desktop, - content: 'desktop_version'.i18n, - onTap: () { - openDesktopVersion(context); - }, - ), + if (Platform.isAndroid) + ListItemFactory.settingsItem( + icon: ImagePaths.desktop, + content: 'desktop_version'.i18n, + onTap: () { + openDesktopVersion(context); + }, + ), ListItemFactory.settingsItem( key: AppKeys.support, icon: ImagePaths.support, @@ -146,14 +171,14 @@ class AccountMenu extends StatelessWidget { return BaseScreen( title: 'Account'.i18n, automaticallyImplyLeading: false, - body: sessionModel - .proUser((BuildContext sessionContext, bool proUser, Widget? child) { + body: sessionModel.proUser( + (BuildContext sessionContext, bool proUser, Widget? child) { return ListView( children: proUser ? proItems(sessionContext) : freeItems(sessionContext, sessionModel), ); - }), + }, websocket), ); } } diff --git a/lib/vpn/vpn_tab.dart b/lib/vpn/vpn_tab.dart index 8acf3e39b..aa09ee73c 100644 --- a/lib/vpn/vpn_tab.dart +++ b/lib/vpn/vpn_tab.dart @@ -9,34 +9,42 @@ import 'vpn_status.dart'; import 'vpn_switch.dart'; class VPNTab extends StatefulWidget { - VPNTab({Key? key}) : super(key: key); + const VPNTab({Key? key}) : super(key: key); @override _VPNTabState createState() => _VPNTabState(); } class _VPNTabState extends State { - WebsocketImpl? websocket; + WebsocketImpl websocket = WebsocketImpl(); @override void initState() { if (isDesktop()) { - setState(() => websocket = WebsocketImpl()); - websocket?.connect(Uri.parse("ws://" + websocketAddr() + '/data')); + connectDesktopSocket(); } super.initState(); } + void connectDesktopSocket() { + try { + websocket.connect(Uri.parse('ws://${websocketAddr()}/data')); + appLogger.i("Socket connected"); + } catch (e) { + appLogger.e('Error connecting to socket: $e'); + } + } + @override void dispose() { - websocket?.close(); + websocket.close(); super.dispose(); } @override Widget build(BuildContext context) { - return sessionModel - .proUser((BuildContext context, bool proUser, Widget? child) { + return sessionModel.proUser( + (BuildContext context, bool proUser, Widget? child) { return BaseScreen( title: SvgPicture.asset( proUser ? ImagePaths.pro_logo : ImagePaths.free_logo, From 5f34ebc224955316c9a79d829c65922f75adac55 Mon Sep 17 00:00:00 2001 From: atavism Date: Sun, 24 Mar 2024 12:01:12 -0700 Subject: [PATCH 35/49] updates to use didChangeAppLifecycleState --- lib/app.dart | 23 ++++++++++- lib/common/session_model.dart | 23 +++++------ lib/common/ui/websocket.dart | 75 ++++++++++++++++++++++++++++------- lib/custom_bottom_bar.dart | 2 - lib/ffi.dart | 3 -- lib/vpn/vpn_model.dart | 7 +++- lib/vpn/vpn_status.dart | 2 +- lib/vpn/vpn_tab.dart | 27 +------------ 8 files changed, 100 insertions(+), 62 deletions(-) diff --git a/lib/app.dart b/lib/app.dart index 294362c35..831ab45bb 100644 --- a/lib/app.dart +++ b/lib/app.dart @@ -1,5 +1,6 @@ import 'package:app_links/app_links.dart'; import 'package:flutter/scheduler.dart'; +import 'package:lantern/common/common_desktop.dart'; import 'package:lantern/core/router/router.dart'; import 'package:lantern/messaging/messaging.dart'; @@ -38,18 +39,36 @@ class LanternApp extends StatefulWidget { State createState() => _LanternAppState(); } -class _LanternAppState extends State { +class _LanternAppState extends State with WidgetsBindingObserver { final translations = Localization.ensureInitialized(); late final AnimationController networkWarningAnimationController; late final Animation networkWarningAnimation; @override void initState() { + super.initState(); + WidgetsBinding.instance!.addObserver(this); _animateNetworkWarning(); WidgetsBinding.instance.addPostFrameCallback((_) { initDeepLinks(); }); - super.initState(); + } + + @override + void dispose() { + WidgetsBinding.instance!.removeObserver(this); + super.dispose(); + } + + @override + void didChangeAppLifecycleState(AppLifecycleState state) async { + if (state == AppLifecycleState.resumed) { + print('App resumed'); + await WebsocketImpl.instance()!.connect(Uri.parse("ws://" + websocketAddr() + '/data')); + } else if (state == AppLifecycleState.paused) { + print('App paused'); + await WebsocketImpl.instance()!.close(); + } } void _animateNetworkWarning() { diff --git a/lib/common/session_model.dart b/lib/common/session_model.dart index 1baf762f4..dd98bb95b 100644 --- a/lib/common/session_model.dart +++ b/lib/common/session_model.dart @@ -73,7 +73,7 @@ class SessionModel extends Model { late ValueNotifier proxyAvailable; late ValueNotifier country; - Widget proUser(ValueWidgetBuilder builder, [WebsocketImpl? websocket]) { + Widget proUser(ValueWidgetBuilder builder) { if (isMobile()) { return subscribedSingleValueBuilder('prouser', builder: builder); } @@ -81,6 +81,7 @@ class SessionModel extends Model { 'prouser', defaultValue: false, onChanges: (setValue) { + final websocket = WebsocketImpl.instance(); if (websocket == null) return; /// Listen for all incoming data websocket.messageStream.listen( @@ -688,18 +689,14 @@ class SessionModel extends Model { String expDate, String cvc, ) async { - if (isMobile()) { - return methodChannel - .invokeMethod('submitStripePayment', { - 'planID': planID, - 'email': email, - 'cardNumber': cardNumber, - 'expDate': expDate, - 'cvc': cvc, - }).then((value) => value as String); - } - await ffiPurchase(planID.toNativeUtf8(), email.toNativeUtf8(), - cardNumber.toNativeUtf8(), expDate.toNativeUtf8(), cvc.toNativeUtf8()); + return methodChannel + .invokeMethod('submitStripePayment', { + 'planID': planID, + 'email': email, + 'cardNumber': cardNumber, + 'expDate': expDate, + 'cvc': cvc, + }).then((value) => value as String); } Future submitFreekassa( diff --git a/lib/common/ui/websocket.dart b/lib/common/ui/websocket.dart index b61824273..9579cb9b0 100644 --- a/lib/common/ui/websocket.dart +++ b/lib/common/ui/websocket.dart @@ -11,20 +11,28 @@ abstract class WebsocketService { } class WebsocketImpl implements WebsocketService { - static final WebsocketImpl _websocket = WebsocketImpl._internal(); + static WebsocketImpl? _websocket; + WebsocketImpl._internal(); + + num _heartTimes = 10000; + num _rcMaxCount = 600; + num _rcTimes = 0; + Timer? _rcTimer; - factory WebsocketImpl() { + static WebsocketImpl? instance() { + if (_websocket == null) { + _websocket = WebsocketImpl._internal(); + } return _websocket; } - WebsocketImpl._internal(); - // streamController is used to control the websocket stream channel StreamController> streamController = StreamController>.broadcast(); // _channel is a stream channel that communicates over a WebSocket. WebSocketChannel? _channel; bool _isConnected = false; + bool _handleClose = false; @override Stream> get messageStream => streamController.stream; @@ -32,21 +40,18 @@ class WebsocketImpl implements WebsocketService { // Creates a new Websocket connection @override Future connect(Uri uri) async { + _handleClose = false; + await close(); + print('Opening websocket connection'); + _channel = WebSocketChannel.connect(uri); _channel!.stream.listen( - (message) async { - final Map json = jsonDecode(message ?? {}); - streamController.add(json); - }, - onDone: () async { - await close(); - }, + (message) => _onMessage(message), + onDone: () => _handleDone(uri), onError: (error) => _handleError(error), ); - _isConnected = true; - print("Websocket connected"); } @@ -59,13 +64,55 @@ class WebsocketImpl implements WebsocketService { print('Websocket error: $error'); } + void _handleDone(Uri uri) { + print("_handleDone called"); + if (!_handleClose) { + reconnect(uri); + } + } + // Close sink for sending values and websocket connection @override Future close() async { + _handleClose = true; if (_channel != null && _channel?.sink != null) { + print('Closing websocket'); await _channel?.sink.close(); + _isConnected = false; + } + } + + Future _onMessage(message) async { + if (!_isConnected) { + _isConnected = true; + + _rcTimes = 0; + _rcTimer?.cancel(); + _rcTimer = null; + } + + final Map json = jsonDecode(message ?? {}); + streamController.add(json); + } + + Future reconnect(Uri uri) async { + if (_rcTimes < _rcMaxCount) { + _rcTimes++; + if (_rcTimer == null) { + _rcTimer = new Timer.periodic(Duration(milliseconds: _heartTimes.toInt()), (timer) { + print('websocket reconnect'); + _channel = WebSocketChannel.connect(uri); + _channel!.stream.listen( + (message) => _onMessage(message), + onDone: () => _handleDone(uri), + onError: (error) => _handleError(error), + ); + }); + } + } else { + _rcTimer?.cancel(); + _rcTimer = null; } - _isConnected = false; } // Send data over a websocket channel diff --git a/lib/custom_bottom_bar.dart b/lib/custom_bottom_bar.dart index d0642781f..34c114c6a 100644 --- a/lib/custom_bottom_bar.dart +++ b/lib/custom_bottom_bar.dart @@ -1,4 +1,3 @@ -import 'package:lantern/common/common_desktop.dart'; import 'package:lantern/custom_bottom_item.dart'; import 'package:lantern/messaging/messaging.dart'; import 'package:lantern/replica/common.dart'; @@ -178,7 +177,6 @@ class CustomBottomBar extends StatelessWidget { : indicatorRed, ), ), - WebsocketImpl(), ), ), label: '', diff --git a/lib/ffi.dart b/lib/ffi.dart index 692b93aa2..91c47e0e3 100644 --- a/lib/ffi.dart +++ b/lib/ffi.dart @@ -80,9 +80,6 @@ Pointer ffiOnBoardingStatus() => Pointer ffiServerInfo() => _bindings.serverInfo().cast(); -Pointer ffiPurchase(planID, email, cardNumber, expDate, cvc) => - _bindings.purchase(planID, email, cardNumber, expDate, cvc).cast(); - Pointer ffiReportIssue(email, issueType, description) => _bindings.reportIssue(email, issueType, description).cast(); diff --git a/lib/vpn/vpn_model.dart b/lib/vpn/vpn_model.dart index fdde3e6aa..5acb5d2ba 100644 --- a/lib/vpn/vpn_model.dart +++ b/lib/vpn/vpn_model.dart @@ -21,13 +21,14 @@ class VpnModel extends Model { } Future handleWebSocketMessage(Map data, Function setValue) async { + print("Received websocket message $data"); if (data["type"] != "vpnstatus") return; final updated = data["message"]["connected"]; final isConnected = updated != null && updated.toString() == "true"; setValue(isConnected ? "connected" : "disconnected"); } - Widget vpnStatus(ValueWidgetBuilder builder, [WebsocketImpl? websocket]) { + Widget vpnStatus(ValueWidgetBuilder builder) { if (isMobile()) { return subscribedSingleValueBuilder( '/vpn_status', @@ -38,8 +39,10 @@ class VpnModel extends Model { 'vpnStatus', defaultValue: '', onChanges: (setValue) { + final websocket = WebsocketImpl.instance(); + if (websocket == null) return; /// Listen for all incoming data - websocket?.messageStream.listen( + websocket.messageStream.listen( (json) => handleWebSocketMessage(json, setValue), onError: (error) => print(error), ); diff --git a/lib/vpn/vpn_status.dart b/lib/vpn/vpn_status.dart index 73632a44e..c39987978 100644 --- a/lib/vpn/vpn_status.dart +++ b/lib/vpn/vpn_status.dart @@ -44,6 +44,6 @@ class VPNStatus extends StatelessWidget { ), ], ); - }, WebsocketImpl()); + }); } } diff --git a/lib/vpn/vpn_tab.dart b/lib/vpn/vpn_tab.dart index 8acf3e39b..2a1476e68 100644 --- a/lib/vpn/vpn_tab.dart +++ b/lib/vpn/vpn_tab.dart @@ -1,5 +1,4 @@ import 'package:lantern/account/split_tunneling.dart'; -import 'package:lantern/common/common_desktop.dart'; import 'package:lantern/messaging/messaging.dart'; import 'package:lantern/vpn/vpn.dart'; import 'vpn_bandwidth.dart'; @@ -8,31 +7,9 @@ import 'vpn_server_location.dart'; import 'vpn_status.dart'; import 'vpn_switch.dart'; -class VPNTab extends StatefulWidget { +class VPNTab extends StatelessWidget { VPNTab({Key? key}) : super(key: key); - @override - _VPNTabState createState() => _VPNTabState(); -} - -class _VPNTabState extends State { - WebsocketImpl? websocket; - - @override - void initState() { - if (isDesktop()) { - setState(() => websocket = WebsocketImpl()); - websocket?.connect(Uri.parse("ws://" + websocketAddr() + '/data')); - } - super.initState(); - } - - @override - void dispose() { - websocket?.close(); - super.dispose(); - } - @override Widget build(BuildContext context) { return sessionModel @@ -79,6 +56,6 @@ class _VPNTabState extends State { ], ), ); - }, websocket); + }); } } From 81b50669bd9bb91d34492bf6cb80775f66759101 Mon Sep 17 00:00:00 2001 From: atavism Date: Sun, 24 Mar 2024 13:34:56 -0700 Subject: [PATCH 36/49] updates to init websocket when app starts --- lib/app.dart | 23 ++-------------- lib/common/ui/websocket.dart | 51 ++++++++++++++++++------------------ lib/main.dart | 1 + lib/vpn/vpn_model.dart | 1 - 4 files changed, 29 insertions(+), 47 deletions(-) diff --git a/lib/app.dart b/lib/app.dart index 831ab45bb..294362c35 100644 --- a/lib/app.dart +++ b/lib/app.dart @@ -1,6 +1,5 @@ import 'package:app_links/app_links.dart'; import 'package:flutter/scheduler.dart'; -import 'package:lantern/common/common_desktop.dart'; import 'package:lantern/core/router/router.dart'; import 'package:lantern/messaging/messaging.dart'; @@ -39,36 +38,18 @@ class LanternApp extends StatefulWidget { State createState() => _LanternAppState(); } -class _LanternAppState extends State with WidgetsBindingObserver { +class _LanternAppState extends State { final translations = Localization.ensureInitialized(); late final AnimationController networkWarningAnimationController; late final Animation networkWarningAnimation; @override void initState() { - super.initState(); - WidgetsBinding.instance!.addObserver(this); _animateNetworkWarning(); WidgetsBinding.instance.addPostFrameCallback((_) { initDeepLinks(); }); - } - - @override - void dispose() { - WidgetsBinding.instance!.removeObserver(this); - super.dispose(); - } - - @override - void didChangeAppLifecycleState(AppLifecycleState state) async { - if (state == AppLifecycleState.resumed) { - print('App resumed'); - await WebsocketImpl.instance()!.connect(Uri.parse("ws://" + websocketAddr() + '/data')); - } else if (state == AppLifecycleState.paused) { - print('App paused'); - await WebsocketImpl.instance()!.close(); - } + super.initState(); } void _animateNetworkWarning() { diff --git a/lib/common/ui/websocket.dart b/lib/common/ui/websocket.dart index 9579cb9b0..1b690dad8 100644 --- a/lib/common/ui/websocket.dart +++ b/lib/common/ui/websocket.dart @@ -5,7 +5,7 @@ import 'package:lantern/common/common_desktop.dart'; abstract class WebsocketService { Stream> get messageStream; - Future connect(Uri uri); + Future connect(); Future close(); void send(String event, Map data); } @@ -39,29 +39,38 @@ class WebsocketImpl implements WebsocketService { // Creates a new Websocket connection @override - Future connect(Uri uri) async { - _handleClose = false; - await close(); + Future connect() async { + final uri = Uri.parse("ws://" + websocketAddr() + '/data'); + if (_isConnected) { + return; + } + print('Opening websocket connection'); - _channel = WebSocketChannel.connect(uri); + try { + _channel = WebSocketChannel.connect(uri); - _channel!.stream.listen( - (message) => _onMessage(message), - onDone: () => _handleDone(uri), - onError: (error) => _handleError(error), - ); + _isConnected = true; + _rcTimes = 0; + _rcTimer?.cancel(); + _rcTimer = null; - print("Websocket connected"); - } + _channel!.stream.listen( + (message) => _onMessage(message), + onDone: () => _handleDone(uri), + onError: (error) => _handleError(error), + ); - void _handleError(dynamic error) { - if (error is WebSocketChannelException) { - close(); - return; + print("Websocket connected"); + } catch (e) { + await close(); + print("Exception opening websocket connection ${e.toString()}"); } + } + void _handleError(dynamic error) { print('Websocket error: $error'); + close(); } void _handleDone(Uri uri) { @@ -78,19 +87,11 @@ class WebsocketImpl implements WebsocketService { if (_channel != null && _channel?.sink != null) { print('Closing websocket'); await _channel?.sink.close(); - _isConnected = false; } + _isConnected = false; } Future _onMessage(message) async { - if (!_isConnected) { - _isConnected = true; - - _rcTimes = 0; - _rcTimer?.cancel(); - _rcTimer = null; - } - final Map json = jsonDecode(message ?? {}); streamController.add(json); } diff --git a/lib/main.dart b/lib/main.dart index b36c420f9..8a3d3e9ba 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -31,6 +31,7 @@ Future main() async { if (isDesktop()) { loadLibrary(); + await WebsocketImpl.instance()!.connect(); await windowManager.ensureInitialized(); WindowOptions windowOptions = const WindowOptions( size: ui.Size(360, 712), diff --git a/lib/vpn/vpn_model.dart b/lib/vpn/vpn_model.dart index 5acb5d2ba..4b40b279f 100644 --- a/lib/vpn/vpn_model.dart +++ b/lib/vpn/vpn_model.dart @@ -21,7 +21,6 @@ class VpnModel extends Model { } Future handleWebSocketMessage(Map data, Function setValue) async { - print("Received websocket message $data"); if (data["type"] != "vpnstatus") return; final updated = data["message"]["connected"]; final isConnected = updated != null && updated.toString() == "true"; From 39d67a96c04cf9c3326988799088fd1f3f97f5fe Mon Sep 17 00:00:00 2001 From: atavism Date: Sun, 24 Mar 2024 13:44:59 -0700 Subject: [PATCH 37/49] clean-ups, add comments --- lib/common/ui/websocket.dart | 59 ++++++++++++++++++------------------ 1 file changed, 29 insertions(+), 30 deletions(-) diff --git a/lib/common/ui/websocket.dart b/lib/common/ui/websocket.dart index 1b690dad8..e75aa800f 100644 --- a/lib/common/ui/websocket.dart +++ b/lib/common/ui/websocket.dart @@ -14,9 +14,11 @@ class WebsocketImpl implements WebsocketService { static WebsocketImpl? _websocket; WebsocketImpl._internal(); - num _heartTimes = 10000; - num _rcMaxCount = 600; - num _rcTimes = 0; + // the maximum number of times we attempt to reconnect + static const _maxRetries = 5; + // The number of times we've tried reconnecting + var _retries = 0; + Timer? _rcTimer; static WebsocketImpl? instance() { @@ -32,7 +34,6 @@ class WebsocketImpl implements WebsocketService { // _channel is a stream channel that communicates over a WebSocket. WebSocketChannel? _channel; bool _isConnected = false; - bool _handleClose = false; @override Stream> get messageStream => streamController.stream; @@ -49,19 +50,19 @@ class WebsocketImpl implements WebsocketService { try { _channel = WebSocketChannel.connect(uri); - - _isConnected = true; - _rcTimes = 0; - _rcTimer?.cancel(); - _rcTimer = null; - _channel!.stream.listen( - (message) => _onMessage(message), + (message) => _handleMessage(message), onDone: () => _handleDone(uri), onError: (error) => _handleError(error), ); print("Websocket connected"); + + _isConnected = true; + _retries = 0; + _rcTimer?.cancel(); + _rcTimer = null; + } catch (e) { await close(); print("Exception opening websocket connection ${e.toString()}"); @@ -74,8 +75,7 @@ class WebsocketImpl implements WebsocketService { } void _handleDone(Uri uri) { - print("_handleDone called"); - if (!_handleClose) { + if (_isConnected) { reconnect(uri); } } @@ -83,36 +83,35 @@ class WebsocketImpl implements WebsocketService { // Close sink for sending values and websocket connection @override Future close() async { - _handleClose = true; + _isConnected = false; if (_channel != null && _channel?.sink != null) { print('Closing websocket'); await _channel?.sink.close(); } - _isConnected = false; } - Future _onMessage(message) async { + Future _handleMessage(message) async { final Map json = jsonDecode(message ?? {}); streamController.add(json); } Future reconnect(Uri uri) async { - if (_rcTimes < _rcMaxCount) { - _rcTimes++; - if (_rcTimer == null) { - _rcTimer = new Timer.periodic(Duration(milliseconds: _heartTimes.toInt()), (timer) { - print('websocket reconnect'); - _channel = WebSocketChannel.connect(uri); - _channel!.stream.listen( - (message) => _onMessage(message), - onDone: () => _handleDone(uri), - onError: (error) => _handleError(error), - ); - }); - } - } else { + if (_retries >= _maxRetries) { _rcTimer?.cancel(); _rcTimer = null; + return; + } + _retries++; + if (_rcTimer == null) { + _rcTimer = new Timer.periodic(const Duration(milliseconds: 1000), (timer) { + print('websocket reconnect'); + _channel = WebSocketChannel.connect(uri); + _channel!.stream.listen( + (message) => _handleMessage(message), + onDone: () => _handleDone(uri), + onError: (error) => _handleError(error), + ); + }); } } From 8fcdc9209910efb669dff7c25ebd097a95cf3004 Mon Sep 17 00:00:00 2001 From: atavism Date: Sun, 24 Mar 2024 13:46:12 -0700 Subject: [PATCH 38/49] updates to websocket service --- lib/common/ui/websocket.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/common/ui/websocket.dart b/lib/common/ui/websocket.dart index e75aa800f..0be4e530b 100644 --- a/lib/common/ui/websocket.dart +++ b/lib/common/ui/websocket.dart @@ -76,7 +76,7 @@ class WebsocketImpl implements WebsocketService { void _handleDone(Uri uri) { if (_isConnected) { - reconnect(uri); + reconnect(uri); } } @@ -92,7 +92,7 @@ class WebsocketImpl implements WebsocketService { Future _handleMessage(message) async { final Map json = jsonDecode(message ?? {}); - streamController.add(json); + streamController.add(json); } Future reconnect(Uri uri) async { From f0d03dec128cb05db19d4a69a8f7eef66252e469 Mon Sep 17 00:00:00 2001 From: atavism Date: Sun, 24 Mar 2024 13:49:22 -0700 Subject: [PATCH 39/49] remove websocket changes to account menu --- lib/account/account.dart | 18 +----------------- 1 file changed, 1 insertion(+), 17 deletions(-) diff --git a/lib/account/account.dart b/lib/account/account.dart index d5a9475d1..b6487a51c 100644 --- a/lib/account/account.dart +++ b/lib/account/account.dart @@ -1,8 +1,6 @@ import 'package:lantern/common/common.dart'; import 'package:lantern/messaging/messaging_model.dart'; -import '../common/common_desktop.dart'; - @RoutePage(name: 'Account') class AccountMenu extends StatefulWidget { const AccountMenu({Key? key}) : super(key: key); @@ -12,25 +10,11 @@ class AccountMenu extends StatefulWidget { } class _AccountMenuState extends State { - WebsocketImpl websocket = WebsocketImpl(); - @override void initState() { - if (isDesktop()) { - connectDesktopSocket(); - } super.initState(); } - void connectDesktopSocket() { - try { - websocket.connect(Uri.parse('ws://${websocketAddr()}/data')); - appLogger.i("Socket connected"); - } catch (e) { - appLogger.e('Error connecting to socket: $e'); - } - } - Future authorizeDeviceForPro(BuildContext context) async => await context.pushRoute(AuthorizePro()); @@ -178,7 +162,7 @@ class _AccountMenuState extends State { ? proItems(sessionContext) : freeItems(sessionContext, sessionModel), ); - }, websocket), + }), ); } } From a8d3a1a5c902a608b91f688a1ce2028872597e9e Mon Sep 17 00:00:00 2001 From: atavism Date: Sun, 24 Mar 2024 13:50:02 -0700 Subject: [PATCH 40/49] apply formatting --- lib/account/account.dart | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/lib/account/account.dart b/lib/account/account.dart index b6487a51c..bb88f1fca 100644 --- a/lib/account/account.dart +++ b/lib/account/account.dart @@ -59,14 +59,14 @@ class _AccountMenuState extends State { ) : const SizedBox(), ), - ListItemFactory.settingsItem( - key: AppKeys.upgrade_lantern_pro, - icon: ImagePaths.pro_icon_black, - content: 'Upgrade to Lantern Pro'.i18n, - onTap: () { - upgradeToLanternPro(context); - }, - ), + ListItemFactory.settingsItem( + key: AppKeys.upgrade_lantern_pro, + icon: ImagePaths.pro_icon_black, + content: 'Upgrade to Lantern Pro'.i18n, + onTap: () { + upgradeToLanternPro(context); + }, + ), ListItemFactory.settingsItem( icon: ImagePaths.star, content: 'Invite Friends'.i18n, @@ -155,8 +155,8 @@ class _AccountMenuState extends State { return BaseScreen( title: 'Account'.i18n, automaticallyImplyLeading: false, - body: sessionModel.proUser( - (BuildContext sessionContext, bool proUser, Widget? child) { + body: sessionModel + .proUser((BuildContext sessionContext, bool proUser, Widget? child) { return ListView( children: proUser ? proItems(sessionContext) From 3cd60c55acce9faef5d82192aa02649e359b6ddb Mon Sep 17 00:00:00 2001 From: atavism Date: Sun, 24 Mar 2024 14:29:59 -0700 Subject: [PATCH 41/49] refresh user data when home page is loaded --- desktop/lib.go | 2 ++ lib/common/session_model.dart | 2 +- lib/vpn/vpn_model.dart | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/desktop/lib.go b/desktop/lib.go index 333b2e12f..519039b46 100644 --- a/desktop/lib.go +++ b/desktop/lib.go @@ -320,6 +320,8 @@ func acceptedTermsVersion() *C.char { //export proUser func proUser() *C.char { + // refresh user data when home page is loaded on desktop + go pro.FetchUserData(a.Settings()) if isProUser, ok := a.IsProUser(); isProUser && ok { return C.CString("true") } diff --git a/lib/common/session_model.dart b/lib/common/session_model.dart index dd98bb95b..85f0ee74c 100644 --- a/lib/common/session_model.dart +++ b/lib/common/session_model.dart @@ -77,11 +77,11 @@ class SessionModel extends Model { if (isMobile()) { return subscribedSingleValueBuilder('prouser', builder: builder); } + final websocket = WebsocketImpl.instance(); return ffiValueBuilder( 'prouser', defaultValue: false, onChanges: (setValue) { - final websocket = WebsocketImpl.instance(); if (websocket == null) return; /// Listen for all incoming data websocket.messageStream.listen( diff --git a/lib/vpn/vpn_model.dart b/lib/vpn/vpn_model.dart index 4b40b279f..01a9caafd 100644 --- a/lib/vpn/vpn_model.dart +++ b/lib/vpn/vpn_model.dart @@ -34,11 +34,11 @@ class VpnModel extends Model { builder: builder, ); } + final websocket = WebsocketImpl.instance(); return ffiValueBuilder( 'vpnStatus', defaultValue: '', onChanges: (setValue) { - final websocket = WebsocketImpl.instance(); if (websocket == null) return; /// Listen for all incoming data websocket.messageStream.listen( From 3f1043681ddd9cc90ae597e20e2f454b31e427d2 Mon Sep 17 00:00:00 2001 From: atavism Date: Sun, 24 Mar 2024 17:51:47 -0700 Subject: [PATCH 42/49] remove redundant methods --- desktop/app/app.go | 8 -------- desktop/lib.go | 5 +++-- 2 files changed, 3 insertions(+), 10 deletions(-) diff --git a/desktop/app/app.go b/desktop/app/app.go index 85e95708a..cd9feaec0 100644 --- a/desktop/app/app.go +++ b/desktop/app/app.go @@ -169,14 +169,6 @@ func (app *App) SetSelectedTab(selectedTab Tab) { app.selectedTab = selectedTab } -func (app *App) EmailAddress() string { - return app.settings.GetEmailAddress() -} - -func (app *App) SetEmailAddress(emailAddress string) { - app.settings.SetEmailAddress(emailAddress) -} - // Run starts the app. func (app *App) Run(isMain bool) { golog.OnFatal(app.exitOnFatal) diff --git a/desktop/lib.go b/desktop/lib.go index 519039b46..9b18fb650 100644 --- a/desktop/lib.go +++ b/desktop/lib.go @@ -223,14 +223,15 @@ func serverInfo() *C.char { //export emailAddress func emailAddress() *C.char { - emailAddress := a.EmailAddress() + settings := a.Settings() + emailAddress := settings.GetEmailAddress() if emailAddress == "" { resp, err := proClient.UserData(userConfig()) if err != nil { return sendError(err) } emailAddress = resp.User.Email - a.SetEmailAddress(emailAddress) + settings.SetEmailAddress(emailAddress) } return C.CString(emailAddress) } From 3f012fa9f97e430571b381714539d14f4092c107 Mon Sep 17 00:00:00 2001 From: atavism Date: Sun, 24 Mar 2024 18:09:41 -0700 Subject: [PATCH 43/49] update onTrayMenuItemClick --- lib/home.dart | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/home.dart b/lib/home.dart index 17cc23653..a5882af0e 100644 --- a/lib/home.dart +++ b/lib/home.dart @@ -133,7 +133,8 @@ class _HomePageState extends State with TrayListener, WindowListener { case 'exit': SystemChannels.platform.invokeMethod('SystemNavigator.pop'); case 'status': - bool isConnected = await ffiVpnStatus() == "connected"; + final status = await ffiVpnStatus().toDartString(); + bool isConnected = status == "connected"; if (isConnected) { sysProxyOff(); await setupMenu(false); From b9b7b673ca7e93c4bc57cb83bbaeb33b5e3df486 Mon Sep 17 00:00:00 2001 From: atavism Date: Sun, 24 Mar 2024 18:33:42 -0700 Subject: [PATCH 44/49] clean-ups, do not fetch user data when checking email address --- desktop/lib.go | 36 +++++++++++++++++++----------------- 1 file changed, 19 insertions(+), 17 deletions(-) diff --git a/desktop/lib.go b/desktop/lib.go index 9b18fb650..bf46732a9 100644 --- a/desktop/lib.go +++ b/desktop/lib.go @@ -175,34 +175,46 @@ func paymentMethods() *C.char { return C.CString(string(b)) } +func getUserData() (*client.User, error) { + resp, err := proClient.UserData(userConfig()) + if err != nil { + return nil, err + } + user := resp.User + if user.Email != "" { + a.Settings().SetEmailAddress(user.Email) + } + return &user, nil +} + //export devices func devices() *C.char { - resp, err := proClient.UserData(userConfig()) + user, err := getUserData() if err != nil { return sendError(err) } - b, _ := json.Marshal(resp.User.Devices) + b, _ := json.Marshal(user.Devices) return C.CString(string(b)) } //export expiryDate func expiryDate() *C.char { - resp, err := proClient.UserData(userConfig()) + user, err := getUserData() if err != nil { return sendError(err) } - tm := time.Unix(resp.User.Expiration, 0) + tm := time.Unix(user.Expiration, 0) exp := tm.Format("01/02/2006") return C.CString(string(exp)) } //export userData func userData() *C.char { - resp, err := proClient.UserData(userConfig()) + user, err := getUserData() if err != nil { return sendError(err) } - b, _ := json.Marshal(resp.User) + b, _ := json.Marshal(user) return C.CString(string(b)) } @@ -223,17 +235,7 @@ func serverInfo() *C.char { //export emailAddress func emailAddress() *C.char { - settings := a.Settings() - emailAddress := settings.GetEmailAddress() - if emailAddress == "" { - resp, err := proClient.UserData(userConfig()) - if err != nil { - return sendError(err) - } - emailAddress = resp.User.Email - settings.SetEmailAddress(emailAddress) - } - return C.CString(emailAddress) + return C.CString(a.Settings().GetEmailAddress()) } //export emailExists From 65a62fbf620508651336c5bca07af89b4ea93fcd Mon Sep 17 00:00:00 2001 From: atavism Date: Mon, 25 Mar 2024 07:17:38 -0700 Subject: [PATCH 45/49] set Localization.locale in setLanguage --- lib/common/session_model.dart | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/common/session_model.dart b/lib/common/session_model.dart index 85f0ee74c..2ff016461 100644 --- a/lib/common/session_model.dart +++ b/lib/common/session_model.dart @@ -293,6 +293,7 @@ class SessionModel extends Model { }); } // Desktop users + Localization.locale = lang; final newLang = lang.toNativeUtf8(); setLang(newLang); return Future(() => null); From e9af14d16e216f46ffb1d655dc555511a8863835 Mon Sep 17 00:00:00 2001 From: atavism Date: Mon, 25 Mar 2024 07:34:57 -0700 Subject: [PATCH 46/49] use Localization.locale when parsing plan json --- lib/common/session_model.dart | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/lib/common/session_model.dart b/lib/common/session_model.dart index 2ff016461..aff655255 100644 --- a/lib/common/session_model.dart +++ b/lib/common/session_model.dart @@ -452,22 +452,25 @@ class SessionModel extends Model { } Plan planFromJson(Map item) { - final formatCurrency = NumberFormat.simpleCurrency(); + final locale = Localization.locale; + final formatCurrency = NumberFormat.simpleCurrency(locale: locale); final currency = formatCurrency.currencyName != null ? formatCurrency.currencyName!.toLowerCase() : "usd"; final res = jsonEncode(item); final plan = Plan.create()..mergeFromProto3Json(jsonDecode(res)); - final price = plan.price[currency] as Int64; if (plan.expectedMonthlyPrice[currency] != null) { var monthlyPrice = plan.expectedMonthlyPrice[currency]!.toInt(); plan.oneMonthCost = formatCurrency .format(monthlyPrice / 100) .toString(); } - plan.totalCost = formatCurrency.format(price.toInt() / 100).toString(); - plan.totalCostBilledOneTime = + if (plan.price[currency] != null) { + final price = plan.price[currency] as Int64; + plan.totalCost = formatCurrency.format(price.toInt() / 100).toString(); + plan.totalCostBilledOneTime = formatCurrency.format(price.toInt() / 100).toString() + ' ' + 'billed_one_time'.i18n; + } return plan; } From ea7e495403616e5edd67cfb805b84f3f06eec373 Mon Sep 17 00:00:00 2001 From: atavism Date: Mon, 25 Mar 2024 07:47:48 -0700 Subject: [PATCH 47/49] fix system menu show button --- lib/home.dart | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/home.dart b/lib/home.dart index a5882af0e..a4d87d9bf 100644 --- a/lib/home.dart +++ b/lib/home.dart @@ -130,6 +130,8 @@ class _HomePageState extends State with TrayListener, WindowListener { @override void onTrayMenuItemClick(MenuItem menuItem) async { switch (menuItem.key) { + case 'show': + windowManager.show(); case 'exit': SystemChannels.platform.invokeMethod('SystemNavigator.pop'); case 'status': From 5a597084b6eae13dc90de8b832e0e5444468cf46 Mon Sep 17 00:00:00 2001 From: atavism Date: Mon, 25 Mar 2024 09:35:00 -0700 Subject: [PATCH 48/49] support redeeming reseller codes on desktop --- desktop/lib.go | 10 ++++++++++ lib/common/session_model.dart | 17 +++++++++++------ lib/ffi.dart | 6 ++++++ lib/plans/reseller_checkout.dart | 6 ++++++ 4 files changed, 33 insertions(+), 6 deletions(-) diff --git a/desktop/lib.go b/desktop/lib.go index bf46732a9..2e8e1a4d1 100644 --- a/desktop/lib.go +++ b/desktop/lib.go @@ -248,6 +248,16 @@ func emailExists(email *C.char) *C.char { return C.CString("false") } +//export redeemResellerCode +func redeemResellerCode(email, currency, deviceName, resellerCode *C.char) *C.char { + _, err := proClient.RedeemResellerCode(userConfig(), C.GoString(email), C.GoString(resellerCode), + C.GoString(deviceName), C.GoString(currency)) + if err != nil { + return sendError(err) + } + return C.CString("true") +} + //export referral func referral() *C.char { referralCode, err := a.ReferralCode(userConfig()) diff --git a/lib/common/session_model.dart b/lib/common/session_model.dart index aff655255..da576c160 100644 --- a/lib/common/session_model.dart +++ b/lib/common/session_model.dart @@ -637,14 +637,19 @@ class SessionModel extends Model { Future redeemResellerCode( String email, + String currency, + String deviceName, String resellerCode, ) async { - return methodChannel.invokeMethod('redeemResellerCode', { - 'email': email, - 'resellerCode': resellerCode, - }).then((value) { - print('value $value'); - }); + if (isMobile()) { + return methodChannel.invokeMethod('redeemResellerCode', { + 'email': email, + 'resellerCode': resellerCode, + }).then((value) { + print('value $value'); + }); + } + await ffiRedeemResellerCode(email, currency, deviceName, resellerCode); } Future submitBitcoinPayment( diff --git a/lib/ffi.dart b/lib/ffi.dart index 91c47e0e3..90fe1881b 100644 --- a/lib/ffi.dart +++ b/lib/ffi.dart @@ -52,6 +52,12 @@ Pointer ffiEmailAddress() => _bindings.emailAddress().cast(); Pointer ffiEmailExists(email) => _bindings.emailExists(email).cast(); +Pointer ffiRedeemResellerCode( + email, currency, deviceName, resellerCode) => + _bindings + .redeemResellerCode(email, currency, deviceName, resellerCode) + .cast(); + Pointer ffiReferral() => _bindings.referral().cast(); Pointer ffiReplicaAddr() => _bindings.replicaAddr().cast(); diff --git a/lib/plans/reseller_checkout.dart b/lib/plans/reseller_checkout.dart index bce365548..8deb5c35c 100644 --- a/lib/plans/reseller_checkout.dart +++ b/lib/plans/reseller_checkout.dart @@ -3,6 +3,7 @@ import 'package:lantern/common/common.dart'; import 'package:lantern/plans/plan_details.dart'; import 'package:lantern/plans/tos.dart'; import 'package:lantern/plans/utils.dart'; +import 'package:intl/intl.dart'; class ResellerCodeFormatter extends TextInputFormatter { @override @@ -170,8 +171,13 @@ class _ResellerCodeCheckoutState extends State { Future onRegisterPro() async { try { context.loaderOverlay.show(); + Locale locale = Localizations.localeOf(context); + final format = NumberFormat.simpleCurrency(locale: locale.toString()); + final currencyName = format.currencyName ?? "USD"; await sessionModel.redeemResellerCode( emailController.text, + currencyName, + Platform.operatingSystem, resellerCodeController.text, ); context.loaderOverlay.hide(); From 05cece0523835151c8a7d2a81f6c827cf4c6c514 Mon Sep 17 00:00:00 2001 From: jigar-f <132374182+jigar-f@users.noreply.github.com> Date: Tue, 26 Mar 2024 21:07:02 +0530 Subject: [PATCH 49/49] Desktop Error handling and other hot fix (#1027) * Added error checking for RedeemResellerCode. * use localize message. * added error caching for report issue. * Added error handling on payments. * fixed issue with report issue not working. * Use compute to avoid freeze UI. * Fixed issue with device not showing up. * Update loading bar. --- assets/locales/en.po | 25 +++++++++-- desktop/lib.go | 63 ++++++++++++++------------- lib/account/report_issue.dart | 16 +++---- lib/app.dart | 15 ++++++- lib/common/session_model.dart | 50 +++++++++++---------- lib/common/ui/app_loading_dialog.dart | 2 +- lib/ffi.dart | 50 ++++++++++++++++----- lib/plans/checkout.dart | 46 ++++++++++--------- lib/plans/reseller_checkout.dart | 20 ++++++--- pubspec.yaml | 2 + 10 files changed, 183 insertions(+), 106 deletions(-) diff --git a/assets/locales/en.po b/assets/locales/en.po index 59d51887c..ef1797d4f 100644 --- a/assets/locales/en.po +++ b/assets/locales/en.po @@ -162,11 +162,18 @@ msgstr "Cannot access blocked sites" msgid "cannot_complete_purchase" msgstr "Cannot complete purchase" + +msgid "discover_not_working" +msgstr "Discover not working " + + + + msgid "cannot_sign_in" msgstr "Cannot sign in" -msgid "loading_spinner_spins_endlessly" -msgstr "Loading spinner spins endlessly" +msgid "spinner_loads_endlessly" +msgstr "Spinner loads endlessly" msgid "slow" msgstr "Slow" @@ -174,8 +181,8 @@ msgstr "Slow" msgid "cannot_link_devices" msgstr "Cannot link devices" -msgid "lantern_crashes" -msgstr "Lantern crashes" +msgid "application_crashes" +msgstr "Application crashes" msgid "other" msgstr "Other" @@ -1472,4 +1479,14 @@ msgstr "Your device has been successfully added. If you do not see the device co msgid "search_apps" msgstr "Search Apps" +msgid "wrong_seller_code" +msgstr "Incorrect reseller code. Please verify and try again." + +msgid "report_issue_error" +msgstr "We're experiencing technical difficulties. Try again later." + +msgid "we_are_experiencing_technical_difficulties" +msgstr "We're experiencing technical difficulties. Try again later." + + diff --git a/desktop/lib.go b/desktop/lib.go index 2e8e1a4d1..eb8b1d91f 100644 --- a/desktop/lib.go +++ b/desktop/lib.go @@ -47,6 +47,19 @@ var ( proClient *client.Client ) +var issueMap = map[string]string{ + "Cannot access blocked sites": "3", + "Cannot complete purchase": "0", + "Cannot sign in": "1", + "Spinner loads endlessly": "2", + "Slow": "4", + "Chat not working": "7", + "Discover not working": "8", + "Cannot link device": "5", + "Application crashes": "6", + "Other": "9", +} + //export start func start() { runtime.LockOSThread() @@ -128,11 +141,7 @@ func sendError(err error) *C.char { if err == nil { return C.CString("") } - errors := map[string]interface{}{ - "error": err.Error(), - } - b, _ := json.Marshal(errors) - return C.CString(string(b)) + return C.CString(err.Error()) } //export selectedTab @@ -248,14 +257,21 @@ func emailExists(email *C.char) *C.char { return C.CString("false") } +// The function returns two C strings: the first represents success, and the second represents an error. +// If the redemption is successful, the first string contains "true", and the second string is nil. +// If an error occurs during redemption, the first string is nil, and the second string contains the error message. +// //export redeemResellerCode -func redeemResellerCode(email, currency, deviceName, resellerCode *C.char) *C.char { +func redeemResellerCode(email, currency, deviceName, resellerCode *C.char) (*C.char, *C.char) { _, err := proClient.RedeemResellerCode(userConfig(), C.GoString(email), C.GoString(resellerCode), C.GoString(deviceName), C.GoString(currency)) if err != nil { - return sendError(err) + log.Debugf("DEBUG: error while redeeming reseller code: %v", err) + return nil, C.CString(err.Error()) + // return sendError(err) } - return C.CString("true") + log.Debugf("DEBUG: redeeming reseller code success: %v", err) + return C.CString("true"), nil } //export referral @@ -352,7 +368,7 @@ func deviceLinkingCode() *C.char { } //export paymentRedirect -func paymentRedirect(planID, currency, provider, email, deviceName *C.char) *C.char { +func paymentRedirect(planID, currency, provider, email, deviceName *C.char) (*C.char, *C.char) { country := a.Settings().GetCountry() resp, err := proClient.PaymentRedirect(userConfig(), &client.PaymentRedirectRequest{ Plan: C.GoString(planID), @@ -363,9 +379,9 @@ func paymentRedirect(planID, currency, provider, email, deviceName *C.char) *C.c CountryCode: country, }) if err != nil { - return sendError(err) + return nil, sendError(err) } - return C.CString(resp.Redirect) + return C.CString(resp.Redirect), nil } //export developmentMode @@ -388,19 +404,6 @@ func replicaAddr() *C.char { return C.CString("") } -var issueMap = map[string]string{ - "Cannot access blocked sites": "3", - "Cannot complete purchase": "0", - "Cannot sign in": "1", - "Spinner loads endlessly": "2", - "Slow": "4", - "Chat not working": "7", - "Discover not working": "8", - "Cannot link device": "5", - "Application crashes": "6", - "Other": "9", -} - func userConfig() *common.UserConfigData { settings := a.Settings() userID, deviceID, token := settings.GetUserID(), settings.GetDeviceID(), settings.GetToken() @@ -415,13 +418,13 @@ func userConfig() *common.UserConfigData { } //export reportIssue -func reportIssue(email, issueType, description *C.char) *C.char { +func reportIssue(email, issueType, description *C.char) (*C.char, *C.char) { deviceID := a.Settings().GetDeviceID() - issueTypeInt, err := strconv.Atoi(C.GoString(issueType)) + issueIndex := issueMap[C.GoString(issueType)] + issueTypeInt, err := strconv.Atoi(issueIndex) if err != nil { - return sendError(err) + return nil, sendError(err) } - uc := userConfig() subscriptionLevel := "free" @@ -448,10 +451,10 @@ func reportIssue(email, issueType, description *C.char) *C.char { nil, ) if err != nil { - return sendError(err) + return nil, sendError(err) } log.Debug("Successfully reported issue") - return C.CString("true") + return C.CString("true"), nil } //export checkUpdates diff --git a/lib/account/report_issue.dart b/lib/account/report_issue.dart index 981a4a675..8d3df014a 100644 --- a/lib/account/report_issue.dart +++ b/lib/account/report_issue.dart @@ -142,10 +142,11 @@ class _ReportIssueState extends State { 'cannot_access_blocked_sites'.i18n, 'cannot_complete_purchase'.i18n, 'cannot_sign_in'.i18n, - 'loading_spinner_spins_endlessly'.i18n, + 'discover_not_working'.i18n, + 'spinner_loads_endlessly'.i18n, 'slow'.i18n, 'cannot_link_devices'.i18n, - 'lantern_crashes'.i18n, + 'application_crashes'.i18n, 'other'.i18n ].map>((String value) { return DropdownMenuItem( @@ -212,9 +213,9 @@ class _ReportIssueState extends State { ); await sessionModel.reportIssue(emailController.value.text, issueController.value.text, descController.value.text); - if (Platform.isIOS) { - AppLoadingDialog.dismissLoadingDialog(context); - } + + AppLoadingDialog.dismissLoadingDialog(context); + CDialog.showInfo( context, title: 'report_sent'.i18n, @@ -226,9 +227,8 @@ class _ReportIssueState extends State { }, ); } catch (error, stackTrace) { - if (Platform.isIOS) { - AppLoadingDialog.dismissLoadingDialog(context); - } + AppLoadingDialog.dismissLoadingDialog(context); + CDialog.showError( context, error: error, diff --git a/lib/app.dart b/lib/app.dart index 294362c35..02a415af6 100644 --- a/lib/app.dart +++ b/lib/app.dart @@ -1,3 +1,4 @@ +import 'package:animated_loading_border/animated_loading_border.dart'; import 'package:app_links/app_links.dart'; import 'package:flutter/scheduler.dart'; import 'package:lantern/core/router/router.dart'; @@ -121,8 +122,18 @@ class _LanternAppState extends State { (context, lang, child) { Localization.locale = lang; return GlobalLoaderOverlay( - overlayColor: Colors.black, - overlayOpacity: 0.6, + useDefaultLoading: false, + overlayColor: Colors.black.withOpacity(0.5), + overlayWidget: Center( + child: AnimatedLoadingBorder( + borderWidth: 5, + borderColor: yellow3, + cornerRadius: 100, + child: SvgPicture.asset( + ImagePaths.lantern_logo, + ), + ), + ), child: I18n( initialLocale: currentLocale(lang), child: MaterialApp.router( diff --git a/lib/common/session_model.dart b/lib/common/session_model.dart index da576c160..740a95922 100644 --- a/lib/common/session_model.dart +++ b/lib/common/session_model.dart @@ -83,12 +83,14 @@ class SessionModel extends Model { defaultValue: false, onChanges: (setValue) { if (websocket == null) return; + /// Listen for all incoming data websocket.messageStream.listen( (json) { if (json["type"] == "pro") { final userStatus = json["message"]["userStatus"]; - final isProUser = userStatus != null && userStatus.toString() == "active"; + final isProUser = + userStatus != null && userStatus.toString() == "active"; setValue(isProUser); } }, @@ -247,11 +249,17 @@ class SessionModel extends Model { } Devices devicesFromJson(dynamic item) { - final items = item as List; - if (items.length == 0) return Devices.create(); - - final res = jsonEncode(items); - return Devices.create()..mergeFromProto3Json(jsonDecode(res)); + final devices = []; + for (final element in item) { + if (element is! Map) continue; + try { + devices.add(Device.create()..mergeFromProto3Json(element)); + } on Exception catch (e) { + // Handle parsing errors as needed + print("Error parsing device data: $e"); + } + } + return Devices.create()..devices.addAll(devices); } Widget devices(ValueWidgetBuilder builder) { @@ -270,7 +278,7 @@ class SessionModel extends Model { fromJsonModel: devicesFromJson, defaultValue: null, builder: builder, - ); + ); } Future setProxyAll(bool on) async { @@ -454,22 +462,22 @@ class SessionModel extends Model { Plan planFromJson(Map item) { final locale = Localization.locale; final formatCurrency = NumberFormat.simpleCurrency(locale: locale); - final currency = formatCurrency.currencyName != null ? formatCurrency.currencyName!.toLowerCase() : "usd"; + final currency = formatCurrency.currencyName != null + ? formatCurrency.currencyName!.toLowerCase() + : "usd"; final res = jsonEncode(item); final plan = Plan.create()..mergeFromProto3Json(jsonDecode(res)); if (plan.expectedMonthlyPrice[currency] != null) { var monthlyPrice = plan.expectedMonthlyPrice[currency]!.toInt(); - plan.oneMonthCost = formatCurrency - .format(monthlyPrice / 100) - .toString(); + plan.oneMonthCost = formatCurrency.format(monthlyPrice / 100).toString(); } if (plan.price[currency] != null) { final price = plan.price[currency] as Int64; plan.totalCost = formatCurrency.format(price.toInt() / 100).toString(); plan.totalCostBilledOneTime = - formatCurrency.format(price.toInt() / 100).toString() + - ' ' + - 'billed_one_time'.i18n; + formatCurrency.format(price.toInt() / 100).toString() + + ' ' + + 'billed_one_time'.i18n; } return plan; } @@ -546,9 +554,7 @@ class SessionModel extends Model { Future reportIssue( String email, String issue, String description) async { if (isDesktop()) { - await ffiReportIssue(email.toNativeUtf8(), issue.toNativeUtf8(), - description.toNativeUtf8()); - return; + return await compute(ffiReportIssue, [email, issue, description]); } return methodChannel.invokeMethod('reportIssue', { 'email': email, @@ -649,7 +655,8 @@ class SessionModel extends Model { print('value $value'); }); } - await ffiRedeemResellerCode(email, currency, deviceName, resellerCode); + ffiRedeemResellerCode(email.toNativeUtf8(), currency.toNativeUtf8(), + deviceName.toNativeUtf8(), resellerCode.toNativeUtf8()); } Future submitBitcoinPayment( @@ -682,13 +689,13 @@ class SessionModel extends Model { String provider, String deviceName, ) async { - final resp = await ffiPaymentRedirect( + final resp = ffiPaymentRedirect( planID.toNativeUtf8(), currency.toNativeUtf8(), provider.toNativeUtf8(), email.toNativeUtf8(), deviceName.toNativeUtf8()); - return resp.toDartString(); + return resp; } Future submitStripePayment( @@ -698,8 +705,7 @@ class SessionModel extends Model { String expDate, String cvc, ) async { - return methodChannel - .invokeMethod('submitStripePayment', { + return methodChannel.invokeMethod('submitStripePayment', { 'planID': planID, 'email': email, 'cardNumber': cardNumber, diff --git a/lib/common/ui/app_loading_dialog.dart b/lib/common/ui/app_loading_dialog.dart index bb9146b26..708b336b3 100644 --- a/lib/common/ui/app_loading_dialog.dart +++ b/lib/common/ui/app_loading_dialog.dart @@ -2,7 +2,7 @@ import 'package:lantern/common/common.dart'; class AppLoadingDialog { static void showLoadingDialog(BuildContext context) { - context.loaderOverlay.show(widget: spinner); + context.loaderOverlay.show(); } static void dismissLoadingDialog(BuildContext context) { diff --git a/lib/ffi.dart b/lib/ffi.dart index 90fe1881b..2c913bc34 100644 --- a/lib/ffi.dart +++ b/lib/ffi.dart @@ -52,11 +52,18 @@ Pointer ffiEmailAddress() => _bindings.emailAddress().cast(); Pointer ffiEmailExists(email) => _bindings.emailExists(email).cast(); -Pointer ffiRedeemResellerCode( - email, currency, deviceName, resellerCode) => - _bindings - .redeemResellerCode(email, currency, deviceName, resellerCode) - .cast(); +Pointer ffiRedeemResellerCode(email, currency, deviceName, resellerCode) { + final result = + _bindings.redeemResellerCode(email, currency, deviceName, resellerCode); + // Check for error + // it means you need to check r1 + if (result.r1 != nullptr) { + // Got error throw error to show error ui state + final errorCode = result.r1.cast().toDartString(); + throw PlatformException(code: errorCode, message: 'wrong_seller_code'.i18n); + } + return result.r0.cast(); +} Pointer ffiReferral() => _bindings.referral().cast(); @@ -86,14 +93,33 @@ Pointer ffiOnBoardingStatus() => Pointer ffiServerInfo() => _bindings.serverInfo().cast(); -Pointer ffiReportIssue(email, issueType, description) => - _bindings.reportIssue(email, issueType, description).cast(); +Future ffiReportIssue(List list) { + final email = list[0].toNativeUtf8(); + final issueType = list[1].toNativeUtf8(); + final description = list[2].toNativeUtf8(); + final result = _bindings.reportIssue(email as Pointer, issueType as Pointer, description as Pointer); + if (result.r1 != nullptr) { + // Got error throw error to show error ui state + final errorCode = result.r1.cast().toDartString(); + throw PlatformException( + code: errorCode, message: 'report_issue_error'.i18n); + } + return Future.value(); +} -Pointer ffiPaymentRedirect( - planID, currency, provider, email, deviceName) => - _bindings - .paymentRedirect(planID, currency, provider, email, deviceName) - .cast(); + +String ffiPaymentRedirect(planID, currency, provider, email, deviceName) { + final result = + _bindings.paymentRedirect(planID, currency, provider, email, deviceName); + if (result.r1 != nullptr) { + // Got error throw error to show error ui state + final errorCode = result.r1.cast().toDartString(); + throw PlatformException( + code: errorCode, + message: 'we_are_experiencing_technical_difficulties'.i18n); + } + return result.r0.cast().toDartString(); +} const String _libName = 'liblantern'; diff --git a/lib/plans/checkout.dart b/lib/plans/checkout.dart index 35d0a449f..4a939094b 100644 --- a/lib/plans/checkout.dart +++ b/lib/plans/checkout.dart @@ -158,27 +158,31 @@ class _CheckoutState extends State } Future openDesktopWebview() async { - String os = Platform.operatingSystem; - Locale locale = Localizations.localeOf(context); - final format = NumberFormat.simpleCurrency(locale: locale.toString()); - final currencyName = format.currencyName ?? "USD"; - final redirectUrl = await sessionModel.paymentRedirect( - widget.plan.id, - currencyName, - emailController.text, - "stripe", - os, - ); - switch (Platform.operatingSystem) { - case 'windows': - await AppBrowser.openWindowsWebview(redirectUrl); - break; - case 'macos': - final browser = AppBrowser(onClose: checkProUser); - await browser.openMacWebview(redirectUrl); - break; - default: - await context.pushRoute(AppWebview(url: redirectUrl)); + try { + String os = Platform.operatingSystem; + Locale locale = Localizations.localeOf(context); + final format = NumberFormat.simpleCurrency(locale: locale.toString()); + final currencyName = format.currencyName ?? "USD"; + final redirectUrl = await sessionModel.paymentRedirect( + widget.plan.id, + currencyName, + emailController.text, + "stripe", + os, + ); + switch (Platform.operatingSystem) { + case 'windows': + await AppBrowser.openWindowsWebview(redirectUrl); + break; + case 'macos': + final browser = AppBrowser(onClose: checkProUser); + await browser.openMacWebview(redirectUrl); + break; + default: + await context.pushRoute(AppWebview(url: redirectUrl)); + } + } catch (e) { + showError(context, error: e); } } diff --git a/lib/plans/reseller_checkout.dart b/lib/plans/reseller_checkout.dart index 8deb5c35c..a5e85f7a4 100644 --- a/lib/plans/reseller_checkout.dart +++ b/lib/plans/reseller_checkout.dart @@ -121,6 +121,9 @@ class _ResellerCodeCheckoutState extends State { controller: emailController, autovalidateMode: AutovalidateMode.disabled, label: 'Email'.i18n, + onChanged: (value) { + setState(() {}); + }, keyboardType: TextInputType.emailAddress, prefixIcon: const CAssetImage(path: ImagePaths.email), ), @@ -139,6 +142,9 @@ class _ResellerCodeCheckoutState extends State { //accounting for dashes controller: resellerCodeController, autovalidateMode: AutovalidateMode.disabled, + onChanged: (value) { + setState(() {}); + }, inputFormatters: [ResellerCodeFormatter()], label: 'Activation Code'.i18n, keyboardType: TextInputType.text, @@ -153,12 +159,12 @@ class _ResellerCodeCheckoutState extends State { TOS(copy: copy), // * resellerCodeCheckout Button( - disabled: emailController.value.text.isEmpty || - emailFieldKey.currentState?.validate() == false || - resellerCodeFieldKey.currentState?.validate() == - false, - text: copy, - onPressed: onRegisterPro), + disabled: emailController.value.text.isEmpty || + emailFieldKey.currentState?.validate() == false || + resellerCodeFieldKey.currentState?.validate() == false, + text: copy, + onPressed: onRegisterPro, + ), ], ) ], @@ -183,6 +189,8 @@ class _ResellerCodeCheckoutState extends State { context.loaderOverlay.hide(); showSuccessDialog(context, widget.isPro, true); } catch (error, stackTrace) { + print(stackTrace); + appLogger.e(error, stackTrace: stackTrace); context.loaderOverlay.hide(); CDialog.showError( context, diff --git a/pubspec.yaml b/pubspec.yaml index 12fd52c1c..6cdaebe43 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -129,6 +129,8 @@ dependencies: ffi: ^2.1.0 # Deeplink handling app_links: ^3.5.1 + # Animated loading border + animated_loading_border: ^0.0.2 # wakelock ^0.6.2 requires win32 ^2.0.0 or ^3.0.0