diff --git a/Firmware/Linux_HCI/HCI.py b/Firmware/Linux_HCI/HCI.py index d38878f..e50c62f 100755 --- a/Firmware/Linux_HCI/HCI.py +++ b/Firmware/Linux_HCI/HCI.py @@ -32,11 +32,15 @@ def bytes_to_strarray(bytes_, with_prefix=False): def run_hci_cmd(cmd, hci="hci0", wait=1): cmd_ = ["hcitool", "-i", hci, "cmd"] cmd_ += cmd - print(cmd_) + #print(cmd_) subprocess.run(cmd_) if wait > 0: time.sleep(wait) +def run_hciconf_leadv_3(hci="hci0"): + #EDIT: the above command makes the advertised service connectable. If you don't want to allow connections, change it to $ sudo hciconfig hci0 leadv 3 + cmd_ = ["hciconfig", hci, "leadv", "3"] + subprocess.run(cmd_) def start_advertising(key, interval_ms=2000): addr = bytearray(key[:6]) @@ -46,14 +50,14 @@ def start_advertising(key, interval_ms=2000): adv[7:29] = key[6:28] adv[29] = key[0] >> 6 - print(f"key ({len(key):2}) {key.hex()}") - print(f"address ({len(addr):2}) {addr.hex()}") - print(f"payload ({len(adv):2}) {adv.hex()}") + #print(f"key ({len(key):2}) {key.hex()}") + #print(f"address ({len(addr):2}) {addr.hex()}") + #print(f"payload ({len(adv):2}) {adv.hex()}") # Set BLE address run_hci_cmd(["0x3f", "0x001"] + bytes_to_strarray(addr, with_prefix=True)[::-1]) subprocess.run(["systemctl", "restart", "bluetooth"]) - time.sleep(1) + time.sleep(3) # Set BLE advertisement payload run_hci_cmd(["0x08", "0x0008"] + [format(len(adv), "x")] + bytes_to_strarray(adv)) @@ -70,6 +74,8 @@ def start_advertising(key, interval_ms=2000): # Start BLE advertising run_hci_cmd(["0x08", "0x000a"] + ["01"], wait=0) + run_hciconf_leadv_3() + def main(args): parser = argparse.ArgumentParser() diff --git a/openhaystack-mobile/lib/accessory/accessory_list.dart b/openhaystack-mobile/lib/accessory/accessory_list.dart index 481025f..f5bc8e7 100644 --- a/openhaystack-mobile/lib/accessory/accessory_list.dart +++ b/openhaystack-mobile/lib/accessory/accessory_list.dart @@ -1,5 +1,6 @@ import 'dart:math'; +import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter_slidable/flutter_slidable.dart'; @@ -63,12 +64,16 @@ class _AccessoryListState extends State { return const NoAccessoriesPlaceholder(); } - // TODO: Refresh Indicator for desktop - // Use pull to refresh method return SlidableAutoCloseBehavior(child: RefreshIndicator( onRefresh: widget.loadLocationUpdates, - child: Scrollbar( + child: ScrollConfiguration( + behavior: ScrollConfiguration.of(context).copyWith( + dragDevices: { + PointerDeviceKind.touch, + PointerDeviceKind.mouse, + }, + ), child: ListView( children: accessories.map((accessory) { // Calculate distance from users devices location diff --git a/openhaystack-mobile/lib/accessory/accessory_registry.dart b/openhaystack-mobile/lib/accessory/accessory_registry.dart index 32c12d8..58a9e50 100644 --- a/openhaystack-mobile/lib/accessory/accessory_registry.dart +++ b/openhaystack-mobile/lib/accessory/accessory_registry.dart @@ -5,6 +5,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:openhaystack_mobile/accessory/accessory_model.dart'; import 'package:latlong2/latlong.dart'; +import 'package:openhaystack_mobile/accessory/accessory_storage.dart'; import 'package:openhaystack_mobile/findMy/find_my_controller.dart'; import 'package:openhaystack_mobile/findMy/models.dart'; @@ -12,7 +13,7 @@ const accessoryStorageKey = 'ACCESSORIES'; class AccessoryRegistry extends ChangeNotifier { - final _storage = const FlutterSecureStorage(); + final _storage = const AccessoryStorage(const FlutterSecureStorage()); final _findMyController = FindMyController(); List _accessories = []; bool loading = false; @@ -99,12 +100,12 @@ class AccessoryRegistry extends ChangeNotifier { var reportsForAccessories = await Future.wait(runningLocationRequests); for (var i = 0; i < currentAccessories.length; i++) { var accessory = currentAccessories.elementAt(i); - var reports = reportsForAccessories.elementAt(i); + var reports = reportsForAccessories.elementAt(i) + .where((report) => report.latitude.abs() <= 90 && report.longitude.abs() < 90 ); print("Found ${reports.length} reports for accessory '${accessory.name}'"); accessory.locationHistory = reports - .where((report) => report.latitude.abs() <= 90 && report.longitude.abs() < 90 ) .map((report) => Pair( LatLng(report.latitude, report.longitude), report.timestamp ?? report.published, diff --git a/openhaystack-mobile/lib/accessory/accessory_storage.dart b/openhaystack-mobile/lib/accessory/accessory_storage.dart new file mode 100644 index 0000000..5b967af --- /dev/null +++ b/openhaystack-mobile/lib/accessory/accessory_storage.dart @@ -0,0 +1,75 @@ +import 'dart:io'; + +import 'package:flutter_secure_storage/flutter_secure_storage.dart'; + +const CRED_MAX_CREDENTIAL_BLOB_SIZE = 5 * 512; + +class AccessoryStorage { + final FlutterSecureStorage flutterSecureStorage; + + const AccessoryStorage(this.flutterSecureStorage); + Future deleteAll() async { + await flutterSecureStorage.deleteAll(); + } + + Future delete({required String key}) async { + if (Platform.isWindows) { + final chunkedKey = '\$${key}_chunk_size'; + + final chunkSize = + int.parse(await flutterSecureStorage.read(key: chunkedKey) ?? '0'); + + if (chunkSize > 0) { + await Future.wait(List.generate(chunkSize, + (i) async => await flutterSecureStorage.delete(key: '${key}_${i + 1}'))); + } else { + await flutterSecureStorage.delete(key: key); + } + await flutterSecureStorage.delete(key:chunkedKey); + } else { + await flutterSecureStorage.delete(key: key); + } + } + + Future write({ + required String key, + required String? value, + }) async { + if (Platform.isWindows && + value != null && + value.length > CRED_MAX_CREDENTIAL_BLOB_SIZE) { + final exp = RegExp(r".{1,512}"); + final matches = exp.allMatches(value).toList(); + final chunkedKey = '\$${key}_chunk_size'; + await Future.wait(List.generate(matches.length + 1, (i) { + return i == 0 + ? flutterSecureStorage.write( + key: chunkedKey, value: matches.length.toString()) + : flutterSecureStorage.write( + key: '${key}_${i}', value: matches[i - 1].group(0)); + })); + } else { + await flutterSecureStorage.write(key: key, value: value); + } + } + + Future read({required String key}) async { + if (Platform.isWindows) { + // await this.delete(key: key); + final chunkedKey = '\$${key}_chunk_size'; + + final chunkSize = + int.parse(await flutterSecureStorage.read(key: chunkedKey) ?? '0'); + + if (chunkSize > 0) { + final chunks = await Future.wait(List.generate(chunkSize, + (i) async => await flutterSecureStorage.read(key: '${key}_${i+1}'))); + return chunks.join(); + } else { + return await flutterSecureStorage.read(key: key); + } + } else { + return await flutterSecureStorage.read(key: key); + } + } +} \ No newline at end of file diff --git a/openhaystack-mobile/lib/dashboard/dashboard_desktop.dart b/openhaystack-mobile/lib/dashboard/dashboard_desktop.dart index b7aa620..27ba9c3 100644 --- a/openhaystack-mobile/lib/dashboard/dashboard_desktop.dart +++ b/openhaystack-mobile/lib/dashboard/dashboard_desktop.dart @@ -1,4 +1,6 @@ import 'package:flutter/material.dart'; +import 'package:flutter_map/flutter_map.dart'; +import 'package:openhaystack_mobile/item_management/item_management_desktop.dart'; import 'package:provider/provider.dart'; import 'package:openhaystack_mobile/accessory/accessory_list.dart'; import 'package:openhaystack_mobile/accessory/accessory_registry.dart'; @@ -6,6 +8,7 @@ import 'package:openhaystack_mobile/location/location_model.dart'; import 'package:openhaystack_mobile/map/map.dart'; import 'package:openhaystack_mobile/preferences/preferences_page.dart'; import 'package:openhaystack_mobile/preferences/user_preferences_model.dart'; +import 'package:latlong2/latlong.dart'; class DashboardDesktop extends StatefulWidget { @@ -20,7 +23,13 @@ class DashboardDesktop extends StatefulWidget { } class _DashboardDesktopState extends State { + final MapController _mapController = MapController(); + void _centerPoint(LatLng point) { + _mapController.fitBounds( + LatLngBounds(point), + ); + } @override void initState() { super.initState(); @@ -40,7 +49,21 @@ class _DashboardDesktopState extends State { /// Fetch locaiton updates for all accessories. Future loadLocationUpdates() async { var accessoryRegistry = Provider.of(context, listen: false); - await accessoryRegistry.loadLocationReports(); + try { + await accessoryRegistry.loadLocationReports(); + } catch (e) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + backgroundColor: Theme.of(context).colorScheme.error, + content: Text( + 'Could not find location reports. Try again later.', + style: TextStyle( + color: Theme.of(context).colorScheme.onError, + ), + ), + ), + ); + } } @override @@ -55,7 +78,12 @@ class _DashboardDesktopState extends State { AppBar( title: const Text('OpenHaystack'), leading: IconButton( - onPressed: () { /* reload */ }, + onPressed: () { + Navigator.push( + context, + MaterialPageRoute(builder: (context) => const ItemManagementDesktop()), + ); + }, icon: const Icon(Icons.menu), ), actions: [ @@ -77,13 +105,16 @@ class _DashboardDesktopState extends State { Expanded( child: AccessoryList( loadLocationUpdates: loadLocationUpdates, + centerOnPoint: _centerPoint, ), ), ], ), ), - const Expanded( - child: AccessoryMap(), + Expanded( + child: AccessoryMap( + mapController: _mapController, + ), ), ], ), diff --git a/openhaystack-mobile/lib/findMy/decrypt_reports.dart b/openhaystack-mobile/lib/findMy/decrypt_reports.dart index f8fefa4..1ab0486 100644 --- a/openhaystack-mobile/lib/findMy/decrypt_reports.dart +++ b/openhaystack-mobile/lib/findMy/decrypt_reports.dart @@ -1,11 +1,13 @@ import 'dart:convert'; -import 'dart:isolate'; import 'dart:typed_data'; +import 'package:flutter/foundation.dart'; import 'package:pointycastle/export.dart'; import 'package:pointycastle/src/utils.dart' as pc_utils; import 'package:openhaystack_mobile/findMy/models.dart'; +import 'package:cryptography/cryptography.dart' as nice_crypto; + class DecryptReports { /// Decrypts a given [FindMyReport] with the given private key. static Future decryptReport( @@ -19,9 +21,8 @@ class DecryptReports { _decodeTimeAndConfidence(payloadData, report); - final privateKey = ECPrivateKey( - pc_utils.decodeBigIntWithSign(1, key), - curveDomainParam); + final privateKey = + ECPrivateKey(pc_utils.decodeBigIntWithSign(1, key), curveDomainParam); final decodePoint = curveDomainParam.curve.decodePoint(ephemeralKeyBytes); final ephemeralPublicKey = ECPublicKey(decodePoint, curveDomainParam); @@ -29,16 +30,17 @@ class DecryptReports { final Uint8List sharedKeyBytes = _ecdh(ephemeralPublicKey, privateKey); final Uint8List derivedKey = _kdf(sharedKeyBytes, ephemeralKeyBytes); - final decryptedPayload = _decryptPayload(encData, derivedKey, tag); + final decryptedPayload = await _decryptPayload(encData, derivedKey, tag); final locationReport = _decodePayload(decryptedPayload, report); return locationReport; } /// Decodes the unencrypted timestamp and confidence - static void _decodeTimeAndConfidence(Uint8List payloadData, FindMyReport report) { - final seenTimeStamp = payloadData.sublist(0, 4).buffer.asByteData() - .getInt32(0, Endian.big); + static void _decodeTimeAndConfidence( + Uint8List payloadData, FindMyReport report) { + final seenTimeStamp = + payloadData.sublist(0, 4).buffer.asByteData().getInt32(0, Endian.big); final timestamp = DateTime(2001).add(Duration(seconds: seenTimeStamp)); final confidence = payloadData.elementAt(4); report.timestamp = timestamp; @@ -47,11 +49,12 @@ class DecryptReports { /// Performs an Elliptic Curve Diffie-Hellman with the given keys. /// Returns the derived raw key data. - static Uint8List _ecdh(ECPublicKey ephemeralPublicKey, ECPrivateKey privateKey) { + static Uint8List _ecdh( + ECPublicKey ephemeralPublicKey, ECPrivateKey privateKey) { final sharedKey = ephemeralPublicKey.Q! * privateKey.d; - final sharedKeyBytes = pc_utils.encodeBigIntAsUnsigned( - sharedKey!.x!.toBigInteger()!); - print("Isolate:${Isolate.current.hashCode}: Shared Key (shared secret): ${base64Encode(sharedKeyBytes)}"); + final sharedKeyBytes = + pc_utils.encodeBigIntAsUnsigned(sharedKey!.x!.toBigInteger()!); + print("Shared Key (shared secret): ${base64Encode(sharedKeyBytes)}"); return sharedKeyBytes; } @@ -60,7 +63,6 @@ class DecryptReports { /// the resulting [FindMyLocationReport]. static FindMyLocationReport _decodePayload( Uint8List payload, FindMyReport report) { - final latitude = payload.buffer.asByteData(0, 4).getUint32(0, Endian.big); final longitude = payload.buffer.asByteData(4, 4).getUint32(0, Endian.big); final accuracy = payload.buffer.asByteData(8, 1).getUint8(0); @@ -74,14 +76,28 @@ class DecryptReports { /// Decrypts the given cipher text with the key data using an AES-GCM block cipher. /// Returns the decrypted raw data. - static Uint8List _decryptPayload( - Uint8List cipherText, Uint8List symmetricKey, Uint8List tag) { + static Future _decryptPayload( + Uint8List cipherText, Uint8List symmetricKey, Uint8List tag) async { final decryptionKey = symmetricKey.sublist(0, 16); final iv = symmetricKey.sublist(16, symmetricKey.length); + if (kIsWeb) { + nice_crypto.SecretKey secretKey = + new nice_crypto.SecretKey(decryptionKey); + + nice_crypto.SecretBox secretBox = new nice_crypto.SecretBox(cipherText, + nonce: iv, mac: nice_crypto.Mac(tag)); + + List decrypted = await nice_crypto.AesGcm.with128bits() + .decrypt(secretBox, secretKey: secretKey); + + return Uint8List.fromList(decrypted); + } final aesGcm = GCMBlockCipher(AESEngine()) - ..init(false, AEADParameters(KeyParameter(decryptionKey), - tag.lengthInBytes * 8, iv, tag)); + ..init( + false, + AEADParameters( + KeyParameter(decryptionKey), tag.lengthInBytes * 8, iv, tag)); final plainText = Uint8List(cipherText.length); var offset = 0; @@ -109,7 +125,7 @@ class DecryptReports { Uint8List out = Uint8List(shaDigest.digestSize); shaDigest.doFinal(out, 0); - print("Isolate:${Isolate.current.hashCode}: Derived key: ${base64Encode(out)}"); + print("Derived key: ${base64Encode(out)}"); return out; } } diff --git a/openhaystack-mobile/lib/findMy/find_my_controller.dart b/openhaystack-mobile/lib/findMy/find_my_controller.dart index 1b1abe4..bc52e90 100644 --- a/openhaystack-mobile/lib/findMy/find_my_controller.dart +++ b/openhaystack-mobile/lib/findMy/find_my_controller.dart @@ -1,6 +1,5 @@ import 'dart:collection'; import 'dart:convert'; -import 'dart:isolate'; import 'dart:typed_data'; import 'package:flutter/foundation.dart'; @@ -14,13 +13,14 @@ import 'package:openhaystack_mobile/findMy/reports_fetcher.dart'; class FindMyController { static const _storage = FlutterSecureStorage(); - static final ECCurve_secp224r1 _curveParams = ECCurve_secp224r1(); + static final ECCurve_secp224r1 _curveParams = ECCurve_secp224r1(); static HashMap _keyCache = HashMap(); /// Starts a new [Isolate], fetches and decrypts all location reports /// for the given [FindMyKeyPair]. /// Returns a list of [FindMyLocationReport]'s. - static Future> computeResults(FindMyKeyPair keyPair) async{ + static Future> computeResults( + FindMyKeyPair keyPair) async { await _loadPrivateKey(keyPair); return compute(_getListedReportResults, keyPair); } @@ -28,11 +28,18 @@ class FindMyController { /// Fetches and decrypts the location reports for the given /// [FindMyKeyPair] from apples FindMy Network. /// Returns a list of [FindMyLocationReport]. - static Future> _getListedReportResults(FindMyKeyPair keyPair) async{ + static Future> _getListedReportResults( + FindMyKeyPair keyPair) async { List results = []; - final jsonResults = await ReportsFetcher.fetchLocationReports(keyPair.getHashedAdvertisementKey()); + final jsonResults = await ReportsFetcher.fetchLocationReports( + keyPair.getHashedAdvertisementKey()); for (var result in jsonResults) { - results.add(await _decryptResult(result, keyPair, keyPair.privateKeyBase64!)); + try { + results.add( + await _decryptResult(result, keyPair, keyPair.privateKeyBase64!)); + } catch (e) { + print(e); + } } return results; } @@ -43,7 +50,8 @@ class FindMyController { String? privateKey; if (!_keyCache.containsKey(keyPair.hashedPublicKey)) { privateKey = await _storage.read(key: keyPair.hashedPublicKey); - final newKey = _keyCache.putIfAbsent(keyPair.hashedPublicKey, () => privateKey); + final newKey = + _keyCache.putIfAbsent(keyPair.hashedPublicKey, () => privateKey); assert(newKey == privateKey); } else { privateKey = _keyCache[keyPair.hashedPublicKey]; @@ -55,27 +63,29 @@ class FindMyController { static ECPublicKey _derivePublicKey(ECPrivateKey privateKey) { final pk = _curveParams.G * privateKey.d; final publicKey = ECPublicKey(pk, _curveParams); - print("Isolate:${Isolate.current.hashCode}: Point Data: ${base64Encode(publicKey.Q!.getEncoded(false))}"); + print("Point Data: ${base64Encode(publicKey.Q!.getEncoded(false))}"); return publicKey; } /// Decrypts the encrypted reports with the given [FindMyKeyPair] and private key. /// Returns the decrypted report as a [FindMyLocationReport]. - static Future _decryptResult(dynamic result, FindMyKeyPair keyPair, String privateKey) async { - assert (result["id"]! == keyPair.getHashedAdvertisementKey(), - "Returned FindMyReport hashed key != requested hashed key"); - - final unixTimestampInMillis = result["datePublished"]; - final datePublished = DateTime.fromMillisecondsSinceEpoch(unixTimestampInMillis); + static Future _decryptResult( + dynamic result, FindMyKeyPair keyPair, String privateKey) async { + assert(result["id"]! == keyPair.getHashedAdvertisementKey(), + "Returned FindMyReport hashed key != requested hashed key"); + + final unixTimestampInMillis = result["datePublished"]; + final datePublished = + DateTime.fromMillisecondsSinceEpoch(unixTimestampInMillis); FindMyReport report = FindMyReport( datePublished, base64Decode(result["payload"]), keyPair.getHashedAdvertisementKey(), result["statusCode"]); - FindMyLocationReport decryptedReport = await DecryptReports - .decryptReport(report, base64Decode(privateKey)); + FindMyLocationReport decryptedReport = + await DecryptReports.decryptReport(report, base64Decode(privateKey)); return decryptedReport; } @@ -86,10 +96,12 @@ class FindMyController { final privateKeyBase64 = await _storage.read(key: base64HashedPublicKey); ECPrivateKey privateKey = ECPrivateKey( - pc_utils.decodeBigIntWithSign(1, base64Decode(privateKeyBase64!)), _curveParams); + pc_utils.decodeBigIntWithSign(1, base64Decode(privateKeyBase64!)), + _curveParams); ECPublicKey publicKey = _derivePublicKey(privateKey); - return FindMyKeyPair(publicKey, base64HashedPublicKey, privateKey, DateTime.now(), -1); + return FindMyKeyPair( + publicKey, base64HashedPublicKey, privateKey, DateTime.now(), -1); } /// Imports a base64 encoded private key to the local [FlutterSecureStorage]. @@ -101,14 +113,11 @@ class FindMyController { final ECPublicKey publicKey = _derivePublicKey(privateKey); final hashedPublicKey = getHashedPublicKey(publicKey: publicKey); final keyPair = FindMyKeyPair( - publicKey, - hashedPublicKey, - privateKey, - DateTime.now(), - -1); - - await _storage.write(key: hashedPublicKey, value: keyPair.getBase64PrivateKey()); - + publicKey, hashedPublicKey, privateKey, DateTime.now(), -1); + + await _storage.write( + key: hashedPublicKey, value: keyPair.getBase64PrivateKey()); + return keyPair; } @@ -117,16 +126,18 @@ class FindMyController { static Future generateKeyPair() async { final ecCurve = ECCurve_secp224r1(); final secureRandom = SecureRandom('Fortuna') - ..seed(KeyParameter( - Platform.instance.platformEntropySource().getBytes(32))); + ..seed( + KeyParameter(Platform.instance.platformEntropySource().getBytes(32))); ECKeyGenerator keyGen = ECKeyGenerator() - ..init(ParametersWithRandom(ECKeyGeneratorParameters(ecCurve), secureRandom)); + ..init(ParametersWithRandom( + ECKeyGeneratorParameters(ecCurve), secureRandom)); final newKeyPair = keyGen.generateKeyPair(); final ECPublicKey publicKey = newKeyPair.publicKey as ECPublicKey; final ECPrivateKey privateKey = newKeyPair.privateKey as ECPrivateKey; final hashedKey = getHashedPublicKey(publicKey: publicKey); - final keyPair = FindMyKeyPair(publicKey, hashedKey, privateKey, DateTime.now(), -1); + final keyPair = + FindMyKeyPair(publicKey, hashedKey, privateKey, DateTime.now(), -1); await _storage.write(key: hashedKey, value: keyPair.getBase64PrivateKey()); return keyPair; @@ -135,7 +146,8 @@ class FindMyController { /// Returns hashed, base64 encoded public key for given [publicKeyBytes] /// or for an [ECPublicKey] object [publicKey], if [publicKeyBytes] equals null. /// Returns the base64 encoded hashed public key as a [String]. - static String getHashedPublicKey({Uint8List? publicKeyBytes, ECPublicKey? publicKey}) { + static String getHashedPublicKey( + {Uint8List? publicKeyBytes, ECPublicKey? publicKey}) { var pkBytes = publicKeyBytes ?? publicKey!.Q!.getEncoded(false); final shaDigest = SHA256Digest(); shaDigest.update(pkBytes, 0, pkBytes.lengthInBytes); @@ -143,4 +155,4 @@ class FindMyController { shaDigest.doFinal(out, 0); return base64Encode(out); } -} \ No newline at end of file +} diff --git a/openhaystack-mobile/lib/history/accessory_history.dart b/openhaystack-mobile/lib/history/accessory_history.dart index 66de060..43173ac 100644 --- a/openhaystack-mobile/lib/history/accessory_history.dart +++ b/openhaystack-mobile/lib/history/accessory_history.dart @@ -1,3 +1,4 @@ +import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'package:flutter_map/plugin_api.dart'; import 'package:openhaystack_mobile/accessory/accessory_model.dart'; @@ -23,16 +24,26 @@ class AccessoryHistory extends StatefulWidget { class _AccessoryHistoryState extends State { final MapController _mapController = MapController(); - + final ScrollController _scrollController = new ScrollController(); + List> locationHistory = []; bool showPopup = false; Pair? popupEntry; double numberOfDays = 7; + int scrolledMarker = -1; @override void initState() { super.initState(); - + _scrollController.addListener(() { + setState(() { + var old = scrolledMarker; + scrolledMarker = _scrollController.offset.toInt() ~/ 24; + if (old != scrolledMarker) { + _mapController.move(widget.accessory.locationHistory.elementAt(scrolledMarker).a, _mapController.zoom); + } + }); + }); _mapController.onReady .then((_) { var historicLocations = widget.accessory.locationHistory @@ -40,122 +51,185 @@ class _AccessoryHistoryState extends State { var bounds = LatLngBounds.fromPoints(historicLocations); _mapController.fitBounds(bounds); }); + buildLocationHistory(); + } + + void buildLocationHistory() { + var now = DateTime.now(); + locationHistory = widget.accessory.locationHistory + .where( + (element) => element.b.isAfter( + now.subtract(Duration(days: numberOfDays.round())), + ), + ).toList(); + } + List buildMarkers() { + List toRet = []; + var length = locationHistory.length; + for (int i=1; i< length-1; i++) { + toRet.add(buildMarker(i)); + } + if (length > 0) { + toRet.add(buildMarker(0)); + } + if (length > 1) { + toRet.add(buildMarker(length-1)); + } + return toRet; + } + + Marker buildMarker(int index) { + var entry = locationHistory.elementAt(index); + return Marker( + point: entry.a, + + builder: (ctx) => GestureDetector( + onTap: () { + setState(() { + showPopup = true; + popupEntry = entry; + }); + }, + child: Icon( + Icons.circle, + size: (locationHistory.first == entry || locationHistory.last == entry) ? 20: 10, + color: (() { + if (locationHistory.first == entry) { + return Colors.amber; + } + if (locationHistory.last == entry) { + return Colors.green; + } + if (index == scrolledMarker) { + return Colors.pink; + } + if (entry == popupEntry) { + return Colors.red; + } + return Theme.of(context).indicatorColor; + })().withOpacity(0.9), + ), + ), + ); + } @override Widget build(BuildContext context) { // Filter for the locations after the specified cutoff date (now - number of days) - var now = DateTime.now(); - List> locationHistory = widget.accessory.locationHistory - .where( - (element) => element.b.isAfter( - now.subtract(Duration(days: numberOfDays.round())), - ), - ).toList(); return Scaffold( appBar: AppBar( title: Text(widget.accessory.name), ), body: SafeArea( - child: Column( - children: [ - Flexible( - flex: 3, - fit: FlexFit.tight, - child: FlutterMap( - mapController: _mapController, - options: MapOptions( - center: LatLng(49.874739, 8.656280), - zoom: 13.0, - interactiveFlags: - InteractiveFlag.pinchZoom | InteractiveFlag.drag | - InteractiveFlag.doubleTapZoom | InteractiveFlag.flingAnimation | - InteractiveFlag.pinchMove, - onTap: (_, __) { - setState(() { - showPopup = false; - popupEntry = null; - }); - }, - ), - layers: [ - TileLayerOptions( - backgroundColor: Theme.of(context).colorScheme.surface, - tileBuilder: (context, child, tile) { - var isDark = (Theme.of(context).brightness == Brightness.dark); - return isDark ? ColorFiltered( - colorFilter: const ColorFilter.matrix([ - -1, 0, 0, 0, 255, - 0, -1, 0, 0, 255, - 0, 0, -1, 0, 255, - 0, 0, 0, 1, 0, - ]), - child: child, - ) : child; - }, - urlTemplate: "https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", - subdomains: ['a', 'b', 'c'], - attributionBuilder: (_) { - return const Text("© OpenStreetMap contributors"); + child: Scrollbar( + child: Column( + children: [ + Flexible( + flex: 4, + fit: FlexFit.tight, + child: FlutterMap( + mapController: _mapController, + options: MapOptions( + center: LatLng(49.874739, 8.656280), + zoom: 13.0, + maxZoom: 18.25, + interactiveFlags: + InteractiveFlag.pinchZoom | InteractiveFlag.drag | + InteractiveFlag.doubleTapZoom | InteractiveFlag.flingAnimation | + InteractiveFlag.pinchMove, + onTap: (_, __) { + setState(() { + showPopup = false; + popupEntry = null; + }); }, ), - // The line connecting the locations chronologically - PolylineLayerOptions( - polylines: [ - Polyline( - points: locationHistory.map((entry) => entry.a).toList(), - strokeWidth: 4, - color: Theme.of(context).colorScheme.primaryVariant, - ), - ], - ), - // The markers for the historic locaitons - MarkerLayerOptions( - markers: locationHistory.map((entry) => Marker( - point: entry.a, - builder: (ctx) => GestureDetector( - onTap: () { - setState(() { - showPopup = true; - popupEntry = entry; - }); - }, - child: Icon( - Icons.circle, - size: 15, - color: entry == popupEntry - ? Colors.red - : Theme.of(context).indicatorColor, + layers: [ + TileLayerOptions( + backgroundColor: Theme.of(context).colorScheme.surface, + tileBuilder: (context, child, tile) { + var isDark = (Theme.of(context).brightness == Brightness.dark); + return isDark ? ColorFiltered( + colorFilter: const ColorFilter.matrix([ + -1, 0, 0, 0, 255, + 0, -1, 0, 0, 255, + 0, 0, -1, 0, 255, + 0, 0, 0, 1, 0, + ]), + child: child, + ) : child; + }, + // urlTemplate: "https://mt0.google.com/vt/lyrs=m@221097413&x={x}&y={y}&z={z}", + urlTemplate: "https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", + subdomains: ['a', 'b', 'c'], + attributionBuilder: (_) { + return const Text("© OpenStreetMap contributors"); + }, + ), + // The line connecting the locations chronologically + PolylineLayerOptions( + polylines: [ + Polyline( + points: locationHistory.map((entry) => entry.a).toList(), + strokeWidth: 4, + color: Theme.of(context).colorScheme.primaryVariant, + isDotted: true ), - ), - )).toList(), - ), - // Displays the tooltip if active - MarkerLayerOptions( - markers: [ - if (showPopup) LocationPopup( - location: popupEntry!.a, - time: popupEntry!.b, - ), - ], - ), - ], + ], + ), + // The markers for the historic locaitons + MarkerLayerOptions( + markers: buildMarkers(), + ), + // Displays the tooltip if active + MarkerLayerOptions( + markers: [ + if (showPopup) LocationPopup( + location: popupEntry!.a, + time: popupEntry!.b, + ), + ], + ), + ], + ), ), - ), - Flexible( - flex: 1, - fit: FlexFit.tight, - child: DaysSelectionSlider( - numberOfDays: numberOfDays, - onChanged: (double newValue) { - setState(() { - numberOfDays = newValue; - }); - }, + Flexible( + flex: 1, + fit: FlexFit.tight, + child: DaysSelectionSlider( + numberOfDays: numberOfDays, + onChanged: (double newValue) { + setState(() { + numberOfDays = newValue; + buildLocationHistory(); + }); + }, + ), ), - ), - ], + Flexible( + flex: 1, + fit: FlexFit.tight, + child: ScrollConfiguration( + behavior: ScrollConfiguration.of(context).copyWith(dragDevices: { + PointerDeviceKind.touch, + PointerDeviceKind.mouse, + },), + child: Padding( + padding: const EdgeInsets.symmetric(vertical: 0, horizontal: 8.0), + child: ListView( + physics: const AlwaysScrollableScrollPhysics(), + padding: EdgeInsets.only(top:12), + controller: _scrollController, + scrollDirection: Axis.vertical, + children: locationHistory.map((l) => SizedBox(height: 24, child: Text(l.b.toString()))).toList(), + ), + ), + ) + ) + ], + ), ), ), ); diff --git a/openhaystack-mobile/lib/item_management/item_export.dart b/openhaystack-mobile/lib/item_management/item_export.dart index e2a29f8..1d46abf 100644 --- a/openhaystack-mobile/lib/item_management/item_export.dart +++ b/openhaystack-mobile/lib/item_management/item_export.dart @@ -3,26 +3,44 @@ import 'dart:io'; import 'package:flutter/material.dart'; import 'package:flutter/foundation.dart'; +import 'package:url_launcher/url_launcher.dart'; import 'package:path_provider/path_provider.dart'; import 'package:provider/provider.dart'; import 'package:openhaystack_mobile/accessory/accessory_dto.dart'; import 'package:openhaystack_mobile/accessory/accessory_model.dart'; import 'package:openhaystack_mobile/accessory/accessory_registry.dart'; import 'package:share_plus/share_plus.dart'; +import 'package:clipboard/clipboard.dart'; class ItemExportMenu extends StatelessWidget { /// The accessory to export from Accessory accessory; /// Displays a bottom sheet with export options. - /// - /// The accessory can be exported to a JSON file or the + /// + /// The accessory can be exported to a JSON file or the /// key parameters can be exported separately. ItemExportMenu({ Key? key, required this.accessory, }) : super(key: key); + Future _share(String s, BuildContext context) async { + if (kIsWeb || Platform.isWindows) { + await FlutterClipboard.copy(s); + ScaffoldMessenger.of(context).showSnackBar( + + const SnackBar( + content: Text('Copied in clipboard'), + + ), + ); + + return; + } + Share.share(s); + } + /// Shows the export options for the [accessory]. void showKeyExportSheet(BuildContext context, Accessory accessory) { showModalBottomSheet(context: context, builder: (BuildContext context) { @@ -43,14 +61,14 @@ class ItemExportMenu extends StatelessWidget { title: const Text('Export All Accessories (JSON)'), onTap: () async { var accessories = Provider.of(context, listen: false).accessories; - await _exportAccessoriesAsJSON(accessories); + await _exportAccessoriesAsJSON(accessories, context); Navigator.pop(context); }, ), ListTile( title: const Text('Export Accessory (JSON)'), onTap: () async { - await _exportAccessoriesAsJSON([accessory]); + await _exportAccessoriesAsJSON([accessory], context); Navigator.pop(context); }, ), @@ -58,7 +76,7 @@ class ItemExportMenu extends StatelessWidget { title: const Text('Export Hashed Advertisement Key (Base64)'), onTap: () async { var advertisementKey = await accessory.getHashedAdvertisementKey(); - Share.share(advertisementKey); + await _share(advertisementKey, context); Navigator.pop(context); }, ), @@ -66,7 +84,7 @@ class ItemExportMenu extends StatelessWidget { title: const Text('Export Advertisement Key (Base64)'), onTap: () async { var advertisementKey = await accessory.getAdvertisementKey(); - Share.share(advertisementKey); + await _share(advertisementKey, context); Navigator.pop(context); }, ), @@ -74,7 +92,7 @@ class ItemExportMenu extends StatelessWidget { title: const Text('Export Private Key (Base64)'), onTap: () async { var privateKey = await accessory.getPrivateKey(); - Share.share(privateKey); + await _share(privateKey, context); Navigator.pop(context); }, ), @@ -85,13 +103,12 @@ class ItemExportMenu extends StatelessWidget { } /// Export the serialized [accessories] as a JSON file. - /// + /// /// The OpenHaystack export format is used for interoperability with /// the desktop app. - Future _exportAccessoriesAsJSON(List accessories) async { + Future _exportAccessoriesAsJSON(List accessories, BuildContext context) async { // Create temporary directory to store export file - Directory tempDir = await getTemporaryDirectory(); - String path = tempDir.path; + // Convert accessories to export format List exportAccessories = []; for (Accessory accessory in accessories) { @@ -117,18 +134,39 @@ class ItemExportMenu extends StatelessWidget { isActive: accessory.isActive, )); } + + JsonEncoder encoder = const JsonEncoder.withIndent(' '); // format output + String encodedAccessories = encoder.convert(exportAccessories); + if (kIsWeb) { + await FlutterClipboard.copy(encodedAccessories); + ScaffoldMessenger.of(context).showSnackBar( + + const SnackBar( + content: Text('Copied in clipboard'), + + ), + ); + return; + } + Directory tempDir = await getTemporaryDirectory(); + String path = tempDir.path; // Create file and write accessories as json const filename = 'accessories.json'; File file = File('$path/$filename'); - JsonEncoder encoder = const JsonEncoder.withIndent(' '); // format output - String encodedAccessories = encoder.convert(exportAccessories); + await file.writeAsString(encodedAccessories); - // Share export file over os share dialog - Share.shareFiles( - [file.path], - mimeTypes: ['application/json'], - subject: filename, - ); + + if (Platform.isWindows) { + // on windows we can open the file. + launch('file://${file.path}'); + } else { + // Share export file over os share dialog + Share.shareFiles( + [file.path], + mimeTypes: ['application/json'], + subject: filename, + ); + } } /// Show an explanation how the different key types are used. diff --git a/openhaystack-mobile/lib/item_management/item_management_desktop.dart b/openhaystack-mobile/lib/item_management/item_management_desktop.dart new file mode 100644 index 0000000..7dc5861 --- /dev/null +++ b/openhaystack-mobile/lib/item_management/item_management_desktop.dart @@ -0,0 +1,25 @@ +import 'package:flutter/material.dart'; +import 'package:openhaystack_mobile/item_management/item_management.dart'; +import 'package:openhaystack_mobile/item_management/new_item_action.dart'; + +class ItemManagementDesktop extends StatefulWidget { + + /// Displays this preferences page with information about the app. + const ItemManagementDesktop({ Key? key }) : super(key: key); + + @override + _ItemManagementDesktopState createState() => _ItemManagementDesktopState(); +} + +class _ItemManagementDesktopState extends State { + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: const Text('Item Management'), + ), + body: const KeyManagement(), + floatingActionButton: const NewKeyAction(), + ); + } +} diff --git a/openhaystack-mobile/lib/main.dart b/openhaystack-mobile/lib/main.dart index 196275b..7f458b8 100644 --- a/openhaystack-mobile/lib/main.dart +++ b/openhaystack-mobile/lib/main.dart @@ -1,6 +1,7 @@ import 'dart:async'; import 'dart:io'; +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'package:receive_sharing_intent/receive_sharing_intent.dart'; @@ -33,7 +34,7 @@ class MyApp extends StatelessWidget { primarySwatch: Colors.blue, ), darkTheme: ThemeData.dark(), - home: const AppLayout(), + home: const AppLayout(), ), ); } @@ -53,16 +54,20 @@ class _AppLayoutState extends State { initState() { super.initState(); - _intentDataStreamSubscription = ReceiveSharingIntent.getMediaStream() - .listen(handleFileSharingIntent, onError: print); - ReceiveSharingIntent.getInitialMedia() - .then(handleFileSharingIntent); + if (!kIsWeb && (Platform.isAndroid || Platform.isIOS)) { + //Only supported on this platforms according to + //https://pub.dev/packages/receive_sharing_intent + _intentDataStreamSubscription = ReceiveSharingIntent.getMediaStream() + .listen(handleFileSharingIntent, onError: print); + ReceiveSharingIntent.getInitialMedia().then(handleFileSharingIntent); + } - var accessoryRegistry = Provider.of(context, listen: false); + var accessoryRegistry = + Provider.of(context, listen: false); accessoryRegistry.loadAccessories(); } - Future handleFileSharingIntent(List files) async { + Future handleFileSharingIntent(List files) async { // Received a sharing intent with a number of files. // Import the accessories for each device in sequence. // If no files are shared do nothing @@ -70,11 +75,13 @@ class _AppLayoutState extends State { if (file.type == SharedMediaType.FILE) { // On iOS the file:// prefix has to be stripped to access the file path String path = Platform.isIOS - ? Uri.decodeComponent(file.path.replaceFirst('file://', '')) - : file.path; - Navigator.push(context, MaterialPageRoute( - builder: (context) => ItemFileImport(filePath: path), - )); + ? Uri.decodeComponent(file.path.replaceFirst('file://', '')) + : file.path; + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => ItemFileImport(filePath: path), + )); } } } @@ -92,7 +99,6 @@ class _AppLayoutState extends State { super.didChangeDependencies(); } - @override Widget build(BuildContext context) { bool isInitialized = context.watch().initialized; diff --git a/openhaystack-mobile/lib/map/map.dart b/openhaystack-mobile/lib/map/map.dart index 676286a..8322882 100644 --- a/openhaystack-mobile/lib/map/map.dart +++ b/openhaystack-mobile/lib/map/map.dart @@ -71,11 +71,14 @@ class _AccessoryMapState extends State { .where((accessory) => accessory.lastLocation != null) .map((accessory) => accessory.lastLocation!) .toList(); - _mapController.fitBounds( - LatLngBounds.fromPoints([...points, ...accessoryPoints]), - options: const FitBoundsOptions( - padding: EdgeInsets.all(25), - )); + points = [... points, ...accessoryPoints]; + if (points.isNotEmpty) { + _mapController.fitBounds( + LatLngBounds.fromPoints(points), + options: const FitBoundsOptions( + padding: EdgeInsets.all(25), + )); + } } @override diff --git a/openhaystack-mobile/pubspec.lock b/openhaystack-mobile/pubspec.lock index 1b7d8a6..3cb4fed 100644 --- a/openhaystack-mobile/pubspec.lock +++ b/openhaystack-mobile/pubspec.lock @@ -7,14 +7,14 @@ packages: name: archive url: "https://pub.dartlang.org" source: hosted - version: "3.2.1" + version: "3.3.0" args: dependency: transitive description: name: args url: "https://pub.dartlang.org" source: hosted - version: "2.3.0" + version: "2.3.1" async: dependency: transitive description: @@ -43,6 +43,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.3.1" + clipboard: + dependency: "direct main" + description: + name: clipboard + url: "https://pub.dartlang.org" + source: hosted + version: "0.1.3" clock: dependency: transitive description: @@ -56,35 +63,42 @@ packages: name: collection url: "https://pub.dartlang.org" source: hosted - version: "1.15.0" + version: "1.16.0" convert: dependency: transitive description: name: convert url: "https://pub.dartlang.org" source: hosted - version: "3.0.1" + version: "3.0.2" crypto: dependency: transitive description: name: crypto url: "https://pub.dartlang.org" source: hosted - version: "3.0.1" + version: "3.0.2" + cryptography: + dependency: "direct main" + description: + name: cryptography + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.5" fake_async: dependency: transitive description: name: fake_async url: "https://pub.dartlang.org" source: hosted - version: "1.2.0" + version: "1.3.0" ffi: dependency: transitive description: name: ffi url: "https://pub.dartlang.org" source: hosted - version: "1.1.2" + version: "1.2.1" file: dependency: transitive description: @@ -98,7 +112,14 @@ packages: name: file_picker url: "https://pub.dartlang.org" source: hosted - version: "4.4.0" + version: "4.6.1" + fixnum: + dependency: transitive + description: + name: fixnum + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.1" flutter: dependency: "direct main" description: flutter @@ -117,7 +138,7 @@ packages: name: flutter_launcher_icons url: "https://pub.dartlang.org" source: hosted - version: "0.9.2" + version: "0.9.3" flutter_lints: dependency: "direct dev" description: @@ -138,7 +159,7 @@ packages: name: flutter_plugin_android_lifecycle url: "https://pub.dartlang.org" source: hosted - version: "2.0.5" + version: "2.0.6" flutter_secure_storage: dependency: "direct main" description: @@ -187,7 +208,7 @@ packages: name: flutter_slidable url: "https://pub.dartlang.org" source: hosted - version: "1.2.0" + version: "1.3.0" flutter_test: dependency: "direct dev" description: flutter @@ -204,7 +225,7 @@ packages: name: geocoding url: "https://pub.dartlang.org" source: hosted - version: "2.0.2" + version: "2.0.4" geocoding_platform_interface: dependency: transitive description: @@ -225,14 +246,14 @@ packages: name: http_parser url: "https://pub.dartlang.org" source: hosted - version: "4.0.0" + version: "4.0.1" image: dependency: transitive description: name: image url: "https://pub.dartlang.org" source: hosted - version: "3.1.3" + version: "3.2.0" intl: dependency: transitive description: @@ -246,7 +267,7 @@ packages: name: js url: "https://pub.dartlang.org" source: hosted - version: "0.6.3" + version: "0.6.4" latlong2: dependency: transitive description: @@ -274,7 +295,7 @@ packages: name: location url: "https://pub.dartlang.org" source: hosted - version: "4.3.0" + version: "4.4.0" location_platform_interface: dependency: transitive description: @@ -309,7 +330,7 @@ packages: name: material_color_utilities url: "https://pub.dartlang.org" source: hosted - version: "0.1.3" + version: "0.1.4" meta: dependency: transitive description: @@ -330,7 +351,7 @@ packages: name: mime url: "https://pub.dartlang.org" source: hosted - version: "1.0.1" + version: "1.0.2" nested: dependency: transitive description: @@ -344,63 +365,63 @@ packages: name: path url: "https://pub.dartlang.org" source: hosted - version: "1.8.0" + version: "1.8.1" path_provider: dependency: "direct main" description: name: path_provider url: "https://pub.dartlang.org" source: hosted - version: "2.0.9" + version: "2.0.11" path_provider_android: dependency: transitive description: name: path_provider_android url: "https://pub.dartlang.org" source: hosted - version: "2.0.11" + version: "2.0.19" path_provider_ios: dependency: transitive description: name: path_provider_ios url: "https://pub.dartlang.org" source: hosted - version: "2.0.7" + version: "2.0.11" path_provider_linux: dependency: transitive description: name: path_provider_linux url: "https://pub.dartlang.org" source: hosted - version: "2.1.5" + version: "2.1.7" path_provider_macos: dependency: transitive description: name: path_provider_macos url: "https://pub.dartlang.org" source: hosted - version: "2.0.5" + version: "2.0.6" path_provider_platform_interface: dependency: transitive description: name: path_provider_platform_interface url: "https://pub.dartlang.org" source: hosted - version: "2.0.3" + version: "2.0.4" path_provider_windows: dependency: transitive description: name: path_provider_windows url: "https://pub.dartlang.org" source: hosted - version: "2.0.5" + version: "2.0.7" petitparser: dependency: transitive description: name: petitparser url: "https://pub.dartlang.org" source: hosted - version: "4.4.0" + version: "5.0.0" platform: dependency: transitive description: @@ -421,7 +442,7 @@ packages: name: pointycastle url: "https://pub.dartlang.org" source: hosted - version: "3.5.1" + version: "3.6.1" positioned_tap_detector_2: dependency: transitive description: @@ -449,14 +470,14 @@ packages: name: provider url: "https://pub.dartlang.org" source: hosted - version: "6.0.2" + version: "6.0.3" quiver: dependency: transitive description: name: quiver url: "https://pub.dartlang.org" source: hosted - version: "3.0.1+1" + version: "3.1.0" receive_sharing_intent: dependency: "direct main" description: @@ -470,77 +491,77 @@ packages: name: share_plus url: "https://pub.dartlang.org" source: hosted - version: "3.1.0" + version: "4.0.9" share_plus_linux: dependency: transitive description: name: share_plus_linux url: "https://pub.dartlang.org" source: hosted - version: "2.0.4" + version: "3.0.0" share_plus_macos: dependency: transitive description: name: share_plus_macos url: "https://pub.dartlang.org" source: hosted - version: "2.0.2" + version: "3.0.1" share_plus_platform_interface: dependency: transitive description: name: share_plus_platform_interface url: "https://pub.dartlang.org" source: hosted - version: "2.0.1" + version: "3.0.3" share_plus_web: dependency: transitive description: name: share_plus_web url: "https://pub.dartlang.org" source: hosted - version: "2.0.4" + version: "3.0.1" share_plus_windows: dependency: transitive description: name: share_plus_windows url: "https://pub.dartlang.org" source: hosted - version: "2.0.3" + version: "3.0.1" shared_preferences: dependency: "direct main" description: name: shared_preferences url: "https://pub.dartlang.org" source: hosted - version: "2.0.13" + version: "2.0.15" shared_preferences_android: dependency: transitive description: name: shared_preferences_android url: "https://pub.dartlang.org" source: hosted - version: "2.0.10" + version: "2.0.12" shared_preferences_ios: dependency: transitive description: name: shared_preferences_ios url: "https://pub.dartlang.org" source: hosted - version: "2.0.9" + version: "2.1.1" shared_preferences_linux: dependency: transitive description: name: shared_preferences_linux url: "https://pub.dartlang.org" source: hosted - version: "2.1.0" + version: "2.1.1" shared_preferences_macos: dependency: transitive description: name: shared_preferences_macos url: "https://pub.dartlang.org" source: hosted - version: "2.0.3" + version: "2.0.4" shared_preferences_platform_interface: dependency: transitive description: @@ -554,14 +575,14 @@ packages: name: shared_preferences_web url: "https://pub.dartlang.org" source: hosted - version: "2.0.3" + version: "2.0.4" shared_preferences_windows: dependency: transitive description: name: shared_preferences_windows url: "https://pub.dartlang.org" source: hosted - version: "2.1.0" + version: "2.1.1" sky_engine: dependency: transitive description: flutter @@ -573,7 +594,7 @@ packages: name: source_span url: "https://pub.dartlang.org" source: hosted - version: "1.8.1" + version: "1.8.2" stack_trace: dependency: transitive description: @@ -608,7 +629,7 @@ packages: name: test_api url: "https://pub.dartlang.org" source: hosted - version: "0.4.8" + version: "0.4.9" transparent_image: dependency: transitive description: @@ -629,7 +650,7 @@ packages: name: typed_data url: "https://pub.dartlang.org" source: hosted - version: "1.3.0" + version: "1.3.1" unicode: dependency: transitive description: @@ -643,70 +664,70 @@ packages: name: url_launcher url: "https://pub.dartlang.org" source: hosted - version: "6.0.20" + version: "6.1.4" url_launcher_android: dependency: transitive description: name: url_launcher_android url: "https://pub.dartlang.org" source: hosted - version: "6.0.14" + version: "6.0.17" url_launcher_ios: dependency: transitive description: name: url_launcher_ios url: "https://pub.dartlang.org" source: hosted - version: "6.0.14" + version: "6.0.17" url_launcher_linux: dependency: transitive description: name: url_launcher_linux url: "https://pub.dartlang.org" source: hosted - version: "3.0.0" + version: "3.0.1" url_launcher_macos: dependency: transitive description: name: url_launcher_macos url: "https://pub.dartlang.org" source: hosted - version: "3.0.0" + version: "3.0.1" url_launcher_platform_interface: dependency: transitive description: name: url_launcher_platform_interface url: "https://pub.dartlang.org" source: hosted - version: "2.0.5" + version: "2.1.0" url_launcher_web: dependency: transitive description: name: url_launcher_web url: "https://pub.dartlang.org" source: hosted - version: "2.0.7" + version: "2.0.12" url_launcher_windows: dependency: transitive description: name: url_launcher_windows url: "https://pub.dartlang.org" source: hosted - version: "3.0.0" + version: "3.0.1" vector_math: dependency: transitive description: name: vector_math url: "https://pub.dartlang.org" source: hosted - version: "2.1.1" + version: "2.1.2" win32: dependency: transitive description: name: win32 url: "https://pub.dartlang.org" source: hosted - version: "2.3.6" + version: "2.5.2" wkt_parser: dependency: transitive description: @@ -727,14 +748,14 @@ packages: name: xml url: "https://pub.dartlang.org" source: hosted - version: "5.3.1" + version: "6.0.1" yaml: dependency: transitive description: name: yaml url: "https://pub.dartlang.org" source: hosted - version: "3.1.0" + version: "3.1.1" sdks: - dart: ">=2.14.0 <3.0.0" - flutter: ">=2.5.0" + dart: ">=2.17.0-0 <3.0.0" + flutter: ">=2.10.0" diff --git a/openhaystack-mobile/pubspec.yaml b/openhaystack-mobile/pubspec.yaml index 7caadf5..1ea9595 100644 --- a/openhaystack-mobile/pubspec.yaml +++ b/openhaystack-mobile/pubspec.yaml @@ -40,7 +40,8 @@ dependencies: # Cryptography # latest version of pointy castle for crypto functions - pointycastle: ^3.4.0 + pointycastle: ^3.6.0 + cryptography: ^2.0.5 # State Management provider: ^6.0.1 @@ -57,10 +58,11 @@ dependencies: # Sharing receive_sharing_intent: ^1.4.5 - share_plus: ^3.0.4 + share_plus: ^4.0.4 url_launcher: ^6.0.17 path_provider: ^2.0.8 maps_launcher: ^2.0.1 + clipboard: ^0.1.3 # The following adds the Cupertino Icons font to your application. # Use with the CupertinoIcons class for iOS style icons. diff --git a/openhaystack-mobile/windows/flutter/generated_plugins.cmake b/openhaystack-mobile/windows/flutter/generated_plugins.cmake index cede039..fa4b456 100644 --- a/openhaystack-mobile/windows/flutter/generated_plugins.cmake +++ b/openhaystack-mobile/windows/flutter/generated_plugins.cmake @@ -8,6 +8,9 @@ list(APPEND FLUTTER_PLUGIN_LIST url_launcher_windows ) +list(APPEND FLUTTER_FFI_PLUGIN_LIST +) + set(PLUGIN_BUNDLED_LIBRARIES) foreach(plugin ${FLUTTER_PLUGIN_LIST}) @@ -16,3 +19,8 @@ foreach(plugin ${FLUTTER_PLUGIN_LIST}) list(APPEND PLUGIN_BUNDLED_LIBRARIES $) list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) endforeach(plugin) + +foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/windows plugins/${ffi_plugin}) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) +endforeach(ffi_plugin) diff --git a/openhaystack-mobile/windows/runner/main.cpp b/openhaystack-mobile/windows/runner/main.cpp index 4926d71..c1681b9 100644 --- a/openhaystack-mobile/windows/runner/main.cpp +++ b/openhaystack-mobile/windows/runner/main.cpp @@ -27,7 +27,7 @@ int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev, FlutterWindow window(project); Win32Window::Point origin(10, 10); Win32Window::Size size(1280, 720); - if (!window.CreateAndShow(L"openhaystack_mobile", origin, size)) { + if (!window.CreateAndShow(L"OpenHaystack", origin, size)) { return EXIT_FAILURE; } window.SetQuitOnClose(true); diff --git a/openhaystack-mobile/windows/runner/resources/app_icon.ico b/openhaystack-mobile/windows/runner/resources/app_icon.ico index c04e20c..f707f5e 100644 Binary files a/openhaystack-mobile/windows/runner/resources/app_icon.ico and b/openhaystack-mobile/windows/runner/resources/app_icon.ico differ