Skip to content

Commit

Permalink
Handle language changes over websocket channel (#1032)
Browse files Browse the repository at this point in the history
* listen for language changes over websocket channel

* only set user as pro if status is not null and active

* use appLogger for error logging

* Add wsMessageProp utility method

* refresh pro user data after redeeming reseller code

* re-enable checkout page on desktop

* rename method and add comment

* add comments
  • Loading branch information
atavism authored Mar 29, 2024
1 parent 536bb23 commit ff9ca6e
Show file tree
Hide file tree
Showing 10 changed files with 63 additions and 41 deletions.
6 changes: 6 additions & 0 deletions desktop/app/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -407,6 +407,12 @@ func (app *App) GetLanguage() string {
// SetLanguage sets the user language
func (app *App) SetLanguage(lang string) {
app.settings.SetLanguage(lang)
if app.ws != nil {
app.ws.SendMessage("pro", map[string]interface{}{
"type": "pro",
"language": lang,
})
}
}

// OnSettingChange sets a callback cb to get called when attr is changed from server.
Expand Down
7 changes: 6 additions & 1 deletion desktop/app/pro.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package app

import (
"encoding/json"
"time"

"github.com/getlantern/errors"
Expand Down Expand Up @@ -107,8 +108,12 @@ func (app *App) servePro(channel ws.UIChannel) error {
}
}
service, err := channel.Register("pro", helloFn)
if err != nil {
return err
}
pro.OnUserData(func(current *client.User, new *client.User) {
log.Debugf("Sending updated user data to all clients: %v", new)
b, _ := json.Marshal(new)
log.Debugf("Sending updated user data to all clients: %s", string(b))
service.Out <- new
})
return err
Expand Down
2 changes: 1 addition & 1 deletion desktop/app/sysproxy.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ func (app *App) notifyConnectionStatus(isConnected bool) {
}
}

func (app *App) SetSysProxy(_sysproxyOff func() error) {
func (app *App) SetSysProxy(_sysproxyOff func() error) {
app.mu.Lock()
defer app.mu.Unlock()
app._sysproxyOff = _sysproxyOff
Expand Down
2 changes: 1 addition & 1 deletion desktop/lib.go
Original file line number Diff line number Diff line change
Expand Up @@ -310,7 +310,7 @@ func lang() *C.char {

//export setSelectLang
func setSelectLang(lang *C.char) {
a.Settings().SetLanguage(C.GoString(lang))
a.SetLanguage(C.GoString(lang))
}

//export country
Expand Down
11 changes: 11 additions & 0 deletions desktop/ws/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ type UIChannel interface {
// Register registers a service with an optional helloFn to send initial
// message to connected clients.
Register(t string, helloFn helloFnType) (*Service, error)
// SendMessage sends data over the given websocket channel
SendMessage(ch string, data any) error
// RegisterWithMsgInitializer is similar to Register, but with an additional
// newMsgFn to initialize the message data type to-be received from WebSocket
// client, instead of letting JSON unmarshaler to guess the data type.
Expand Down Expand Up @@ -118,6 +120,15 @@ func (c *uiChannel) Handler() http.Handler {
return c.clients
}

func (c *uiChannel) SendMessage(t string, data any) error {
service := c.services[t]
if service == nil {
return fmt.Errorf("No service registered %s", t)
}
service.writeMsg(data, c.clients.Out)
return nil
}

func (c *uiChannel) Register(t string, helloFn helloFnType) (*Service, error) {
return c.RegisterWithMsgInitializer(t, helloFn, nil)
}
Expand Down
44 changes: 29 additions & 15 deletions lib/common/session_model.dart
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,13 @@ class SessionModel extends Model {
late ValueNotifier<bool?> proxyAvailable;
late ValueNotifier<String?> country;

// wsMessageProp parses the given json, checks if it represents a pro user message and
// returns the value (if any) in the map for the given property.
String? wsMessageProp(Map<String, dynamic> json, String field) {
if (json["type"] != "pro") return null;
return json["message"][field];
}

Widget proUser(ValueWidgetBuilder<bool> builder) {
if (isMobile()) {
return subscribedSingleValueBuilder<bool>('prouser', builder: builder);
Expand All @@ -83,18 +90,12 @@ 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";
setValue(isProUser);
}
final userStatus = wsMessageProp(json, "userStatus");
if (userStatus != null && userStatus.toString() == "active") setValue(true);
},
onError: (error) => print(error),
onError: (error) => appLogger.i("websocket error: ${error.description}"),
);
},
ffiProUser,
Expand Down Expand Up @@ -183,9 +184,20 @@ class SessionModel extends Model {
if (isMobile()) {
return subscribedSingleValueBuilder<String>('lang', builder: builder);
}
final websocket = WebsocketImpl.instance();
return ffiValueBuilder<String>(
'lang',
defaultValue: 'en',
onChanges: (setValue) {
if (websocket == null) return;
websocket.messageStream.listen(
(json) {
final language = wsMessageProp(json, "language");
if (language != null && language != "") setValue(language);
},
onError: (error) => appLogger.i("websocket error: ${error.description}"),
);
},
ffiLang,
builder: builder,
);
Expand Down Expand Up @@ -256,7 +268,7 @@ class SessionModel extends Model {
devices.add(Device.create()..mergeFromProto3Json(element));
} on Exception catch (e) {
// Handle parsing errors as needed
print("Error parsing device data: $e");
appLogger.i("Error parsing device data: $e");
}
}
return Devices.create()..devices.addAll(devices);
Expand Down Expand Up @@ -608,11 +620,13 @@ class SessionModel extends Model {
String url,
String title,
) async {
return methodChannel.invokeMethod('trackUserAction', <String, dynamic>{
'name': name,
'url': url,
'title': title,
});
if (isMobile()) {
return methodChannel.invokeMethod('trackUserAction', <String, dynamic>{
'name': name,
'url': url,
'title': title,
});
}
}

Future<String> requestLinkCode() {
Expand Down
2 changes: 2 additions & 0 deletions lib/ffi.dart
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,8 @@ Pointer<Utf8> ffiRedeemResellerCode(email, currency, deviceName, resellerCode) {
final errorCode = result.r1.cast<Utf8>().toDartString();
throw PlatformException(code: errorCode, message: 'wrong_seller_code'.i18n);
}
// if successful redeeming a reseller code, immediately refresh Pro user data
ffiProUser();
return result.r0.cast<Utf8>();
}

Expand Down
5 changes: 3 additions & 2 deletions lib/plans/checkout.dart
Original file line number Diff line number Diff line change
Expand Up @@ -402,13 +402,14 @@ class _CheckoutState extends State<Checkout>
),
),
// * Price summary, unused pro time disclaimer, Continue button

Center(
child: Tooltip(
message: AppKeys.continueCheckout,
child: Button(
text: 'continue'.i18n,
disabled: !showContinueButton,
// for Pro users renewing their accounts, we always have an e-mail address
// so it's unnecessary to disable the continue button
disabled: !widget.isPro ? !showContinueButton : false,
onPressed: onContinueTapped,
),
),
Expand Down
11 changes: 4 additions & 7 deletions lib/plans/plan_details.dart
Original file line number Diff line number Diff line change
Expand Up @@ -121,23 +121,20 @@ class PlanCard extends StatelessWidget {

void onPlanTap(BuildContext context) {
switch (Platform.operatingSystem) {
case 'android':
_androidCheckOut(context);
break;
case 'ios':
throw Exception("Not support at the moment");
break;
default:
throw Exception("Not support at the moment");
// * Fallback code
// proceed to the default checkout page on Android and desktop
_checkOut(context);
break;
}
}

Future<void> _androidCheckOut(BuildContext context) async {
Future<void> _checkOut(BuildContext context) async {
final isPlayVersion = sessionModel.isPlayVersion.value ?? false;
final inRussia = sessionModel.country.value == 'RU';
// * Play version
// * Play version (Android only)
if (isPlayVersion && !inRussia) {
await context.pushRoute(
PlayCheckout(
Expand Down
14 changes: 0 additions & 14 deletions lib/plans/reseller_checkout.dart
Original file line number Diff line number Diff line change
Expand Up @@ -201,19 +201,5 @@ class _ResellerCodeCheckoutState extends State<ResellerCodeCheckout> {
.toString(), // This is coming localized
);
}

// .timeout(
// const Duration(seconds: 20),
// onTimeout: () => onAPIcallTimeout(
// code: 'redeemresellerCodeTimeout',
// message: 'reseller_timeout'.i18n,
// ),
// )
// .then((value) {
// context.loaderOverlay.hide();
// showSuccessDialog(context, widget.isPro, true);
// }).onError((error, stackTrace) {
//
// });
}
}

0 comments on commit ff9ca6e

Please sign in to comment.