diff --git a/core/Clash.Meta b/core/Clash.Meta index 7475a82..eaa9968 160000 --- a/core/Clash.Meta +++ b/core/Clash.Meta @@ -1 +1 @@ -Subproject commit 7475a82936c03816e0ccbd48c7f6be727300cc73 +Subproject commit eaa996819d8e7f262e539f5e424a59cd79ffdd57 diff --git a/core/common.go b/core/common.go index 088d89c..3c47a6a 100644 --- a/core/common.go +++ b/core/common.go @@ -46,6 +46,11 @@ type Process struct { Target string `json:"target"` } +type Now struct { + Name string `json:"name"` + Value string `json:"value"` +} + func restartExecutable(execPath string) { var err error executor.Shutdown() diff --git a/core/dart-bridge/message.go b/core/dart-bridge/message.go index 59f4328..4b3b8b6 100644 --- a/core/dart-bridge/message.go +++ b/core/dart-bridge/message.go @@ -10,6 +10,7 @@ const ( Log MessageType = "log" Tun MessageType = "tun" Delay MessageType = "delay" + Now MessageType = "now" Process MessageType = "process" ) diff --git a/core/hub.go b/core/hub.go index b25e8e3..ed74712 100644 --- a/core/hub.go +++ b/core/hub.go @@ -7,6 +7,7 @@ import ( "fmt" "github.com/metacubex/mihomo/adapter" "github.com/metacubex/mihomo/adapter/outboundgroup" + "github.com/metacubex/mihomo/adapter/provider" "github.com/metacubex/mihomo/common/utils" "github.com/metacubex/mihomo/config" "github.com/metacubex/mihomo/constant" @@ -243,8 +244,7 @@ func getProviders() *C.char { func getProvider(name *C.char) *C.char { providerName := C.GoString(name) providers := tunnel.Providers() - var provider = providers[providerName] - data, err := json.Marshal(provider) + data, err := json.Marshal(providers[providerName]) if err != nil { return C.CString("") } @@ -257,3 +257,29 @@ func initNativeApiBridge(api unsafe.Pointer, port C.longlong) { i := int64(port) bridge.Port = &i } + +func init() { + provider.HealthcheckHook = func(name string, delay uint16) { + delayData := &Delay{ + Name: name, + } + if delay == 0 { + delayData.Value = -1 + } else { + delayData.Value = int32(delay) + } + bridge.SendMessage(bridge.Message{ + Type: bridge.Delay, + Data: delayData, + }) + } + adapter.NowChangeHook = func(name, value string) { + bridge.SendMessage(bridge.Message{ + Type: bridge.Now, + Data: Now{ + Name: name, + Value: value, + }, + }) + } +} diff --git a/lib/clash/message.dart b/lib/clash/message.dart index 8bb014b..ed79e01 100644 --- a/lib/clash/message.dart +++ b/lib/clash/message.dart @@ -15,6 +15,8 @@ abstract mixin class ClashMessageListener { void onDelay(Delay delay) {} void onProcess(Metadata metadata) {} + + void onNow(Now now) {} } class ClashMessage { @@ -41,6 +43,9 @@ class ClashMessage { case MessageType.process: listener.onProcess(Metadata.fromJson(m.data)); break; + case MessageType.now: + listener.onNow(Now.fromJson(m.data)); + break; } } }); diff --git a/lib/enum/enum.dart b/lib/enum/enum.dart index 9430e2c..5ee2ce0 100644 --- a/lib/enum/enum.dart +++ b/lib/enum/enum.dart @@ -52,4 +52,4 @@ enum ProfileType { file, url } enum ResultType { success, error } -enum MessageType { log, tun, delay, process } +enum MessageType { log, tun, delay, process, now } diff --git a/lib/fragments/dashboard/network_detection.dart b/lib/fragments/dashboard/network_detection.dart index b17b212..4485993 100644 --- a/lib/fragments/dashboard/network_detection.dart +++ b/lib/fragments/dashboard/network_detection.dart @@ -1,6 +1,5 @@ import 'package:fl_clash/common/common.dart'; import 'package:fl_clash/models/models.dart'; -import 'package:fl_clash/state.dart'; import 'package:fl_clash/widgets/widgets.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; @@ -73,56 +72,6 @@ class _NetworkDetectionState extends State { ); } - _updateCurrentDelay( - String? currentProxyName, - int? delay, - bool isCurrent, - bool isInit, - ) { - if (!isCurrent || currentProxyName == null || !isInit) return; - WidgetsBinding.instance.addPostFrameCallback((timeStamp) { - if (delay == null) { - context.appController.setDelay( - Delay( - name: currentProxyName, - value: 0, - ), - ); - globalState.updateCurrentDelay( - currentProxyName, - ); - } - }); - } - - _updateCurrentDelayContainer(Widget child) { - return Selector3( - selector: (_, appState, config, clashConfig) { - final proxyName = appState.getCurrentProxyName( - config.currentProxyName, - clashConfig.mode, - ); - return UpdateCurrentDelaySelectorState( - isInit: appState.isInit, - currentProxyName: proxyName, - delay: appState.delayMap[proxyName], - isCurrent: appState.currentLabel == 'dashboard', - ); - }, - builder: (_, state, __) { - _updateCurrentDelay( - state.currentProxyName, - state.delay, - state.isCurrent, - state.isInit, - ); - return child; - }, - child: child, - ); - } - @override Widget build(BuildContext context) { return CommonCard( @@ -130,58 +79,59 @@ class _NetworkDetectionState extends State { iconData: Icons.network_check, label: appLocalizations.networkDetection, ), - child: _updateCurrentDelayContainer( - Selector3( - selector: (_, appState, config, clashConfig) { - final proxyName = appState.getCurrentProxyName( - config.currentProxyName, - clashConfig.mode, - ); - return NetworkDetectionSelectorState( - isInit: appState.isInit, - currentProxyName: proxyName, - delay: appState.delayMap[proxyName], - ); - }, - builder: (_, state, __) { - return Container( - padding: const EdgeInsets.all(16).copyWith(top: 0), - child: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Flexible( - flex: 0, - child: TooltipText( - text: Text( - state.currentProxyName ?? appLocalizations.noProxy, - overflow: TextOverflow.ellipsis, - maxLines: 1, - style: - Theme.of(context).textTheme.titleMedium?.toSoftBold(), + child: Selector3( + selector: (_, appState, config, clashConfig) { + final proxyName = appState.getCurrentProxyName( + config.currentProxyName, + clashConfig.mode, + ); + return NetworkDetectionSelectorState( + isInit: appState.isInit, + currentProxyName: proxyName, + delay: appState.getDelay( + proxyName, + ), + ); + }, + builder: (_, state, __) { + return Container( + padding: const EdgeInsets.all(16).copyWith(top: 0), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Flexible( + flex: 0, + child: TooltipText( + text: Text( + state.currentProxyName ?? appLocalizations.noProxy, + overflow: TextOverflow.ellipsis, + maxLines: 1, + style: + Theme.of(context).textTheme.titleMedium?.toSoftBold(), + ), + ), + ), + const SizedBox( + height: 8, + ), + Flexible( + child: Container( + height: context.appController.measure.titleLargeHeight, + alignment: Alignment.centerLeft, + child: FadeBox( + child: _buildDescription( + state.currentProxyName, + state.delay, ), ), ), - const SizedBox( - height: 8, - ), - Flexible( - child: Container( - height: context.appController.measure.titleLargeHeight, - alignment: Alignment.centerLeft, - child: FadeBox( - child: _buildDescription( - state.currentProxyName, - state.delay, - ), - ), - ), - ), - ], - ), - ); - }, - ), + ), + ], + ), + ); + }, ), ); } diff --git a/lib/fragments/proxies.dart b/lib/fragments/proxies.dart index 03e3b63..660c629 100644 --- a/lib/fragments/proxies.dart +++ b/lib/fragments/proxies.dart @@ -58,74 +58,76 @@ class _ProxiesFragmentState extends State @override Widget build(BuildContext context) { - return Selector( - selector: (_, appState) => appState.currentLabel == 'proxies', - builder: (_, isCurrent, child) { - if (isCurrent) { - _initActions(); - } - return child!; - }, - child: Selector3( - selector: (_, appState, config, clashConfig) { - final currentGroups = appState.getCurrentGroups(clashConfig.mode); - final currentProxyName = appState.getCurrentGroupNameWithGroups( - currentGroups, - config.currentGroupName, - clashConfig.mode, - ); - final currentIndex = currentGroups - .indexWhere((element) => element.name == currentProxyName); - return ProxiesSelectorState( - currentIndex: currentIndex != -1 ? currentIndex : 0, - groups: currentGroups, - ); - }, - builder: (_, state, __) { - if (_tabController != null) { - _tabController!.dispose(); - _tabController = null; + return DelayTestButtonContainer( + child: Selector( + selector: (_, appState) => appState.currentLabel == 'proxies', + builder: (_, isCurrent, child) { + if (isCurrent) { + _initActions(); } - _tabController = TabController( - length: state.groups.length, - vsync: this, - initialIndex: state.currentIndex, - ); - return Column( - mainAxisAlignment: MainAxisAlignment.start, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - TabBar( - controller: _tabController, - padding: const EdgeInsets.symmetric(horizontal: 16), - dividerColor: Colors.transparent, - isScrollable: true, - tabAlignment: TabAlignment.start, - overlayColor: - const MaterialStatePropertyAll(Colors.transparent), - tabs: [ - for (final group in state.groups) - Tab( - text: group.name, - ), - ], - ), - Expanded( - child: TabBarView( + return child!; + }, + child: Selector3( + selector: (_, appState, config, clashConfig) { + final currentGroups = appState.getCurrentGroups(clashConfig.mode); + final currentProxyName = appState.getCurrentGroupNameWithGroups( + currentGroups, + config.currentGroupName, + clashConfig.mode, + ); + final currentIndex = currentGroups + .indexWhere((element) => element.name == currentProxyName); + return ProxiesSelectorState( + currentIndex: currentIndex != -1 ? currentIndex : 0, + groups: currentGroups, + ); + }, + builder: (_, state, __) { + if (_tabController != null) { + _tabController!.dispose(); + _tabController = null; + } + _tabController = TabController( + length: state.groups.length, + vsync: this, + initialIndex: state.currentIndex, + ); + return Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + TabBar( controller: _tabController, - children: [ + padding: const EdgeInsets.symmetric(horizontal: 16), + dividerColor: Colors.transparent, + isScrollable: true, + tabAlignment: TabAlignment.start, + overlayColor: + const MaterialStatePropertyAll(Colors.transparent), + tabs: [ for (final group in state.groups) - KeepContainer( - child: ProxiesTabView( - group: group, - ), + Tab( + text: group.name, ), ], ), - ) - ], - ); - }, + Expanded( + child: TabBarView( + controller: _tabController, + children: [ + for (final group in state.groups) + KeepContainer( + child: ProxiesTabView( + group: group, + ), + ), + ], + ), + ) + ], + ); + }, + ), ), ); } @@ -143,56 +145,7 @@ class ProxiesTabView extends StatefulWidget { State createState() => _ProxiesTabViewState(); } -class _ProxiesTabViewState extends State - with SingleTickerProviderStateMixin { - var lock = false; - late AnimationController _controller; - late Animation _scale; - late Animation _opacity; - - @override - void initState() { - super.initState(); - _controller = AnimationController( - vsync: this, - duration: const Duration( - milliseconds: 200, - ), - ); - _scale = Tween( - begin: 1.0, - end: 0.8, - ).animate( - CurvedAnimation( - parent: _controller, - curve: const Interval( - 0.0, - 0.3, - curve: Curves.easeIn, - ), - ), - ); - _opacity = Tween( - begin: 1.0, - end: 0.0, - ).animate( - CurvedAnimation( - parent: _controller, - curve: const Interval( - 0, - 1, - curve: Curves.easeIn, - ), - ), - ); - } - - @override - void dispose() { - super.dispose(); - _controller.dispose(); - } - +class _ProxiesTabViewState extends State { Group get group => widget.group; get measure => context.appController.measure; @@ -235,29 +188,6 @@ class _ProxiesTabViewState extends State return proxies; } - _getDelayMap() async { - if (lock == true) return; - lock = true; - _controller.forward(); - for (final proxy in group.all) { - context.appController.setDelay( - Delay( - name: proxy.name, - value: 0, - ), - ); - clashCore.delay( - proxy.name, - ); - } - await Future.delayed( - appConstant.httpTimeoutDuration + appConstant.moreDuration, - ); - lock = false; - _controller.reverse(); - setState(() {}); - } - double _getItemHeight() { return 12 * 2 + measure.bodyMediumHeight * 2 + @@ -321,7 +251,7 @@ class _ProxiesTabViewState extends State SizedBox( height: measure.labelSmallHeight, child: Selector( - selector: (context, appState) => appState.delayMap[proxy.name], + selector: (context, appState) => appState.getDelay(proxy.name), builder: (_, delay, __) { return FadeBox( child: Builder( @@ -388,7 +318,7 @@ class _ProxiesTabViewState extends State builder: (_, proxy) { final isSelected = group.type == GroupType.Selector ? group.name == state.currentGroupName && - proxy.name == state.currentProxyName + proxy.name == state.currentProxyName : group.now == proxy.name; return _card( isSelected: isSelected, @@ -415,59 +345,148 @@ class _ProxiesTabViewState extends State return Selector( selector: (_, config) => config.proxiesSortType, builder: (_, proxiesSortType, __) { - return FloatLayout( - floatingWidget: FloatWrapper( - child: AnimatedBuilder( - animation: _controller, - builder: (_, __) { - return Transform.scale( - scale: _scale.value, - child: SizedBox( - width: 56, - height: 56, - child: Opacity( - opacity: _opacity.value, - child: FloatingActionButton( - heroTag: null, - onPressed: _getDelayMap, - child: const Icon(Icons.network_ping), - ), - ), - ), - ); - }, - ), - ), - child: Align( - alignment: Alignment.topCenter, - child: SlotLayout( - config: { - Breakpoints.small: SlotLayout.from( - key: const Key('proxies_grid_small'), - builder: (_) => _buildGrid( - proxiesSortType: proxiesSortType, - columns: 2, - ), + return Align( + alignment: Alignment.topCenter, + child: SlotLayout( + config: { + Breakpoints.small: SlotLayout.from( + key: const Key('proxies_grid_small'), + builder: (_) => _buildGrid( + proxiesSortType: proxiesSortType, + columns: 2, ), - Breakpoints.medium: SlotLayout.from( - key: const Key('proxies_grid_medium'), - builder: (_) => _buildGrid( - proxiesSortType: proxiesSortType, - columns: 3, - ), + ), + Breakpoints.medium: SlotLayout.from( + key: const Key('proxies_grid_medium'), + builder: (_) => _buildGrid( + proxiesSortType: proxiesSortType, + columns: 3, ), - Breakpoints.large: SlotLayout.from( - key: const Key('proxies_grid_large'), - builder: (_) => _buildGrid( - proxiesSortType: proxiesSortType, - columns: 4, - ), + ), + Breakpoints.large: SlotLayout.from( + key: const Key('proxies_grid_large'), + builder: (_) => _buildGrid( + proxiesSortType: proxiesSortType, + columns: 4, ), - }, - ), + ), + }, ), ); }, ); } } + +class DelayTestButtonContainer extends StatefulWidget { + final Widget child; + + const DelayTestButtonContainer({ + super.key, + required this.child, + }); + + @override + State createState() => + _DelayTestButtonContainerState(); +} + +class _DelayTestButtonContainerState extends State + with SingleTickerProviderStateMixin { + late AnimationController _controller; + late Animation _scale; + late Animation _opacity; + + _getDelayMap() async { + _controller.forward(); + // for (final proxy in group.all) { + // context.appController.setDelay( + // Delay( + // name: proxy.name, + // value: 0, + // ), + // ); + // clashCore.delay( + // proxy.name, + // ); + // } + await Future.delayed( + appConstant.httpTimeoutDuration + appConstant.moreDuration, + ); + _controller.reverse(); + setState(() {}); + } + + @override + void initState() { + super.initState(); + _controller = AnimationController( + vsync: this, + duration: const Duration( + milliseconds: 300, + ), + ); + _scale = Tween( + begin: 1.0, + end: 0.0, + ).animate( + CurvedAnimation( + parent: _controller, + curve: const Interval( + 0, + 1, + curve: Curves.easeIn, + ), + ), + ); + _opacity = Tween( + begin: 1.0, + end: 0.0, + ).animate( + CurvedAnimation( + parent: _controller, + curve: const Interval( + 0, + 1, + curve: Curves.easeIn, + ), + ), + ); + } + + @override + void dispose() { + _controller.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return FloatLayout( + floatingWidget: FloatWrapper( + child: AnimatedBuilder( + animation: _controller, + builder: (_, child) { + return SizedBox( + width: 56, + height: 56, + child: Transform.scale( + scale: _scale.value, + child: Opacity( + opacity: _opacity.value, + child: child!, + ), + ), + ); + }, + child: FloatingActionButton( + heroTag: null, + onPressed: _getDelayMap, + child: const Icon(Icons.network_ping), + ), + ), + ), + child: widget.child, + ); + } +} diff --git a/lib/models/app.dart b/lib/models/app.dart index 4e21faf..68cc17b 100644 --- a/lib/models/app.dart +++ b/lib/models/app.dart @@ -79,6 +79,15 @@ class AppState with ChangeNotifier { } } + int? getDelay(String? proxyName) { + if (proxyName == null) return null; + final index = groups.indexWhere((element) => element.name == proxyName); + if (index == -1) return _delayMap[proxyName]; + final group = groups[index]; + if (group.now == null) return null; + return _delayMap[group.now]; + } + setDelay(Delay delay) { if (_delayMap[delay.name] != delay.value) { _delayMap = Map.from(_delayMap)..[delay.name] = delay.value; @@ -144,7 +153,7 @@ class AppState with ChangeNotifier { List get groups => _groups; set groups(List value) { - if (_groups != value) { + if (!const ListEquality().equals(_groups, value)) { _groups = value; notifyListeners(); } diff --git a/lib/models/ffi.dart b/lib/models/ffi.dart index 241aa19..421c6cc 100644 --- a/lib/models/ffi.dart +++ b/lib/models/ffi.dart @@ -52,6 +52,16 @@ class Delay with _$Delay { factory Delay.fromJson(Map json) => _$DelayFromJson(json); } +@freezed +class Now with _$Now { + const factory Now({ + required String name, + required String value, + }) = _Now; + + factory Now.fromJson(Map json) => _$NowFromJson(json); +} + @freezed class Process with _$Process { const factory Process({ diff --git a/lib/models/generated/ffi.freezed.dart b/lib/models/generated/ffi.freezed.dart index 55862e8..c493299 100644 --- a/lib/models/generated/ffi.freezed.dart +++ b/lib/models/generated/ffi.freezed.dart @@ -672,6 +672,151 @@ abstract class _Delay implements Delay { throw _privateConstructorUsedError; } +Now _$NowFromJson(Map json) { + return _Now.fromJson(json); +} + +/// @nodoc +mixin _$Now { + String get name => throw _privateConstructorUsedError; + String get value => throw _privateConstructorUsedError; + + Map toJson() => throw _privateConstructorUsedError; + @JsonKey(ignore: true) + $NowCopyWith get copyWith => throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $NowCopyWith<$Res> { + factory $NowCopyWith(Now value, $Res Function(Now) then) = + _$NowCopyWithImpl<$Res, Now>; + @useResult + $Res call({String name, String value}); +} + +/// @nodoc +class _$NowCopyWithImpl<$Res, $Val extends Now> implements $NowCopyWith<$Res> { + _$NowCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? name = null, + Object? value = null, + }) { + return _then(_value.copyWith( + name: null == name + ? _value.name + : name // ignore: cast_nullable_to_non_nullable + as String, + value: null == value + ? _value.value + : value // ignore: cast_nullable_to_non_nullable + as String, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$NowImplCopyWith<$Res> implements $NowCopyWith<$Res> { + factory _$$NowImplCopyWith(_$NowImpl value, $Res Function(_$NowImpl) then) = + __$$NowImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({String name, String value}); +} + +/// @nodoc +class __$$NowImplCopyWithImpl<$Res> extends _$NowCopyWithImpl<$Res, _$NowImpl> + implements _$$NowImplCopyWith<$Res> { + __$$NowImplCopyWithImpl(_$NowImpl _value, $Res Function(_$NowImpl) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? name = null, + Object? value = null, + }) { + return _then(_$NowImpl( + name: null == name + ? _value.name + : name // ignore: cast_nullable_to_non_nullable + as String, + value: null == value + ? _value.value + : value // ignore: cast_nullable_to_non_nullable + as String, + )); + } +} + +/// @nodoc +@JsonSerializable() +class _$NowImpl implements _Now { + const _$NowImpl({required this.name, required this.value}); + + factory _$NowImpl.fromJson(Map json) => + _$$NowImplFromJson(json); + + @override + final String name; + @override + final String value; + + @override + String toString() { + return 'Now(name: $name, value: $value)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$NowImpl && + (identical(other.name, name) || other.name == name) && + (identical(other.value, value) || other.value == value)); + } + + @JsonKey(ignore: true) + @override + int get hashCode => Object.hash(runtimeType, name, value); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$NowImplCopyWith<_$NowImpl> get copyWith => + __$$NowImplCopyWithImpl<_$NowImpl>(this, _$identity); + + @override + Map toJson() { + return _$$NowImplToJson( + this, + ); + } +} + +abstract class _Now implements Now { + const factory _Now( + {required final String name, required final String value}) = _$NowImpl; + + factory _Now.fromJson(Map json) = _$NowImpl.fromJson; + + @override + String get name; + @override + String get value; + @override + @JsonKey(ignore: true) + _$$NowImplCopyWith<_$NowImpl> get copyWith => + throw _privateConstructorUsedError; +} + Process _$ProcessFromJson(Map json) { return _Process.fromJson(json); } diff --git a/lib/models/generated/ffi.g.dart b/lib/models/generated/ffi.g.dart index 5e3fd98..233ba14 100644 --- a/lib/models/generated/ffi.g.dart +++ b/lib/models/generated/ffi.g.dart @@ -53,6 +53,7 @@ const _$MessageTypeEnumMap = { MessageType.tun: 'tun', MessageType.delay: 'delay', MessageType.process: 'process', + MessageType.now: 'now', }; _$DelayImpl _$$DelayImplFromJson(Map json) => _$DelayImpl( @@ -66,6 +67,16 @@ Map _$$DelayImplToJson(_$DelayImpl instance) => 'value': instance.value, }; +_$NowImpl _$$NowImplFromJson(Map json) => _$NowImpl( + name: json['name'] as String, + value: json['value'] as String, + ); + +Map _$$NowImplToJson(_$NowImpl instance) => { + 'name': instance.name, + 'value': instance.value, + }; + _$ProcessImpl _$$ProcessImplFromJson(Map json) => _$ProcessImpl( uid: (json['uid'] as num).toInt(), diff --git a/lib/widgets/clash_message_container.dart b/lib/widgets/clash_message_container.dart index fb45a53..dce02db 100644 --- a/lib/widgets/clash_message_container.dart +++ b/lib/widgets/clash_message_container.dart @@ -55,6 +55,18 @@ class _ClashMessageContainerState extends State super.onTun(fd); } + @override + void onNow(Now now) { + List groups = List.from(context.appController.appState.groups); + final index = groups.indexWhere( + (element) => element.name == now.name, + ); + if (index == -1 || groups[index].now == now.value) return; + groups[index] = groups[index].copyWith(now: now.value); + context.appController.appState.groups = groups; + super.onNow(now); + } + @override void onProcess(Metadata metadata) async { var packageName = await app?.getPackageName(metadata);