feat(useragent): callouts feature for approving new things
This commit is contained in:
16
useragent/lib/features/callouts/active_callout.dart
Normal file
16
useragent/lib/features/callouts/active_callout.dart
Normal file
@@ -0,0 +1,16 @@
|
||||
import 'package:arbiter/features/callouts/callout_event.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
|
||||
part 'active_callout.freezed.dart';
|
||||
|
||||
@freezed
|
||||
abstract class ActiveCallout with _$ActiveCallout {
|
||||
const factory ActiveCallout({
|
||||
required String id,
|
||||
required String title,
|
||||
required String description,
|
||||
String? iconUrl,
|
||||
required DateTime addedAt,
|
||||
required CalloutData data,
|
||||
}) = _ActiveCallout;
|
||||
}
|
||||
304
useragent/lib/features/callouts/active_callout.freezed.dart
Normal file
304
useragent/lib/features/callouts/active_callout.freezed.dart
Normal file
@@ -0,0 +1,304 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
// coverage:ignore-file
|
||||
// ignore_for_file: type=lint
|
||||
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
|
||||
|
||||
part of 'active_callout.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// FreezedGenerator
|
||||
// **************************************************************************
|
||||
|
||||
// dart format off
|
||||
T _$identity<T>(T value) => value;
|
||||
/// @nodoc
|
||||
mixin _$ActiveCallout {
|
||||
|
||||
String get id; String get title; String get description; String? get iconUrl; DateTime get addedAt; CalloutData get data;
|
||||
/// Create a copy of ActiveCallout
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@pragma('vm:prefer-inline')
|
||||
$ActiveCalloutCopyWith<ActiveCallout> get copyWith => _$ActiveCalloutCopyWithImpl<ActiveCallout>(this as ActiveCallout, _$identity);
|
||||
|
||||
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is ActiveCallout&&(identical(other.id, id) || other.id == id)&&(identical(other.title, title) || other.title == title)&&(identical(other.description, description) || other.description == description)&&(identical(other.iconUrl, iconUrl) || other.iconUrl == iconUrl)&&(identical(other.addedAt, addedAt) || other.addedAt == addedAt)&&(identical(other.data, data) || other.data == data));
|
||||
}
|
||||
|
||||
|
||||
@override
|
||||
int get hashCode => Object.hash(runtimeType,id,title,description,iconUrl,addedAt,data);
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'ActiveCallout(id: $id, title: $title, description: $description, iconUrl: $iconUrl, addedAt: $addedAt, data: $data)';
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract mixin class $ActiveCalloutCopyWith<$Res> {
|
||||
factory $ActiveCalloutCopyWith(ActiveCallout value, $Res Function(ActiveCallout) _then) = _$ActiveCalloutCopyWithImpl;
|
||||
@useResult
|
||||
$Res call({
|
||||
String id, String title, String description, String? iconUrl, DateTime addedAt, CalloutData data
|
||||
});
|
||||
|
||||
|
||||
$CalloutDataCopyWith<$Res> get data;
|
||||
|
||||
}
|
||||
/// @nodoc
|
||||
class _$ActiveCalloutCopyWithImpl<$Res>
|
||||
implements $ActiveCalloutCopyWith<$Res> {
|
||||
_$ActiveCalloutCopyWithImpl(this._self, this._then);
|
||||
|
||||
final ActiveCallout _self;
|
||||
final $Res Function(ActiveCallout) _then;
|
||||
|
||||
/// Create a copy of ActiveCallout
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? title = null,Object? description = null,Object? iconUrl = freezed,Object? addedAt = null,Object? data = null,}) {
|
||||
return _then(_self.copyWith(
|
||||
id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
|
||||
as String,title: null == title ? _self.title : title // ignore: cast_nullable_to_non_nullable
|
||||
as String,description: null == description ? _self.description : description // ignore: cast_nullable_to_non_nullable
|
||||
as String,iconUrl: freezed == iconUrl ? _self.iconUrl : iconUrl // ignore: cast_nullable_to_non_nullable
|
||||
as String?,addedAt: null == addedAt ? _self.addedAt : addedAt // ignore: cast_nullable_to_non_nullable
|
||||
as DateTime,data: null == data ? _self.data : data // ignore: cast_nullable_to_non_nullable
|
||||
as CalloutData,
|
||||
));
|
||||
}
|
||||
/// Create a copy of ActiveCallout
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override
|
||||
@pragma('vm:prefer-inline')
|
||||
$CalloutDataCopyWith<$Res> get data {
|
||||
|
||||
return $CalloutDataCopyWith<$Res>(_self.data, (value) {
|
||||
return _then(_self.copyWith(data: value));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// Adds pattern-matching-related methods to [ActiveCallout].
|
||||
extension ActiveCalloutPatterns on ActiveCallout {
|
||||
/// A variant of `map` that fallback to returning `orElse`.
|
||||
///
|
||||
/// It is equivalent to doing:
|
||||
/// ```dart
|
||||
/// switch (sealedClass) {
|
||||
/// case final Subclass value:
|
||||
/// return ...;
|
||||
/// case _:
|
||||
/// return orElse();
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult maybeMap<TResult extends Object?>(TResult Function( _ActiveCallout value)? $default,{required TResult orElse(),}){
|
||||
final _that = this;
|
||||
switch (_that) {
|
||||
case _ActiveCallout() when $default != null:
|
||||
return $default(_that);case _:
|
||||
return orElse();
|
||||
|
||||
}
|
||||
}
|
||||
/// A `switch`-like method, using callbacks.
|
||||
///
|
||||
/// Callbacks receives the raw object, upcasted.
|
||||
/// It is equivalent to doing:
|
||||
/// ```dart
|
||||
/// switch (sealedClass) {
|
||||
/// case final Subclass value:
|
||||
/// return ...;
|
||||
/// case final Subclass2 value:
|
||||
/// return ...;
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult map<TResult extends Object?>(TResult Function( _ActiveCallout value) $default,){
|
||||
final _that = this;
|
||||
switch (_that) {
|
||||
case _ActiveCallout():
|
||||
return $default(_that);case _:
|
||||
throw StateError('Unexpected subclass');
|
||||
|
||||
}
|
||||
}
|
||||
/// A variant of `map` that fallback to returning `null`.
|
||||
///
|
||||
/// It is equivalent to doing:
|
||||
/// ```dart
|
||||
/// switch (sealedClass) {
|
||||
/// case final Subclass value:
|
||||
/// return ...;
|
||||
/// case _:
|
||||
/// return null;
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult? mapOrNull<TResult extends Object?>(TResult? Function( _ActiveCallout value)? $default,){
|
||||
final _that = this;
|
||||
switch (_that) {
|
||||
case _ActiveCallout() when $default != null:
|
||||
return $default(_that);case _:
|
||||
return null;
|
||||
|
||||
}
|
||||
}
|
||||
/// A variant of `when` that fallback to an `orElse` callback.
|
||||
///
|
||||
/// It is equivalent to doing:
|
||||
/// ```dart
|
||||
/// switch (sealedClass) {
|
||||
/// case Subclass(:final field):
|
||||
/// return ...;
|
||||
/// case _:
|
||||
/// return orElse();
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String id, String title, String description, String? iconUrl, DateTime addedAt, CalloutData data)? $default,{required TResult orElse(),}) {final _that = this;
|
||||
switch (_that) {
|
||||
case _ActiveCallout() when $default != null:
|
||||
return $default(_that.id,_that.title,_that.description,_that.iconUrl,_that.addedAt,_that.data);case _:
|
||||
return orElse();
|
||||
|
||||
}
|
||||
}
|
||||
/// A `switch`-like method, using callbacks.
|
||||
///
|
||||
/// As opposed to `map`, this offers destructuring.
|
||||
/// It is equivalent to doing:
|
||||
/// ```dart
|
||||
/// switch (sealedClass) {
|
||||
/// case Subclass(:final field):
|
||||
/// return ...;
|
||||
/// case Subclass2(:final field2):
|
||||
/// return ...;
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String id, String title, String description, String? iconUrl, DateTime addedAt, CalloutData data) $default,) {final _that = this;
|
||||
switch (_that) {
|
||||
case _ActiveCallout():
|
||||
return $default(_that.id,_that.title,_that.description,_that.iconUrl,_that.addedAt,_that.data);case _:
|
||||
throw StateError('Unexpected subclass');
|
||||
|
||||
}
|
||||
}
|
||||
/// A variant of `when` that fallback to returning `null`
|
||||
///
|
||||
/// It is equivalent to doing:
|
||||
/// ```dart
|
||||
/// switch (sealedClass) {
|
||||
/// case Subclass(:final field):
|
||||
/// return ...;
|
||||
/// case _:
|
||||
/// return null;
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String id, String title, String description, String? iconUrl, DateTime addedAt, CalloutData data)? $default,) {final _that = this;
|
||||
switch (_that) {
|
||||
case _ActiveCallout() when $default != null:
|
||||
return $default(_that.id,_that.title,_that.description,_that.iconUrl,_that.addedAt,_that.data);case _:
|
||||
return null;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
|
||||
|
||||
class _ActiveCallout implements ActiveCallout {
|
||||
const _ActiveCallout({required this.id, required this.title, required this.description, this.iconUrl, required this.addedAt, required this.data});
|
||||
|
||||
|
||||
@override final String id;
|
||||
@override final String title;
|
||||
@override final String description;
|
||||
@override final String? iconUrl;
|
||||
@override final DateTime addedAt;
|
||||
@override final CalloutData data;
|
||||
|
||||
/// Create a copy of ActiveCallout
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override @JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@pragma('vm:prefer-inline')
|
||||
_$ActiveCalloutCopyWith<_ActiveCallout> get copyWith => __$ActiveCalloutCopyWithImpl<_ActiveCallout>(this, _$identity);
|
||||
|
||||
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is _ActiveCallout&&(identical(other.id, id) || other.id == id)&&(identical(other.title, title) || other.title == title)&&(identical(other.description, description) || other.description == description)&&(identical(other.iconUrl, iconUrl) || other.iconUrl == iconUrl)&&(identical(other.addedAt, addedAt) || other.addedAt == addedAt)&&(identical(other.data, data) || other.data == data));
|
||||
}
|
||||
|
||||
|
||||
@override
|
||||
int get hashCode => Object.hash(runtimeType,id,title,description,iconUrl,addedAt,data);
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'ActiveCallout(id: $id, title: $title, description: $description, iconUrl: $iconUrl, addedAt: $addedAt, data: $data)';
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract mixin class _$ActiveCalloutCopyWith<$Res> implements $ActiveCalloutCopyWith<$Res> {
|
||||
factory _$ActiveCalloutCopyWith(_ActiveCallout value, $Res Function(_ActiveCallout) _then) = __$ActiveCalloutCopyWithImpl;
|
||||
@override @useResult
|
||||
$Res call({
|
||||
String id, String title, String description, String? iconUrl, DateTime addedAt, CalloutData data
|
||||
});
|
||||
|
||||
|
||||
@override $CalloutDataCopyWith<$Res> get data;
|
||||
|
||||
}
|
||||
/// @nodoc
|
||||
class __$ActiveCalloutCopyWithImpl<$Res>
|
||||
implements _$ActiveCalloutCopyWith<$Res> {
|
||||
__$ActiveCalloutCopyWithImpl(this._self, this._then);
|
||||
|
||||
final _ActiveCallout _self;
|
||||
final $Res Function(_ActiveCallout) _then;
|
||||
|
||||
/// Create a copy of ActiveCallout
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? title = null,Object? description = null,Object? iconUrl = freezed,Object? addedAt = null,Object? data = null,}) {
|
||||
return _then(_ActiveCallout(
|
||||
id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
|
||||
as String,title: null == title ? _self.title : title // ignore: cast_nullable_to_non_nullable
|
||||
as String,description: null == description ? _self.description : description // ignore: cast_nullable_to_non_nullable
|
||||
as String,iconUrl: freezed == iconUrl ? _self.iconUrl : iconUrl // ignore: cast_nullable_to_non_nullable
|
||||
as String?,addedAt: null == addedAt ? _self.addedAt : addedAt // ignore: cast_nullable_to_non_nullable
|
||||
as DateTime,data: null == data ? _self.data : data // ignore: cast_nullable_to_non_nullable
|
||||
as CalloutData,
|
||||
));
|
||||
}
|
||||
|
||||
/// Create a copy of ActiveCallout
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override
|
||||
@pragma('vm:prefer-inline')
|
||||
$CalloutDataCopyWith<$Res> get data {
|
||||
|
||||
return $CalloutDataCopyWith<$Res>(_self.data, (value) {
|
||||
return _then(_self.copyWith(data: value));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// dart format on
|
||||
25
useragent/lib/features/callouts/callout_event.dart
Normal file
25
useragent/lib/features/callouts/callout_event.dart
Normal file
@@ -0,0 +1,25 @@
|
||||
import 'package:arbiter/proto/client.pb.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'package:hooks_riverpod/experimental/mutation.dart';
|
||||
|
||||
part 'callout_event.freezed.dart';
|
||||
|
||||
@freezed
|
||||
sealed class CalloutData with _$CalloutData {
|
||||
const factory CalloutData.connectApproval({
|
||||
required String pubkey,
|
||||
required ClientInfo clientInfo,
|
||||
}) = ConnectApprovalData;
|
||||
}
|
||||
|
||||
@freezed
|
||||
sealed class CalloutEvent with _$CalloutEvent {
|
||||
const factory CalloutEvent.added({
|
||||
required String id,
|
||||
required CalloutData data,
|
||||
}) = CalloutEventAdded;
|
||||
|
||||
const factory CalloutEvent.cancelled({
|
||||
required String id,
|
||||
}) = CalloutEventCancelled;
|
||||
}
|
||||
602
useragent/lib/features/callouts/callout_event.freezed.dart
Normal file
602
useragent/lib/features/callouts/callout_event.freezed.dart
Normal file
@@ -0,0 +1,602 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
// coverage:ignore-file
|
||||
// ignore_for_file: type=lint
|
||||
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
|
||||
|
||||
part of 'callout_event.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// FreezedGenerator
|
||||
// **************************************************************************
|
||||
|
||||
// dart format off
|
||||
T _$identity<T>(T value) => value;
|
||||
/// @nodoc
|
||||
mixin _$CalloutData {
|
||||
|
||||
String get pubkey; ClientInfo get clientInfo;
|
||||
/// Create a copy of CalloutData
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@pragma('vm:prefer-inline')
|
||||
$CalloutDataCopyWith<CalloutData> get copyWith => _$CalloutDataCopyWithImpl<CalloutData>(this as CalloutData, _$identity);
|
||||
|
||||
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is CalloutData&&(identical(other.pubkey, pubkey) || other.pubkey == pubkey)&&(identical(other.clientInfo, clientInfo) || other.clientInfo == clientInfo));
|
||||
}
|
||||
|
||||
|
||||
@override
|
||||
int get hashCode => Object.hash(runtimeType,pubkey,clientInfo);
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'CalloutData(pubkey: $pubkey, clientInfo: $clientInfo)';
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract mixin class $CalloutDataCopyWith<$Res> {
|
||||
factory $CalloutDataCopyWith(CalloutData value, $Res Function(CalloutData) _then) = _$CalloutDataCopyWithImpl;
|
||||
@useResult
|
||||
$Res call({
|
||||
String pubkey, ClientInfo clientInfo
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
/// @nodoc
|
||||
class _$CalloutDataCopyWithImpl<$Res>
|
||||
implements $CalloutDataCopyWith<$Res> {
|
||||
_$CalloutDataCopyWithImpl(this._self, this._then);
|
||||
|
||||
final CalloutData _self;
|
||||
final $Res Function(CalloutData) _then;
|
||||
|
||||
/// Create a copy of CalloutData
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@pragma('vm:prefer-inline') @override $Res call({Object? pubkey = null,Object? clientInfo = null,}) {
|
||||
return _then(_self.copyWith(
|
||||
pubkey: null == pubkey ? _self.pubkey : pubkey // ignore: cast_nullable_to_non_nullable
|
||||
as String,clientInfo: null == clientInfo ? _self.clientInfo : clientInfo // ignore: cast_nullable_to_non_nullable
|
||||
as ClientInfo,
|
||||
));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/// Adds pattern-matching-related methods to [CalloutData].
|
||||
extension CalloutDataPatterns on CalloutData {
|
||||
/// A variant of `map` that fallback to returning `orElse`.
|
||||
///
|
||||
/// It is equivalent to doing:
|
||||
/// ```dart
|
||||
/// switch (sealedClass) {
|
||||
/// case final Subclass value:
|
||||
/// return ...;
|
||||
/// case _:
|
||||
/// return orElse();
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult maybeMap<TResult extends Object?>({TResult Function( ConnectApprovalData value)? connectApproval,required TResult orElse(),}){
|
||||
final _that = this;
|
||||
switch (_that) {
|
||||
case ConnectApprovalData() when connectApproval != null:
|
||||
return connectApproval(_that);case _:
|
||||
return orElse();
|
||||
|
||||
}
|
||||
}
|
||||
/// A `switch`-like method, using callbacks.
|
||||
///
|
||||
/// Callbacks receives the raw object, upcasted.
|
||||
/// It is equivalent to doing:
|
||||
/// ```dart
|
||||
/// switch (sealedClass) {
|
||||
/// case final Subclass value:
|
||||
/// return ...;
|
||||
/// case final Subclass2 value:
|
||||
/// return ...;
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult map<TResult extends Object?>({required TResult Function( ConnectApprovalData value) connectApproval,}){
|
||||
final _that = this;
|
||||
switch (_that) {
|
||||
case ConnectApprovalData():
|
||||
return connectApproval(_that);}
|
||||
}
|
||||
/// A variant of `map` that fallback to returning `null`.
|
||||
///
|
||||
/// It is equivalent to doing:
|
||||
/// ```dart
|
||||
/// switch (sealedClass) {
|
||||
/// case final Subclass value:
|
||||
/// return ...;
|
||||
/// case _:
|
||||
/// return null;
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult? mapOrNull<TResult extends Object?>({TResult? Function( ConnectApprovalData value)? connectApproval,}){
|
||||
final _that = this;
|
||||
switch (_that) {
|
||||
case ConnectApprovalData() when connectApproval != null:
|
||||
return connectApproval(_that);case _:
|
||||
return null;
|
||||
|
||||
}
|
||||
}
|
||||
/// A variant of `when` that fallback to an `orElse` callback.
|
||||
///
|
||||
/// It is equivalent to doing:
|
||||
/// ```dart
|
||||
/// switch (sealedClass) {
|
||||
/// case Subclass(:final field):
|
||||
/// return ...;
|
||||
/// case _:
|
||||
/// return orElse();
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>({TResult Function( String pubkey, ClientInfo clientInfo)? connectApproval,required TResult orElse(),}) {final _that = this;
|
||||
switch (_that) {
|
||||
case ConnectApprovalData() when connectApproval != null:
|
||||
return connectApproval(_that.pubkey,_that.clientInfo);case _:
|
||||
return orElse();
|
||||
|
||||
}
|
||||
}
|
||||
/// A `switch`-like method, using callbacks.
|
||||
///
|
||||
/// As opposed to `map`, this offers destructuring.
|
||||
/// It is equivalent to doing:
|
||||
/// ```dart
|
||||
/// switch (sealedClass) {
|
||||
/// case Subclass(:final field):
|
||||
/// return ...;
|
||||
/// case Subclass2(:final field2):
|
||||
/// return ...;
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult when<TResult extends Object?>({required TResult Function( String pubkey, ClientInfo clientInfo) connectApproval,}) {final _that = this;
|
||||
switch (_that) {
|
||||
case ConnectApprovalData():
|
||||
return connectApproval(_that.pubkey,_that.clientInfo);}
|
||||
}
|
||||
/// A variant of `when` that fallback to returning `null`
|
||||
///
|
||||
/// It is equivalent to doing:
|
||||
/// ```dart
|
||||
/// switch (sealedClass) {
|
||||
/// case Subclass(:final field):
|
||||
/// return ...;
|
||||
/// case _:
|
||||
/// return null;
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>({TResult? Function( String pubkey, ClientInfo clientInfo)? connectApproval,}) {final _that = this;
|
||||
switch (_that) {
|
||||
case ConnectApprovalData() when connectApproval != null:
|
||||
return connectApproval(_that.pubkey,_that.clientInfo);case _:
|
||||
return null;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
|
||||
|
||||
class ConnectApprovalData implements CalloutData {
|
||||
const ConnectApprovalData({required this.pubkey, required this.clientInfo});
|
||||
|
||||
|
||||
@override final String pubkey;
|
||||
@override final ClientInfo clientInfo;
|
||||
|
||||
/// Create a copy of CalloutData
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override @JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@pragma('vm:prefer-inline')
|
||||
$ConnectApprovalDataCopyWith<ConnectApprovalData> get copyWith => _$ConnectApprovalDataCopyWithImpl<ConnectApprovalData>(this, _$identity);
|
||||
|
||||
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is ConnectApprovalData&&(identical(other.pubkey, pubkey) || other.pubkey == pubkey)&&(identical(other.clientInfo, clientInfo) || other.clientInfo == clientInfo));
|
||||
}
|
||||
|
||||
|
||||
@override
|
||||
int get hashCode => Object.hash(runtimeType,pubkey,clientInfo);
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'CalloutData.connectApproval(pubkey: $pubkey, clientInfo: $clientInfo)';
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract mixin class $ConnectApprovalDataCopyWith<$Res> implements $CalloutDataCopyWith<$Res> {
|
||||
factory $ConnectApprovalDataCopyWith(ConnectApprovalData value, $Res Function(ConnectApprovalData) _then) = _$ConnectApprovalDataCopyWithImpl;
|
||||
@override @useResult
|
||||
$Res call({
|
||||
String pubkey, ClientInfo clientInfo
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
/// @nodoc
|
||||
class _$ConnectApprovalDataCopyWithImpl<$Res>
|
||||
implements $ConnectApprovalDataCopyWith<$Res> {
|
||||
_$ConnectApprovalDataCopyWithImpl(this._self, this._then);
|
||||
|
||||
final ConnectApprovalData _self;
|
||||
final $Res Function(ConnectApprovalData) _then;
|
||||
|
||||
/// Create a copy of CalloutData
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override @pragma('vm:prefer-inline') $Res call({Object? pubkey = null,Object? clientInfo = null,}) {
|
||||
return _then(ConnectApprovalData(
|
||||
pubkey: null == pubkey ? _self.pubkey : pubkey // ignore: cast_nullable_to_non_nullable
|
||||
as String,clientInfo: null == clientInfo ? _self.clientInfo : clientInfo // ignore: cast_nullable_to_non_nullable
|
||||
as ClientInfo,
|
||||
));
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
mixin _$CalloutEvent {
|
||||
|
||||
String get id;
|
||||
/// Create a copy of CalloutEvent
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@pragma('vm:prefer-inline')
|
||||
$CalloutEventCopyWith<CalloutEvent> get copyWith => _$CalloutEventCopyWithImpl<CalloutEvent>(this as CalloutEvent, _$identity);
|
||||
|
||||
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is CalloutEvent&&(identical(other.id, id) || other.id == id));
|
||||
}
|
||||
|
||||
|
||||
@override
|
||||
int get hashCode => Object.hash(runtimeType,id);
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'CalloutEvent(id: $id)';
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract mixin class $CalloutEventCopyWith<$Res> {
|
||||
factory $CalloutEventCopyWith(CalloutEvent value, $Res Function(CalloutEvent) _then) = _$CalloutEventCopyWithImpl;
|
||||
@useResult
|
||||
$Res call({
|
||||
String id
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
/// @nodoc
|
||||
class _$CalloutEventCopyWithImpl<$Res>
|
||||
implements $CalloutEventCopyWith<$Res> {
|
||||
_$CalloutEventCopyWithImpl(this._self, this._then);
|
||||
|
||||
final CalloutEvent _self;
|
||||
final $Res Function(CalloutEvent) _then;
|
||||
|
||||
/// Create a copy of CalloutEvent
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@pragma('vm:prefer-inline') @override $Res call({Object? id = null,}) {
|
||||
return _then(_self.copyWith(
|
||||
id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/// Adds pattern-matching-related methods to [CalloutEvent].
|
||||
extension CalloutEventPatterns on CalloutEvent {
|
||||
/// A variant of `map` that fallback to returning `orElse`.
|
||||
///
|
||||
/// It is equivalent to doing:
|
||||
/// ```dart
|
||||
/// switch (sealedClass) {
|
||||
/// case final Subclass value:
|
||||
/// return ...;
|
||||
/// case _:
|
||||
/// return orElse();
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult maybeMap<TResult extends Object?>({TResult Function( CalloutEventAdded value)? added,TResult Function( CalloutEventCancelled value)? cancelled,required TResult orElse(),}){
|
||||
final _that = this;
|
||||
switch (_that) {
|
||||
case CalloutEventAdded() when added != null:
|
||||
return added(_that);case CalloutEventCancelled() when cancelled != null:
|
||||
return cancelled(_that);case _:
|
||||
return orElse();
|
||||
|
||||
}
|
||||
}
|
||||
/// A `switch`-like method, using callbacks.
|
||||
///
|
||||
/// Callbacks receives the raw object, upcasted.
|
||||
/// It is equivalent to doing:
|
||||
/// ```dart
|
||||
/// switch (sealedClass) {
|
||||
/// case final Subclass value:
|
||||
/// return ...;
|
||||
/// case final Subclass2 value:
|
||||
/// return ...;
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult map<TResult extends Object?>({required TResult Function( CalloutEventAdded value) added,required TResult Function( CalloutEventCancelled value) cancelled,}){
|
||||
final _that = this;
|
||||
switch (_that) {
|
||||
case CalloutEventAdded():
|
||||
return added(_that);case CalloutEventCancelled():
|
||||
return cancelled(_that);}
|
||||
}
|
||||
/// A variant of `map` that fallback to returning `null`.
|
||||
///
|
||||
/// It is equivalent to doing:
|
||||
/// ```dart
|
||||
/// switch (sealedClass) {
|
||||
/// case final Subclass value:
|
||||
/// return ...;
|
||||
/// case _:
|
||||
/// return null;
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult? mapOrNull<TResult extends Object?>({TResult? Function( CalloutEventAdded value)? added,TResult? Function( CalloutEventCancelled value)? cancelled,}){
|
||||
final _that = this;
|
||||
switch (_that) {
|
||||
case CalloutEventAdded() when added != null:
|
||||
return added(_that);case CalloutEventCancelled() when cancelled != null:
|
||||
return cancelled(_that);case _:
|
||||
return null;
|
||||
|
||||
}
|
||||
}
|
||||
/// A variant of `when` that fallback to an `orElse` callback.
|
||||
///
|
||||
/// It is equivalent to doing:
|
||||
/// ```dart
|
||||
/// switch (sealedClass) {
|
||||
/// case Subclass(:final field):
|
||||
/// return ...;
|
||||
/// case _:
|
||||
/// return orElse();
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>({TResult Function( String id, CalloutData data)? added,TResult Function( String id)? cancelled,required TResult orElse(),}) {final _that = this;
|
||||
switch (_that) {
|
||||
case CalloutEventAdded() when added != null:
|
||||
return added(_that.id,_that.data);case CalloutEventCancelled() when cancelled != null:
|
||||
return cancelled(_that.id);case _:
|
||||
return orElse();
|
||||
|
||||
}
|
||||
}
|
||||
/// A `switch`-like method, using callbacks.
|
||||
///
|
||||
/// As opposed to `map`, this offers destructuring.
|
||||
/// It is equivalent to doing:
|
||||
/// ```dart
|
||||
/// switch (sealedClass) {
|
||||
/// case Subclass(:final field):
|
||||
/// return ...;
|
||||
/// case Subclass2(:final field2):
|
||||
/// return ...;
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult when<TResult extends Object?>({required TResult Function( String id, CalloutData data) added,required TResult Function( String id) cancelled,}) {final _that = this;
|
||||
switch (_that) {
|
||||
case CalloutEventAdded():
|
||||
return added(_that.id,_that.data);case CalloutEventCancelled():
|
||||
return cancelled(_that.id);}
|
||||
}
|
||||
/// A variant of `when` that fallback to returning `null`
|
||||
///
|
||||
/// It is equivalent to doing:
|
||||
/// ```dart
|
||||
/// switch (sealedClass) {
|
||||
/// case Subclass(:final field):
|
||||
/// return ...;
|
||||
/// case _:
|
||||
/// return null;
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>({TResult? Function( String id, CalloutData data)? added,TResult? Function( String id)? cancelled,}) {final _that = this;
|
||||
switch (_that) {
|
||||
case CalloutEventAdded() when added != null:
|
||||
return added(_that.id,_that.data);case CalloutEventCancelled() when cancelled != null:
|
||||
return cancelled(_that.id);case _:
|
||||
return null;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
|
||||
|
||||
class CalloutEventAdded implements CalloutEvent {
|
||||
const CalloutEventAdded({required this.id, required this.data});
|
||||
|
||||
|
||||
@override final String id;
|
||||
final CalloutData data;
|
||||
|
||||
/// Create a copy of CalloutEvent
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override @JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@pragma('vm:prefer-inline')
|
||||
$CalloutEventAddedCopyWith<CalloutEventAdded> get copyWith => _$CalloutEventAddedCopyWithImpl<CalloutEventAdded>(this, _$identity);
|
||||
|
||||
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is CalloutEventAdded&&(identical(other.id, id) || other.id == id)&&(identical(other.data, data) || other.data == data));
|
||||
}
|
||||
|
||||
|
||||
@override
|
||||
int get hashCode => Object.hash(runtimeType,id,data);
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'CalloutEvent.added(id: $id, data: $data)';
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract mixin class $CalloutEventAddedCopyWith<$Res> implements $CalloutEventCopyWith<$Res> {
|
||||
factory $CalloutEventAddedCopyWith(CalloutEventAdded value, $Res Function(CalloutEventAdded) _then) = _$CalloutEventAddedCopyWithImpl;
|
||||
@override @useResult
|
||||
$Res call({
|
||||
String id, CalloutData data
|
||||
});
|
||||
|
||||
|
||||
$CalloutDataCopyWith<$Res> get data;
|
||||
|
||||
}
|
||||
/// @nodoc
|
||||
class _$CalloutEventAddedCopyWithImpl<$Res>
|
||||
implements $CalloutEventAddedCopyWith<$Res> {
|
||||
_$CalloutEventAddedCopyWithImpl(this._self, this._then);
|
||||
|
||||
final CalloutEventAdded _self;
|
||||
final $Res Function(CalloutEventAdded) _then;
|
||||
|
||||
/// Create a copy of CalloutEvent
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? data = null,}) {
|
||||
return _then(CalloutEventAdded(
|
||||
id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
|
||||
as String,data: null == data ? _self.data : data // ignore: cast_nullable_to_non_nullable
|
||||
as CalloutData,
|
||||
));
|
||||
}
|
||||
|
||||
/// Create a copy of CalloutEvent
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override
|
||||
@pragma('vm:prefer-inline')
|
||||
$CalloutDataCopyWith<$Res> get data {
|
||||
|
||||
return $CalloutDataCopyWith<$Res>(_self.data, (value) {
|
||||
return _then(_self.copyWith(data: value));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
|
||||
|
||||
class CalloutEventCancelled implements CalloutEvent {
|
||||
const CalloutEventCancelled({required this.id});
|
||||
|
||||
|
||||
@override final String id;
|
||||
|
||||
/// Create a copy of CalloutEvent
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override @JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@pragma('vm:prefer-inline')
|
||||
$CalloutEventCancelledCopyWith<CalloutEventCancelled> get copyWith => _$CalloutEventCancelledCopyWithImpl<CalloutEventCancelled>(this, _$identity);
|
||||
|
||||
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is CalloutEventCancelled&&(identical(other.id, id) || other.id == id));
|
||||
}
|
||||
|
||||
|
||||
@override
|
||||
int get hashCode => Object.hash(runtimeType,id);
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'CalloutEvent.cancelled(id: $id)';
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract mixin class $CalloutEventCancelledCopyWith<$Res> implements $CalloutEventCopyWith<$Res> {
|
||||
factory $CalloutEventCancelledCopyWith(CalloutEventCancelled value, $Res Function(CalloutEventCancelled) _then) = _$CalloutEventCancelledCopyWithImpl;
|
||||
@override @useResult
|
||||
$Res call({
|
||||
String id
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
/// @nodoc
|
||||
class _$CalloutEventCancelledCopyWithImpl<$Res>
|
||||
implements $CalloutEventCancelledCopyWith<$Res> {
|
||||
_$CalloutEventCancelledCopyWithImpl(this._self, this._then);
|
||||
|
||||
final CalloutEventCancelled _self;
|
||||
final $Res Function(CalloutEventCancelled) _then;
|
||||
|
||||
/// Create a copy of CalloutEvent
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override @pragma('vm:prefer-inline') $Res call({Object? id = null,}) {
|
||||
return _then(CalloutEventCancelled(
|
||||
id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
));
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
// dart format on
|
||||
57
useragent/lib/features/callouts/callout_manager.dart
Normal file
57
useragent/lib/features/callouts/callout_manager.dart
Normal file
@@ -0,0 +1,57 @@
|
||||
import 'package:arbiter/features/callouts/active_callout.dart';
|
||||
import 'package:arbiter/features/callouts/callout_event.dart';
|
||||
import 'package:arbiter/features/callouts/types/sdk_connect_approve.dart'
|
||||
as connect_approve;
|
||||
import 'package:arbiter/proto/client.pb.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
|
||||
part 'callout_manager.g.dart';
|
||||
|
||||
@Riverpod(keepAlive: true)
|
||||
class CalloutManager extends _$CalloutManager {
|
||||
@override
|
||||
Map<String, ActiveCallout> build() {
|
||||
ref.listen(connect_approve.connectApproveEventsProvider, (_, next) {
|
||||
next.whenData(_processEvent);
|
||||
});
|
||||
return {};
|
||||
}
|
||||
|
||||
void _processEvent(CalloutEvent event) {
|
||||
switch (event) {
|
||||
case CalloutEventAdded(:final id, :final data):
|
||||
state = {...state, id: _toActiveCallout(id, data)};
|
||||
case CalloutEventCancelled(:final id):
|
||||
state = {...state}..remove(id);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> sendDecision(String id, bool approved) async {
|
||||
final callout = state[id];
|
||||
if (callout == null) return;
|
||||
switch (callout.data) {
|
||||
case ConnectApprovalData(:final pubkey):
|
||||
await connect_approve.sendDecision(ref, pubkey, approved);
|
||||
}
|
||||
dismiss(id);
|
||||
}
|
||||
|
||||
void dismiss(String id) {
|
||||
state = {...state}..remove(id);
|
||||
}
|
||||
}
|
||||
|
||||
ActiveCallout _toActiveCallout(String id, CalloutData data) => switch (data) {
|
||||
ConnectApprovalData(:final clientInfo) => ActiveCallout(
|
||||
id: id,
|
||||
title: 'Connection Request',
|
||||
description: _clientDisplayName(clientInfo) != null
|
||||
? '${_clientDisplayName(clientInfo)} is requesting a connection.'
|
||||
: 'An SDK client is requesting a connection.',
|
||||
addedAt: DateTime.now(),
|
||||
data: data,
|
||||
),
|
||||
};
|
||||
|
||||
String? _clientDisplayName(ClientInfo info) =>
|
||||
info.hasName() && info.name.isNotEmpty ? info.name : null;
|
||||
67
useragent/lib/features/callouts/callout_manager.g.dart
Normal file
67
useragent/lib/features/callouts/callout_manager.g.dart
Normal file
@@ -0,0 +1,67 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'callout_manager.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// RiverpodGenerator
|
||||
// **************************************************************************
|
||||
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
// ignore_for_file: type=lint, type=warning
|
||||
|
||||
@ProviderFor(CalloutManager)
|
||||
final calloutManagerProvider = CalloutManagerProvider._();
|
||||
|
||||
final class CalloutManagerProvider
|
||||
extends $NotifierProvider<CalloutManager, Map<String, ActiveCallout>> {
|
||||
CalloutManagerProvider._()
|
||||
: super(
|
||||
from: null,
|
||||
argument: null,
|
||||
retry: null,
|
||||
name: r'calloutManagerProvider',
|
||||
isAutoDispose: false,
|
||||
dependencies: null,
|
||||
$allTransitiveDependencies: null,
|
||||
);
|
||||
|
||||
@override
|
||||
String debugGetCreateSourceHash() => _$calloutManagerHash();
|
||||
|
||||
@$internal
|
||||
@override
|
||||
CalloutManager create() => CalloutManager();
|
||||
|
||||
/// {@macro riverpod.override_with_value}
|
||||
Override overrideWithValue(Map<String, ActiveCallout> value) {
|
||||
return $ProviderOverride(
|
||||
origin: this,
|
||||
providerOverride: $SyncValueProvider<Map<String, ActiveCallout>>(value),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
String _$calloutManagerHash() => r'1d42ddcd9e5b8669a7ec08709b9dde9df6865bda';
|
||||
|
||||
abstract class _$CalloutManager extends $Notifier<Map<String, ActiveCallout>> {
|
||||
Map<String, ActiveCallout> build();
|
||||
@$mustCallSuper
|
||||
@override
|
||||
void runBuild() {
|
||||
final ref =
|
||||
this.ref
|
||||
as $Ref<Map<String, ActiveCallout>, Map<String, ActiveCallout>>;
|
||||
final element =
|
||||
ref.element
|
||||
as $ClassProviderElement<
|
||||
AnyNotifier<
|
||||
Map<String, ActiveCallout>,
|
||||
Map<String, ActiveCallout>
|
||||
>,
|
||||
Map<String, ActiveCallout>,
|
||||
Object?,
|
||||
Object?
|
||||
>;
|
||||
element.handleCreate(ref, build);
|
||||
}
|
||||
}
|
||||
99
useragent/lib/features/callouts/show_callout.dart
Normal file
99
useragent/lib/features/callouts/show_callout.dart
Normal file
@@ -0,0 +1,99 @@
|
||||
import 'package:arbiter/features/callouts/callout_event.dart';
|
||||
import 'package:arbiter/features/callouts/callout_manager.dart';
|
||||
import 'package:arbiter/screens/callouts/sdk_connect.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
|
||||
Future<void> showCallout(BuildContext context, WidgetRef ref, String id) async {
|
||||
final data = ref.read(calloutManagerProvider)[id]?.data;
|
||||
if (data == null) return;
|
||||
|
||||
await showGeneralDialog(
|
||||
context: context,
|
||||
barrierDismissible: false,
|
||||
barrierLabel: MaterialLocalizations.of(context).modalBarrierDismissLabel,
|
||||
barrierColor: Colors.transparent,
|
||||
transitionDuration: const Duration(milliseconds: 320),
|
||||
pageBuilder: (_, animation, _) => _CalloutOverlay(
|
||||
id: id,
|
||||
data: data,
|
||||
animation: animation,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
class _CalloutOverlay extends ConsumerWidget {
|
||||
const _CalloutOverlay({
|
||||
required this.id,
|
||||
required this.data,
|
||||
required this.animation,
|
||||
});
|
||||
|
||||
final String id;
|
||||
final CalloutData data;
|
||||
final Animation<double> animation;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
ref.listen(
|
||||
calloutManagerProvider.select((map) => map.containsKey(id)),
|
||||
(wasPresent, isPresent) {
|
||||
if (wasPresent == true && !isPresent && context.mounted) {
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
final content = switch (data) {
|
||||
ConnectApprovalData(:final pubkey, :final clientInfo) => SdkConnectCallout(
|
||||
pubkey: pubkey,
|
||||
clientInfo: clientInfo,
|
||||
onAccept: () => ref.read(calloutManagerProvider.notifier).sendDecision(id, true),
|
||||
onDecline: () => ref.read(calloutManagerProvider.notifier).sendDecision(id, false),
|
||||
),
|
||||
};
|
||||
|
||||
final barrierAnim = CurvedAnimation(
|
||||
parent: animation,
|
||||
curve: const Interval(0, 0.3125, curve: Curves.easeOut),
|
||||
);
|
||||
final popupAnim = CurvedAnimation(
|
||||
parent: animation,
|
||||
curve: const Interval(0.3125, 1, curve: Curves.easeOutCubic),
|
||||
);
|
||||
|
||||
return Material(
|
||||
type: MaterialType.transparency,
|
||||
child: Stack(
|
||||
children: [
|
||||
Positioned.fill(
|
||||
child: AnimatedBuilder(
|
||||
animation: barrierAnim,
|
||||
builder: (_, __) => ColoredBox(
|
||||
color: Colors.black.withValues(alpha: 0.35 * barrierAnim.value),
|
||||
),
|
||||
),
|
||||
),
|
||||
SafeArea(
|
||||
child: Align(
|
||||
alignment: Alignment.bottomCenter,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: FadeTransition(
|
||||
opacity: popupAnim,
|
||||
child: SlideTransition(
|
||||
position: Tween<Offset>(
|
||||
begin: const Offset(0, 0.08),
|
||||
end: Offset.zero,
|
||||
).animate(popupAnim),
|
||||
child: content,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
218
useragent/lib/features/callouts/show_callout_list.dart
Normal file
218
useragent/lib/features/callouts/show_callout_list.dart
Normal file
@@ -0,0 +1,218 @@
|
||||
import 'package:arbiter/features/callouts/active_callout.dart';
|
||||
import 'package:arbiter/features/callouts/callout_manager.dart';
|
||||
import 'package:arbiter/features/callouts/show_callout.dart';
|
||||
import 'package:arbiter/theme/palette.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:sizer/sizer.dart';
|
||||
import 'package:timeago/timeago.dart' as timeago;
|
||||
|
||||
Future<void> showCalloutList(BuildContext context, WidgetRef ref) async {
|
||||
final selectedId = await showGeneralDialog<String>(
|
||||
context: context,
|
||||
barrierDismissible: true,
|
||||
barrierLabel: MaterialLocalizations.of(context).modalBarrierDismissLabel,
|
||||
barrierColor: Colors.transparent,
|
||||
transitionDuration: const Duration(milliseconds: 280),
|
||||
pageBuilder: (_, animation, __) => _CalloutListOverlay(animation: animation),
|
||||
);
|
||||
|
||||
if (selectedId != null && context.mounted) {
|
||||
await showCallout(context, ref, selectedId);
|
||||
}
|
||||
}
|
||||
|
||||
class _CalloutListOverlay extends ConsumerWidget {
|
||||
const _CalloutListOverlay({required this.animation});
|
||||
|
||||
final Animation<double> animation;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final callouts = ref.watch(calloutManagerProvider);
|
||||
|
||||
final barrierAnim = CurvedAnimation(
|
||||
parent: animation,
|
||||
curve: const Interval(0, 0.3, curve: Curves.easeOut),
|
||||
);
|
||||
final panelAnim = CurvedAnimation(
|
||||
parent: animation,
|
||||
curve: const Interval(0.3, 1, curve: Curves.easeOutCubic),
|
||||
);
|
||||
|
||||
return Material(
|
||||
type: MaterialType.transparency,
|
||||
child: Stack(
|
||||
children: [
|
||||
Positioned.fill(
|
||||
child: GestureDetector(
|
||||
behavior: HitTestBehavior.opaque,
|
||||
onTap: () => Navigator.of(context).pop(),
|
||||
child: AnimatedBuilder(
|
||||
animation: barrierAnim,
|
||||
builder: (_, __) => ColoredBox(
|
||||
color: Colors.black.withValues(alpha: 0.35 * barrierAnim.value),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
SafeArea(
|
||||
child: Align(
|
||||
alignment: Alignment.bottomCenter,
|
||||
child: Padding(
|
||||
padding: EdgeInsets.all(1.6.h),
|
||||
child: FadeTransition(
|
||||
opacity: panelAnim,
|
||||
child: SlideTransition(
|
||||
position: Tween<Offset>(
|
||||
begin: const Offset(0, 0.08),
|
||||
end: Offset.zero,
|
||||
).animate(panelAnim),
|
||||
child: GestureDetector(
|
||||
onTap: () {},
|
||||
child: _CalloutListPanel(callouts: callouts),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _CalloutListPanel extends StatelessWidget {
|
||||
const _CalloutListPanel({required this.callouts});
|
||||
|
||||
final Map<String, ActiveCallout> callouts;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = Theme.of(context);
|
||||
|
||||
return Container(
|
||||
width: double.infinity,
|
||||
constraints: BoxConstraints(maxHeight: 48.h),
|
||||
decoration: BoxDecoration(
|
||||
color: Palette.cream,
|
||||
borderRadius: BorderRadius.circular(24),
|
||||
border: Border.all(color: Palette.line),
|
||||
),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Padding(
|
||||
padding: EdgeInsets.fromLTRB(2.h, 2.h, 2.h, 1.2.h),
|
||||
child: Text(
|
||||
'Notifications',
|
||||
style: theme.textTheme.titleMedium?.copyWith(
|
||||
color: Palette.ink,
|
||||
fontWeight: FontWeight.w800,
|
||||
),
|
||||
),
|
||||
),
|
||||
if (callouts.isEmpty)
|
||||
Padding(
|
||||
padding: EdgeInsets.fromLTRB(2.h, 0, 2.h, 2.h),
|
||||
child: Text(
|
||||
'No pending notifications.',
|
||||
style: theme.textTheme.bodyMedium?.copyWith(
|
||||
color: Palette.ink.withValues(alpha: 0.50),
|
||||
),
|
||||
),
|
||||
)
|
||||
else
|
||||
Flexible(
|
||||
child: SingleChildScrollView(
|
||||
padding: EdgeInsets.fromLTRB(1.2.h, 0, 1.2.h, 1.2.h),
|
||||
child: Column(
|
||||
spacing: 0.5.h,
|
||||
children: [
|
||||
for (final entry in callouts.values)
|
||||
_CalloutListEntry(
|
||||
callout: entry,
|
||||
onTap: () => Navigator.of(context).pop(entry.id),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _CalloutListEntry extends StatelessWidget {
|
||||
const _CalloutListEntry({required this.callout, required this.onTap});
|
||||
|
||||
final ActiveCallout callout;
|
||||
final VoidCallback onTap;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = Theme.of(context);
|
||||
|
||||
return InkWell(
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
onTap: onTap,
|
||||
child: Container(
|
||||
padding: EdgeInsets.symmetric(horizontal: 1.2.h, vertical: 1.2.h),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
border: Border.all(color: Palette.line),
|
||||
),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
spacing: 1.2.h,
|
||||
children: [
|
||||
if (callout.iconUrl != null)
|
||||
CircleAvatar(
|
||||
radius: 2.2.h,
|
||||
backgroundColor: Palette.line,
|
||||
backgroundImage: NetworkImage(callout.iconUrl!),
|
||||
),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
spacing: 0.3.h,
|
||||
children: [
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text(
|
||||
callout.title,
|
||||
style: theme.textTheme.bodyMedium?.copyWith(
|
||||
color: Palette.ink,
|
||||
fontWeight: FontWeight.w700,
|
||||
),
|
||||
),
|
||||
),
|
||||
Text(
|
||||
timeago.format(callout.addedAt),
|
||||
style: theme.textTheme.bodySmall?.copyWith(
|
||||
color: Palette.ink.withValues(alpha: 0.45),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
Text(
|
||||
callout.description,
|
||||
style: theme.textTheme.bodySmall?.copyWith(
|
||||
color: Palette.ink.withValues(alpha: 0.65),
|
||||
height: 1.4,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:arbiter/features/callouts/callout_event.dart';
|
||||
import 'package:arbiter/proto/user_agent.pb.dart';
|
||||
import 'package:arbiter/providers/connection/connection_manager.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
|
||||
part 'sdk_connect_approve.g.dart';
|
||||
|
||||
@riverpod
|
||||
Stream<CalloutEvent> connectApproveEvents(Ref ref) async* {
|
||||
final connection = await ref.watch(connectionManagerProvider.future);
|
||||
if (connection == null) return;
|
||||
|
||||
await for (final message in connection.outOfBandMessages) {
|
||||
switch (message.whichPayload()) {
|
||||
case UserAgentResponse_Payload.sdkClientConnectionRequest:
|
||||
final body = message.sdkClientConnectionRequest;
|
||||
final id = base64Encode(body.pubkey);
|
||||
yield CalloutEvent.added(
|
||||
id: 'connect_approve:$id',
|
||||
data: CalloutData.connectApproval(
|
||||
pubkey: id,
|
||||
clientInfo: body.info,
|
||||
),
|
||||
);
|
||||
|
||||
case UserAgentResponse_Payload.sdkClientConnectionCancel:
|
||||
final id = base64Encode(message.sdkClientConnectionCancel.pubkey);
|
||||
yield CalloutEvent.cancelled(id: 'connect_approve:$id');
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> sendDecision(Ref ref, String pubkey, bool approved) async {
|
||||
final connection = await ref.watch(connectionManagerProvider.future);
|
||||
if (connection == null) return;
|
||||
|
||||
final bytes = base64Decode(pubkey);
|
||||
|
||||
final req = UserAgentRequest(sdkClientConnectionResponse: SdkClientConnectionResponse(
|
||||
approved: approved,
|
||||
pubkey: bytes
|
||||
));
|
||||
|
||||
await connection.tell(req);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'sdk_connect_approve.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// RiverpodGenerator
|
||||
// **************************************************************************
|
||||
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
// ignore_for_file: type=lint, type=warning
|
||||
|
||||
@ProviderFor(connectApproveEvents)
|
||||
final connectApproveEventsProvider = ConnectApproveEventsProvider._();
|
||||
|
||||
final class ConnectApproveEventsProvider
|
||||
extends
|
||||
$FunctionalProvider<
|
||||
AsyncValue<CalloutEvent>,
|
||||
CalloutEvent,
|
||||
Stream<CalloutEvent>
|
||||
>
|
||||
with $FutureModifier<CalloutEvent>, $StreamProvider<CalloutEvent> {
|
||||
ConnectApproveEventsProvider._()
|
||||
: super(
|
||||
from: null,
|
||||
argument: null,
|
||||
retry: null,
|
||||
name: r'connectApproveEventsProvider',
|
||||
isAutoDispose: true,
|
||||
dependencies: null,
|
||||
$allTransitiveDependencies: null,
|
||||
);
|
||||
|
||||
@override
|
||||
String debugGetCreateSourceHash() => _$connectApproveEventsHash();
|
||||
|
||||
@$internal
|
||||
@override
|
||||
$StreamProviderElement<CalloutEvent> $createElement(
|
||||
$ProviderPointer pointer,
|
||||
) => $StreamProviderElement(pointer);
|
||||
|
||||
@override
|
||||
Stream<CalloutEvent> create(Ref ref) {
|
||||
return connectApproveEvents(ref);
|
||||
}
|
||||
}
|
||||
|
||||
String _$connectApproveEventsHash() =>
|
||||
r'6a0998288afc0836a7c1701a983f64c33d318fd6';
|
||||
@@ -66,7 +66,7 @@ Future<Connection> connectAndAuthorize(
|
||||
KeyAlgorithm.ed25519 => KeyType.KEY_TYPE_ED25519,
|
||||
},
|
||||
);
|
||||
final response = await connection.request(
|
||||
final response = await connection.ask(
|
||||
UserAgentRequest(authChallengeRequest: req),
|
||||
);
|
||||
talker.info(
|
||||
@@ -94,7 +94,7 @@ Future<Connection> connectAndAuthorize(
|
||||
);
|
||||
|
||||
final signature = await key.sign(challenge);
|
||||
final solutionResponse = await connection.request(
|
||||
final solutionResponse = await connection.ask(
|
||||
UserAgentRequest(authChallengeSolution: AuthChallengeSolution(signature: signature)),
|
||||
);
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@ class Connection {
|
||||
|
||||
Stream<UserAgentResponse> get outOfBandMessages => _outOfBandMessages.stream;
|
||||
|
||||
Future<UserAgentResponse> request(UserAgentRequest message) async {
|
||||
Future<UserAgentResponse> ask(UserAgentRequest message) async {
|
||||
_ensureOpen();
|
||||
|
||||
final requestId = _nextRequestId++;
|
||||
@@ -49,7 +49,23 @@ class Connection {
|
||||
return completer.future;
|
||||
}
|
||||
|
||||
Future<void> tell(UserAgentRequest message) async {
|
||||
_ensureOpen();
|
||||
|
||||
final requestId = _nextRequestId++;
|
||||
message.id = requestId;
|
||||
|
||||
talker.debug('Sending message: ${message.toDebugString()}');
|
||||
|
||||
try {
|
||||
_tx.add(message);
|
||||
} catch (error, stackTrace) {
|
||||
talker.error('Failed to send message: $error', error, stackTrace);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> close() async {
|
||||
talker.debug('Closing connection...');
|
||||
final rxSubscription = _rxSubscription;
|
||||
if (rxSubscription == null) {
|
||||
return;
|
||||
@@ -86,6 +102,7 @@ class Connection {
|
||||
}
|
||||
|
||||
void _handleDone() {
|
||||
talker.debug('Connection closed by server.');
|
||||
if (_rxSubscription == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import 'package:arbiter/proto/user_agent.pb.dart';
|
||||
import 'package:protobuf/well_known_types/google/protobuf/empty.pb.dart';
|
||||
|
||||
Future<List<WalletEntry>> listEvmWallets(Connection connection) async {
|
||||
final response = await connection.request(
|
||||
final response = await connection.ask(
|
||||
UserAgentRequest(evmWalletList: Empty()),
|
||||
);
|
||||
if (!response.hasEvmWalletList()) {
|
||||
@@ -25,7 +25,7 @@ Future<List<WalletEntry>> listEvmWallets(Connection connection) async {
|
||||
}
|
||||
|
||||
Future<void> createEvmWallet(Connection connection) async {
|
||||
final response = await connection.request(
|
||||
final response = await connection.ask(
|
||||
UserAgentRequest(evmWalletCreate: Empty()),
|
||||
);
|
||||
if (!response.hasEvmWalletCreate()) {
|
||||
|
||||
@@ -7,7 +7,7 @@ import 'package:protobuf/well_known_types/google/protobuf/timestamp.pb.dart';
|
||||
Future<List<GrantEntry>> listEvmGrants(Connection connection) async {
|
||||
final request = EvmGrantListRequest();
|
||||
|
||||
final response = await connection.request(
|
||||
final response = await connection.ask(
|
||||
UserAgentRequest(evmGrantList: request),
|
||||
);
|
||||
if (!response.hasEvmGrantList()) {
|
||||
@@ -43,7 +43,7 @@ Future<int> createEvmGrant(
|
||||
}
|
||||
|
||||
Future<void> deleteEvmGrant(Connection connection, int grantId) async {
|
||||
final response = await connection.request(
|
||||
final response = await connection.ask(
|
||||
UserAgentRequest(evmGrantDelete: EvmGrantDeleteRequest(grantId: grantId)),
|
||||
);
|
||||
if (!response.hasEvmGrantDelete()) {
|
||||
|
||||
@@ -10,7 +10,7 @@ Future<BootstrapResult> bootstrapVault(
|
||||
) async {
|
||||
final encryptedKey = await _encryptVaultKeyMaterial(connection, password);
|
||||
|
||||
final response = await connection.request(
|
||||
final response = await connection.ask(
|
||||
UserAgentRequest(
|
||||
bootstrapEncryptedKey: BootstrapEncryptedKey(
|
||||
nonce: encryptedKey.nonce,
|
||||
@@ -31,7 +31,7 @@ Future<BootstrapResult> bootstrapVault(
|
||||
Future<UnsealResult> unsealVault(Connection connection, String password) async {
|
||||
final encryptedKey = await _encryptVaultKeyMaterial(connection, password);
|
||||
|
||||
final response = await connection.request(
|
||||
final response = await connection.ask(
|
||||
UserAgentRequest(
|
||||
unsealEncryptedKey: UnsealEncryptedKey(
|
||||
nonce: encryptedKey.nonce,
|
||||
@@ -56,7 +56,7 @@ Future<_EncryptedVaultKey> _encryptVaultKeyMaterial(
|
||||
final clientKeyPair = await keyExchange.newKeyPair();
|
||||
final clientPublicKey = await clientKeyPair.extractPublicKey();
|
||||
|
||||
final handshakeResponse = await connection.request(
|
||||
final handshakeResponse = await connection.ask(
|
||||
UserAgentRequest(unsealStart: UnsealStart(clientPubkey: clientPublicKey.bytes)),
|
||||
);
|
||||
if (!handshakeResponse.hasUnsealStartResponse()) {
|
||||
|
||||
@@ -14,7 +14,7 @@ class ConnectionManager extends _$ConnectionManager {
|
||||
Future<Connection?> build() async {
|
||||
final serverInfo = await ref.watch(serverInfoProvider.future);
|
||||
final key = await ref.watch(keyProvider.future);
|
||||
final token = ref.watch(bootstrapTokenProvider);
|
||||
final token = ref.read(bootstrapTokenProvider);
|
||||
|
||||
if (serverInfo == null || key == null) {
|
||||
return null;
|
||||
|
||||
@@ -12,7 +12,7 @@ Future<List<SdkClientEntry>?> sdkClients(Ref ref) async {
|
||||
return null;
|
||||
}
|
||||
|
||||
final resp = await connection.request(
|
||||
final resp = await connection.ask(
|
||||
UserAgentRequest(sdkClientList: Empty()),
|
||||
);
|
||||
|
||||
|
||||
@@ -48,4 +48,4 @@ final class SdkClientsProvider
|
||||
}
|
||||
}
|
||||
|
||||
String _$sdkClientsHash() => r'833c249d9cc2f83921453e0ece354a9a2d9f4482';
|
||||
String _$sdkClientsHash() => r'9b50ef901a7b68e4e604d6d0b4777dbd3e6499e1';
|
||||
|
||||
@@ -13,7 +13,7 @@ Future<VaultState?> vaultState(Ref ref) async {
|
||||
return null;
|
||||
}
|
||||
|
||||
final resp = await conn.request(UserAgentRequest(queryVaultState: Empty()));
|
||||
final resp = await conn.ask(UserAgentRequest(queryVaultState: Empty()));
|
||||
if (resp.whichPayload() != UserAgentResponse_Payload.vaultState) {
|
||||
talker.warning('Expected vault state response, got ${resp.whichPayload()}');
|
||||
return null;
|
||||
|
||||
@@ -46,4 +46,4 @@ final class VaultStateProvider
|
||||
}
|
||||
}
|
||||
|
||||
String _$vaultStateHash() => r'97085e49bc3a296e36fa6c04a8f4c9abafac0835';
|
||||
String _$vaultStateHash() => r'81887aa99a3e928efd73dbe85caf81284c9f5803';
|
||||
|
||||
151
useragent/lib/screens/callouts/sdk_connect.dart
Normal file
151
useragent/lib/screens/callouts/sdk_connect.dart
Normal file
@@ -0,0 +1,151 @@
|
||||
import 'package:arbiter/proto/client.pb.dart';
|
||||
import 'package:arbiter/theme/palette.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:sizer/sizer.dart';
|
||||
|
||||
class SdkConnectCallout extends StatelessWidget {
|
||||
const SdkConnectCallout({
|
||||
super.key,
|
||||
required this.pubkey,
|
||||
required this.clientInfo,
|
||||
this.onAccept,
|
||||
this.onDecline,
|
||||
});
|
||||
|
||||
final String pubkey;
|
||||
final ClientInfo clientInfo;
|
||||
final VoidCallback? onAccept;
|
||||
final VoidCallback? onDecline;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = Theme.of(context);
|
||||
|
||||
final name = clientInfo.hasName() && clientInfo.name.isNotEmpty
|
||||
? clientInfo.name
|
||||
: _shortPubkey(pubkey);
|
||||
|
||||
final hasDescription =
|
||||
clientInfo.hasDescription() && clientInfo.description.isNotEmpty;
|
||||
final hasVersion =
|
||||
clientInfo.hasVersion() && clientInfo.version.isNotEmpty;
|
||||
final showInfoCard = hasDescription || hasVersion;
|
||||
|
||||
return Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Palette.cream,
|
||||
borderRadius: BorderRadius.circular(24),
|
||||
border: Border.all(color: Palette.line),
|
||||
),
|
||||
padding: EdgeInsets.all(2.4.h),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
spacing: 1.6.h,
|
||||
children: [
|
||||
// if (clientInfo.iconUrl != null)
|
||||
// CircleAvatar(
|
||||
// radius: 36,
|
||||
// backgroundColor: Palette.line,
|
||||
// backgroundImage: NetworkImage(iconUrl!),
|
||||
// ),
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
spacing: 0.4.h,
|
||||
children: [
|
||||
Text(
|
||||
name,
|
||||
textAlign: TextAlign.center,
|
||||
style: theme.textTheme.titleLarge?.copyWith(
|
||||
color: Palette.ink,
|
||||
fontWeight: FontWeight.w800,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
'is requesting a connection',
|
||||
textAlign: TextAlign.center,
|
||||
style: theme.textTheme.bodyMedium?.copyWith(
|
||||
color: Palette.ink.withValues(alpha: 0.55),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
if (showInfoCard)
|
||||
Container(
|
||||
width: double.infinity,
|
||||
decoration: BoxDecoration(
|
||||
color: Palette.ink.withValues(alpha: 0.04),
|
||||
borderRadius: BorderRadius.circular(14),
|
||||
border: Border.all(color: Palette.line),
|
||||
),
|
||||
padding: EdgeInsets.symmetric(
|
||||
horizontal: 1.6.w,
|
||||
vertical: 1.2.h,
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
spacing: 0.6.h,
|
||||
children: [
|
||||
if (hasDescription)
|
||||
Text(
|
||||
clientInfo.description,
|
||||
style: theme.textTheme.bodyMedium?.copyWith(
|
||||
color: Palette.ink.withValues(alpha: 0.80),
|
||||
height: 1.5,
|
||||
),
|
||||
),
|
||||
if (hasVersion)
|
||||
Text(
|
||||
'v${clientInfo.version}',
|
||||
style: theme.textTheme.bodySmall?.copyWith(
|
||||
color: Palette.ink.withValues(alpha: 0.50),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
children: [
|
||||
Expanded(
|
||||
child: OutlinedButton(
|
||||
onPressed: onDecline,
|
||||
style: OutlinedButton.styleFrom(
|
||||
foregroundColor: Palette.coral,
|
||||
side: BorderSide(
|
||||
color: Palette.coral.withValues(alpha: 0.50),
|
||||
),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(14),
|
||||
),
|
||||
padding: EdgeInsets.symmetric(vertical: 1.4.h),
|
||||
),
|
||||
child: const Text('Decline'),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: FilledButton(
|
||||
onPressed: onAccept,
|
||||
style: FilledButton.styleFrom(
|
||||
backgroundColor: Palette.ink,
|
||||
foregroundColor: Palette.cream,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(14),
|
||||
),
|
||||
padding: EdgeInsets.symmetric(vertical: 1.4.h),
|
||||
),
|
||||
child: const Text('Accept'),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
String _shortPubkey(String base64Pubkey) {
|
||||
if (base64Pubkey.length < 12) return base64Pubkey;
|
||||
return '${base64Pubkey.substring(0, 8)}…${base64Pubkey.substring(base64Pubkey.length - 4)}';
|
||||
}
|
||||
@@ -1,7 +1,11 @@
|
||||
import 'package:arbiter/features/callouts/callout_manager.dart';
|
||||
import 'package:arbiter/features/callouts/show_callout_list.dart';
|
||||
import 'package:arbiter/router.gr.dart';
|
||||
import 'package:arbiter/theme/palette.dart';
|
||||
import 'package:auto_route/auto_route.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_adaptive_scaffold/flutter_adaptive_scaffold.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
|
||||
const breakpoints = MaterialAdaptiveBreakpoints();
|
||||
|
||||
@@ -17,7 +21,6 @@ class DashboardRouter extends StatelessWidget {
|
||||
routes: routes,
|
||||
transitionBuilder: (context, child, animation) => FadeTransition(
|
||||
opacity: animation,
|
||||
// the passed child is technically our animated selected-tab page
|
||||
child: child,
|
||||
),
|
||||
builder: (context, child) {
|
||||
@@ -53,8 +56,54 @@ class DashboardRouter extends StatelessWidget {
|
||||
selectedIndex: currentActive,
|
||||
transitionDuration: const Duration(milliseconds: 800),
|
||||
internalAnimations: true,
|
||||
trailingNavRail: const _CalloutBell(),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _CalloutBell extends ConsumerWidget {
|
||||
const _CalloutBell({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final count = ref.watch(
|
||||
calloutManagerProvider.select((map) => map.length),
|
||||
);
|
||||
|
||||
return IconButton(
|
||||
onPressed: () => showCalloutList(context, ref),
|
||||
icon: Stack(
|
||||
clipBehavior: Clip.none,
|
||||
children: [
|
||||
Icon(
|
||||
count > 0 ? Icons.notifications : Icons.notifications_outlined,
|
||||
color: Palette.ink,
|
||||
),
|
||||
if (count > 0)
|
||||
Positioned(
|
||||
top: -2,
|
||||
right: -4,
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 4, vertical: 1),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.green,
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
),
|
||||
child: Text(
|
||||
count > 99 ? '99+' : '$count',
|
||||
style: const TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 9,
|
||||
fontWeight: FontWeight.w800,
|
||||
height: 1.2,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,17 +8,9 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:arbiter/theme/palette.dart';
|
||||
import 'package:sizer/sizer.dart';
|
||||
|
||||
// ─── Palette ──────────────────────────────────────────────────────────────────
|
||||
|
||||
class _Palette {
|
||||
static const ink = Color(0xFF15263C);
|
||||
static const coral = Color(0xFFE26254);
|
||||
static const cream = Color(0xFFFFFAF4);
|
||||
static const line = Color(0x1A15263C);
|
||||
}
|
||||
|
||||
// ─── Column width getters ─────────────────────────────────────────────────────
|
||||
|
||||
double get _accentStripWidth => 0.8.w;
|
||||
@@ -92,8 +84,8 @@ class _StatePanel extends StatelessWidget {
|
||||
return Container(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(24),
|
||||
color: _Palette.cream.withValues(alpha: 0.92),
|
||||
border: Border.all(color: _Palette.line),
|
||||
color: Palette.cream.withValues(alpha: 0.92),
|
||||
border: Border.all(color: Palette.line),
|
||||
),
|
||||
child: Padding(
|
||||
padding: EdgeInsets.all(2.8.h),
|
||||
@@ -107,12 +99,12 @@ class _StatePanel extends StatelessWidget {
|
||||
child: const CircularProgressIndicator(strokeWidth: 2.5),
|
||||
)
|
||||
else
|
||||
Icon(icon, size: 34, color: _Palette.coral),
|
||||
Icon(icon, size: 34, color: Palette.coral),
|
||||
SizedBox(height: 1.8.h),
|
||||
Text(
|
||||
title,
|
||||
style: theme.textTheme.headlineSmall?.copyWith(
|
||||
color: _Palette.ink,
|
||||
color: Palette.ink,
|
||||
fontWeight: FontWeight.w800,
|
||||
),
|
||||
),
|
||||
@@ -120,7 +112,7 @@ class _StatePanel extends StatelessWidget {
|
||||
Text(
|
||||
body,
|
||||
style: theme.textTheme.bodyLarge?.copyWith(
|
||||
color: _Palette.ink.withValues(alpha: 0.72),
|
||||
color: Palette.ink.withValues(alpha: 0.72),
|
||||
height: 1.5,
|
||||
),
|
||||
),
|
||||
@@ -155,8 +147,8 @@ class _Header extends StatelessWidget {
|
||||
padding: EdgeInsets.symmetric(horizontal: 1.6.w, vertical: 1.2.h),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(18),
|
||||
color: _Palette.cream,
|
||||
border: Border.all(color: _Palette.line),
|
||||
color: Palette.cream,
|
||||
border: Border.all(color: Palette.line),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
@@ -164,7 +156,7 @@ class _Header extends StatelessWidget {
|
||||
child: Text(
|
||||
'SDK Clients',
|
||||
style: theme.textTheme.titleMedium?.copyWith(
|
||||
color: _Palette.ink,
|
||||
color: Palette.ink,
|
||||
fontWeight: FontWeight.w800,
|
||||
),
|
||||
),
|
||||
@@ -173,7 +165,7 @@ class _Header extends StatelessWidget {
|
||||
Text(
|
||||
'Syncing',
|
||||
style: theme.textTheme.bodySmall?.copyWith(
|
||||
color: _Palette.ink.withValues(alpha: 0.62),
|
||||
color: Palette.ink.withValues(alpha: 0.62),
|
||||
fontWeight: FontWeight.w700,
|
||||
),
|
||||
),
|
||||
@@ -182,8 +174,8 @@ class _Header extends StatelessWidget {
|
||||
OutlinedButton.icon(
|
||||
onPressed: () => onRefresh(),
|
||||
style: OutlinedButton.styleFrom(
|
||||
foregroundColor: _Palette.ink,
|
||||
side: BorderSide(color: _Palette.line),
|
||||
foregroundColor: Palette.ink,
|
||||
side: BorderSide(color: Palette.line),
|
||||
padding: EdgeInsets.symmetric(
|
||||
horizontal: 1.4.w,
|
||||
vertical: 1.2.h,
|
||||
@@ -209,7 +201,7 @@ class _ClientTableHeader extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final style = Theme.of(context).textTheme.labelLarge?.copyWith(
|
||||
color: _Palette.ink.withValues(alpha: 0.72),
|
||||
color: Palette.ink.withValues(alpha: 0.72),
|
||||
fontWeight: FontWeight.w800,
|
||||
letterSpacing: 0.3,
|
||||
);
|
||||
@@ -218,7 +210,7 @@ class _ClientTableHeader extends StatelessWidget {
|
||||
padding: EdgeInsets.symmetric(vertical: 1.4.h),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
color: _Palette.ink.withValues(alpha: 0.04),
|
||||
color: Palette.ink.withValues(alpha: 0.04),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
@@ -257,7 +249,7 @@ class _ClientTableRow extends HookWidget {
|
||||
final expanded = useState(false);
|
||||
final accent = _accentColor(client.pubkey);
|
||||
final theme = Theme.of(context);
|
||||
final muted = _Palette.ink.withValues(alpha: 0.62);
|
||||
final muted = Palette.ink.withValues(alpha: 0.62);
|
||||
|
||||
final name = client.info.name.isEmpty ? '—' : client.info.name;
|
||||
final version = client.info.version.isEmpty ? '—' : client.info.version;
|
||||
@@ -301,7 +293,7 @@ class _ClientTableRow extends HookWidget {
|
||||
child: Text(
|
||||
'${client.id}',
|
||||
style: theme.textTheme.bodyLarge?.copyWith(
|
||||
color: _Palette.ink,
|
||||
color: Palette.ink,
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -313,7 +305,7 @@ class _ClientTableRow extends HookWidget {
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: theme.textTheme.bodyMedium?.copyWith(
|
||||
color: _Palette.ink,
|
||||
color: Palette.ink,
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -395,7 +387,7 @@ class _ClientTableRow extends HookWidget {
|
||||
child: Text(
|
||||
_shortPubkey(client.pubkey),
|
||||
style: theme.textTheme.bodySmall?.copyWith(
|
||||
color: _Palette.ink,
|
||||
color: Palette.ink,
|
||||
fontFamily: 'monospace',
|
||||
),
|
||||
),
|
||||
@@ -444,8 +436,8 @@ class _ClientTable extends StatelessWidget {
|
||||
return Container(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(24),
|
||||
color: _Palette.cream.withValues(alpha: 0.92),
|
||||
border: Border.all(color: _Palette.line),
|
||||
color: Palette.cream.withValues(alpha: 0.92),
|
||||
border: Border.all(color: Palette.line),
|
||||
),
|
||||
child: Padding(
|
||||
padding: EdgeInsets.all(2.h),
|
||||
@@ -459,7 +451,7 @@ class _ClientTable extends StatelessWidget {
|
||||
Text(
|
||||
'Registered clients',
|
||||
style: theme.textTheme.titleLarge?.copyWith(
|
||||
color: _Palette.ink,
|
||||
color: Palette.ink,
|
||||
fontWeight: FontWeight.w800,
|
||||
),
|
||||
),
|
||||
@@ -467,7 +459,7 @@ class _ClientTable extends StatelessWidget {
|
||||
Text(
|
||||
'Every entry here has authenticated with Arbiter at least once.',
|
||||
style: theme.textTheme.bodyMedium?.copyWith(
|
||||
color: _Palette.ink.withValues(alpha: 0.70),
|
||||
color: Palette.ink.withValues(alpha: 0.70),
|
||||
height: 1.4,
|
||||
),
|
||||
),
|
||||
@@ -564,7 +556,7 @@ class ClientsScreen extends HookConsumerWidget {
|
||||
return Scaffold(
|
||||
body: SafeArea(
|
||||
child: RefreshIndicator.adaptive(
|
||||
color: _Palette.ink,
|
||||
color: Palette.ink,
|
||||
backgroundColor: Colors.white,
|
||||
onRefresh: refresh,
|
||||
child: ListView(
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import 'dart:math' as math;
|
||||
|
||||
import 'package:arbiter/proto/evm.pb.dart';
|
||||
import 'package:arbiter/theme/palette.dart';
|
||||
import 'package:arbiter/providers/connection/connection_manager.dart';
|
||||
import 'package:arbiter/providers/evm/evm.dart';
|
||||
import 'package:auto_route/auto_route.dart';
|
||||
@@ -89,7 +90,7 @@ class EvmScreen extends HookConsumerWidget {
|
||||
return Scaffold(
|
||||
body: SafeArea(
|
||||
child: RefreshIndicator.adaptive(
|
||||
color: _Palette.ink,
|
||||
color: Palette.ink,
|
||||
backgroundColor: Colors.white,
|
||||
onRefresh: refreshWallets,
|
||||
child: ListView(
|
||||
@@ -114,13 +115,6 @@ class EvmScreen extends HookConsumerWidget {
|
||||
}
|
||||
}
|
||||
|
||||
class _Palette {
|
||||
static const ink = Color(0xFF15263C);
|
||||
static const coral = Color(0xFFE26254);
|
||||
static const cream = Color(0xFFFFFAF4);
|
||||
static const line = Color(0x1A15263C);
|
||||
}
|
||||
|
||||
double get _accentStripWidth => 0.8.w;
|
||||
double get _cellHorizontalPadding => 1.8.w;
|
||||
double get _walletColumnWidth => 18.w;
|
||||
@@ -148,8 +142,8 @@ class _Header extends StatelessWidget {
|
||||
padding: EdgeInsets.symmetric(horizontal: 1.6.w, vertical: 1.2.h),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(18),
|
||||
color: _Palette.cream,
|
||||
border: Border.all(color: _Palette.line),
|
||||
color: Palette.cream,
|
||||
border: Border.all(color: Palette.line),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
@@ -157,7 +151,7 @@ class _Header extends StatelessWidget {
|
||||
child: Text(
|
||||
'EVM Wallet Vault',
|
||||
style: theme.textTheme.titleMedium?.copyWith(
|
||||
color: _Palette.ink,
|
||||
color: Palette.ink,
|
||||
fontWeight: FontWeight.w800,
|
||||
),
|
||||
),
|
||||
@@ -166,7 +160,7 @@ class _Header extends StatelessWidget {
|
||||
Text(
|
||||
'Syncing',
|
||||
style: theme.textTheme.bodySmall?.copyWith(
|
||||
color: _Palette.ink.withValues(alpha: 0.62),
|
||||
color: Palette.ink.withValues(alpha: 0.62),
|
||||
fontWeight: FontWeight.w700,
|
||||
),
|
||||
),
|
||||
@@ -175,7 +169,7 @@ class _Header extends StatelessWidget {
|
||||
FilledButton.icon(
|
||||
onPressed: isCreating ? null : () => onCreate(),
|
||||
style: FilledButton.styleFrom(
|
||||
backgroundColor: _Palette.ink,
|
||||
backgroundColor: Palette.ink,
|
||||
foregroundColor: Colors.white,
|
||||
padding: EdgeInsets.symmetric(horizontal: 1.4.w, vertical: 1.2.h),
|
||||
shape: RoundedRectangleBorder(
|
||||
@@ -195,8 +189,8 @@ class _Header extends StatelessWidget {
|
||||
OutlinedButton.icon(
|
||||
onPressed: () => onRefresh(),
|
||||
style: OutlinedButton.styleFrom(
|
||||
foregroundColor: _Palette.ink,
|
||||
side: BorderSide(color: _Palette.line),
|
||||
foregroundColor: Palette.ink,
|
||||
side: BorderSide(color: Palette.line),
|
||||
padding: EdgeInsets.symmetric(horizontal: 1.4.w, vertical: 1.2.h),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(14),
|
||||
@@ -223,8 +217,8 @@ class _WalletTable extends StatelessWidget {
|
||||
return Container(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(24),
|
||||
color: _Palette.cream.withValues(alpha: 0.92),
|
||||
border: Border.all(color: _Palette.line),
|
||||
color: Palette.cream.withValues(alpha: 0.92),
|
||||
border: Border.all(color: Palette.line),
|
||||
),
|
||||
child: Padding(
|
||||
padding: EdgeInsets.all(2.h),
|
||||
@@ -238,7 +232,7 @@ class _WalletTable extends StatelessWidget {
|
||||
Text(
|
||||
'Managed wallets',
|
||||
style: theme.textTheme.titleLarge?.copyWith(
|
||||
color: _Palette.ink,
|
||||
color: Palette.ink,
|
||||
fontWeight: FontWeight.w800,
|
||||
),
|
||||
),
|
||||
@@ -246,7 +240,7 @@ class _WalletTable extends StatelessWidget {
|
||||
Text(
|
||||
'Every address here is generated and held by Arbiter.',
|
||||
style: theme.textTheme.bodyMedium?.copyWith(
|
||||
color: _Palette.ink.withValues(alpha: 0.70),
|
||||
color: Palette.ink.withValues(alpha: 0.70),
|
||||
height: 1.4,
|
||||
),
|
||||
),
|
||||
@@ -288,7 +282,7 @@ class _WalletTableHeader extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final style = Theme.of(context).textTheme.labelLarge?.copyWith(
|
||||
color: _Palette.ink.withValues(alpha: 0.72),
|
||||
color: Palette.ink.withValues(alpha: 0.72),
|
||||
fontWeight: FontWeight.w800,
|
||||
letterSpacing: 0.3,
|
||||
);
|
||||
@@ -297,7 +291,7 @@ class _WalletTableHeader extends StatelessWidget {
|
||||
padding: EdgeInsets.symmetric(vertical: 1.4.h),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
color: _Palette.ink.withValues(alpha: 0.04),
|
||||
color: Palette.ink.withValues(alpha: 0.04),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
@@ -328,10 +322,10 @@ class _WalletTableRow extends StatelessWidget {
|
||||
final rowHeight = 5.h;
|
||||
final walletStyle = Theme.of(
|
||||
context,
|
||||
).textTheme.bodyLarge?.copyWith(color: _Palette.ink);
|
||||
).textTheme.bodyLarge?.copyWith(color: Palette.ink);
|
||||
final addressStyle = Theme.of(
|
||||
context,
|
||||
).textTheme.bodyMedium?.copyWith(color: _Palette.ink);
|
||||
).textTheme.bodyMedium?.copyWith(color: Palette.ink);
|
||||
|
||||
return Container(
|
||||
height: rowHeight,
|
||||
@@ -420,8 +414,8 @@ class _StatePanel extends StatelessWidget {
|
||||
return Container(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(24),
|
||||
color: _Palette.cream.withValues(alpha: 0.92),
|
||||
border: Border.all(color: _Palette.line),
|
||||
color: Palette.cream.withValues(alpha: 0.92),
|
||||
border: Border.all(color: Palette.line),
|
||||
),
|
||||
child: Padding(
|
||||
padding: EdgeInsets.all(2.8.h),
|
||||
@@ -435,12 +429,12 @@ class _StatePanel extends StatelessWidget {
|
||||
child: CircularProgressIndicator(strokeWidth: 2.5),
|
||||
)
|
||||
else
|
||||
Icon(icon, size: 34, color: _Palette.coral),
|
||||
Icon(icon, size: 34, color: Palette.coral),
|
||||
SizedBox(height: 1.8.h),
|
||||
Text(
|
||||
title,
|
||||
style: theme.textTheme.headlineSmall?.copyWith(
|
||||
color: _Palette.ink,
|
||||
color: Palette.ink,
|
||||
fontWeight: FontWeight.w800,
|
||||
),
|
||||
),
|
||||
@@ -448,7 +442,7 @@ class _StatePanel extends StatelessWidget {
|
||||
Text(
|
||||
body,
|
||||
style: theme.textTheme.bodyLarge?.copyWith(
|
||||
color: _Palette.ink.withValues(alpha: 0.72),
|
||||
color: Palette.ink.withValues(alpha: 0.72),
|
||||
height: 1.5,
|
||||
),
|
||||
),
|
||||
|
||||
8
useragent/lib/theme/palette.dart
Normal file
8
useragent/lib/theme/palette.dart
Normal file
@@ -0,0 +1,8 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class Palette {
|
||||
static const ink = Color(0xFF15263C);
|
||||
static const coral = Color(0xFFE26254);
|
||||
static const cream = Color(0xFFFFFAF4);
|
||||
static const line = Color(0x1A15263C);
|
||||
}
|
||||
@@ -581,6 +581,7 @@
|
||||
DEVELOPMENT_TEAM = 8L884L537J;
|
||||
ENABLE_APP_SANDBOX = YES;
|
||||
ENABLE_INCOMING_NETWORK_CONNECTIONS = YES;
|
||||
ENABLE_OUTGOING_NETWORK_CONNECTIONS = YES;
|
||||
INFOPLIST_FILE = Runner/Info.plist;
|
||||
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.productivity";
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
@@ -724,6 +725,7 @@
|
||||
DEVELOPMENT_TEAM = 8L884L537J;
|
||||
ENABLE_APP_SANDBOX = YES;
|
||||
ENABLE_INCOMING_NETWORK_CONNECTIONS = YES;
|
||||
ENABLE_OUTGOING_NETWORK_CONNECTIONS = YES;
|
||||
INFOPLIST_FILE = Runner/Info.plist;
|
||||
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.productivity";
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
@@ -752,6 +754,7 @@
|
||||
DEVELOPMENT_TEAM = 8L884L537J;
|
||||
ENABLE_APP_SANDBOX = YES;
|
||||
ENABLE_INCOMING_NETWORK_CONNECTIONS = YES;
|
||||
ENABLE_OUTGOING_NETWORK_CONNECTIONS = YES;
|
||||
INFOPLIST_FILE = Runner/Info.plist;
|
||||
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.productivity";
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
|
||||
@@ -4,7 +4,5 @@
|
||||
<dict>
|
||||
<key>keychain-access-groups</key>
|
||||
<array/>
|
||||
<key>com.apple.security.network.client</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
|
||||
@@ -2,11 +2,7 @@
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>com.apple.security.app-sandbox</key>
|
||||
<true/>
|
||||
<key>keychain-access-groups</key>
|
||||
<array/>
|
||||
<key>com.apple.security.network.client</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
|
||||
@@ -529,6 +529,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.1.2"
|
||||
intl:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: intl
|
||||
sha256: "3df61194eb431efc39c4ceba583b95633a403f46c9fd341e550ce0bfa50e9aa5"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.20.2"
|
||||
io:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -1062,6 +1070,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.6.12"
|
||||
timeago:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: timeago
|
||||
sha256: b05159406a97e1cbb2b9ee4faa9fb096fe0e2dfcd8b08fcd2a00553450d3422e
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.7.1"
|
||||
typed_data:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
||||
@@ -33,6 +33,7 @@ dependencies:
|
||||
protobuf: ^6.0.0
|
||||
freezed_annotation: ^3.1.0
|
||||
json_annotation: ^4.9.0
|
||||
timeago: ^3.7.1
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
|
||||
Reference in New Issue
Block a user