Skip to content

Commit

Permalink
Add ffi value notifier, to be able to re-use session model across UI …
Browse files Browse the repository at this point in the history
…components
  • Loading branch information
atavism committed Nov 26, 2023
1 parent 3310169 commit d2cb162
Show file tree
Hide file tree
Showing 8 changed files with 191 additions and 12 deletions.
15 changes: 15 additions & 0 deletions desktop/app/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,9 @@ type App struct {
// should not be called. In short, this slice and its elements should be treated as read-only.
proxies []balancer.Dialer
proxiesLock sync.RWMutex

selectedTab Tab
selectedTabMu sync.Mutex
}

// NewApp creates a new desktop app that initializes the app and acts as a moderator between all desktop components.
Expand All @@ -98,6 +101,7 @@ func NewApp(flags flashlight.Flags, configDir string, settings *Settings) *App {
exited: eventual.NewValue(),
settings: settings,
analyticsSession: analyticsSession,
selectedTab: AccountTab,
statsTracker: NewStatsTracker(),
ws: ws.NewUIChannel(),
}
Expand All @@ -117,6 +121,17 @@ func newAnalyticsSession(settings *Settings) analytics.Session {
}
}

func (app *App) SelectedTab() Tab {
app.selectedTabMu.Lock()
defer app.selectedTabMu.Unlock()
return app.selectedTab
}

func (app *App) SetSelectedTab(selectedTab Tab) {
app.selectedTabMu.Lock()
defer app.selectedTabMu.Unlock()
app.selectedTab = selectedTab
}

