Compare commits
6 Commits
3f9881e943
...
3.3.0+146
| Author | SHA1 | Date | |
|---|---|---|---|
|
24791b3293
|
|||
|
3ac263d483
|
|||
|
2445d8adf8
|
|||
|
d4f95bbbf4
|
|||
|
943e4b7b5c
|
|||
|
7edc02a1d3
|
@@ -1333,5 +1333,8 @@
|
||||
"fund": "Fund",
|
||||
"fundsRecent": "Recent Funds",
|
||||
"fundCreateNew": "Create New",
|
||||
"fundCreateNewHint": "Create a new fund for your message. Select recipients and amount."
|
||||
"fundCreateNewHint": "Create a new fund for your message. Select recipients and amount.",
|
||||
"amountOfSplits": "Amount of Splits",
|
||||
"enterNumberOfSplits": "Enter Splits Amount",
|
||||
"orCreateWith": "Or\ncreate with"
|
||||
}
|
||||
|
||||
@@ -176,6 +176,8 @@ sealed class SnWalletFund with _$SnWalletFund {
|
||||
required String id,
|
||||
required String currency,
|
||||
required double totalAmount,
|
||||
required double remainingAmount,
|
||||
required int amountOfSplits,
|
||||
required int splitType, // 0: even, 1: random
|
||||
required int
|
||||
status, // 0: created, 1: partially claimed, 2: fully claimed, 3: expired
|
||||
@@ -184,6 +186,7 @@ sealed class SnWalletFund with _$SnWalletFund {
|
||||
required SnAccount? creatorAccount,
|
||||
required DateTime expiredAt,
|
||||
required List<SnWalletFundRecipient> recipients,
|
||||
required bool isOpen,
|
||||
required DateTime createdAt,
|
||||
required DateTime updatedAt,
|
||||
required DateTime? deletedAt,
|
||||
|
||||
@@ -2553,9 +2553,9 @@ $SnWalletSubscriptionCopyWith<$Res>? get subscription {
|
||||
/// @nodoc
|
||||
mixin _$SnWalletFund {
|
||||
|
||||
String get id; String get currency; double get totalAmount; int get splitType;// 0: even, 1: random
|
||||
String get id; String get currency; double get totalAmount; double get remainingAmount; int get amountOfSplits; int get splitType;// 0: even, 1: random
|
||||
int get status;// 0: created, 1: partially claimed, 2: fully claimed, 3: expired
|
||||
String? get message; String get creatorAccountId; SnAccount? get creatorAccount; DateTime get expiredAt; List<SnWalletFundRecipient> get recipients; DateTime get createdAt; DateTime get updatedAt; DateTime? get deletedAt;
|
||||
String? get message; String get creatorAccountId; SnAccount? get creatorAccount; DateTime get expiredAt; List<SnWalletFundRecipient> get recipients; bool get isOpen; DateTime get createdAt; DateTime get updatedAt; DateTime? get deletedAt;
|
||||
/// Create a copy of SnWalletFund
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@@ -2568,16 +2568,16 @@ $SnWalletFundCopyWith<SnWalletFund> get copyWith => _$SnWalletFundCopyWithImpl<S
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is SnWalletFund&&(identical(other.id, id) || other.id == id)&&(identical(other.currency, currency) || other.currency == currency)&&(identical(other.totalAmount, totalAmount) || other.totalAmount == totalAmount)&&(identical(other.splitType, splitType) || other.splitType == splitType)&&(identical(other.status, status) || other.status == status)&&(identical(other.message, message) || other.message == message)&&(identical(other.creatorAccountId, creatorAccountId) || other.creatorAccountId == creatorAccountId)&&(identical(other.creatorAccount, creatorAccount) || other.creatorAccount == creatorAccount)&&(identical(other.expiredAt, expiredAt) || other.expiredAt == expiredAt)&&const DeepCollectionEquality().equals(other.recipients, recipients)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt));
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is SnWalletFund&&(identical(other.id, id) || other.id == id)&&(identical(other.currency, currency) || other.currency == currency)&&(identical(other.totalAmount, totalAmount) || other.totalAmount == totalAmount)&&(identical(other.remainingAmount, remainingAmount) || other.remainingAmount == remainingAmount)&&(identical(other.amountOfSplits, amountOfSplits) || other.amountOfSplits == amountOfSplits)&&(identical(other.splitType, splitType) || other.splitType == splitType)&&(identical(other.status, status) || other.status == status)&&(identical(other.message, message) || other.message == message)&&(identical(other.creatorAccountId, creatorAccountId) || other.creatorAccountId == creatorAccountId)&&(identical(other.creatorAccount, creatorAccount) || other.creatorAccount == creatorAccount)&&(identical(other.expiredAt, expiredAt) || other.expiredAt == expiredAt)&&const DeepCollectionEquality().equals(other.recipients, recipients)&&(identical(other.isOpen, isOpen) || other.isOpen == isOpen)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt));
|
||||
}
|
||||
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@override
|
||||
int get hashCode => Object.hash(runtimeType,id,currency,totalAmount,splitType,status,message,creatorAccountId,creatorAccount,expiredAt,const DeepCollectionEquality().hash(recipients),createdAt,updatedAt,deletedAt);
|
||||
int get hashCode => Object.hash(runtimeType,id,currency,totalAmount,remainingAmount,amountOfSplits,splitType,status,message,creatorAccountId,creatorAccount,expiredAt,const DeepCollectionEquality().hash(recipients),isOpen,createdAt,updatedAt,deletedAt);
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'SnWalletFund(id: $id, currency: $currency, totalAmount: $totalAmount, splitType: $splitType, status: $status, message: $message, creatorAccountId: $creatorAccountId, creatorAccount: $creatorAccount, expiredAt: $expiredAt, recipients: $recipients, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt)';
|
||||
return 'SnWalletFund(id: $id, currency: $currency, totalAmount: $totalAmount, remainingAmount: $remainingAmount, amountOfSplits: $amountOfSplits, splitType: $splitType, status: $status, message: $message, creatorAccountId: $creatorAccountId, creatorAccount: $creatorAccount, expiredAt: $expiredAt, recipients: $recipients, isOpen: $isOpen, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt)';
|
||||
}
|
||||
|
||||
|
||||
@@ -2588,7 +2588,7 @@ abstract mixin class $SnWalletFundCopyWith<$Res> {
|
||||
factory $SnWalletFundCopyWith(SnWalletFund value, $Res Function(SnWalletFund) _then) = _$SnWalletFundCopyWithImpl;
|
||||
@useResult
|
||||
$Res call({
|
||||
String id, String currency, double totalAmount, int splitType, int status, String? message, String creatorAccountId, SnAccount? creatorAccount, DateTime expiredAt, List<SnWalletFundRecipient> recipients, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt
|
||||
String id, String currency, double totalAmount, double remainingAmount, int amountOfSplits, int splitType, int status, String? message, String creatorAccountId, SnAccount? creatorAccount, DateTime expiredAt, List<SnWalletFundRecipient> recipients, bool isOpen, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt
|
||||
});
|
||||
|
||||
|
||||
@@ -2605,19 +2605,22 @@ class _$SnWalletFundCopyWithImpl<$Res>
|
||||
|
||||
/// Create a copy of SnWalletFund
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? currency = null,Object? totalAmount = null,Object? splitType = null,Object? status = null,Object? message = freezed,Object? creatorAccountId = null,Object? creatorAccount = freezed,Object? expiredAt = null,Object? recipients = null,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,}) {
|
||||
@pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? currency = null,Object? totalAmount = null,Object? remainingAmount = null,Object? amountOfSplits = null,Object? splitType = null,Object? status = null,Object? message = freezed,Object? creatorAccountId = null,Object? creatorAccount = freezed,Object? expiredAt = null,Object? recipients = null,Object? isOpen = null,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,}) {
|
||||
return _then(_self.copyWith(
|
||||
id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
|
||||
as String,currency: null == currency ? _self.currency : currency // ignore: cast_nullable_to_non_nullable
|
||||
as String,totalAmount: null == totalAmount ? _self.totalAmount : totalAmount // ignore: cast_nullable_to_non_nullable
|
||||
as double,splitType: null == splitType ? _self.splitType : splitType // ignore: cast_nullable_to_non_nullable
|
||||
as double,remainingAmount: null == remainingAmount ? _self.remainingAmount : remainingAmount // ignore: cast_nullable_to_non_nullable
|
||||
as double,amountOfSplits: null == amountOfSplits ? _self.amountOfSplits : amountOfSplits // ignore: cast_nullable_to_non_nullable
|
||||
as int,splitType: null == splitType ? _self.splitType : splitType // ignore: cast_nullable_to_non_nullable
|
||||
as int,status: null == status ? _self.status : status // ignore: cast_nullable_to_non_nullable
|
||||
as int,message: freezed == message ? _self.message : message // ignore: cast_nullable_to_non_nullable
|
||||
as String?,creatorAccountId: null == creatorAccountId ? _self.creatorAccountId : creatorAccountId // ignore: cast_nullable_to_non_nullable
|
||||
as String,creatorAccount: freezed == creatorAccount ? _self.creatorAccount : creatorAccount // ignore: cast_nullable_to_non_nullable
|
||||
as SnAccount?,expiredAt: null == expiredAt ? _self.expiredAt : expiredAt // ignore: cast_nullable_to_non_nullable
|
||||
as DateTime,recipients: null == recipients ? _self.recipients : recipients // ignore: cast_nullable_to_non_nullable
|
||||
as List<SnWalletFundRecipient>,createdAt: null == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable
|
||||
as List<SnWalletFundRecipient>,isOpen: null == isOpen ? _self.isOpen : isOpen // ignore: cast_nullable_to_non_nullable
|
||||
as bool,createdAt: null == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable
|
||||
as DateTime,updatedAt: null == updatedAt ? _self.updatedAt : updatedAt // ignore: cast_nullable_to_non_nullable
|
||||
as DateTime,deletedAt: freezed == deletedAt ? _self.deletedAt : deletedAt // ignore: cast_nullable_to_non_nullable
|
||||
as DateTime?,
|
||||
@@ -2714,10 +2717,10 @@ return $default(_that);case _:
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String id, String currency, double totalAmount, int splitType, int status, String? message, String creatorAccountId, SnAccount? creatorAccount, DateTime expiredAt, List<SnWalletFundRecipient> recipients, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt)? $default,{required TResult orElse(),}) {final _that = this;
|
||||
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String id, String currency, double totalAmount, double remainingAmount, int amountOfSplits, int splitType, int status, String? message, String creatorAccountId, SnAccount? creatorAccount, DateTime expiredAt, List<SnWalletFundRecipient> recipients, bool isOpen, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt)? $default,{required TResult orElse(),}) {final _that = this;
|
||||
switch (_that) {
|
||||
case _SnWalletFund() when $default != null:
|
||||
return $default(_that.id,_that.currency,_that.totalAmount,_that.splitType,_that.status,_that.message,_that.creatorAccountId,_that.creatorAccount,_that.expiredAt,_that.recipients,_that.createdAt,_that.updatedAt,_that.deletedAt);case _:
|
||||
return $default(_that.id,_that.currency,_that.totalAmount,_that.remainingAmount,_that.amountOfSplits,_that.splitType,_that.status,_that.message,_that.creatorAccountId,_that.creatorAccount,_that.expiredAt,_that.recipients,_that.isOpen,_that.createdAt,_that.updatedAt,_that.deletedAt);case _:
|
||||
return orElse();
|
||||
|
||||
}
|
||||
@@ -2735,10 +2738,10 @@ return $default(_that.id,_that.currency,_that.totalAmount,_that.splitType,_that.
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String id, String currency, double totalAmount, int splitType, int status, String? message, String creatorAccountId, SnAccount? creatorAccount, DateTime expiredAt, List<SnWalletFundRecipient> recipients, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt) $default,) {final _that = this;
|
||||
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String id, String currency, double totalAmount, double remainingAmount, int amountOfSplits, int splitType, int status, String? message, String creatorAccountId, SnAccount? creatorAccount, DateTime expiredAt, List<SnWalletFundRecipient> recipients, bool isOpen, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt) $default,) {final _that = this;
|
||||
switch (_that) {
|
||||
case _SnWalletFund():
|
||||
return $default(_that.id,_that.currency,_that.totalAmount,_that.splitType,_that.status,_that.message,_that.creatorAccountId,_that.creatorAccount,_that.expiredAt,_that.recipients,_that.createdAt,_that.updatedAt,_that.deletedAt);}
|
||||
return $default(_that.id,_that.currency,_that.totalAmount,_that.remainingAmount,_that.amountOfSplits,_that.splitType,_that.status,_that.message,_that.creatorAccountId,_that.creatorAccount,_that.expiredAt,_that.recipients,_that.isOpen,_that.createdAt,_that.updatedAt,_that.deletedAt);}
|
||||
}
|
||||
/// A variant of `when` that fallback to returning `null`
|
||||
///
|
||||
@@ -2752,10 +2755,10 @@ return $default(_that.id,_that.currency,_that.totalAmount,_that.splitType,_that.
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String id, String currency, double totalAmount, int splitType, int status, String? message, String creatorAccountId, SnAccount? creatorAccount, DateTime expiredAt, List<SnWalletFundRecipient> recipients, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt)? $default,) {final _that = this;
|
||||
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String id, String currency, double totalAmount, double remainingAmount, int amountOfSplits, int splitType, int status, String? message, String creatorAccountId, SnAccount? creatorAccount, DateTime expiredAt, List<SnWalletFundRecipient> recipients, bool isOpen, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt)? $default,) {final _that = this;
|
||||
switch (_that) {
|
||||
case _SnWalletFund() when $default != null:
|
||||
return $default(_that.id,_that.currency,_that.totalAmount,_that.splitType,_that.status,_that.message,_that.creatorAccountId,_that.creatorAccount,_that.expiredAt,_that.recipients,_that.createdAt,_that.updatedAt,_that.deletedAt);case _:
|
||||
return $default(_that.id,_that.currency,_that.totalAmount,_that.remainingAmount,_that.amountOfSplits,_that.splitType,_that.status,_that.message,_that.creatorAccountId,_that.creatorAccount,_that.expiredAt,_that.recipients,_that.isOpen,_that.createdAt,_that.updatedAt,_that.deletedAt);case _:
|
||||
return null;
|
||||
|
||||
}
|
||||
@@ -2767,12 +2770,14 @@ return $default(_that.id,_that.currency,_that.totalAmount,_that.splitType,_that.
|
||||
@JsonSerializable()
|
||||
|
||||
class _SnWalletFund implements SnWalletFund {
|
||||
const _SnWalletFund({required this.id, required this.currency, required this.totalAmount, required this.splitType, required this.status, required this.message, required this.creatorAccountId, required this.creatorAccount, required this.expiredAt, required final List<SnWalletFundRecipient> recipients, required this.createdAt, required this.updatedAt, required this.deletedAt}): _recipients = recipients;
|
||||
const _SnWalletFund({required this.id, required this.currency, required this.totalAmount, required this.remainingAmount, required this.amountOfSplits, required this.splitType, required this.status, required this.message, required this.creatorAccountId, required this.creatorAccount, required this.expiredAt, required final List<SnWalletFundRecipient> recipients, required this.isOpen, required this.createdAt, required this.updatedAt, required this.deletedAt}): _recipients = recipients;
|
||||
factory _SnWalletFund.fromJson(Map<String, dynamic> json) => _$SnWalletFundFromJson(json);
|
||||
|
||||
@override final String id;
|
||||
@override final String currency;
|
||||
@override final double totalAmount;
|
||||
@override final double remainingAmount;
|
||||
@override final int amountOfSplits;
|
||||
@override final int splitType;
|
||||
// 0: even, 1: random
|
||||
@override final int status;
|
||||
@@ -2788,6 +2793,7 @@ class _SnWalletFund implements SnWalletFund {
|
||||
return EqualUnmodifiableListView(_recipients);
|
||||
}
|
||||
|
||||
@override final bool isOpen;
|
||||
@override final DateTime createdAt;
|
||||
@override final DateTime updatedAt;
|
||||
@override final DateTime? deletedAt;
|
||||
@@ -2805,16 +2811,16 @@ Map<String, dynamic> toJson() {
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is _SnWalletFund&&(identical(other.id, id) || other.id == id)&&(identical(other.currency, currency) || other.currency == currency)&&(identical(other.totalAmount, totalAmount) || other.totalAmount == totalAmount)&&(identical(other.splitType, splitType) || other.splitType == splitType)&&(identical(other.status, status) || other.status == status)&&(identical(other.message, message) || other.message == message)&&(identical(other.creatorAccountId, creatorAccountId) || other.creatorAccountId == creatorAccountId)&&(identical(other.creatorAccount, creatorAccount) || other.creatorAccount == creatorAccount)&&(identical(other.expiredAt, expiredAt) || other.expiredAt == expiredAt)&&const DeepCollectionEquality().equals(other._recipients, _recipients)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt));
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is _SnWalletFund&&(identical(other.id, id) || other.id == id)&&(identical(other.currency, currency) || other.currency == currency)&&(identical(other.totalAmount, totalAmount) || other.totalAmount == totalAmount)&&(identical(other.remainingAmount, remainingAmount) || other.remainingAmount == remainingAmount)&&(identical(other.amountOfSplits, amountOfSplits) || other.amountOfSplits == amountOfSplits)&&(identical(other.splitType, splitType) || other.splitType == splitType)&&(identical(other.status, status) || other.status == status)&&(identical(other.message, message) || other.message == message)&&(identical(other.creatorAccountId, creatorAccountId) || other.creatorAccountId == creatorAccountId)&&(identical(other.creatorAccount, creatorAccount) || other.creatorAccount == creatorAccount)&&(identical(other.expiredAt, expiredAt) || other.expiredAt == expiredAt)&&const DeepCollectionEquality().equals(other._recipients, _recipients)&&(identical(other.isOpen, isOpen) || other.isOpen == isOpen)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt));
|
||||
}
|
||||
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@override
|
||||
int get hashCode => Object.hash(runtimeType,id,currency,totalAmount,splitType,status,message,creatorAccountId,creatorAccount,expiredAt,const DeepCollectionEquality().hash(_recipients),createdAt,updatedAt,deletedAt);
|
||||
int get hashCode => Object.hash(runtimeType,id,currency,totalAmount,remainingAmount,amountOfSplits,splitType,status,message,creatorAccountId,creatorAccount,expiredAt,const DeepCollectionEquality().hash(_recipients),isOpen,createdAt,updatedAt,deletedAt);
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'SnWalletFund(id: $id, currency: $currency, totalAmount: $totalAmount, splitType: $splitType, status: $status, message: $message, creatorAccountId: $creatorAccountId, creatorAccount: $creatorAccount, expiredAt: $expiredAt, recipients: $recipients, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt)';
|
||||
return 'SnWalletFund(id: $id, currency: $currency, totalAmount: $totalAmount, remainingAmount: $remainingAmount, amountOfSplits: $amountOfSplits, splitType: $splitType, status: $status, message: $message, creatorAccountId: $creatorAccountId, creatorAccount: $creatorAccount, expiredAt: $expiredAt, recipients: $recipients, isOpen: $isOpen, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt)';
|
||||
}
|
||||
|
||||
|
||||
@@ -2825,7 +2831,7 @@ abstract mixin class _$SnWalletFundCopyWith<$Res> implements $SnWalletFundCopyWi
|
||||
factory _$SnWalletFundCopyWith(_SnWalletFund value, $Res Function(_SnWalletFund) _then) = __$SnWalletFundCopyWithImpl;
|
||||
@override @useResult
|
||||
$Res call({
|
||||
String id, String currency, double totalAmount, int splitType, int status, String? message, String creatorAccountId, SnAccount? creatorAccount, DateTime expiredAt, List<SnWalletFundRecipient> recipients, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt
|
||||
String id, String currency, double totalAmount, double remainingAmount, int amountOfSplits, int splitType, int status, String? message, String creatorAccountId, SnAccount? creatorAccount, DateTime expiredAt, List<SnWalletFundRecipient> recipients, bool isOpen, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt
|
||||
});
|
||||
|
||||
|
||||
@@ -2842,19 +2848,22 @@ class __$SnWalletFundCopyWithImpl<$Res>
|
||||
|
||||
/// Create a copy of SnWalletFund
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? currency = null,Object? totalAmount = null,Object? splitType = null,Object? status = null,Object? message = freezed,Object? creatorAccountId = null,Object? creatorAccount = freezed,Object? expiredAt = null,Object? recipients = null,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,}) {
|
||||
@override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? currency = null,Object? totalAmount = null,Object? remainingAmount = null,Object? amountOfSplits = null,Object? splitType = null,Object? status = null,Object? message = freezed,Object? creatorAccountId = null,Object? creatorAccount = freezed,Object? expiredAt = null,Object? recipients = null,Object? isOpen = null,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,}) {
|
||||
return _then(_SnWalletFund(
|
||||
id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
|
||||
as String,currency: null == currency ? _self.currency : currency // ignore: cast_nullable_to_non_nullable
|
||||
as String,totalAmount: null == totalAmount ? _self.totalAmount : totalAmount // ignore: cast_nullable_to_non_nullable
|
||||
as double,splitType: null == splitType ? _self.splitType : splitType // ignore: cast_nullable_to_non_nullable
|
||||
as double,remainingAmount: null == remainingAmount ? _self.remainingAmount : remainingAmount // ignore: cast_nullable_to_non_nullable
|
||||
as double,amountOfSplits: null == amountOfSplits ? _self.amountOfSplits : amountOfSplits // ignore: cast_nullable_to_non_nullable
|
||||
as int,splitType: null == splitType ? _self.splitType : splitType // ignore: cast_nullable_to_non_nullable
|
||||
as int,status: null == status ? _self.status : status // ignore: cast_nullable_to_non_nullable
|
||||
as int,message: freezed == message ? _self.message : message // ignore: cast_nullable_to_non_nullable
|
||||
as String?,creatorAccountId: null == creatorAccountId ? _self.creatorAccountId : creatorAccountId // ignore: cast_nullable_to_non_nullable
|
||||
as String,creatorAccount: freezed == creatorAccount ? _self.creatorAccount : creatorAccount // ignore: cast_nullable_to_non_nullable
|
||||
as SnAccount?,expiredAt: null == expiredAt ? _self.expiredAt : expiredAt // ignore: cast_nullable_to_non_nullable
|
||||
as DateTime,recipients: null == recipients ? _self._recipients : recipients // ignore: cast_nullable_to_non_nullable
|
||||
as List<SnWalletFundRecipient>,createdAt: null == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable
|
||||
as List<SnWalletFundRecipient>,isOpen: null == isOpen ? _self.isOpen : isOpen // ignore: cast_nullable_to_non_nullable
|
||||
as bool,createdAt: null == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable
|
||||
as DateTime,updatedAt: null == updatedAt ? _self.updatedAt : updatedAt // ignore: cast_nullable_to_non_nullable
|
||||
as DateTime,deletedAt: freezed == deletedAt ? _self.deletedAt : deletedAt // ignore: cast_nullable_to_non_nullable
|
||||
as DateTime?,
|
||||
|
||||
@@ -336,6 +336,8 @@ _SnWalletFund _$SnWalletFundFromJson(
|
||||
id: json['id'] as String,
|
||||
currency: json['currency'] as String,
|
||||
totalAmount: (json['total_amount'] as num).toDouble(),
|
||||
remainingAmount: (json['remaining_amount'] as num).toDouble(),
|
||||
amountOfSplits: (json['amount_of_splits'] as num).toInt(),
|
||||
splitType: (json['split_type'] as num).toInt(),
|
||||
status: (json['status'] as num).toInt(),
|
||||
message: json['message'] as String?,
|
||||
@@ -349,6 +351,7 @@ _SnWalletFund _$SnWalletFundFromJson(
|
||||
(json['recipients'] as List<dynamic>)
|
||||
.map((e) => SnWalletFundRecipient.fromJson(e as Map<String, dynamic>))
|
||||
.toList(),
|
||||
isOpen: json['is_open'] as bool,
|
||||
createdAt: DateTime.parse(json['created_at'] as String),
|
||||
updatedAt: DateTime.parse(json['updated_at'] as String),
|
||||
deletedAt:
|
||||
@@ -362,6 +365,8 @@ Map<String, dynamic> _$SnWalletFundToJson(_SnWalletFund instance) =>
|
||||
'id': instance.id,
|
||||
'currency': instance.currency,
|
||||
'total_amount': instance.totalAmount,
|
||||
'remaining_amount': instance.remainingAmount,
|
||||
'amount_of_splits': instance.amountOfSplits,
|
||||
'split_type': instance.splitType,
|
||||
'status': instance.status,
|
||||
'message': instance.message,
|
||||
@@ -369,6 +374,7 @@ Map<String, dynamic> _$SnWalletFundToJson(_SnWalletFund instance) =>
|
||||
'creator_account': instance.creatorAccount?.toJson(),
|
||||
'expired_at': instance.expiredAt.toIso8601String(),
|
||||
'recipients': instance.recipients.map((e) => e.toJson()).toList(),
|
||||
'is_open': instance.isOpen,
|
||||
'created_at': instance.createdAt.toIso8601String(),
|
||||
'updated_at': instance.updatedAt.toIso8601String(),
|
||||
'deleted_at': instance.deletedAt?.toIso8601String(),
|
||||
|
||||
@@ -6,7 +6,7 @@ part of 'messages_notifier.dart';
|
||||
// RiverpodGenerator
|
||||
// **************************************************************************
|
||||
|
||||
String _$messagesNotifierHash() => r'c009eb8598e8b5fbcece2d0b5213b2e434edb3b2';
|
||||
String _$messagesNotifierHash() => r'fc9c99024a0801efa4894f250aea8bdc6127a0b6';
|
||||
|
||||
/// Copied from Dart SDK
|
||||
class _SystemHash {
|
||||
|
||||
@@ -375,16 +375,17 @@ class AccountScreen extends HookConsumerWidget {
|
||||
);
|
||||
},
|
||||
),
|
||||
ListTile(
|
||||
minTileHeight: 48,
|
||||
leading: const Icon(Symbols.files),
|
||||
trailing: const Icon(Symbols.chevron_right),
|
||||
contentPadding: EdgeInsets.symmetric(horizontal: 24),
|
||||
title: Text('files').tr(),
|
||||
onTap: () {
|
||||
context.goNamed('files');
|
||||
},
|
||||
),
|
||||
if (!isWideScreen(context))
|
||||
ListTile(
|
||||
minTileHeight: 48,
|
||||
leading: const Icon(Symbols.files),
|
||||
trailing: const Icon(Symbols.chevron_right),
|
||||
contentPadding: EdgeInsets.symmetric(horizontal: 24),
|
||||
title: Text('files').tr(),
|
||||
onTap: () {
|
||||
context.goNamed('files');
|
||||
},
|
||||
),
|
||||
ListTile(
|
||||
minTileHeight: 48,
|
||||
leading: const Icon(Symbols.wallet),
|
||||
|
||||
@@ -55,6 +55,7 @@ class CreateFundSheet extends StatefulWidget {
|
||||
|
||||
class _CreateFundSheetState extends State<CreateFundSheet> {
|
||||
final amountController = TextEditingController();
|
||||
final splitsController = TextEditingController(text: '1');
|
||||
final messageController = TextEditingController();
|
||||
String selectedCurrency = 'golds';
|
||||
int selectedSplitType = 0; // 0: even, 1: random
|
||||
@@ -64,6 +65,7 @@ class _CreateFundSheetState extends State<CreateFundSheet> {
|
||||
void dispose() {
|
||||
amountController.dispose();
|
||||
messageController.dispose();
|
||||
splitsController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@@ -157,49 +159,84 @@ class _CreateFundSheetState extends State<CreateFundSheet> {
|
||||
},
|
||||
),
|
||||
|
||||
// Split Type Section (only show when there are 2+ recipients)
|
||||
if (selectedRecipients.length >= 2) ...[
|
||||
const Gap(16),
|
||||
Text(
|
||||
'splitType'.tr(),
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
const Gap(16),
|
||||
|
||||
// Amount of Splits Section
|
||||
Text(
|
||||
'amountOfSplits'.tr(),
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
),
|
||||
const Gap(8),
|
||||
TextField(
|
||||
controller: splitsController,
|
||||
keyboardType: TextInputType.number,
|
||||
inputFormatters: [FilteringTextInputFormatter.digitsOnly],
|
||||
decoration: InputDecoration(
|
||||
labelText: 'enterNumberOfSplits'.tr(),
|
||||
hintText:
|
||||
selectedRecipients.isNotEmpty
|
||||
? selectedRecipients.length.toString()
|
||||
: '1',
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: const BorderRadius.all(
|
||||
Radius.circular(12),
|
||||
),
|
||||
),
|
||||
),
|
||||
const Gap(8),
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: RadioListTile<int>(
|
||||
title: Text('evenSplit'.tr()),
|
||||
subtitle: Text('equalAmountEach'.tr()),
|
||||
value: 0,
|
||||
groupValue: selectedSplitType,
|
||||
onChanged: (value) {
|
||||
if (value != null) {
|
||||
setState(() => selectedSplitType = value);
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: RadioListTile<int>(
|
||||
title: Text('randomSplit'.tr()),
|
||||
subtitle: Text('randomAmountEach'.tr()),
|
||||
value: 1,
|
||||
groupValue: selectedSplitType,
|
||||
onChanged: (value) {
|
||||
if (value != null) {
|
||||
setState(() => selectedSplitType = value);
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
onTapOutside:
|
||||
(_) => FocusManager.instance.primaryFocus?.unfocus(),
|
||||
onChanged: (value) {
|
||||
if (value.isEmpty && selectedRecipients.isNotEmpty) {
|
||||
splitsController.text =
|
||||
selectedRecipients.length.toString();
|
||||
}
|
||||
},
|
||||
),
|
||||
|
||||
const Gap(16),
|
||||
Text(
|
||||
'splitType'.tr(),
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
],
|
||||
),
|
||||
const Gap(8),
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: RadioListTile<int>(
|
||||
title: Text('evenSplit'.tr()),
|
||||
subtitle: Text('equalAmountEach'.tr()),
|
||||
value: 0,
|
||||
groupValue: selectedSplitType,
|
||||
onChanged: (value) {
|
||||
if (value != null) {
|
||||
setState(() => selectedSplitType = value);
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: RadioListTile<int>(
|
||||
title: Text('randomSplit'.tr()),
|
||||
subtitle: Text('randomAmountEach'.tr()),
|
||||
value: 1,
|
||||
groupValue: selectedSplitType,
|
||||
onChanged: (value) {
|
||||
if (value != null) {
|
||||
setState(() => selectedSplitType = value);
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
const Gap(16),
|
||||
|
||||
@@ -496,16 +533,23 @@ class _CreateFundSheetState extends State<CreateFundSheet> {
|
||||
|
||||
Future<void> _createFund() async {
|
||||
final amount = double.tryParse(amountController.text);
|
||||
final splits = int.tryParse(splitsController.text);
|
||||
|
||||
if (amount == null || amount <= 0) {
|
||||
showErrorAlert('invalidAmount'.tr());
|
||||
return;
|
||||
}
|
||||
|
||||
if (splits == null || splits <= 0) {
|
||||
showErrorAlert('invalidNumberOfSplits'.tr());
|
||||
return;
|
||||
}
|
||||
|
||||
final data = {
|
||||
'currency': selectedCurrency,
|
||||
'total_amount': amount,
|
||||
'split_type': selectedSplitType,
|
||||
'amount_of_splits': splits,
|
||||
'recipient_account_ids': selectedRecipients.map((r) => r.id).toList(),
|
||||
'message':
|
||||
messageController.text.trim().isEmpty
|
||||
|
||||
@@ -3,8 +3,8 @@ import 'package:island/models/embed.dart';
|
||||
import 'package:island/utils/mapping.dart';
|
||||
import 'package:island/widgets/content/embed/link.dart';
|
||||
import 'package:island/widgets/poll/poll_submit.dart';
|
||||
import 'package:island/widgets/wallet/fund_envelope.dart';
|
||||
import 'package:material_symbols_icons/material_symbols_icons.dart';
|
||||
import 'package:styled_widget/styled_widget.dart';
|
||||
|
||||
class EmbedListWidget extends StatelessWidget {
|
||||
final List<dynamic> embeds;
|
||||
@@ -96,16 +96,32 @@ class EmbedListWidget extends StatelessWidget {
|
||||
horizontal: renderingPadding.horizontal,
|
||||
vertical: 8,
|
||||
),
|
||||
child:
|
||||
embedData['id'] == null
|
||||
? const Text('Poll was unavailable...')
|
||||
: PollSubmit(
|
||||
pollId: embedData['id'],
|
||||
onSubmit: (_) {},
|
||||
isReadonly: !isInteractive,
|
||||
isInitiallyExpanded: isFullPost,
|
||||
).padding(horizontal: 16, vertical: 12),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 16,
|
||||
vertical: 12,
|
||||
),
|
||||
child:
|
||||
embedData['id'] == null
|
||||
? const Text('Poll was unavailable...')
|
||||
: PollSubmit(
|
||||
pollId: embedData['id'],
|
||||
onSubmit: (_) {},
|
||||
isReadonly: !isInteractive,
|
||||
isInitiallyExpanded: isFullPost,
|
||||
),
|
||||
),
|
||||
),
|
||||
'fund' =>
|
||||
embedData['id'] == null
|
||||
? const Text('Fund envelope was unavailable...')
|
||||
: FundEnvelopeWidget(
|
||||
fundId: embedData['id'],
|
||||
margin: EdgeInsets.symmetric(
|
||||
horizontal: renderingPadding.horizontal,
|
||||
vertical: 8,
|
||||
),
|
||||
),
|
||||
_ => Text('Unable show embed: ${embedData['type']}'),
|
||||
},
|
||||
),
|
||||
|
||||
@@ -3,12 +3,12 @@ import 'package:flutter/material.dart';
|
||||
|
||||
class UniversalVideo extends StatelessWidget {
|
||||
final String uri;
|
||||
final double aspectRatio;
|
||||
final double? aspectRatio;
|
||||
final bool autoplay;
|
||||
const UniversalVideo({
|
||||
super.key,
|
||||
required this.uri,
|
||||
required this.aspectRatio,
|
||||
this.aspectRatio,
|
||||
this.autoplay = false,
|
||||
});
|
||||
|
||||
|
||||
@@ -16,10 +16,8 @@ import 'package:island/widgets/content/sheet.dart';
|
||||
import 'package:island/widgets/post/compose_attachments.dart';
|
||||
import 'package:island/widgets/post/compose_form_fields.dart';
|
||||
import 'package:island/widgets/post/compose_info_banner.dart';
|
||||
import 'package:island/widgets/post/compose_settings_sheet.dart';
|
||||
import 'package:island/widgets/post/compose_shared.dart';
|
||||
import 'package:island/widgets/post/compose_state_utils.dart';
|
||||
import 'package:island/widgets/post/compose_submit_utils.dart';
|
||||
import 'package:island/widgets/post/compose_toolbar.dart';
|
||||
import 'package:island/widgets/post/post_item.dart';
|
||||
import 'package:island/widgets/post/publishers_modal.dart';
|
||||
@@ -143,16 +141,11 @@ class PostComposeCard extends HookConsumerWidget {
|
||||
|
||||
// Helper methods
|
||||
void showSettingsSheet() {
|
||||
showModalBottomSheet(
|
||||
context: context,
|
||||
isScrollControlled: true,
|
||||
useRootNavigator: true,
|
||||
builder: (context) => ComposeSettingsSheet(state: composeState),
|
||||
);
|
||||
ComposeLogic.showSettingsSheet(context, composeState);
|
||||
}
|
||||
|
||||
Future<void> performSubmit() async {
|
||||
await ComposeSubmitUtils.performSubmit(
|
||||
await ComposeLogic.performSubmit(
|
||||
ref,
|
||||
composeState,
|
||||
context,
|
||||
|
||||
@@ -9,10 +9,8 @@ import 'package:island/services/compose_storage_db.dart';
|
||||
import 'package:island/services/event_bus.dart';
|
||||
import 'package:island/widgets/content/sheet.dart';
|
||||
import 'package:island/widgets/post/compose_card.dart';
|
||||
import 'package:island/widgets/post/compose_settings_sheet.dart';
|
||||
import 'package:island/widgets/post/compose_shared.dart';
|
||||
import 'package:island/widgets/post/compose_state_utils.dart';
|
||||
import 'package:island/widgets/post/compose_submit_utils.dart';
|
||||
import 'package:material_symbols_icons/symbols.dart';
|
||||
|
||||
/// A dialog that wraps PostComposeCard for easy use in dialogs.
|
||||
@@ -104,16 +102,11 @@ class PostComposeDialog extends HookConsumerWidget {
|
||||
|
||||
// Helper methods for actions
|
||||
void showSettingsSheet() {
|
||||
showModalBottomSheet(
|
||||
context: context,
|
||||
isScrollControlled: true,
|
||||
useRootNavigator: true,
|
||||
builder: (context) => ComposeSettingsSheet(state: state),
|
||||
);
|
||||
ComposeLogic.showSettingsSheet(context, state);
|
||||
}
|
||||
|
||||
Future<void> performSubmit() async {
|
||||
await ComposeSubmitUtils.performSubmit(
|
||||
await ComposeLogic.performSubmit(
|
||||
ref,
|
||||
state,
|
||||
context,
|
||||
|
||||
@@ -2,6 +2,7 @@ import 'dart:async';
|
||||
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:island/services/event_bus.dart';
|
||||
import 'package:island/widgets/post/compose_settings_sheet.dart';
|
||||
import 'package:mime/mime.dart';
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
@@ -21,6 +22,7 @@ import 'package:island/services/compose_storage_db.dart';
|
||||
import 'package:island/widgets/alert.dart';
|
||||
import 'package:island/widgets/post/compose_link_attachments.dart';
|
||||
import 'package:island/widgets/post/compose_poll.dart';
|
||||
import 'package:island/widgets/post/compose_fund.dart';
|
||||
import 'package:island/widgets/post/compose_recorder.dart';
|
||||
import 'package:island/pods/file_pool.dart';
|
||||
import 'package:pasteboard/pasteboard.dart';
|
||||
@@ -44,6 +46,8 @@ class ComposeState {
|
||||
int postType;
|
||||
// Linked poll id for this compose session (nullable)
|
||||
final ValueNotifier<String?> pollId;
|
||||
// Linked fund id for this compose session (nullable)
|
||||
final ValueNotifier<String?> fundId;
|
||||
Timer? _autoSaveTimer;
|
||||
|
||||
ComposeState({
|
||||
@@ -63,7 +67,9 @@ class ComposeState {
|
||||
required this.draftId,
|
||||
this.postType = 0,
|
||||
String? pollId,
|
||||
}) : pollId = ValueNotifier<String?>(pollId);
|
||||
String? fundId,
|
||||
}) : pollId = ValueNotifier<String?>(pollId),
|
||||
fundId = ValueNotifier<String?>(fundId);
|
||||
|
||||
void startAutoSave(WidgetRef ref) {
|
||||
_autoSaveTimer?.cancel();
|
||||
@@ -98,6 +104,22 @@ class ComposeLogic {
|
||||
// Initialize categories from original post
|
||||
final categories = originalPost?.categories ?? <SnPostCategory>[];
|
||||
|
||||
// Extract poll and fund IDs from embeds
|
||||
String? pollId;
|
||||
String? fundId;
|
||||
if (originalPost?.meta?['embeds'] is List) {
|
||||
final embeds =
|
||||
(originalPost!.meta!['embeds'] as List).cast<Map<String, dynamic>>();
|
||||
try {
|
||||
final pollEmbed = embeds.firstWhere((e) => e['type'] == 'poll');
|
||||
pollId = pollEmbed['id'];
|
||||
} catch (_) {}
|
||||
try {
|
||||
final fundEmbed = embeds.firstWhere((e) => e['type'] == 'fund');
|
||||
fundId = fundEmbed['id'];
|
||||
} catch (_) {}
|
||||
}
|
||||
|
||||
return ComposeState(
|
||||
attachments: ValueNotifier<List<UniversalFile>>(
|
||||
originalPost?.attachments
|
||||
@@ -131,8 +153,8 @@ class ComposeLogic {
|
||||
embedView: ValueNotifier<SnPostEmbedView?>(originalPost?.embedView),
|
||||
draftId: id,
|
||||
postType: postType,
|
||||
// initialize without poll by default
|
||||
pollId: null,
|
||||
pollId: pollId,
|
||||
fundId: fundId,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -158,6 +180,8 @@ class ComposeLogic {
|
||||
draftId: draft.id,
|
||||
postType: postType,
|
||||
pollId: null,
|
||||
// initialize without fund by default
|
||||
fundId: null,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -618,15 +642,40 @@ class ComposeLogic {
|
||||
state.pollId.value = poll.id;
|
||||
}
|
||||
|
||||
static Future<void> performAction(
|
||||
static Future<void> pickFund(
|
||||
WidgetRef ref,
|
||||
ComposeState state,
|
||||
BuildContext context,
|
||||
) async {
|
||||
if (state.fundId.value != null) {
|
||||
state.fundId.value = null;
|
||||
return;
|
||||
}
|
||||
|
||||
final fund = await showModalBottomSheet(
|
||||
context: context,
|
||||
useRootNavigator: true,
|
||||
isScrollControlled: true,
|
||||
builder: (context) => const ComposeFundSheet(),
|
||||
);
|
||||
|
||||
if (fund == null) return;
|
||||
state.fundId.value = fund.id;
|
||||
}
|
||||
|
||||
/// Unified submit method that returns the created/updated post.
|
||||
static Future<SnPost> performSubmit(
|
||||
WidgetRef ref,
|
||||
ComposeState state,
|
||||
BuildContext context, {
|
||||
SnPost? originalPost,
|
||||
SnPost? repliedPost,
|
||||
SnPost? forwardedPost,
|
||||
required Function() onSuccess,
|
||||
}) async {
|
||||
if (state.submitting.value) return;
|
||||
if (state.submitting.value) {
|
||||
throw Exception('Already submitting');
|
||||
}
|
||||
|
||||
// Don't submit empty posts (no content and no attachments)
|
||||
final hasContent =
|
||||
@@ -636,25 +685,26 @@ class ComposeLogic {
|
||||
final hasAttachments = state.attachments.value.isNotEmpty;
|
||||
|
||||
if (!hasContent && !hasAttachments) {
|
||||
if (context.mounted) {
|
||||
showSnackBar('postContentEmpty'.tr());
|
||||
}
|
||||
return; // Don't submit empty posts
|
||||
showErrorAlert('postContentEmpty'.tr());
|
||||
throw Exception('Post content is empty'); // Don't submit empty posts
|
||||
}
|
||||
|
||||
try {
|
||||
state.submitting.value = true;
|
||||
|
||||
// pload any local attachments first
|
||||
// Upload any local attachments first
|
||||
await Future.wait(
|
||||
state.attachments.value
|
||||
.asMap()
|
||||
.entries
|
||||
.where((entry) => entry.value.isOnDevice)
|
||||
.map((entry) => uploadAttachment(ref, state, entry.key)),
|
||||
.map(
|
||||
(entry) => ComposeLogic.uploadAttachment(ref, state, entry.key),
|
||||
),
|
||||
);
|
||||
|
||||
// Prepare API request
|
||||
final client = ref.watch(apiClientProvider);
|
||||
final client = ref.read(apiClientProvider);
|
||||
final isNewPost = originalPost == null;
|
||||
final endpoint =
|
||||
'/sphere${isNewPost ? '/posts' : '/posts/${originalPost.id}'}';
|
||||
@@ -679,43 +729,85 @@ class ComposeLogic {
|
||||
'categories': state.categories.value.map((e) => e.slug).toList(),
|
||||
if (state.realm.value != null) 'realm_id': state.realm.value?.id,
|
||||
if (state.pollId.value != null) 'poll_id': state.pollId.value,
|
||||
if (state.fundId.value != null) 'fund_id': state.fundId.value,
|
||||
if (state.embedView.value != null)
|
||||
'embed_view': state.embedView.value!.toJson(),
|
||||
};
|
||||
|
||||
// Send request
|
||||
await client.request(
|
||||
final response = await client.request(
|
||||
endpoint,
|
||||
queryParameters: {'pub': state.currentPublisher.value?.name},
|
||||
data: payload,
|
||||
options: Options(method: isNewPost ? 'POST' : 'PATCH'),
|
||||
);
|
||||
|
||||
// Delete draft after successful submission
|
||||
if (state.postType == 1) {
|
||||
// Delete article draft
|
||||
await ref
|
||||
.read(composeStorageNotifierProvider.notifier)
|
||||
.deleteDraft(state.draftId);
|
||||
} else {
|
||||
// Delete regular post draft
|
||||
await ref
|
||||
.read(composeStorageNotifierProvider.notifier)
|
||||
.deleteDraft(state.draftId);
|
||||
}
|
||||
|
||||
if (context.mounted) {
|
||||
Navigator.of(context).maybePop(true);
|
||||
}
|
||||
// Parse the response into a SnPost
|
||||
final post = SnPost.fromJson(response.data);
|
||||
|
||||
// Call the success callback
|
||||
onSuccess();
|
||||
eventBus.fire(PostCreatedEvent());
|
||||
|
||||
return post;
|
||||
} catch (err) {
|
||||
showErrorAlert(err);
|
||||
// Show error message if context is mounted
|
||||
if (context.mounted) {
|
||||
ScaffoldMessenger.of(
|
||||
context,
|
||||
).showSnackBar(SnackBar(content: Text('Error: $err')));
|
||||
}
|
||||
rethrow;
|
||||
} finally {
|
||||
state.submitting.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
static Future<void> performAction(
|
||||
WidgetRef ref,
|
||||
ComposeState state,
|
||||
BuildContext context, {
|
||||
SnPost? originalPost,
|
||||
SnPost? repliedPost,
|
||||
SnPost? forwardedPost,
|
||||
}) async {
|
||||
await ComposeLogic.performSubmit(
|
||||
ref,
|
||||
state,
|
||||
context,
|
||||
originalPost: originalPost,
|
||||
repliedPost: repliedPost,
|
||||
forwardedPost: forwardedPost,
|
||||
onSuccess: () async {
|
||||
// Delete draft after successful submission
|
||||
if (state.postType == 1) {
|
||||
// Delete article draft
|
||||
await ref
|
||||
.read(composeStorageNotifierProvider.notifier)
|
||||
.deleteDraft(state.draftId);
|
||||
} else {
|
||||
// Delete regular post draft
|
||||
await ref
|
||||
.read(composeStorageNotifierProvider.notifier)
|
||||
.deleteDraft(state.draftId);
|
||||
}
|
||||
|
||||
if (context.mounted) {
|
||||
Navigator.of(context).maybePop(true);
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/// Shows the settings sheet modal.
|
||||
static void showSettingsSheet(BuildContext context, ComposeState state) {
|
||||
showModalBottomSheet(
|
||||
context: context,
|
||||
isScrollControlled: true,
|
||||
builder: (context) => ComposeSettingsSheet(state: state),
|
||||
);
|
||||
}
|
||||
|
||||
static Future<void> handlePaste(ComposeState state) async {
|
||||
final clipboard = await Pasteboard.image;
|
||||
if (clipboard == null) return;
|
||||
@@ -778,5 +870,6 @@ class ComposeLogic {
|
||||
state.realm.dispose();
|
||||
state.embedView.dispose();
|
||||
state.pollId.dispose();
|
||||
state.fundId.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,10 +8,8 @@ import 'package:island/screens/posts/compose.dart';
|
||||
import 'package:island/services/compose_storage_db.dart';
|
||||
import 'package:island/widgets/content/sheet.dart';
|
||||
import 'package:island/widgets/post/compose_card.dart';
|
||||
import 'package:island/widgets/post/compose_settings_sheet.dart';
|
||||
import 'package:island/widgets/post/compose_shared.dart';
|
||||
import 'package:island/widgets/post/compose_state_utils.dart';
|
||||
import 'package:island/widgets/post/compose_submit_utils.dart';
|
||||
import 'package:material_symbols_icons/symbols.dart';
|
||||
|
||||
/// A dialog that wraps PostComposeCard for easy use in dialogs.
|
||||
@@ -106,16 +104,11 @@ class PostComposeSheet extends HookConsumerWidget {
|
||||
|
||||
// Helper methods for actions
|
||||
void showSettingsSheet() {
|
||||
showModalBottomSheet(
|
||||
context: context,
|
||||
isScrollControlled: true,
|
||||
useRootNavigator: true,
|
||||
builder: (context) => ComposeSettingsSheet(state: state),
|
||||
);
|
||||
ComposeLogic.showSettingsSheet(context, state);
|
||||
}
|
||||
|
||||
Future<void> performSubmit() async {
|
||||
await ComposeSubmitUtils.performSubmit(
|
||||
await ComposeLogic.performSubmit(
|
||||
ref,
|
||||
state,
|
||||
context,
|
||||
|
||||
@@ -1,145 +0,0 @@
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:island/models/post.dart';
|
||||
import 'package:island/pods/network.dart';
|
||||
import 'package:island/services/event_bus.dart';
|
||||
import 'package:island/widgets/post/compose_settings_sheet.dart';
|
||||
import 'package:island/widgets/post/compose_shared.dart';
|
||||
|
||||
/// Utility class for common compose submit logic.
|
||||
class ComposeSubmitUtils {
|
||||
/// Performs the submit action for posts.
|
||||
static Future<SnPost> performSubmit(
|
||||
WidgetRef ref,
|
||||
ComposeState state,
|
||||
BuildContext context, {
|
||||
SnPost? originalPost,
|
||||
SnPost? repliedPost,
|
||||
SnPost? forwardedPost,
|
||||
required Function() onSuccess,
|
||||
}) async {
|
||||
if (state.submitting.value) {
|
||||
throw Exception('Already submitting');
|
||||
}
|
||||
|
||||
// Don't submit empty posts (no content and no attachments)
|
||||
final hasContent =
|
||||
state.titleController.text.trim().isNotEmpty ||
|
||||
state.descriptionController.text.trim().isNotEmpty ||
|
||||
state.contentController.text.trim().isNotEmpty;
|
||||
final hasAttachments = state.attachments.value.isNotEmpty;
|
||||
|
||||
if (!hasContent && !hasAttachments) {
|
||||
// Show error message if context is mounted
|
||||
if (context.mounted) {
|
||||
ScaffoldMessenger.of(
|
||||
context,
|
||||
).showSnackBar(SnackBar(content: Text('postContentEmpty')));
|
||||
}
|
||||
throw Exception('Post content is empty'); // Don't submit empty posts
|
||||
}
|
||||
|
||||
try {
|
||||
state.submitting.value = true;
|
||||
|
||||
// Upload any local attachments first
|
||||
await Future.wait(
|
||||
state.attachments.value
|
||||
.asMap()
|
||||
.entries
|
||||
.where((entry) => entry.value.isOnDevice)
|
||||
.map(
|
||||
(entry) => ComposeLogic.uploadAttachment(ref, state, entry.key),
|
||||
),
|
||||
);
|
||||
|
||||
// Prepare API request
|
||||
final client = ref.read(apiClientProvider);
|
||||
final isNewPost = originalPost == null;
|
||||
final endpoint =
|
||||
'/sphere${isNewPost ? '/posts' : '/posts/${originalPost.id}'}';
|
||||
|
||||
// Create request payload
|
||||
final payload = {
|
||||
'title': state.titleController.text,
|
||||
'description': state.descriptionController.text,
|
||||
'content': state.contentController.text,
|
||||
if (state.slugController.text.isNotEmpty)
|
||||
'slug': state.slugController.text,
|
||||
'visibility': state.visibility.value,
|
||||
'attachments':
|
||||
state.attachments.value
|
||||
.where((e) => e.isOnCloud)
|
||||
.map((e) => e.data.id)
|
||||
.toList(),
|
||||
'type': state.postType,
|
||||
if (repliedPost != null) 'replied_post_id': repliedPost.id,
|
||||
if (forwardedPost != null) 'forwarded_post_id': forwardedPost.id,
|
||||
'tags': state.tags.value,
|
||||
'categories': state.categories.value.map((e) => e.slug).toList(),
|
||||
if (state.realm.value != null) 'realm_id': state.realm.value?.id,
|
||||
if (state.pollId.value != null) 'poll_id': state.pollId.value,
|
||||
if (state.embedView.value != null)
|
||||
'embed_view': state.embedView.value!.toJson(),
|
||||
};
|
||||
|
||||
// Send request
|
||||
final response = await client.request(
|
||||
endpoint,
|
||||
queryParameters: {'pub': state.currentPublisher.value?.name},
|
||||
data: payload,
|
||||
options: Options(method: isNewPost ? 'POST' : 'PATCH'),
|
||||
);
|
||||
|
||||
// Parse the response into a SnPost
|
||||
final post = SnPost.fromJson(response.data);
|
||||
|
||||
// Call the success callback
|
||||
onSuccess();
|
||||
eventBus.fire(PostCreatedEvent());
|
||||
|
||||
return post;
|
||||
} catch (err) {
|
||||
// Show error message if context is mounted
|
||||
if (context.mounted) {
|
||||
ScaffoldMessenger.of(
|
||||
context,
|
||||
).showSnackBar(SnackBar(content: Text('Error: $err')));
|
||||
}
|
||||
rethrow;
|
||||
} finally {
|
||||
state.submitting.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
/// Shows the settings sheet modal.
|
||||
static void showSettingsSheet(BuildContext context, ComposeState state) {
|
||||
showModalBottomSheet(
|
||||
context: context,
|
||||
isScrollControlled: true,
|
||||
builder: (context) => ComposeSettingsSheet(state: state),
|
||||
);
|
||||
}
|
||||
|
||||
/// Handles keyboard press events for compose shortcuts.
|
||||
static void handleKeyPress(
|
||||
KeyEvent event,
|
||||
ComposeState state,
|
||||
WidgetRef ref,
|
||||
BuildContext context, {
|
||||
SnPost? originalPost,
|
||||
SnPost? repliedPost,
|
||||
SnPost? forwardedPost,
|
||||
}) {
|
||||
ComposeLogic.handleKeyPress(
|
||||
event,
|
||||
state,
|
||||
ref,
|
||||
context,
|
||||
originalPost: originalPost,
|
||||
repliedPost: repliedPost,
|
||||
forwardedPost: forwardedPost,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -54,6 +54,10 @@ class ComposeToolbar extends HookConsumerWidget {
|
||||
ComposeLogic.pickPoll(ref, state, context);
|
||||
}
|
||||
|
||||
void pickFund() {
|
||||
ComposeLogic.pickFund(ref, state, context);
|
||||
}
|
||||
|
||||
void showEmbedSheet() {
|
||||
showModalBottomSheet(
|
||||
context: context,
|
||||
@@ -143,6 +147,29 @@ class ComposeToolbar extends HookConsumerWidget {
|
||||
);
|
||||
},
|
||||
),
|
||||
// Fund button with visual state when a fund is linked
|
||||
ListenableBuilder(
|
||||
listenable: state.fundId,
|
||||
builder: (context, _) {
|
||||
return IconButton(
|
||||
onPressed: pickFund,
|
||||
icon: const Icon(Symbols.account_balance_wallet),
|
||||
tooltip: 'fund'.tr(),
|
||||
color: colorScheme.primary,
|
||||
visualDensity: const VisualDensity(
|
||||
horizontal: -4,
|
||||
vertical: -2,
|
||||
),
|
||||
style: ButtonStyle(
|
||||
backgroundColor: WidgetStatePropertyAll(
|
||||
state.fundId.value != null
|
||||
? colorScheme.primary.withOpacity(0.15)
|
||||
: null,
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
// Embed button with visual state when embed is present
|
||||
ListenableBuilder(
|
||||
listenable: state.embedView,
|
||||
@@ -252,6 +279,25 @@ class ComposeToolbar extends HookConsumerWidget {
|
||||
);
|
||||
},
|
||||
),
|
||||
// Fund button with visual state when a fund is linked
|
||||
ListenableBuilder(
|
||||
listenable: state.fundId,
|
||||
builder: (context, _) {
|
||||
return IconButton(
|
||||
onPressed: pickFund,
|
||||
icon: const Icon(Symbols.account_balance_wallet),
|
||||
tooltip: 'fund'.tr(),
|
||||
color: colorScheme.primary,
|
||||
style: ButtonStyle(
|
||||
backgroundColor: WidgetStatePropertyAll(
|
||||
state.fundId.value != null
|
||||
? colorScheme.primary.withOpacity(0.15)
|
||||
: null,
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
// Embed button with visual state when embed is present
|
||||
ListenableBuilder(
|
||||
listenable: state.embedView,
|
||||
|
||||
596
lib/widgets/wallet/fund_envelope.dart
Normal file
596
lib/widgets/wallet/fund_envelope.dart
Normal file
@@ -0,0 +1,596 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:island/models/wallet.dart';
|
||||
import 'package:island/pods/network.dart';
|
||||
import 'package:island/pods/userinfo.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
|
||||
part 'fund_envelope.g.dart';
|
||||
|
||||
@riverpod
|
||||
Future<SnWalletFund> walletFund(Ref ref, String fundId) async {
|
||||
final apiClient = ref.watch(apiClientProvider);
|
||||
final resp = await apiClient.get('/pass/wallets/funds/$fundId');
|
||||
return SnWalletFund.fromJson(resp.data);
|
||||
}
|
||||
|
||||
class FundEnvelopeWidget extends HookConsumerWidget {
|
||||
const FundEnvelopeWidget({
|
||||
super.key,
|
||||
required this.fundId,
|
||||
this.maxWidth,
|
||||
this.margin,
|
||||
});
|
||||
|
||||
final String fundId;
|
||||
final double? maxWidth;
|
||||
final EdgeInsetsGeometry? margin;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final fundAsync = ref.watch(walletFundProvider(fundId));
|
||||
|
||||
return Container(
|
||||
width: maxWidth,
|
||||
margin: margin ?? const EdgeInsets.symmetric(vertical: 8),
|
||||
child: fundAsync.when(
|
||||
loading:
|
||||
() => Card(
|
||||
margin: EdgeInsets.zero,
|
||||
child: const Padding(
|
||||
padding: EdgeInsets.all(16),
|
||||
child: Center(child: CircularProgressIndicator()),
|
||||
),
|
||||
),
|
||||
error:
|
||||
(error, stack) => Card(
|
||||
margin: EdgeInsets.zero,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Column(
|
||||
children: [
|
||||
Icon(
|
||||
Icons.error_outline,
|
||||
color: Theme.of(context).colorScheme.error,
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
'Failed to load fund envelope',
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).colorScheme.error,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
data:
|
||||
(fund) => Card(
|
||||
margin: EdgeInsets.zero,
|
||||
clipBehavior: Clip.antiAlias,
|
||||
child: InkWell(
|
||||
onTap: () => _showClaimDialog(context, ref, fund),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// Fund title and status
|
||||
Row(
|
||||
children: [
|
||||
Icon(
|
||||
Icons.account_balance_wallet,
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Expanded(
|
||||
child: Text(
|
||||
'Fund Envelope',
|
||||
style: Theme.of(context).textTheme.titleMedium
|
||||
?.copyWith(fontWeight: FontWeight.w600),
|
||||
),
|
||||
),
|
||||
_buildStatusChips(context, fund),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
|
||||
// Amount information
|
||||
Row(
|
||||
children: [
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'${fund.totalAmount.toStringAsFixed(2)} ${fund.currency}',
|
||||
style: Theme.of(
|
||||
context,
|
||||
).textTheme.headlineSmall?.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
if (fund.remainingAmount != fund.totalAmount)
|
||||
Text(
|
||||
'Remaining: ${fund.remainingAmount.toStringAsFixed(2)} ${fund.currency}',
|
||||
style: Theme.of(
|
||||
context,
|
||||
).textTheme.bodySmall?.copyWith(
|
||||
color:
|
||||
Theme.of(context).colorScheme.secondary,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
'Split: ${fund.splitType == 0 ? 'Evenly' : 'Randomly'}',
|
||||
style: Theme.of(
|
||||
context,
|
||||
).textTheme.bodySmall?.copyWith(
|
||||
color: Theme.of(context)
|
||||
.textTheme
|
||||
.bodySmall
|
||||
?.color
|
||||
?.withOpacity(0.7),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
// Recipients overview
|
||||
if (fund.recipients.isNotEmpty) ...[
|
||||
const SizedBox(height: 12),
|
||||
_buildRecipientsOverview(context, fund),
|
||||
],
|
||||
|
||||
// Message
|
||||
if (fund.message != null && fund.message!.isNotEmpty) ...[
|
||||
const SizedBox(height: 12),
|
||||
Text(
|
||||
'"${fund.message}"',
|
||||
style: Theme.of(
|
||||
context,
|
||||
).textTheme.bodyMedium?.copyWith(
|
||||
fontStyle: FontStyle.italic,
|
||||
color:
|
||||
Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
),
|
||||
),
|
||||
],
|
||||
|
||||
// Creator info
|
||||
if (fund.creatorAccount != null) ...[
|
||||
const SizedBox(height: 12),
|
||||
Row(
|
||||
children: [
|
||||
Icon(
|
||||
Icons.person,
|
||||
size: 16,
|
||||
color:
|
||||
Theme.of(
|
||||
context,
|
||||
).colorScheme.onSurfaceVariant,
|
||||
),
|
||||
const SizedBox(width: 4),
|
||||
Text(
|
||||
fund.creatorAccount!.nick,
|
||||
style: Theme.of(
|
||||
context,
|
||||
).textTheme.bodySmall?.copyWith(
|
||||
color:
|
||||
Theme.of(
|
||||
context,
|
||||
).colorScheme.onSurfaceVariant,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
|
||||
// Expiry info
|
||||
const SizedBox(height: 6),
|
||||
Row(
|
||||
children: [
|
||||
Icon(
|
||||
Icons.schedule,
|
||||
size: 16,
|
||||
color:
|
||||
Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
),
|
||||
const SizedBox(width: 4),
|
||||
Text(
|
||||
_formatDate(fund.expiredAt),
|
||||
style: Theme.of(
|
||||
context,
|
||||
).textTheme.bodySmall?.copyWith(
|
||||
color:
|
||||
Theme.of(
|
||||
context,
|
||||
).colorScheme.onSurfaceVariant,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _showClaimDialog(
|
||||
BuildContext context,
|
||||
WidgetRef ref,
|
||||
SnWalletFund fund,
|
||||
) {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder:
|
||||
(dialogContext) => FundClaimDialog(
|
||||
fund: fund,
|
||||
onClaim: () async {
|
||||
try {
|
||||
final apiClient = ref.read(apiClientProvider);
|
||||
await apiClient.post('/pass/wallets/funds/${fund.id}/receive');
|
||||
|
||||
// Refresh the fund data after claiming
|
||||
ref.invalidate(walletFundProvider(fund.id));
|
||||
|
||||
if (dialogContext.mounted) {
|
||||
Navigator.of(dialogContext).pop();
|
||||
ScaffoldMessenger.of(dialogContext).showSnackBar(
|
||||
SnackBar(content: Text('Fund claimed successfully!'.tr())),
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
if (dialogContext.mounted) {
|
||||
ScaffoldMessenger.of(dialogContext).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text('Failed to claim fund: $e'),
|
||||
backgroundColor:
|
||||
Theme.of(dialogContext).colorScheme.error,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildStatusChip(BuildContext context, int status) {
|
||||
String text;
|
||||
Color color;
|
||||
|
||||
switch (status) {
|
||||
case 0:
|
||||
text = 'Created';
|
||||
color = Colors.blue;
|
||||
break;
|
||||
case 1:
|
||||
text = 'Partially Claimed';
|
||||
color = Colors.orange;
|
||||
break;
|
||||
case 2:
|
||||
text = 'Fully Claimed';
|
||||
color = Colors.green;
|
||||
break;
|
||||
case 3:
|
||||
text = 'Expired';
|
||||
color = Colors.red;
|
||||
break;
|
||||
default:
|
||||
text = 'Unknown';
|
||||
color = Colors.grey;
|
||||
}
|
||||
|
||||
return Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2),
|
||||
decoration: BoxDecoration(
|
||||
color: color.withOpacity(0.1),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
border: Border.all(color: color.withOpacity(0.3)),
|
||||
),
|
||||
child: Text(
|
||||
text,
|
||||
style: TextStyle(
|
||||
fontSize: 10,
|
||||
color: color,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildStatusChips(BuildContext context, SnWalletFund fund) {
|
||||
return Row(
|
||||
children: [
|
||||
if (fund.isOpen) ...[
|
||||
_buildOpenFundBadge(context),
|
||||
const SizedBox(width: 6),
|
||||
],
|
||||
_buildStatusChip(context, fund.status),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildOpenFundBadge(BuildContext context) {
|
||||
return Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.green.withOpacity(0.1),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
border: Border.all(color: Colors.green.withOpacity(0.3)),
|
||||
),
|
||||
child: Text(
|
||||
'Open Fund'.tr(),
|
||||
style: const TextStyle(
|
||||
fontSize: 10,
|
||||
color: Colors.green,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildRecipientsOverview(BuildContext context, SnWalletFund fund) {
|
||||
final claimedCount = fund.recipients.where((r) => r.isReceived).length;
|
||||
final totalCount =
|
||||
fund.isOpen ? fund.amountOfSplits : fund.recipients.length;
|
||||
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'Recipients ($claimedCount/$totalCount claimed)',
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
LinearProgressIndicator(
|
||||
value: totalCount > 0 ? claimedCount / totalCount : 0,
|
||||
backgroundColor:
|
||||
Theme.of(context).colorScheme.surfaceContainerHighest,
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
String _formatDate(DateTime date) {
|
||||
try {
|
||||
final now = DateTime.now();
|
||||
final difference = date.difference(now);
|
||||
|
||||
if (difference.isNegative) {
|
||||
return 'Expired ${difference.inDays.abs()} days ago';
|
||||
} else if (difference.inDays == 0) {
|
||||
final hours = difference.inHours;
|
||||
if (hours == 0) {
|
||||
return 'Expires soon';
|
||||
}
|
||||
return 'Expires in $hours hour${hours == 1 ? '' : 's'}';
|
||||
} else if (difference.inDays < 7) {
|
||||
return 'Expires in ${difference.inDays} day${difference.inDays == 1 ? '' : 's'}';
|
||||
} else {
|
||||
return '${date.day}/${date.month}/${date.year}';
|
||||
}
|
||||
} catch (e) {
|
||||
return date.toString();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class FundClaimDialog extends HookConsumerWidget {
|
||||
const FundClaimDialog({super.key, required this.fund, required this.onClaim});
|
||||
|
||||
final SnWalletFund fund;
|
||||
final VoidCallback onClaim;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final userInfo = ref.watch(userInfoProvider);
|
||||
|
||||
// Check if user can claim
|
||||
final now = DateTime.now();
|
||||
final isExpired = fund.expiredAt.isBefore(now);
|
||||
final hasRemainingAmount = fund.remainingAmount > 0;
|
||||
final hasUserClaimed =
|
||||
userInfo.value != null &&
|
||||
fund.recipients.any(
|
||||
(recipient) =>
|
||||
recipient.recipientAccountId == userInfo.value!.id &&
|
||||
recipient.isReceived,
|
||||
);
|
||||
final userAbleToClaim =
|
||||
userInfo.value != null &&
|
||||
(fund.isOpen ||
|
||||
fund.recipients.any(
|
||||
(recipient) => recipient.recipientAccountId == userInfo.value!.id,
|
||||
));
|
||||
|
||||
final canClaim =
|
||||
!isExpired && hasRemainingAmount && !hasUserClaimed && userAbleToClaim;
|
||||
|
||||
// Get claimed recipients for display
|
||||
final claimedRecipients =
|
||||
fund.recipients.where((r) => r.isReceived).toList();
|
||||
final unclaimedRecipients =
|
||||
fund.recipients.where((r) => !r.isReceived).toList();
|
||||
|
||||
final remainingSplits =
|
||||
fund.isOpen
|
||||
? fund.amountOfSplits - claimedRecipients.length
|
||||
: unclaimedRecipients.length;
|
||||
|
||||
return AlertDialog(
|
||||
title: Row(
|
||||
children: [
|
||||
Icon(
|
||||
Icons.account_balance_wallet,
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Text('Claim Fund'.tr()),
|
||||
],
|
||||
),
|
||||
content: SingleChildScrollView(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// Fund info
|
||||
Text(
|
||||
'${fund.totalAmount.toStringAsFixed(2)} ${fund.currency}',
|
||||
style: Theme.of(context).textTheme.headlineSmall?.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
),
|
||||
|
||||
// Remaining amount
|
||||
Text(
|
||||
'${fund.remainingAmount.toStringAsFixed(2)} ${fund.currency} / ${remainingSplits} splits',
|
||||
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
|
||||
color: Theme.of(context).colorScheme.secondary,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
|
||||
// Status indicator
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
||||
decoration: BoxDecoration(
|
||||
color:
|
||||
fund.isOpen
|
||||
? Colors.green.withOpacity(0.1)
|
||||
: Colors.blue.withOpacity(0.1),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
border: Border.all(
|
||||
color:
|
||||
fund.isOpen
|
||||
? Colors.green.withOpacity(0.3)
|
||||
: Colors.blue.withOpacity(0.3),
|
||||
),
|
||||
),
|
||||
child: Text(
|
||||
fund.isOpen ? 'Open Fund'.tr() : 'Invite Only'.tr(),
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
color: fund.isOpen ? Colors.green : Colors.blue,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
const SizedBox(height: 16),
|
||||
|
||||
// Claimed recipients section
|
||||
if (claimedRecipients.isNotEmpty) ...[
|
||||
Text(
|
||||
'Already Claimed'.tr(),
|
||||
style: Theme.of(
|
||||
context,
|
||||
).textTheme.titleSmall?.copyWith(fontWeight: FontWeight.w600),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
...claimedRecipients.map(
|
||||
(recipient) => Padding(
|
||||
padding: const EdgeInsets.only(bottom: 4),
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(Icons.check_circle, size: 16, color: Colors.green),
|
||||
const SizedBox(width: 8),
|
||||
Expanded(
|
||||
child: Text(
|
||||
recipient.recipientAccount?.nick ?? 'Unknown User',
|
||||
style: Theme.of(
|
||||
context,
|
||||
).textTheme.bodySmall?.copyWith(
|
||||
decoration: TextDecoration.lineThrough,
|
||||
color:
|
||||
Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
),
|
||||
),
|
||||
),
|
||||
Text(
|
||||
'${recipient.amount.toStringAsFixed(2)} ${fund.currency}',
|
||||
style: Theme.of(context).textTheme.bodySmall?.copyWith(
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
],
|
||||
|
||||
// Unclaimed recipients section
|
||||
if (unclaimedRecipients.isNotEmpty) ...[
|
||||
Text(
|
||||
'Available to Claim'.tr(),
|
||||
style: Theme.of(
|
||||
context,
|
||||
).textTheme.titleSmall?.copyWith(fontWeight: FontWeight.w600),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
...unclaimedRecipients.map(
|
||||
(recipient) => Padding(
|
||||
padding: const EdgeInsets.only(bottom: 4),
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(
|
||||
Icons.radio_button_unchecked,
|
||||
size: 16,
|
||||
color: Theme.of(
|
||||
context,
|
||||
).colorScheme.onSurfaceVariant.withOpacity(0.5),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Expanded(
|
||||
child: Text(
|
||||
recipient.recipientAccount?.nick ?? 'Unknown User',
|
||||
style: Theme.of(context).textTheme.bodySmall,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
'${recipient.amount.toStringAsFixed(2)} ${fund.currency}',
|
||||
style: Theme.of(context).textTheme.bodySmall?.copyWith(
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
child: Text('Cancel'.tr()),
|
||||
),
|
||||
if (canClaim)
|
||||
FilledButton.icon(
|
||||
icon: const Icon(Icons.account_balance_wallet),
|
||||
label: Text('Claim'.tr()),
|
||||
onPressed: onClaim,
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
151
lib/widgets/wallet/fund_envelope.g.dart
Normal file
151
lib/widgets/wallet/fund_envelope.g.dart
Normal file
@@ -0,0 +1,151 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'fund_envelope.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// RiverpodGenerator
|
||||
// **************************************************************************
|
||||
|
||||
String _$walletFundHash() => r'521fa280708e71266f8164268ba11f135f4ba810';
|
||||
|
||||
/// Copied from Dart SDK
|
||||
class _SystemHash {
|
||||
_SystemHash._();
|
||||
|
||||
static int combine(int hash, int value) {
|
||||
// ignore: parameter_assignments
|
||||
hash = 0x1fffffff & (hash + value);
|
||||
// ignore: parameter_assignments
|
||||
hash = 0x1fffffff & (hash + ((0x0007ffff & hash) << 10));
|
||||
return hash ^ (hash >> 6);
|
||||
}
|
||||
|
||||
static int finish(int hash) {
|
||||
// ignore: parameter_assignments
|
||||
hash = 0x1fffffff & (hash + ((0x03ffffff & hash) << 3));
|
||||
// ignore: parameter_assignments
|
||||
hash = hash ^ (hash >> 11);
|
||||
return 0x1fffffff & (hash + ((0x00003fff & hash) << 15));
|
||||
}
|
||||
}
|
||||
|
||||
/// See also [walletFund].
|
||||
@ProviderFor(walletFund)
|
||||
const walletFundProvider = WalletFundFamily();
|
||||
|
||||
/// See also [walletFund].
|
||||
class WalletFundFamily extends Family<AsyncValue<SnWalletFund>> {
|
||||
/// See also [walletFund].
|
||||
const WalletFundFamily();
|
||||
|
||||
/// See also [walletFund].
|
||||
WalletFundProvider call(String fundId) {
|
||||
return WalletFundProvider(fundId);
|
||||
}
|
||||
|
||||
@override
|
||||
WalletFundProvider getProviderOverride(
|
||||
covariant WalletFundProvider provider,
|
||||
) {
|
||||
return call(provider.fundId);
|
||||
}
|
||||
|
||||
static const Iterable<ProviderOrFamily>? _dependencies = null;
|
||||
|
||||
@override
|
||||
Iterable<ProviderOrFamily>? get dependencies => _dependencies;
|
||||
|
||||
static const Iterable<ProviderOrFamily>? _allTransitiveDependencies = null;
|
||||
|
||||
@override
|
||||
Iterable<ProviderOrFamily>? get allTransitiveDependencies =>
|
||||
_allTransitiveDependencies;
|
||||
|
||||
@override
|
||||
String? get name => r'walletFundProvider';
|
||||
}
|
||||
|
||||
/// See also [walletFund].
|
||||
class WalletFundProvider extends AutoDisposeFutureProvider<SnWalletFund> {
|
||||
/// See also [walletFund].
|
||||
WalletFundProvider(String fundId)
|
||||
: this._internal(
|
||||
(ref) => walletFund(ref as WalletFundRef, fundId),
|
||||
from: walletFundProvider,
|
||||
name: r'walletFundProvider',
|
||||
debugGetCreateSourceHash:
|
||||
const bool.fromEnvironment('dart.vm.product')
|
||||
? null
|
||||
: _$walletFundHash,
|
||||
dependencies: WalletFundFamily._dependencies,
|
||||
allTransitiveDependencies: WalletFundFamily._allTransitiveDependencies,
|
||||
fundId: fundId,
|
||||
);
|
||||
|
||||
WalletFundProvider._internal(
|
||||
super._createNotifier, {
|
||||
required super.name,
|
||||
required super.dependencies,
|
||||
required super.allTransitiveDependencies,
|
||||
required super.debugGetCreateSourceHash,
|
||||
required super.from,
|
||||
required this.fundId,
|
||||
}) : super.internal();
|
||||
|
||||
final String fundId;
|
||||
|
||||
@override
|
||||
Override overrideWith(
|
||||
FutureOr<SnWalletFund> Function(WalletFundRef provider) create,
|
||||
) {
|
||||
return ProviderOverride(
|
||||
origin: this,
|
||||
override: WalletFundProvider._internal(
|
||||
(ref) => create(ref as WalletFundRef),
|
||||
from: from,
|
||||
name: null,
|
||||
dependencies: null,
|
||||
allTransitiveDependencies: null,
|
||||
debugGetCreateSourceHash: null,
|
||||
fundId: fundId,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
AutoDisposeFutureProviderElement<SnWalletFund> createElement() {
|
||||
return _WalletFundProviderElement(this);
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return other is WalletFundProvider && other.fundId == fundId;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode {
|
||||
var hash = _SystemHash.combine(0, runtimeType.hashCode);
|
||||
hash = _SystemHash.combine(hash, fundId.hashCode);
|
||||
|
||||
return _SystemHash.finish(hash);
|
||||
}
|
||||
}
|
||||
|
||||
@Deprecated('Will be removed in 3.0. Use Ref instead')
|
||||
// ignore: unused_element
|
||||
mixin WalletFundRef on AutoDisposeFutureProviderRef<SnWalletFund> {
|
||||
/// The parameter `fundId` of this provider.
|
||||
String get fundId;
|
||||
}
|
||||
|
||||
class _WalletFundProviderElement
|
||||
extends AutoDisposeFutureProviderElement<SnWalletFund>
|
||||
with WalletFundRef {
|
||||
_WalletFundProviderElement(super.provider);
|
||||
|
||||
@override
|
||||
String get fundId => (origin as WalletFundProvider).fundId;
|
||||
}
|
||||
|
||||
// ignore_for_file: type=lint
|
||||
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package
|
||||
@@ -16,7 +16,7 @@ publish_to: "none" # Remove this line if you wish to publish to pub.dev
|
||||
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
|
||||
# In Windows, build-name is used as the major, minor, and patch parts
|
||||
# of the product and file versions while build-number is used as the build suffix.
|
||||
version: 3.3.0+145
|
||||
version: 3.3.0+146
|
||||
|
||||
environment:
|
||||
sdk: ^3.7.2
|
||||
|
||||
Reference in New Issue
Block a user