1.2.3
This commit is contained in:
129
lib/src/ambient_widget.dart
Normal file
129
lib/src/ambient_widget.dart
Normal file
@@ -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<StatefulWidget> createState() => _AmbientModeState();
|
||||
}
|
||||
|
||||
class _AmbientModeState extends State<AmbientMode> 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;
|
||||
}
|
||||
}
|
||||
94
lib/src/shape_widget.dart
Normal file
94
lib/src/shape_widget.dart
Normal file
@@ -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<StatefulWidget> createState() => _WatchShapeState();
|
||||
}
|
||||
|
||||
class _WatchShapeState extends State<WatchShape> {
|
||||
// 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;
|
||||
}
|
||||
152
lib/src/wear.dart
Normal file
152
lib/src/wear.dart
Normal file
@@ -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 = <AmbientCallback>[];
|
||||
|
||||
/// 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<dynamic> _onMethodCallHandler(MethodCall call) async {
|
||||
switch (call.method) {
|
||||
case 'onEnterAmbient':
|
||||
final args = (call.arguments as Map).cast<String, bool>();
|
||||
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<AmbientCallback>.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<String> getShape() async {
|
||||
try {
|
||||
return (await _channel.invokeMethod<String>('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<bool> isAmbient() async {
|
||||
try {
|
||||
return (await _channel.invokeMethod<bool>('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<void> setAutoResumeEnabled(bool enabled) async {
|
||||
try {
|
||||
await _channel.invokeMethod<String>(
|
||||
'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<void> setAmbientOffloadEnabled(bool enabled) async {
|
||||
try {
|
||||
await _channel.invokeMethod<String>(
|
||||
'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() {}
|
||||
}
|
||||
3
lib/wear_plus.dart
Normal file
3
lib/wear_plus.dart
Normal file
@@ -0,0 +1,3 @@
|
||||
export 'src/ambient_widget.dart';
|
||||
export 'src/shape_widget.dart';
|
||||
export 'src/wear.dart';
|
||||
Reference in New Issue
Block a user