It is working now yay ¯\_(ツ)_/¯
This commit is contained in:
parent
39aed2715e
commit
cab4f6279d
|
@ -1,5 +1,6 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:lokinet_lib/lokinet_lib.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
|
||||
void main() {
|
||||
runApp(MyApp());
|
||||
|
@ -11,6 +12,7 @@ class MyApp extends StatelessWidget {
|
|||
Widget build(BuildContext context) {
|
||||
return MaterialApp(
|
||||
title: 'Lokinet App',
|
||||
debugShowCheckedModeBanner: false,
|
||||
theme: ThemeData(
|
||||
// This is the theme of your application.
|
||||
//
|
||||
|
@ -55,6 +57,7 @@ class _MyHomePageState extends State<MyHomePage> {
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final key = new GlobalKey<ScaffoldState>();
|
||||
// This method is rerun every time setState is called, for instance as done
|
||||
// by the _incrementCounter method above.
|
||||
//
|
||||
|
@ -62,6 +65,7 @@ class _MyHomePageState extends State<MyHomePage> {
|
|||
// fast, so that you can just rebuild anything that needs updating rather
|
||||
// than having to individually change instances of widgets.
|
||||
return Scaffold(
|
||||
key: key,
|
||||
appBar: AppBar(
|
||||
// Here we take the value from the MyHomePage object that was created by
|
||||
// the App.build method, and use it to set our appbar title.
|
||||
|
@ -96,6 +100,9 @@ class _MyHomePageState extends State<MyHomePage> {
|
|||
child: Text('Bootstrap'),
|
||||
),
|
||||
onPressed: () async {
|
||||
final sharedPreferences = await SharedPreferences.getInstance();
|
||||
sharedPreferences.setInt(
|
||||
'test', 30);
|
||||
await LokinetLib.bootstrapLokinet();
|
||||
},
|
||||
),
|
||||
|
@ -111,6 +118,24 @@ class _MyHomePageState extends State<MyHomePage> {
|
|||
},
|
||||
),
|
||||
Divider(),
|
||||
TextButton(
|
||||
child: Padding(
|
||||
padding: EdgeInsets.all(10),
|
||||
child: Text('Is this thing on?'),
|
||||
),
|
||||
onPressed: () async {
|
||||
if (await LokinetLib.isRunning) {
|
||||
key.currentState.showSnackBar(new SnackBar(
|
||||
content: new Text('Yes!'),
|
||||
));
|
||||
} else {
|
||||
key.currentState.showSnackBar(new SnackBar(
|
||||
content: new Text('No!'),
|
||||
));
|
||||
}
|
||||
},
|
||||
),
|
||||
Divider(),
|
||||
TextButton(
|
||||
child: Padding(
|
||||
padding: EdgeInsets.all(10),
|
||||
|
|
|
@ -0,0 +1,101 @@
|
|||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:oxen_service_node/src/utils/theme/theme_changer.dart';
|
||||
import 'package:oxen_service_node/src/utils/theme/themes.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
class OxenAppBar extends StatelessWidget
|
||||
implements ObstructingPreferredSizeWidget {
|
||||
factory OxenAppBar(
|
||||
{BuildContext context,
|
||||
Widget leading,
|
||||
Widget middle,
|
||||
Widget trailing,
|
||||
Color backgroundColor}) {
|
||||
final _themeChanger = Provider.of<ThemeChanger>(context);
|
||||
final _isDarkTheme = _themeChanger.theme == Themes.darkTheme;
|
||||
|
||||
return OxenAppBar._internal(
|
||||
leading: leading,
|
||||
middle: middle,
|
||||
trailing: trailing,
|
||||
height: _height,
|
||||
backgroundColor:
|
||||
_isDarkTheme ? Theme.of(context).backgroundColor : backgroundColor);
|
||||
}
|
||||
|
||||
factory OxenAppBar.withShadow(
|
||||
{BuildContext context,
|
||||
Widget leading,
|
||||
Widget middle,
|
||||
Widget trailing,
|
||||
Color backgroundColor}) {
|
||||
final _themeChanger = Provider.of<ThemeChanger>(context);
|
||||
final _isDarkTheme = _themeChanger.theme == Themes.darkTheme;
|
||||
|
||||
return OxenAppBar._internal(
|
||||
leading: leading,
|
||||
middle: middle,
|
||||
trailing: trailing,
|
||||
height: 80,
|
||||
backgroundColor:
|
||||
_isDarkTheme ? Theme.of(context).backgroundColor : backgroundColor,
|
||||
decoration: BoxDecoration(
|
||||
color: _isDarkTheme
|
||||
? Theme.of(context).backgroundColor
|
||||
: backgroundColor,
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Color.fromRGBO(132, 141, 198, 0.11),
|
||||
blurRadius: 8,
|
||||
offset: Offset(0, 2))
|
||||
]),
|
||||
);
|
||||
}
|
||||
|
||||
OxenAppBar._internal(
|
||||
{this.leading,
|
||||
this.middle,
|
||||
this.trailing,
|
||||
this.backgroundColor,
|
||||
this.decoration,
|
||||
this.height = _height});
|
||||
|
||||
static const _originalHeight = 44.0; // iOS nav bar height
|
||||
static const _height = 60.0;
|
||||
|
||||
final Widget leading;
|
||||
final Widget middle;
|
||||
final Widget trailing;
|
||||
final Color backgroundColor;
|
||||
final BoxDecoration decoration;
|
||||
final double height;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final pad = height - _originalHeight;
|
||||
final paddingTop = pad / 2;
|
||||
final _paddingBottom = (pad / 2);
|
||||
|
||||
return Container(
|
||||
decoration: decoration ?? BoxDecoration(color: backgroundColor),
|
||||
padding:
|
||||
EdgeInsetsDirectional.only(bottom: _paddingBottom, top: paddingTop),
|
||||
child: CupertinoNavigationBar(
|
||||
leading: leading,
|
||||
middle: middle,
|
||||
trailing: trailing,
|
||||
backgroundColor: backgroundColor,
|
||||
border: null,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Size get preferredSize => Size.fromHeight(height);
|
||||
|
||||
@override
|
||||
bool shouldFullyObstruct(BuildContext context) {
|
||||
return false;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,57 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:oxen_service_node/src/utils/theme/palette.dart';
|
||||
|
||||
class OxenTextField extends StatelessWidget {
|
||||
OxenTextField(
|
||||
{this.enabled = true,
|
||||
this.hintText,
|
||||
this.keyboardType,
|
||||
this.controller,
|
||||
this.validator,
|
||||
this.inputFormatters,
|
||||
this.prefixIcon,
|
||||
this.suffixIcon,
|
||||
this.focusNode});
|
||||
|
||||
final bool enabled;
|
||||
final String hintText;
|
||||
final TextInputType keyboardType;
|
||||
final TextEditingController controller;
|
||||
final String Function(String) validator;
|
||||
final List<TextInputFormatter> inputFormatters;
|
||||
final Widget prefixIcon;
|
||||
final Widget suffixIcon;
|
||||
final FocusNode focusNode;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return TextFormField(
|
||||
onFieldSubmitted: (_) => FocusScope.of(context).unfocus(),
|
||||
enabled: enabled,
|
||||
controller: controller,
|
||||
focusNode: focusNode,
|
||||
style: TextStyle(
|
||||
fontSize: 18.0,
|
||||
color: Theme.of(context).accentTextTheme.overline.color),
|
||||
keyboardType: keyboardType,
|
||||
inputFormatters: inputFormatters,
|
||||
decoration: InputDecoration(
|
||||
prefixIcon: prefixIcon,
|
||||
suffixIcon: suffixIcon,
|
||||
hintStyle:
|
||||
TextStyle(fontSize: 18.0, color: Theme.of(context).hintColor),
|
||||
hintText: hintText,
|
||||
focusedBorder: OutlineInputBorder(
|
||||
borderSide: BorderSide(color: OxenPalette.teal, width: 2.0)),
|
||||
enabledBorder: OutlineInputBorder(
|
||||
borderSide: BorderSide(
|
||||
color: Theme.of(context).focusColor, width: 1.0)),
|
||||
errorBorder: OutlineInputBorder(
|
||||
borderSide: BorderSide(color: OxenPalette.red, width: 1.0)),
|
||||
focusedErrorBorder: OutlineInputBorder(
|
||||
borderSide: BorderSide(color: OxenPalette.red, width: 1.0)),
|
||||
errorStyle: TextStyle(color: OxenPalette.red)),
|
||||
validator: validator);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,122 @@
|
|||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:oxen_service_node/src/utils/theme/theme_changer.dart';
|
||||
import 'package:oxen_service_node/src/utils/theme/themes.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
import 'oxen/oxen_app_bar.dart';
|
||||
|
||||
enum AppBarStyle { regular, withShadow }
|
||||
|
||||
abstract class OxenBasePage extends StatelessWidget {
|
||||
String get title => null;
|
||||
|
||||
bool get isModalBackButton => false;
|
||||
|
||||
Color get backgroundColor => Colors.white;
|
||||
|
||||
bool get resizeToAvoidBottomPadding => true;
|
||||
|
||||
AppBarStyle get appBarStyle => AppBarStyle.regular;
|
||||
|
||||
void onClose(BuildContext context) => Navigator.of(context).pop();
|
||||
|
||||
final GlobalKey<ScaffoldState> scaffoldKey = new GlobalKey<ScaffoldState>();
|
||||
|
||||
Widget leading(BuildContext context) {
|
||||
if (ModalRoute.of(context).isFirst) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final _backButton = Icon(Icons.arrow_back_ios_sharp, size: 25);
|
||||
final _closeButton = Icon(Icons.close_sharp, size: 25);
|
||||
|
||||
return SizedBox(
|
||||
height: 37,
|
||||
width: isModalBackButton ? 37 : 20,
|
||||
child: ButtonTheme(
|
||||
minWidth: double.minPositive,
|
||||
child: FlatButton(
|
||||
highlightColor: Colors.transparent,
|
||||
splashColor: Colors.transparent,
|
||||
padding: EdgeInsets.all(0),
|
||||
onPressed: () => onClose(context),
|
||||
child: isModalBackButton ? _closeButton : _backButton),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget middle(BuildContext context) {
|
||||
return title == null
|
||||
? null
|
||||
: Text(
|
||||
title,
|
||||
style: TextStyle(
|
||||
fontSize: 16.0,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Theme.of(context).primaryTextTheme.headline6.color),
|
||||
);
|
||||
}
|
||||
|
||||
Widget trailing(BuildContext context) => null;
|
||||
|
||||
Widget floatingActionButton(BuildContext context) => null;
|
||||
|
||||
ObstructingPreferredSizeWidget appBar(BuildContext context) {
|
||||
final _themeChanger = Provider.of<ThemeChanger>(context);
|
||||
final _isDarkTheme = _themeChanger.theme == Themes.darkTheme;
|
||||
|
||||
switch (appBarStyle) {
|
||||
case AppBarStyle.regular:
|
||||
return OxenAppBar(
|
||||
context: context,
|
||||
leading: leading(context),
|
||||
middle: middle(context),
|
||||
trailing: trailing(context),
|
||||
backgroundColor: _isDarkTheme
|
||||
? Theme.of(context).backgroundColor
|
||||
: backgroundColor);
|
||||
|
||||
case AppBarStyle.withShadow:
|
||||
return OxenAppBar.withShadow(
|
||||
context: context,
|
||||
leading: leading(context),
|
||||
middle: middle(context),
|
||||
trailing: trailing(context),
|
||||
backgroundColor: _isDarkTheme
|
||||
? Theme.of(context).backgroundColor
|
||||
: backgroundColor);
|
||||
|
||||
default:
|
||||
return OxenAppBar(
|
||||
context: context,
|
||||
leading: leading(context),
|
||||
middle: middle(context),
|
||||
trailing: trailing(context),
|
||||
backgroundColor: _isDarkTheme
|
||||
? Theme.of(context).backgroundColor
|
||||
: backgroundColor);
|
||||
}
|
||||
}
|
||||
|
||||
Widget body(BuildContext context);
|
||||
|
||||
Widget bottomNavigationBar(BuildContext context) => null;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final _themeChanger = Provider.of<ThemeChanger>(context);
|
||||
final _isDarkTheme = _themeChanger.theme == Themes.darkTheme;
|
||||
|
||||
return Scaffold(
|
||||
key: scaffoldKey,
|
||||
backgroundColor:
|
||||
_isDarkTheme ? Theme.of(context).backgroundColor : backgroundColor,
|
||||
resizeToAvoidBottomPadding: resizeToAvoidBottomPadding,
|
||||
appBar: appBar(context),
|
||||
body: SafeArea(child: body(context)),
|
||||
floatingActionButton: floatingActionButton(context),
|
||||
bottomNavigationBar: bottomNavigationBar(context),
|
||||
floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat);
|
||||
}
|
||||
}
|
|
@ -2,6 +2,8 @@ package network.loki.lokinet;
|
|||
|
||||
import android.content.Intent;
|
||||
import android.net.VpnService;
|
||||
import android.os.Binder;
|
||||
import android.os.IBinder;
|
||||
import android.os.ParcelFileDescriptor;
|
||||
import android.util.Log;
|
||||
|
||||
|
@ -11,6 +13,8 @@ public class LokinetDaemon extends VpnService {
|
|||
|
||||
public static final String ACTION_CONNECT = "network.loki.lokinet.START";
|
||||
public static final String ACTION_DISCONNECT = "network.loki.lokinet.STOP";
|
||||
public static final String LOG_TAG = "LokinetDaemon";
|
||||
public static final String MESSAGE_CHANNEL = "LOKINET_DAEMON";
|
||||
|
||||
static {
|
||||
System.loadLibrary("lokinet-android");
|
||||
|
@ -34,8 +38,6 @@ public class LokinetDaemon extends VpnService {
|
|||
|
||||
private static native String DetectFreeRange();
|
||||
|
||||
public static final String LOG_TAG = "LokinetDaemon";
|
||||
|
||||
ByteBuffer impl = null;
|
||||
ParcelFileDescriptor iface;
|
||||
int m_FD = -1;
|
||||
|
@ -48,8 +50,8 @@ public class LokinetDaemon extends VpnService {
|
|||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
super.onDestroy();
|
||||
disconnect();
|
||||
super.onDestroy();
|
||||
disconnect();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -60,96 +62,118 @@ public class LokinetDaemon extends VpnService {
|
|||
disconnect();
|
||||
return START_NOT_STICKY;
|
||||
} else {
|
||||
if (!IsRunning()) {
|
||||
if (impl != null) {
|
||||
Free(impl);
|
||||
impl = null;
|
||||
}
|
||||
impl = Obtain();
|
||||
if (impl == null) {
|
||||
Log.e(LOG_TAG, "got nullptr when creating llarp::Context in jni");
|
||||
return START_NOT_STICKY;
|
||||
}
|
||||
|
||||
String dataDir = getFilesDir().toString();
|
||||
LokinetConfig config;
|
||||
try {
|
||||
config = new LokinetConfig(dataDir);
|
||||
} catch (RuntimeException ex) {
|
||||
Log.e(LOG_TAG, ex.toString());
|
||||
return START_NOT_STICKY;
|
||||
}
|
||||
|
||||
// FIXME: make these configurable
|
||||
String exitNode = "exit.loki";
|
||||
String upstreamDNS = "1.1.1.1";
|
||||
String ourRange = DetectFreeRange();
|
||||
|
||||
if (ourRange.isEmpty()) {
|
||||
Log.e(LOG_TAG, "cannot detect free range");
|
||||
return START_NOT_STICKY;
|
||||
}
|
||||
|
||||
|
||||
// set up config values
|
||||
config.AddDefaultValue("network", "exit-node", exitNode);
|
||||
config.AddDefaultValue("network", "ifaddr", ourRange);
|
||||
config.AddDefaultValue("dns", "upstream", upstreamDNS);
|
||||
|
||||
|
||||
if (!config.Load()) {
|
||||
Log.e(LOG_TAG, "failed to load (or create) config file at: " + dataDir + "/loki.network.loki.lokinet.ini");
|
||||
return START_NOT_STICKY;
|
||||
}
|
||||
|
||||
VpnService.Builder builder = new VpnService.Builder();
|
||||
|
||||
builder.setMtu(1500);
|
||||
|
||||
String[] parts = ourRange.split("/");
|
||||
String ourIP = parts[0];
|
||||
int ourMask = Integer.parseInt(parts[1]);
|
||||
|
||||
builder.addAddress(ourIP, ourMask);
|
||||
builder.addRoute("0.0.0.0", 0);
|
||||
builder.addDnsServer(upstreamDNS);
|
||||
builder.setSession("Lokinet");
|
||||
builder.setConfigureIntent(null);
|
||||
|
||||
iface = builder.establish();
|
||||
if (iface == null) {
|
||||
Log.e(LOG_TAG, "VPN Interface from builder.establish() came back null");
|
||||
return START_NOT_STICKY;
|
||||
}
|
||||
|
||||
m_FD = iface.detachFd();
|
||||
|
||||
InjectVPNFD();
|
||||
new Thread(() -> {
|
||||
Configure(config);
|
||||
m_UDPSocket = GetUDPSocket();
|
||||
protect(m_UDPSocket);
|
||||
Mainloop();
|
||||
}).start();
|
||||
|
||||
Log.d(LOG_TAG, "started successfully!");
|
||||
} else {
|
||||
Log.d(LOG_TAG, "already running");
|
||||
}
|
||||
return START_STICKY;
|
||||
boolean connectedSucessfully = connect("exit.loki");
|
||||
if (connectedSucessfully)
|
||||
return START_STICKY;
|
||||
else
|
||||
return START_NOT_STICKY;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean connect(String exitNode) {
|
||||
if (!IsRunning()) {
|
||||
if (impl != null) {
|
||||
Free(impl);
|
||||
impl = null;
|
||||
}
|
||||
impl = Obtain();
|
||||
if (impl == null) {
|
||||
Log.e(LOG_TAG, "got nullptr when creating llarp::Context in jni");
|
||||
return false;
|
||||
}
|
||||
|
||||
String dataDir = getFilesDir().toString();
|
||||
LokinetConfig config;
|
||||
try {
|
||||
config = new LokinetConfig(dataDir);
|
||||
} catch (RuntimeException ex) {
|
||||
Log.e(LOG_TAG, ex.toString());
|
||||
return false;
|
||||
}
|
||||
|
||||
// FIXME: make these configurable
|
||||
// String exitNode = "exit.loki";
|
||||
String upstreamDNS = "1.1.1.1";
|
||||
String ourRange = DetectFreeRange();
|
||||
|
||||
if (ourRange.isEmpty()) {
|
||||
Log.e(LOG_TAG, "cannot detect free range");
|
||||
return false;
|
||||
}
|
||||
|
||||
// set up config values
|
||||
config.AddDefaultValue("network", "exit-node", exitNode);
|
||||
config.AddDefaultValue("network", "ifaddr", ourRange);
|
||||
config.AddDefaultValue("dns", "upstream", upstreamDNS);
|
||||
|
||||
|
||||
if (!config.Load()) {
|
||||
Log.e(LOG_TAG, "failed to load (or create) config file at: " + dataDir + "/loki.network.loki.lokinet.ini");
|
||||
return false;
|
||||
}
|
||||
|
||||
VpnService.Builder builder = new VpnService.Builder();
|
||||
|
||||
builder.setMtu(1500);
|
||||
|
||||
String[] parts = ourRange.split("/");
|
||||
String ourIP = parts[0];
|
||||
int ourMask = Integer.parseInt(parts[1]);
|
||||
|
||||
builder.addAddress(ourIP, ourMask);
|
||||
builder.addRoute("0.0.0.0", 0);
|
||||
builder.addDnsServer(upstreamDNS);
|
||||
builder.setSession("Lokinet");
|
||||
builder.setConfigureIntent(null);
|
||||
|
||||
iface = builder.establish();
|
||||
if (iface == null) {
|
||||
Log.e(LOG_TAG, "VPN Interface from builder.establish() came back null");
|
||||
return false;
|
||||
}
|
||||
|
||||
m_FD = iface.detachFd();
|
||||
|
||||
InjectVPNFD();
|
||||
new Thread(() -> {
|
||||
Configure(config);
|
||||
m_UDPSocket = GetUDPSocket();
|
||||
protect(m_UDPSocket);
|
||||
Mainloop();
|
||||
}).start();
|
||||
|
||||
Log.d(LOG_TAG, "started successfully!");
|
||||
} else {
|
||||
Log.d(LOG_TAG, "already running");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private void disconnect() {
|
||||
Log.d(LOG_TAG, "Before STOP");
|
||||
if (IsRunning()) {
|
||||
Stop();
|
||||
}
|
||||
Log.d(LOG_TAG, "After STOP");
|
||||
if (impl != null) {
|
||||
Free(impl);
|
||||
impl = null;
|
||||
}
|
||||
Log.d(LOG_TAG, "After FREE");
|
||||
// if (impl != null) {
|
||||
// Free(impl);
|
||||
// impl = null;
|
||||
// }
|
||||
}
|
||||
|
||||
/**
|
||||
* Class for clients to access. Because we know this service always
|
||||
* runs in the same process as its clients, we don't need to deal with
|
||||
* IPC.
|
||||
*/
|
||||
public class LocalBinder extends Binder {
|
||||
public LokinetDaemon getService() {
|
||||
return LokinetDaemon.this;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public IBinder onBind(Intent intent) {
|
||||
return mBinder;
|
||||
}
|
||||
|
||||
private final IBinder mBinder = new LocalBinder();
|
||||
}
|
||||
|
|
|
@ -1,8 +1,13 @@
|
|||
package io.oxen.lokinet_lib
|
||||
|
||||
import android.app.Activity.RESULT_OK
|
||||
import android.content.BroadcastReceiver
|
||||
import android.content.Context
|
||||
import android.content.Context.MODE_PRIVATE
|
||||
import android.content.Intent
|
||||
import android.content.SharedPreferences
|
||||
import android.net.VpnService
|
||||
import android.util.Log
|
||||
import androidx.annotation.NonNull
|
||||
import io.flutter.embedding.engine.plugins.FlutterPlugin
|
||||
import io.flutter.embedding.engine.plugins.activity.ActivityAware
|
||||
|
@ -12,9 +17,17 @@ import io.flutter.plugin.common.MethodChannel
|
|||
import io.flutter.plugin.common.MethodChannel.MethodCallHandler
|
||||
import io.flutter.plugin.common.PluginRegistry
|
||||
import network.loki.lokinet.LokinetDaemon
|
||||
import android.content.IntentFilter
|
||||
import android.widget.Toast
|
||||
import android.content.ComponentName
|
||||
import android.os.IBinder
|
||||
import android.content.ServiceConnection
|
||||
|
||||
/** LokinetLibPlugin */
|
||||
class LokinetLibPlugin : FlutterPlugin, MethodCallHandler, ActivityAware {
|
||||
private var mShouldUnbind: Boolean = false
|
||||
private var mBoundService: LokinetDaemon? = null
|
||||
|
||||
private lateinit var activityBinding: ActivityPluginBinding
|
||||
|
||||
/// The MethodChannel that will the communication between Flutter and native Android
|
||||
|
@ -23,19 +36,19 @@ class LokinetLibPlugin : FlutterPlugin, MethodCallHandler, ActivityAware {
|
|||
/// when the Flutter Engine is detached from the Activity
|
||||
private lateinit var channel: MethodChannel
|
||||
|
||||
override fun onAttachedToEngine(@NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) {
|
||||
override fun onAttachedToEngine(@NonNull binding: FlutterPlugin.FlutterPluginBinding) {
|
||||
System.loadLibrary("lokinet-android")
|
||||
|
||||
channel = MethodChannel(flutterPluginBinding.binaryMessenger, "lokinet_lib")
|
||||
channel = MethodChannel(binding.binaryMessenger, "lokinet_lib")
|
||||
channel.setMethodCallHandler(this)
|
||||
}
|
||||
|
||||
override fun onDetachedFromEngine(@NonNull binding: FlutterPlugin.FlutterPluginBinding) {
|
||||
channel.setMethodCallHandler(null)
|
||||
doUnbindService()
|
||||
}
|
||||
|
||||
override fun onMethodCall(@NonNull call: MethodCall, @NonNull result: MethodChannel.Result) {
|
||||
|
||||
when (call.method) {
|
||||
"prepare" -> {
|
||||
val intent = VpnService.prepare(activityBinding.activity.applicationContext)
|
||||
|
@ -47,7 +60,7 @@ class LokinetLibPlugin : FlutterPlugin, MethodCallHandler, ActivityAware {
|
|||
} else {
|
||||
result.success(false)
|
||||
}
|
||||
listener?.let { activityBinding.removeActivityResultListener(it) };
|
||||
listener?.let { activityBinding.removeActivityResultListener(it) }
|
||||
true
|
||||
}
|
||||
activityBinding.addActivityResultListener(listener)
|
||||
|
@ -73,16 +86,25 @@ class LokinetLibPlugin : FlutterPlugin, MethodCallHandler, ActivityAware {
|
|||
lokinetIntent.action = LokinetDaemon.ACTION_CONNECT
|
||||
|
||||
activityBinding.activity.applicationContext.startService(lokinetIntent)
|
||||
doBindService()
|
||||
|
||||
result.success(true);
|
||||
result.success(true)
|
||||
}
|
||||
"disconnect" -> {
|
||||
val lokinetIntent = Intent(activityBinding.activity.applicationContext, LokinetDaemon::class.java)
|
||||
lokinetIntent.action = LokinetDaemon.ACTION_DISCONNECT
|
||||
|
||||
activityBinding.activity.applicationContext.startService(lokinetIntent)
|
||||
doBindService()
|
||||
|
||||
result.success(true);
|
||||
result.success(true)
|
||||
}
|
||||
"isRunning" -> {
|
||||
if (mBoundService != null) {
|
||||
result.success(mBoundService!!.IsRunning())
|
||||
} else {
|
||||
result.success(false)
|
||||
}
|
||||
}
|
||||
else -> result.notImplemented()
|
||||
}
|
||||
|
@ -99,4 +121,36 @@ class LokinetLibPlugin : FlutterPlugin, MethodCallHandler, ActivityAware {
|
|||
}
|
||||
|
||||
override fun onDetachedFromActivityForConfigChanges() {}
|
||||
|
||||
private val mConnection: ServiceConnection = object : ServiceConnection {
|
||||
override fun onServiceConnected(className: ComponentName, service: IBinder) {
|
||||
mBoundService = (service as LokinetDaemon.LocalBinder).getService()
|
||||
}
|
||||
|
||||
override fun onServiceDisconnected(className: ComponentName) {
|
||||
mBoundService = null
|
||||
}
|
||||
}
|
||||
|
||||
fun doBindService() {
|
||||
if (activityBinding.activity.applicationContext.bindService(
|
||||
Intent(activityBinding.activity.applicationContext, LokinetDaemon::class.java),
|
||||
mConnection, Context.BIND_AUTO_CREATE
|
||||
)
|
||||
) {
|
||||
mShouldUnbind = true
|
||||
} else {
|
||||
Log.e(
|
||||
LokinetDaemon.LOG_TAG, "Error: The requested service doesn't exist, or this client isn't allowed access to it."
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun doUnbindService() {
|
||||
if (mShouldUnbind) {
|
||||
activityBinding.activity.applicationContext.unbindService(mConnection)
|
||||
mShouldUnbind = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -36,4 +36,9 @@ class LokinetLib {
|
|||
final bool disconnect = await _channel.invokeMethod('disconnect');
|
||||
return disconnect;
|
||||
}
|
||||
|
||||
static Future<bool> get isRunning async {
|
||||
final bool isRunning = await _channel.invokeMethod('isRunning');
|
||||
return isRunning;
|
||||
}
|
||||
}
|
||||
|
|
94
pubspec.lock
94
pubspec.lock
|
@ -21,42 +21,42 @@ packages:
|
|||
name: async
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.5.0-nullsafety.1"
|
||||
version: "2.5.0"
|
||||
boolean_selector:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: boolean_selector
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.1.0-nullsafety.1"
|
||||
version: "2.1.0"
|
||||
characters:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: characters
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.1.0-nullsafety.3"
|
||||
version: "1.1.0"
|
||||
charcode:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: charcode
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.2.0-nullsafety.1"
|
||||
version: "1.2.0"
|
||||
clock:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: clock
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.1.0-nullsafety.1"
|
||||
version: "1.1.0"
|
||||
collection:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: collection
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.15.0-nullsafety.3"
|
||||
version: "1.15.0"
|
||||
convert:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -84,7 +84,7 @@ packages:
|
|||
name: fake_async
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.2.0-nullsafety.1"
|
||||
version: "1.2.0"
|
||||
ffi:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -116,6 +116,11 @@ packages:
|
|||
description: flutter
|
||||
source: sdk
|
||||
version: "0.0.0"
|
||||
flutter_web_plugins:
|
||||
dependency: transitive
|
||||
description: flutter
|
||||
source: sdk
|
||||
version: "0.0.0"
|
||||
image:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -130,6 +135,13 @@ packages:
|
|||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.16.1"
|
||||
js:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: js
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.6.3"
|
||||
lokinet_lib:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
@ -143,21 +155,21 @@ packages:
|
|||
name: matcher
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.12.10-nullsafety.1"
|
||||
version: "0.12.10"
|
||||
meta:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: meta
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.3.0-nullsafety.3"
|
||||
version: "1.3.0"
|
||||
path:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.8.0-nullsafety.1"
|
||||
version: "1.8.0"
|
||||
path_provider:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -221,6 +233,48 @@ packages:
|
|||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "3.0.13"
|
||||
shared_preferences:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: shared_preferences
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.5.12+4"
|
||||
shared_preferences_linux:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shared_preferences_linux
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.0.2+4"
|
||||
shared_preferences_macos:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shared_preferences_macos
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.0.1+11"
|
||||
shared_preferences_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shared_preferences_platform_interface
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.0.4"
|
||||
shared_preferences_web:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shared_preferences_web
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.1.2+7"
|
||||
shared_preferences_windows:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shared_preferences_windows
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.0.2+3"
|
||||
sky_engine:
|
||||
dependency: transitive
|
||||
description: flutter
|
||||
|
@ -232,56 +286,56 @@ packages:
|
|||
name: source_span
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.8.0-nullsafety.2"
|
||||
version: "1.8.0"
|
||||
stack_trace:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: stack_trace
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.10.0-nullsafety.1"
|
||||
version: "1.10.0"
|
||||
stream_channel:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: stream_channel
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.1.0-nullsafety.1"
|
||||
version: "2.1.0"
|
||||
string_scanner:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: string_scanner
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.1.0-nullsafety.1"
|
||||
version: "1.1.0"
|
||||
term_glyph:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: term_glyph
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.2.0-nullsafety.1"
|
||||
version: "1.2.0"
|
||||
test_api:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: test_api
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.2.19-nullsafety.2"
|
||||
version: "0.2.19"
|
||||
typed_data:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: typed_data
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.3.0-nullsafety.3"
|
||||
version: "1.3.0"
|
||||
vector_math:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: vector_math
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.1.0-nullsafety.3"
|
||||
version: "2.1.0"
|
||||
win32:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -311,5 +365,5 @@ packages:
|
|||
source: hosted
|
||||
version: "2.2.1"
|
||||
sdks:
|
||||
dart: ">=2.10.0-110 <2.11.0"
|
||||
flutter: ">=1.20.0 <2.0.0"
|
||||
dart: ">=2.12.0-0.0 <3.0.0"
|
||||
flutter: ">=1.20.0"
|
||||
|
|
|
@ -25,6 +25,7 @@ dependencies:
|
|||
sdk: flutter
|
||||
lokinet_lib:
|
||||
path: ./lokinet_lib
|
||||
shared_preferences: ^0.5.12+4
|
||||
|
||||
# The following adds the Cupertino Icons font to your application.
|
||||
# Use with the CupertinoIcons class for iOS style icons.
|
||||
|
|
Loading…
Reference in New Issue