From 3f5f894e86d6fe6e031221ed8abd795120a65589 Mon Sep 17 00:00:00 2001
From: 4831c0 <4831c0@proton.me>
Date: Fri, 4 Apr 2025 17:04:55 +0200
Subject: [PATCH] 1.2.3
---
CHANGELOG.md | 49 ++++++
LICENSE | 27 ++++
README.md | 51 ++++++
analysis_options.yaml | 1 +
android/build.gradle | 56 +++++++
android/gradle.properties | 2 +
.../gradle/wrapper/gradle-wrapper.properties | 5 +
android/proguard-rules.pro | 3 +
android/settings.gradle | 1 +
android/src/main/AndroidManifest.xml | 11 ++
.../kotlin/dev/rexios/wear_plus/WearPlugin.kt | 147 +++++++++++++++++
lib/src/ambient_widget.dart | 129 +++++++++++++++
lib/src/shape_widget.dart | 94 +++++++++++
lib/src/wear.dart | 152 ++++++++++++++++++
lib/wear_plus.dart | 3 +
pubspec.yaml | 22 +++
16 files changed, 753 insertions(+)
create mode 100644 CHANGELOG.md
create mode 100644 LICENSE
create mode 100644 README.md
create mode 100644 analysis_options.yaml
create mode 100644 android/build.gradle
create mode 100644 android/gradle.properties
create mode 100644 android/gradle/wrapper/gradle-wrapper.properties
create mode 100644 android/proguard-rules.pro
create mode 100644 android/settings.gradle
create mode 100644 android/src/main/AndroidManifest.xml
create mode 100644 android/src/main/kotlin/dev/rexios/wear_plus/WearPlugin.kt
create mode 100644 lib/src/ambient_widget.dart
create mode 100644 lib/src/shape_widget.dart
create mode 100644 lib/src/wear.dart
create mode 100644 lib/wear_plus.dart
create mode 100644 pubspec.yaml
diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
index 0000000..6076988
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,49 @@
+# 1.2.3
+
+- Replaces deprecated native lifecycle observer methods (by [@Crdzbird](https://github.com/Crdzbird) in [#6](https://github.com/Rexios80/wear_plus/pull/6))
+
+# 1.2.2
+
+- Fixes build issue
+
+# 1.2.1
+
+- Updates proguard rules for R8
+
+# 1.2.0
+
+- First release of `wear_plus`. Replace any references to the `wear` plugin with `wear_plus` to switch.
+- `InheritedShape` is now private. This would be a breaking change if this wasn't the first release of a new plugin.
+
+# 1.1.0
+
+- Fix issue with non-windows builds breaking.
+
+# 1.0.0
+
+- Null Safety Migration (Finally!)
+ - Thanks to Rexios and Peter Ullrich.
+ - Min Dart 2.12 / Flutter 2.5
+- Updated native component versions:
+ - Gradle 6.5
+ - Android Gradle Plugin 4.1.0
+ - Android compileSdkVersion 31
+ - AndroidX Wear 1.2.0
+ - Kotlin 1.5.10
+ - Removed `jcenter()` repo requirement.
+
+# 0.1.1
+
+- Fix Kotlin/Android compileOnly dep on com.google.android.wearable:wearable.
+
+# 0.1.0
+
+- Updated to AndroidX and Android embedding v2.
+- Renamed `Shape` is now `WearShape`.
+- Renamed `Mode` is now `WearMode`.
+- Deprecated `InheritedShape`.
+ _Add `WatchShape` instead and use `WatchShape.of(context)` to get the shape value._
+
+# 0.0.1
+
+- Initial release
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..7d72d53
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,27 @@
+Copyright 2018 The Chromium Authors. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above
+ copyright notice, this list of conditions and the following
+ disclaimer in the documentation and/or other materials provided
+ with the distribution.
+ * Neither the name of Google Inc. nor the names of its
+ contributors may be used to endorse or promote products derived
+ from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
\ No newline at end of file
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..2f80487
--- /dev/null
+++ b/README.md
@@ -0,0 +1,51 @@
+# Flutter Wear Plugin
+
+A plugin that offers Flutter support for Wear OS by Google (Android Wear).
+
+__To use this plugin you must set your `minSdkVersion` to `23`.__
+
+
+# Tutorial
+
+https://medium.com/flutter-community/flutter-building-wearos-app-fedf0f06d1b4
+
+
+# Widgets
+
+There currently three widgets provided by the plugin:
+
+* WatchShape: determines whether the watch is square or round.
+* AmbientMode: builder that provides what mode the watch is in. The widget will rebuild whenever the watch changes mode.
+
+
+## Setup
+
+If you are creating a standalone watch app, add the following to your manifest:
+
+```xml
+
+
+
+```
+
+## Example
+
+```dart
+class WatchScreen extends StatelessWidget {
+ @override
+ Widget build(BuildContext context) {
+ return WatchShape(
+ builder: (BuildContext context, WearShape shape, Widget? child) {
+ return AmbientMode(
+ builder: (context, mode, child) {
+ return mode == Mode.active ? ActiveWatchFace() : AmbientWatchFace();
+ },
+ );
+ },
+ );
+ }
+}
+```
diff --git a/analysis_options.yaml b/analysis_options.yaml
new file mode 100644
index 0000000..5f6db13
--- /dev/null
+++ b/analysis_options.yaml
@@ -0,0 +1 @@
+include: package:rexios_lints/flutter/package_extra.yaml
\ No newline at end of file
diff --git a/android/build.gradle b/android/build.gradle
new file mode 100644
index 0000000..3a2d6c7
--- /dev/null
+++ b/android/build.gradle
@@ -0,0 +1,56 @@
+group 'dev.rexios.wear_plus'
+version '1.0-SNAPSHOT'
+
+buildscript {
+ ext.kotlin_version = '1.7.20'
+ repositories {
+ google()
+ mavenCentral()
+ }
+
+ dependencies {
+ classpath 'com.android.tools.build:gradle:7.3.1'
+ classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
+ }
+}
+
+rootProject.allprojects {
+ repositories {
+ google()
+ mavenCentral()
+ }
+}
+
+apply plugin: 'com.android.library'
+apply plugin: 'kotlin-android'
+//apply plugin: 'kotlin-android-extensions'
+
+android {
+ compileSdkVersion 31
+
+ sourceSets {
+ main.java.srcDirs += 'src/main/kotlin'
+ }
+ defaultConfig {
+ minSdkVersion 23
+ consumerProguardFiles 'proguard-rules.pro'
+ }
+ lintOptions {
+ disable 'InvalidPackage'
+ }
+ compileOptions {
+ sourceCompatibility JavaVersion.VERSION_11
+ targetCompatibility JavaVersion.VERSION_11
+ }
+ kotlinOptions {
+ jvmTarget = '11'
+ }
+ namespace "dev.rexios.wear_plus"
+}
+
+dependencies {
+ implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
+ implementation 'androidx.wear:wear:1.3.0'
+ implementation 'com.google.android.support:wearable:2.9.0'
+ compileOnly 'com.google.android.wearable:wearable:2.9.0'
+}
diff --git a/android/gradle.properties b/android/gradle.properties
new file mode 100644
index 0000000..646c51b
--- /dev/null
+++ b/android/gradle.properties
@@ -0,0 +1,2 @@
+android.useAndroidX=true
+android.enableJetifier=true
diff --git a/android/gradle/wrapper/gradle-wrapper.properties b/android/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..3c46198
--- /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-6.5-all.zip
diff --git a/android/proguard-rules.pro b/android/proguard-rules.pro
new file mode 100644
index 0000000..72bff7b
--- /dev/null
+++ b/android/proguard-rules.pro
@@ -0,0 +1,3 @@
+-keep class dev.rexios.wear_plus.** { *; }
+-dontwarn com.google.android.wearable.compat.WearableActivityController$AmbientCallback
+-dontwarn com.google.android.wearable.compat.WearableActivityController
\ No newline at end of file
diff --git a/android/settings.gradle b/android/settings.gradle
new file mode 100644
index 0000000..4adff7b
--- /dev/null
+++ b/android/settings.gradle
@@ -0,0 +1 @@
+rootProject.name = 'wear'
diff --git a/android/src/main/AndroidManifest.xml b/android/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..f885959
--- /dev/null
+++ b/android/src/main/AndroidManifest.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+
+
diff --git a/android/src/main/kotlin/dev/rexios/wear_plus/WearPlugin.kt b/android/src/main/kotlin/dev/rexios/wear_plus/WearPlugin.kt
new file mode 100644
index 0000000..1937d61
--- /dev/null
+++ b/android/src/main/kotlin/dev/rexios/wear_plus/WearPlugin.kt
@@ -0,0 +1,147 @@
+package dev.rexios.wear_plus
+
+import android.os.Bundle
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.LifecycleEventObserver
+import androidx.lifecycle.LifecycleOwner
+import com.google.android.wearable.compat.WearableActivityController
+import io.flutter.embedding.engine.plugins.FlutterPlugin
+import io.flutter.embedding.engine.plugins.activity.ActivityAware
+import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding
+import io.flutter.embedding.engine.plugins.lifecycle.HiddenLifecycleReference
+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
+
+
+class WearPlugin : FlutterPlugin, ActivityAware, MethodCallHandler, LifecycleEventObserver {
+ private var mAmbientCallback = WearableAmbientCallback()
+ private var mMethodChannel: MethodChannel? = null
+ private var mActivityBinding: ActivityPluginBinding? = null
+ private var mAmbientController: WearableActivityController? = null
+
+ companion object {
+ const val TAG = "WearPlugin"
+ const val BURN_IN_PROTECTION = WearableActivityController.EXTRA_BURN_IN_PROTECTION
+ const val LOW_BIT_AMBIENT = WearableActivityController.EXTRA_LOWBIT_AMBIENT
+ }
+
+ override fun onAttachedToEngine(binding: FlutterPlugin.FlutterPluginBinding) {
+ mMethodChannel = MethodChannel(binding.binaryMessenger, "wear")
+ mMethodChannel!!.setMethodCallHandler(this)
+ }
+
+ override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) {
+ mMethodChannel?.setMethodCallHandler(this)
+ mMethodChannel = null
+ }
+
+ override fun onAttachedToActivity(binding: ActivityPluginBinding) {
+ attachAmbientController(binding)
+ }
+
+ override fun onDetachedFromActivityForConfigChanges() {
+ detachAmbientController()
+ }
+
+ override fun onReattachedToActivityForConfigChanges(binding: ActivityPluginBinding) {
+ attachAmbientController(binding)
+ }
+
+ override fun onDetachedFromActivity() {
+ detachAmbientController()
+ }
+
+ private fun attachAmbientController(binding: ActivityPluginBinding) {
+ mActivityBinding = binding
+ mAmbientController = WearableActivityController(TAG, binding.activity, mAmbientCallback)
+ mAmbientController?.setAmbientEnabled()
+ val reference = (binding.lifecycle as HiddenLifecycleReference)
+ reference.lifecycle.addObserver(this)
+ }
+
+ private fun detachAmbientController() {
+ mActivityBinding?.let {
+ val reference = (it.lifecycle as HiddenLifecycleReference)
+ reference.lifecycle.removeObserver(this)
+ }
+ mActivityBinding = null
+
+ }
+
+ override fun onMethodCall(call: MethodCall, result: Result) {
+ when (call.method) {
+ "getShape" -> {
+ val activity = mActivityBinding?.activity
+ when {
+ activity == null -> {
+ result.error("no-activity", "No android activity available.", null)
+ }
+ activity.resources.configuration.isScreenRound -> {
+ result.success("round")
+ }
+ else -> {
+ result.success("square")
+ }
+ }
+ }
+ "isAmbient" -> {
+ result.success(mAmbientController?.isAmbient ?: false)
+ }
+ "setAutoResumeEnabled" -> {
+ val enabled = call.argument("enabled")
+ if (mAmbientController == null || enabled == null) {
+ result.error("not-ready", "Ambient mode controller not ready", null)
+ } else {
+ mAmbientController!!.setAutoResumeEnabled(enabled)
+ result.success(null)
+ }
+ }
+ "setAmbientOffloadEnabled" -> {
+ val enabled = call.argument("enabled")
+ if (mAmbientController == null || enabled == null) {
+ result.error("not-ready", "Ambient mode controller not ready", null)
+ } else {
+ mAmbientController!!.setAmbientOffloadEnabled(enabled)
+ result.success(null)
+ }
+ }
+ else -> result.notImplemented()
+ }
+ }
+
+ inner class WearableAmbientCallback : WearableActivityController.AmbientCallback() {
+ override fun onEnterAmbient(ambientDetails: Bundle) {
+ val burnInProtection = ambientDetails.getBoolean(BURN_IN_PROTECTION, false)
+ val lowBitAmbient = ambientDetails.getBoolean(LOW_BIT_AMBIENT, false)
+ mMethodChannel?.invokeMethod("onEnterAmbient", mapOf(
+ "burnInProtection" to burnInProtection,
+ "lowBitAmbient" to lowBitAmbient
+ ))
+ }
+
+ override fun onExitAmbient() {
+ mMethodChannel?.invokeMethod("onExitAmbient", null)
+ }
+
+ override fun onUpdateAmbient() {
+ mMethodChannel?.invokeMethod("onUpdateAmbient", null)
+ }
+
+ override fun onInvalidateAmbientOffload() {
+ mMethodChannel?.invokeMethod("onInvalidateAmbientOffload", null)
+ }
+ }
+
+ override fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event) {
+ when (event) {
+ Lifecycle.Event.ON_CREATE -> mAmbientController?.onCreate()
+ Lifecycle.Event.ON_RESUME -> mAmbientController?.onResume()
+ Lifecycle.Event.ON_PAUSE -> mAmbientController?.onPause()
+ Lifecycle.Event.ON_STOP -> mAmbientController?.onStop()
+ Lifecycle.Event.ON_DESTROY -> mAmbientController?.onDestroy()
+ else -> {}
+ }
+ }
+}
diff --git a/lib/src/ambient_widget.dart b/lib/src/ambient_widget.dart
new file mode 100644
index 0000000..5840619
--- /dev/null
+++ b/lib/src/ambient_widget.dart
@@ -0,0 +1,129 @@
+import 'package:flutter/material.dart';
+import 'package:wear_plus/src/wear.dart';
+
+/// Ambient modes for a Wear device
+enum WearMode {
+ /// The screen is active
+ active,
+
+ /// The screen is in ambient mode
+ ambient,
+}
+
+/// Builds a child for [AmbientMode]
+typedef AmbientModeWidgetBuilder = Widget Function(
+ BuildContext context,
+ WearMode mode,
+ Widget? child,
+);
+
+/// Widget that listens for when a Wear device enters full power or ambient mode,
+/// and provides this in a builder. It optionally takes an [onUpdate] function that's
+/// called every time the wear device triggers an ambient update request.
+@immutable
+class AmbientMode extends StatefulWidget {
+ /// Constructor
+ const AmbientMode({
+ super.key,
+ required this.builder,
+ this.child,
+ this.onUpdate,
+ });
+
+ /// Built when the mode changes
+ final AmbientModeWidgetBuilder builder;
+
+ /// Optional child that will not get rebuilt when the mode changes
+ final Widget? child;
+
+ /// Called each time the the wear device triggers an ambient update request.
+ final VoidCallback? onUpdate;
+
+ /// Get current [WearMode].
+ static WearMode wearModeOf(BuildContext context) {
+ return context
+ .dependOnInheritedWidgetOfExactType<_InheritedAmbientMode>()!
+ .mode;
+ }
+
+ /// Get current [AmbientDetails].
+ static AmbientDetails ambientDetailsOf(BuildContext context) {
+ return context
+ .dependOnInheritedWidgetOfExactType<_InheritedAmbientMode>()!
+ .details;
+ }
+
+ @override
+ State createState() => _AmbientModeState();
+}
+
+class _AmbientModeState extends State with AmbientCallback {
+ var _ambientMode = WearMode.active;
+ final _ambientDetails = const AmbientDetails(false, false);
+
+ @override
+ void initState() {
+ super.initState();
+ Wear.instance.registerAmbientCallback(this);
+ _initState();
+ }
+
+ void _initState() async {
+ final isAmbient = await Wear.instance.isAmbient();
+ _updateMode(isAmbient);
+ }
+
+ @override
+ void dispose() {
+ Wear.instance.unregisterAmbientCallback(this);
+ super.dispose();
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ return _InheritedAmbientMode(
+ mode: _ambientMode,
+ details: _ambientDetails,
+ child: Builder(
+ builder: (context) {
+ return widget.builder(context, _ambientMode, widget.child);
+ },
+ ),
+ );
+ }
+
+ void _updateMode(bool isAmbient) {
+ if (!mounted) return;
+ setState(
+ () => _ambientMode = isAmbient ? WearMode.ambient : WearMode.active,
+ );
+ }
+
+ @override
+ void onEnterAmbient(AmbientDetails ambientDetails) => _updateMode(true);
+
+ @override
+ void onExitAmbient() => _updateMode(false);
+
+ @override
+ void onUpdateAmbient() {
+ _updateMode(true);
+ widget.onUpdate?.call();
+ }
+}
+
+class _InheritedAmbientMode extends InheritedWidget {
+ const _InheritedAmbientMode({
+ required this.mode,
+ required this.details,
+ required super.child,
+ });
+
+ final WearMode mode;
+ final AmbientDetails details;
+
+ @override
+ bool updateShouldNotify(_InheritedAmbientMode old) {
+ return mode != old.mode || details != old.details;
+ }
+}
diff --git a/lib/src/shape_widget.dart b/lib/src/shape_widget.dart
new file mode 100644
index 0000000..f846655
--- /dev/null
+++ b/lib/src/shape_widget.dart
@@ -0,0 +1,94 @@
+import 'package:flutter/widgets.dart';
+import 'package:wear_plus/src/wear.dart';
+
+/// Shape of a Wear device
+enum WearShape {
+ /// The display is square
+ square,
+
+ /// The display is round
+ round,
+}
+
+/// Builds a child for a [WatchShape]
+typedef WatchShapeBuilder = Widget Function(
+ BuildContext context,
+ WearShape shape,
+ Widget? child,
+);
+
+/// Builder widget for watch shapes
+@immutable
+class WatchShape extends StatefulWidget {
+ /// Constructor
+ const WatchShape({
+ super.key,
+ required this.builder,
+ this.child,
+ });
+
+ /// Built when the shape changes
+ final WatchShapeBuilder builder;
+
+ /// Optional child that will not get rebuilt when the shape changes
+ final Widget? child;
+
+ /// Call [WatchShape.of(context)] to retrieve the shape further down
+ /// in the widget hierarchy.
+ static WearShape of(BuildContext context) {
+ return _InheritedShape.of(context).shape;
+ }
+
+ @override
+ State createState() => _WatchShapeState();
+}
+
+class _WatchShapeState extends State {
+ // Default to round until the platform returns the shape
+ // round being the most common form factor for WearOS
+ var _shape = WearShape.round;
+
+ @override
+ void initState() {
+ super.initState();
+ _initState();
+ }
+
+ void _initState() async {
+ final shape = await Wear.instance.getShape();
+ if (!mounted) return;
+ setState(
+ () => _shape = (shape == 'round' ? WearShape.round : WearShape.square),
+ );
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ return _InheritedShape(
+ shape: _shape,
+ child: Builder(
+ builder: (context) {
+ return widget.builder(context, _shape, widget.child);
+ },
+ ),
+ );
+ }
+}
+
+class _InheritedShape extends InheritedWidget {
+ /// Constructor
+ const _InheritedShape({
+ required this.shape,
+ required super.child,
+ });
+
+ final WearShape shape;
+
+ static _InheritedShape of(BuildContext context) {
+ return context.dependOnInheritedWidgetOfExactType<_InheritedShape>()!;
+ }
+
+ @override
+ bool updateShouldNotify(_InheritedShape oldWidget) =>
+ shape != oldWidget.shape;
+}
diff --git a/lib/src/wear.dart b/lib/src/wear.dart
new file mode 100644
index 0000000..7c71c7e
--- /dev/null
+++ b/lib/src/wear.dart
@@ -0,0 +1,152 @@
+import 'package:flutter/foundation.dart' show debugPrint;
+import 'package:flutter/services.dart'
+ show MethodChannel, MethodCall, PlatformException;
+
+/// Provides access to Wearable features
+class Wear {
+ static const _channel = MethodChannel('wear');
+
+ /// Get the [Wear] instance
+ factory Wear() => instance;
+
+ /// Access to the singleton instance
+ static final instance = Wear._();
+
+ Wear._() {
+ _channel.setMethodCallHandler(_onMethodCallHandler);
+ }
+
+ final _ambientCallbacks = [];
+
+ /// Register callback for ambient notifications
+ void registerAmbientCallback(AmbientCallback callback) {
+ _ambientCallbacks.add(callback);
+ }
+
+ /// Unregister callback for ambient notifications
+ void unregisterAmbientCallback(AmbientCallback callback) {
+ _ambientCallbacks.remove(callback);
+ }
+
+ Future _onMethodCallHandler(MethodCall call) async {
+ switch (call.method) {
+ case 'onEnterAmbient':
+ final args = (call.arguments as Map).cast();
+ final details =
+ AmbientDetails(args['burnInProtection']!, args['lowBitAmbient']!);
+ _notifyAmbientCallbacks((callback) => callback.onEnterAmbient(details));
+ case 'onExitAmbient':
+ _notifyAmbientCallbacks((callback) => callback.onExitAmbient());
+ case 'onUpdateAmbient':
+ _notifyAmbientCallbacks((callback) => callback.onUpdateAmbient());
+ case 'onInvalidateAmbientOffload':
+ _notifyAmbientCallbacks(
+ (callback) => callback.onInvalidateAmbientOffload(),
+ );
+ }
+ }
+
+ void _notifyAmbientCallbacks(Function(AmbientCallback callback) fn) {
+ final callbacks = List.from(_ambientCallbacks);
+ for (final callback in callbacks) {
+ try {
+ fn(callback);
+ } catch (e, st) {
+ debugPrint('Failed callback: $callback\n$e\n$st');
+ }
+ }
+ }
+
+ /// Fetches the shape of the watch face
+ Future getShape() async {
+ try {
+ return (await _channel.invokeMethod('getShape'))!;
+ } on PlatformException catch (e, st) {
+ // Default to round
+ debugPrint('Error calling getShape: $e\n$st');
+ return 'round';
+ }
+ }
+
+ /// Tells the application if we are currently in ambient mode
+ Future isAmbient() async {
+ try {
+ return (await _channel.invokeMethod('isAmbient'))!;
+ } on PlatformException catch (e, st) {
+ debugPrint('Error calling isAmbient: $e\n$st');
+ return false;
+ }
+ }
+
+ /// Sets whether this activity's task should be moved to the front when
+ /// the system exits ambient mode.
+ ///
+ /// If true, the activity's task may be moved to the front if it was the
+ /// last activity to be running when ambient started, depending on how
+ /// much time the system spent in ambient mode.
+ ///
+ Future setAutoResumeEnabled(bool enabled) async {
+ try {
+ await _channel.invokeMethod(
+ 'setAutoResumeEnabled',
+ {'enabled': enabled},
+ );
+ } on PlatformException catch (e, st) {
+ debugPrint('Error calling setAutoResumeEnabled: $e\n$st');
+ rethrow;
+ }
+ }
+
+ /// Sets whether this activity is currently in a state that supports ambient offload mode.
+ Future setAmbientOffloadEnabled(bool enabled) async {
+ try {
+ await _channel.invokeMethod(
+ 'setAmbientOffloadEnabled',
+ {'enabled': enabled},
+ );
+ } on PlatformException catch (e, st) {
+ debugPrint('Error calling setAmbientOffloadEnabled: $e\n$st');
+ rethrow;
+ }
+ }
+}
+
+/// Provides details of current ambient mode configuration.
+class AmbientDetails {
+ /// Constructor
+ const AmbientDetails(this.burnInProtection, this.lowBitAmbient);
+
+ /// Used to indicate whether burn-in protection is required.
+ ///
+ /// When this property is set to true, views must be shifted around
+ /// periodically in ambient mode. To ensure that content isn't
+ /// shifted off the screen, avoid placing content within 10 pixels
+ /// of the edge of the screen. Activities should also avoid solid
+ /// white areas to prevent pixel burn-in. Both of these
+ /// requirements only apply in ambient mode, and only when
+ /// this property is set to true.
+ final bool burnInProtection;
+
+ /// Used to indicate whether the device has low-bit ambient mode.
+ ///
+ /// When this property is set to true, the screen supports fewer bits
+ /// for each color in ambient mode. In this case, activities should
+ /// disable anti-aliasing in ambient mode.
+ final bool lowBitAmbient;
+}
+
+/// Callback to receive ambient mode state changes.
+mixin AmbientCallback {
+ /// Called when an activity is entering ambient mode.
+ void onEnterAmbient(AmbientDetails ambientDetails) {}
+
+ /// Called when an activity should exit ambient mode.
+ void onExitAmbient() {}
+
+ /// Called when the system is updating the display for ambient mode.
+ void onUpdateAmbient() {}
+
+ /// Called to inform an activity that whatever decomposition it has sent to
+ /// Sidekick is no longer valid and should be re-sent before enabling ambient offload.
+ void onInvalidateAmbientOffload() {}
+}
diff --git a/lib/wear_plus.dart b/lib/wear_plus.dart
new file mode 100644
index 0000000..0e5e1de
--- /dev/null
+++ b/lib/wear_plus.dart
@@ -0,0 +1,3 @@
+export 'src/ambient_widget.dart';
+export 'src/shape_widget.dart';
+export 'src/wear.dart';
diff --git a/pubspec.yaml b/pubspec.yaml
new file mode 100644
index 0000000..75461c3
--- /dev/null
+++ b/pubspec.yaml
@@ -0,0 +1,22 @@
+name: wear_plus
+description: An actively maintained plugin that offers Flutter support for Wear OS by Google
+version: 1.2.3
+homepage: https://github.com/Rexios80/wear_plus
+
+environment:
+ sdk: ^3.0.0
+ flutter: ">=2.5.0"
+
+dependencies:
+ flutter:
+ sdk: flutter
+
+dev_dependencies:
+ rexios_lints: ^10.1.0
+
+flutter:
+ plugin:
+ platforms:
+ android:
+ package: dev.rexios.wear_plus
+ pluginClass: WearPlugin