forked from core/mobile_nebula
Hide QR code scanner on devices without cameras (#101)
This commit is contained in:
parent
6b1bbf7352
commit
a5139c4335
|
@ -7,6 +7,7 @@ import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.IntentFilter
|
import android.content.IntentFilter
|
||||||
import android.content.ServiceConnection
|
import android.content.ServiceConnection
|
||||||
|
import android.content.pm.PackageManager
|
||||||
import android.net.VpnService
|
import android.net.VpnService
|
||||||
import android.os.*
|
import android.os.*
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
|
@ -62,6 +63,7 @@ class MainActivity: FlutterActivity() {
|
||||||
ui!!.setMethodCallHandler { call, result ->
|
ui!!.setMethodCallHandler { call, result ->
|
||||||
when(call.method) {
|
when(call.method) {
|
||||||
"android.registerActiveSite" -> registerActiveSite(result)
|
"android.registerActiveSite" -> registerActiveSite(result)
|
||||||
|
"android.deviceHasCamera" -> deviceHasCamera(result)
|
||||||
|
|
||||||
"nebula.parseCerts" -> nebulaParseCerts(call, result)
|
"nebula.parseCerts" -> nebulaParseCerts(call, result)
|
||||||
"nebula.generateKeyPair" -> nebulaGenerateKeyPair(result)
|
"nebula.generateKeyPair" -> nebulaGenerateKeyPair(result)
|
||||||
|
@ -120,6 +122,10 @@ class MainActivity: FlutterActivity() {
|
||||||
result.success(null)
|
result.success(null)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun deviceHasCamera(result: MethodChannel.Result) {
|
||||||
|
result.success(context.packageManager.hasSystemFeature(PackageManager.FEATURE_CAMERA_ANY))
|
||||||
|
}
|
||||||
|
|
||||||
private fun nebulaParseCerts(call: MethodCall, result: MethodChannel.Result) {
|
private fun nebulaParseCerts(call: MethodCall, result: MethodChannel.Result) {
|
||||||
val certs = call.argument<String>("certs")
|
val certs = call.argument<String>("certs")
|
||||||
if (certs == "") {
|
if (certs == "") {
|
||||||
|
|
|
@ -21,6 +21,7 @@ class HostInfoScreen extends StatefulWidget {
|
||||||
required this.pending,
|
required this.pending,
|
||||||
this.onChanged,
|
this.onChanged,
|
||||||
required this.site,
|
required this.site,
|
||||||
|
required this.supportsQRScanning,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
final bool isLighthouse;
|
final bool isLighthouse;
|
||||||
|
@ -29,6 +30,8 @@ class HostInfoScreen extends StatefulWidget {
|
||||||
final Function? onChanged;
|
final Function? onChanged;
|
||||||
final Site site;
|
final Site site;
|
||||||
|
|
||||||
|
final bool supportsQRScanning;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
_HostInfoScreenState createState() => _HostInfoScreenState();
|
_HostInfoScreenState createState() => _HostInfoScreenState();
|
||||||
}
|
}
|
||||||
|
@ -72,7 +75,10 @@ class _HostInfoScreenState extends State<HostInfoScreen> {
|
||||||
labelWidth: 150,
|
labelWidth: 150,
|
||||||
content: Text(hostInfo.cert!.details.name),
|
content: Text(hostInfo.cert!.details.name),
|
||||||
onPressed: () => Utils.openPage(
|
onPressed: () => Utils.openPage(
|
||||||
context, (context) => CertificateDetailsScreen(certInfo: CertificateInfo(cert: hostInfo.cert!))))
|
context, (context) => CertificateDetailsScreen(
|
||||||
|
certInfo: CertificateInfo(cert: hostInfo.cert!),
|
||||||
|
supportsQRScanning: widget.supportsQRScanning,
|
||||||
|
)))
|
||||||
: Container(),
|
: Container(),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
|
@ -74,6 +74,8 @@ class _MainScreenState extends State<MainScreen> {
|
||||||
// A set of widgets to display in a column that represents an error blocking us from moving forward entirely
|
// A set of widgets to display in a column that represents an error blocking us from moving forward entirely
|
||||||
List<Widget>? error;
|
List<Widget>? error;
|
||||||
|
|
||||||
|
bool supportsQRScanning = false;
|
||||||
|
|
||||||
static const platform = MethodChannel('net.defined.mobileNebula/NebulaVpnService');
|
static const platform = MethodChannel('net.defined.mobileNebula/NebulaVpnService');
|
||||||
RefreshController refreshController = RefreshController();
|
RefreshController refreshController = RefreshController();
|
||||||
ScrollController scrollController = ScrollController();
|
ScrollController scrollController = ScrollController();
|
||||||
|
@ -123,6 +125,16 @@ class _MainScreenState extends State<MainScreen> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Determine whether the device supports QR scanning. For example, some
|
||||||
|
// Chromebooks do not have camera support.
|
||||||
|
if (Platform.isAndroid) {
|
||||||
|
platform.invokeMethod("android.deviceHasCamera").then(
|
||||||
|
(hasCamera) => setState(() => supportsQRScanning = hasCamera)
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
supportsQRScanning = true;
|
||||||
|
}
|
||||||
|
|
||||||
return SimplePage(
|
return SimplePage(
|
||||||
title: Text('Nebula'),
|
title: Text('Nebula'),
|
||||||
scrollable: SimpleScrollable.vertical,
|
scrollable: SimpleScrollable.vertical,
|
||||||
|
@ -133,12 +145,11 @@ class _MainScreenState extends State<MainScreen> {
|
||||||
onPressed: () => Utils.openPage(context, (context) {
|
onPressed: () => Utils.openPage(context, (context) {
|
||||||
return SiteConfigScreen(onSave: (_) {
|
return SiteConfigScreen(onSave: (_) {
|
||||||
_loadSites();
|
_loadSites();
|
||||||
});
|
}, supportsQRScanning: supportsQRScanning);
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
refreshController: refreshController,
|
refreshController: refreshController,
|
||||||
onRefresh: () {
|
onRefresh: () {
|
||||||
print("onRefresh");
|
|
||||||
_loadSites();
|
_loadSites();
|
||||||
refreshController.refreshCompleted();
|
refreshController.refreshCompleted();
|
||||||
},
|
},
|
||||||
|
@ -198,7 +209,11 @@ class _MainScreenState extends State<MainScreen> {
|
||||||
site: site,
|
site: site,
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
Utils.openPage(context, (context) {
|
Utils.openPage(context, (context) {
|
||||||
return SiteDetailScreen(site: site, onChanged: () => _loadSites());
|
return SiteDetailScreen(
|
||||||
|
site: site,
|
||||||
|
onChanged: () => _loadSites(),
|
||||||
|
supportsQRScanning: supportsQRScanning,
|
||||||
|
);
|
||||||
});
|
});
|
||||||
}));
|
}));
|
||||||
});
|
});
|
||||||
|
|
|
@ -21,10 +21,16 @@ import 'package:pull_to_refresh/pull_to_refresh.dart';
|
||||||
//TODO: ios is now the problem with connecting screwing our ability to query the hostmap (its a race)
|
//TODO: ios is now the problem with connecting screwing our ability to query the hostmap (its a race)
|
||||||
|
|
||||||
class SiteDetailScreen extends StatefulWidget {
|
class SiteDetailScreen extends StatefulWidget {
|
||||||
const SiteDetailScreen({Key? key, required this.site, this.onChanged}) : super(key: key);
|
const SiteDetailScreen({
|
||||||
|
Key? key,
|
||||||
|
required this.site,
|
||||||
|
this.onChanged,
|
||||||
|
required this.supportsQRScanning,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
final Site site;
|
final Site site;
|
||||||
final Function? onChanged;
|
final Function? onChanged;
|
||||||
|
final bool supportsQRScanning;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
_SiteDetailScreenState createState() => _SiteDetailScreenState();
|
_SiteDetailScreenState createState() => _SiteDetailScreenState();
|
||||||
|
@ -193,7 +199,9 @@ class _SiteDetailScreenState extends State<SiteDetailScreen> {
|
||||||
setState(() {
|
setState(() {
|
||||||
activeHosts = hosts;
|
activeHosts = hosts;
|
||||||
});
|
});
|
||||||
}));
|
},
|
||||||
|
supportsQRScanning: widget.supportsQRScanning,
|
||||||
|
));
|
||||||
},
|
},
|
||||||
label: Text("Active"),
|
label: Text("Active"),
|
||||||
content: Container(alignment: Alignment.centerRight, child: active)),
|
content: Container(alignment: Alignment.centerRight, child: active)),
|
||||||
|
@ -211,7 +219,9 @@ class _SiteDetailScreenState extends State<SiteDetailScreen> {
|
||||||
setState(() {
|
setState(() {
|
||||||
pendingHosts = hosts;
|
pendingHosts = hosts;
|
||||||
});
|
});
|
||||||
}));
|
},
|
||||||
|
supportsQRScanning: widget.supportsQRScanning,
|
||||||
|
));
|
||||||
},
|
},
|
||||||
label: Text("Pending"),
|
label: Text("Pending"),
|
||||||
content: Container(alignment: Alignment.centerRight, child: pending))
|
content: Container(alignment: Alignment.centerRight, child: pending))
|
||||||
|
@ -231,7 +241,9 @@ class _SiteDetailScreenState extends State<SiteDetailScreen> {
|
||||||
onSave: (site) async {
|
onSave: (site) async {
|
||||||
changed = true;
|
changed = true;
|
||||||
setState(() {});
|
setState(() {});
|
||||||
});
|
},
|
||||||
|
supportsQRScanning: widget.supportsQRScanning,
|
||||||
|
);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
|
|
@ -10,8 +10,14 @@ import 'package:mobile_nebula/services/utils.dart';
|
||||||
import 'package:pull_to_refresh/pull_to_refresh.dart';
|
import 'package:pull_to_refresh/pull_to_refresh.dart';
|
||||||
|
|
||||||
class SiteTunnelsScreen extends StatefulWidget {
|
class SiteTunnelsScreen extends StatefulWidget {
|
||||||
const SiteTunnelsScreen(
|
const SiteTunnelsScreen({
|
||||||
{Key? key, required this.site, required this.tunnels, required this.pending, required this.onChanged})
|
Key? key,
|
||||||
|
required this.site,
|
||||||
|
required this.tunnels,
|
||||||
|
required this.pending,
|
||||||
|
required this.onChanged,
|
||||||
|
required this.supportsQRScanning,
|
||||||
|
})
|
||||||
: super(key: key);
|
: super(key: key);
|
||||||
|
|
||||||
final Site site;
|
final Site site;
|
||||||
|
@ -19,6 +25,8 @@ class SiteTunnelsScreen extends StatefulWidget {
|
||||||
final bool pending;
|
final bool pending;
|
||||||
final Function(List<HostInfo>)? onChanged;
|
final Function(List<HostInfo>)? onChanged;
|
||||||
|
|
||||||
|
final bool supportsQRScanning;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
_SiteTunnelsScreenState createState() => _SiteTunnelsScreenState();
|
_SiteTunnelsScreenState createState() => _SiteTunnelsScreenState();
|
||||||
}
|
}
|
||||||
|
@ -67,7 +75,10 @@ class _SiteTunnelsScreenState extends State<SiteTunnelsScreen> {
|
||||||
site: widget.site,
|
site: widget.site,
|
||||||
onChanged: () {
|
onChanged: () {
|
||||||
_listHostmap();
|
_listHostmap();
|
||||||
})),
|
},
|
||||||
|
supportsQRScanning: widget.supportsQRScanning,
|
||||||
|
),
|
||||||
|
),
|
||||||
label: Row(children: <Widget>[Padding(child: icon, padding: EdgeInsets.only(right: 10)), Text(hostInfo.vpnIp)]),
|
label: Row(children: <Widget>[Padding(child: icon, padding: EdgeInsets.only(right: 10)), Text(hostInfo.vpnIp)]),
|
||||||
labelWidth: ipWidth,
|
labelWidth: ipWidth,
|
||||||
content: Container(alignment: Alignment.centerRight, child: Text(hostInfo.cert?.details.name ?? "")),
|
content: Container(alignment: Alignment.centerRight, child: Text(hostInfo.cert?.details.name ?? "")),
|
||||||
|
|
|
@ -30,6 +30,7 @@ class AddCertificateScreen extends StatefulWidget {
|
||||||
this.onReplace,
|
this.onReplace,
|
||||||
required this.pubKey,
|
required this.pubKey,
|
||||||
required this.privKey,
|
required this.privKey,
|
||||||
|
required this.supportsQRScanning,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
// onSave will pop a new CertificateDetailsScreen.
|
// onSave will pop a new CertificateDetailsScreen.
|
||||||
|
@ -42,6 +43,8 @@ class AddCertificateScreen extends StatefulWidget {
|
||||||
final String pubKey;
|
final String pubKey;
|
||||||
final String privKey;
|
final String privKey;
|
||||||
|
|
||||||
|
final bool supportsQRScanning;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
_AddCertificateScreenState createState() => _AddCertificateScreenState();
|
_AddCertificateScreenState createState() => _AddCertificateScreenState();
|
||||||
}
|
}
|
||||||
|
@ -100,6 +103,16 @@ class _AddCertificateScreenState extends State<AddCertificateScreen> {
|
||||||
}
|
}
|
||||||
|
|
||||||
List<Widget> _buildLoadCert() {
|
List<Widget> _buildLoadCert() {
|
||||||
|
Map<String, Widget> children = {
|
||||||
|
'paste': Text('Copy/Paste'),
|
||||||
|
'file': Text('File'),
|
||||||
|
};
|
||||||
|
|
||||||
|
// not all devices have a camera for QR codes
|
||||||
|
if (widget.supportsQRScanning) {
|
||||||
|
children['qr'] = Text('QR Code');
|
||||||
|
}
|
||||||
|
|
||||||
List<Widget> items = [
|
List<Widget> items = [
|
||||||
Padding(
|
Padding(
|
||||||
padding: EdgeInsets.fromLTRB(10, 25, 10, 0),
|
padding: EdgeInsets.fromLTRB(10, 25, 10, 0),
|
||||||
|
@ -112,11 +125,7 @@ class _AddCertificateScreenState extends State<AddCertificateScreen> {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
children: {
|
children: children,
|
||||||
'paste': Text('Copy/Paste'),
|
|
||||||
'file': Text('File'),
|
|
||||||
'qr': Text('QR Code'),
|
|
||||||
},
|
|
||||||
))
|
))
|
||||||
];
|
];
|
||||||
|
|
||||||
|
@ -124,7 +133,7 @@ class _AddCertificateScreenState extends State<AddCertificateScreen> {
|
||||||
items.addAll(_addPaste());
|
items.addAll(_addPaste());
|
||||||
} else if (inputType == 'file') {
|
} else if (inputType == 'file') {
|
||||||
items.addAll(_addFile());
|
items.addAll(_addFile());
|
||||||
} else {
|
} else if (inputType == 'qr') {
|
||||||
items.addAll(_addQr());
|
items.addAll(_addQr());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -257,7 +266,9 @@ class _AddCertificateScreenState extends State<AddCertificateScreen> {
|
||||||
onSave: () {
|
onSave: () {
|
||||||
Navigator.pop(context);
|
Navigator.pop(context);
|
||||||
widget.onSave!(CertificateResult(certInfo: tryCertInfo, key: keyController.text));
|
widget.onSave!(CertificateResult(certInfo: tryCertInfo, key: keyController.text));
|
||||||
});
|
},
|
||||||
|
supportsQRScanning: widget.supportsQRScanning,
|
||||||
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,11 +21,14 @@ class CAListScreen extends StatefulWidget {
|
||||||
Key? key,
|
Key? key,
|
||||||
required this.cas,
|
required this.cas,
|
||||||
this.onSave,
|
this.onSave,
|
||||||
|
required this.supportsQRScanning,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
final List<CertificateInfo> cas;
|
final List<CertificateInfo> cas;
|
||||||
final ValueChanged<List<CertificateInfo>>? onSave;
|
final ValueChanged<List<CertificateInfo>>? onSave;
|
||||||
|
|
||||||
|
final bool supportsQRScanning;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
_CAListScreenState createState() => _CAListScreenState();
|
_CAListScreenState createState() => _CAListScreenState();
|
||||||
}
|
}
|
||||||
|
@ -88,7 +91,9 @@ class _CAListScreenState extends State<CAListScreen> {
|
||||||
changed = true;
|
changed = true;
|
||||||
cas.remove(key);
|
cas.remove(key);
|
||||||
});
|
});
|
||||||
});
|
},
|
||||||
|
supportsQRScanning: widget.supportsQRScanning,
|
||||||
|
);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
));
|
));
|
||||||
|
@ -129,6 +134,16 @@ class _CAListScreenState extends State<CAListScreen> {
|
||||||
}
|
}
|
||||||
|
|
||||||
List<Widget> _addCA() {
|
List<Widget> _addCA() {
|
||||||
|
Map<String, Widget> children = {
|
||||||
|
'paste': Text('Copy/Paste'),
|
||||||
|
'file': Text('File'),
|
||||||
|
};
|
||||||
|
|
||||||
|
// not all devices have a camera for QR codes
|
||||||
|
if (widget.supportsQRScanning) {
|
||||||
|
children['qr'] = Text('QR Code');
|
||||||
|
}
|
||||||
|
|
||||||
List<Widget> items = [
|
List<Widget> items = [
|
||||||
Padding(
|
Padding(
|
||||||
padding: EdgeInsets.fromLTRB(10, 25, 10, 0),
|
padding: EdgeInsets.fromLTRB(10, 25, 10, 0),
|
||||||
|
@ -141,11 +156,7 @@ class _CAListScreenState extends State<CAListScreen> {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
children: {
|
children: children,
|
||||||
'paste': Text('Copy/Paste'),
|
|
||||||
'file': Text('File'),
|
|
||||||
'qr': Text('QR Code'),
|
|
||||||
},
|
|
||||||
))
|
))
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
|
@ -18,6 +18,7 @@ class CertificateDetailsScreen extends StatefulWidget {
|
||||||
this.onReplace,
|
this.onReplace,
|
||||||
this.pubKey,
|
this.pubKey,
|
||||||
this.privKey,
|
this.privKey,
|
||||||
|
required this.supportsQRScanning,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
final CertificateInfo certInfo;
|
final CertificateInfo certInfo;
|
||||||
|
@ -35,6 +36,8 @@ class CertificateDetailsScreen extends StatefulWidget {
|
||||||
final String? pubKey;
|
final String? pubKey;
|
||||||
final String? privKey;
|
final String? privKey;
|
||||||
|
|
||||||
|
final bool supportsQRScanning;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
_CertificateDetailsScreenState createState() => _CertificateDetailsScreenState();
|
_CertificateDetailsScreenState createState() => _CertificateDetailsScreenState();
|
||||||
}
|
}
|
||||||
|
@ -178,7 +181,9 @@ class _CertificateDetailsScreenState extends State<CertificateDetailsScreen> {
|
||||||
duration: const Duration(milliseconds: 10), curve: Curves.linearToEaseOut);
|
duration: const Duration(milliseconds: 10), curve: Curves.linearToEaseOut);
|
||||||
},
|
},
|
||||||
pubKey: widget.pubKey!,
|
pubKey: widget.pubKey!,
|
||||||
privKey: widget.privKey!);
|
privKey: widget.privKey!,
|
||||||
|
supportsQRScanning: widget.supportsQRScanning,
|
||||||
|
);
|
||||||
});
|
});
|
||||||
})));
|
})));
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,6 +27,7 @@ class SiteConfigScreen extends StatefulWidget {
|
||||||
Key? key,
|
Key? key,
|
||||||
this.site,
|
this.site,
|
||||||
required this.onSave,
|
required this.onSave,
|
||||||
|
required this.supportsQRScanning,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
final Site? site;
|
final Site? site;
|
||||||
|
@ -34,6 +35,8 @@ class SiteConfigScreen extends StatefulWidget {
|
||||||
// This is called after the target OS has saved the configuration
|
// This is called after the target OS has saved the configuration
|
||||||
final ValueChanged<Site> onSave;
|
final ValueChanged<Site> onSave;
|
||||||
|
|
||||||
|
final bool supportsQRScanning;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
_SiteConfigScreenState createState() => _SiteConfigScreenState();
|
_SiteConfigScreenState createState() => _SiteConfigScreenState();
|
||||||
}
|
}
|
||||||
|
@ -186,7 +189,9 @@ class _SiteConfigScreenState extends State<SiteConfigScreen> {
|
||||||
site.certInfo = result.certInfo;
|
site.certInfo = result.certInfo;
|
||||||
site.key = result.key;
|
site.key = result.key;
|
||||||
});
|
});
|
||||||
});
|
},
|
||||||
|
supportsQRScanning: widget.supportsQRScanning,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return AddCertificateScreen(
|
return AddCertificateScreen(
|
||||||
|
@ -198,7 +203,9 @@ class _SiteConfigScreenState extends State<SiteConfigScreen> {
|
||||||
site.certInfo = result.certInfo;
|
site.certInfo = result.certInfo;
|
||||||
site.key = result.key;
|
site.key = result.key;
|
||||||
});
|
});
|
||||||
});
|
},
|
||||||
|
supportsQRScanning: widget.supportsQRScanning,
|
||||||
|
);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
@ -222,7 +229,9 @@ class _SiteConfigScreenState extends State<SiteConfigScreen> {
|
||||||
changed = true;
|
changed = true;
|
||||||
site.ca = ca;
|
site.ca = ca;
|
||||||
});
|
});
|
||||||
});
|
},
|
||||||
|
supportsQRScanning: widget.supportsQRScanning,
|
||||||
|
);
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
],
|
],
|
||||||
|
|
Loading…
Reference in New Issue