diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e9dc58d --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +.DS_Store +.dart_tool/ + +.packages +.pub/ + +build/ diff --git a/.metadata b/.metadata new file mode 100644 index 0000000..1940d99 --- /dev/null +++ b/.metadata @@ -0,0 +1,10 @@ +# This file tracks properties of this Flutter project. +# Used by Flutter tool to assess capabilities and perform upgrades etc. +# +# This file should be version controlled and should not be manually edited. + +version: + revision: 2d2a1ffec95cc70a3218872a2cd3f8de4933c42f + channel: stable + +project_type: plugin diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..40fafae --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,3 @@ +## 0.0.1 + +* Initial Release \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..9f340bd --- /dev/null +++ b/README.md @@ -0,0 +1,207 @@ +# flutter_p2p + +A Wi-Fi Direct Plugin for Flutter. + +This plugin is in alpha and only supports android at the moment. + +## Getting Started + +### Required permissions +- `android.permission.CHANGE_WIFI_STATE` +- `android.permission.ACCESS_FINE_LOCATION` +- `android.permission.ACCESS_COARSE_LOCATION` +- `android.permission.CHANGE_NETWORK_STATE` +- `android.permission.INTERNET` +- `android.permission.ACCESS_NETWORK_STATE` +- `android.permission.ACCESS_WIFI_STATE` + +### Request permission +In order to scan for devices and connect to devices you need to ask for the location Permission +```dart +Future _checkPermission() async { + if (!await FlutterP2p.isLocationPermissionGranted()) { + await FlutterP2p.requestLocationPermission(); + return false; + } + return true; +} +``` + +### Register / unregister from WiFi events +To receive notifications for connection changes or device changes (peers discovered etc.) you have +to subscribe to the wifiEvents and register the plugin to the native events. +```dart +class _MyAppState extends State with WidgetsBindingObserver { + @override + void initState() { + super.initState(); + _register(); + WidgetsBinding.instance.addObserver(this); + } + + @override + void dispose() { + WidgetsBinding.instance.removeObserver(this); + super.dispose(); + } + + @override + void didChangeAppLifecycleState(AppLifecycleState state) { + // Stop handling events when the app doesn't run to prevent battery draining + + if (state == AppLifecycleState.resumed) { + _register(); + } else if (state == AppLifecycleState.paused) { + _unregister(); + } + } + + List _subscriptions = []; + + void _register() async { + if (!await _checkPermission()) { + return; + } + _subscriptions.add(FlutterP2p.wifiEvents.stateChange.listen((change) { + // Handle wifi state change + })); + + _subscriptions.add(FlutterP2p.wifiEvents.connectionChange.listen((change) { + // Handle changes of the connection + })); + + _subscriptions.add(FlutterP2p.wifiEvents.thisDeviceChange.listen((change) { + // Handle changes of this device + })); + + _subscriptions.add(FlutterP2p.wifiEvents.peersChange.listen((change) { + // Handle discovered peers + })); + + FlutterP2p.register(); // Register to the native events which are send to the streams above + } + + void _unregister() { + _subscriptions.forEach((subscription) => subscription.cancel()); // Cancel subscriptions + FlutterP2p.unregister(); // Unregister from native events + } +} +``` + + +### Discover devices +After you subscribed to the events you only need to call the `FlutterP2p.discoverDevices()` method. +```dart + +List _peers = []; + +void _register() async { + + /// ... + + _subscriptions.add(FlutterP2p.wifiEvents.peersChange.listen((change) { + setState(() { + _peers = change.devices; + }); + })); + + /// ... +} + +void _discover() { + FlutterP2p.discoverDevices(); +} +``` + +### Connect to a device +Call `FlutterP2p.connect(device);` and listen to the `FlutterP2p.wifiEvents.connectionChange` + +```dart + + bool _isConnected = false; + bool _isHost = false; + String _deviceAddress = ""; + + void _register() async { + // ... + + _subscriptions.add(FlutterP2p.wifiEvents.connectionChange.listen((change) { + setState(() { + _isConnected = change.networkInfo.isConnected; + _isHost = change.wifiP2pInfo.isGroupOwner; + _deviceAddress = change.wifiP2pInfo.groupOwnerAddress; + }); + })); + + // ... + } +``` + +### Transferring data between devices +After you are connected to a device you can transfer data async in both directions (client -> host, host -> client). + +On the host: +```dart + // Open a port and create a socket + + P2pSocket _socket; + void _openPortAndAccept(int port) async { + var socket = await FlutterP2p.openHostPort(port); + setState(() { + _socket = socket; + }); + + var buffer = ""; + socket.inputStream.listen((data) { + var msg = String.fromCharCodes(data.data); + buffer += msg; + + if (data.dataAvailable == 0) { + _showSnackBar("Data Received: $buffer"); + buffer = ""; + } + }); + + // Write data to the client using the _socket.write(UInt8List) or `_socket.writeString("Hello")` method + + + print("_openPort done"); + + // accept a connection on the created socket + await FlutterP2p.acceptPort(port); + print("_accept done"); + } +``` + +On the client: +```dart + // Connect to the port and create a socket + + P2pSocket _socket; + _connectToPort(int port) async { + var socket = await FlutterP2p.connectToHost( + _deviceAddress, // see above `Connect to a device` + port, + timeout: 100000, // timeout in milliseconds (default 500) + ); + + setState(() { + _socket = socket; + }); + + var buffer = ""; + socket.inputStream.listen((data) { + var msg = String.fromCharCodes(data.data); + buffer += msg; + + if (data.dataAvailable == 0) { + _showSnackBar("Received from host: $buffer"); + buffer = ""; + } + }); + + // Write data to the host using the _socket.write(UInt8List) or `_socket.writeString("Hello")` method + + print("_connectToPort done"); + } +``` \ No newline at end of file diff --git a/android/.gitignore b/android/.gitignore new file mode 100644 index 0000000..c6cbe56 --- /dev/null +++ b/android/.gitignore @@ -0,0 +1,8 @@ +*.iml +.gradle +/local.properties +/.idea/workspace.xml +/.idea/libraries +.DS_Store +/build +/captures diff --git a/android/build.gradle b/android/build.gradle new file mode 100644 index 0000000..09429df --- /dev/null +++ b/android/build.gradle @@ -0,0 +1,70 @@ +group 'de.mintware.flutter_p2p' +version '1.0-SNAPSHOT' + +buildscript { + ext.kotlin_version = '1.2.71' + ext.protobufVersion = '0.8.8' + + repositories { + google() + jcenter() + } + + dependencies { + classpath 'com.android.tools.build:gradle:3.2.1' + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + classpath "com.google.protobuf:protobuf-gradle-plugin:$protobufVersion" + } +} + +rootProject.allprojects { + repositories { + google() + jcenter() + } +} + +apply plugin: 'com.android.library' +apply plugin: 'kotlin-android' +apply plugin: 'com.google.protobuf' + +android { + compileSdkVersion 28 + + sourceSets { + main.java.srcDirs += 'src/main/kotlin' + main.proto.srcDirs += '../protos' + } + defaultConfig { + minSdkVersion 23 + testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" + } + lintOptions { + disable 'InvalidPackage' + } +} + +protobuf { + // Configure the protoc executable + protoc { + // Download from repositories + artifact = 'com.google.protobuf:protoc:3.6.1' + } + plugins { + javalite { + // The codegen for lite comes as a separate artifact + artifact = 'com.google.protobuf:protoc-gen-javalite:3.0.0' + } + } + generateProtoTasks { + all().each { task -> + task.plugins { + javalite {} + } + } + } +} +dependencies { + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" + implementation 'com.google.protobuf:protobuf-lite:3.0.1' +} diff --git a/android/gradle.properties b/android/gradle.properties new file mode 100644 index 0000000..2bd6f4f --- /dev/null +++ b/android/gradle.properties @@ -0,0 +1,2 @@ +org.gradle.jvmargs=-Xmx1536M + diff --git a/android/gradle/wrapper/gradle-wrapper.properties b/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..019065d --- /dev/null +++ b/android/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.2-all.zip diff --git a/android/settings.gradle b/android/settings.gradle new file mode 100644 index 0000000..a8705d2 --- /dev/null +++ b/android/settings.gradle @@ -0,0 +1 @@ +rootProject.name = 'flutter_p2p' diff --git a/android/src/main/AndroidManifest.xml b/android/src/main/AndroidManifest.xml new file mode 100644 index 0000000..3c830b9 --- /dev/null +++ b/android/src/main/AndroidManifest.xml @@ -0,0 +1,3 @@ + + diff --git a/android/src/main/kotlin/de/mintware/flutter_p2p/Config.kt b/android/src/main/kotlin/de/mintware/flutter_p2p/Config.kt new file mode 100644 index 0000000..6c7852c --- /dev/null +++ b/android/src/main/kotlin/de/mintware/flutter_p2p/Config.kt @@ -0,0 +1,16 @@ +/* + * This file is part of the flutter_p2p package. + * + * Copyright 2019 by Julian Finkler + * + * For the full copyright and license information, please read the LICENSE + * file that was distributed with this source code. + * + */ + +package de.mintware.flutter_p2p + +class Config { + var bufferSize: Int = 1024; + var timeout: Int = 500; +} \ No newline at end of file diff --git a/android/src/main/kotlin/de/mintware/flutter_p2p/FlutterP2pPlugin.kt b/android/src/main/kotlin/de/mintware/flutter_p2p/FlutterP2pPlugin.kt new file mode 100644 index 0000000..2e2b276 --- /dev/null +++ b/android/src/main/kotlin/de/mintware/flutter_p2p/FlutterP2pPlugin.kt @@ -0,0 +1,367 @@ +/* + * This file is part of the flutter_p2p package. + * + * Copyright 2019 by Julian Finkler + * + * For the full copyright and license information, please read the LICENSE + * file that was distributed with this source code. + * + */ + +package de.mintware.flutter_p2p + +import android.Manifest +import io.flutter.plugin.common.MethodCall +import io.flutter.plugin.common.MethodChannel +import io.flutter.plugin.common.MethodChannel.MethodCallHandler +import io.flutter.plugin.common.MethodChannel.Result +import io.flutter.plugin.common.PluginRegistry.Registrar +import android.content.ContentValues.TAG +import android.content.Context +import android.content.IntentFilter +import android.net.wifi.p2p.WifiP2pManager +import android.os.Looper +import android.util.Log +import java.lang.reflect.Method +import java.util.HashMap +import android.content.pm.PackageManager +import android.net.wifi.p2p.WifiP2pConfig +import de.mintware.flutter_p2p.utility.EventChannelPool +import de.mintware.flutter_p2p.wifi_direct.SocketPool +import de.mintware.flutter_p2p.wifi_direct.WiFiDirectBroadcastReceiver + + +class FlutterP2pPlugin(private val registrar: Registrar +) : MethodCallHandler { + + private val intentFilter = IntentFilter() + private var receiver: WiFiDirectBroadcastReceiver? = null + private val eventPool: EventChannelPool = EventChannelPool(registrar.messenger()) + private lateinit var socketPool: SocketPool + + private lateinit var channel: WifiP2pManager.Channel + private lateinit var manager: WifiP2pManager + + companion object { + private const val REQUEST_ENABLE_LOCATION = 600 + private const val CH_STATE_CHANGE = "bc/state-change" + private const val CH_PEERS_CHANGE = "bc/peers-change" + private const val CH_CON_CHANGE = "bc/connection-change" + private const val CH_DEVICE_CHANGE = "bc/this-device-change" + private const val CH_SOCKET_READ = "socket/read" + val config: Config = Config(); + + @JvmStatic + fun registerWith(registrar: Registrar) { + val channel = MethodChannel(registrar.messenger(), "de.mintware.flutter_p2p/flutter_p2p") + + val plugin = FlutterP2pPlugin(registrar) + plugin.setupEventPool() + channel.setMethodCallHandler(plugin) + } + } + + init { + setupIntentFilters() + setupWifiP2pManager() + } + + fun setupEventPool() { + eventPool.register(CH_STATE_CHANGE) + eventPool.register(CH_PEERS_CHANGE) + eventPool.register(CH_CON_CHANGE) + eventPool.register(CH_DEVICE_CHANGE) + eventPool.register(CH_SOCKET_READ) + + socketPool = SocketPool(eventPool.getHandler(CH_SOCKET_READ)) + } + + private fun setupIntentFilters() { + intentFilter.apply { + // Indicates a change in the Wi-Fi P2P status. + addAction(WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION) + // Indicates a change in the list of available peers. + addAction(WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION) + // Indicates the state of Wi-Fi P2P connectivity has changed. + addAction(WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION) + // Indicates this device'base details have changed. + addAction(WifiP2pManager.WIFI_P2P_THIS_DEVICE_CHANGED_ACTION) + } + } + + private fun setupWifiP2pManager() { + manager = registrar.context().getSystemService(Context.WIFI_P2P_SERVICE) as WifiP2pManager + channel = manager.initialize(registrar.context(), Looper.getMainLooper(), null) + } + + + //region Platform channel methods + + + //region Permissions + + @Suppress("unused", "UNUSED_PARAMETER") + private fun requestLocationPermission(call: MethodCall, result: Result) { + val perm = arrayOf(Manifest.permission.ACCESS_FINE_LOCATION) + registrar.activity().requestPermissions(perm, REQUEST_ENABLE_LOCATION) + result.success(true) + } + + @Suppress("unused", "UNUSED_PARAMETER") + private fun isLocationPermissionGranted(call: MethodCall, result: Result) { + val permission = Manifest.permission.ACCESS_FINE_LOCATION + result.success(PackageManager.PERMISSION_GRANTED == registrar.context().checkSelfPermission(permission)) + } + //endregion + + //region WiFi Event Subscription + + /** + * Subscribe to WiFi Events + * + * @param call The Method call + * @param result The Method result + */ + @Suppress("unused", "UNUSED_PARAMETER") + fun register(call: MethodCall, result: Result) { + if (receiver != null) { + result.success(false) + return + } + + receiver = WiFiDirectBroadcastReceiver( + manager, + channel, + eventPool.getHandler(CH_STATE_CHANGE).sink, + eventPool.getHandler(CH_PEERS_CHANGE).sink, + eventPool.getHandler(CH_CON_CHANGE).sink, + eventPool.getHandler(CH_DEVICE_CHANGE).sink + ) + registrar.context().registerReceiver(receiver, intentFilter) + result.success(true) + } + + /** + * Unsubscribe from WiFi Events + * + * @param call The Method call + * @param result The Method result + */ + @Suppress("unused", "UNUSED_PARAMETER") + fun unregister(call: MethodCall, result: Result) { + if (receiver == null) { + result.success(false) + return + } + + registrar.context().unregisterReceiver(receiver) + result.success(true) + } + //endregion + + //region Discover + + /** + * Start discovering WiFi devices + * + * @param call The Method call + * @param result The Method result + */ + @Suppress("unused", "UNUSED_PARAMETER") + fun discover(call: MethodCall, result: Result) { + manager.discoverPeers(channel, object : WifiP2pManager.ActionListener { + override fun onSuccess() { + result.success(true) + } + + override fun onFailure(reasonCode: Int) { + result.error(reasonCode.toString(), null, null) + } + }) + } + + /** + * Stop discovering WiFi devices + * + * @param call The Method call + * @param result The Method result + */ + @Suppress("unused", "UNUSED_PARAMETER") + fun stopDiscover(call: MethodCall, result: Result) { + manager.stopPeerDiscovery(channel, object : WifiP2pManager.ActionListener { + override fun onSuccess() { + result.success(true) + } + + override fun onFailure(reasonCode: Int) { + result.error(reasonCode.toString(), null, null) + } + }) + } + //endregion + + //region Connection + + @Suppress("unused", "UNUSED_PARAMETER") + fun connect(call: MethodCall, result: Result) { + val device = Protos.WifiP2pDevice.parseFrom(call.argument("payload")) + + val config = WifiP2pConfig().apply { + deviceAddress = device.deviceAddress + } + + manager.connect(channel, config, object : WifiP2pManager.ActionListener { + override fun onSuccess() { + result.success(true) + } + + override fun onFailure(reasonCode: Int) { + result.error(reasonCode.toString(), null, null) + } + }) + } + + @Suppress("unused", "UNUSED_PARAMETER") + fun cancelConnect(call: MethodCall, result: Result) { + manager.cancelConnect(channel, object : WifiP2pManager.ActionListener { + override fun onSuccess() { + result.success(true) + } + + override fun onFailure(reasonCode: Int) { + result.error(reasonCode.toString(), null, null) + } + }) + } + + //endregion + + //region Host Advertising + + @Suppress("unused", "UNUSED_PARAMETER") + fun openHostPort(call: MethodCall, result: Result) { + val port = call.argument("port") + if (port == null) { + result.error("Invalid port given", null, null) + return + } + + socketPool.openSocket(port) + result.success(true) + } + + @Suppress("unused", "UNUSED_PARAMETER") + fun closeHostPort(call: MethodCall, result: Result) { + val port = call.argument("port") + if (port == null) { + result.error("Invalid port given", null, null) + return + } + + socketPool.closeSocket(port) + result.success(true) + } + + @Suppress("unused", "UNUSED_PARAMETER") + fun acceptPort(call: MethodCall, result: Result) { + val port = call.argument("port") + if (port == null) { + result.error("Invalid port given", null, null) + return + } + + socketPool.acceptClientConnection(port) + result.success(true) + } + + //endregion + + //region Client Connection + + @Suppress("unused", "UNUSED_PARAMETER") + fun connectToHost(call: MethodCall, result: Result) { + val address = call.argument("address") + val port = call.argument("port") + val timeout = call.argument("timeout") ?: FlutterP2pPlugin.config.timeout + + if (port == null || address == null) { + result.error("Invalid address or port given", null, null) + return + } + + socketPool.connectToHost(address, port, timeout) + result.success(true) + } + + @Suppress("unused", "UNUSED_PARAMETER") + fun disconnectFromHost(call: MethodCall, result: Result) { + val port = call.argument("port") + if (port == null) { + result.error("Invalid port given", null, null) + return + } + this.socketPool.disconnectFromHost(port) + result.success(true) + } + //endregion + + //region Data Transfer + + @Suppress("unused", "UNUSED_PARAMETER") + fun sendDataToHost(call: MethodCall, result: Result) { + val socketMessage = Protos.SocketMessage.parseFrom(call.argument("payload")) + + this.socketPool.sendDataToHost(socketMessage.port, socketMessage.data.toByteArray()) + result.success(true) + } + + @Suppress("unused", "UNUSED_PARAMETER") + fun sendDataToClient(call: MethodCall, result: Result) { + val socketMessage = Protos.SocketMessage.parseFrom(call.argument("payload")) + + this.socketPool.sendDataToClient(socketMessage.port, socketMessage.data.toByteArray()) + result.success(true) + } + + // endregion + + // endregion + + // region MethodCallHandler + private val methodMap = HashMap() + + override fun onMethodCall(call: MethodCall, result: Result) { + if (methodMap.isEmpty()) { + fetchMethods() + } + + val method = methodMap[call.method] + if (null == method) { + result.notImplemented() + return + } + + Log.v(TAG, "Method: " + call.method) + val args = arrayOfNulls(2) + args[0] = call + args[1] = result + + try { + method.invoke(this, *args) + } catch (e: Exception) { + result.error(call.method, e.message, e) + } + + } + + private fun fetchMethods() { + + val c = this::class.java + val m = c.declaredMethods + + for (method in m) { + methodMap[method.name] = method + } + } + //endregion +} diff --git a/android/src/main/kotlin/de/mintware/flutter_p2p/StreamHandler.kt b/android/src/main/kotlin/de/mintware/flutter_p2p/StreamHandler.kt new file mode 100644 index 0000000..d7f58e8 --- /dev/null +++ b/android/src/main/kotlin/de/mintware/flutter_p2p/StreamHandler.kt @@ -0,0 +1,36 @@ +/* + * This file is part of the flutter_p2p package. + * + * Copyright 2019 by Julian Finkler + * + * For the full copyright and license information, please read the LICENSE + * file that was distributed with this source code. + * + */ + +package de.mintware.flutter_p2p + +import io.flutter.plugin.common.EventChannel + +class StreamHandler : EventChannel.StreamHandler { + + companion object { + internal fun createForChannel(channel: EventChannel): StreamHandler { + val handler = StreamHandler() + handler.channel = channel + channel.setStreamHandler(handler) + return handler + } + } + + var channel: EventChannel? = null + var sink: EventChannel.EventSink? = null + + override fun onListen(o: Any?, eventSink: EventChannel.EventSink?) { + sink = eventSink + } + + override fun onCancel(o: Any?) { + sink = null + } +} \ No newline at end of file diff --git a/android/src/main/kotlin/de/mintware/flutter_p2p/utility/EventChannelPool.kt b/android/src/main/kotlin/de/mintware/flutter_p2p/utility/EventChannelPool.kt new file mode 100644 index 0000000..ea8e7a2 --- /dev/null +++ b/android/src/main/kotlin/de/mintware/flutter_p2p/utility/EventChannelPool.kt @@ -0,0 +1,51 @@ +/* + * This file is part of the flutter_p2p package. + * + * Copyright 2019 by Julian Finkler + * + * For the full copyright and license information, please read the LICENSE + * file that was distributed with this source code. + * + */ + +package de.mintware.flutter_p2p.utility + +import de.mintware.flutter_p2p.StreamHandler +import io.flutter.plugin.common.BinaryMessenger +import io.flutter.plugin.common.EventChannel +import java.lang.Error + +class EventChannelPool(private val messenger: BinaryMessenger +) { + private val base = "de.mintware.flutter_p2p" + private val eventChannels = HashMap(); + + fun register(name: String): StreamHandler { + if (isRegistered(name)) { + throw Error("A channel with this name already exists."); + } + + val channel = EventChannel(messenger, "${base}/${name}") + eventChannels[name] = StreamHandler.createForChannel(channel); + return eventChannels[name]!!; + } + + fun unregister(name: String) { + if (!isRegistered(name)) { + throw Error("A channel with this name does not exist."); + } + + eventChannels.remove(name); + } + + fun getHandler(name: String): StreamHandler { + if (!isRegistered(name)) { + throw Error("A channel with this name does not exist."); + } + return eventChannels[name]!!; + } + + fun isRegistered(name: String): Boolean { + return eventChannels.containsKey(name); + } +} \ No newline at end of file diff --git a/android/src/main/kotlin/de/mintware/flutter_p2p/utility/ProtoHelper.kt b/android/src/main/kotlin/de/mintware/flutter_p2p/utility/ProtoHelper.kt new file mode 100644 index 0000000..6ba86ed --- /dev/null +++ b/android/src/main/kotlin/de/mintware/flutter_p2p/utility/ProtoHelper.kt @@ -0,0 +1,81 @@ +/* + * This file is part of the flutter_p2p package. + * + * Copyright 2019 by Julian Finkler + * + * For the full copyright and license information, please read the LICENSE + * file that was distributed with this source code. + * + */ + +package de.mintware.flutter_p2p.utility + +import android.net.NetworkInfo +import android.net.wifi.p2p.WifiP2pDevice +import android.net.wifi.p2p.WifiP2pInfo +import com.google.protobuf.ByteString +import de.mintware.flutter_p2p.Protos + +class ProtoHelper { + companion object { + fun create(isEnabled: Boolean): Protos.StateChange { + return Protos.StateChange.newBuilder() + .setIsEnabled(isEnabled) + .build() + } + + fun create(device: WifiP2pDevice): Protos.WifiP2pDevice { + return Protos.WifiP2pDevice.newBuilder() + .setWpsPbcSupported(device.wpsPbcSupported()) + .setWpsKeypadSupported(device.wpsPbcSupported()) + .setWpsDisplaySupported(device.wpsDisplaySupported()) + .setIsServiceDiscoveryCapable(device.isServiceDiscoveryCapable) + .setIsGroupOwner(device.isGroupOwner) + .setDeviceName(device.deviceName ?: "") + .setDeviceAddress(device.deviceAddress ?: "") + .setPrimaryDeviceType(device.primaryDeviceType ?: "") + .setSecondaryDeviceType(device.secondaryDeviceType ?: "") + .setStatusValue(device.status) + .build(); + } + + fun create(devices: List): Protos.WifiP2pDeviceList { + return Protos.WifiP2pDeviceList.newBuilder() + .addAllDevices(devices.map(Companion::create)) + .build(); + } + + fun create(p2pInfo: WifiP2pInfo, networkInfo: NetworkInfo): Protos.ConnectionChange { + return Protos.ConnectionChange.newBuilder() + .setWifiP2PInfo(create(p2pInfo)) + .setNetworkInfo(create(networkInfo)) + .build(); + } + + fun create(p2pInfo: WifiP2pInfo): Protos.WifiP2pInfo { + return Protos.WifiP2pInfo.newBuilder() + .setGroupFormed(p2pInfo.groupFormed) + .setIsGroupOwner(p2pInfo.isGroupOwner) + .setGroupOwnerAddress(p2pInfo.groupOwnerAddress?.hostAddress ?: "") + .build(); + } + + fun create(networkInfo: NetworkInfo): Protos.NetworkInfo { + return Protos.NetworkInfo.newBuilder() + .setSubType(networkInfo.subtype) + .setIsConnected(networkInfo.isConnected) + .setDetailedStateValue(networkInfo.detailedState.ordinal) + .setExtraInfo(networkInfo.extraInfo ?: "") + .build(); + } + + fun create(port: Int, data: ByteArray, dataAvailable: Int): Protos.SocketMessage { + return Protos.SocketMessage.newBuilder() + .setData(ByteString.copyFrom(data)) + .setPort(port) + .setDataAvailable(dataAvailable) + .build(); + } + + } +} \ No newline at end of file diff --git a/android/src/main/kotlin/de/mintware/flutter_p2p/wifi_direct/SocketPool.kt b/android/src/main/kotlin/de/mintware/flutter_p2p/wifi_direct/SocketPool.kt new file mode 100644 index 0000000..7ac4c60 --- /dev/null +++ b/android/src/main/kotlin/de/mintware/flutter_p2p/wifi_direct/SocketPool.kt @@ -0,0 +1,100 @@ +/* + * This file is part of the flutter_p2p package. + * + * Copyright 2019 by Julian Finkler + * + * For the full copyright and license information, please read the LICENSE + * file that was distributed with this source code. + * + */ + +package de.mintware.flutter_p2p.wifi_direct + +import de.mintware.flutter_p2p.StreamHandler +import de.mintware.flutter_p2p.wifi_direct.transfer.Client +import de.mintware.flutter_p2p.wifi_direct.transfer.Host +import java.net.ServerSocket + +class SocketPool(private val inputStreamHandler: StreamHandler) { + + private val clientPool = mutableListOf() + private val hosts = mutableListOf() + + fun openSocket(port: Int): Host { + if (getHostByPort(port) != null) { + throw Exception("A socket with this port already exist") + } + + val socket = ServerSocket(port) + + val host = Host(socket, inputStreamHandler) + hosts.add(host) + + return host + } + + fun acceptClientConnection(port: Int) { + val host: Host = getHostByPort(port) + ?: throw Exception("A socket with this port is not registered.") + host.execute() + } + + fun closeSocket(port: Int) { + val socket: Host = getHostByPort(port) + ?: throw Exception("A socket with this port is not registered.") + socket.serverSocket.close() + } + + fun connectToHost(address: String, port: Int, timeout: Int): Client { + val client = Client(address, port, inputStreamHandler, timeout) + clientPool.add(client) + client.execute() + + return client + } + + fun sendDataToHost(port: Int, data: ByteArray) { + val client: Client = getClientByPort(port) + ?: throw Exception("A socket with this port is not connected.") + + try { + client.writeToOutput(data) + } catch (e: Exception) { + e.printStackTrace() + } + } + + fun sendDataToClient(port: Int, data: ByteArray) { + val host: Host = getHostByPort(port) + ?: throw Exception("A socket with this port is not connected.") + + host.writeToOutput(data) + } + + fun disconnectFromHost(port: Int) { + val client: Client = getClientByPort(port) + ?: throw Exception("A socket with this port is not connected.") + + client.socket.takeIf { it.isConnected }?.apply { + close() + } + } + + fun disconnectFromClient(port: Int) { + val host: Host = getHostByPort(port) + ?: throw Exception("A socket with this port is not connected.") + + host.serverSocket.takeIf { !it.isClosed }?.apply { + close() + } + } + + private fun getHostByPort(port: Int): Host? { + return hosts.firstOrNull { s -> s.serverSocket.localPort == port } + } + + + private fun getClientByPort(port: Int): Client? { + return clientPool.firstOrNull { s -> s.port == port } + } +} \ No newline at end of file diff --git a/android/src/main/kotlin/de/mintware/flutter_p2p/wifi_direct/WiFiDirectBroadcastReceiver.kt b/android/src/main/kotlin/de/mintware/flutter_p2p/wifi_direct/WiFiDirectBroadcastReceiver.kt new file mode 100644 index 0000000..2d1b24f --- /dev/null +++ b/android/src/main/kotlin/de/mintware/flutter_p2p/wifi_direct/WiFiDirectBroadcastReceiver.kt @@ -0,0 +1,95 @@ +/* + * This file is part of the flutter_p2p package. + * + * Copyright 2019 by Julian Finkler + * + * For the full copyright and license information, please read the LICENSE + * file that was distributed with this source code. + * + */ + +package de.mintware.flutter_p2p.wifi_direct + +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.net.NetworkInfo +import android.net.wifi.p2p.WifiP2pDevice +import android.net.wifi.p2p.WifiP2pInfo +import android.net.wifi.p2p.WifiP2pManager +import de.mintware.flutter_p2p.Protos +import de.mintware.flutter_p2p.utility.ProtoHelper +import io.flutter.plugin.common.EventChannel + +class WiFiDirectBroadcastReceiver(private val manager: WifiP2pManager, + private val channel: WifiP2pManager.Channel, + private val stateChangedSink: EventChannel.EventSink?, + peersChangedSink: EventChannel.EventSink?, + private val connectionChangedSink: EventChannel.EventSink?, + private val thisDeviceChangedSink: EventChannel.EventSink? +) : BroadcastReceiver() { + + private val peerListListener = WiFiDirectPeerListListener(peersChangedSink); + + override fun onReceive(context: Context?, intent: Intent?) { + if (intent == null) { + return; + } + + when (intent.action) { + WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION -> onStateChanged(intent) + WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION -> onPeersChanged() + WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION -> onConnectionChanged(intent) + WifiP2pManager.WIFI_P2P_THIS_DEVICE_CHANGED_ACTION -> onThisDeviceChanged(intent) + } + } + + private fun onConnectionChanged(intent: Intent) { + val p2pInfo = intent.getParcelableExtra(WifiP2pManager.EXTRA_WIFI_P2P_INFO) as WifiP2pInfo + val networkInfo = intent.getParcelableExtra(WifiP2pManager.EXTRA_NETWORK_INFO) as NetworkInfo + + manager?.let { manager -> + + if (networkInfo.isConnected) { + + manager.requestConnectionInfo(channel, WifiP2pManager.ConnectionInfoListener { info -> + // InetAddress from WifiP2pInfo struct. + val groupOwnerAddress: String = info.groupOwnerAddress.hostAddress + + // After the group negotiation, we can determine the group owner + // (server). + if (info.groupFormed && info.isGroupOwner) { + // Do whatever tasks are specific to the group owner. + // One common case is creating a group owner thread and accepting + // incoming connections. + } else if (info.groupFormed) { + // The other device acts as the peer (client). In this case, + // you'll want to create a peer thread that connects + // to the group owner. + } + + }) + } + } + + connectionChangedSink?.success(ProtoHelper.create(p2pInfo, networkInfo).toByteArray()) + } + + private fun onStateChanged(intent: Intent) { + val state = intent.getIntExtra(WifiP2pManager.EXTRA_WIFI_STATE, -1) + val isConnected = state == WifiP2pManager.WIFI_P2P_STATE_ENABLED + val stateChange: Protos.StateChange = ProtoHelper.create(isConnected); + + stateChangedSink?.success(stateChange.toByteArray()); + } + + private fun onPeersChanged() { + manager.requestPeers(channel, peerListListener) + } + + private fun onThisDeviceChanged(intent: Intent) { + val device = intent.getParcelableExtra(WifiP2pManager.EXTRA_WIFI_P2P_DEVICE) as WifiP2pDevice + val dev: Protos.WifiP2pDevice = ProtoHelper.create(device); + thisDeviceChangedSink?.success(dev.toByteArray()) + } +} \ No newline at end of file diff --git a/android/src/main/kotlin/de/mintware/flutter_p2p/wifi_direct/WiFiDirectPeerListListener.kt b/android/src/main/kotlin/de/mintware/flutter_p2p/wifi_direct/WiFiDirectPeerListListener.kt new file mode 100644 index 0000000..e627d5e --- /dev/null +++ b/android/src/main/kotlin/de/mintware/flutter_p2p/wifi_direct/WiFiDirectPeerListListener.kt @@ -0,0 +1,46 @@ +/* + * This file is part of the flutter_p2p package. + * + * Copyright 2019 by Julian Finkler + * + * For the full copyright and license information, please read the LICENSE + * file that was distributed with this source code. + * + */ + +package de.mintware.flutter_p2p.wifi_direct + +import android.net.wifi.p2p.WifiP2pDevice +import android.net.wifi.p2p.WifiP2pDeviceList +import android.net.wifi.p2p.WifiP2pManager +import android.util.Log +import de.mintware.flutter_p2p.utility.ProtoHelper +import io.flutter.plugin.common.EventChannel + +class WiFiDirectPeerListListener(private val peersChangedSink: EventChannel.EventSink? +) : WifiP2pManager.PeerListListener { + companion object { + const val TAG = "Flutter P2P" + } + + private val peers = mutableListOf() + + override fun onPeersAvailable(peerList: WifiP2pDeviceList) { + val refreshedPeers = peerList.deviceList + if ( refreshedPeers != peers) { + peers.clear() + + if (refreshedPeers != null) { + peers.addAll(refreshedPeers) + } + + peersChangedSink?.success(ProtoHelper.create(peers).toByteArray()); + } + + if (peers.isEmpty()) { + Log.d(TAG, "No devices found") + return + } + } + +} \ No newline at end of file diff --git a/android/src/main/kotlin/de/mintware/flutter_p2p/wifi_direct/WriteDataToSocketTask.kt b/android/src/main/kotlin/de/mintware/flutter_p2p/wifi_direct/WriteDataToSocketTask.kt new file mode 100644 index 0000000..7b77a56 --- /dev/null +++ b/android/src/main/kotlin/de/mintware/flutter_p2p/wifi_direct/WriteDataToSocketTask.kt @@ -0,0 +1,29 @@ +/* + * This file is part of the flutter_p2p package. + * + * Copyright 2019 by Julian Finkler + * + * For the full copyright and license information, please read the LICENSE + * file that was distributed with this source code. + * + */ + +package de.mintware.flutter_p2p.wifi_direct + +import android.os.AsyncTask +import java.net.Socket + +class WriteDataToSocketTask(private val socket: Socket, + private val data: ByteArray +) : AsyncTask() { + + override fun doInBackground(vararg params: Void?): Boolean? { + if (!socket.isClosed) { +// return false; + } + + socket.getOutputStream().write(data); + socket.getOutputStream().flush(); + return true; + } +} \ No newline at end of file diff --git a/android/src/main/kotlin/de/mintware/flutter_p2p/wifi_direct/transfer/Client.kt b/android/src/main/kotlin/de/mintware/flutter_p2p/wifi_direct/transfer/Client.kt new file mode 100644 index 0000000..0a11ec8 --- /dev/null +++ b/android/src/main/kotlin/de/mintware/flutter_p2p/wifi_direct/transfer/Client.kt @@ -0,0 +1,43 @@ +/* + * This file is part of the flutter_p2p package. + * + * Copyright 2019 by Julian Finkler + * + * For the full copyright and license information, please read the LICENSE + * file that was distributed with this source code. + * + */ + +package de.mintware.flutter_p2p.wifi_direct.transfer + +import de.mintware.flutter_p2p.StreamHandler +import java.net.InetSocketAddress +import java.net.Socket + +class Client(private val address: String, + val port: Int, + inputStreamHandler: StreamHandler, + private val timeout: Int +) : SocketTask(inputStreamHandler) { + + private lateinit var socketHandler: SocketHandler + + init { + socket = Socket() + socket.bind(null) + } + + override fun doInBackground(vararg params: Void?): Boolean { + try { + val socketAddress = InetSocketAddress(address, port) + socket.connect(socketAddress, timeout) + socketHandler = SocketHandler(socket, false) + socketHandler.handleInput { data -> publishProgress(data) } + } catch (e: Exception) { + e.printStackTrace() + return false + } + return true + } + +} \ No newline at end of file diff --git a/android/src/main/kotlin/de/mintware/flutter_p2p/wifi_direct/transfer/Host.kt b/android/src/main/kotlin/de/mintware/flutter_p2p/wifi_direct/transfer/Host.kt new file mode 100644 index 0000000..f180e83 --- /dev/null +++ b/android/src/main/kotlin/de/mintware/flutter_p2p/wifi_direct/transfer/Host.kt @@ -0,0 +1,36 @@ +/* + * This file is part of the flutter_p2p package. + * + * Copyright 2019 by Julian Finkler + * + * For the full copyright and license information, please read the LICENSE + * file that was distributed with this source code. + * + */ + +package de.mintware.flutter_p2p.wifi_direct.transfer + +import de.mintware.flutter_p2p.StreamHandler +import java.net.ServerSocket + +class Host(val serverSocket: ServerSocket, + inputStreamHandler: StreamHandler +) : SocketTask(inputStreamHandler) { + + private lateinit var handler: SocketHandler + + override fun doInBackground(vararg params: Void?): Boolean { + + try { + socket = serverSocket.accept() + handler = SocketHandler(socket, true); + handler.handleInput { data -> publishProgress(data) } + } catch (e: Exception) { + e.printStackTrace() + return false + } + + return true + } + +} \ No newline at end of file diff --git a/android/src/main/kotlin/de/mintware/flutter_p2p/wifi_direct/transfer/SocketHandler.kt b/android/src/main/kotlin/de/mintware/flutter_p2p/wifi_direct/transfer/SocketHandler.kt new file mode 100644 index 0000000..98c59e3 --- /dev/null +++ b/android/src/main/kotlin/de/mintware/flutter_p2p/wifi_direct/transfer/SocketHandler.kt @@ -0,0 +1,34 @@ +/* + * This file is part of the flutter_p2p package. + * + * Copyright 2019 by Julian Finkler + * + * For the full copyright and license information, please read the LICENSE + * file that was distributed with this source code. + * + */ + +package de.mintware.flutter_p2p.wifi_direct.transfer + +import de.mintware.flutter_p2p.FlutterP2pPlugin +import de.mintware.flutter_p2p.utility.ProtoHelper +import java.io.InputStream +import java.net.Socket + +class SocketHandler(private val socket: Socket, + private val isHost: Boolean +) { + private val inputStream: InputStream = socket.getInputStream(); + + fun handleInput(cb: (data: ByteArray) -> Unit) { + val buf = ByteArray(FlutterP2pPlugin.config.bufferSize) + + var readCount = 0; + + val port = if (isHost) socket.localPort else (socket.port) + while ({ readCount = inputStream.read(buf);readCount }() != -1) { + val result = ProtoHelper.create(port, buf.take(readCount).toByteArray(), inputStream.available()) + cb(result.toByteArray()) + } + } +} \ No newline at end of file diff --git a/android/src/main/kotlin/de/mintware/flutter_p2p/wifi_direct/transfer/SocketTask.kt b/android/src/main/kotlin/de/mintware/flutter_p2p/wifi_direct/transfer/SocketTask.kt new file mode 100644 index 0000000..90f8bdc --- /dev/null +++ b/android/src/main/kotlin/de/mintware/flutter_p2p/wifi_direct/transfer/SocketTask.kt @@ -0,0 +1,34 @@ +/* + * This file is part of the flutter_p2p package. + * + * Copyright 2019 by Julian Finkler + * + * For the full copyright and license information, please read the LICENSE + * file that was distributed with this source code. + * + */ + +package de.mintware.flutter_p2p.wifi_direct.transfer + +import android.os.AsyncTask +import de.mintware.flutter_p2p.StreamHandler +import java.net.Socket + +abstract class SocketTask(private val inputStreamHandler: StreamHandler) : AsyncTask() { + + lateinit var socket: Socket + + override fun onProgressUpdate(vararg values: ByteArray?) { + inputStreamHandler.sink?.success(values[0]) + } + + fun writeToOutput(bytes: ByteArray): Boolean { + try { + val task = WriteDataToStreamTask(socket.getOutputStream(), bytes) + task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + } catch (e: Exception) { + e.printStackTrace() + } + return true; + } +} \ No newline at end of file diff --git a/android/src/main/kotlin/de/mintware/flutter_p2p/wifi_direct/transfer/WriteDataToStreamTask.kt b/android/src/main/kotlin/de/mintware/flutter_p2p/wifi_direct/transfer/WriteDataToStreamTask.kt new file mode 100644 index 0000000..b5b4d5f --- /dev/null +++ b/android/src/main/kotlin/de/mintware/flutter_p2p/wifi_direct/transfer/WriteDataToStreamTask.kt @@ -0,0 +1,26 @@ +/* + * This file is part of the flutter_p2p package. + * + * Copyright 2019 by Julian Finkler + * + * For the full copyright and license information, please read the LICENSE + * file that was distributed with this source code. + * + */ + +package de.mintware.flutter_p2p.wifi_direct.transfer + +import android.os.AsyncTask +import java.io.OutputStream + +class WriteDataToStreamTask(private val stream: OutputStream, + private val bytes: ByteArray +) : AsyncTask() { + + override fun doInBackground(vararg params: Void?): Boolean { + stream.write(bytes) + stream.flush() + return true; + } + +} \ No newline at end of file diff --git a/example/.gitignore b/example/.gitignore new file mode 100644 index 0000000..2ddde2a --- /dev/null +++ b/example/.gitignore @@ -0,0 +1,73 @@ +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.buildlog/ +.history +.svn/ + +# IntelliJ related +*.iml +*.ipr +*.iws +.idea/ + +# The .vscode folder contains launch configuration and tasks you configure in +# VS Code which you may wish to be included in version control, so this line +# is commented out by default. +#.vscode/ + +# Flutter/Dart/Pub related +**/doc/api/ +.dart_tool/ +.flutter-plugins +.packages +.pub-cache/ +.pub/ +/build/ + +# Android related +**/android/**/gradle-wrapper.jar +**/android/.gradle +**/android/captures/ +**/android/gradlew +**/android/gradlew.bat +**/android/local.properties +**/android/**/GeneratedPluginRegistrant.java + +# iOS/XCode related +**/ios/**/*.mode1v3 +**/ios/**/*.mode2v3 +**/ios/**/*.moved-aside +**/ios/**/*.pbxuser +**/ios/**/*.perspectivev3 +**/ios/**/*sync/ +**/ios/**/.sconsign.dblite +**/ios/**/.tags* +**/ios/**/.vagrant/ +**/ios/**/DerivedData/ +**/ios/**/Icon? +**/ios/**/Pods/ +**/ios/**/.symlinks/ +**/ios/**/profile +**/ios/**/xcuserdata +**/ios/.generated/ +**/ios/Flutter/App.framework +**/ios/Flutter/Flutter.framework +**/ios/Flutter/Generated.xcconfig +**/ios/Flutter/app.flx +**/ios/Flutter/app.zip +**/ios/Flutter/flutter_assets/ +**/ios/Flutter/flutter_export_environment.sh +**/ios/ServiceDefinitions.json +**/ios/Runner/GeneratedPluginRegistrant.* + +# Exceptions to above rules. +!**/ios/**/default.mode1v3 +!**/ios/**/default.mode2v3 +!**/ios/**/default.pbxuser +!**/ios/**/default.perspectivev3 +!/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages diff --git a/example/.metadata b/example/.metadata new file mode 100644 index 0000000..aeb01ee --- /dev/null +++ b/example/.metadata @@ -0,0 +1,10 @@ +# This file tracks properties of this Flutter project. +# Used by Flutter tool to assess capabilities and perform upgrades etc. +# +# This file should be version controlled and should not be manually edited. + +version: + revision: 2d2a1ffec95cc70a3218872a2cd3f8de4933c42f + channel: stable + +project_type: app diff --git a/example/README.md b/example/README.md new file mode 100644 index 0000000..ced6366 --- /dev/null +++ b/example/README.md @@ -0,0 +1,16 @@ +# flutter_p2p_example + +Demonstrates how to use the flutter_p2p plugin. + +## Getting Started + +This project is a starting point for a Flutter application. + +A few resources to get you started if this is your first Flutter project: + +- [Lab: Write your first Flutter app](https://flutter.dev/docs/get-started/codelab) +- [Cookbook: Useful Flutter samples](https://flutter.dev/docs/cookbook) + +For help getting started with Flutter, view our +[online documentation](https://flutter.dev/docs), which offers tutorials, +samples, guidance on mobile development, and a full API reference. diff --git a/example/android/app/build.gradle b/example/android/app/build.gradle new file mode 100644 index 0000000..5bfdfa7 --- /dev/null +++ b/example/android/app/build.gradle @@ -0,0 +1,67 @@ +def localProperties = new Properties() +def localPropertiesFile = rootProject.file('local.properties') +if (localPropertiesFile.exists()) { + localPropertiesFile.withReader('UTF-8') { reader -> + localProperties.load(reader) + } +} + +def flutterRoot = localProperties.getProperty('flutter.sdk') +if (flutterRoot == null) { + throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") +} + +def flutterVersionCode = localProperties.getProperty('flutter.versionCode') +if (flutterVersionCode == null) { + flutterVersionCode = '1' +} + +def flutterVersionName = localProperties.getProperty('flutter.versionName') +if (flutterVersionName == null) { + flutterVersionName = '1.0' +} + +apply plugin: 'com.android.application' +apply plugin: 'kotlin-android' +apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" + +android { + compileSdkVersion 28 + + sourceSets { + main.java.srcDirs += 'src/main/kotlin' + } + + lintOptions { + disable 'InvalidPackage' + } + + defaultConfig { + // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). + applicationId "de.mintware.flutter_p2p_example" + minSdkVersion 23 + targetSdkVersion 28 + versionCode flutterVersionCode.toInteger() + versionName flutterVersionName + testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" + } + + buildTypes { + release { + // TODO: Add your own signing config for the release build. + // Signing with the debug keys for now, so `flutter run --release` works. + signingConfig signingConfigs.debug + } + } +} + +flutter { + source '../..' +} + +dependencies { + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" + testImplementation 'junit:junit:4.12' + androidTestImplementation 'com.android.support.test:runner:1.0.2' + androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' +} diff --git a/example/android/app/src/debug/AndroidManifest.xml b/example/android/app/src/debug/AndroidManifest.xml new file mode 100644 index 0000000..7b0e1a3 --- /dev/null +++ b/example/android/app/src/debug/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/example/android/app/src/main/AndroidManifest.xml b/example/android/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..c43ffd4 --- /dev/null +++ b/example/android/app/src/main/AndroidManifest.xml @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/example/android/app/src/main/kotlin/de/mintware/flutter_p2p_example/MainActivity.kt b/example/android/app/src/main/kotlin/de/mintware/flutter_p2p_example/MainActivity.kt new file mode 100644 index 0000000..5491b66 --- /dev/null +++ b/example/android/app/src/main/kotlin/de/mintware/flutter_p2p_example/MainActivity.kt @@ -0,0 +1,13 @@ +package de.mintware.flutter_p2p_example + +import android.os.Bundle + +import io.flutter.app.FlutterActivity +import io.flutter.plugins.GeneratedPluginRegistrant + +class MainActivity: FlutterActivity() { + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + GeneratedPluginRegistrant.registerWith(this) + } +} diff --git a/example/android/app/src/main/res/drawable/launch_background.xml b/example/android/app/src/main/res/drawable/launch_background.xml new file mode 100644 index 0000000..304732f --- /dev/null +++ b/example/android/app/src/main/res/drawable/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000..db77bb4 Binary files /dev/null and b/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000..17987b7 Binary files /dev/null and b/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000..09d4391 Binary files /dev/null and b/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000..d5f1c8d Binary files /dev/null and b/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000..4d6372e Binary files /dev/null and b/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/example/android/app/src/main/res/values/styles.xml b/example/android/app/src/main/res/values/styles.xml new file mode 100644 index 0000000..00fa441 --- /dev/null +++ b/example/android/app/src/main/res/values/styles.xml @@ -0,0 +1,8 @@ + + + + diff --git a/example/android/app/src/profile/AndroidManifest.xml b/example/android/app/src/profile/AndroidManifest.xml new file mode 100644 index 0000000..7b0e1a3 --- /dev/null +++ b/example/android/app/src/profile/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/example/android/build.gradle b/example/android/build.gradle new file mode 100644 index 0000000..9388a77 --- /dev/null +++ b/example/android/build.gradle @@ -0,0 +1,32 @@ +buildscript { + ext.kotlin_version = '1.2.71' + repositories { + google() + jcenter() + } + + dependencies { + classpath 'com.android.tools.build:gradle:3.2.1' + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + } +} + +allprojects { + repositories { + google() + jcenter() + mavenCentral() + } +} + +rootProject.buildDir = '../build' +subprojects { + project.buildDir = "${rootProject.buildDir}/${project.name}" +} +subprojects { + project.evaluationDependsOn(':app') +} + +task clean(type: Delete) { + delete rootProject.buildDir +} diff --git a/example/android/gradle.properties b/example/android/gradle.properties new file mode 100644 index 0000000..2bd6f4f --- /dev/null +++ b/example/android/gradle.properties @@ -0,0 +1,2 @@ +org.gradle.jvmargs=-Xmx1536M + diff --git a/example/android/gradle/wrapper/gradle-wrapper.properties b/example/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..2819f02 --- /dev/null +++ b/example/android/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Fri Jun 23 08:50:38 CEST 2017 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.2-all.zip diff --git a/example/android/settings.gradle b/example/android/settings.gradle new file mode 100644 index 0000000..5a2f14f --- /dev/null +++ b/example/android/settings.gradle @@ -0,0 +1,15 @@ +include ':app' + +def flutterProjectRoot = rootProject.projectDir.parentFile.toPath() + +def plugins = new Properties() +def pluginsFile = new File(flutterProjectRoot.toFile(), '.flutter-plugins') +if (pluginsFile.exists()) { + pluginsFile.withReader('UTF-8') { reader -> plugins.load(reader) } +} + +plugins.each { name, path -> + def pluginDirectory = flutterProjectRoot.resolve(path).resolve('android').toFile() + include ":$name" + project(":$name").projectDir = pluginDirectory +} diff --git a/example/ios/Flutter/AppFrameworkInfo.plist b/example/ios/Flutter/AppFrameworkInfo.plist new file mode 100644 index 0000000..6b4c0f7 --- /dev/null +++ b/example/ios/Flutter/AppFrameworkInfo.plist @@ -0,0 +1,26 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + App + CFBundleIdentifier + io.flutter.flutter.app + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + App + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1.0 + MinimumOSVersion + 8.0 + + diff --git a/example/ios/Flutter/Debug.xcconfig b/example/ios/Flutter/Debug.xcconfig new file mode 100644 index 0000000..e8efba1 --- /dev/null +++ b/example/ios/Flutter/Debug.xcconfig @@ -0,0 +1,2 @@ +#include "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" +#include "Generated.xcconfig" diff --git a/example/ios/Flutter/Release.xcconfig b/example/ios/Flutter/Release.xcconfig new file mode 100644 index 0000000..399e934 --- /dev/null +++ b/example/ios/Flutter/Release.xcconfig @@ -0,0 +1,2 @@ +#include "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" +#include "Generated.xcconfig" diff --git a/example/ios/Podfile b/example/ios/Podfile new file mode 100644 index 0000000..e5e1440 --- /dev/null +++ b/example/ios/Podfile @@ -0,0 +1,77 @@ +# Using a CDN with CocoaPods 1.7.2 or later can save a lot of time on pod installation, but it's experimental rather than the default. +# source 'https://cdn.cocoapods.org/' + +# Uncomment this line to define a global platform for your project +# platform :ios, '9.0' + +# CocoaPods analytics sends network stats synchronously affecting flutter build latency. +ENV['COCOAPODS_DISABLE_STATS'] = 'true' + +project 'Runner', { + 'Debug' => :debug, + 'Profile' => :release, + 'Release' => :release, +} + +def parse_KV_file(file, separator='=') + file_abs_path = File.expand_path(file) + if !File.exists? file_abs_path + return []; + end + pods_ary = [] + skip_line_start_symbols = ["#", "/"] + File.foreach(file_abs_path) { |line| + next if skip_line_start_symbols.any? { |symbol| line =~ /^\s*#{symbol}/ } + plugin = line.split(pattern=separator) + if plugin.length == 2 + podname = plugin[0].strip() + path = plugin[1].strip() + podpath = File.expand_path("#{path}", file_abs_path) + pods_ary.push({:name => podname, :path => podpath}); + else + puts "Invalid plugin specification: #{line}" + end + } + return pods_ary +end + +target 'Runner' do + use_frameworks! + + # Prepare symlinks folder. We use symlinks to avoid having Podfile.lock + # referring to absolute paths on developers' machines. + system('rm -rf .symlinks') + system('mkdir -p .symlinks/plugins') + + # Flutter Pods + generated_xcode_build_settings = parse_KV_file('./Flutter/Generated.xcconfig') + if generated_xcode_build_settings.empty? + puts "Generated.xcconfig must exist. If you're running pod install manually, make sure flutter pub get is executed first." + end + generated_xcode_build_settings.map { |p| + if p[:name] == 'FLUTTER_FRAMEWORK_DIR' + symlink = File.join('.symlinks', 'flutter') + File.symlink(File.dirname(p[:path]), symlink) + pod 'Flutter', :path => File.join(symlink, File.basename(p[:path])) + end + } + + # Plugin Pods + plugin_pods = parse_KV_file('../.flutter-plugins') + plugin_pods.map { |p| + symlink = File.join('.symlinks', 'plugins', p[:name]) + File.symlink(p[:path], symlink) + pod p[:name], :path => File.join(symlink, 'ios') + } +end + +# Prevent Cocoapods from embedding a second Flutter framework and causing an error with the new Xcode build system. +install! 'cocoapods', :disable_input_output_paths => true + +post_install do |installer| + installer.pods_project.targets.each do |target| + target.build_configurations.each do |config| + config.build_settings['ENABLE_BITCODE'] = 'NO' + end + end +end diff --git a/example/ios/Runner.xcodeproj/project.pbxproj b/example/ios/Runner.xcodeproj/project.pbxproj new file mode 100644 index 0000000..2f282d2 --- /dev/null +++ b/example/ios/Runner.xcodeproj/project.pbxproj @@ -0,0 +1,519 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 46; + objects = { + +/* Begin PBXBuildFile section */ + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; + 3B80C3941E831B6300D905FE /* App.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3B80C3931E831B6300D905FE /* App.framework */; }; + 3B80C3951E831B6300D905FE /* App.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 3B80C3931E831B6300D905FE /* App.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; + 9705A1C61CF904A100538489 /* Flutter.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9740EEBA1CF902C7004384FC /* Flutter.framework */; }; + 9705A1C71CF904A300538489 /* Flutter.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 9740EEBA1CF902C7004384FC /* Flutter.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + 9740EEB41CF90195004384FC /* Debug.xcconfig in Resources */ = {isa = PBXBuildFile; fileRef = 9740EEB21CF90195004384FC /* Debug.xcconfig */; }; + 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; + 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; + 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; +/* End PBXBuildFile section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 9705A1C41CF9048500538489 /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + 3B80C3951E831B6300D905FE /* App.framework in Embed Frameworks */, + 9705A1C71CF904A300538489 /* Flutter.framework in Embed Frameworks */, + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; + 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; + 3B80C3931E831B6300D905FE /* App.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = App.framework; path = Flutter/App.framework; sourceTree = ""; }; + 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; + 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; + 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; + 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; + 9740EEBA1CF902C7004384FC /* Flutter.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Flutter.framework; path = Flutter/Flutter.framework; sourceTree = ""; }; + 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; + 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; + 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 97C146EB1CF9000F007C117D /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 9705A1C61CF904A100538489 /* Flutter.framework in Frameworks */, + 3B80C3941E831B6300D905FE /* App.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 9740EEB11CF90186004384FC /* Flutter */ = { + isa = PBXGroup; + children = ( + 3B80C3931E831B6300D905FE /* App.framework */, + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, + 9740EEBA1CF902C7004384FC /* Flutter.framework */, + 9740EEB21CF90195004384FC /* Debug.xcconfig */, + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, + 9740EEB31CF90195004384FC /* Generated.xcconfig */, + ); + name = Flutter; + sourceTree = ""; + }; + 97C146E51CF9000F007C117D = { + isa = PBXGroup; + children = ( + 9740EEB11CF90186004384FC /* Flutter */, + 97C146F01CF9000F007C117D /* Runner */, + 97C146EF1CF9000F007C117D /* Products */, + ); + sourceTree = ""; + }; + 97C146EF1CF9000F007C117D /* Products */ = { + isa = PBXGroup; + children = ( + 97C146EE1CF9000F007C117D /* Runner.app */, + ); + name = Products; + sourceTree = ""; + }; + 97C146F01CF9000F007C117D /* Runner */ = { + isa = PBXGroup; + children = ( + 97C146FA1CF9000F007C117D /* Main.storyboard */, + 97C146FD1CF9000F007C117D /* Assets.xcassets */, + 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, + 97C147021CF9000F007C117D /* Info.plist */, + 97C146F11CF9000F007C117D /* Supporting Files */, + 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, + 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, + 74858FAE1ED2DC5600515810 /* AppDelegate.swift */, + 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */, + ); + path = Runner; + sourceTree = ""; + }; + 97C146F11CF9000F007C117D /* Supporting Files */ = { + isa = PBXGroup; + children = ( + ); + name = "Supporting Files"; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 97C146ED1CF9000F007C117D /* Runner */ = { + isa = PBXNativeTarget; + buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; + buildPhases = ( + 9740EEB61CF901F6004384FC /* Run Script */, + 97C146EA1CF9000F007C117D /* Sources */, + 97C146EB1CF9000F007C117D /* Frameworks */, + 97C146EC1CF9000F007C117D /* Resources */, + 9705A1C41CF9048500538489 /* Embed Frameworks */, + 3B06AD1E1E4923F5004D2608 /* Thin Binary */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = Runner; + productName = Runner; + productReference = 97C146EE1CF9000F007C117D /* Runner.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 97C146E61CF9000F007C117D /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 1020; + ORGANIZATIONNAME = "The Chromium Authors"; + TargetAttributes = { + 97C146ED1CF9000F007C117D = { + CreatedOnToolsVersion = 7.3.1; + LastSwiftMigration = 0910; + }; + }; + }; + buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; + compatibilityVersion = "Xcode 3.2"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 97C146E51CF9000F007C117D; + productRefGroup = 97C146EF1CF9000F007C117D /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 97C146ED1CF9000F007C117D /* Runner */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 97C146EC1CF9000F007C117D /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, + 9740EEB41CF90195004384FC /* Debug.xcconfig in Resources */, + 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, + 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Thin Binary"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" thin"; + }; + 9740EEB61CF901F6004384FC /* Run Script */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Run Script"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 97C146EA1CF9000F007C117D /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXVariantGroup section */ + 97C146FA1CF9000F007C117D /* Main.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 97C146FB1CF9000F007C117D /* Base */, + ); + name = Main.storyboard; + sourceTree = ""; + }; + 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 97C147001CF9000F007C117D /* Base */, + ); + name = LaunchScreen.storyboard; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 249021D3217E4FDB00AE95B9 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Profile; + }; + 249021D4217E4FDB00AE95B9 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/Flutter", + ); + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + LIBRARY_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/Flutter", + ); + PRODUCT_BUNDLE_IDENTIFIER = de.mintware.flutterP2pExample; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 4.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Profile; + }; + 97C147031CF9000F007C117D /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 97C147041CF9000F007C117D /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 97C147061CF9000F007C117D /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/Flutter", + ); + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + LIBRARY_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/Flutter", + ); + PRODUCT_BUNDLE_IDENTIFIER = de.mintware.flutterP2pExample; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 4.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Debug; + }; + 97C147071CF9000F007C117D /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/Flutter", + ); + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + LIBRARY_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/Flutter", + ); + PRODUCT_BUNDLE_IDENTIFIER = de.mintware.flutterP2pExample; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 4.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 97C147031CF9000F007C117D /* Debug */, + 97C147041CF9000F007C117D /* Release */, + 249021D3217E4FDB00AE95B9 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 97C147061CF9000F007C117D /* Debug */, + 97C147071CF9000F007C117D /* Release */, + 249021D4217E4FDB00AE95B9 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + + }; + rootObject = 97C146E61CF9000F007C117D /* Project object */; +} diff --git a/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..1d526a1 --- /dev/null +++ b/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme new file mode 100644 index 0000000..a28140c --- /dev/null +++ b/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -0,0 +1,91 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/example/ios/Runner.xcworkspace/contents.xcworkspacedata b/example/ios/Runner.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..1d526a1 --- /dev/null +++ b/example/ios/Runner.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/example/ios/Runner/AppDelegate.swift b/example/ios/Runner/AppDelegate.swift new file mode 100644 index 0000000..70693e4 --- /dev/null +++ b/example/ios/Runner/AppDelegate.swift @@ -0,0 +1,13 @@ +import UIKit +import Flutter + +@UIApplicationMain +@objc class AppDelegate: FlutterAppDelegate { + override func application( + _ application: UIApplication, + didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? + ) -> Bool { + GeneratedPluginRegistrant.register(with: self) + return super.application(application, didFinishLaunchingWithOptions: launchOptions) + } +} diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..d36b1fa --- /dev/null +++ b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,122 @@ +{ + "images" : [ + { + "size" : "20x20", + "idiom" : "iphone", + "filename" : "Icon-App-20x20@2x.png", + "scale" : "2x" + }, + { + "size" : "20x20", + "idiom" : "iphone", + "filename" : "Icon-App-20x20@3x.png", + "scale" : "3x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@1x.png", + "scale" : "1x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@2x.png", + "scale" : "2x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@3x.png", + "scale" : "3x" + }, + { + "size" : "40x40", + "idiom" : "iphone", + "filename" : "Icon-App-40x40@2x.png", + "scale" : "2x" + }, + { + "size" : "40x40", + "idiom" : "iphone", + "filename" : "Icon-App-40x40@3x.png", + "scale" : "3x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "Icon-App-60x60@2x.png", + "scale" : "2x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "Icon-App-60x60@3x.png", + "scale" : "3x" + }, + { + "size" : "20x20", + "idiom" : "ipad", + "filename" : "Icon-App-20x20@1x.png", + "scale" : "1x" + }, + { + "size" : "20x20", + "idiom" : "ipad", + "filename" : "Icon-App-20x20@2x.png", + "scale" : "2x" + }, + { + "size" : "29x29", + "idiom" : "ipad", + "filename" : "Icon-App-29x29@1x.png", + "scale" : "1x" + }, + { + "size" : "29x29", + "idiom" : "ipad", + "filename" : "Icon-App-29x29@2x.png", + "scale" : "2x" + }, + { + "size" : "40x40", + "idiom" : "ipad", + "filename" : "Icon-App-40x40@1x.png", + "scale" : "1x" + }, + { + "size" : "40x40", + "idiom" : "ipad", + "filename" : "Icon-App-40x40@2x.png", + "scale" : "2x" + }, + { + "size" : "76x76", + "idiom" : "ipad", + "filename" : "Icon-App-76x76@1x.png", + "scale" : "1x" + }, + { + "size" : "76x76", + "idiom" : "ipad", + "filename" : "Icon-App-76x76@2x.png", + "scale" : "2x" + }, + { + "size" : "83.5x83.5", + "idiom" : "ipad", + "filename" : "Icon-App-83.5x83.5@2x.png", + "scale" : "2x" + }, + { + "size" : "1024x1024", + "idiom" : "ios-marketing", + "filename" : "Icon-App-1024x1024@1x.png", + "scale" : "1x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png new file mode 100644 index 0000000..dc9ada4 Binary files /dev/null and b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png differ diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png new file mode 100644 index 0000000..28c6bf0 Binary files /dev/null and b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png differ diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png new file mode 100644 index 0000000..2ccbfd9 Binary files /dev/null and b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png differ diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png new file mode 100644 index 0000000..f091b6b Binary files /dev/null and b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png differ diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png new file mode 100644 index 0000000..4cde121 Binary files /dev/null and b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png differ diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png new file mode 100644 index 0000000..d0ef06e Binary files /dev/null and b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png differ diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png new file mode 100644 index 0000000..dcdc230 Binary files /dev/null and b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png differ diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png new file mode 100644 index 0000000..2ccbfd9 Binary files /dev/null and b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png differ diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png new file mode 100644 index 0000000..c8f9ed8 Binary files /dev/null and b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png differ diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png new file mode 100644 index 0000000..a6d6b86 Binary files /dev/null and b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png differ diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png new file mode 100644 index 0000000..a6d6b86 Binary files /dev/null and b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png differ diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png new file mode 100644 index 0000000..75b2d16 Binary files /dev/null and b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png differ diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png new file mode 100644 index 0000000..c4df70d Binary files /dev/null and b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png differ diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png new file mode 100644 index 0000000..6a84f41 Binary files /dev/null and b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png differ diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png new file mode 100644 index 0000000..d0e1f58 Binary files /dev/null and b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png differ diff --git a/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json b/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json new file mode 100644 index 0000000..0bedcf2 --- /dev/null +++ b/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "LaunchImage.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "LaunchImage@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "LaunchImage@3x.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png b/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png new file mode 100644 index 0000000..9da19ea Binary files /dev/null and b/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png differ diff --git a/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png b/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png new file mode 100644 index 0000000..9da19ea Binary files /dev/null and b/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png differ diff --git a/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png b/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png new file mode 100644 index 0000000..9da19ea Binary files /dev/null and b/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png differ diff --git a/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md b/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md new file mode 100644 index 0000000..89c2725 --- /dev/null +++ b/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md @@ -0,0 +1,5 @@ +# Launch Screen Assets + +You can customize the launch screen with your own desired assets by replacing the image files in this directory. + +You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. \ No newline at end of file diff --git a/example/ios/Runner/Base.lproj/LaunchScreen.storyboard b/example/ios/Runner/Base.lproj/LaunchScreen.storyboard new file mode 100644 index 0000000..f2e259c --- /dev/null +++ b/example/ios/Runner/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/example/ios/Runner/Base.lproj/Main.storyboard b/example/ios/Runner/Base.lproj/Main.storyboard new file mode 100644 index 0000000..f3c2851 --- /dev/null +++ b/example/ios/Runner/Base.lproj/Main.storyboard @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/example/ios/Runner/Info.plist b/example/ios/Runner/Info.plist new file mode 100644 index 0000000..e353f42 --- /dev/null +++ b/example/ios/Runner/Info.plist @@ -0,0 +1,45 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + flutter_p2p_example + CFBundlePackageType + APPL + CFBundleShortVersionString + $(FLUTTER_BUILD_NAME) + CFBundleSignature + ???? + CFBundleVersion + $(FLUTTER_BUILD_NUMBER) + LSRequiresIPhoneOS + + UILaunchStoryboardName + LaunchScreen + UIMainStoryboardFile + Main + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UIViewControllerBasedStatusBarAppearance + + + diff --git a/example/ios/Runner/Runner-Bridging-Header.h b/example/ios/Runner/Runner-Bridging-Header.h new file mode 100644 index 0000000..7335fdf --- /dev/null +++ b/example/ios/Runner/Runner-Bridging-Header.h @@ -0,0 +1 @@ +#import "GeneratedPluginRegistrant.h" \ No newline at end of file diff --git a/example/lib/main.dart b/example/lib/main.dart new file mode 100644 index 0000000..38cfdb6 --- /dev/null +++ b/example/lib/main.dart @@ -0,0 +1,217 @@ +/* + * This file is part of the flutter_p2p package. + * + * Copyright 2019 by Julian Finkler + * + * For the full copyright and license information, please read the LICENSE + * file that was distributed with this source code. + * + */ + +import 'dart:convert'; + +import 'package:flutter/material.dart'; +import 'dart:async'; + +import 'package:flutter_p2p/flutter_p2p.dart'; +import 'package:flutter_p2p/gen/protos/protos.pb.dart'; + +void main() => runApp(MyApp()); + +class MyApp extends StatefulWidget { + @override + _MyAppState createState() => _MyAppState(); +} + +class _MyAppState extends State with WidgetsBindingObserver { + @override + void initState() { + super.initState(); + _register(); + WidgetsBinding.instance.addObserver(this); + } + + @override + void dispose() { + WidgetsBinding.instance.removeObserver(this); + super.dispose(); + } + + @override + void didChangeAppLifecycleState(AppLifecycleState state) { + if (state == AppLifecycleState.resumed) { + _register(); + } else if (state == AppLifecycleState.paused) { + _unregister(); + } + } + + List devices = []; + + var _isConnected = false; + var _isHost = false; + + List _subscriptions = []; + + void _register() async { + if (!await _checkPermission()) { + return; + } + _subscriptions.add(FlutterP2p.wifiEvents.stateChange.listen((change) { + print("stateChange: ${change.isEnabled}"); + })); + + _subscriptions.add(FlutterP2p.wifiEvents.connectionChange.listen((change) { + setState(() { + _isConnected = change.networkInfo.isConnected; + _isHost = change.wifiP2pInfo.isGroupOwner; + _deviceAddress = change.wifiP2pInfo.groupOwnerAddress; + }); + print( + "connectionChange: ${change.wifiP2pInfo.isGroupOwner}, Connected: ${change.networkInfo.isConnected}"); + })); + + _subscriptions.add(FlutterP2p.wifiEvents.thisDeviceChange.listen((change) { + print( + "deviceChange: ${change.deviceName} / ${change.deviceAddress} / ${change.primaryDeviceType} / ${change.secondaryDeviceType} ${change.isGroupOwner ? 'GO' : '-GO'}"); + })); + + _subscriptions.add(FlutterP2p.wifiEvents.peersChange.listen((change) { + print("peersChange: ${change.devices.length}"); + change.devices.forEach((device) { + print("device: ${device.deviceName} / ${device.deviceAddress}"); + }); + + setState(() { + devices = change.devices; + }); + })); + + FlutterP2p.register(); + } + + void _unregister() { + _subscriptions.forEach((subscription) => subscription.cancel()); + FlutterP2p.unregister(); + } + + P2pSocket _socket; + void _openPortAndAccept(int port) async { + var socket = await FlutterP2p.openHostPort(port); + setState(() { + _socket = socket; + }); + + var buffer = ""; + socket.inputStream.listen((data) { + var msg = String.fromCharCodes(data.data); + buffer += msg; + if (data.dataAvailable == 0) { + snackBar("Data Received: $buffer"); + socket.writeString("Successfully received: $buffer"); + buffer = ""; + } + }); + + print("_openPort done"); + + await FlutterP2p.acceptPort(port); + print("_accept done"); + } + + var _deviceAddress = ""; + + _connectToPort(int port) async { + var socket = await FlutterP2p.connectToHost( + _deviceAddress, + port, + timeout: 100000, + ); + + setState(() { + _socket = socket; + }); + + _socket.inputStream.listen((data) { + var msg = utf8.decode(data.data); + snackBar("Received from Host: $msg"); + }); + + print("_connectToPort done"); + } + + Future _checkPermission() async { + if (!await FlutterP2p.isLocationPermissionGranted()) { + await FlutterP2p.requestLocationPermission(); + return false; + } + return true; + } + + final _scaffoldKey = GlobalKey(); + + @override + Widget build(BuildContext context) { + return MaterialApp( + home: Scaffold( + key: _scaffoldKey, + appBar: AppBar( + title: const Text('Plugin example app 2'), + ), + body: Column( + children: [ + Text(_isConnected + ? "Connected: ${_isHost ? "Host" : "Client"}" + : "Disconnected"), + RaisedButton( + onPressed: () => FlutterP2p.discoverDevices(), + child: Text("Discover Devices"), + ), + RaisedButton( + onPressed: _isConnected && _isHost + ? () => _openPortAndAccept(8888) + : null, + child: Text("Open and accept data from port 8888"), + ), + RaisedButton( + onPressed: _isConnected ? () => _connectToPort(8888) : null, + child: Text("Connect to port 8888"), + ), + RaisedButton( + onPressed: _socket != null + ? () => _socket.writeString("Hello World") + : null, + child: Text("Send hello world"), + ), + Expanded( + child: ListView( + children: this.devices.map((d) { + return ListTile( + title: Text(d.deviceName), + subtitle: Text(d.deviceAddress), + onTap: () { + print( + "${_isConnected ? "Disconnect" : "Connect"} to device: $_deviceAddress"); + return _isConnected + ? FlutterP2p.cancelConnect(d) + : FlutterP2p.connect(d); + }, + ); + }).toList(), + ), + ), + ], + ), + ), + ); + } + + snackBar(String text) { + _scaffoldKey.currentState.showSnackBar( + SnackBar( + content: Text(text), + duration: Duration(seconds: 2), + ), + ); + } +} diff --git a/example/pubspec.lock b/example/pubspec.lock new file mode 100644 index 0000000..b1cf05c --- /dev/null +++ b/example/pubspec.lock @@ -0,0 +1,167 @@ +# Generated by pub +# See https://dart.dev/tools/pub/glossary#lockfile +packages: + async: + dependency: transitive + description: + name: async + url: "https://pub.dartlang.org" + source: hosted + version: "2.3.0" + boolean_selector: + dependency: transitive + description: + name: boolean_selector + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.5" + charcode: + dependency: transitive + description: + name: charcode + url: "https://pub.dartlang.org" + source: hosted + version: "1.1.2" + collection: + dependency: transitive + description: + name: collection + url: "https://pub.dartlang.org" + source: hosted + version: "1.14.11" + cupertino_icons: + dependency: "direct main" + description: + name: cupertino_icons + url: "https://pub.dartlang.org" + source: hosted + version: "0.1.2" + fixnum: + dependency: transitive + description: + name: fixnum + url: "https://pub.dartlang.org" + source: hosted + version: "0.10.9" + flutter: + dependency: "direct main" + description: flutter + source: sdk + version: "0.0.0" + flutter_p2p: + dependency: "direct dev" + description: + path: ".." + relative: true + source: path + version: "0.0.1" + flutter_test: + dependency: "direct dev" + description: flutter + source: sdk + version: "0.0.0" + matcher: + dependency: transitive + description: + name: matcher + url: "https://pub.dartlang.org" + source: hosted + version: "0.12.5" + meta: + dependency: transitive + description: + name: meta + url: "https://pub.dartlang.org" + source: hosted + version: "1.1.7" + path: + dependency: transitive + description: + name: path + url: "https://pub.dartlang.org" + source: hosted + version: "1.6.4" + pedantic: + dependency: transitive + description: + name: pedantic + url: "https://pub.dartlang.org" + source: hosted + version: "1.8.0+1" + protobuf: + dependency: transitive + description: + name: protobuf + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.0" + quiver: + dependency: transitive + description: + name: quiver + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.5" + sky_engine: + dependency: transitive + description: flutter + source: sdk + version: "0.0.99" + source_span: + dependency: transitive + description: + name: source_span + url: "https://pub.dartlang.org" + source: hosted + version: "1.5.5" + stack_trace: + dependency: transitive + description: + name: stack_trace + url: "https://pub.dartlang.org" + source: hosted + version: "1.9.3" + stream_channel: + dependency: transitive + description: + name: stream_channel + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.0" + string_scanner: + dependency: transitive + description: + name: string_scanner + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.5" + term_glyph: + dependency: transitive + description: + name: term_glyph + url: "https://pub.dartlang.org" + source: hosted + version: "1.1.0" + test_api: + dependency: transitive + description: + name: test_api + url: "https://pub.dartlang.org" + source: hosted + version: "0.2.5" + typed_data: + dependency: transitive + description: + name: typed_data + url: "https://pub.dartlang.org" + source: hosted + version: "1.1.6" + vector_math: + dependency: transitive + description: + name: vector_math + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.8" +sdks: + dart: ">=2.2.2 <3.0.0" diff --git a/example/pubspec.yaml b/example/pubspec.yaml new file mode 100644 index 0000000..43bd3bc --- /dev/null +++ b/example/pubspec.yaml @@ -0,0 +1,63 @@ +name: flutter_p2p_example +description: Demonstrates how to use the flutter_p2p plugin. +publish_to: 'none' + +environment: + sdk: ">=2.1.0 <3.0.0" + +dependencies: + flutter: + sdk: flutter + + # The following adds the Cupertino Icons font to your application. + # Use with the CupertinoIcons class for iOS style icons. + cupertino_icons: ^0.1.2 + +dev_dependencies: + flutter_test: + sdk: flutter + + flutter_p2p: + path: ../ + +# For information on the generic Dart part of this file, see the +# following page: https://dart.dev/tools/pub/pubspec + +# The following section is specific to Flutter. +flutter: + + # The following line ensures that the Material Icons font is + # included with your application, so that you can use the icons in + # the material Icons class. + uses-material-design: true + + # To add assets to your application, add an assets section, like this: + # assets: + # - images/a_dot_burr.jpeg + # - images/a_dot_ham.jpeg + + # An image asset can refer to one or more resolution-specific "variants", see + # https://flutter.dev/assets-and-images/#resolution-aware. + + # For details regarding adding assets from package dependencies, see + # https://flutter.dev/assets-and-images/#from-packages + + # To add custom fonts to your application, add a fonts section here, + # in this "flutter" section. Each entry in this list should have a + # "family" key with the font family name, and a "fonts" key with a + # list giving the asset and other descriptors for the font. For + # example: + # fonts: + # - family: Schyler + # fonts: + # - asset: fonts/Schyler-Regular.ttf + # - asset: fonts/Schyler-Italic.ttf + # style: italic + # - family: Trajan Pro + # fonts: + # - asset: fonts/TrajanPro.ttf + # - asset: fonts/TrajanPro_Bold.ttf + # weight: 700 + # + # For details regarding fonts from package dependencies, + # see https://flutter.dev/custom-fonts/#from-packages diff --git a/example/test/widget_test.dart b/example/test/widget_test.dart new file mode 100644 index 0000000..a5f4549 --- /dev/null +++ b/example/test/widget_test.dart @@ -0,0 +1,27 @@ +// This is a basic Flutter widget test. +// +// To perform an interaction with a widget in your test, use the WidgetTester +// utility that Flutter provides. For example, you can send tap and scroll +// gestures. You can also use WidgetTester to find child widgets in the widget +// tree, read text, and verify that the values of widget properties are correct. + +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import 'package:flutter_p2p_example/main.dart'; + +void main() { + testWidgets('Verify Platform version', (WidgetTester tester) async { + // Build our app and trigger a frame. + await tester.pumpWidget(MyApp()); + + // Verify that platform version is retrieved. + expect( + find.byWidgetPredicate( + (Widget widget) => widget is Text && + widget.data.startsWith('Running on:'), + ), + findsOneWidget, + ); + }); +} diff --git a/flutter_p2p.iml b/flutter_p2p.iml new file mode 100644 index 0000000..0866d2f --- /dev/null +++ b/flutter_p2p.iml @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/generate_proto.sh b/generate_proto.sh new file mode 100755 index 0000000..96f0745 --- /dev/null +++ b/generate_proto.sh @@ -0,0 +1,4 @@ +#!/bin/sh + +protoc --dart_out=./lib/gen ./protos/protos.proto +protoc --swift_out=./ios/Classes ./protos/protos.proto diff --git a/ios/.gitignore b/ios/.gitignore new file mode 100644 index 0000000..aa479fd --- /dev/null +++ b/ios/.gitignore @@ -0,0 +1,37 @@ +.idea/ +.vagrant/ +.sconsign.dblite +.svn/ + +.DS_Store +*.swp +profile + +DerivedData/ +build/ +GeneratedPluginRegistrant.h +GeneratedPluginRegistrant.m + +.generated/ + +*.pbxuser +*.mode1v3 +*.mode2v3 +*.perspectivev3 + +!default.pbxuser +!default.mode1v3 +!default.mode2v3 +!default.perspectivev3 + +xcuserdata + +*.moved-aside + +*.pyc +*sync/ +Icon? +.tags* + +/Flutter/Generated.xcconfig +/Flutter/flutter_export_environment.sh \ No newline at end of file diff --git a/ios/Assets/.gitkeep b/ios/Assets/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/ios/Classes/FlutterP2pPlugin.h b/ios/Classes/FlutterP2pPlugin.h new file mode 100644 index 0000000..f000e40 --- /dev/null +++ b/ios/Classes/FlutterP2pPlugin.h @@ -0,0 +1,4 @@ +#import + +@interface FlutterP2pPlugin : NSObject +@end diff --git a/ios/Classes/FlutterP2pPlugin.m b/ios/Classes/FlutterP2pPlugin.m new file mode 100644 index 0000000..342cb51 --- /dev/null +++ b/ios/Classes/FlutterP2pPlugin.m @@ -0,0 +1,8 @@ +#import "FlutterP2pPlugin.h" +#import + +@implementation FlutterP2pPlugin ++ (void)registerWithRegistrar:(NSObject*)registrar { + [SwiftFlutterP2pPlugin registerWithRegistrar:registrar]; +} +@end diff --git a/ios/Classes/SwiftFlutterP2pPlugin.swift b/ios/Classes/SwiftFlutterP2pPlugin.swift new file mode 100644 index 0000000..c05a983 --- /dev/null +++ b/ios/Classes/SwiftFlutterP2pPlugin.swift @@ -0,0 +1,14 @@ +import Flutter +import UIKit + +public class SwiftFlutterP2pPlugin: NSObject, FlutterPlugin { + public static func register(with registrar: FlutterPluginRegistrar) { + let channel = FlutterMethodChannel(name: "flutter_p2p", binaryMessenger: registrar.messenger()) + let instance = SwiftFlutterP2pPlugin() + registrar.addMethodCallDelegate(instance, channel: channel) + } + + public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) { + result("iOS " + UIDevice.current.systemVersion) + } +} diff --git a/ios/Classes/protos/protos.pb.swift b/ios/Classes/protos/protos.pb.swift new file mode 100644 index 0000000..19ffba2 --- /dev/null +++ b/ios/Classes/protos/protos.pb.swift @@ -0,0 +1,713 @@ +// DO NOT EDIT. +// +// Generated by the Swift generator plugin for the protocol buffer compiler. +// Source: protos/protos.proto +// +// For information on using the generated types, please see the documentation: +// https://github.com/apple/swift-protobuf/ + +// protoc --dart_out=./lib/gen ./protos/protos.proto +// protoc --swift_out=./ios/gen ./protos/protos.proto + +import Foundation +import SwiftProtobuf + +// If the compiler emits an error on this type, it is because this file +// was generated by a version of the `protoc` Swift plug-in that is +// incompatible with the version of SwiftProtobuf to which you are linking. +// Please ensure that your are building against the same version of the API +// that was used to generate this file. +fileprivate struct _GeneratedWithProtocGenSwiftVersion: SwiftProtobuf.ProtobufAPIVersionCheck { + struct _2: SwiftProtobuf.ProtobufAPIVersion_2 {} + typealias Version = _2 +} + +struct StateChange { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + var isEnabled: Bool = false + + var unknownFields = SwiftProtobuf.UnknownStorage() + + init() {} +} + +struct WifiP2pDevice { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + var wpsPbcSupported: Bool = false + + var wpsKeypadSupported: Bool = false + + var wpsDisplaySupported: Bool = false + + var isServiceDiscoveryCapable: Bool = false + + var isGroupOwner: Bool = false + + var deviceName: String = String() + + var deviceAddress: String = String() + + var primaryDeviceType: String = String() + + var secondaryDeviceType: String = String() + + var status: WifiP2pDevice.Status = .connected + + var unknownFields = SwiftProtobuf.UnknownStorage() + + enum Status: SwiftProtobuf.Enum { + typealias RawValue = Int + case connected // = 0 + case invited // = 1 + case failed // = 2 + case available // = 3 + case unavailable // = 4 + case UNRECOGNIZED(Int) + + init() { + self = .connected + } + + init?(rawValue: Int) { + switch rawValue { + case 0: self = .connected + case 1: self = .invited + case 2: self = .failed + case 3: self = .available + case 4: self = .unavailable + default: self = .UNRECOGNIZED(rawValue) + } + } + + var rawValue: Int { + switch self { + case .connected: return 0 + case .invited: return 1 + case .failed: return 2 + case .available: return 3 + case .unavailable: return 4 + case .UNRECOGNIZED(let i): return i + } + } + + } + + init() {} +} + +#if swift(>=4.2) + +extension WifiP2pDevice.Status: CaseIterable { + // The compiler won't synthesize support with the UNRECOGNIZED case. + static var allCases: [WifiP2pDevice.Status] = [ + .connected, + .invited, + .failed, + .available, + .unavailable, + ] +} + +#endif // swift(>=4.2) + +struct WifiP2pDeviceList { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + var devices: [WifiP2pDevice] = [] + + var unknownFields = SwiftProtobuf.UnknownStorage() + + init() {} +} + +struct ConnectionChange { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + var wifiP2PInfo: WifiP2pInfo { + get {return _storage._wifiP2PInfo ?? WifiP2pInfo()} + set {_uniqueStorage()._wifiP2PInfo = newValue} + } + /// Returns true if `wifiP2PInfo` has been explicitly set. + var hasWifiP2PInfo: Bool {return _storage._wifiP2PInfo != nil} + /// Clears the value of `wifiP2PInfo`. Subsequent reads from it will return its default value. + mutating func clearWifiP2PInfo() {_uniqueStorage()._wifiP2PInfo = nil} + + var networkInfo: NetworkInfo { + get {return _storage._networkInfo ?? NetworkInfo()} + set {_uniqueStorage()._networkInfo = newValue} + } + /// Returns true if `networkInfo` has been explicitly set. + var hasNetworkInfo: Bool {return _storage._networkInfo != nil} + /// Clears the value of `networkInfo`. Subsequent reads from it will return its default value. + mutating func clearNetworkInfo() {_uniqueStorage()._networkInfo = nil} + + var unknownFields = SwiftProtobuf.UnknownStorage() + + init() {} + + fileprivate var _storage = _StorageClass.defaultInstance +} + +struct WifiP2pInfo { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + var groupFormed: Bool = false + + var isGroupOwner: Bool = false + + var groupOwnerAddress: String = String() + + var unknownFields = SwiftProtobuf.UnknownStorage() + + init() {} +} + +struct NetworkInfo { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + var subType: Int32 = 0 + + var isConnected: Bool = false + + var detailedState: NetworkInfo.DetailedState = .idle + + var extraInfo: String = String() + + var unknownFields = SwiftProtobuf.UnknownStorage() + + enum DetailedState: SwiftProtobuf.Enum { + typealias RawValue = Int + case idle // = 0 + case scanning // = 1 + case connecting // = 2 + case authenticating // = 3 + case obtainingIpaddr // = 4 + case connected // = 5 + case suspended // = 6 + case disconnecting // = 7 + case disconnected // = 8 + case failed // = 9 + case blocked // = 10 + case verifyingPoorLink // = 11 + case captivePortalCheck // = 12 + case UNRECOGNIZED(Int) + + init() { + self = .idle + } + + init?(rawValue: Int) { + switch rawValue { + case 0: self = .idle + case 1: self = .scanning + case 2: self = .connecting + case 3: self = .authenticating + case 4: self = .obtainingIpaddr + case 5: self = .connected + case 6: self = .suspended + case 7: self = .disconnecting + case 8: self = .disconnected + case 9: self = .failed + case 10: self = .blocked + case 11: self = .verifyingPoorLink + case 12: self = .captivePortalCheck + default: self = .UNRECOGNIZED(rawValue) + } + } + + var rawValue: Int { + switch self { + case .idle: return 0 + case .scanning: return 1 + case .connecting: return 2 + case .authenticating: return 3 + case .obtainingIpaddr: return 4 + case .connected: return 5 + case .suspended: return 6 + case .disconnecting: return 7 + case .disconnected: return 8 + case .failed: return 9 + case .blocked: return 10 + case .verifyingPoorLink: return 11 + case .captivePortalCheck: return 12 + case .UNRECOGNIZED(let i): return i + } + } + + } + + init() {} +} + +#if swift(>=4.2) + +extension NetworkInfo.DetailedState: CaseIterable { + // The compiler won't synthesize support with the UNRECOGNIZED case. + static var allCases: [NetworkInfo.DetailedState] = [ + .idle, + .scanning, + .connecting, + .authenticating, + .obtainingIpaddr, + .connected, + .suspended, + .disconnecting, + .disconnected, + .failed, + .blocked, + .verifyingPoorLink, + .captivePortalCheck, + ] +} + +#endif // swift(>=4.2) + +struct RequestPermissionResult { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + var requestCode: Int32 = 0 + + var grantedPermissions: [String] = [] + + var unknownFields = SwiftProtobuf.UnknownStorage() + + init() {} +} + +struct SocketMessage { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + var port: Int32 = 0 + + /// The number of bytes which are still available + var dataAvailable: Int32 = 0 + + /// The data + var data: Data = SwiftProtobuf.Internal.emptyData + + var unknownFields = SwiftProtobuf.UnknownStorage() + + init() {} +} + +// MARK: - Code below here is support for the SwiftProtobuf runtime. + +extension StateChange: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + static let protoMessageName: String = "StateChange" + static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 1: .same(proto: "isEnabled"), + ] + + mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + switch fieldNumber { + case 1: try decoder.decodeSingularBoolField(value: &self.isEnabled) + default: break + } + } + } + + func traverse(visitor: inout V) throws { + if self.isEnabled != false { + try visitor.visitSingularBoolField(value: self.isEnabled, fieldNumber: 1) + } + try unknownFields.traverse(visitor: &visitor) + } + + static func ==(lhs: StateChange, rhs: StateChange) -> Bool { + if lhs.isEnabled != rhs.isEnabled {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension WifiP2pDevice: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + static let protoMessageName: String = "WifiP2pDevice" + static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 1: .same(proto: "wpsPbcSupported"), + 2: .same(proto: "wpsKeypadSupported"), + 3: .same(proto: "wpsDisplaySupported"), + 4: .same(proto: "isServiceDiscoveryCapable"), + 5: .same(proto: "isGroupOwner"), + 6: .same(proto: "deviceName"), + 7: .same(proto: "deviceAddress"), + 8: .same(proto: "primaryDeviceType"), + 9: .same(proto: "secondaryDeviceType"), + 10: .same(proto: "status"), + ] + + mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + switch fieldNumber { + case 1: try decoder.decodeSingularBoolField(value: &self.wpsPbcSupported) + case 2: try decoder.decodeSingularBoolField(value: &self.wpsKeypadSupported) + case 3: try decoder.decodeSingularBoolField(value: &self.wpsDisplaySupported) + case 4: try decoder.decodeSingularBoolField(value: &self.isServiceDiscoveryCapable) + case 5: try decoder.decodeSingularBoolField(value: &self.isGroupOwner) + case 6: try decoder.decodeSingularStringField(value: &self.deviceName) + case 7: try decoder.decodeSingularStringField(value: &self.deviceAddress) + case 8: try decoder.decodeSingularStringField(value: &self.primaryDeviceType) + case 9: try decoder.decodeSingularStringField(value: &self.secondaryDeviceType) + case 10: try decoder.decodeSingularEnumField(value: &self.status) + default: break + } + } + } + + func traverse(visitor: inout V) throws { + if self.wpsPbcSupported != false { + try visitor.visitSingularBoolField(value: self.wpsPbcSupported, fieldNumber: 1) + } + if self.wpsKeypadSupported != false { + try visitor.visitSingularBoolField(value: self.wpsKeypadSupported, fieldNumber: 2) + } + if self.wpsDisplaySupported != false { + try visitor.visitSingularBoolField(value: self.wpsDisplaySupported, fieldNumber: 3) + } + if self.isServiceDiscoveryCapable != false { + try visitor.visitSingularBoolField(value: self.isServiceDiscoveryCapable, fieldNumber: 4) + } + if self.isGroupOwner != false { + try visitor.visitSingularBoolField(value: self.isGroupOwner, fieldNumber: 5) + } + if !self.deviceName.isEmpty { + try visitor.visitSingularStringField(value: self.deviceName, fieldNumber: 6) + } + if !self.deviceAddress.isEmpty { + try visitor.visitSingularStringField(value: self.deviceAddress, fieldNumber: 7) + } + if !self.primaryDeviceType.isEmpty { + try visitor.visitSingularStringField(value: self.primaryDeviceType, fieldNumber: 8) + } + if !self.secondaryDeviceType.isEmpty { + try visitor.visitSingularStringField(value: self.secondaryDeviceType, fieldNumber: 9) + } + if self.status != .connected { + try visitor.visitSingularEnumField(value: self.status, fieldNumber: 10) + } + try unknownFields.traverse(visitor: &visitor) + } + + static func ==(lhs: WifiP2pDevice, rhs: WifiP2pDevice) -> Bool { + if lhs.wpsPbcSupported != rhs.wpsPbcSupported {return false} + if lhs.wpsKeypadSupported != rhs.wpsKeypadSupported {return false} + if lhs.wpsDisplaySupported != rhs.wpsDisplaySupported {return false} + if lhs.isServiceDiscoveryCapable != rhs.isServiceDiscoveryCapable {return false} + if lhs.isGroupOwner != rhs.isGroupOwner {return false} + if lhs.deviceName != rhs.deviceName {return false} + if lhs.deviceAddress != rhs.deviceAddress {return false} + if lhs.primaryDeviceType != rhs.primaryDeviceType {return false} + if lhs.secondaryDeviceType != rhs.secondaryDeviceType {return false} + if lhs.status != rhs.status {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension WifiP2pDevice.Status: SwiftProtobuf._ProtoNameProviding { + static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 0: .same(proto: "CONNECTED"), + 1: .same(proto: "INVITED"), + 2: .same(proto: "FAILED"), + 3: .same(proto: "AVAILABLE"), + 4: .same(proto: "UNAVAILABLE"), + ] +} + +extension WifiP2pDeviceList: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + static let protoMessageName: String = "WifiP2pDeviceList" + static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 1: .same(proto: "devices"), + ] + + mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + switch fieldNumber { + case 1: try decoder.decodeRepeatedMessageField(value: &self.devices) + default: break + } + } + } + + func traverse(visitor: inout V) throws { + if !self.devices.isEmpty { + try visitor.visitRepeatedMessageField(value: self.devices, fieldNumber: 1) + } + try unknownFields.traverse(visitor: &visitor) + } + + static func ==(lhs: WifiP2pDeviceList, rhs: WifiP2pDeviceList) -> Bool { + if lhs.devices != rhs.devices {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension ConnectionChange: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + static let protoMessageName: String = "ConnectionChange" + static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 1: .same(proto: "wifiP2pInfo"), + 2: .same(proto: "networkInfo"), + ] + + fileprivate class _StorageClass { + var _wifiP2PInfo: WifiP2pInfo? = nil + var _networkInfo: NetworkInfo? = nil + + static let defaultInstance = _StorageClass() + + private init() {} + + init(copying source: _StorageClass) { + _wifiP2PInfo = source._wifiP2PInfo + _networkInfo = source._networkInfo + } + } + + fileprivate mutating func _uniqueStorage() -> _StorageClass { + if !isKnownUniquelyReferenced(&_storage) { + _storage = _StorageClass(copying: _storage) + } + return _storage + } + + mutating func decodeMessage(decoder: inout D) throws { + _ = _uniqueStorage() + try withExtendedLifetime(_storage) { (_storage: _StorageClass) in + while let fieldNumber = try decoder.nextFieldNumber() { + switch fieldNumber { + case 1: try decoder.decodeSingularMessageField(value: &_storage._wifiP2PInfo) + case 2: try decoder.decodeSingularMessageField(value: &_storage._networkInfo) + default: break + } + } + } + } + + func traverse(visitor: inout V) throws { + try withExtendedLifetime(_storage) { (_storage: _StorageClass) in + if let v = _storage._wifiP2PInfo { + try visitor.visitSingularMessageField(value: v, fieldNumber: 1) + } + if let v = _storage._networkInfo { + try visitor.visitSingularMessageField(value: v, fieldNumber: 2) + } + } + try unknownFields.traverse(visitor: &visitor) + } + + static func ==(lhs: ConnectionChange, rhs: ConnectionChange) -> Bool { + if lhs._storage !== rhs._storage { + let storagesAreEqual: Bool = withExtendedLifetime((lhs._storage, rhs._storage)) { (_args: (_StorageClass, _StorageClass)) in + let _storage = _args.0 + let rhs_storage = _args.1 + if _storage._wifiP2PInfo != rhs_storage._wifiP2PInfo {return false} + if _storage._networkInfo != rhs_storage._networkInfo {return false} + return true + } + if !storagesAreEqual {return false} + } + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension WifiP2pInfo: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + static let protoMessageName: String = "WifiP2pInfo" + static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 1: .same(proto: "groupFormed"), + 2: .same(proto: "isGroupOwner"), + 3: .same(proto: "groupOwnerAddress"), + ] + + mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + switch fieldNumber { + case 1: try decoder.decodeSingularBoolField(value: &self.groupFormed) + case 2: try decoder.decodeSingularBoolField(value: &self.isGroupOwner) + case 3: try decoder.decodeSingularStringField(value: &self.groupOwnerAddress) + default: break + } + } + } + + func traverse(visitor: inout V) throws { + if self.groupFormed != false { + try visitor.visitSingularBoolField(value: self.groupFormed, fieldNumber: 1) + } + if self.isGroupOwner != false { + try visitor.visitSingularBoolField(value: self.isGroupOwner, fieldNumber: 2) + } + if !self.groupOwnerAddress.isEmpty { + try visitor.visitSingularStringField(value: self.groupOwnerAddress, fieldNumber: 3) + } + try unknownFields.traverse(visitor: &visitor) + } + + static func ==(lhs: WifiP2pInfo, rhs: WifiP2pInfo) -> Bool { + if lhs.groupFormed != rhs.groupFormed {return false} + if lhs.isGroupOwner != rhs.isGroupOwner {return false} + if lhs.groupOwnerAddress != rhs.groupOwnerAddress {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension NetworkInfo: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + static let protoMessageName: String = "NetworkInfo" + static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 1: .same(proto: "subType"), + 2: .same(proto: "isConnected"), + 3: .same(proto: "detailedState"), + 4: .same(proto: "extraInfo"), + ] + + mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + switch fieldNumber { + case 1: try decoder.decodeSingularInt32Field(value: &self.subType) + case 2: try decoder.decodeSingularBoolField(value: &self.isConnected) + case 3: try decoder.decodeSingularEnumField(value: &self.detailedState) + case 4: try decoder.decodeSingularStringField(value: &self.extraInfo) + default: break + } + } + } + + func traverse(visitor: inout V) throws { + if self.subType != 0 { + try visitor.visitSingularInt32Field(value: self.subType, fieldNumber: 1) + } + if self.isConnected != false { + try visitor.visitSingularBoolField(value: self.isConnected, fieldNumber: 2) + } + if self.detailedState != .idle { + try visitor.visitSingularEnumField(value: self.detailedState, fieldNumber: 3) + } + if !self.extraInfo.isEmpty { + try visitor.visitSingularStringField(value: self.extraInfo, fieldNumber: 4) + } + try unknownFields.traverse(visitor: &visitor) + } + + static func ==(lhs: NetworkInfo, rhs: NetworkInfo) -> Bool { + if lhs.subType != rhs.subType {return false} + if lhs.isConnected != rhs.isConnected {return false} + if lhs.detailedState != rhs.detailedState {return false} + if lhs.extraInfo != rhs.extraInfo {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension NetworkInfo.DetailedState: SwiftProtobuf._ProtoNameProviding { + static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 0: .same(proto: "IDLE"), + 1: .same(proto: "SCANNING"), + 2: .same(proto: "CONNECTING"), + 3: .same(proto: "AUTHENTICATING"), + 4: .same(proto: "OBTAINING_IPADDR"), + 5: .same(proto: "CONNECTED"), + 6: .same(proto: "SUSPENDED"), + 7: .same(proto: "DISCONNECTING"), + 8: .same(proto: "DISCONNECTED"), + 9: .same(proto: "FAILED"), + 10: .same(proto: "BLOCKED"), + 11: .same(proto: "VERIFYING_POOR_LINK"), + 12: .same(proto: "CAPTIVE_PORTAL_CHECK"), + ] +} + +extension RequestPermissionResult: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + static let protoMessageName: String = "RequestPermissionResult" + static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 1: .same(proto: "requestCode"), + 2: .same(proto: "grantedPermissions"), + ] + + mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + switch fieldNumber { + case 1: try decoder.decodeSingularInt32Field(value: &self.requestCode) + case 2: try decoder.decodeRepeatedStringField(value: &self.grantedPermissions) + default: break + } + } + } + + func traverse(visitor: inout V) throws { + if self.requestCode != 0 { + try visitor.visitSingularInt32Field(value: self.requestCode, fieldNumber: 1) + } + if !self.grantedPermissions.isEmpty { + try visitor.visitRepeatedStringField(value: self.grantedPermissions, fieldNumber: 2) + } + try unknownFields.traverse(visitor: &visitor) + } + + static func ==(lhs: RequestPermissionResult, rhs: RequestPermissionResult) -> Bool { + if lhs.requestCode != rhs.requestCode {return false} + if lhs.grantedPermissions != rhs.grantedPermissions {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension SocketMessage: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + static let protoMessageName: String = "SocketMessage" + static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 1: .same(proto: "port"), + 2: .same(proto: "dataAvailable"), + 3: .same(proto: "data"), + ] + + mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + switch fieldNumber { + case 1: try decoder.decodeSingularInt32Field(value: &self.port) + case 2: try decoder.decodeSingularInt32Field(value: &self.dataAvailable) + case 3: try decoder.decodeSingularBytesField(value: &self.data) + default: break + } + } + } + + func traverse(visitor: inout V) throws { + if self.port != 0 { + try visitor.visitSingularInt32Field(value: self.port, fieldNumber: 1) + } + if self.dataAvailable != 0 { + try visitor.visitSingularInt32Field(value: self.dataAvailable, fieldNumber: 2) + } + if !self.data.isEmpty { + try visitor.visitSingularBytesField(value: self.data, fieldNumber: 3) + } + try unknownFields.traverse(visitor: &visitor) + } + + static func ==(lhs: SocketMessage, rhs: SocketMessage) -> Bool { + if lhs.port != rhs.port {return false} + if lhs.dataAvailable != rhs.dataAvailable {return false} + if lhs.data != rhs.data {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} diff --git a/ios/flutter_p2p.podspec b/ios/flutter_p2p.podspec new file mode 100644 index 0000000..db239de --- /dev/null +++ b/ios/flutter_p2p.podspec @@ -0,0 +1,21 @@ +# +# To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html +# +Pod::Spec.new do |s| + s.name = 'flutter_p2p' + s.version = '0.0.1' + s.summary = 'A P2P Plugin for Flutter' + s.description = <<-DESC +A P2P Plugin for Flutter + DESC + s.homepage = 'http://example.com' + s.license = { :file => '../LICENSE' } + s.author = { 'Your Company' => 'email@example.com' } + s.source = { :path => '.' } + s.source_files = 'Classes/**/*' + s.public_header_files = 'Classes/**/*.h' + s.dependency 'Flutter' + + s.ios.deployment_target = '8.0' +end + diff --git a/lib/broadcast_handler.dart b/lib/broadcast_handler.dart new file mode 100644 index 0000000..638d4a2 --- /dev/null +++ b/lib/broadcast_handler.dart @@ -0,0 +1,72 @@ +/* + * This file is part of the flutter_p2p package. + * + * Copyright 2019 by Julian Finkler + * + * For the full copyright and license information, please read the LICENSE + * file that was distributed with this source code. + * + */ + +part of 'flutter_p2p.dart'; + +class WiFiDirectBroadcastReceiver { + static const _channelBase = FlutterP2p.channelBase; + + static EventChannel _stateChangeChannel = + EventChannel("$_channelBase/bc/state-change"); + + static EventChannel _peersChangeChannel = + EventChannel("$_channelBase/bc/peers-change"); + + static EventChannel _connectionChangeChannel = + EventChannel("$_channelBase/bc/connection-change"); + + static EventChannel _thisDeviceChangeChannel = + EventChannel("$_channelBase/bc/this-device-change"); + + static Stream _stateChangeStream; + static Stream _peersChangeStream; + static Stream _connectionChangeStream; + static Stream _thisDeviceChangeStream; + + Stream get stateChange { + if (_stateChangeStream == null) { + _stateChangeStream = _stateChangeChannel + .receiveBroadcastStream() + .map((src) => StateChange.fromBuffer(src)); + } + + return _stateChangeStream; + } + + Stream get peersChange { + if (_peersChangeStream == null) { + _peersChangeStream = _peersChangeChannel + .receiveBroadcastStream() + .map((src) => WifiP2pDeviceList.fromBuffer(src)); + } + + return _peersChangeStream; + } + + Stream get connectionChange { + if (_connectionChangeStream == null) { + _connectionChangeStream = _connectionChangeChannel + .receiveBroadcastStream() + .map((src) => ConnectionChange.fromBuffer(src)); + } + + return _connectionChangeStream; + } + + Stream get thisDeviceChange { + if (_thisDeviceChangeStream == null) { + _thisDeviceChangeStream = _thisDeviceChangeChannel + .receiveBroadcastStream() + .map((src) => WifiP2pDevice.fromBuffer(src)); + } + + return _thisDeviceChangeStream; + } +} diff --git a/lib/flutter_p2p.dart b/lib/flutter_p2p.dart new file mode 100644 index 0000000..1903102 --- /dev/null +++ b/lib/flutter_p2p.dart @@ -0,0 +1,22 @@ +/* + * This file is part of the flutter_p2p package. + * + * Copyright 2019 by Julian Finkler + * + * For the full copyright and license information, please read the LICENSE + * file that was distributed with this source code. + * + */ + +library flutter_p2p; + +import 'dart:convert'; +import 'dart:typed_data'; + +import 'package:flutter/services.dart'; +import 'gen/protos/protos.pb.dart'; + +part 'broadcast_handler.dart'; +part 'plugin.dart'; +part 'p2p_socket.dart'; +part 'socket_master.dart'; diff --git a/lib/gen/protos/protos.pb.dart b/lib/gen/protos/protos.pb.dart new file mode 100644 index 0000000..3def0b7 --- /dev/null +++ b/lib/gen/protos/protos.pb.dart @@ -0,0 +1,435 @@ +/// +// Generated code. Do not modify. +// source: protos/protos.proto +// +// @dart = 2.3 +// ignore_for_file: camel_case_types,non_constant_identifier_names,library_prefixes,unused_import,unused_shown_name,return_of_invalid_type + +import 'dart:core' as $core; + +import 'package:protobuf/protobuf.dart' as $pb; + +import 'protos.pbenum.dart'; + +export 'protos.pbenum.dart'; + +class StateChange extends $pb.GeneratedMessage { + static final $pb.BuilderInfo _i = $pb.BuilderInfo('StateChange', createEmptyInstance: create) + ..aOB(1, 'isEnabled', protoName: 'isEnabled') + ..hasRequiredFields = false + ; + + StateChange._() : super(); + factory StateChange() => create(); + factory StateChange.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r); + factory StateChange.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r); + StateChange clone() => StateChange()..mergeFromMessage(this); + StateChange copyWith(void Function(StateChange) updates) => super.copyWith((message) => updates(message as StateChange)); + $pb.BuilderInfo get info_ => _i; + @$core.pragma('dart2js:noInline') + static StateChange create() => StateChange._(); + StateChange createEmptyInstance() => create(); + static $pb.PbList createRepeated() => $pb.PbList(); + @$core.pragma('dart2js:noInline') + static StateChange getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor(create); + static StateChange _defaultInstance; + + @$pb.TagNumber(1) + $core.bool get isEnabled => $_getBF(0); + @$pb.TagNumber(1) + set isEnabled($core.bool v) { $_setBool(0, v); } + @$pb.TagNumber(1) + $core.bool hasIsEnabled() => $_has(0); + @$pb.TagNumber(1) + void clearIsEnabled() => clearField(1); +} + +class WifiP2pDevice extends $pb.GeneratedMessage { + static final $pb.BuilderInfo _i = $pb.BuilderInfo('WifiP2pDevice', createEmptyInstance: create) + ..aOB(1, 'wpsPbcSupported', protoName: 'wpsPbcSupported') + ..aOB(2, 'wpsKeypadSupported', protoName: 'wpsKeypadSupported') + ..aOB(3, 'wpsDisplaySupported', protoName: 'wpsDisplaySupported') + ..aOB(4, 'isServiceDiscoveryCapable', protoName: 'isServiceDiscoveryCapable') + ..aOB(5, 'isGroupOwner', protoName: 'isGroupOwner') + ..aOS(6, 'deviceName', protoName: 'deviceName') + ..aOS(7, 'deviceAddress', protoName: 'deviceAddress') + ..aOS(8, 'primaryDeviceType', protoName: 'primaryDeviceType') + ..aOS(9, 'secondaryDeviceType', protoName: 'secondaryDeviceType') + ..e(10, 'status', $pb.PbFieldType.OE, defaultOrMaker: WifiP2pDevice_Status.CONNECTED, valueOf: WifiP2pDevice_Status.valueOf, enumValues: WifiP2pDevice_Status.values) + ..hasRequiredFields = false + ; + + WifiP2pDevice._() : super(); + factory WifiP2pDevice() => create(); + factory WifiP2pDevice.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r); + factory WifiP2pDevice.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r); + WifiP2pDevice clone() => WifiP2pDevice()..mergeFromMessage(this); + WifiP2pDevice copyWith(void Function(WifiP2pDevice) updates) => super.copyWith((message) => updates(message as WifiP2pDevice)); + $pb.BuilderInfo get info_ => _i; + @$core.pragma('dart2js:noInline') + static WifiP2pDevice create() => WifiP2pDevice._(); + WifiP2pDevice createEmptyInstance() => create(); + static $pb.PbList createRepeated() => $pb.PbList(); + @$core.pragma('dart2js:noInline') + static WifiP2pDevice getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor(create); + static WifiP2pDevice _defaultInstance; + + @$pb.TagNumber(1) + $core.bool get wpsPbcSupported => $_getBF(0); + @$pb.TagNumber(1) + set wpsPbcSupported($core.bool v) { $_setBool(0, v); } + @$pb.TagNumber(1) + $core.bool hasWpsPbcSupported() => $_has(0); + @$pb.TagNumber(1) + void clearWpsPbcSupported() => clearField(1); + + @$pb.TagNumber(2) + $core.bool get wpsKeypadSupported => $_getBF(1); + @$pb.TagNumber(2) + set wpsKeypadSupported($core.bool v) { $_setBool(1, v); } + @$pb.TagNumber(2) + $core.bool hasWpsKeypadSupported() => $_has(1); + @$pb.TagNumber(2) + void clearWpsKeypadSupported() => clearField(2); + + @$pb.TagNumber(3) + $core.bool get wpsDisplaySupported => $_getBF(2); + @$pb.TagNumber(3) + set wpsDisplaySupported($core.bool v) { $_setBool(2, v); } + @$pb.TagNumber(3) + $core.bool hasWpsDisplaySupported() => $_has(2); + @$pb.TagNumber(3) + void clearWpsDisplaySupported() => clearField(3); + + @$pb.TagNumber(4) + $core.bool get isServiceDiscoveryCapable => $_getBF(3); + @$pb.TagNumber(4) + set isServiceDiscoveryCapable($core.bool v) { $_setBool(3, v); } + @$pb.TagNumber(4) + $core.bool hasIsServiceDiscoveryCapable() => $_has(3); + @$pb.TagNumber(4) + void clearIsServiceDiscoveryCapable() => clearField(4); + + @$pb.TagNumber(5) + $core.bool get isGroupOwner => $_getBF(4); + @$pb.TagNumber(5) + set isGroupOwner($core.bool v) { $_setBool(4, v); } + @$pb.TagNumber(5) + $core.bool hasIsGroupOwner() => $_has(4); + @$pb.TagNumber(5) + void clearIsGroupOwner() => clearField(5); + + @$pb.TagNumber(6) + $core.String get deviceName => $_getSZ(5); + @$pb.TagNumber(6) + set deviceName($core.String v) { $_setString(5, v); } + @$pb.TagNumber(6) + $core.bool hasDeviceName() => $_has(5); + @$pb.TagNumber(6) + void clearDeviceName() => clearField(6); + + @$pb.TagNumber(7) + $core.String get deviceAddress => $_getSZ(6); + @$pb.TagNumber(7) + set deviceAddress($core.String v) { $_setString(6, v); } + @$pb.TagNumber(7) + $core.bool hasDeviceAddress() => $_has(6); + @$pb.TagNumber(7) + void clearDeviceAddress() => clearField(7); + + @$pb.TagNumber(8) + $core.String get primaryDeviceType => $_getSZ(7); + @$pb.TagNumber(8) + set primaryDeviceType($core.String v) { $_setString(7, v); } + @$pb.TagNumber(8) + $core.bool hasPrimaryDeviceType() => $_has(7); + @$pb.TagNumber(8) + void clearPrimaryDeviceType() => clearField(8); + + @$pb.TagNumber(9) + $core.String get secondaryDeviceType => $_getSZ(8); + @$pb.TagNumber(9) + set secondaryDeviceType($core.String v) { $_setString(8, v); } + @$pb.TagNumber(9) + $core.bool hasSecondaryDeviceType() => $_has(8); + @$pb.TagNumber(9) + void clearSecondaryDeviceType() => clearField(9); + + @$pb.TagNumber(10) + WifiP2pDevice_Status get status => $_getN(9); + @$pb.TagNumber(10) + set status(WifiP2pDevice_Status v) { setField(10, v); } + @$pb.TagNumber(10) + $core.bool hasStatus() => $_has(9); + @$pb.TagNumber(10) + void clearStatus() => clearField(10); +} + +class WifiP2pDeviceList extends $pb.GeneratedMessage { + static final $pb.BuilderInfo _i = $pb.BuilderInfo('WifiP2pDeviceList', createEmptyInstance: create) + ..pc(1, 'devices', $pb.PbFieldType.PM, subBuilder: WifiP2pDevice.create) + ..hasRequiredFields = false + ; + + WifiP2pDeviceList._() : super(); + factory WifiP2pDeviceList() => create(); + factory WifiP2pDeviceList.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r); + factory WifiP2pDeviceList.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r); + WifiP2pDeviceList clone() => WifiP2pDeviceList()..mergeFromMessage(this); + WifiP2pDeviceList copyWith(void Function(WifiP2pDeviceList) updates) => super.copyWith((message) => updates(message as WifiP2pDeviceList)); + $pb.BuilderInfo get info_ => _i; + @$core.pragma('dart2js:noInline') + static WifiP2pDeviceList create() => WifiP2pDeviceList._(); + WifiP2pDeviceList createEmptyInstance() => create(); + static $pb.PbList createRepeated() => $pb.PbList(); + @$core.pragma('dart2js:noInline') + static WifiP2pDeviceList getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor(create); + static WifiP2pDeviceList _defaultInstance; + + @$pb.TagNumber(1) + $core.List get devices => $_getList(0); +} + +class ConnectionChange extends $pb.GeneratedMessage { + static final $pb.BuilderInfo _i = $pb.BuilderInfo('ConnectionChange', createEmptyInstance: create) + ..aOM(1, 'wifiP2pInfo', protoName: 'wifiP2pInfo', subBuilder: WifiP2pInfo.create) + ..aOM(2, 'networkInfo', protoName: 'networkInfo', subBuilder: NetworkInfo.create) + ..hasRequiredFields = false + ; + + ConnectionChange._() : super(); + factory ConnectionChange() => create(); + factory ConnectionChange.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r); + factory ConnectionChange.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r); + ConnectionChange clone() => ConnectionChange()..mergeFromMessage(this); + ConnectionChange copyWith(void Function(ConnectionChange) updates) => super.copyWith((message) => updates(message as ConnectionChange)); + $pb.BuilderInfo get info_ => _i; + @$core.pragma('dart2js:noInline') + static ConnectionChange create() => ConnectionChange._(); + ConnectionChange createEmptyInstance() => create(); + static $pb.PbList createRepeated() => $pb.PbList(); + @$core.pragma('dart2js:noInline') + static ConnectionChange getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor(create); + static ConnectionChange _defaultInstance; + + @$pb.TagNumber(1) + WifiP2pInfo get wifiP2pInfo => $_getN(0); + @$pb.TagNumber(1) + set wifiP2pInfo(WifiP2pInfo v) { setField(1, v); } + @$pb.TagNumber(1) + $core.bool hasWifiP2pInfo() => $_has(0); + @$pb.TagNumber(1) + void clearWifiP2pInfo() => clearField(1); + @$pb.TagNumber(1) + WifiP2pInfo ensureWifiP2pInfo() => $_ensure(0); + + @$pb.TagNumber(2) + NetworkInfo get networkInfo => $_getN(1); + @$pb.TagNumber(2) + set networkInfo(NetworkInfo v) { setField(2, v); } + @$pb.TagNumber(2) + $core.bool hasNetworkInfo() => $_has(1); + @$pb.TagNumber(2) + void clearNetworkInfo() => clearField(2); + @$pb.TagNumber(2) + NetworkInfo ensureNetworkInfo() => $_ensure(1); +} + +class WifiP2pInfo extends $pb.GeneratedMessage { + static final $pb.BuilderInfo _i = $pb.BuilderInfo('WifiP2pInfo', createEmptyInstance: create) + ..aOB(1, 'groupFormed', protoName: 'groupFormed') + ..aOB(2, 'isGroupOwner', protoName: 'isGroupOwner') + ..aOS(3, 'groupOwnerAddress', protoName: 'groupOwnerAddress') + ..hasRequiredFields = false + ; + + WifiP2pInfo._() : super(); + factory WifiP2pInfo() => create(); + factory WifiP2pInfo.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r); + factory WifiP2pInfo.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r); + WifiP2pInfo clone() => WifiP2pInfo()..mergeFromMessage(this); + WifiP2pInfo copyWith(void Function(WifiP2pInfo) updates) => super.copyWith((message) => updates(message as WifiP2pInfo)); + $pb.BuilderInfo get info_ => _i; + @$core.pragma('dart2js:noInline') + static WifiP2pInfo create() => WifiP2pInfo._(); + WifiP2pInfo createEmptyInstance() => create(); + static $pb.PbList createRepeated() => $pb.PbList(); + @$core.pragma('dart2js:noInline') + static WifiP2pInfo getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor(create); + static WifiP2pInfo _defaultInstance; + + @$pb.TagNumber(1) + $core.bool get groupFormed => $_getBF(0); + @$pb.TagNumber(1) + set groupFormed($core.bool v) { $_setBool(0, v); } + @$pb.TagNumber(1) + $core.bool hasGroupFormed() => $_has(0); + @$pb.TagNumber(1) + void clearGroupFormed() => clearField(1); + + @$pb.TagNumber(2) + $core.bool get isGroupOwner => $_getBF(1); + @$pb.TagNumber(2) + set isGroupOwner($core.bool v) { $_setBool(1, v); } + @$pb.TagNumber(2) + $core.bool hasIsGroupOwner() => $_has(1); + @$pb.TagNumber(2) + void clearIsGroupOwner() => clearField(2); + + @$pb.TagNumber(3) + $core.String get groupOwnerAddress => $_getSZ(2); + @$pb.TagNumber(3) + set groupOwnerAddress($core.String v) { $_setString(2, v); } + @$pb.TagNumber(3) + $core.bool hasGroupOwnerAddress() => $_has(2); + @$pb.TagNumber(3) + void clearGroupOwnerAddress() => clearField(3); +} + +class NetworkInfo extends $pb.GeneratedMessage { + static final $pb.BuilderInfo _i = $pb.BuilderInfo('NetworkInfo', createEmptyInstance: create) + ..a<$core.int>(1, 'subType', $pb.PbFieldType.O3, protoName: 'subType') + ..aOB(2, 'isConnected', protoName: 'isConnected') + ..e(3, 'detailedState', $pb.PbFieldType.OE, protoName: 'detailedState', defaultOrMaker: NetworkInfo_DetailedState.IDLE, valueOf: NetworkInfo_DetailedState.valueOf, enumValues: NetworkInfo_DetailedState.values) + ..aOS(4, 'extraInfo', protoName: 'extraInfo') + ..hasRequiredFields = false + ; + + NetworkInfo._() : super(); + factory NetworkInfo() => create(); + factory NetworkInfo.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r); + factory NetworkInfo.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r); + NetworkInfo clone() => NetworkInfo()..mergeFromMessage(this); + NetworkInfo copyWith(void Function(NetworkInfo) updates) => super.copyWith((message) => updates(message as NetworkInfo)); + $pb.BuilderInfo get info_ => _i; + @$core.pragma('dart2js:noInline') + static NetworkInfo create() => NetworkInfo._(); + NetworkInfo createEmptyInstance() => create(); + static $pb.PbList createRepeated() => $pb.PbList(); + @$core.pragma('dart2js:noInline') + static NetworkInfo getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor(create); + static NetworkInfo _defaultInstance; + + @$pb.TagNumber(1) + $core.int get subType => $_getIZ(0); + @$pb.TagNumber(1) + set subType($core.int v) { $_setSignedInt32(0, v); } + @$pb.TagNumber(1) + $core.bool hasSubType() => $_has(0); + @$pb.TagNumber(1) + void clearSubType() => clearField(1); + + @$pb.TagNumber(2) + $core.bool get isConnected => $_getBF(1); + @$pb.TagNumber(2) + set isConnected($core.bool v) { $_setBool(1, v); } + @$pb.TagNumber(2) + $core.bool hasIsConnected() => $_has(1); + @$pb.TagNumber(2) + void clearIsConnected() => clearField(2); + + @$pb.TagNumber(3) + NetworkInfo_DetailedState get detailedState => $_getN(2); + @$pb.TagNumber(3) + set detailedState(NetworkInfo_DetailedState v) { setField(3, v); } + @$pb.TagNumber(3) + $core.bool hasDetailedState() => $_has(2); + @$pb.TagNumber(3) + void clearDetailedState() => clearField(3); + + @$pb.TagNumber(4) + $core.String get extraInfo => $_getSZ(3); + @$pb.TagNumber(4) + set extraInfo($core.String v) { $_setString(3, v); } + @$pb.TagNumber(4) + $core.bool hasExtraInfo() => $_has(3); + @$pb.TagNumber(4) + void clearExtraInfo() => clearField(4); +} + +class RequestPermissionResult extends $pb.GeneratedMessage { + static final $pb.BuilderInfo _i = $pb.BuilderInfo('RequestPermissionResult', createEmptyInstance: create) + ..a<$core.int>(1, 'requestCode', $pb.PbFieldType.O3, protoName: 'requestCode') + ..pPS(2, 'grantedPermissions', protoName: 'grantedPermissions') + ..hasRequiredFields = false + ; + + RequestPermissionResult._() : super(); + factory RequestPermissionResult() => create(); + factory RequestPermissionResult.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r); + factory RequestPermissionResult.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r); + RequestPermissionResult clone() => RequestPermissionResult()..mergeFromMessage(this); + RequestPermissionResult copyWith(void Function(RequestPermissionResult) updates) => super.copyWith((message) => updates(message as RequestPermissionResult)); + $pb.BuilderInfo get info_ => _i; + @$core.pragma('dart2js:noInline') + static RequestPermissionResult create() => RequestPermissionResult._(); + RequestPermissionResult createEmptyInstance() => create(); + static $pb.PbList createRepeated() => $pb.PbList(); + @$core.pragma('dart2js:noInline') + static RequestPermissionResult getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor(create); + static RequestPermissionResult _defaultInstance; + + @$pb.TagNumber(1) + $core.int get requestCode => $_getIZ(0); + @$pb.TagNumber(1) + set requestCode($core.int v) { $_setSignedInt32(0, v); } + @$pb.TagNumber(1) + $core.bool hasRequestCode() => $_has(0); + @$pb.TagNumber(1) + void clearRequestCode() => clearField(1); + + @$pb.TagNumber(2) + $core.List<$core.String> get grantedPermissions => $_getList(1); +} + +class SocketMessage extends $pb.GeneratedMessage { + static final $pb.BuilderInfo _i = $pb.BuilderInfo('SocketMessage', createEmptyInstance: create) + ..a<$core.int>(1, 'port', $pb.PbFieldType.O3) + ..a<$core.int>(2, 'dataAvailable', $pb.PbFieldType.O3, protoName: 'dataAvailable') + ..a<$core.List<$core.int>>(3, 'data', $pb.PbFieldType.OY) + ..hasRequiredFields = false + ; + + SocketMessage._() : super(); + factory SocketMessage() => create(); + factory SocketMessage.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r); + factory SocketMessage.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r); + SocketMessage clone() => SocketMessage()..mergeFromMessage(this); + SocketMessage copyWith(void Function(SocketMessage) updates) => super.copyWith((message) => updates(message as SocketMessage)); + $pb.BuilderInfo get info_ => _i; + @$core.pragma('dart2js:noInline') + static SocketMessage create() => SocketMessage._(); + SocketMessage createEmptyInstance() => create(); + static $pb.PbList createRepeated() => $pb.PbList(); + @$core.pragma('dart2js:noInline') + static SocketMessage getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor(create); + static SocketMessage _defaultInstance; + + @$pb.TagNumber(1) + $core.int get port => $_getIZ(0); + @$pb.TagNumber(1) + set port($core.int v) { $_setSignedInt32(0, v); } + @$pb.TagNumber(1) + $core.bool hasPort() => $_has(0); + @$pb.TagNumber(1) + void clearPort() => clearField(1); + + @$pb.TagNumber(2) + $core.int get dataAvailable => $_getIZ(1); + @$pb.TagNumber(2) + set dataAvailable($core.int v) { $_setSignedInt32(1, v); } + @$pb.TagNumber(2) + $core.bool hasDataAvailable() => $_has(1); + @$pb.TagNumber(2) + void clearDataAvailable() => clearField(2); + + @$pb.TagNumber(3) + $core.List<$core.int> get data => $_getN(2); + @$pb.TagNumber(3) + set data($core.List<$core.int> v) { $_setBytes(2, v); } + @$pb.TagNumber(3) + $core.bool hasData() => $_has(2); + @$pb.TagNumber(3) + void clearData() => clearField(3); +} + diff --git a/lib/gen/protos/protos.pbenum.dart b/lib/gen/protos/protos.pbenum.dart new file mode 100644 index 0000000..72f8b43 --- /dev/null +++ b/lib/gen/protos/protos.pbenum.dart @@ -0,0 +1,69 @@ +/// +// Generated code. Do not modify. +// source: protos/protos.proto +// +// @dart = 2.3 +// ignore_for_file: camel_case_types,non_constant_identifier_names,library_prefixes,unused_import,unused_shown_name,return_of_invalid_type + +// ignore_for_file: UNDEFINED_SHOWN_NAME,UNUSED_SHOWN_NAME +import 'dart:core' as $core; +import 'package:protobuf/protobuf.dart' as $pb; + +class WifiP2pDevice_Status extends $pb.ProtobufEnum { + static const WifiP2pDevice_Status CONNECTED = WifiP2pDevice_Status._(0, 'CONNECTED'); + static const WifiP2pDevice_Status INVITED = WifiP2pDevice_Status._(1, 'INVITED'); + static const WifiP2pDevice_Status FAILED = WifiP2pDevice_Status._(2, 'FAILED'); + static const WifiP2pDevice_Status AVAILABLE = WifiP2pDevice_Status._(3, 'AVAILABLE'); + static const WifiP2pDevice_Status UNAVAILABLE = WifiP2pDevice_Status._(4, 'UNAVAILABLE'); + + static const $core.List values = [ + CONNECTED, + INVITED, + FAILED, + AVAILABLE, + UNAVAILABLE, + ]; + + static final $core.Map<$core.int, WifiP2pDevice_Status> _byValue = $pb.ProtobufEnum.initByValue(values); + static WifiP2pDevice_Status valueOf($core.int value) => _byValue[value]; + + const WifiP2pDevice_Status._($core.int v, $core.String n) : super(v, n); +} + +class NetworkInfo_DetailedState extends $pb.ProtobufEnum { + static const NetworkInfo_DetailedState IDLE = NetworkInfo_DetailedState._(0, 'IDLE'); + static const NetworkInfo_DetailedState SCANNING = NetworkInfo_DetailedState._(1, 'SCANNING'); + static const NetworkInfo_DetailedState CONNECTING = NetworkInfo_DetailedState._(2, 'CONNECTING'); + static const NetworkInfo_DetailedState AUTHENTICATING = NetworkInfo_DetailedState._(3, 'AUTHENTICATING'); + static const NetworkInfo_DetailedState OBTAINING_IPADDR = NetworkInfo_DetailedState._(4, 'OBTAINING_IPADDR'); + static const NetworkInfo_DetailedState CONNECTED = NetworkInfo_DetailedState._(5, 'CONNECTED'); + static const NetworkInfo_DetailedState SUSPENDED = NetworkInfo_DetailedState._(6, 'SUSPENDED'); + static const NetworkInfo_DetailedState DISCONNECTING = NetworkInfo_DetailedState._(7, 'DISCONNECTING'); + static const NetworkInfo_DetailedState DISCONNECTED = NetworkInfo_DetailedState._(8, 'DISCONNECTED'); + static const NetworkInfo_DetailedState FAILED = NetworkInfo_DetailedState._(9, 'FAILED'); + static const NetworkInfo_DetailedState BLOCKED = NetworkInfo_DetailedState._(10, 'BLOCKED'); + static const NetworkInfo_DetailedState VERIFYING_POOR_LINK = NetworkInfo_DetailedState._(11, 'VERIFYING_POOR_LINK'); + static const NetworkInfo_DetailedState CAPTIVE_PORTAL_CHECK = NetworkInfo_DetailedState._(12, 'CAPTIVE_PORTAL_CHECK'); + + static const $core.List values = [ + IDLE, + SCANNING, + CONNECTING, + AUTHENTICATING, + OBTAINING_IPADDR, + CONNECTED, + SUSPENDED, + DISCONNECTING, + DISCONNECTED, + FAILED, + BLOCKED, + VERIFYING_POOR_LINK, + CAPTIVE_PORTAL_CHECK, + ]; + + static final $core.Map<$core.int, NetworkInfo_DetailedState> _byValue = $pb.ProtobufEnum.initByValue(values); + static NetworkInfo_DetailedState valueOf($core.int value) => _byValue[value]; + + const NetworkInfo_DetailedState._($core.int v, $core.String n) : super(v, n); +} + diff --git a/lib/gen/protos/protos.pbjson.dart b/lib/gen/protos/protos.pbjson.dart new file mode 100644 index 0000000..7d5ee43 --- /dev/null +++ b/lib/gen/protos/protos.pbjson.dart @@ -0,0 +1,113 @@ +/// +// Generated code. Do not modify. +// source: protos/protos.proto +// +// @dart = 2.3 +// ignore_for_file: camel_case_types,non_constant_identifier_names,library_prefixes,unused_import,unused_shown_name,return_of_invalid_type + +const StateChange$json = const { + '1': 'StateChange', + '2': const [ + const {'1': 'isEnabled', '3': 1, '4': 1, '5': 8, '10': 'isEnabled'}, + ], +}; + +const WifiP2pDevice$json = const { + '1': 'WifiP2pDevice', + '2': const [ + const {'1': 'wpsPbcSupported', '3': 1, '4': 1, '5': 8, '10': 'wpsPbcSupported'}, + const {'1': 'wpsKeypadSupported', '3': 2, '4': 1, '5': 8, '10': 'wpsKeypadSupported'}, + const {'1': 'wpsDisplaySupported', '3': 3, '4': 1, '5': 8, '10': 'wpsDisplaySupported'}, + const {'1': 'isServiceDiscoveryCapable', '3': 4, '4': 1, '5': 8, '10': 'isServiceDiscoveryCapable'}, + const {'1': 'isGroupOwner', '3': 5, '4': 1, '5': 8, '10': 'isGroupOwner'}, + const {'1': 'deviceName', '3': 6, '4': 1, '5': 9, '10': 'deviceName'}, + const {'1': 'deviceAddress', '3': 7, '4': 1, '5': 9, '10': 'deviceAddress'}, + const {'1': 'primaryDeviceType', '3': 8, '4': 1, '5': 9, '10': 'primaryDeviceType'}, + const {'1': 'secondaryDeviceType', '3': 9, '4': 1, '5': 9, '10': 'secondaryDeviceType'}, + const {'1': 'status', '3': 10, '4': 1, '5': 14, '6': '.WifiP2pDevice.Status', '10': 'status'}, + ], + '4': const [WifiP2pDevice_Status$json], +}; + +const WifiP2pDevice_Status$json = const { + '1': 'Status', + '2': const [ + const {'1': 'CONNECTED', '2': 0}, + const {'1': 'INVITED', '2': 1}, + const {'1': 'FAILED', '2': 2}, + const {'1': 'AVAILABLE', '2': 3}, + const {'1': 'UNAVAILABLE', '2': 4}, + ], +}; + +const WifiP2pDeviceList$json = const { + '1': 'WifiP2pDeviceList', + '2': const [ + const {'1': 'devices', '3': 1, '4': 3, '5': 11, '6': '.WifiP2pDevice', '10': 'devices'}, + ], +}; + +const ConnectionChange$json = const { + '1': 'ConnectionChange', + '2': const [ + const {'1': 'wifiP2pInfo', '3': 1, '4': 1, '5': 11, '6': '.WifiP2pInfo', '10': 'wifiP2pInfo'}, + const {'1': 'networkInfo', '3': 2, '4': 1, '5': 11, '6': '.NetworkInfo', '10': 'networkInfo'}, + ], +}; + +const WifiP2pInfo$json = const { + '1': 'WifiP2pInfo', + '2': const [ + const {'1': 'groupFormed', '3': 1, '4': 1, '5': 8, '10': 'groupFormed'}, + const {'1': 'isGroupOwner', '3': 2, '4': 1, '5': 8, '10': 'isGroupOwner'}, + const {'1': 'groupOwnerAddress', '3': 3, '4': 1, '5': 9, '10': 'groupOwnerAddress'}, + ], +}; + +const NetworkInfo$json = const { + '1': 'NetworkInfo', + '2': const [ + const {'1': 'subType', '3': 1, '4': 1, '5': 5, '10': 'subType'}, + const {'1': 'isConnected', '3': 2, '4': 1, '5': 8, '10': 'isConnected'}, + const {'1': 'detailedState', '3': 3, '4': 1, '5': 14, '6': '.NetworkInfo.DetailedState', '10': 'detailedState'}, + const {'1': 'extraInfo', '3': 4, '4': 1, '5': 9, '10': 'extraInfo'}, + ], + '4': const [NetworkInfo_DetailedState$json], +}; + +const NetworkInfo_DetailedState$json = const { + '1': 'DetailedState', + '2': const [ + const {'1': 'IDLE', '2': 0}, + const {'1': 'SCANNING', '2': 1}, + const {'1': 'CONNECTING', '2': 2}, + const {'1': 'AUTHENTICATING', '2': 3}, + const {'1': 'OBTAINING_IPADDR', '2': 4}, + const {'1': 'CONNECTED', '2': 5}, + const {'1': 'SUSPENDED', '2': 6}, + const {'1': 'DISCONNECTING', '2': 7}, + const {'1': 'DISCONNECTED', '2': 8}, + const {'1': 'FAILED', '2': 9}, + const {'1': 'BLOCKED', '2': 10}, + const {'1': 'VERIFYING_POOR_LINK', '2': 11}, + const {'1': 'CAPTIVE_PORTAL_CHECK', '2': 12}, + ], +}; + +const RequestPermissionResult$json = const { + '1': 'RequestPermissionResult', + '2': const [ + const {'1': 'requestCode', '3': 1, '4': 1, '5': 5, '10': 'requestCode'}, + const {'1': 'grantedPermissions', '3': 2, '4': 3, '5': 9, '10': 'grantedPermissions'}, + ], +}; + +const SocketMessage$json = const { + '1': 'SocketMessage', + '2': const [ + const {'1': 'port', '3': 1, '4': 1, '5': 5, '10': 'port'}, + const {'1': 'dataAvailable', '3': 2, '4': 1, '5': 5, '10': 'dataAvailable'}, + const {'1': 'data', '3': 3, '4': 1, '5': 12, '10': 'data'}, + ], +}; + diff --git a/lib/gen/protos/protos.pbserver.dart b/lib/gen/protos/protos.pbserver.dart new file mode 100644 index 0000000..4aebf0b --- /dev/null +++ b/lib/gen/protos/protos.pbserver.dart @@ -0,0 +1,9 @@ +/// +// Generated code. Do not modify. +// source: protos/protos.proto +// +// @dart = 2.3 +// ignore_for_file: camel_case_types,non_constant_identifier_names,library_prefixes,unused_import,unused_shown_name,return_of_invalid_type + +export 'protos.pb.dart'; + diff --git a/lib/p2p_socket.dart b/lib/p2p_socket.dart new file mode 100644 index 0000000..52697c4 --- /dev/null +++ b/lib/p2p_socket.dart @@ -0,0 +1,29 @@ +/* + * This file is part of the flutter_p2p package. + * + * Copyright 2019 by Julian Finkler + * + * For the full copyright and license information, please read the LICENSE + * file that was distributed with this source code. + * + */ + +part of 'flutter_p2p.dart'; + +class P2pSocket { + final bool isHost; + final int port; + final Stream _inputStream; + + Stream get inputStream => _inputStream; + + P2pSocket(this.port, this.isHost, this._inputStream); + + Future write(Uint8List data) async { + return FlutterP2p.sendData(port, isHost, data); + } + + Future writeString(String text) { + return write(utf8.encode(text)); + } +} diff --git a/lib/plugin.dart b/lib/plugin.dart new file mode 100644 index 0000000..15cfa2c --- /dev/null +++ b/lib/plugin.dart @@ -0,0 +1,129 @@ +/* + * This file is part of the flutter_p2p package. + * + * Copyright 2019 by Julian Finkler + * + * For the full copyright and license information, please read the LICENSE + * file that was distributed with this source code. + * + */ + +part of 'flutter_p2p.dart'; + +class FlutterP2p { + static const channelBase = "de.mintware.flutter_p2p"; + + static const _channel = const MethodChannel('$channelBase/flutter_p2p'); + + static WiFiDirectBroadcastReceiver wifiEvents = WiFiDirectBroadcastReceiver(); + static SocketMaster _socketMaster = SocketMaster(_channel); + + //region Permissions + + static Future isLocationPermissionGranted() async { + return await _channel.invokeMethod("isLocationPermissionGranted", {}); + } + + static Future requestLocationPermission() async { + return await _channel.invokeMethod("requestLocationPermission", {}); + } + + //endregion + + //region WiFi Event Subscription + + static Future register() async { + return await _channel.invokeMethod("register", {}); + } + + static Future unregister() async { + return await _channel.invokeMethod("unregister", {}); + } + + //endregion + + //region Discover + + static Future discoverDevices() async { + return await _channel.invokeMethod("discover", {}); + } + + static Future stopDiscoverDevices() async { + return await _channel.invokeMethod("stopDiscover", {}); + } + + //endregion + + //region Connection + + static Future connect(WifiP2pDevice device) async { + return await _channel + .invokeMethod("connect", {"payload": device.writeToBuffer()}); + } + + static Future cancelConnect(WifiP2pDevice device) async { + return await _channel.invokeMethod("cancelConnect", {}); + } + + //endregion + + //region Host Advertising + + static Future openHostPort(int port) async { + await _channel.invokeMethod("openHostPort", {"port": port}); + return _socketMaster.registerSocket(port, true); + } + + static Future closeHostPort(int port) async { + await _channel.invokeMethod("closeHostPort", {"port": port}); + return _socketMaster.unregisterServerPort(port); + } + + static Future acceptPort(int port) async { + return await _channel.invokeMethod("acceptPort", {"port": port}); + } + + //endregion + + //region Client Connection + + static Future connectToHost( + String address, + int port, { + int timeout = 500, + }) async { + if (await _channel.invokeMethod("connectToHost", { + "address": address, + "port": port, + "timeout": timeout, + })) { + return _socketMaster.registerSocket(port, false); + } + return null; + } + + static Future disconnectFromHost(int port) async { + return await _channel.invokeMethod("disconnectFromHost", { + "port": port, + }); + } + + //endregion + + //region Data Transfer + + static Future sendData(int port, bool isHost, Uint8List data) async { + var req = SocketMessage.create(); + req.port = port; + req.data = data; + req.dataAvailable = 0; + + var action = isHost ? "sendDataToClient" : "sendDataToHost"; + return await _channel.invokeMethod(action, { + "payload": req.writeToBuffer(), + }); + } + +// endregion + +} diff --git a/lib/socket_master.dart b/lib/socket_master.dart new file mode 100644 index 0000000..cedbef2 --- /dev/null +++ b/lib/socket_master.dart @@ -0,0 +1,55 @@ +/* + * This file is part of the flutter_p2p package. + * + * Copyright 2019 by Julian Finkler + * + * For the full copyright and license information, please read the LICENSE + * file that was distributed with this source code. + * + */ + +part of 'flutter_p2p.dart'; + +class SocketMaster { + static const _channelBase = FlutterP2p.channelBase; + + var _socketReadChannel = EventChannel("$_channelBase/socket/read"); + final MethodChannel _methodChannel; + + Map sockets = {}; + + Stream _readStream; + + SocketMaster(this._methodChannel) { + _readStream = _socketReadChannel.receiveBroadcastStream().map((a) { + try { + return SocketMessage.fromBuffer(a); + } catch (e) { + print(e); + throw e; + } + }); + } + + P2pSocket registerSocket(int port, bool isHost) { + if (sockets[port] == null) { + sockets[port] = P2pSocket( + port, + isHost, + _readStream.where((s) { + return s.port == port; + }), + ); + } + + return sockets[port]; + } + + unregisterServerPort(int port) { + if (sockets[port] == null) { + throw Exception("The port $port is not registered."); + } + + sockets.remove(port); + } +} diff --git a/protos/protos.proto b/protos/protos.proto new file mode 100644 index 0000000..d3bbed8 --- /dev/null +++ b/protos/protos.proto @@ -0,0 +1,84 @@ +// protoc --dart_out=./lib/gen ./protos/protos.proto +// protoc --swift_out=./ios/gen ./protos/protos.proto + +syntax = "proto3"; + +// `java_package` should match the package name you declare for `androidPackage` in your pubspec.yaml +option java_package = "de.mintware.flutter_p2p"; + +message StateChange { + bool isEnabled = 1; +} + +message WifiP2pDevice { + bool wpsPbcSupported = 1; + bool wpsKeypadSupported = 2; + bool wpsDisplaySupported = 3; + bool isServiceDiscoveryCapable = 4; + bool isGroupOwner = 5; + + string deviceName = 6; + string deviceAddress = 7; + string primaryDeviceType = 8; + string secondaryDeviceType = 9; + + Status status = 10; + + enum Status { + CONNECTED = 0; + INVITED = 1; + FAILED = 2; + AVAILABLE = 3; + UNAVAILABLE = 4; + } +} + +message WifiP2pDeviceList { + repeated WifiP2pDevice devices = 1; +} + +message ConnectionChange { + WifiP2pInfo wifiP2pInfo = 1; + NetworkInfo networkInfo = 2; +} + +message WifiP2pInfo { + bool groupFormed = 1; + bool isGroupOwner = 2; + string groupOwnerAddress = 3; +} + +message NetworkInfo { + + int32 subType = 1; + bool isConnected = 2; + DetailedState detailedState = 3; + string extraInfo = 4; + + enum DetailedState { + IDLE = 0; + SCANNING = 1; + CONNECTING = 2; + AUTHENTICATING = 3; + OBTAINING_IPADDR = 4; + CONNECTED = 5; + SUSPENDED = 6; + DISCONNECTING = 7; + DISCONNECTED = 8; + FAILED = 9; + BLOCKED = 10; + VERIFYING_POOR_LINK = 11; + CAPTIVE_PORTAL_CHECK = 12; + } +} + +message RequestPermissionResult { + int32 requestCode = 1; + repeated string grantedPermissions = 2; +} + +message SocketMessage { + int32 port = 1; + int32 dataAvailable = 2; // The number of bytes which are still available + bytes data = 3; // The data +} \ No newline at end of file diff --git a/pubspec.lock b/pubspec.lock new file mode 100644 index 0000000..74e1474 --- /dev/null +++ b/pubspec.lock @@ -0,0 +1,153 @@ +# Generated by pub +# See https://dart.dev/tools/pub/glossary#lockfile +packages: + async: + dependency: transitive + description: + name: async + url: "https://pub.dartlang.org" + source: hosted + version: "2.3.0" + boolean_selector: + dependency: transitive + description: + name: boolean_selector + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.5" + charcode: + dependency: transitive + description: + name: charcode + url: "https://pub.dartlang.org" + source: hosted + version: "1.1.2" + collection: + dependency: transitive + description: + name: collection + url: "https://pub.dartlang.org" + source: hosted + version: "1.14.11" + fixnum: + dependency: transitive + description: + name: fixnum + url: "https://pub.dartlang.org" + source: hosted + version: "0.10.9" + flutter: + dependency: "direct main" + description: flutter + source: sdk + version: "0.0.0" + flutter_test: + dependency: "direct dev" + description: flutter + source: sdk + version: "0.0.0" + matcher: + dependency: transitive + description: + name: matcher + url: "https://pub.dartlang.org" + source: hosted + version: "0.12.5" + meta: + dependency: transitive + description: + name: meta + url: "https://pub.dartlang.org" + source: hosted + version: "1.1.7" + path: + dependency: transitive + description: + name: path + url: "https://pub.dartlang.org" + source: hosted + version: "1.6.4" + pedantic: + dependency: transitive + description: + name: pedantic + url: "https://pub.dartlang.org" + source: hosted + version: "1.8.0+1" + protobuf: + dependency: "direct main" + description: + name: protobuf + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.0" + quiver: + dependency: transitive + description: + name: quiver + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.5" + sky_engine: + dependency: transitive + description: flutter + source: sdk + version: "0.0.99" + source_span: + dependency: transitive + description: + name: source_span + url: "https://pub.dartlang.org" + source: hosted + version: "1.5.5" + stack_trace: + dependency: transitive + description: + name: stack_trace + url: "https://pub.dartlang.org" + source: hosted + version: "1.9.3" + stream_channel: + dependency: transitive + description: + name: stream_channel + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.0" + string_scanner: + dependency: transitive + description: + name: string_scanner + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.5" + term_glyph: + dependency: transitive + description: + name: term_glyph + url: "https://pub.dartlang.org" + source: hosted + version: "1.1.0" + test_api: + dependency: transitive + description: + name: test_api + url: "https://pub.dartlang.org" + source: hosted + version: "0.2.5" + typed_data: + dependency: transitive + description: + name: typed_data + url: "https://pub.dartlang.org" + source: hosted + version: "1.1.6" + vector_math: + dependency: transitive + description: + name: vector_math + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.8" +sdks: + dart: ">=2.2.2 <3.0.0" diff --git a/pubspec.yaml b/pubspec.yaml new file mode 100644 index 0000000..ee70dd3 --- /dev/null +++ b/pubspec.yaml @@ -0,0 +1,28 @@ +name: flutter_p2p +description: A WiFi Direct Plugin for Flutter +version: 0.0.1 +author: Julian Finkler +homepage: https://www.mintware.de +repository: https://github.com/mintware-de/flutter_p2p +issue_tracker: https://github.com/mintware-de/flutter_p2p/issues +documentation: https://github.com/mintware-de/flutter_p2p/wiki + +environment: + sdk: ">=2.1.0 <3.0.0" + +dependencies: + protobuf: ^1.0.0 + flutter: + sdk: flutter + +dev_dependencies: + flutter_test: + sdk: flutter + +# For information on the generic Dart part of this file, see the +# following page: https://dart.dev/tools/pub/pubspec + +flutter: + plugin: + androidPackage: de.mintware.flutter_p2p + pluginClass: FlutterP2pPlugin \ No newline at end of file diff --git a/test/flutter_p2p_test.dart b/test/flutter_p2p_test.dart new file mode 100644 index 0000000..e839bcd --- /dev/null +++ b/test/flutter_p2p_test.dart @@ -0,0 +1,27 @@ +/* + * This file is part of the flutter_p2p package. + * + * Copyright 2019 by Julian Finkler + * + * For the full copyright and license information, please read the LICENSE + * file that was distributed with this source code. + * + */ + +void main() { +// const MethodChannel channel = MethodChannel('flutter_p2p'); +// +// setUp(() { +// channel.setMockMethodCallHandler((MethodCall methodCall) async { +// return '42'; +// }); +// }); +// +// tearDown(() { +// channel.setMockMethodCallHandler(null); +// }); +// +// test('getPlatformVersion', () async { +// expect(await FlutterP2p.platformVersion, '42'); +// }); +}