func (app *App) GetDebugHttpHandlers() []server.PathHandler {
return []server.PathHandler{{
Expand Down
27 changes: 27 additions & 0 deletions desktop/app/tab.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package app

import (
"errors"
"strings"
)

var (
ErrMissingTab = errors.New("missing tab")
AccountTab = Tab("account")
DeveloperTab = Tab("developer")
VPNTab = Tab("vpn")
UnknownTab = Tab("")
)

// Identifies a specific tab in the desktop app
type Tab string

// Parse the given string into a Tab
func ParseTab(s string) (Tab, error) {
normalized := strings.ToLower(strings.TrimSpace(s))
if normalized == "" {
// leave currency empty
return UnknownTab, ErrMissingTab
}
return Tab(normalized), nil
}
26 changes: 15 additions & 11 deletions desktop/lib.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import (
"runtime"
"runtime/debug"
"strconv"
"sync"

"github.com/getlantern/appdir"
"github.com/getlantern/android-lantern/desktop/app"
Expand All @@ -24,8 +23,8 @@ import "C"

var (
log = golog.LoggerFor("lantern-desktop.main")
selectedTab = "account"
selectedTabMu sync.Mutex
a *app.App

proClient *pro.ProClient
settings *app.Settings
)
Expand All @@ -43,7 +42,7 @@ func Start() *C.char {
cdir := configDir(&flags)
settings = loadSettings(cdir)
proClient = pro.New()
a := app.NewApp(flags, cdir, settings)
a = app.NewApp(flags, cdir, settings)
log.Debug("Running headless")
go func() {
runApp(a)
Expand Down Expand Up @@ -89,17 +88,17 @@ func sendError(err error) *C.char {

//export SelectedTab
func SelectedTab() *C.char {
selectedTabMu.Lock()
defer selectedTabMu.Unlock()
return C.CString(selectedTab)
return C.CString(string(a.SelectedTab()))
}

//export SetSelectTab
func SetSelectTab(ttab *C.char) {
tab := C.GoString(ttab)
selectedTabMu.Lock()
defer selectedTabMu.Unlock()
selectedTab = tab
tab, err := app.ParseTab(C.GoString(ttab))
if err != nil {
log.Error(err)
return
}
a.SetSelectedTab(tab)
}

//export Plans
Expand All @@ -124,6 +123,11 @@ func UserData() *C.char {
return C.CString(string(b))
}

//export EmailAddress
func EmailAddress() *C.char {
return C.CString("")
}

// loadSettings loads the initial settings at startup, either from disk or using defaults.
func loadSettings(configDir string) *app.Settings {
path := filepath.Join(configDir, "settings.yaml")
Expand Down
6 changes: 6 additions & 0 deletions lib/common/common_desktop.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export 'dart:convert';
export 'dart:ffi'; // For FFI

export 'package:lantern/desktop/ffi.dart';
export 'package:ffi/ffi.dart';
export 'package:ffi/src/utf8.dart';
72 changes: 72 additions & 0 deletions lib/common/ffi_subscriber.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import 'common.dart';
import 'common_desktop.dart';


class FfiValueNotifier<T> extends SubscribedNotifier<T?> {
FfiValueNotifier(
Pointer<Utf8> Function() ffiFunction,
T? defaultValue,
void Function() removeFromCache, {
bool details = false,
T Function(Uint8List serialized)? deserialize,
}) : super(defaultValue, removeFromCache) {
value = ffiFunction().toDartString() as T?;
}
}

/// A ValueListenableBuilder that obtains its single value by subscribing to a
/// path in the database.
class FfiValueBuilder<T> extends ValueListenableBuilder<T?> {
FfiValueBuilder(
String path,
ValueNotifier<T?> notifier,
ValueWidgetBuilder<T> builder,
) : super(
valueListenable: notifier,
builder: (BuildContext context, T? value, Widget? child) =>
value == null ? const SizedBox() : builder(context, value, child),
);

@override
_FfiValueBuilderState createState() =>
_FfiValueBuilderState<T>();
}

class _FfiValueBuilderState<T>
extends State<ValueListenableBuilder<T?>> {
T? value;

@override
void initState() {
super.initState();
value = widget.valueListenable.value;
widget.valueListenable.addListener(_valueChanged);
}

@override
void didUpdateWidget(ValueListenableBuilder<T?> oldWidget) {
if (oldWidget.valueListenable != widget.valueListenable) {
oldWidget.valueListenable.removeListener(_valueChanged);
value = widget.valueListenable.value;
widget.valueListenable.addListener(_valueChanged);
}
super.didUpdateWidget(oldWidget);
}

@override
void dispose() {
widget.valueListenable.removeListener(_valueChanged);
super.dispose();
}

void _valueChanged() {
setState(() {
value = widget.valueListenable.value;
});
}

@override
Widget build(BuildContext context) {
return widget.builder(context, value, widget.child);
}
}
45 changes: 45 additions & 0 deletions lib/common/model.dart
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
import 'dart:collection';

import 'package:lantern/common/ffi_subscriber.dart';
import 'package:lantern/messaging/messaging.dart';
import 'common_desktop.dart';

abstract class Model {
late MethodChannel methodChannel;
late ModelEventChannel _updatesChannel;
final Map<String, SubscribedSingleValueNotifier> _singleValueNotifierCache =
HashMap();
final Map<String, FfiValueNotifier> _ffiValueNotifierCache =
HashMap();
final Map<String, SubscribedListNotifier> _listNotifierCache = HashMap();
Event? event;

Expand Down Expand Up @@ -45,6 +49,24 @@ abstract class Model {
return result;
}

ValueListenableBuilder<T?> ffiValueBuilder<T>(
String path,
Pointer<Utf8> Function() ffiFunction, {
T? defaultValue,
required ValueWidgetBuilder<T> builder,
bool details = false,
T Function(Uint8List serialized)? deserialize,
}) {
var notifier = ffiValueNotifier(
ffiFunction,
path,
defaultValue,
details: details,
deserialize: deserialize,
);
return FfiValueBuilder<T>(path, notifier, builder);
}

ValueListenableBuilder<T?> subscribedSingleValueBuilder<T>(
String path, {
T? defaultValue,
Expand All @@ -61,6 +83,29 @@ abstract class Model {
return SubscribedSingleValueBuilder<T>(path, notifier, builder);
}

ValueNotifier<T?> ffiValueNotifier<T>(
Pointer<Utf8> Function() ffiFunction,
String path,
T? defaultValue, {
bool details = false,
T Function(Uint8List serialized)? deserialize,
}) {
var result =
_ffiValueNotifierCache[path] as FfiValueNotifier<T>?;
if (result == null) {
result = FfiValueNotifier(
ffiFunction,
defaultValue,
() {
_ffiValueNotifierCache.remove(path);
},
);
_ffiValueNotifierCache[path] = result;
}
return result;
}


ValueNotifier<T?> singleValueNotifier<T>(
String path,
T? defaultValue, {
Expand Down
11 changes: 10 additions & 1 deletion lib/common/session_model.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import 'package:lantern/replica/common.dart';

import 'common.dart';
import 'common_desktop.dart';


final sessionModel = SessionModel();

Expand Down Expand Up @@ -120,8 +122,15 @@ class SessionModel extends Model {
}

Widget emailAddress(ValueWidgetBuilder<String> builder) {
return subscribedSingleValueBuilder<String>(
if (Platform.isAndroid) {
return subscribedSingleValueBuilder<String>(
'emailAddress',
builder: builder,
);
}
return ffiValueBuilder<String>(
'emailAddress',
ffiEmailAddress,
builder: builder,
);
}
Expand Down
1 change: 1 addition & 0 deletions lib/desktop/ffi.dart
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ final SelectedTab selectedTab =

final ProFunc getPlans = dylib.lookup<ffi.NativeFunction<pro_func>>('Plans').asFunction();
final ProFunc getUserData = dylib.lookup<ffi.NativeFunction<pro_func>>('UserData').asFunction();
final ProFunc ffiEmailAddress = dylib.lookup<ffi.NativeFunction<pro_func>>('EmailAddress').asFunction();

void loadLibrary() {
start();
Expand Down

0 comments on commit d2cb162

Please sign in to comment.