Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 14 additions & 3 deletions ios/Runner.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
A565D15527CEA6E600816E0B /* TableInterfaceController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A565D15427CEA6E600816E0B /* TableInterfaceController.swift */; };
A565D15727CF6C5F00816E0B /* ConnectivityService.swift in Sources */ = {isa = PBXBuildFile; fileRef = A565D15627CF6C5F00816E0B /* ConnectivityService.swift */; };
A565D15927CF6C8500816E0B /* TableDataPersistanceService.swift in Sources */ = {isa = PBXBuildFile; fileRef = A565D15827CF6C8500816E0B /* TableDataPersistanceService.swift */; };
A565D15C27D0BA1700816E0B /* WeatherService.swift in Sources */ = {isa = PBXBuildFile; fileRef = A565D15B27D0BA1700816E0B /* WeatherService.swift */; };
A565D15E27D0CE0000816E0B /* WeatherInterfaceController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A565D15D27D0CE0000816E0B /* WeatherInterfaceController.swift */; };
A597401D27CE9D790080003C /* TableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = A597401C27CE9D790080003C /* TableViewCell.swift */; };
A5C47DA427CCCC6400DBE1C2 /* Interface.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = A5C47DA227CCCC6400DBE1C2 /* Interface.storyboard */; };
A5C47DA627CCCC6600DBE1C2 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = A5C47DA527CCCC6600DBE1C2 /* Assets.xcassets */; };
Expand Down Expand Up @@ -97,6 +99,8 @@
A565D15427CEA6E600816E0B /* TableInterfaceController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TableInterfaceController.swift; sourceTree = "<group>"; };
A565D15627CF6C5F00816E0B /* ConnectivityService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConnectivityService.swift; sourceTree = "<group>"; };
A565D15827CF6C8500816E0B /* TableDataPersistanceService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TableDataPersistanceService.swift; sourceTree = "<group>"; };
A565D15B27D0BA1700816E0B /* WeatherService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WeatherService.swift; sourceTree = "<group>"; };
A565D15D27D0CE0000816E0B /* WeatherInterfaceController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WeatherInterfaceController.swift; sourceTree = "<group>"; };
A597401C27CE9D790080003C /* TableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TableViewCell.swift; sourceTree = "<group>"; };
A5C47DA027CCCC6400DBE1C2 /* watch.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = watch.app; sourceTree = BUILT_PRODUCTS_DIR; };
A5C47DA327CCCC6400DBE1C2 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Interface.storyboard; sourceTree = "<group>"; };
Expand Down Expand Up @@ -181,6 +185,7 @@
children = (
A565D15627CF6C5F00816E0B /* ConnectivityService.swift */,
A565D15827CF6C8500816E0B /* TableDataPersistanceService.swift */,
A565D15B27D0BA1700816E0B /* WeatherService.swift */,
);
path = Services;
sourceTree = "<group>";
Expand All @@ -200,6 +205,7 @@
A565D15A27CF6C9100816E0B /* Services */,
A565D15427CEA6E600816E0B /* TableInterfaceController.swift */,
A597401C27CE9D790080003C /* TableViewCell.swift */,
A565D15D27D0CE0000816E0B /* WeatherInterfaceController.swift */,
A5C47DB027CCCC6600DBE1C2 /* InterfaceController.swift */,
A5C47DB227CCCC6600DBE1C2 /* ExtensionDelegate.swift */,
A5C47DB427CCCC6600DBE1C2 /* NotificationController.swift */,
Expand Down Expand Up @@ -389,10 +395,12 @@
buildActionMask = 2147483647;
files = (
A565D15927CF6C8500816E0B /* TableDataPersistanceService.swift in Sources */,
A565D15C27D0BA1700816E0B /* WeatherService.swift in Sources */,
A565D15527CEA6E600816E0B /* TableInterfaceController.swift in Sources */,
A597401D27CE9D790080003C /* TableViewCell.swift in Sources */,
A565D15727CF6C5F00816E0B /* ConnectivityService.swift in Sources */,
A5C47DB527CCCC6600DBE1C2 /* NotificationController.swift in Sources */,
A565D15E27D0CE0000816E0B /* WeatherInterfaceController.swift in Sources */,
A5C47DB727CCCC6600DBE1C2 /* ComplicationController.swift in Sources */,
A5C47DB327CCCC6600DBE1C2 /* ExtensionDelegate.swift in Sources */,
A5C47DB127CCCC6600DBE1C2 /* InterfaceController.swift in Sources */,
Expand Down Expand Up @@ -807,8 +815,9 @@
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = "watch WatchKit Extension/Info.plist";
INFOPLIST_KEY_CFBundleDisplayName = "watch WatchKit Extension";
INFOPLIST_KEY_CLKComplicationPrincipalClass = watch_WatchKit_Extension.ComplicationController;
INFOPLIST_KEY_CLKComplicationPrincipalClass = "$(PRODUCT_MODULE_NAME).ComplicationController";
INFOPLIST_KEY_NSHumanReadableCopyright = "";
INFOPLIST_KEY_WKCompanionAppBundleIdentifier = com.nativeCommunication.demo.demo;
INFOPLIST_KEY_WKExtensionDelegateClassName = watch_WatchKit_Extension.ExtensionDelegate;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
Expand Down Expand Up @@ -849,8 +858,9 @@
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = "watch WatchKit Extension/Info.plist";
INFOPLIST_KEY_CFBundleDisplayName = "watch WatchKit Extension";
INFOPLIST_KEY_CLKComplicationPrincipalClass = watch_WatchKit_Extension.ComplicationController;
INFOPLIST_KEY_CLKComplicationPrincipalClass = "$(PRODUCT_MODULE_NAME).ComplicationController";
INFOPLIST_KEY_NSHumanReadableCopyright = "";
INFOPLIST_KEY_WKCompanionAppBundleIdentifier = com.nativeCommunication.demo.demo;
INFOPLIST_KEY_WKExtensionDelegateClassName = watch_WatchKit_Extension.ExtensionDelegate;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
Expand Down Expand Up @@ -888,8 +898,9 @@
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = "watch WatchKit Extension/Info.plist";
INFOPLIST_KEY_CFBundleDisplayName = "watch WatchKit Extension";
INFOPLIST_KEY_CLKComplicationPrincipalClass = watch_WatchKit_Extension.ComplicationController;
INFOPLIST_KEY_CLKComplicationPrincipalClass = "$(PRODUCT_MODULE_NAME).ComplicationController";
INFOPLIST_KEY_NSHumanReadableCopyright = "";
INFOPLIST_KEY_WKCompanionAppBundleIdentifier = com.nativeCommunication.demo.demo;
INFOPLIST_KEY_WKExtensionDelegateClassName = watch_WatchKit_Extension.ExtensionDelegate;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
Expand Down
116 changes: 51 additions & 65 deletions ios/Runner/AppDelegate.swift
Original file line number Diff line number Diff line change
@@ -1,80 +1,84 @@
import UIKit
import Flutter
import WatchConnectivity
import WatchConnectivity

@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {
var counter: Int = 0
var flutterEventSink: FlutterEventSink?
let wcSession = WCSession.default
var timer: Timer?

var methodChannel: FlutterMethodChannel?
let wcSession = WCSession.default

override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {

// Activate session with watch
activateSession()

// Initializing FlutterViewController, he is needed for the binary messenger
let controller : FlutterViewController = window?.rootViewController as! FlutterViewController
methodChannel = FlutterMethodChannel(name: "samples.flutter.dev/battery",
binaryMessenger: controller.binaryMessenger)
// Event channel - stream on Flutter side
let eventChannel = FlutterEventChannel(name: "samples.flutter.dev/counter", binaryMessenger: controller.binaryMessenger)
eventChannel.setStreamHandler(self)

// Activating session for watch
if( WCSession.isSupported()) {
wcSession.delegate = self
wcSession.activate()
} else {
print("Watch not supported")
}

// Incomming method invocations from Flutter side
methodChannel?.setMethodCallHandler({ [weak self]
(call: FlutterMethodCall, result: @escaping FlutterResult) -> Void in

// Check conditions for messaging
// Check conditions for ANY messaging
guard
let watchSession = self?.wcSession,
watchSession.activationState == .activated,
watchSession.isPaired == true,
watchSession.isWatchAppInstalled == true
else {
print("Conditions for messaging are not met.")
result(false)
return
}

switch call.method {
case "presentTableData":
// Get data from arguments of a call from Flutter
guard let tableData = call.arguments as? Array<String> else { return }
let watchData: [String: Any] = ["method": "presentTableData", "data": tableData]
print("Sedinging table data: \n", tableData)
// If reachable, go with live messaging, if not reachable update application context
if watchSession.isReachable == true {
//print("Watch app is reachable! Going live... ")
watchSession.sendMessage(watchData, replyHandler: nil, errorHandler: nil)
} else {
//print("Watch app is not reachable, updating context... ")
do {
try watchSession.updateApplicationContext(watchData)
} catch(_) {
//print("Error occurred while updating application context: ", error)
}
}
result(true)

case "incrementWatchCounter":
guard let methodData = call.arguments as? Int else {
result("false")
return
}

let watchData: [String: Any] = ["method": "incrementWatchCounter", "data": methodData]
print("Sedinging incrementWatchCounter: \n", watchData)
watchSession.sendMessage(watchData, replyHandler: nil, errorHandler: nil)
result(true)

case "getBatteryLevel":
self?.receiveBatteryLevel(result: result)

case "presentTableData":
// Get data from message
guard let tableData = call.arguments as? Array<String> else {
print("Table data is NOT a list of strings: ", call.arguments)
return
}
let watchData: [String: Any] = ["method": "presentTableData", "data": tableData]
// If reachable, go with live messaging, if not reachable update application context
if watchSession.isReachable == true {
print("Watch app is reachable! Going live... ")
watchSession.sendMessage(watchData, replyHandler: nil, errorHandler: nil)
} else {
print("Watch app is not reachable, updating context... ")
do {
try watchSession.updateApplicationContext(watchData)
} catch(let error) {
print("Error occurred while updating application context: ", error)
}
}
result(true)

default:
result(FlutterMethodNotImplemented)
}
Expand All @@ -96,36 +100,9 @@ import WatchConnectivity
result(Int(device.batteryLevel * 100))
}
}

func activateSession() {
if( WCSession.isSupported()) {
wcSession.delegate = self
wcSession.activate()
}
}


}

// MARK: - Flutter stream handler
extension AppDelegate: FlutterStreamHandler {
func onListen(withArguments arguments: Any?, eventSink events: @escaping FlutterEventSink) -> FlutterError? {
self.flutterEventSink = events
timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(onTimerUp), userInfo: nil, repeats: true)
return nil
}

func onCancel(withArguments arguments: Any?) -> FlutterError? {
timer?.invalidate()
flutterEventSink = nil
return nil
}

@objc func onTimerUp() {
counter += 1
flutterEventSink?(counter)
}
}
// MARK: - WCSessionDelegate methods -

extension AppDelegate: WCSessionDelegate {
func sessionDidBecomeInactive(_ session: WCSession) {
Expand All @@ -137,8 +114,6 @@ extension AppDelegate: WCSessionDelegate {
}

func session(_ session: WCSession, didReceiveMessage message: [String : Any]) {
print("Received message: ", message)

// Invoking method for to Flutter side, MUST BE ON MAIN THREAD!
DispatchQueue.main.async { [weak self] in
if
Expand All @@ -152,13 +127,24 @@ extension AppDelegate: WCSessionDelegate {
func session(_ session: WCSession, activationDidCompleteWith activationState: WCSessionActivationState, error: Error?) {
print("@session did complete with: acctivation state: ", activationState.rawValue)
}

func sessionWatchStateDidChange(_ session: WCSession) {
print("Watch state changed: ")
print(" Activation state: ", session.activationState)
print(" Is paired: ", session.isPaired)
print(" Is reachable: ", session.isReachable)
}

// MARK: - Flutter stream handler -
extension AppDelegate: FlutterStreamHandler {
func onListen(withArguments arguments: Any?, eventSink events: @escaping FlutterEventSink) -> FlutterError? {
self.flutterEventSink = events
timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(onTimerUp), userInfo: nil, repeats: true)
return nil
}

func onCancel(withArguments arguments: Any?) -> FlutterError? {
timer?.invalidate()
flutterEventSink = nil
return nil
}

@objc func onTimerUp() {
counter += 1
flutterEventSink?(counter)
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
{
"images" : [
{
"filename" : "ghost.png",
"idiom" : "watch",
"scale" : "2x"
},
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
{
"images" : [
{
"filename" : "ghost.png",
"idiom" : "watch",
"scale" : "2x"
},
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
47 changes: 39 additions & 8 deletions ios/watch WatchKit Extension/ComplicationController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,15 @@ import ClockKit

class ComplicationController: NSObject, CLKComplicationDataSource {

// MARK: - Properties -

private let tableDataPersistanceService = TableDataPersistanceService()

// MARK: - Complication Configuration

func getComplicationDescriptors(handler: @escaping ([CLKComplicationDescriptor]) -> Void) {
let descriptors = [
CLKComplicationDescriptor(identifier: "complication", displayName: "Runner", supportedFamilies: CLKComplicationFamily.allCases)
CLKComplicationDescriptor(identifier: "complication", displayName: "Runner", supportedFamilies: [CLKComplicationFamily.circularSmall, CLKComplicationFamily.graphicCircular, CLKComplicationFamily.graphicCorner])
// Multiple complication support can be added here with more descriptors
]

Expand All @@ -25,7 +29,7 @@ class ComplicationController: NSObject, CLKComplicationDataSource {
func handleSharedComplicationDescriptors(_ complicationDescriptors: [CLKComplicationDescriptor]) {
// Do any necessary work to support these newly shared complication descriptors
}

// MARK: - Timeline Configuration

func getTimelineEndDate(for complication: CLKComplication, withHandler handler: @escaping (Date?) -> Void) {
Expand All @@ -37,23 +41,50 @@ class ComplicationController: NSObject, CLKComplicationDataSource {
// Call the handler with your desired behavior when the device is locked
handler(.showOnLockScreen)
}

// MARK: - Timeline Population

func getCurrentTimelineEntry(for complication: CLKComplication, withHandler handler: @escaping (CLKComplicationTimelineEntry?) -> Void) {
// Call the handler with the current timeline entry
handler(nil)
if let template = getComplicationTemplate(for: complication, using: Date()) {
let entry = CLKComplicationTimelineEntry(date: Date(), complicationTemplate: template)
handler(entry)
} else {
handler(nil)
}
}

func getTimelineEntries(for complication: CLKComplication, after date: Date, limit: Int, withHandler handler: @escaping ([CLKComplicationTimelineEntry]?) -> Void) {
// Call the handler with the timeline entries after the given date
handler(nil)
}

// MARK: - Sample Templates

func getLocalizableSampleTemplate(for complication: CLKComplication, withHandler handler: @escaping (CLKComplicationTemplate?) -> Void) {
// This method will be called once per supported complication, and the results will be cached
handler(nil)
let template = getComplicationTemplate(for: complication, using: Date())
if let t = template {
handler(t)
} else {
handler(nil)
}
}
}


// MARK: - Private methods -
private extension ComplicationController {
func getComplicationTemplate(for complication: CLKComplication, using date: Date) -> CLKComplicationTemplate? {
let text = tableDataPersistanceService.getTableData().first ?? "n/a"
switch complication.family {
case .graphicCorner:
return CLKComplicationTemplateGraphicCornerGaugeText(gaugeProvider: CLKSimpleGaugeProvider(style: .fill, gaugeColor: .purple, fillFraction: 0.44), outerTextProvider: CLKSimpleTextProvider(text: text))
case .graphicCircular:
return CLKComplicationTemplateGraphicCircularStackText(line1TextProvider: CLKSimpleTextProvider(text: "\(text)1"), line2TextProvider: CLKSimpleTextProvider(text: "\(text)2"))
case .circularSmall:
return CLKComplicationTemplateCircularSmallSimpleText(textProvider: CLKSimpleTextProvider(text: text))
default:
return nil
}
}
}
Loading