diff --git a/desktop/lib.go b/desktop/lib.go index e7e02891a..887beeae5 100644 --- a/desktop/lib.go +++ b/desktop/lib.go @@ -115,11 +115,12 @@ func start() { }() golog.SetPrepender(logging.Timestamped) + handleSignals(a) go func() { defer logging.Close() i18nInit(a) - runApp(a) + a.Run(true) err := a.WaitForExit() if err != nil { @@ -171,12 +172,12 @@ func fetchPayentMethodV4() error { //export sysProxyOn func sysProxyOn() { - a.SysproxyOn() + go a.SysproxyOn() } //export sysProxyOff func sysProxyOff() { - a.SysProxyOff() + go a.SysProxyOff() } //export selectedTab @@ -241,6 +242,11 @@ func paymentMethodsV4() *C.char { return C.CString(string(b)) } +func cachedUserData() (*protos.User, bool) { + uc := userConfig(a.Settings()) + return app.GetUserDataFast(context.Background(), uc.GetUserID()) +} + func getUserData() (*protos.User, error) { resp, err := proClient.UserData(context.Background()) if err != nil { @@ -253,21 +259,29 @@ func getUserData() (*protos.User, error) { return user, nil } +// tryCacheUserData retrieves the latest user data for the given user. +// It first checks the cache and if present returns the user data stored there +func tryCacheUserData() (*protos.User, error) { + if cacheUserData, isOldFound := cachedUserData(); isOldFound { + return cacheUserData, nil + } + return getUserData() +} + // this method is reposible for checking if the user has updated plan or bought plans // //export hasPlanUpdatedOrBuy func hasPlanUpdatedOrBuy() *C.char { //Get the cached user data log.Debugf("DEBUG: Checking if user has updated plan or bought new plan") - uc := userConfig(a.Settings()) - cahcheUserData, isOldFound := app.GetUserDataFast(context.Background(), uc.GetUserID()) + cacheUserData, isOldFound := cachedUserData() //Get latest user data resp, err := proClient.UserData(context.Background()) if err != nil { return sendError(err) } if isOldFound { - if cahcheUserData.Expiration < resp.User.Expiration { + if cacheUserData.Expiration < resp.User.Expiration { // New data has a later expiration return C.CString(string("true")) } @@ -277,7 +291,7 @@ func hasPlanUpdatedOrBuy() *C.char { //export devices func devices() *C.char { - user, err := getUserData() + user, err := tryCacheUserData() if err != nil { return sendError(err) } @@ -320,7 +334,7 @@ func removeDevice(deviceId *C.char) *C.char { //export expiryDate func expiryDate() *C.char { - user, err := getUserData() + user, err := tryCacheUserData() if err != nil { return sendError(err) } @@ -518,6 +532,11 @@ func paymentRedirect(planID, currency, provider, email, deviceName *C.char) *C.c return sendJson(resp) } +//export exitApp +func exitApp() { + a.Exit(nil) +} + //export developmentMode func developmentMode() *C.char { return C.CString("false") @@ -641,12 +660,6 @@ func configDir(flags *flashlight.Flags) string { return cdir } -func runApp(a *app.App) { - // Schedule cleanup actions - handleSignals(a) - 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() @@ -690,6 +703,7 @@ func handleSignals(a *app.App) { go func() { s := <-c log.Debugf("Got signal \"%s\", exiting...", s) + a.Exit(nil) }() } diff --git a/lib/account/device_linking/authorize_device_via_email.dart b/lib/account/device_linking/authorize_device_via_email.dart index d202b1b46..b65d494e5 100644 --- a/lib/account/device_linking/authorize_device_via_email.dart +++ b/lib/account/device_linking/authorize_device_via_email.dart @@ -31,10 +31,6 @@ class AuthorizeDeviceViaEmail extends StatelessWidget { controller: emailController, autovalidateMode: AutovalidateMode.disabled, //TODO: this throws an error when we set it to AutovalidateMode.onUserInteraction - contentPadding: const EdgeInsetsDirectional.only( - top: 8.0, - bottom: 8.0, - ), label: 'Email'.i18n, helperText: 'auth_email_helper_text'.i18n, keyboardType: TextInputType.emailAddress, diff --git a/lib/account/report_issue.dart b/lib/account/report_issue.dart index f27effdf0..860c177de 100644 --- a/lib/account/report_issue.dart +++ b/lib/account/report_issue.dart @@ -1,5 +1,4 @@ // ignore_for_file: use_build_context_synchronously - import 'package:email_validator/email_validator.dart'; import 'package:lantern/common/common.dart'; import 'package:lantern/common/ui/app_loading_dialog.dart'; @@ -88,10 +87,6 @@ class _ReportIssueState extends State { initialValue: emailAddress, controller: emailController, autovalidateMode: AutovalidateMode.disabled, - contentPadding: const EdgeInsetsDirectional.only( - top: 8.0, - bottom: 8.0, - ), label: 'email'.i18n, onChanged: (value) { setState(() {}); @@ -138,6 +133,12 @@ class _ReportIssueState extends State { issueController.text = newValue!; }); }, + padding: isDesktop() + ? const EdgeInsetsDirectional.only( + top: 8, + bottom: 8, + ) + : const EdgeInsetsDirectional.all(0), items: [ 'cannot_access_blocked_sites'.i18n, 'cannot_complete_purchase'.i18n, @@ -161,8 +162,9 @@ class _ReportIssueState extends State { child: CTextField( tooltipMessage: 'report_description'.i18n, controller: descController, - contentPadding: const EdgeInsetsDirectional.all(8.0), - label: '', + contentPadding: isDesktop() + ? const EdgeInsetsDirectional.all(16.0) + : const EdgeInsetsDirectional.all(8.0), hintText: 'issue_description'.i18n, autovalidateMode: AutovalidateMode.disabled, maxLines: 8, diff --git a/lib/common/common.dart b/lib/common/common.dart index b836b2c9e..238c7feff 100644 --- a/lib/common/common.dart +++ b/lib/common/common.dart @@ -92,6 +92,7 @@ export 'ui/text_highlighter.dart'; export 'ui/text_styles.dart'; export 'ui/transitions.dart'; export 'app_secret.dart'; + final appLogger = Logger( printer: PrettyPrinter( methodCount: 0, @@ -110,4 +111,4 @@ bool isMobile() { bool isDesktop() { return Platform.isMacOS || Platform.isLinux || Platform.isWindows; -} \ No newline at end of file +} diff --git a/lib/common/ui/custom/text_field.dart b/lib/common/ui/custom/text_field.dart index 2bad57437..ef53b7a44 100644 --- a/lib/common/ui/custom/text_field.dart +++ b/lib/common/ui/custom/text_field.dart @@ -7,7 +7,7 @@ import 'package:lantern/common/common.dart'; class CTextField extends StatefulWidget { late final CustomTextEditingController controller; late final String? initialValue; - late final dynamic label; + late final dynamic? label; late final String? helperText; late final String? hintText; late final Widget? prefixIcon; @@ -36,7 +36,7 @@ class CTextField extends StatefulWidget { CTextField({ required this.controller, this.initialValue, - required this.label, + this.label, this.helperText, this.hintText, this.prefixIcon, @@ -154,7 +154,15 @@ class _CTextFieldState extends State { widget.textCapitalization ?? TextCapitalization.none, decoration: InputDecoration( contentPadding: widget.contentPadding ?? - const EdgeInsetsDirectional.all(0), + (isDesktop() + ? const EdgeInsetsDirectional.only( + top: 24, + bottom: 24, + ) + : const EdgeInsetsDirectional.only( + top: 8, + bottom: 8, + )), isDense: true, floatingLabelBehavior: FloatingLabelBehavior.never, // we handle floating labels using our custom method below @@ -204,29 +212,30 @@ class _CTextFieldState extends State { ), ), // * Label - (widget.label is String) - ? Container( - margin: const EdgeInsetsDirectional.only(start: 11), - padding: EdgeInsetsDirectional.only( - start: hasFocus ? 2 : 0, - end: hasFocus ? 2 : 0, - ), - color: white, - child: !hasFocus && widget.controller.value.text.isEmpty - ? Container() - : CText( - widget.label, - style: CTextStyle( - fontSize: 12, - lineHeight: 12, - color: fieldKey.currentState?.mounted == true && - fieldKey.currentState?.hasError == true - ? indicatorRed - : blue4, + if (widget.label != null) + (widget.label is String) + ? Container( + margin: const EdgeInsetsDirectional.only(start: 11), + padding: EdgeInsetsDirectional.only( + start: hasFocus ? 2 : 0, + end: hasFocus ? 2 : 0, + ), + color: white, + child: !hasFocus && widget.controller.value.text.isEmpty + ? Container() + : CText( + widget.label, + style: CTextStyle( + fontSize: 12, + lineHeight: 12, + color: fieldKey.currentState?.mounted == true && + fieldKey.currentState?.hasError == true + ? indicatorRed + : blue4, + ), ), - ), - ) - : widget.label, + ) + : widget.label, ], ); } diff --git a/lib/ffi.dart b/lib/ffi.dart index 118b2be98..fb9fd108b 100644 --- a/lib/ffi.dart +++ b/lib/ffi.dart @@ -21,6 +21,11 @@ void setLang(lang) => _bindings.setSelectLang(lang); String websocketAddr() => _bindings.websocketAddr().cast().toDartString(); +void ffiExit() { + _bindings.exitApp(); + SystemChannels.platform.invokeMethod('SystemNavigator.pop'); +} + Pointer ffiVpnStatus() => _bindings.vpnStatus().cast(); Pointer ffiSelectedTab() => _bindings.selectedTab().cast(); diff --git a/lib/home.dart b/lib/home.dart index ef3dae1a8..ab8a0d6ea 100644 --- a/lib/home.dart +++ b/lib/home.dart @@ -31,58 +31,65 @@ class _HomePageState extends State with TrayListener, WindowListener { MethodChannel? navigationChannel; Function()? _cancelEventSubscription; - _HomePageState(); - @override void initState() { - if (isDesktop()) { + _startupSequence(); + super.initState(); + } + + void _startupSequence() { + if (isMobile()) { + // This is a mobile device + channelListener(); + } else { + // This is a desktop device setupTrayManager(); windowManager.addListener(this); _init(); } - super.initState(); - if (isMobile()) { - mainMethodChannel = const MethodChannel('lantern_method_channel'); - navigationChannel = const MethodChannel('navigation'); - sessionModel.getChatEnabled().then((chatEnabled) { - if (chatEnabled) { - messagingModel - .shouldShowTryLanternChatModal() - .then((shouldShowModal) async { - if (shouldShowModal) { - // open VPN tab - await sessionModel.setSelectedTab(TAB_VPN); - // show Try Lantern Chat dialog - await context.router - .push(FullScreenDialogPage(widget: TryLanternChat())); - } - }); - } - }); + } - navigationChannel?.setMethodCallHandler(_handleNativeNavigationRequest); - // Let back-end know that we're ready to handle navigation - navigationChannel?.invokeListMethod('ready'); - _cancelEventSubscription = - sessionModel.eventManager.subscribe(Event.All, (event, params) { - switch (event) { - case Event.SurveyAvailable: - // show survey snackbar - showSuerySnackbar( - context: context, - buttonText: params['buttonText'] as String, - message: params['message'] as String, - onPressed: () { - mainMethodChannel?.invokeMethod('showLastSurvey'); - ScaffoldMessenger.of(context).hideCurrentSnackBar(); - }); + void channelListener() { + mainMethodChannel = const MethodChannel('lantern_method_channel'); + navigationChannel = const MethodChannel('navigation'); + sessionModel.getChatEnabled().then((chatEnabled) { + if (chatEnabled) { + messagingModel + .shouldShowTryLanternChatModal() + .then((shouldShowModal) async { + if (shouldShowModal) { + // open VPN tab + await sessionModel.setSelectedTab(TAB_VPN); + // show Try Lantern Chat dialog + await context.router + .push(FullScreenDialogPage(widget: TryLanternChat())); + } + }); + } + }); - break; - default: - break; - } - }); - } + navigationChannel?.setMethodCallHandler(_handleNativeNavigationRequest); + // Let back-end know that we're ready to handle navigation + navigationChannel?.invokeListMethod('ready'); + _cancelEventSubscription = + sessionModel.eventManager.subscribe(Event.All, (event, params) { + switch (event) { + case Event.SurveyAvailable: + // show survey snackbar + showSuerySnackbar( + context: context, + buttonText: params['buttonText'] as String, + message: params['message'] as String, + onPressed: () { + mainMethodChannel?.invokeMethod('showLastSurvey'); + ScaffoldMessenger.of(context).hideCurrentSnackBar(); + }); + + break; + default: + break; + } + }); } void _init() async { @@ -111,11 +118,11 @@ class _HomePageState extends State with TrayListener, WindowListener { void onTrayMenuItemClick(MenuItem menuItem) async { switch (menuItem.key) { case 'show': - windowManager.show(); + windowManager.focus(); case 'exit': - SystemChannels.platform.invokeMethod('SystemNavigator.pop'); - case 'status': - final status = await ffiVpnStatus().toDartString(); + ffiExit(); + case 'status': + final status = ffiVpnStatus().toDartString(); bool isConnected = status == "connected"; if (isConnected) { sysProxyOff(); @@ -157,6 +164,11 @@ class _HomePageState extends State with TrayListener, WindowListener { } } + @override + void onWindowEvent(String eventName) { + print('[WindowManager] onWindowEvent: $eventName'); + } + Future _handleNativeNavigationRequest(MethodCall methodCall) async { switch (methodCall.method) { case 'openConversation': @@ -168,6 +180,12 @@ class _HomePageState extends State with TrayListener, WindowListener { } } + @override + void onWindowFocus() { + print('[WindowManager] onWindowFocus'); + setState(() {}); + } + @override void dispose() { if (isDesktop()) { diff --git a/lib/plans/checkout.dart b/lib/plans/checkout.dart index e87f5fced..0d17978a6 100644 --- a/lib/plans/checkout.dart +++ b/lib/plans/checkout.dart @@ -124,10 +124,6 @@ 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), @@ -140,10 +136,6 @@ 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();