first commit

This commit is contained in:
ksenia312
2024-01-31 14:16:22 +01:00
commit 541fbeaaf3
132 changed files with 6463 additions and 0 deletions
+38
View File
@@ -0,0 +1,38 @@
.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/ephemeral/
/Flutter/flutter_export_environment.sh
View File
@@ -0,0 +1,35 @@
import Flutter
class ConnectedDeviceStreamHandler: NSObject, FlutterStreamHandler {
private var eventSink: FlutterEventSink?
private var timer : Timer?
func onListen(withArguments arguments: Any?, eventSink: @escaping FlutterEventSink) -> FlutterError? {
let deviceId = arguments as! String
self.eventSink = eventSink
startSendingUpdates(deviceId: deviceId)
return nil
}
func onCancel(withArguments arguments: Any?) -> FlutterError? {
stopSendingUpdates()
eventSink = nil
return nil
}
func startSendingUpdates(deviceId:String) {
self.timer = Timer.scheduledTimer(withTimeInterval: 2.0, repeats: true) { [weak self] _ in
guard let self = self else { return }
var result: String?
if let device = NearbyDevicesStore.instance.find(for: deviceId) {
result = device.toJsonString()
}
self.eventSink?(result)
}
}
func stopSendingUpdates() {
self.timer?.invalidate()
self.timer = nil
}
}
+15
View File
@@ -0,0 +1,15 @@
//
// Logger.swift
// nearby_service
//
// Created by Kseniia Nikitina on 26/1/24.
//
import Foundation
class Logger {
static func error(message: String) {
NSLog("NearbyServicePluginError -- %@", message)
}
}
+90
View File
@@ -0,0 +1,90 @@
import Foundation
import MultipeerConnectivity
let SERVICE_TYPE = "mp-connection"
let PEER_ID = "PEER-ID"
let DEVICE_NAME = "DEVICE-NAME"
class MyDeviceDataGenerator {
static func generate(name: String?) -> NearbyDevice {
return NearbyDevice(
peerID: ArchivedData.getPeerID(),
name: ArchivedData.getRequireName(from: name),
deviceType: UIDevice.current.model,
os: UIDevice.current.systemName,
osVersion: UIDevice.current.systemVersion
)
}
}
class ArchivedData {
static func getPeerID() -> MCPeerID {
if let archivedPeerID = getArchivedPeerID() {
return archivedPeerID
} else {
let peerID = MCPeerID(
displayName: UIDevice.current.name.replacingOccurrences(of: " ", with: "_")
+ "_"
+ String(Int.random(in: 1000..<50000))
)
savePeerID(for: peerID)
return peerID
}
}
static func getRequireName(from name: String?) -> String {
let archivedName = ArchivedData.getArchivedName()
if let newName = name {
if (archivedName != newName) {
ArchivedData.saveName(for: newName)
}
return newName
}
return archivedName ?? UIDevice.current.name
}
static func getArchivedPeerID() -> MCPeerID? {
guard let savedData = UserDefaults.standard.data(forKey: PEER_ID),
let unarchivedPeerID = try? NSKeyedUnarchiver.unarchivedObject(ofClass: MCPeerID.self, from: savedData)
else {
return nil
}
return unarchivedPeerID
}
static func savePeerID(for peerID: MCPeerID) {
do {
UserDefaults.standard.set(
try NSKeyedArchiver.archivedData(withRootObject: peerID, requiringSecureCoding: false),
forKey: PEER_ID
)
} catch let e {
Logger.error(message: e.localizedDescription)
}
}
static func getArchivedName() -> String? {
guard let savedData = UserDefaults.standard.data(forKey: DEVICE_NAME),
let unarchivedName = try? NSKeyedUnarchiver.unarchivedObject(ofClass: NSData.self, from: savedData)
else {
return nil
}
return String(data: unarchivedName as Data, encoding: .utf8)
}
static func saveName(for name: String) {
if let data = name.data(using: .utf8) {
do {
UserDefaults.standard.set(
try NSKeyedArchiver.archivedData(withRootObject: data, requiringSecureCoding: false),
forKey: DEVICE_NAME
)
} catch let e {
Logger.error(message: e.localizedDescription)
}
}
}
}
+86
View File
@@ -0,0 +1,86 @@
import Foundation
import MultipeerConnectivity
class NearbyDevice : NSObject {
var name: String
var peerID: MCPeerID
var deviceType: String?
var os: String?
var osVersion: String?
var session: NearbySession?
init(
peerID: MCPeerID,
name:String,
deviceType:String? = nil,
os: String? = nil,
osVersion: String? = nil
) {
self.name=name
self.peerID = peerID
self.deviceType=deviceType
self.os=os
self.osVersion=osVersion
}
func createSession(for peerID: MCPeerID) -> NearbySession {
self.session = NearbySession.create(peerID: peerID)
return session!
}
func deleteSession() {
self.session = nil
}
}
extension NearbyDevice {
static func fromDictionary(for dictionary: [String: String]?, with peerID: MCPeerID) -> NearbyDevice {
let device = NearbyDevice(
peerID: peerID,
name:(dictionary?["displayName"] as String?) ?? peerID.displayName,
deviceType: dictionary?["deviceType"] as String?,
os: dictionary?["os"] as String?,
osVersion: dictionary?["osVersion"] as String?
)
return device
}
func toDictionary() -> [String: String] {
var deviceDict: [String: String] = [
"displayName": self.name,
"id": self.peerID.displayName,
]
if let os = self.os {
deviceDict["os"] = os
}
if let deviceType = self.deviceType {
deviceDict["deviceType"] = deviceType
}
if let osVersion = self.osVersion {
deviceDict["osVersion"] = osVersion
}
if let session = self.session {
deviceDict["state"] = String(session.state.rawValue)
} else {
deviceDict["state"] = String(0)
}
return deviceDict
}
func toJsonString() -> String? {
var result: String?
do {
let jsonData = try JSONSerialization.data(withJSONObject: toDictionary())
if let jsonString = String(data: jsonData, encoding: .utf8) {
result = jsonString
}
} catch let error {
Logger.error(message: error.localizedDescription)
}
return result
}
}
+58
View File
@@ -0,0 +1,58 @@
//
// NearbyDevicesStore.swift
// nearby_service
//
// Created by Kseniia Nikitina on 16/1/24.
//
import Foundation
import MultipeerConnectivity
class NearbyDevicesStore : NSObject {
static let instance = NearbyDevicesStore()
private var devices : [NearbyDevice] = []
func getDevices() -> [NearbyDevice] {
return devices
}
func find(for deviceId: String) -> NearbyDevice? {
return devices.first { device in
return device.peerID.displayName == deviceId
}
}
func getDevicesToJsonString() -> String {
let devicesObject = devices.map { device in
return device.toDictionary()
}
do {
let jsonData = try JSONSerialization.data(withJSONObject: devicesObject)
if let jsonString = String(data: jsonData, encoding: .utf8) {
return jsonString
}
} catch {
return "[]"
}
return "[]"
}
func add(for peerID: MCPeerID, discoveryInfo: [String: String]? = nil) -> NearbyDevice? {
devices = devices.filter{$0.peerID.displayName != peerID.displayName}
let device = NearbyDevice.fromDictionary(for: discoveryInfo, with: peerID)
self.devices.append(device)
return device
}
func remove(for peerID: MCPeerID) {
self.devices = devices.filter{$0.peerID.displayName != peerID.displayName}
}
func clear() {
self.devices = []
}
}
+154
View File
@@ -0,0 +1,154 @@
import Foundation
import Flutter
import MultipeerConnectivity
class NearbyManager: NSObject {
var device: NearbyDevice!
var advertiser: MCNearbyServiceAdvertiser!
var browser: MCNearbyServiceBrowser!
var invitationHandlers: [String: ((Bool, MCSession?) -> Void)] = [:]
func initialize(for deviceName: String? = nil, result: @escaping FlutterResult) {
self.device = MyDeviceDataGenerator.generate(name: deviceName)
self.advertiser = MCNearbyServiceAdvertiser(
peer: self.device.peerID,
discoveryInfo: self.device.toDictionary(),
serviceType: SERVICE_TYPE
)
self.advertiser.delegate = self
self.browser = MCNearbyServiceBrowser(peer: self.device.peerID, serviceType: SERVICE_TYPE)
self.browser.delegate = self
result(true)
}
func getSavedDeviceName(result: @escaping FlutterResult) {
result(ArchivedData.getArchivedName())
}
func getCurrentDevice(result: @escaping FlutterResult) {
result(device.toJsonString())
}
func openServicesSettings(result: @escaping FlutterResult) {
if let url = URL(string:UIApplication.openSettingsURLString) {
if UIApplication.shared.canOpenURL(url) {
UIApplication.shared.open(url, options: [:], completionHandler: nil)
}
}
result(true)
}
func startAdvertising(result: @escaping FlutterResult) {
self.advertiser.startAdvertisingPeer()
result(true)
}
func startBrowsing(result: @escaping FlutterResult) {
self.browser.startBrowsingForPeers()
result(true)
}
func stopAdvertising(result: @escaping FlutterResult) {
self.advertiser.stopAdvertisingPeer()
NearbyDevicesStore.instance.clear()
result(true)
}
func stopBrowsing(result: @escaping FlutterResult) {
self.browser.stopBrowsingForPeers()
NearbyDevicesStore.instance.clear()
result(true)
}
func getPeers(result: @escaping FlutterResult) {
result(NearbyDevicesStore.instance.getDevicesToJsonString())
}
func invite(for deviceId: String, result: @escaping FlutterResult) {
do {
let device = NearbyDevicesStore.instance.find(for: deviceId)
if let requireDevice = device {
let nearbySession = requireDevice.createSession(for: self.device.peerID)
self.browser.invitePeer(
requireDevice.peerID,
to: nearbySession.session,
withContext: try JSONSerialization.data(withJSONObject:["displayName": self.device.name]),
timeout: 0
)
result(true)
}
} catch let error {
Logger.error(message: error.localizedDescription)
result(false)
}
}
func acceptInvite(for deviceId: String, result: @escaping FlutterResult) {
let device = NearbyDevicesStore.instance.find(for: deviceId)
if let requireDevice = device {
let nearbySession = requireDevice.createSession(for: self.device.peerID)
self.invitationHandlers[deviceId]?(true, nearbySession.session)
result(true)
}
}
func disconnect(for deviceId: String, result: @escaping FlutterResult) {
let device = NearbyDevicesStore.instance.find(for: deviceId)
device?.deleteSession()
result(true)
}
func send(for message: String, with receiverId: String, result: @escaping FlutterResult) {
let device = NearbyDevicesStore.instance.find(for: receiverId)
do {
if let requireDevice = device {
let data = [
"name": self.device.name,
"message": message
]
try requireDevice.session?.session?.send(
try JSONSerialization.data(withJSONObject: data),
toPeers: [requireDevice.peerID],
with: MCSessionSendDataMode.reliable
)
}
} catch let error {
Logger.error(message: error.localizedDescription)
}
result(true)
}
}
extension NearbyManager: MCNearbyServiceAdvertiserDelegate {
func advertiser(_ advertiser: MCNearbyServiceAdvertiser, didReceiveInvitationFromPeer peerID: MCPeerID, withContext context: Data?, invitationHandler: @escaping (Bool, MCSession?) -> Void
) {
var dict: Dictionary<String, String>?
do {
dict = try JSONSerialization.jsonObject(with: context ?? Data()) as? Dictionary<String, String>
} catch let error {
Logger.error(message: error.localizedDescription)
}
_ = NearbyDevicesStore.instance.add(for: peerID, discoveryInfo: dict)
self.invitationHandlers[peerID.displayName] = invitationHandler
}
}
extension NearbyManager: MCNearbyServiceBrowserDelegate {
func browser(_ browser: MCNearbyServiceBrowser, foundPeer peerID: MCPeerID, withDiscoveryInfo info: [String : String]?) {
_ = NearbyDevicesStore.instance.add(for: peerID, discoveryInfo: info)
}
func browser(_ browser: MCNearbyServiceBrowser, lostPeer peerID: MCPeerID) {
let device = NearbyDevicesStore.instance.find(for: peerID.displayName)
device?.deleteSession()
NearbyDevicesStore.instance.remove(for: peerID)
}
}
+59
View File
@@ -0,0 +1,59 @@
//
// NearbyMessageConverter.swift
// nearby_service
//
// Created by Kseniia Nikitina on 29/1/24.
//
import Foundation
import MultipeerConnectivity
class NearbyMessageConverter {
static func convert(userInfo: [AnyHashable : Any]?) -> String? {
if let data = getMessageData(userInfo: userInfo),
let peerID = getMessagePeerID(userInfo: userInfo) {
return createMessage(data: data, peerID: peerID)
}
return nil
}
static private func getMessageData(userInfo: [AnyHashable : Any]?) -> [String: String]? {
do {
if let data = userInfo?["data"] as? Data,
let dictionary = try JSONSerialization.jsonObject(with: data, options: []) as? [String: String] {
return dictionary
}
} catch let error {
Logger.error(message: error.localizedDescription)
}
return nil
}
static private func getMessagePeerID(userInfo: [AnyHashable:Any]?) -> MCPeerID? {
return userInfo?["from"] as? MCPeerID
}
static private func createMessage(data: [String: String], peerID: MCPeerID)-> String? {
do {
if let message = data["message"],
let name = data["name"] {
var result: String?
let jsonObject = [
"message": message,
"sender": ["id": peerID.displayName, "displayName": name],
] as [String : Any]
let jsonData = try JSONSerialization.data(withJSONObject: jsonObject)
if let jsonString = String(data: jsonData, encoding: .utf8) {
result = jsonString
}
return result
}
} catch let error {
Logger.error(message: error.localizedDescription)
}
return nil
}
}
@@ -0,0 +1,32 @@
import Flutter
class NearbyPeersStreamHandler: NSObject, FlutterStreamHandler {
private var eventSink: FlutterEventSink?
private var timer : Timer?
func onListen(withArguments arguments: Any?, eventSink: @escaping FlutterEventSink) -> FlutterError? {
self.eventSink = eventSink
startSendingUpdates()
return nil
}
func onCancel(withArguments arguments: Any?) -> FlutterError? {
stopSendingUpdates()
eventSink = nil
return nil
}
func startSendingUpdates() {
self.timer = Timer.scheduledTimer(withTimeInterval: 2.0, repeats: true) { [weak self] _ in
guard let self = self else { return }
let devicesList = NearbyDevicesStore.instance.getDevicesToJsonString()
self.eventSink?(devicesList)
}
}
func stopSendingUpdates() {
self.timer?.invalidate()
self.timer = nil
}
}
+137
View File
@@ -0,0 +1,137 @@
import Flutter
import MultipeerConnectivity
import UIKit
public class NearbyServicePlugin: NSObject, FlutterPlugin {
let manager: NearbyManager;
let channel: FlutterMethodChannel
init(manager: NearbyManager, channel: FlutterMethodChannel) {
self.manager = manager
self.channel = channel
}
public static func register(with registrar: FlutterPluginRegistrar) {
let channel = FlutterMethodChannel(
name: "nearby_service",
binaryMessenger: registrar.messenger()
)
let nearbyPeersChannel = FlutterEventChannel(
name: "nearby_service_peers",
binaryMessenger: registrar.messenger()
)
let connectedDeviceChannel = FlutterEventChannel(
name: "nearby_service_connected_device",
binaryMessenger: registrar.messenger()
)
let nearbyPeersStreamHandler = NearbyPeersStreamHandler()
nearbyPeersChannel.setStreamHandler(nearbyPeersStreamHandler)
let connectedDeviceStreamHandler = ConnectedDeviceStreamHandler()
connectedDeviceChannel.setStreamHandler(connectedDeviceStreamHandler)
let manager = NearbyManager()
let instance = NearbyServicePlugin(manager: manager, channel: channel)
registrar.addMethodCallDelegate(instance, channel: channel)
NotificationCenter.default.addObserver(
instance,
selector: #selector(messageReceived),
name: NearbySession.messageReceived,
object: nil
)
}
public func detachFromEngine(for registrar: FlutterPluginRegistrar) {
NearbyDevicesStore.instance.clear()
}
@objc func messageReceived(notification: Notification) {
DispatchQueue.main.async {
let result = NearbyMessageConverter.convert(userInfo: notification.userInfo)
self.channel.invokeMethod("invoke_nearby_service_message_received", arguments: result)
}
}
public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
switch call.method {
case "getPlatformVersion":
result("iOS " + UIDevice.current.systemVersion)
case "getPlatformModel":
result(UIDevice.current.name)
case "initialize":
if let deviceName: String = getArgument(for: "deviceName", call: call) {
manager.initialize(for: deviceName, result: result)
} else {
manager.initialize(for: nil, result: result)
}
case "getSavedDeviceName":
manager.getSavedDeviceName(result: result)
case "getCurrentDevice":
manager.getCurrentDevice(result: result)
case "openServicesSettings":
manager.openServicesSettings(result: result)
case "startAdvertising":
manager.startAdvertising(result: result)
case "startBrowsing":
manager.startBrowsing(result: result)
case "stopAdvertising":
manager.stopAdvertising(result: result)
case "stopBrowsing":
manager.stopBrowsing(result: result)
case "getPeers":
manager.getPeers(result: result)
case "invite":
if let deviceId: String = getArgument(for: "deviceId", call: call) {
manager.invite(for: deviceId, result: result)
} else {
result(false)
}
case "acceptInvite":
if let deviceId : String = getArgument(for: "deviceId", call: call) {
manager.acceptInvite(for: deviceId, result: result)
} else {
result(false)
}
case "disconnect":
if let deviceId: String = getArgument(for: "deviceId", call: call) {
manager.disconnect(for: deviceId, result: result)
} else {
result(false)
}
case "send":
if let message: String = getArgument(for: "message", call: call) {
if let receiver : Dictionary<String, AnyObject> = getArgument(for: "receiver", call: call),
let receiverId : String = receiver["id"] as? String
{
manager.send(for: message, with: receiverId, result: result)
} else {
result(false)
}
} else {
result(false)
}
default:
result(FlutterMethodNotImplemented)
}
}
private func getArgument<T>(for name: String, call: FlutterMethodCall) -> T? {
guard let data = call.arguments as? Dictionary<String, AnyObject> else {
return nil
}
guard let argument: T = data[name] as? T else {
return nil
}
return argument
}
}
+51
View File
@@ -0,0 +1,51 @@
//
// NearbySession.swift
// nearby_service
//
// Created by Kseniia Nikitina on 16/1/24.
//
import Foundation
import MultipeerConnectivity
class NearbySession: NSObject {
var session: MCSession!
var state: MCSessionState = MCSessionState.notConnected
private init(peerID: MCPeerID) {
self.session = MCSession(peer: peerID)
}
static let messageReceived = Notification.Name("NearbySessionReceivedMessage")
static func create(peerID: MCPeerID) -> NearbySession {
let instance = NearbySession(peerID: peerID)
instance.session.delegate = instance
return instance
}
}
extension NearbySession: MCSessionDelegate {
func session(_ session: MCSession, peer peerID: MCPeerID, didChange state: MCSessionState) {
self.state = state
}
func session(_ session: MCSession, didReceive data: Data, fromPeer peerID: MCPeerID) {
NotificationCenter.default.post(
name: NearbySession.messageReceived,
object: nil,
userInfo: ["from": peerID, "data": data]
)
}
func session(_ session: MCSession, didReceive stream: InputStream, withName streamName: String, fromPeer peerID: MCPeerID) {
}
func session(_ session: MCSession, didStartReceivingResourceWithName resourceName: String, fromPeer peerID: MCPeerID, with progress: Progress) {
}
func session(_ session: MCSession, didFinishReceivingResourceWithName resourceName: String, fromPeer peerID: MCPeerID, at localURL: URL?, withError error: Error?) {
}
}
+23
View File
@@ -0,0 +1,23 @@
#
# To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html.
# Run `pod lib lint nearby_service.podspec` to validate before publishing.
#
Pod::Spec.new do |s|
s.name = 'nearby_service'
s.version = '0.0.1'
s.summary = 'A new Flutter plugin project.'
s.description = <<-DESC
A new Flutter plugin project.
DESC
s.homepage = 'http://example.com'
s.license = { :file => '../LICENSE' }
s.author = { 'Your Company' => '[email protected]' }
s.source = { :path => '.' }
s.source_files = 'Classes/**/*'
s.dependency 'Flutter'
s.platform = :ios, '12.0'
# Flutter.framework does not contain a i386 slice.
s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES', 'EXCLUDED_ARCHS[sdk=iphonesimulator*]' => 'i386' }
s.swift_version = '5.0'
end