diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml
index d762758518..733217ec1e 100644
--- a/android/app/src/main/AndroidManifest.xml
+++ b/android/app/src/main/AndroidManifest.xml
@@ -21,6 +21,7 @@
+
result.success(LanternApp.session.hTTPAddr)
+
"isPlayServiceAvailable" -> {
result.success(LanternApp.getInAppBilling().isPlayStoreAvailable())
}
@@ -249,6 +251,7 @@ class SessionModel internal constructor(
} else {
startResult!!.httpAddr
}
+
val sOCKS5Addr: String
get() = if (startResult == null) {
""
diff --git a/android/build.gradle b/android/build.gradle
index f14804e0e2..c7f60b45e9 100644
--- a/android/build.gradle
+++ b/android/build.gradle
@@ -51,6 +51,14 @@ allprojects {
maven { url 'https://jitpack.io' }
}
+ /// TODO: remove. hotfix for flutter_inappwebview (using androidx.webkit:webkit:1.8.0)
+ /// webview_flutter_android: ^3.16.2 androidx.webkit:webkit ^1.9.0 remove SUPPRESS_ERROR_PAGE
+ configurations.all {
+ resolutionStrategy {
+ force 'androidx.webkit:webkit:1.8.0'
+ }
+ }
+
// This code fixes a 'namespace not specified' error upgrading AGP to >= 8.x.x.
subprojects { subproject ->
subproject.tasks.whenTaskAdded {
diff --git a/android/gradle.properties b/android/gradle.properties
index 25b7c18522..06952d7610 100644
--- a/android/gradle.properties
+++ b/android/gradle.properties
@@ -1,4 +1,4 @@
-org.gradle.jvmargs=-Xmx4048m -Dfile.encoding=UTF-8
+org.gradle.jvmargs=-Xmx4048m
android.useAndroidX=true
android.enableJetifier=true
org.gradle.caching=true
@@ -9,5 +9,4 @@ kotlin.jvm.target.validation.mode = IGNORE
# Additional Settings
org.gradle.daemon=true
-org.gradle.parallel=true
org.gradle.configureondemand=true
\ No newline at end of file
diff --git a/desktop/app/config.go b/desktop/app/config.go
index c5c61cf5b6..44a7097084 100644
--- a/desktop/app/config.go
+++ b/desktop/app/config.go
@@ -29,6 +29,7 @@ type ChatOptions struct {
type ConfigOptions struct {
DevelopmentMode bool `json:"developmentMode"`
ReplicaAddr string `json:"replicaAddr"`
+ HttpProxyAddr string `json:"httpProxyAddr"`
AuthEnabled bool `json:"authEnabled"`
ChatEnabled bool `json:"chatEnabled"`
SplitTunneling bool `json:"splitTunneling"`
@@ -83,6 +84,7 @@ func (app *App) sendConfigOptions() {
DevelopmentMode: common.IsDevEnvironment(),
AppVersion: common.ApplicationVersion,
ReplicaAddr: "",
+ HttpProxyAddr: app.settings.GetAddr(),
AuthEnabled: authEnabled(app),
ChatEnabled: false,
SplitTunneling: false,
diff --git a/lib/app.dart b/lib/app.dart
index bd49b5c00e..7f42a927e9 100644
--- a/lib/app.dart
+++ b/lib/app.dart
@@ -90,6 +90,7 @@ class _LanternAppState extends State
if (!hasConnection) {
return;
}
+
final vpnConnected = await vpnModel.isVpnConnected();
/// If vpn is not connected then we should not show the connectivity warning
diff --git a/lib/common/common.dart b/lib/common/common.dart
index 619506ead4..1c50c86e6f 100644
--- a/lib/common/common.dart
+++ b/lib/common/common.dart
@@ -55,6 +55,7 @@ export 'once.dart';
export 'session_model.dart';
export 'single_value_subscriber.dart';
export 'ui/app_buttons.dart';
+export 'ui/app_webview.dart';
export 'ui/audio.dart';
export 'ui/base_screen.dart';
export 'ui/basic_memory_image.dart';
diff --git a/lib/common/config.dart b/lib/common/config.dart
index 978219825e..4ae77ad228 100644
--- a/lib/common/config.dart
+++ b/lib/common/config.dart
@@ -39,6 +39,7 @@ class ConfigOptions {
final String sdkVersion;
final String deviceId;
final String expirationDate;
+ final String httpProxyAddr;
final Map? plans;
Devices devices = Devices();
@@ -57,6 +58,7 @@ class ConfigOptions {
this.fetchedProxiesConfig = false,
this.expirationDate = '',
this.sdkVersion = '',
+ this.httpProxyAddr = '',
this.deviceId = '',
this.plans = null,
this.paymentMethods = null,
@@ -82,6 +84,7 @@ class ConfigOptions {
developmentMode: parsedJson['developmentMode'],
authEnabled: parsedJson['authEnabled'],
chatEnabled: parsedJson['chatEnabled'],
+ httpProxyAddr: parsedJson['httpProxyAddr'],
splitTunneling: parsedJson['splitTunneling'],
hasSucceedingProxy: parsedJson['hasSucceedingProxy'],
fetchedGlobalConfig: parsedJson['fetchedGlobalConfig'],
diff --git a/lib/common/session_model.dart b/lib/common/session_model.dart
index 544aae4354..7b7f501e79 100644
--- a/lib/common/session_model.dart
+++ b/lib/common/session_model.dart
@@ -26,7 +26,7 @@ class SessionModel extends Model {
late ValueNotifier country;
late ValueNotifier referralNotifier;
late ValueNotifier deviceIdNotifier;
- ValueNotifier langNotifier=ValueNotifier('en_us');
+ ValueNotifier langNotifier = ValueNotifier('en_us');
late ValueNotifier proxyAllNotifier;
late ValueNotifier serverInfoNotifier;
late ValueNotifier userEmail;
@@ -431,6 +431,10 @@ class SessionModel extends Model {
throw Exception("Not supported on mobile");
}
+ Future proxyAddr() async {
+ return await methodChannel.invokeMethod('proxyAddr', {});
+ }
+
/// Auth API end
Future getCountryCode() async {
return await methodChannel
@@ -864,12 +868,6 @@ class SessionModel extends Model {
await compute(LanternFFI.emailExists, email);
}
- Future openWebview(String url) {
- return methodChannel.invokeMethod('openWebview', {
- 'url': url,
- });
- }
-
Future refreshAppsList() async {
await methodChannel.invokeMethod('refreshAppsList');
}
diff --git a/lib/common/ui/app_webview.dart b/lib/common/ui/app_webview.dart
index 7ff629a252..b047d4ed90 100644
--- a/lib/common/ui/app_webview.dart
+++ b/lib/common/ui/app_webview.dart
@@ -20,7 +20,7 @@ class AppWebView extends StatefulWidget {
class _AppWebViewState extends State {
final InAppWebViewSettings settings = InAppWebViewSettings(
- isInspectable: false,
+ isInspectable: kDebugMode,
javaScriptEnabled: true,
mediaPlaybackRequiresUserGesture: false,
allowsInlineMediaPlayback: false,
@@ -47,15 +47,41 @@ class _AppWebViewState extends State {
class AppBrowser extends InAppBrowser {
final VoidCallback? onClose;
- final InAppBrowserClassSettings settings = InAppBrowserClassSettings(
- browserSettings: InAppBrowserSettings(hideUrlBar: true),
- webViewSettings: InAppWebViewSettings(
- javaScriptEnabled: true, isInspectable: kDebugMode));
+
+ static final InAppBrowserClassSettings settings = InAppBrowserClassSettings(
+ browserSettings: InAppBrowserSettings(
+ hideTitleBar: true,
+ hideToolbarBottom: true,
+ presentationStyle: ModalPresentationStyle.POPOVER,
+ ),
+ webViewSettings: InAppWebViewSettings(
+ sharedCookiesEnabled: true,
+ javaScriptEnabled: true,
+ useOnDownloadStart: true,
+ useShouldOverrideUrlLoading: true,
+ isInspectable: kDebugMode,
+ ),
+ );
AppBrowser({
- required this.onClose,
+ this.onClose,
});
+ static Future setProxyAddr() async {
+ var proxyAvailable =
+ await WebViewFeature.isFeatureSupported(WebViewFeature.PROXY_OVERRIDE);
+ if (proxyAvailable) {
+ ProxyController proxyController = ProxyController.instance();
+ final proxyAddr = await sessionModel.proxyAddr();
+ await proxyController.clearProxyOverride();
+ await proxyController.setProxyOverride(
+ settings: ProxySettings(
+ proxyRules: [ProxyRule(url: "http://$proxyAddr")],
+ bypassRules: [],
+ ));
+ }
+ }
+
@override
Future onBrowserCreated() async {
print("Browser created");
@@ -76,13 +102,24 @@ class AppBrowser extends InAppBrowser {
print("Can't load ${request.url}.. Error: ${error.description}");
}
+ @override
+ Future shouldOverrideUrlLoading(
+ navigationAction) async {
+ final url = navigationAction.request.url!;
+ if (url.scheme.startsWith("alipay")) {
+ await launchUrl(url, mode: LaunchMode.platformDefault);
+ return NavigationActionPolicy.CANCEL;
+ }
+ return NavigationActionPolicy.ALLOW;
+ }
+
@override
void onProgressChanged(progress) {
print("Progress: $progress");
}
@override
- void onExit() {
+ Future onExit() async {
print("Browser closed");
onClose?.call();
}
@@ -115,7 +152,12 @@ class AppBrowser extends InAppBrowser {
InAppBrowser.openWithSystemBrowser(url: WebUri(url));
break;
default:
- await launchUrl(Uri.parse(url), mode: LaunchMode.platformDefault);
+ await setProxyAddr();
+ final instance = AppBrowser();
+ await instance.openUrlRequest(
+ urlRequest: URLRequest(url: WebUri(url)),
+ settings: settings,
+ );
break;
}
}
diff --git a/lib/plans/checkout.dart b/lib/plans/checkout.dart
index 24c14478ec..a8921f55be 100644
--- a/lib/plans/checkout.dart
+++ b/lib/plans/checkout.dart
@@ -187,12 +187,13 @@ class _CheckoutState extends State
Iterable> paymentMethods,
) {
var widgets = [];
- for (final paymentMethod in paymentMethods) {
+ for (final pathAndValue in sortProviders(paymentMethods)) {
+ final paymentMethod = pathAndValue.value;
if (widgets.length == 2) {
- widgets.add(options());
+ if (paymentMethods.length != 2) widgets.add(options());
if (!showMoreOptions) break;
}
- widgets.addAll(paymentProviders(paymentMethod.value));
+ widgets.addAll(paymentProviders(paymentMethod));
}
return widgets;
}
@@ -286,7 +287,7 @@ class _CheckoutState extends State
context.loaderOverlay.hide();
final btcPayURL = value;
- await sessionModel.openWebview(btcPayURL);
+ await AppBrowser.openWebview(btcPayURL);
} catch (error, stackTrace) {
context.loaderOverlay.hide();
showError(context, error: error, stackTrace: stackTrace);
@@ -304,7 +305,7 @@ class _CheckoutState extends State
context.loaderOverlay.hide();
final froPayURL = value;
- await sessionModel.openWebview(froPayURL);
+ await AppBrowser.openWebview(froPayURL);
} catch (error, stackTrace) {
context.loaderOverlay.hide();
showError(context, error: error, stackTrace: stackTrace);
@@ -339,14 +340,13 @@ class _CheckoutState extends State
context.loaderOverlay.hide();
final shepherdURL = value;
- await sessionModel.openWebview(shepherdURL);
+ await AppBrowser.openWebview(shepherdURL);
} catch (error, stackTrace) {
context.loaderOverlay.hide();
showError(context, error: error, stackTrace: stackTrace);
}
}
-
// This methods is responsible for polling for user data
// so if user has done payment or renew plans and show
void hasPlansUpdateOrBuy() {
@@ -403,7 +403,7 @@ class _CheckoutState extends State
context.loaderOverlay.hide();
final btcPayURL = value;
- await sessionModel.openWebview(btcPayURL);
+ await AppBrowser.openWebview(btcPayURL);
} catch (error, stackTrace) {
context.loaderOverlay.hide();
showError(context, error: error, stackTrace: stackTrace);
@@ -464,9 +464,10 @@ class _CheckoutState extends State
assert(widget.authFlow != null, 'authFlow is null');
switch (widget.authFlow!) {
case AuthFlow.createAccount:
- /// There is edge case where user is signup with email and password but not pro
- /// this happens when does restore purchase on other device so older device
- /// does not have pro status but have email and password
+
+ /// There is edge case where user is signup with email and password but not pro
+ /// this happens when does restore purchase on other device so older device
+ /// does not have pro status but have email and password
if (sessionModel.hasUserSignedInNotifier.value ?? false) {
showSuccessDialog(context, widget.isPro);
return;
@@ -493,7 +494,7 @@ class _CheckoutState extends State
case AuthFlow.updateAccount:
// TODO: Handle this case.
case AuthFlow.restoreAccount:
- // TODO: Handle this case.
+ // TODO: Handle this case.
}
}
}
diff --git a/lib/plans/checkout_legacy.dart b/lib/plans/checkout_legacy.dart
index 423f499f1d..9192eaf0f6 100644
--- a/lib/plans/checkout_legacy.dart
+++ b/lib/plans/checkout_legacy.dart
@@ -23,7 +23,6 @@ class CheckoutLegacy extends StatefulWidget {
class _CheckoutLegacyState extends State
with SingleTickerProviderStateMixin {
-
bool showMoreOptions = false;
bool showContinueButton = false;
@@ -34,20 +33,20 @@ class _CheckoutLegacyState extends State
validator: (value) => value!.isEmpty
? null
: EmailValidator.validate(value ?? '')
- ? null
- : 'please_enter_a_valid_email_address'.i18n,
+ ? null
+ : 'please_enter_a_valid_email_address'.i18n,
);
late final refCodeController = CustomTextEditingController(
formKey: refCodeFieldKey,
validator: (value) =>
- // only allow letters and numbers as well as 6 <= length <= 13
- value == null ||
- value.characters.isEmpty ||
- RegExp(r'^[a-zA-Z0-9]*$').hasMatch(value) &&
- (6 <= value.characters.length &&
- value.characters.length <= 13)
- ? null
- : 'invalid_or_incomplete_referral_code'.i18n,
+ // only allow letters and numbers as well as 6 <= length <= 13
+ value == null ||
+ value.characters.isEmpty ||
+ RegExp(r'^[a-zA-Z0-9]*$').hasMatch(value) &&
+ (6 <= value.characters.length &&
+ value.characters.length <= 13)
+ ? null
+ : 'invalid_or_incomplete_referral_code'.i18n,
);
var isRefCodeFieldShowing = false;
@@ -84,13 +83,13 @@ class _CheckoutLegacyState extends State
padVertical: true,
body: sessionModel.paymentMethods(
builder: (
- context,
- Iterable> paymentMethods,
- Widget? child,
- ) {
+ context,
+ Iterable> paymentMethods,
+ Widget? child,
+ ) {
defaultProviderIfNecessary(paymentMethods.toList());
- return sessionModel.emailAddress((context, emailAddress, child) =>
- Column(
+ return sessionModel.emailAddress(
+ (context, emailAddress, child) => Column(
children: [
PlanStep(
stepNum: '2',
@@ -127,7 +126,7 @@ class _CheckoutLegacyState extends State
),
Container(
padding:
- const EdgeInsetsDirectional.only(top: 16, bottom: 16),
+ const EdgeInsetsDirectional.only(top: 16, bottom: 16),
width: MediaQuery.of(context).size.width,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
@@ -192,48 +191,50 @@ class _CheckoutLegacyState extends State
}
Widget options() => CInkWell(
- onTap: () {
- setState(() {
- showMoreOptions = !showMoreOptions;
- });
- },
- child: Container(
- padding: const EdgeInsetsDirectional.only(bottom: 24),
- child: Row(
- mainAxisAlignment: MainAxisAlignment.center,
- crossAxisAlignment: CrossAxisAlignment.center,
- children: [
- CText(
- showMoreOptions ? 'fewer_options'.i18n : 'more_options'.i18n,
- style: tsBody1,
- ),
- const Padding(
- padding: EdgeInsetsDirectional.only(start: 8),
- child: CAssetImage(
- path: ImagePaths.down_arrow,
- ),
+ onTap: () {
+ setState(() {
+ showMoreOptions = !showMoreOptions;
+ });
+ },
+ child: Container(
+ padding: const EdgeInsetsDirectional.only(bottom: 24),
+ child: Row(
+ mainAxisAlignment: MainAxisAlignment.center,
+ crossAxisAlignment: CrossAxisAlignment.center,
+ children: [
+ CText(
+ showMoreOptions ? 'fewer_options'.i18n : 'more_options'.i18n,
+ style: tsBody1,
+ ),
+ const Padding(
+ padding: EdgeInsetsDirectional.only(start: 8),
+ child: CAssetImage(
+ path: ImagePaths.down_arrow,
+ ),
+ ),
+ ],
),
- ],
- ),
- ),
- );
+ ),
+ );
List paymentOptions(
- Iterable> paymentMethods,
- ) {
+ Iterable> paymentMethods,
+ ) {
var widgets = [];
- for (final paymentMethod in paymentMethods) {
+ for (final pathAndValue in sortProviders(paymentMethods)) {
+ final paymentMethod = pathAndValue.value;
if (widgets.length == 2) {
widgets.add(options());
if (!showMoreOptions) break;
}
- widgets.addAll(paymentProviders(paymentMethod.value));
+ widgets.addAll(paymentProviders(paymentMethod));
}
return widgets;
}
List paymentProviders(PaymentMethod paymentMethods) {
var providers = [];
+
for (final provider in paymentMethods.providers) {
providers.add(
PaymentProvider(
@@ -263,11 +264,10 @@ class _CheckoutLegacyState extends State
//Class methods
void selectPaymentProvider(Providers provider) {
setState(
- () => selectedPaymentProvider = provider,
+ () => selectedPaymentProvider = provider,
);
}
-
Future onContinueTapped() async {
var refCode = refCodeController.value;
try {
@@ -343,7 +343,7 @@ class _CheckoutLegacyState extends State
context.loaderOverlay.hide();
final btcPayURL = value;
- await sessionModel.openWebview(btcPayURL);
+ await AppBrowser.openWebview(btcPayURL);
} catch (error, stackTrace) {
context.loaderOverlay.hide();
showError(context, error: error, stackTrace: stackTrace);
@@ -361,14 +361,13 @@ class _CheckoutLegacyState extends State
context.loaderOverlay.hide();
final froPayURL = value;
- await sessionModel.openWebview(froPayURL);
+ await AppBrowser.openWebview(froPayURL);
} catch (error, stackTrace) {
context.loaderOverlay.hide();
showError(context, error: error, stackTrace: stackTrace);
}
}
-
void _proceedWithShepherd() async {
try {
context.loaderOverlay.show();
@@ -380,7 +379,7 @@ class _CheckoutLegacyState extends State
context.loaderOverlay.hide();
final shepherdURL = value;
- await sessionModel.openWebview(shepherdURL);
+ await AppBrowser.openWebview(shepherdURL);
} catch (error, stackTrace) {
context.loaderOverlay.hide();
showError(context, error: error, stackTrace: stackTrace);
@@ -393,7 +392,7 @@ class _CheckoutLegacyState extends State
appLogger.i("calling hasPlansUpdateOrBuy to update plans or buy");
try {
retry(
- () async {
+ () async {
/// Polling for userData that user has updates plans or buy
final plansUpdated = await sessionModel.hasUpdatePlansOrBuy();
if (plansUpdated) {
@@ -443,7 +442,7 @@ class _CheckoutLegacyState extends State
context.loaderOverlay.hide();
final btcPayURL = value;
- await sessionModel.openWebview(btcPayURL);
+ await AppBrowser.openWebview(btcPayURL);
} catch (error, stackTrace) {
context.loaderOverlay.hide();
showError(context, error: error, stackTrace: stackTrace);
@@ -483,5 +482,4 @@ class _CheckoutLegacyState extends State
//If needed to change default value changing to from server
selectedPaymentProvider = paymentMethod.providers[0].name.toPaymentEnum();
}
-
}
diff --git a/lib/plans/utils.dart b/lib/plans/utils.dart
index ef68417178..b1b6d314dd 100644
--- a/lib/plans/utils.dart
+++ b/lib/plans/utils.dart
@@ -7,13 +7,13 @@ import 'package:lantern/common/ui/app_webview.dart';
const defaultTimeoutDuration = Duration(seconds: 10);
bool isProdPlay() {
- if (sessionModel.isStoreVersion.value ??false) {
+ if (sessionModel.isStoreVersion.value ?? false) {
return true;
}
- if (sessionModel.isTestPlayVersion.value ??false) {
+ if (sessionModel.isTestPlayVersion.value ?? false) {
return true;
}
- return false;
+ return false;
}
const lanternStarLogo = CAssetImage(
@@ -160,8 +160,11 @@ Future openDesktopPaymentWebview(
}
}
-
-
+List> sortProviders(
+ Iterable> paymentMethods,
+) =>
+ paymentMethods.toList()
+ ..sort((a, b) => a.value.method.compareTo(b.value.method));
Plan planFromJson(Map item) {
print("called plans $item");
diff --git a/pubspec.lock b/pubspec.lock
index 2c73d77a0f..3c260661af 100644
--- a/pubspec.lock
+++ b/pubspec.lock
@@ -2126,10 +2126,10 @@ packages:
dependency: transitive
description:
name: vm_service
- sha256: f652077d0bdf60abe4c1f6377448e8655008eef28f128bc023f7b5e8dfeb48fc
+ sha256: "5c5f338a667b4c644744b661f309fb8080bb94b18a7e91ef1dbd343bed00ed6d"
url: "https://pub.dev"
source: hosted
- version: "14.2.4"
+ version: "14.2.5"
watcher:
dependency: transitive
description:
@@ -2190,10 +2190,10 @@ packages:
dependency: transitive
description:
name: webview_flutter_android
- sha256: "6e64fcb1c19d92024da8f33503aaeeda35825d77142c01d0ea2aa32edc79fdc8"
+ sha256: dad3313c9ead95517bb1cae5e1c9d20ba83729d5a59e5e83c0a2d66203f27f91
url: "https://pub.dev"
source: hosted
- version: "3.16.7"
+ version: "3.16.1"
webview_flutter_platform_interface:
dependency: transitive
description:
diff --git a/pubspec.yaml b/pubspec.yaml
index af6c3c02e5..064dcb5bb9 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -157,12 +157,6 @@ dev_dependencies:
# following page: https://dart.dev/tools/pub/pubspec
dependency_overrides:
- # TODO: Recheck once flutter_inappwebview version >6.0.0 is released
- flutter_inappwebview_android:
- git:
- url: https://github.com/holzgeist/flutter_inappwebview
- path: flutter_inappwebview_android
- ref: d89b1d32638b49dfc58c4b7c84153be0c269d057
flutter_inappwebview_macos:
git:
url: https://github.com/andychucs/flutter_inappwebview.git