💄 Optimize publisher first time UX
♻️ Split up the forms and list screens 💄 Use dropdown forms fields instead of selection
This commit is contained in:
@@ -124,3 +124,30 @@ sealed class SnWalletOrder with _$SnWalletOrder {
|
||||
factory SnWalletOrder.fromJson(Map<String, dynamic> json) =>
|
||||
_$SnWalletOrderFromJson(json);
|
||||
}
|
||||
|
||||
@freezed
|
||||
sealed class SnWalletGift with _$SnWalletGift {
|
||||
const factory SnWalletGift({
|
||||
required String id,
|
||||
required String giftCode,
|
||||
required String subscriptionIdentifier,
|
||||
required String? recipientId,
|
||||
required SnAccount? recipient,
|
||||
required String gifterId,
|
||||
required SnAccount? gifter,
|
||||
required String? redeemerId,
|
||||
required SnAccount? redeemer,
|
||||
required String? message,
|
||||
required int status,
|
||||
required DateTime? redeemedAt,
|
||||
required DateTime? expiredAt,
|
||||
required String? subscriptionId,
|
||||
required SnWalletSubscription? subscription,
|
||||
required DateTime createdAt,
|
||||
required DateTime updatedAt,
|
||||
required DateTime? deletedAt,
|
||||
}) = _SnWalletGift;
|
||||
|
||||
factory SnWalletGift.fromJson(Map<String, dynamic> json) =>
|
||||
_$SnWalletGiftFromJson(json);
|
||||
}
|
||||
|
@@ -1852,4 +1852,408 @@ as DateTime?,
|
||||
|
||||
}
|
||||
|
||||
|
||||
/// @nodoc
|
||||
mixin _$SnWalletGift {
|
||||
|
||||
String get id; String get giftCode; String get subscriptionIdentifier; String? get recipientId; SnAccount? get recipient; String get gifterId; SnAccount? get gifter; String? get redeemerId; SnAccount? get redeemer; String? get message; int get status; DateTime? get redeemedAt; DateTime? get expiredAt; String? get subscriptionId; SnWalletSubscription? get subscription; DateTime get createdAt; DateTime get updatedAt; DateTime? get deletedAt;
|
||||
/// Create a copy of SnWalletGift
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@pragma('vm:prefer-inline')
|
||||
$SnWalletGiftCopyWith<SnWalletGift> get copyWith => _$SnWalletGiftCopyWithImpl<SnWalletGift>(this as SnWalletGift, _$identity);
|
||||
|
||||
/// Serializes this SnWalletGift to a JSON map.
|
||||
Map<String, dynamic> toJson();
|
||||
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is SnWalletGift&&(identical(other.id, id) || other.id == id)&&(identical(other.giftCode, giftCode) || other.giftCode == giftCode)&&(identical(other.subscriptionIdentifier, subscriptionIdentifier) || other.subscriptionIdentifier == subscriptionIdentifier)&&(identical(other.recipientId, recipientId) || other.recipientId == recipientId)&&(identical(other.recipient, recipient) || other.recipient == recipient)&&(identical(other.gifterId, gifterId) || other.gifterId == gifterId)&&(identical(other.gifter, gifter) || other.gifter == gifter)&&(identical(other.redeemerId, redeemerId) || other.redeemerId == redeemerId)&&(identical(other.redeemer, redeemer) || other.redeemer == redeemer)&&(identical(other.message, message) || other.message == message)&&(identical(other.status, status) || other.status == status)&&(identical(other.redeemedAt, redeemedAt) || other.redeemedAt == redeemedAt)&&(identical(other.expiredAt, expiredAt) || other.expiredAt == expiredAt)&&(identical(other.subscriptionId, subscriptionId) || other.subscriptionId == subscriptionId)&&(identical(other.subscription, subscription) || other.subscription == subscription)&&(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,giftCode,subscriptionIdentifier,recipientId,recipient,gifterId,gifter,redeemerId,redeemer,message,status,redeemedAt,expiredAt,subscriptionId,subscription,createdAt,updatedAt,deletedAt);
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'SnWalletGift(id: $id, giftCode: $giftCode, subscriptionIdentifier: $subscriptionIdentifier, recipientId: $recipientId, recipient: $recipient, gifterId: $gifterId, gifter: $gifter, redeemerId: $redeemerId, redeemer: $redeemer, message: $message, status: $status, redeemedAt: $redeemedAt, expiredAt: $expiredAt, subscriptionId: $subscriptionId, subscription: $subscription, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt)';
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract mixin class $SnWalletGiftCopyWith<$Res> {
|
||||
factory $SnWalletGiftCopyWith(SnWalletGift value, $Res Function(SnWalletGift) _then) = _$SnWalletGiftCopyWithImpl;
|
||||
@useResult
|
||||
$Res call({
|
||||
String id, String giftCode, String subscriptionIdentifier, String? recipientId, SnAccount? recipient, String gifterId, SnAccount? gifter, String? redeemerId, SnAccount? redeemer, String? message, int status, DateTime? redeemedAt, DateTime? expiredAt, String? subscriptionId, SnWalletSubscription? subscription, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt
|
||||
});
|
||||
|
||||
|
||||
$SnAccountCopyWith<$Res>? get recipient;$SnAccountCopyWith<$Res>? get gifter;$SnAccountCopyWith<$Res>? get redeemer;$SnWalletSubscriptionCopyWith<$Res>? get subscription;
|
||||
|
||||
}
|
||||
/// @nodoc
|
||||
class _$SnWalletGiftCopyWithImpl<$Res>
|
||||
implements $SnWalletGiftCopyWith<$Res> {
|
||||
_$SnWalletGiftCopyWithImpl(this._self, this._then);
|
||||
|
||||
final SnWalletGift _self;
|
||||
final $Res Function(SnWalletGift) _then;
|
||||
|
||||
/// Create a copy of SnWalletGift
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? giftCode = null,Object? subscriptionIdentifier = null,Object? recipientId = freezed,Object? recipient = freezed,Object? gifterId = null,Object? gifter = freezed,Object? redeemerId = freezed,Object? redeemer = freezed,Object? message = freezed,Object? status = null,Object? redeemedAt = freezed,Object? expiredAt = freezed,Object? subscriptionId = freezed,Object? subscription = freezed,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,giftCode: null == giftCode ? _self.giftCode : giftCode // ignore: cast_nullable_to_non_nullable
|
||||
as String,subscriptionIdentifier: null == subscriptionIdentifier ? _self.subscriptionIdentifier : subscriptionIdentifier // ignore: cast_nullable_to_non_nullable
|
||||
as String,recipientId: freezed == recipientId ? _self.recipientId : recipientId // ignore: cast_nullable_to_non_nullable
|
||||
as String?,recipient: freezed == recipient ? _self.recipient : recipient // ignore: cast_nullable_to_non_nullable
|
||||
as SnAccount?,gifterId: null == gifterId ? _self.gifterId : gifterId // ignore: cast_nullable_to_non_nullable
|
||||
as String,gifter: freezed == gifter ? _self.gifter : gifter // ignore: cast_nullable_to_non_nullable
|
||||
as SnAccount?,redeemerId: freezed == redeemerId ? _self.redeemerId : redeemerId // ignore: cast_nullable_to_non_nullable
|
||||
as String?,redeemer: freezed == redeemer ? _self.redeemer : redeemer // ignore: cast_nullable_to_non_nullable
|
||||
as SnAccount?,message: freezed == message ? _self.message : message // ignore: cast_nullable_to_non_nullable
|
||||
as String?,status: null == status ? _self.status : status // ignore: cast_nullable_to_non_nullable
|
||||
as int,redeemedAt: freezed == redeemedAt ? _self.redeemedAt : redeemedAt // ignore: cast_nullable_to_non_nullable
|
||||
as DateTime?,expiredAt: freezed == expiredAt ? _self.expiredAt : expiredAt // ignore: cast_nullable_to_non_nullable
|
||||
as DateTime?,subscriptionId: freezed == subscriptionId ? _self.subscriptionId : subscriptionId // ignore: cast_nullable_to_non_nullable
|
||||
as String?,subscription: freezed == subscription ? _self.subscription : subscription // ignore: cast_nullable_to_non_nullable
|
||||
as SnWalletSubscription?,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?,
|
||||
));
|
||||
}
|
||||
/// Create a copy of SnWalletGift
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override
|
||||
@pragma('vm:prefer-inline')
|
||||
$SnAccountCopyWith<$Res>? get recipient {
|
||||
if (_self.recipient == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $SnAccountCopyWith<$Res>(_self.recipient!, (value) {
|
||||
return _then(_self.copyWith(recipient: value));
|
||||
});
|
||||
}/// Create a copy of SnWalletGift
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override
|
||||
@pragma('vm:prefer-inline')
|
||||
$SnAccountCopyWith<$Res>? get gifter {
|
||||
if (_self.gifter == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $SnAccountCopyWith<$Res>(_self.gifter!, (value) {
|
||||
return _then(_self.copyWith(gifter: value));
|
||||
});
|
||||
}/// Create a copy of SnWalletGift
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override
|
||||
@pragma('vm:prefer-inline')
|
||||
$SnAccountCopyWith<$Res>? get redeemer {
|
||||
if (_self.redeemer == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $SnAccountCopyWith<$Res>(_self.redeemer!, (value) {
|
||||
return _then(_self.copyWith(redeemer: value));
|
||||
});
|
||||
}/// Create a copy of SnWalletGift
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override
|
||||
@pragma('vm:prefer-inline')
|
||||
$SnWalletSubscriptionCopyWith<$Res>? get subscription {
|
||||
if (_self.subscription == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $SnWalletSubscriptionCopyWith<$Res>(_self.subscription!, (value) {
|
||||
return _then(_self.copyWith(subscription: value));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// Adds pattern-matching-related methods to [SnWalletGift].
|
||||
extension SnWalletGiftPatterns on SnWalletGift {
|
||||
/// 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( _SnWalletGift value)? $default,{required TResult orElse(),}){
|
||||
final _that = this;
|
||||
switch (_that) {
|
||||
case _SnWalletGift() 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( _SnWalletGift value) $default,){
|
||||
final _that = this;
|
||||
switch (_that) {
|
||||
case _SnWalletGift():
|
||||
return $default(_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( _SnWalletGift value)? $default,){
|
||||
final _that = this;
|
||||
switch (_that) {
|
||||
case _SnWalletGift() 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 giftCode, String subscriptionIdentifier, String? recipientId, SnAccount? recipient, String gifterId, SnAccount? gifter, String? redeemerId, SnAccount? redeemer, String? message, int status, DateTime? redeemedAt, DateTime? expiredAt, String? subscriptionId, SnWalletSubscription? subscription, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt)? $default,{required TResult orElse(),}) {final _that = this;
|
||||
switch (_that) {
|
||||
case _SnWalletGift() when $default != null:
|
||||
return $default(_that.id,_that.giftCode,_that.subscriptionIdentifier,_that.recipientId,_that.recipient,_that.gifterId,_that.gifter,_that.redeemerId,_that.redeemer,_that.message,_that.status,_that.redeemedAt,_that.expiredAt,_that.subscriptionId,_that.subscription,_that.createdAt,_that.updatedAt,_that.deletedAt);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 giftCode, String subscriptionIdentifier, String? recipientId, SnAccount? recipient, String gifterId, SnAccount? gifter, String? redeemerId, SnAccount? redeemer, String? message, int status, DateTime? redeemedAt, DateTime? expiredAt, String? subscriptionId, SnWalletSubscription? subscription, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt) $default,) {final _that = this;
|
||||
switch (_that) {
|
||||
case _SnWalletGift():
|
||||
return $default(_that.id,_that.giftCode,_that.subscriptionIdentifier,_that.recipientId,_that.recipient,_that.gifterId,_that.gifter,_that.redeemerId,_that.redeemer,_that.message,_that.status,_that.redeemedAt,_that.expiredAt,_that.subscriptionId,_that.subscription,_that.createdAt,_that.updatedAt,_that.deletedAt);}
|
||||
}
|
||||
/// 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 giftCode, String subscriptionIdentifier, String? recipientId, SnAccount? recipient, String gifterId, SnAccount? gifter, String? redeemerId, SnAccount? redeemer, String? message, int status, DateTime? redeemedAt, DateTime? expiredAt, String? subscriptionId, SnWalletSubscription? subscription, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt)? $default,) {final _that = this;
|
||||
switch (_that) {
|
||||
case _SnWalletGift() when $default != null:
|
||||
return $default(_that.id,_that.giftCode,_that.subscriptionIdentifier,_that.recipientId,_that.recipient,_that.gifterId,_that.gifter,_that.redeemerId,_that.redeemer,_that.message,_that.status,_that.redeemedAt,_that.expiredAt,_that.subscriptionId,_that.subscription,_that.createdAt,_that.updatedAt,_that.deletedAt);case _:
|
||||
return null;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
@JsonSerializable()
|
||||
|
||||
class _SnWalletGift implements SnWalletGift {
|
||||
const _SnWalletGift({required this.id, required this.giftCode, required this.subscriptionIdentifier, required this.recipientId, required this.recipient, required this.gifterId, required this.gifter, required this.redeemerId, required this.redeemer, required this.message, required this.status, required this.redeemedAt, required this.expiredAt, required this.subscriptionId, required this.subscription, required this.createdAt, required this.updatedAt, required this.deletedAt});
|
||||
factory _SnWalletGift.fromJson(Map<String, dynamic> json) => _$SnWalletGiftFromJson(json);
|
||||
|
||||
@override final String id;
|
||||
@override final String giftCode;
|
||||
@override final String subscriptionIdentifier;
|
||||
@override final String? recipientId;
|
||||
@override final SnAccount? recipient;
|
||||
@override final String gifterId;
|
||||
@override final SnAccount? gifter;
|
||||
@override final String? redeemerId;
|
||||
@override final SnAccount? redeemer;
|
||||
@override final String? message;
|
||||
@override final int status;
|
||||
@override final DateTime? redeemedAt;
|
||||
@override final DateTime? expiredAt;
|
||||
@override final String? subscriptionId;
|
||||
@override final SnWalletSubscription? subscription;
|
||||
@override final DateTime createdAt;
|
||||
@override final DateTime updatedAt;
|
||||
@override final DateTime? deletedAt;
|
||||
|
||||
/// Create a copy of SnWalletGift
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override @JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@pragma('vm:prefer-inline')
|
||||
_$SnWalletGiftCopyWith<_SnWalletGift> get copyWith => __$SnWalletGiftCopyWithImpl<_SnWalletGift>(this, _$identity);
|
||||
|
||||
@override
|
||||
Map<String, dynamic> toJson() {
|
||||
return _$SnWalletGiftToJson(this, );
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is _SnWalletGift&&(identical(other.id, id) || other.id == id)&&(identical(other.giftCode, giftCode) || other.giftCode == giftCode)&&(identical(other.subscriptionIdentifier, subscriptionIdentifier) || other.subscriptionIdentifier == subscriptionIdentifier)&&(identical(other.recipientId, recipientId) || other.recipientId == recipientId)&&(identical(other.recipient, recipient) || other.recipient == recipient)&&(identical(other.gifterId, gifterId) || other.gifterId == gifterId)&&(identical(other.gifter, gifter) || other.gifter == gifter)&&(identical(other.redeemerId, redeemerId) || other.redeemerId == redeemerId)&&(identical(other.redeemer, redeemer) || other.redeemer == redeemer)&&(identical(other.message, message) || other.message == message)&&(identical(other.status, status) || other.status == status)&&(identical(other.redeemedAt, redeemedAt) || other.redeemedAt == redeemedAt)&&(identical(other.expiredAt, expiredAt) || other.expiredAt == expiredAt)&&(identical(other.subscriptionId, subscriptionId) || other.subscriptionId == subscriptionId)&&(identical(other.subscription, subscription) || other.subscription == subscription)&&(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,giftCode,subscriptionIdentifier,recipientId,recipient,gifterId,gifter,redeemerId,redeemer,message,status,redeemedAt,expiredAt,subscriptionId,subscription,createdAt,updatedAt,deletedAt);
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'SnWalletGift(id: $id, giftCode: $giftCode, subscriptionIdentifier: $subscriptionIdentifier, recipientId: $recipientId, recipient: $recipient, gifterId: $gifterId, gifter: $gifter, redeemerId: $redeemerId, redeemer: $redeemer, message: $message, status: $status, redeemedAt: $redeemedAt, expiredAt: $expiredAt, subscriptionId: $subscriptionId, subscription: $subscription, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt)';
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract mixin class _$SnWalletGiftCopyWith<$Res> implements $SnWalletGiftCopyWith<$Res> {
|
||||
factory _$SnWalletGiftCopyWith(_SnWalletGift value, $Res Function(_SnWalletGift) _then) = __$SnWalletGiftCopyWithImpl;
|
||||
@override @useResult
|
||||
$Res call({
|
||||
String id, String giftCode, String subscriptionIdentifier, String? recipientId, SnAccount? recipient, String gifterId, SnAccount? gifter, String? redeemerId, SnAccount? redeemer, String? message, int status, DateTime? redeemedAt, DateTime? expiredAt, String? subscriptionId, SnWalletSubscription? subscription, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt
|
||||
});
|
||||
|
||||
|
||||
@override $SnAccountCopyWith<$Res>? get recipient;@override $SnAccountCopyWith<$Res>? get gifter;@override $SnAccountCopyWith<$Res>? get redeemer;@override $SnWalletSubscriptionCopyWith<$Res>? get subscription;
|
||||
|
||||
}
|
||||
/// @nodoc
|
||||
class __$SnWalletGiftCopyWithImpl<$Res>
|
||||
implements _$SnWalletGiftCopyWith<$Res> {
|
||||
__$SnWalletGiftCopyWithImpl(this._self, this._then);
|
||||
|
||||
final _SnWalletGift _self;
|
||||
final $Res Function(_SnWalletGift) _then;
|
||||
|
||||
/// Create a copy of SnWalletGift
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? giftCode = null,Object? subscriptionIdentifier = null,Object? recipientId = freezed,Object? recipient = freezed,Object? gifterId = null,Object? gifter = freezed,Object? redeemerId = freezed,Object? redeemer = freezed,Object? message = freezed,Object? status = null,Object? redeemedAt = freezed,Object? expiredAt = freezed,Object? subscriptionId = freezed,Object? subscription = freezed,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,}) {
|
||||
return _then(_SnWalletGift(
|
||||
id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
|
||||
as String,giftCode: null == giftCode ? _self.giftCode : giftCode // ignore: cast_nullable_to_non_nullable
|
||||
as String,subscriptionIdentifier: null == subscriptionIdentifier ? _self.subscriptionIdentifier : subscriptionIdentifier // ignore: cast_nullable_to_non_nullable
|
||||
as String,recipientId: freezed == recipientId ? _self.recipientId : recipientId // ignore: cast_nullable_to_non_nullable
|
||||
as String?,recipient: freezed == recipient ? _self.recipient : recipient // ignore: cast_nullable_to_non_nullable
|
||||
as SnAccount?,gifterId: null == gifterId ? _self.gifterId : gifterId // ignore: cast_nullable_to_non_nullable
|
||||
as String,gifter: freezed == gifter ? _self.gifter : gifter // ignore: cast_nullable_to_non_nullable
|
||||
as SnAccount?,redeemerId: freezed == redeemerId ? _self.redeemerId : redeemerId // ignore: cast_nullable_to_non_nullable
|
||||
as String?,redeemer: freezed == redeemer ? _self.redeemer : redeemer // ignore: cast_nullable_to_non_nullable
|
||||
as SnAccount?,message: freezed == message ? _self.message : message // ignore: cast_nullable_to_non_nullable
|
||||
as String?,status: null == status ? _self.status : status // ignore: cast_nullable_to_non_nullable
|
||||
as int,redeemedAt: freezed == redeemedAt ? _self.redeemedAt : redeemedAt // ignore: cast_nullable_to_non_nullable
|
||||
as DateTime?,expiredAt: freezed == expiredAt ? _self.expiredAt : expiredAt // ignore: cast_nullable_to_non_nullable
|
||||
as DateTime?,subscriptionId: freezed == subscriptionId ? _self.subscriptionId : subscriptionId // ignore: cast_nullable_to_non_nullable
|
||||
as String?,subscription: freezed == subscription ? _self.subscription : subscription // ignore: cast_nullable_to_non_nullable
|
||||
as SnWalletSubscription?,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?,
|
||||
));
|
||||
}
|
||||
|
||||
/// Create a copy of SnWalletGift
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override
|
||||
@pragma('vm:prefer-inline')
|
||||
$SnAccountCopyWith<$Res>? get recipient {
|
||||
if (_self.recipient == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $SnAccountCopyWith<$Res>(_self.recipient!, (value) {
|
||||
return _then(_self.copyWith(recipient: value));
|
||||
});
|
||||
}/// Create a copy of SnWalletGift
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override
|
||||
@pragma('vm:prefer-inline')
|
||||
$SnAccountCopyWith<$Res>? get gifter {
|
||||
if (_self.gifter == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $SnAccountCopyWith<$Res>(_self.gifter!, (value) {
|
||||
return _then(_self.copyWith(gifter: value));
|
||||
});
|
||||
}/// Create a copy of SnWalletGift
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override
|
||||
@pragma('vm:prefer-inline')
|
||||
$SnAccountCopyWith<$Res>? get redeemer {
|
||||
if (_self.redeemer == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $SnAccountCopyWith<$Res>(_self.redeemer!, (value) {
|
||||
return _then(_self.copyWith(redeemer: value));
|
||||
});
|
||||
}/// Create a copy of SnWalletGift
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override
|
||||
@pragma('vm:prefer-inline')
|
||||
$SnWalletSubscriptionCopyWith<$Res>? get subscription {
|
||||
if (_self.subscription == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $SnWalletSubscriptionCopyWith<$Res>(_self.subscription!, (value) {
|
||||
return _then(_self.copyWith(subscription: value));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// dart format on
|
||||
|
@@ -228,3 +228,70 @@ Map<String, dynamic> _$SnWalletOrderToJson(_SnWalletOrder instance) =>
|
||||
'updated_at': instance.updatedAt.toIso8601String(),
|
||||
'deleted_at': instance.deletedAt?.toIso8601String(),
|
||||
};
|
||||
|
||||
_SnWalletGift _$SnWalletGiftFromJson(Map<String, dynamic> json) =>
|
||||
_SnWalletGift(
|
||||
id: json['id'] as String,
|
||||
giftCode: json['gift_code'] as String,
|
||||
subscriptionIdentifier: json['subscription_identifier'] as String,
|
||||
recipientId: json['recipient_id'] as String?,
|
||||
recipient:
|
||||
json['recipient'] == null
|
||||
? null
|
||||
: SnAccount.fromJson(json['recipient'] as Map<String, dynamic>),
|
||||
gifterId: json['gifter_id'] as String,
|
||||
gifter:
|
||||
json['gifter'] == null
|
||||
? null
|
||||
: SnAccount.fromJson(json['gifter'] as Map<String, dynamic>),
|
||||
redeemerId: json['redeemer_id'] as String?,
|
||||
redeemer:
|
||||
json['redeemer'] == null
|
||||
? null
|
||||
: SnAccount.fromJson(json['redeemer'] as Map<String, dynamic>),
|
||||
message: json['message'] as String?,
|
||||
status: (json['status'] as num).toInt(),
|
||||
redeemedAt:
|
||||
json['redeemed_at'] == null
|
||||
? null
|
||||
: DateTime.parse(json['redeemed_at'] as String),
|
||||
expiredAt:
|
||||
json['expired_at'] == null
|
||||
? null
|
||||
: DateTime.parse(json['expired_at'] as String),
|
||||
subscriptionId: json['subscription_id'] as String?,
|
||||
subscription:
|
||||
json['subscription'] == null
|
||||
? null
|
||||
: SnWalletSubscription.fromJson(
|
||||
json['subscription'] as Map<String, dynamic>,
|
||||
),
|
||||
createdAt: DateTime.parse(json['created_at'] as String),
|
||||
updatedAt: DateTime.parse(json['updated_at'] as String),
|
||||
deletedAt:
|
||||
json['deleted_at'] == null
|
||||
? null
|
||||
: DateTime.parse(json['deleted_at'] as String),
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$SnWalletGiftToJson(_SnWalletGift instance) =>
|
||||
<String, dynamic>{
|
||||
'id': instance.id,
|
||||
'gift_code': instance.giftCode,
|
||||
'subscription_identifier': instance.subscriptionIdentifier,
|
||||
'recipient_id': instance.recipientId,
|
||||
'recipient': instance.recipient?.toJson(),
|
||||
'gifter_id': instance.gifterId,
|
||||
'gifter': instance.gifter?.toJson(),
|
||||
'redeemer_id': instance.redeemerId,
|
||||
'redeemer': instance.redeemer?.toJson(),
|
||||
'message': instance.message,
|
||||
'status': instance.status,
|
||||
'redeemed_at': instance.redeemedAt?.toIso8601String(),
|
||||
'expired_at': instance.expiredAt?.toIso8601String(),
|
||||
'subscription_id': instance.subscriptionId,
|
||||
'subscription': instance.subscription?.toJson(),
|
||||
'created_at': instance.createdAt.toIso8601String(),
|
||||
'updated_at': instance.updatedAt.toIso8601String(),
|
||||
'deleted_at': instance.deletedAt?.toIso8601String(),
|
||||
};
|
||||
|
@@ -35,6 +35,7 @@ import 'package:island/screens/account/me/profile_update.dart';
|
||||
import 'package:island/screens/account/leveling.dart';
|
||||
import 'package:island/screens/account/me/account_settings.dart';
|
||||
import 'package:island/screens/chat/chat.dart';
|
||||
import 'package:island/screens/chat/chat_form.dart';
|
||||
import 'package:island/screens/chat/room.dart';
|
||||
import 'package:island/screens/chat/room_detail.dart';
|
||||
import 'package:island/screens/chat/call.dart';
|
||||
@@ -48,7 +49,7 @@ import 'package:island/screens/stickers/pack_detail.dart';
|
||||
import 'package:island/screens/discovery/feeds/feed_marketplace.dart';
|
||||
import 'package:island/screens/discovery/feeds/feed_detail.dart';
|
||||
import 'package:island/screens/creators/poll/poll_list.dart';
|
||||
import 'package:island/screens/creators/publishers.dart';
|
||||
import 'package:island/screens/creators/publishers_form.dart';
|
||||
import 'package:island/screens/creators/webfeed/webfeed_list.dart';
|
||||
import 'package:island/screens/creators/webfeed/webfeed_edit.dart';
|
||||
import 'package:island/screens/poll/poll_editor.dart';
|
||||
@@ -59,6 +60,7 @@ import 'package:island/screens/auth/login.dart';
|
||||
import 'package:island/screens/auth/create_account.dart';
|
||||
import 'package:island/screens/settings.dart';
|
||||
import 'package:island/screens/realm/realms.dart';
|
||||
import 'package:island/screens/realm/realm_form.dart';
|
||||
import 'package:island/screens/realm/realm_detail.dart';
|
||||
import 'package:island/screens/account/event_calendar.dart';
|
||||
import 'package:island/screens/discovery/realms.dart';
|
||||
|
@@ -1,10 +1,12 @@
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:island/pods/network.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
|
||||
part 'captcha.config.g.dart';
|
||||
|
||||
@riverpod
|
||||
Future<String> captchaUrl(Ref ref) async {
|
||||
const baseUrl = "https://solian.app";
|
||||
final apiClient = ref.watch(apiClientProvider);
|
||||
final baseUrl = await apiClient.get('/config/site');
|
||||
return '$baseUrl/auth/captcha';
|
||||
}
|
||||
|
@@ -1,5 +1,3 @@
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:croppy/croppy.dart' hide cropImage;
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
@@ -7,23 +5,17 @@ import 'package:go_router/go_router.dart';
|
||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:gap/gap.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:image_picker/image_picker.dart';
|
||||
import 'package:island/models/chat.dart';
|
||||
import 'package:island/models/file.dart';
|
||||
import 'package:island/models/realm.dart';
|
||||
import 'package:island/pods/chat/call.dart';
|
||||
import 'package:island/pods/chat/chat_summary.dart';
|
||||
import 'package:island/pods/network.dart';
|
||||
import 'package:island/screens/realm/realms.dart';
|
||||
import 'package:island/services/file.dart';
|
||||
import 'package:island/services/file_uploader.dart';
|
||||
import 'package:island/services/responsive.dart';
|
||||
import 'package:island/widgets/account/account_picker.dart';
|
||||
import 'package:island/widgets/alert.dart';
|
||||
import 'package:island/widgets/app_scaffold.dart';
|
||||
import 'package:island/widgets/content/cloud_files.dart';
|
||||
import 'package:island/widgets/content/sheet.dart';
|
||||
import 'package:island/widgets/realm/realm_selection_dropdown.dart';
|
||||
import 'package:island/widgets/response.dart';
|
||||
import 'package:material_symbols_icons/symbols.dart';
|
||||
import 'package:relative_time/relative_time.dart';
|
||||
@@ -568,269 +560,6 @@ Future<SnChatMember?> chatroomIdentity(Ref ref, String? identifier) async {
|
||||
}
|
||||
}
|
||||
|
||||
class NewChatScreen extends StatelessWidget {
|
||||
const NewChatScreen({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return const EditChatScreen();
|
||||
}
|
||||
}
|
||||
|
||||
class EditChatScreen extends HookConsumerWidget {
|
||||
final String? id;
|
||||
const EditChatScreen({super.key, this.id});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final formKey = useMemoized(() => GlobalKey<FormState>(), []);
|
||||
|
||||
final submitting = useState(false);
|
||||
|
||||
final nameController = useTextEditingController();
|
||||
final descriptionController = useTextEditingController();
|
||||
final picture = useState<SnCloudFile?>(null);
|
||||
final background = useState<SnCloudFile?>(null);
|
||||
final isPublic = useState(true);
|
||||
final isCommunity = useState(false);
|
||||
|
||||
final chat = ref.watch(chatroomProvider(id));
|
||||
|
||||
final joinedRealms = ref.watch(realmsJoinedProvider);
|
||||
final currentRealm = useState<SnRealm?>(null);
|
||||
|
||||
useEffect(() {
|
||||
if (chat.value != null) {
|
||||
nameController.text = chat.value!.name ?? '';
|
||||
descriptionController.text = chat.value!.description ?? '';
|
||||
picture.value = chat.value!.picture;
|
||||
background.value = chat.value!.background;
|
||||
isPublic.value = chat.value!.isPublic;
|
||||
isCommunity.value = chat.value!.isCommunity;
|
||||
currentRealm.value = joinedRealms.value?.firstWhereOrNull(
|
||||
(realm) => realm.id == chat.value!.realmId,
|
||||
);
|
||||
}
|
||||
return;
|
||||
}, [chat, joinedRealms]);
|
||||
|
||||
void setPicture(String position) async {
|
||||
showLoadingModal(context);
|
||||
var result = await ref
|
||||
.read(imagePickerProvider)
|
||||
.pickImage(source: ImageSource.gallery);
|
||||
if (result == null) {
|
||||
if (context.mounted) hideLoadingModal(context);
|
||||
return;
|
||||
}
|
||||
if (!context.mounted) return;
|
||||
hideLoadingModal(context);
|
||||
result = await cropImage(
|
||||
context,
|
||||
image: result,
|
||||
allowedAspectRatios: [
|
||||
if (position == 'background')
|
||||
const CropAspectRatio(height: 7, width: 16)
|
||||
else
|
||||
const CropAspectRatio(height: 1, width: 1),
|
||||
],
|
||||
);
|
||||
if (result == null) {
|
||||
if (context.mounted) hideLoadingModal(context);
|
||||
return;
|
||||
}
|
||||
if (!context.mounted) return;
|
||||
showLoadingModal(context);
|
||||
|
||||
submitting.value = true;
|
||||
try {
|
||||
final cloudFile =
|
||||
await FileUploader.createCloudFile(
|
||||
client: ref.read(apiClientProvider),
|
||||
fileData: UniversalFile(
|
||||
data: result,
|
||||
type: UniversalFileType.image,
|
||||
),
|
||||
).future;
|
||||
if (cloudFile == null) {
|
||||
throw ArgumentError('Failed to upload the file...');
|
||||
}
|
||||
switch (position) {
|
||||
case 'picture':
|
||||
picture.value = cloudFile;
|
||||
case 'background':
|
||||
background.value = cloudFile;
|
||||
}
|
||||
} catch (err) {
|
||||
showErrorAlert(err);
|
||||
} finally {
|
||||
if (context.mounted) hideLoadingModal(context);
|
||||
submitting.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> performAction() async {
|
||||
if (!formKey.currentState!.validate()) return;
|
||||
|
||||
submitting.value = true;
|
||||
try {
|
||||
final client = ref.watch(apiClientProvider);
|
||||
final resp = await client.request(
|
||||
id == null ? '/sphere/chat' : '/sphere/chat/$id',
|
||||
data: {
|
||||
'name': nameController.text,
|
||||
'description': descriptionController.text,
|
||||
'background_id': background.value?.id,
|
||||
'picture_id': picture.value?.id,
|
||||
'realm_id': currentRealm.value?.id,
|
||||
'is_public': isPublic.value,
|
||||
'is_community': isCommunity.value,
|
||||
},
|
||||
options: Options(method: id == null ? 'POST' : 'PATCH'),
|
||||
);
|
||||
if (context.mounted) {
|
||||
context.pop(SnChatRoom.fromJson(resp.data));
|
||||
}
|
||||
} catch (err) {
|
||||
showErrorAlert(err);
|
||||
} finally {
|
||||
submitting.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
return AppScaffold(
|
||||
appBar: AppBar(
|
||||
title: Text(id == null ? 'createChatRoom' : 'editChatRoom').tr(),
|
||||
leading: const PageBackButton(),
|
||||
),
|
||||
body: SingleChildScrollView(
|
||||
child: Column(
|
||||
children: [
|
||||
RealmSelectionDropdown(
|
||||
value: currentRealm.value,
|
||||
realms: joinedRealms.when(
|
||||
data: (realms) => realms,
|
||||
loading: () => [],
|
||||
error: (_, _) => [],
|
||||
),
|
||||
onChanged: (SnRealm? value) {
|
||||
currentRealm.value = value;
|
||||
},
|
||||
isLoading: joinedRealms.isLoading,
|
||||
error: joinedRealms.error?.toString(),
|
||||
),
|
||||
AspectRatio(
|
||||
aspectRatio: 16 / 7,
|
||||
child: Stack(
|
||||
clipBehavior: Clip.none,
|
||||
fit: StackFit.expand,
|
||||
children: [
|
||||
GestureDetector(
|
||||
child: Container(
|
||||
color: Theme.of(context).colorScheme.surfaceContainerHigh,
|
||||
child:
|
||||
background.value != null
|
||||
? CloudFileWidget(
|
||||
item: background.value!,
|
||||
fit: BoxFit.cover,
|
||||
)
|
||||
: const SizedBox.shrink(),
|
||||
),
|
||||
onTap: () {
|
||||
setPicture('background');
|
||||
},
|
||||
),
|
||||
Positioned(
|
||||
left: 20,
|
||||
bottom: -32,
|
||||
child: GestureDetector(
|
||||
child: ProfilePictureWidget(
|
||||
fileId: picture.value?.id,
|
||||
radius: 40,
|
||||
fallbackIcon: Symbols.group,
|
||||
),
|
||||
onTap: () {
|
||||
setPicture('picture');
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
).padding(bottom: 32),
|
||||
Form(
|
||||
key: formKey,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
TextFormField(
|
||||
controller: nameController,
|
||||
decoration: const InputDecoration(labelText: 'Name'),
|
||||
onTapOutside:
|
||||
(_) => FocusManager.instance.primaryFocus?.unfocus(),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
TextFormField(
|
||||
controller: descriptionController,
|
||||
decoration: const InputDecoration(
|
||||
labelText: 'Description',
|
||||
alignLabelWithHint: true,
|
||||
),
|
||||
minLines: 3,
|
||||
maxLines: null,
|
||||
onTapOutside:
|
||||
(_) => FocusManager.instance.primaryFocus?.unfocus(),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Card(
|
||||
margin: EdgeInsets.zero,
|
||||
child: Column(
|
||||
children: [
|
||||
CheckboxListTile(
|
||||
secondary: const Icon(Symbols.public),
|
||||
title: Text('publicChat').tr(),
|
||||
subtitle: Text('publicChatDescription').tr(),
|
||||
value: isPublic.value,
|
||||
onChanged: (value) {
|
||||
isPublic.value = value ?? true;
|
||||
},
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
),
|
||||
CheckboxListTile(
|
||||
secondary: const Icon(Symbols.travel_explore),
|
||||
title: Text('communityChat').tr(),
|
||||
subtitle: Text('communityChatDescription').tr(),
|
||||
value: isCommunity.value,
|
||||
onChanged: (value) {
|
||||
isCommunity.value = value ?? false;
|
||||
},
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Align(
|
||||
alignment: Alignment.centerRight,
|
||||
child: TextButton.icon(
|
||||
onPressed: submitting.value ? null : performAction,
|
||||
label: const Text('Save'),
|
||||
icon: const Icon(Symbols.save),
|
||||
),
|
||||
),
|
||||
],
|
||||
).padding(all: 24),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@riverpod
|
||||
Future<List<SnChatMember>> chatroomInvites(Ref ref) async {
|
||||
final client = ref.watch(apiClientProvider);
|
||||
|
299
lib/screens/chat/chat_form.dart
Normal file
299
lib/screens/chat/chat_form.dart
Normal file
@@ -0,0 +1,299 @@
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:croppy/croppy.dart' hide cropImage;
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:image_picker/image_picker.dart';
|
||||
import 'package:island/models/chat.dart';
|
||||
import 'package:island/models/file.dart';
|
||||
import 'package:island/models/realm.dart';
|
||||
import 'package:island/pods/network.dart';
|
||||
import 'package:island/screens/chat/chat.dart';
|
||||
import 'package:island/screens/realm/realms.dart';
|
||||
import 'package:island/services/file.dart';
|
||||
import 'package:island/services/file_uploader.dart';
|
||||
import 'package:island/widgets/alert.dart';
|
||||
import 'package:island/widgets/app_scaffold.dart';
|
||||
import 'package:island/widgets/content/cloud_files.dart';
|
||||
import 'package:material_symbols_icons/symbols.dart';
|
||||
import 'package:styled_widget/styled_widget.dart';
|
||||
|
||||
class NewChatScreen extends StatelessWidget {
|
||||
const NewChatScreen({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return const EditChatScreen();
|
||||
}
|
||||
}
|
||||
|
||||
class EditChatScreen extends HookConsumerWidget {
|
||||
final String? id;
|
||||
const EditChatScreen({super.key, this.id});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final formKey = useMemoized(() => GlobalKey<FormState>(), []);
|
||||
|
||||
final submitting = useState(false);
|
||||
|
||||
final nameController = useTextEditingController();
|
||||
final descriptionController = useTextEditingController();
|
||||
final picture = useState<SnCloudFile?>(null);
|
||||
final background = useState<SnCloudFile?>(null);
|
||||
final isPublic = useState(true);
|
||||
final isCommunity = useState(false);
|
||||
|
||||
final chat = ref.watch(chatroomProvider(id));
|
||||
|
||||
final joinedRealms = ref.watch(realmsJoinedProvider);
|
||||
final currentRealm = useState<SnRealm?>(null);
|
||||
|
||||
useEffect(() {
|
||||
if (chat.value != null) {
|
||||
nameController.text = chat.value!.name ?? '';
|
||||
descriptionController.text = chat.value!.description ?? '';
|
||||
picture.value = chat.value!.picture;
|
||||
background.value = chat.value!.background;
|
||||
isPublic.value = chat.value!.isPublic;
|
||||
isCommunity.value = chat.value!.isCommunity;
|
||||
currentRealm.value = joinedRealms.value?.firstWhereOrNull(
|
||||
(realm) => realm.id == chat.value!.realmId,
|
||||
);
|
||||
}
|
||||
return;
|
||||
}, [chat, joinedRealms]);
|
||||
|
||||
void setPicture(String position) async {
|
||||
showLoadingModal(context);
|
||||
var result = await ref
|
||||
.read(imagePickerProvider)
|
||||
.pickImage(source: ImageSource.gallery);
|
||||
if (result == null) {
|
||||
if (context.mounted) hideLoadingModal(context);
|
||||
return;
|
||||
}
|
||||
if (!context.mounted) return;
|
||||
hideLoadingModal(context);
|
||||
result = await cropImage(
|
||||
context,
|
||||
image: result,
|
||||
allowedAspectRatios: [
|
||||
if (position == 'background')
|
||||
const CropAspectRatio(height: 7, width: 16)
|
||||
else
|
||||
const CropAspectRatio(height: 1, width: 1),
|
||||
],
|
||||
);
|
||||
if (result == null) {
|
||||
if (context.mounted) hideLoadingModal(context);
|
||||
return;
|
||||
}
|
||||
if (!context.mounted) return;
|
||||
showLoadingModal(context);
|
||||
|
||||
submitting.value = true;
|
||||
try {
|
||||
final cloudFile =
|
||||
await FileUploader.createCloudFile(
|
||||
client: ref.read(apiClientProvider),
|
||||
fileData: UniversalFile(
|
||||
data: result,
|
||||
type: UniversalFileType.image,
|
||||
),
|
||||
).future;
|
||||
if (cloudFile == null) {
|
||||
throw ArgumentError('Failed to upload the file...');
|
||||
}
|
||||
switch (position) {
|
||||
case 'picture':
|
||||
picture.value = cloudFile;
|
||||
case 'background':
|
||||
background.value = cloudFile;
|
||||
}
|
||||
} catch (err) {
|
||||
showErrorAlert(err);
|
||||
} finally {
|
||||
if (context.mounted) hideLoadingModal(context);
|
||||
submitting.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> performAction() async {
|
||||
if (!formKey.currentState!.validate()) return;
|
||||
|
||||
submitting.value = true;
|
||||
try {
|
||||
final client = ref.watch(apiClientProvider);
|
||||
final resp = await client.request(
|
||||
id == null ? '/sphere/chat' : '/sphere/chat/$id',
|
||||
data: {
|
||||
'name': nameController.text,
|
||||
'description': descriptionController.text,
|
||||
'background_id': background.value?.id,
|
||||
'picture_id': picture.value?.id,
|
||||
'realm_id': currentRealm.value?.id,
|
||||
'is_public': isPublic.value,
|
||||
'is_community': isCommunity.value,
|
||||
},
|
||||
options: Options(method: id == null ? 'POST' : 'PATCH'),
|
||||
);
|
||||
if (context.mounted) {
|
||||
context.pop(SnChatRoom.fromJson(resp.data));
|
||||
}
|
||||
} catch (err) {
|
||||
showErrorAlert(err);
|
||||
} finally {
|
||||
submitting.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
return AppScaffold(
|
||||
appBar: AppBar(
|
||||
title: Text(id == null ? 'createChatRoom' : 'editChatRoom').tr(),
|
||||
leading: const PageBackButton(),
|
||||
),
|
||||
body: SingleChildScrollView(
|
||||
child: Column(
|
||||
children: [
|
||||
AspectRatio(
|
||||
aspectRatio: 16 / 7,
|
||||
child: Stack(
|
||||
clipBehavior: Clip.none,
|
||||
fit: StackFit.expand,
|
||||
children: [
|
||||
GestureDetector(
|
||||
child: Container(
|
||||
color: Theme.of(context).colorScheme.surfaceContainerHigh,
|
||||
child:
|
||||
background.value != null
|
||||
? CloudFileWidget(
|
||||
item: background.value!,
|
||||
fit: BoxFit.cover,
|
||||
)
|
||||
: const SizedBox.shrink(),
|
||||
),
|
||||
onTap: () {
|
||||
setPicture('background');
|
||||
},
|
||||
),
|
||||
Positioned(
|
||||
left: 20,
|
||||
bottom: -32,
|
||||
child: GestureDetector(
|
||||
child: ProfilePictureWidget(
|
||||
fileId: picture.value?.id,
|
||||
radius: 40,
|
||||
fallbackIcon: Symbols.group,
|
||||
),
|
||||
onTap: () {
|
||||
setPicture('picture');
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
).padding(bottom: 32),
|
||||
Form(
|
||||
key: formKey,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
TextFormField(
|
||||
controller: nameController,
|
||||
decoration: const InputDecoration(labelText: 'Name'),
|
||||
onTapOutside:
|
||||
(_) => FocusManager.instance.primaryFocus?.unfocus(),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
TextFormField(
|
||||
controller: descriptionController,
|
||||
decoration: const InputDecoration(
|
||||
labelText: 'Description',
|
||||
alignLabelWithHint: true,
|
||||
),
|
||||
minLines: 3,
|
||||
maxLines: null,
|
||||
onTapOutside:
|
||||
(_) => FocusManager.instance.primaryFocus?.unfocus(),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
DropdownButtonFormField<SnRealm>(
|
||||
value: currentRealm.value,
|
||||
decoration: InputDecoration(labelText: 'realm'.tr()),
|
||||
items: [
|
||||
DropdownMenuItem<SnRealm>(
|
||||
value: null,
|
||||
child: Text('none'.tr()),
|
||||
),
|
||||
...joinedRealms.maybeWhen(
|
||||
data:
|
||||
(realms) => realms.map(
|
||||
(realm) => DropdownMenuItem(
|
||||
value: realm,
|
||||
child: Text(realm.name),
|
||||
),
|
||||
),
|
||||
orElse: () => [],
|
||||
),
|
||||
],
|
||||
onChanged:
|
||||
joinedRealms.isLoading
|
||||
? null
|
||||
: (SnRealm? value) {
|
||||
currentRealm.value = value;
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Card(
|
||||
margin: EdgeInsets.zero,
|
||||
child: Column(
|
||||
children: [
|
||||
CheckboxListTile(
|
||||
secondary: const Icon(Symbols.public),
|
||||
title: Text('publicChat').tr(),
|
||||
subtitle: Text('publicChatDescription').tr(),
|
||||
value: isPublic.value,
|
||||
onChanged: (value) {
|
||||
isPublic.value = value ?? true;
|
||||
},
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
),
|
||||
CheckboxListTile(
|
||||
secondary: const Icon(Symbols.travel_explore),
|
||||
title: Text('communityChat').tr(),
|
||||
subtitle: Text('communityChatDescription').tr(),
|
||||
value: isCommunity.value,
|
||||
onChanged: (value) {
|
||||
isCommunity.value = value ?? false;
|
||||
},
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Align(
|
||||
alignment: Alignment.centerRight,
|
||||
child: TextButton.icon(
|
||||
onPressed: submitting.value ? null : performAction,
|
||||
label: const Text('Save'),
|
||||
icon: const Icon(Symbols.save),
|
||||
),
|
||||
),
|
||||
],
|
||||
).padding(all: 24),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@@ -9,7 +9,7 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:island/models/post.dart';
|
||||
import 'package:island/models/publisher.dart';
|
||||
import 'package:island/pods/network.dart';
|
||||
import 'package:island/screens/creators/publishers.dart';
|
||||
import 'package:island/screens/creators/publishers_form.dart';
|
||||
import 'package:island/services/responsive.dart';
|
||||
import 'package:island/utils/text.dart';
|
||||
import 'package:island/widgets/account/account_picker.dart';
|
||||
|
@@ -18,12 +18,11 @@ import 'package:island/services/file_uploader.dart';
|
||||
import 'package:island/widgets/alert.dart';
|
||||
import 'package:island/widgets/app_scaffold.dart';
|
||||
import 'package:island/widgets/content/cloud_files.dart';
|
||||
import 'package:island/widgets/realm/realm_selection_dropdown.dart';
|
||||
import 'package:material_symbols_icons/symbols.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
import 'package:styled_widget/styled_widget.dart';
|
||||
|
||||
part 'publishers.g.dart';
|
||||
part 'publishers_form.g.dart';
|
||||
|
||||
@riverpod
|
||||
Future<List<SnPublisher>> publishersManaged(Ref ref) async {
|
||||
@@ -187,19 +186,6 @@ class EditPublisherScreen extends HookConsumerWidget {
|
||||
padding: EdgeInsets.only(bottom: 16),
|
||||
child: Column(
|
||||
children: [
|
||||
RealmSelectionDropdown(
|
||||
value: currentRealm.value,
|
||||
realms: joinedRealms.when(
|
||||
data: (realms) => realms,
|
||||
loading: () => [],
|
||||
error: (_, _) => [],
|
||||
),
|
||||
onChanged: (SnRealm? value) {
|
||||
currentRealm.value = value;
|
||||
},
|
||||
isLoading: joinedRealms.isLoading,
|
||||
error: joinedRealms.error?.toString(),
|
||||
),
|
||||
AspectRatio(
|
||||
aspectRatio: 16 / 7,
|
||||
child: Stack(
|
||||
@@ -273,6 +259,32 @@ class EditPublisherScreen extends HookConsumerWidget {
|
||||
onTapOutside:
|
||||
(_) => FocusManager.instance.primaryFocus?.unfocus(),
|
||||
),
|
||||
DropdownButtonFormField<SnRealm>(
|
||||
value: currentRealm.value,
|
||||
decoration: InputDecoration(labelText: 'realm'.tr()),
|
||||
items: [
|
||||
DropdownMenuItem<SnRealm>(
|
||||
value: null,
|
||||
child: Text('individual'.tr()),
|
||||
),
|
||||
...joinedRealms.maybeWhen(
|
||||
data:
|
||||
(realms) => realms.map(
|
||||
(realm) => DropdownMenuItem(
|
||||
value: realm,
|
||||
child: Text(realm.name),
|
||||
),
|
||||
),
|
||||
orElse: () => [],
|
||||
),
|
||||
],
|
||||
onChanged:
|
||||
joinedRealms.isLoading
|
||||
? null
|
||||
: (SnRealm? value) {
|
||||
currentRealm.value = value;
|
||||
},
|
||||
),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
@@ -1,6 +1,6 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'publishers.dart';
|
||||
part of 'publishers_form.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// RiverpodGenerator
|
@@ -8,7 +8,7 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:island/models/developer.dart';
|
||||
import 'package:island/models/publisher.dart';
|
||||
import 'package:island/pods/network.dart';
|
||||
import 'package:island/screens/creators/publishers.dart';
|
||||
import 'package:island/screens/creators/publishers_form.dart';
|
||||
import 'package:island/services/responsive.dart';
|
||||
import 'package:island/widgets/alert.dart';
|
||||
import 'package:island/widgets/app_scaffold.dart';
|
||||
|
@@ -6,7 +6,7 @@ import 'package:gap/gap.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:island/models/file.dart';
|
||||
import 'package:island/models/post.dart';
|
||||
import 'package:island/screens/creators/publishers.dart';
|
||||
import 'package:island/screens/creators/publishers_form.dart';
|
||||
import 'package:island/screens/posts/compose_article.dart';
|
||||
import 'package:island/services/responsive.dart';
|
||||
import 'package:island/widgets/app_scaffold.dart';
|
||||
|
@@ -8,7 +8,7 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:island/models/file.dart';
|
||||
import 'package:island/models/post.dart';
|
||||
|
||||
import 'package:island/screens/creators/publishers.dart';
|
||||
import 'package:island/screens/creators/publishers_form.dart';
|
||||
import 'package:island/services/responsive.dart';
|
||||
import 'package:island/widgets/app_scaffold.dart';
|
||||
import 'package:island/widgets/attachment_uploader.dart';
|
||||
|
276
lib/screens/realm/realm_form.dart
Normal file
276
lib/screens/realm/realm_form.dart
Normal file
@@ -0,0 +1,276 @@
|
||||
import 'package:croppy/croppy.dart' show CropAspectRatio;
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:image_picker/image_picker.dart';
|
||||
import 'package:island/models/file.dart';
|
||||
import 'package:island/models/realm.dart';
|
||||
import 'package:island/pods/network.dart';
|
||||
import 'package:island/screens/realm/realms.dart';
|
||||
import 'package:island/services/file.dart';
|
||||
import 'package:island/services/file_uploader.dart';
|
||||
import 'package:island/widgets/alert.dart';
|
||||
import 'package:island/widgets/app_scaffold.dart';
|
||||
import 'package:island/widgets/content/cloud_files.dart';
|
||||
import 'package:material_symbols_icons/symbols.dart';
|
||||
import 'package:styled_widget/styled_widget.dart';
|
||||
|
||||
class NewRealmScreen extends StatelessWidget {
|
||||
const NewRealmScreen({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return const EditRealmScreen();
|
||||
}
|
||||
}
|
||||
|
||||
class EditRealmScreen extends HookConsumerWidget {
|
||||
final String? slug;
|
||||
const EditRealmScreen({super.key, this.slug});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final submitting = useState(false);
|
||||
|
||||
final picture = useState<SnCloudFile?>(null);
|
||||
final background = useState<SnCloudFile?>(null);
|
||||
final isPublic = useState(true);
|
||||
final isCommunity = useState(false);
|
||||
|
||||
final slugController = useTextEditingController();
|
||||
final nameController = useTextEditingController();
|
||||
final descriptionController = useTextEditingController();
|
||||
|
||||
final formKey = useMemoized(GlobalKey<FormState>.new, const []);
|
||||
|
||||
final realm = ref.watch(realmProvider(slug));
|
||||
|
||||
useEffect(() {
|
||||
if (realm.value != null) {
|
||||
picture.value = realm.value!.picture;
|
||||
background.value = realm.value!.background;
|
||||
slugController.text = realm.value!.slug;
|
||||
nameController.text = realm.value!.name;
|
||||
descriptionController.text = realm.value!.description;
|
||||
isPublic.value = realm.value!.isPublic;
|
||||
isCommunity.value = realm.value!.isCommunity;
|
||||
}
|
||||
return null;
|
||||
}, [realm]);
|
||||
|
||||
void setPicture(String position) async {
|
||||
showLoadingModal(context);
|
||||
var result = await ref
|
||||
.read(imagePickerProvider)
|
||||
.pickImage(source: ImageSource.gallery);
|
||||
if (result == null) {
|
||||
if (context.mounted) hideLoadingModal(context);
|
||||
return;
|
||||
}
|
||||
if (!context.mounted) return;
|
||||
hideLoadingModal(context);
|
||||
result = await cropImage(
|
||||
context,
|
||||
image: result,
|
||||
allowedAspectRatios: [
|
||||
if (position == 'background')
|
||||
const CropAspectRatio(height: 7, width: 16)
|
||||
else
|
||||
const CropAspectRatio(height: 1, width: 1),
|
||||
],
|
||||
);
|
||||
if (result == null) {
|
||||
if (context.mounted) hideLoadingModal(context);
|
||||
return;
|
||||
}
|
||||
if (!context.mounted) return;
|
||||
showLoadingModal(context);
|
||||
submitting.value = true;
|
||||
try {
|
||||
final cloudFile =
|
||||
await FileUploader.createCloudFile(
|
||||
client: ref.read(apiClientProvider),
|
||||
fileData: UniversalFile(
|
||||
data: result,
|
||||
type: UniversalFileType.image,
|
||||
),
|
||||
).future;
|
||||
if (cloudFile == null) {
|
||||
throw ArgumentError('Failed to upload the file...');
|
||||
}
|
||||
switch (position) {
|
||||
case 'picture':
|
||||
picture.value = cloudFile;
|
||||
case 'background':
|
||||
background.value = cloudFile;
|
||||
}
|
||||
} catch (err) {
|
||||
showErrorAlert(err);
|
||||
} finally {
|
||||
if (context.mounted) hideLoadingModal(context);
|
||||
submitting.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> performAction() async {
|
||||
if (!formKey.currentState!.validate()) return;
|
||||
|
||||
submitting.value = true;
|
||||
try {
|
||||
final client = ref.watch(apiClientProvider);
|
||||
final resp = await client.request(
|
||||
'/sphere${slug == null ? '/realms' : '/realms/$slug'}',
|
||||
data: {
|
||||
'slug': slugController.text,
|
||||
'name': nameController.text,
|
||||
'description': descriptionController.text,
|
||||
'background_id': background.value?.id,
|
||||
'picture_id': picture.value?.id,
|
||||
'is_public': isPublic.value,
|
||||
'is_community': isCommunity.value,
|
||||
},
|
||||
options: Options(method: slug == null ? 'POST' : 'PATCH'),
|
||||
);
|
||||
if (context.mounted) {
|
||||
context.pop(SnRealm.fromJson(resp.data));
|
||||
}
|
||||
} catch (err) {
|
||||
showErrorAlert(err);
|
||||
} finally {
|
||||
submitting.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
return AppScaffold(
|
||||
isNoBackground: false,
|
||||
appBar: AppBar(
|
||||
title: Text(slug == null ? 'createRealm'.tr() : 'editRealm'.tr()),
|
||||
leading: const PageBackButton(),
|
||||
),
|
||||
body: SingleChildScrollView(
|
||||
child: Column(
|
||||
children: [
|
||||
AspectRatio(
|
||||
aspectRatio: 16 / 7,
|
||||
child: Stack(
|
||||
clipBehavior: Clip.none,
|
||||
fit: StackFit.expand,
|
||||
children: [
|
||||
GestureDetector(
|
||||
child: Container(
|
||||
color: Theme.of(context).colorScheme.surfaceContainerHigh,
|
||||
child:
|
||||
background.value != null
|
||||
? CloudFileWidget(
|
||||
item: background.value!,
|
||||
fit: BoxFit.cover,
|
||||
)
|
||||
: const SizedBox.shrink(),
|
||||
),
|
||||
onTap: () {
|
||||
setPicture('background');
|
||||
},
|
||||
),
|
||||
Positioned(
|
||||
left: 20,
|
||||
bottom: -32,
|
||||
child: GestureDetector(
|
||||
child: ProfilePictureWidget(
|
||||
fileId: picture.value?.id,
|
||||
radius: 40,
|
||||
fallbackIcon: Symbols.group,
|
||||
),
|
||||
onTap: () {
|
||||
setPicture('picture');
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
).padding(bottom: 32),
|
||||
Form(
|
||||
key: formKey,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
TextFormField(
|
||||
controller: slugController,
|
||||
decoration: InputDecoration(
|
||||
labelText: 'slug'.tr(),
|
||||
helperText: 'slugHint'.tr(),
|
||||
),
|
||||
onTapOutside:
|
||||
(_) => FocusManager.instance.primaryFocus?.unfocus(),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
TextFormField(
|
||||
controller: nameController,
|
||||
decoration: InputDecoration(labelText: 'name'.tr()),
|
||||
onTapOutside:
|
||||
(_) => FocusManager.instance.primaryFocus?.unfocus(),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
TextFormField(
|
||||
controller: descriptionController,
|
||||
decoration: InputDecoration(
|
||||
labelText: 'description'.tr(),
|
||||
alignLabelWithHint: true,
|
||||
),
|
||||
minLines: 3,
|
||||
maxLines: null,
|
||||
onTapOutside:
|
||||
(_) => FocusManager.instance.primaryFocus?.unfocus(),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Card(
|
||||
margin: EdgeInsets.zero,
|
||||
child: Column(
|
||||
children: [
|
||||
CheckboxListTile(
|
||||
secondary: const Icon(Symbols.public),
|
||||
title: Text('publicRealm').tr(),
|
||||
subtitle: Text('publicRealmDescription').tr(),
|
||||
value: isPublic.value,
|
||||
onChanged: (value) {
|
||||
isPublic.value = value ?? true;
|
||||
},
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
),
|
||||
CheckboxListTile(
|
||||
secondary: const Icon(Symbols.travel_explore),
|
||||
title: Text('communityRealm').tr(),
|
||||
subtitle: Text('communityRealmDescription').tr(),
|
||||
value: isCommunity.value,
|
||||
onChanged: (value) {
|
||||
isCommunity.value = value ?? false;
|
||||
},
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Align(
|
||||
alignment: Alignment.centerRight,
|
||||
child: TextButton.icon(
|
||||
onPressed: submitting.value ? null : performAction,
|
||||
label: Text('saveChanges'.tr()),
|
||||
icon: const Icon(Symbols.save),
|
||||
),
|
||||
),
|
||||
],
|
||||
).padding(all: 24),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@@ -1,17 +1,10 @@
|
||||
import 'package:croppy/croppy.dart' show CropAspectRatio;
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:gap/gap.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:image_picker/image_picker.dart';
|
||||
import 'package:island/models/file.dart';
|
||||
import 'package:island/models/realm.dart';
|
||||
import 'package:island/pods/network.dart';
|
||||
import 'package:island/services/file.dart';
|
||||
import 'package:island/services/file_uploader.dart';
|
||||
import 'package:island/widgets/alert.dart';
|
||||
import 'package:island/widgets/app_scaffold.dart';
|
||||
import 'package:island/widgets/content/cloud_files.dart';
|
||||
@@ -32,6 +25,14 @@ Future<List<SnRealm>> realmsJoined(Ref ref) async {
|
||||
return resp.data.map((e) => SnRealm.fromJson(e)).cast<SnRealm>().toList();
|
||||
}
|
||||
|
||||
@riverpod
|
||||
Future<SnRealm?> realm(Ref ref, String? identifier) async {
|
||||
if (identifier == null) return null;
|
||||
final client = ref.watch(apiClientProvider);
|
||||
final resp = await client.get('/sphere/realms/$identifier');
|
||||
return SnRealm.fromJson(resp.data);
|
||||
}
|
||||
|
||||
class RealmListScreen extends HookConsumerWidget {
|
||||
const RealmListScreen({super.key});
|
||||
|
||||
@@ -124,271 +125,6 @@ class RealmListScreen extends HookConsumerWidget {
|
||||
}
|
||||
}
|
||||
|
||||
@riverpod
|
||||
Future<SnRealm?> realm(Ref ref, String? identifier) async {
|
||||
if (identifier == null) return null;
|
||||
final client = ref.watch(apiClientProvider);
|
||||
final resp = await client.get('/sphere/realms/$identifier');
|
||||
return SnRealm.fromJson(resp.data);
|
||||
}
|
||||
|
||||
class NewRealmScreen extends StatelessWidget {
|
||||
const NewRealmScreen({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return const EditRealmScreen();
|
||||
}
|
||||
}
|
||||
|
||||
class EditRealmScreen extends HookConsumerWidget {
|
||||
final String? slug;
|
||||
const EditRealmScreen({super.key, this.slug});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final submitting = useState(false);
|
||||
|
||||
final picture = useState<SnCloudFile?>(null);
|
||||
final background = useState<SnCloudFile?>(null);
|
||||
final isPublic = useState(true);
|
||||
final isCommunity = useState(false);
|
||||
|
||||
final slugController = useTextEditingController();
|
||||
final nameController = useTextEditingController();
|
||||
final descriptionController = useTextEditingController();
|
||||
|
||||
final formKey = useMemoized(GlobalKey<FormState>.new, const []);
|
||||
|
||||
final realm = ref.watch(realmProvider(slug));
|
||||
|
||||
useEffect(() {
|
||||
if (realm.value != null) {
|
||||
picture.value = realm.value!.picture;
|
||||
background.value = realm.value!.background;
|
||||
slugController.text = realm.value!.slug;
|
||||
nameController.text = realm.value!.name;
|
||||
descriptionController.text = realm.value!.description;
|
||||
isPublic.value = realm.value!.isPublic;
|
||||
isCommunity.value = realm.value!.isCommunity;
|
||||
}
|
||||
return null;
|
||||
}, [realm]);
|
||||
|
||||
void setPicture(String position) async {
|
||||
showLoadingModal(context);
|
||||
var result = await ref
|
||||
.read(imagePickerProvider)
|
||||
.pickImage(source: ImageSource.gallery);
|
||||
if (result == null) {
|
||||
if (context.mounted) hideLoadingModal(context);
|
||||
return;
|
||||
}
|
||||
if (!context.mounted) return;
|
||||
hideLoadingModal(context);
|
||||
result = await cropImage(
|
||||
context,
|
||||
image: result,
|
||||
allowedAspectRatios: [
|
||||
if (position == 'background')
|
||||
const CropAspectRatio(height: 7, width: 16)
|
||||
else
|
||||
const CropAspectRatio(height: 1, width: 1),
|
||||
],
|
||||
);
|
||||
if (result == null) {
|
||||
if (context.mounted) hideLoadingModal(context);
|
||||
return;
|
||||
}
|
||||
if (!context.mounted) return;
|
||||
showLoadingModal(context);
|
||||
submitting.value = true;
|
||||
try {
|
||||
final cloudFile =
|
||||
await FileUploader.createCloudFile(
|
||||
client: ref.read(apiClientProvider),
|
||||
fileData: UniversalFile(
|
||||
data: result,
|
||||
type: UniversalFileType.image,
|
||||
),
|
||||
).future;
|
||||
if (cloudFile == null) {
|
||||
throw ArgumentError('Failed to upload the file...');
|
||||
}
|
||||
switch (position) {
|
||||
case 'picture':
|
||||
picture.value = cloudFile;
|
||||
case 'background':
|
||||
background.value = cloudFile;
|
||||
}
|
||||
} catch (err) {
|
||||
showErrorAlert(err);
|
||||
} finally {
|
||||
if (context.mounted) hideLoadingModal(context);
|
||||
submitting.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> performAction() async {
|
||||
if (!formKey.currentState!.validate()) return;
|
||||
|
||||
submitting.value = true;
|
||||
try {
|
||||
final client = ref.watch(apiClientProvider);
|
||||
final resp = await client.request(
|
||||
'/sphere${slug == null ? '/realms' : '/realms/$slug'}',
|
||||
data: {
|
||||
'slug': slugController.text,
|
||||
'name': nameController.text,
|
||||
'description': descriptionController.text,
|
||||
'background_id': background.value?.id,
|
||||
'picture_id': picture.value?.id,
|
||||
'is_public': isPublic.value,
|
||||
'is_community': isCommunity.value,
|
||||
},
|
||||
options: Options(method: slug == null ? 'POST' : 'PATCH'),
|
||||
);
|
||||
if (context.mounted) {
|
||||
context.pop(SnRealm.fromJson(resp.data));
|
||||
}
|
||||
} catch (err) {
|
||||
showErrorAlert(err);
|
||||
} finally {
|
||||
submitting.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
return AppScaffold(
|
||||
isNoBackground: false,
|
||||
appBar: AppBar(
|
||||
title: Text(slug == null ? 'createRealm'.tr() : 'editRealm'.tr()),
|
||||
leading: const PageBackButton(),
|
||||
),
|
||||
body: SingleChildScrollView(
|
||||
child: Column(
|
||||
children: [
|
||||
AspectRatio(
|
||||
aspectRatio: 16 / 7,
|
||||
child: Stack(
|
||||
clipBehavior: Clip.none,
|
||||
fit: StackFit.expand,
|
||||
children: [
|
||||
GestureDetector(
|
||||
child: Container(
|
||||
color: Theme.of(context).colorScheme.surfaceContainerHigh,
|
||||
child:
|
||||
background.value != null
|
||||
? CloudFileWidget(
|
||||
item: background.value!,
|
||||
fit: BoxFit.cover,
|
||||
)
|
||||
: const SizedBox.shrink(),
|
||||
),
|
||||
onTap: () {
|
||||
setPicture('background');
|
||||
},
|
||||
),
|
||||
Positioned(
|
||||
left: 20,
|
||||
bottom: -32,
|
||||
child: GestureDetector(
|
||||
child: ProfilePictureWidget(
|
||||
fileId: picture.value?.id,
|
||||
radius: 40,
|
||||
fallbackIcon: Symbols.group,
|
||||
),
|
||||
onTap: () {
|
||||
setPicture('picture');
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
).padding(bottom: 32),
|
||||
Form(
|
||||
key: formKey,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
TextFormField(
|
||||
controller: slugController,
|
||||
decoration: InputDecoration(
|
||||
labelText: 'slug'.tr(),
|
||||
helperText: 'slugHint'.tr(),
|
||||
),
|
||||
onTapOutside:
|
||||
(_) => FocusManager.instance.primaryFocus?.unfocus(),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
TextFormField(
|
||||
controller: nameController,
|
||||
decoration: InputDecoration(labelText: 'name'.tr()),
|
||||
onTapOutside:
|
||||
(_) => FocusManager.instance.primaryFocus?.unfocus(),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
TextFormField(
|
||||
controller: descriptionController,
|
||||
decoration: InputDecoration(
|
||||
labelText: 'description'.tr(),
|
||||
alignLabelWithHint: true,
|
||||
),
|
||||
minLines: 3,
|
||||
maxLines: null,
|
||||
onTapOutside:
|
||||
(_) => FocusManager.instance.primaryFocus?.unfocus(),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Card(
|
||||
margin: EdgeInsets.zero,
|
||||
child: Column(
|
||||
children: [
|
||||
CheckboxListTile(
|
||||
secondary: const Icon(Symbols.public),
|
||||
title: Text('publicRealm').tr(),
|
||||
subtitle: Text('publicRealmDescription').tr(),
|
||||
value: isPublic.value,
|
||||
onChanged: (value) {
|
||||
isPublic.value = value ?? true;
|
||||
},
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
),
|
||||
CheckboxListTile(
|
||||
secondary: const Icon(Symbols.travel_explore),
|
||||
title: Text('communityRealm').tr(),
|
||||
subtitle: Text('communityRealmDescription').tr(),
|
||||
value: isCommunity.value,
|
||||
onChanged: (value) {
|
||||
isCommunity.value = value ?? false;
|
||||
},
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Align(
|
||||
alignment: Alignment.centerRight,
|
||||
child: TextButton.icon(
|
||||
onPressed: submitting.value ? null : performAction,
|
||||
label: Text('saveChanges'.tr()),
|
||||
icon: const Icon(Symbols.save),
|
||||
),
|
||||
),
|
||||
],
|
||||
).padding(all: 24),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@riverpod
|
||||
Future<List<SnRealmMember>> realmInvites(Ref ref) async {
|
||||
final client = ref.watch(apiClientProvider);
|
||||
|
@@ -5,12 +5,15 @@ import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:gap/gap.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:island/models/account.dart';
|
||||
import 'package:island/models/wallet.dart';
|
||||
import 'package:island/pods/network.dart';
|
||||
import 'package:island/pods/userinfo.dart';
|
||||
import 'package:island/services/time.dart';
|
||||
import 'package:island/widgets/account/account_picker.dart';
|
||||
import 'package:island/widgets/account/restore_purchase_sheet.dart';
|
||||
import 'package:island/widgets/alert.dart';
|
||||
import 'package:island/widgets/content/sheet.dart';
|
||||
import 'package:island/widgets/payment/payment_overlay.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:material_symbols_icons/symbols.dart';
|
||||
@@ -31,6 +34,39 @@ Future<SnWalletSubscription?> accountStellarSubscription(Ref ref) async {
|
||||
}
|
||||
}
|
||||
|
||||
@riverpod
|
||||
Future<List<SnWalletGift>> accountSentGifts(
|
||||
Ref ref, {
|
||||
int offset = 0,
|
||||
int take = 20,
|
||||
}) async {
|
||||
final client = ref.watch(apiClientProvider);
|
||||
final resp = await client.get(
|
||||
'/id/subscriptions/gifts/sent?offset=$offset&take=$take',
|
||||
);
|
||||
return (resp.data as List).map((e) => SnWalletGift.fromJson(e)).toList();
|
||||
}
|
||||
|
||||
@riverpod
|
||||
Future<List<SnWalletGift>> accountReceivedGifts(
|
||||
Ref ref, {
|
||||
int offset = 0,
|
||||
int take = 20,
|
||||
}) async {
|
||||
final client = ref.watch(apiClientProvider);
|
||||
final resp = await client.get(
|
||||
'/id/subscriptions/gifts/received?offset=$offset&take=$take',
|
||||
);
|
||||
return (resp.data as List).map((e) => SnWalletGift.fromJson(e)).toList();
|
||||
}
|
||||
|
||||
@riverpod
|
||||
Future<SnWalletGift> accountGift(Ref ref, String giftId) async {
|
||||
final client = ref.watch(apiClientProvider);
|
||||
final resp = await client.get('/id/subscriptions/gifts/$giftId');
|
||||
return SnWalletGift.fromJson(resp.data);
|
||||
}
|
||||
|
||||
class StellarProgramTab extends HookConsumerWidget {
|
||||
const StellarProgramTab({super.key});
|
||||
|
||||
@@ -45,6 +81,8 @@ class StellarProgramTab extends HookConsumerWidget {
|
||||
children: [
|
||||
_buildMembershipSection(context, ref, stellarSubscription),
|
||||
const Gap(16),
|
||||
_buildGiftingSection(context, ref),
|
||||
const Gap(16),
|
||||
],
|
||||
),
|
||||
);
|
||||
@@ -466,4 +504,728 @@ class StellarProgramTab extends HookConsumerWidget {
|
||||
if (context.mounted) hideLoadingModal(context);
|
||||
}
|
||||
}
|
||||
|
||||
Widget _buildGiftingSection(BuildContext context, WidgetRef ref) {
|
||||
final sentGifts = ref.watch(accountSentGiftsProvider());
|
||||
final receivedGifts = ref.watch(accountReceivedGiftsProvider());
|
||||
|
||||
return Card(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Icon(
|
||||
Icons.card_giftcard,
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
size: 24,
|
||||
),
|
||||
const Gap(8),
|
||||
Text(
|
||||
'Gift Subscriptions',
|
||||
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
|
||||
),
|
||||
],
|
||||
),
|
||||
const Gap(12),
|
||||
|
||||
// Purchase Gift Section
|
||||
Text(
|
||||
'Purchase a Gift',
|
||||
style: TextStyle(fontSize: 16, fontWeight: FontWeight.w600),
|
||||
),
|
||||
const Gap(8),
|
||||
_buildGiftPurchaseOptions(context, ref),
|
||||
const Gap(16),
|
||||
|
||||
// Redeem Gift Section
|
||||
Text(
|
||||
'Redeem a Gift',
|
||||
style: TextStyle(fontSize: 16, fontWeight: FontWeight.w600),
|
||||
),
|
||||
const Gap(8),
|
||||
_buildGiftRedeemSection(context, ref),
|
||||
const Gap(16),
|
||||
|
||||
// Gift History
|
||||
Text(
|
||||
'Gift History',
|
||||
style: TextStyle(fontSize: 16, fontWeight: FontWeight.w600),
|
||||
),
|
||||
const Gap(8),
|
||||
_buildGiftHistory(context, ref, sentGifts, receivedGifts),
|
||||
],
|
||||
).padding(all: 16),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildGiftPurchaseOptions(BuildContext context, WidgetRef ref) {
|
||||
final tiers = [
|
||||
{
|
||||
'id': 'solian.stellar.primary',
|
||||
'name': 'Stellar Gift',
|
||||
'price': 'Same as membership',
|
||||
'color': Colors.blue,
|
||||
},
|
||||
{
|
||||
'id': 'solian.stellar.nova',
|
||||
'name': 'Nova Gift',
|
||||
'price': 'Same as membership',
|
||||
'color': Color.fromRGBO(57, 197, 187, 1),
|
||||
},
|
||||
{
|
||||
'id': 'solian.stellar.supernova',
|
||||
'name': 'Supernova Gift',
|
||||
'price': 'Same as membership',
|
||||
'color': Colors.orange,
|
||||
},
|
||||
];
|
||||
|
||||
return Column(
|
||||
children:
|
||||
tiers.map((tier) {
|
||||
final tierColor = tier['color'] as Color;
|
||||
|
||||
return Container(
|
||||
margin: const EdgeInsets.only(bottom: 8),
|
||||
child: Material(
|
||||
color: Colors.transparent,
|
||||
child: InkWell(
|
||||
onTap:
|
||||
() => _showPurchaseGiftDialog(
|
||||
context,
|
||||
ref,
|
||||
tier['id'] as String,
|
||||
),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(12),
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.surface,
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
border: Border.all(
|
||||
color: Theme.of(
|
||||
context,
|
||||
).colorScheme.outline.withOpacity(0.2),
|
||||
width: 1,
|
||||
),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Container(
|
||||
width: 4,
|
||||
height: 40,
|
||||
decoration: BoxDecoration(
|
||||
color: tierColor,
|
||||
borderRadius: BorderRadius.circular(2),
|
||||
),
|
||||
),
|
||||
const Gap(12),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
tier['name'] as String,
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
tier['price'] as String,
|
||||
style: Theme.of(
|
||||
context,
|
||||
).textTheme.bodyMedium?.copyWith(
|
||||
color:
|
||||
Theme.of(
|
||||
context,
|
||||
).colorScheme.onSurfaceVariant,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Icon(
|
||||
Icons.arrow_forward_ios,
|
||||
size: 16,
|
||||
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}).toList(),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildGiftRedeemSection(BuildContext context, WidgetRef ref) {
|
||||
return Container(
|
||||
padding: const EdgeInsets.all(12),
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.surface,
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
border: Border.all(
|
||||
color: Theme.of(context).colorScheme.outline.withOpacity(0.2),
|
||||
),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'Enter gift code to redeem',
|
||||
style: Theme.of(context).textTheme.bodyMedium,
|
||||
),
|
||||
const Gap(8),
|
||||
TextField(
|
||||
decoration: InputDecoration(
|
||||
hintText: 'Enter gift code',
|
||||
border: OutlineInputBorder(),
|
||||
enabledBorder: OutlineInputBorder(
|
||||
borderSide: BorderSide(
|
||||
color: Theme.of(context).colorScheme.outline.withOpacity(0.2),
|
||||
),
|
||||
),
|
||||
focusedBorder: OutlineInputBorder(
|
||||
borderSide: BorderSide(
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
),
|
||||
suffixIcon: IconButton(
|
||||
icon: Icon(Icons.redeem),
|
||||
onPressed: () => _showRedeemGiftDialog(context, ref),
|
||||
),
|
||||
),
|
||||
onSubmitted: (code) => _redeemGift(context, ref, code),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildGiftHistory(
|
||||
BuildContext context,
|
||||
WidgetRef ref,
|
||||
AsyncValue<List<SnWalletGift>> sentGifts,
|
||||
AsyncValue<List<SnWalletGift>> receivedGifts,
|
||||
) {
|
||||
return Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: OutlinedButton(
|
||||
onPressed:
|
||||
() => _showGiftHistorySheet(context, ref, sentGifts, true),
|
||||
child: Text('Sent Gifts'),
|
||||
),
|
||||
),
|
||||
const Gap(8),
|
||||
Expanded(
|
||||
child: OutlinedButton(
|
||||
onPressed:
|
||||
() => _showGiftHistorySheet(context, ref, receivedGifts, false),
|
||||
child: Text('Received Gifts'),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _showGiftHistorySheet(
|
||||
BuildContext context,
|
||||
WidgetRef ref,
|
||||
AsyncValue<List<SnWalletGift>> giftsAsync,
|
||||
bool isSent,
|
||||
) async {
|
||||
await showModalBottomSheet(
|
||||
isScrollControlled: true,
|
||||
useRootNavigator: true,
|
||||
context: context,
|
||||
builder:
|
||||
(context) => SheetScaffold(
|
||||
titleText: isSent ? 'Sent Gifts' : 'Received Gifts',
|
||||
child: giftsAsync.when(
|
||||
data:
|
||||
(gifts) =>
|
||||
gifts.isEmpty
|
||||
? Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Text(
|
||||
isSent ? 'No sent gifts' : 'No received gifts',
|
||||
),
|
||||
)
|
||||
: ListView.builder(
|
||||
itemCount: gifts.length,
|
||||
itemBuilder:
|
||||
(context, index) => _buildGiftItem(
|
||||
context,
|
||||
ref,
|
||||
gifts[index],
|
||||
isSent,
|
||||
),
|
||||
),
|
||||
loading: () => const Center(child: CircularProgressIndicator()),
|
||||
error: (error, stack) => Center(child: Text('Error: $error')),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildGiftItem(
|
||||
BuildContext context,
|
||||
WidgetRef ref,
|
||||
SnWalletGift gift,
|
||||
bool isSent,
|
||||
) {
|
||||
final statusText = _getGiftStatusText(gift.status);
|
||||
final statusColor = _getGiftStatusColor(context, gift.status);
|
||||
final canCancel = isSent && (gift.status == 0 || gift.status == 1);
|
||||
|
||||
return Container(
|
||||
margin: const EdgeInsets.symmetric(vertical: 4, horizontal: 16),
|
||||
padding: const EdgeInsets.all(12),
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.surface,
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
border: Border.all(
|
||||
color: Theme.of(context).colorScheme.outline.withOpacity(0.2),
|
||||
),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Text(
|
||||
'Code: ${gift.giftCode}',
|
||||
style: TextStyle(fontWeight: FontWeight.w600),
|
||||
),
|
||||
const Spacer(),
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
||||
decoration: BoxDecoration(
|
||||
color: statusColor.withOpacity(0.1),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: Text(
|
||||
statusText,
|
||||
style: TextStyle(
|
||||
color: statusColor,
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const Gap(4),
|
||||
Text(
|
||||
'Subscription: ${gift.subscriptionIdentifier}',
|
||||
style: Theme.of(context).textTheme.bodySmall,
|
||||
),
|
||||
if (gift.recipient != null && isSent) ...[
|
||||
const Gap(4),
|
||||
Text(
|
||||
'To: ${gift.recipient!.name}',
|
||||
style: Theme.of(context).textTheme.bodySmall,
|
||||
),
|
||||
],
|
||||
if (gift.gifter != null && !isSent) ...[
|
||||
const Gap(4),
|
||||
Text(
|
||||
'From: ${gift.gifter!.name}',
|
||||
style: Theme.of(context).textTheme.bodySmall,
|
||||
),
|
||||
],
|
||||
if (gift.message != null && gift.message!.isNotEmpty) ...[
|
||||
const Gap(4),
|
||||
Text(
|
||||
'Message: ${gift.message}',
|
||||
style: Theme.of(context).textTheme.bodySmall,
|
||||
),
|
||||
],
|
||||
if (canCancel) ...[
|
||||
const Gap(8),
|
||||
Align(
|
||||
alignment: Alignment.centerRight,
|
||||
child: OutlinedButton.icon(
|
||||
onPressed: () => _cancelGift(context, ref, gift),
|
||||
icon: const Icon(Icons.cancel, size: 16),
|
||||
label: const Text('Cancel'),
|
||||
style: OutlinedButton.styleFrom(
|
||||
foregroundColor: Theme.of(context).colorScheme.error,
|
||||
side: BorderSide(color: Theme.of(context).colorScheme.error),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
String _getGiftStatusText(int status) {
|
||||
switch (status) {
|
||||
case 0:
|
||||
return 'Created';
|
||||
case 1:
|
||||
return 'Sent';
|
||||
case 2:
|
||||
return 'Redeemed';
|
||||
case 3:
|
||||
return 'Cancelled';
|
||||
case 4:
|
||||
return 'Expired';
|
||||
default:
|
||||
return 'Unknown';
|
||||
}
|
||||
}
|
||||
|
||||
Color _getGiftStatusColor(BuildContext context, int status) {
|
||||
switch (status) {
|
||||
case 0:
|
||||
return Colors.grey;
|
||||
case 1:
|
||||
return Colors.blue;
|
||||
case 2:
|
||||
return Colors.green;
|
||||
case 3:
|
||||
return Colors.red;
|
||||
case 4:
|
||||
return Colors.orange;
|
||||
default:
|
||||
return Theme.of(context).colorScheme.primary;
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _showPurchaseGiftDialog(
|
||||
BuildContext context,
|
||||
WidgetRef ref,
|
||||
String subscriptionId,
|
||||
) async {
|
||||
final messageController = TextEditingController();
|
||||
|
||||
final recipient = await showModalBottomSheet<SnAccount>(
|
||||
isScrollControlled: true,
|
||||
useRootNavigator: true,
|
||||
context: context,
|
||||
builder:
|
||||
(context) => SheetScaffold(
|
||||
titleText: 'Select Recipient (Optional)',
|
||||
child: Column(
|
||||
children: [
|
||||
Expanded(child: AccountPickerSheet()),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: OutlinedButton(
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
child: Text('Skip (Open Gift)'),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
if (!context.mounted) return;
|
||||
|
||||
final message = await showModalBottomSheet<String>(
|
||||
isScrollControlled: true,
|
||||
useRootNavigator: true,
|
||||
context: context,
|
||||
builder:
|
||||
(context) => SheetScaffold(
|
||||
titleText: 'Add Message (Optional)',
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Column(
|
||||
children: [
|
||||
TextField(
|
||||
controller: messageController,
|
||||
decoration: InputDecoration(
|
||||
labelText: 'Message',
|
||||
hintText: 'Add a personal message',
|
||||
border: OutlineInputBorder(),
|
||||
enabledBorder: OutlineInputBorder(
|
||||
borderSide: BorderSide(
|
||||
color: Theme.of(
|
||||
context,
|
||||
).colorScheme.outline.withOpacity(0.2),
|
||||
),
|
||||
),
|
||||
focusedBorder: OutlineInputBorder(
|
||||
borderSide: BorderSide(
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
),
|
||||
),
|
||||
maxLines: 3,
|
||||
autofocus: true,
|
||||
),
|
||||
const Gap(16),
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: OutlinedButton(
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
child: Text('Skip'),
|
||||
),
|
||||
),
|
||||
const Gap(8),
|
||||
Expanded(
|
||||
child: FilledButton(
|
||||
onPressed:
|
||||
() => Navigator.of(context).pop(
|
||||
messageController.text.trim().isEmpty
|
||||
? null
|
||||
: messageController.text.trim(),
|
||||
),
|
||||
child: Text('Add Message'),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
if (context.mounted) {
|
||||
await _purchaseGift(context, ref, subscriptionId, recipient?.id, message);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _purchaseGift(
|
||||
BuildContext context,
|
||||
WidgetRef ref,
|
||||
String subscriptionId,
|
||||
String? recipientId,
|
||||
String? message,
|
||||
) async {
|
||||
final client = ref.watch(apiClientProvider);
|
||||
try {
|
||||
showLoadingModal(context);
|
||||
final resp = await client.post(
|
||||
'/id/subscriptions/gifts/purchase',
|
||||
data: {
|
||||
'subscription_identifier': subscriptionId,
|
||||
if (recipientId != null) 'recipient_id': recipientId,
|
||||
'payment_method': 'solian.wallet',
|
||||
'payment_details': {'currency': 'golds'},
|
||||
if (message != null) 'message': message,
|
||||
'gift_duration_days': 30,
|
||||
'subscription_duration_days': 30,
|
||||
},
|
||||
options: Options(headers: {'X-Noop': true}),
|
||||
);
|
||||
final gift = SnWalletGift.fromJson(resp.data);
|
||||
if (gift.status == 1) return; // Already paid
|
||||
|
||||
final orderResp = await client.post(
|
||||
'/id/subscriptions/gifts/${gift.id}/order',
|
||||
);
|
||||
final order = SnWalletOrder.fromJson(orderResp.data);
|
||||
|
||||
if (context.mounted) hideLoadingModal(context);
|
||||
|
||||
// Show payment overlay to complete the payment
|
||||
if (!context.mounted) return;
|
||||
final paidOrder = await PaymentOverlay.show(
|
||||
context: context,
|
||||
order: order,
|
||||
enableBiometric: true,
|
||||
);
|
||||
|
||||
if (context.mounted) showLoadingModal(context);
|
||||
|
||||
if (paidOrder != null) {
|
||||
// Wait for server to handle order
|
||||
await Future.delayed(const Duration(seconds: 1));
|
||||
|
||||
// Get the updated gift
|
||||
final giftResp = await client.get('/id/subscriptions/gifts/${gift.id}');
|
||||
final updatedGift = SnWalletGift.fromJson(giftResp.data);
|
||||
|
||||
if (context.mounted) hideLoadingModal(context);
|
||||
|
||||
// Show gift code dialog
|
||||
if (context.mounted) {
|
||||
await showDialog(
|
||||
context: context,
|
||||
builder:
|
||||
(context) => AlertDialog(
|
||||
title: Text('Gift Purchased!'),
|
||||
content: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text('Gift Code: ${updatedGift.giftCode}'),
|
||||
const Gap(8),
|
||||
Text(
|
||||
'Share this code with the recipient to redeem the gift.',
|
||||
),
|
||||
if (updatedGift.recipientId == null) ...[
|
||||
const Gap(8),
|
||||
Text('This is an open gift that anyone can redeem.'),
|
||||
],
|
||||
],
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
child: Text('OK'),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
ref.invalidate(accountSentGiftsProvider);
|
||||
} catch (err) {
|
||||
showErrorAlert(err);
|
||||
} finally {
|
||||
if (context.mounted) hideLoadingModal(context);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _showRedeemGiftDialog(
|
||||
BuildContext context,
|
||||
WidgetRef ref,
|
||||
) async {
|
||||
final codeController = TextEditingController();
|
||||
|
||||
final result = await showDialog<String>(
|
||||
context: context,
|
||||
builder:
|
||||
(context) => AlertDialog(
|
||||
title: Text('Redeem Gift'),
|
||||
content: TextField(
|
||||
controller: codeController,
|
||||
decoration: InputDecoration(
|
||||
labelText: 'Gift Code',
|
||||
hintText: 'Enter the gift code',
|
||||
border: OutlineInputBorder(),
|
||||
enabledBorder: OutlineInputBorder(
|
||||
borderSide: BorderSide(
|
||||
color: Theme.of(
|
||||
context,
|
||||
).colorScheme.outline.withOpacity(0.2),
|
||||
),
|
||||
),
|
||||
focusedBorder: OutlineInputBorder(
|
||||
borderSide: BorderSide(
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
),
|
||||
),
|
||||
autofocus: true,
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
child: Text('Cancel'),
|
||||
),
|
||||
FilledButton(
|
||||
onPressed:
|
||||
() => Navigator.of(context).pop(codeController.text.trim()),
|
||||
child: Text('Redeem'),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
if (result != null && result.isNotEmpty && context.mounted) {
|
||||
await _redeemGift(context, ref, result);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _redeemGift(
|
||||
BuildContext context,
|
||||
WidgetRef ref,
|
||||
String giftCode,
|
||||
) async {
|
||||
final client = ref.watch(apiClientProvider);
|
||||
try {
|
||||
showLoadingModal(context);
|
||||
|
||||
// First check if gift can be redeemed
|
||||
final checkResp = await client.get(
|
||||
'/id/subscriptions/gifts/check/$giftCode',
|
||||
);
|
||||
final checkData = checkResp.data as Map<String, dynamic>;
|
||||
|
||||
if (!checkData['can_redeem']) {
|
||||
if (context.mounted) hideLoadingModal(context);
|
||||
showErrorAlert(checkData['error'] ?? 'Gift cannot be redeemed');
|
||||
return;
|
||||
}
|
||||
|
||||
// Redeem the gift
|
||||
await client.post(
|
||||
'/id/subscriptions/gifts/redeem',
|
||||
data: {'gift_code': giftCode},
|
||||
);
|
||||
|
||||
if (context.mounted) {
|
||||
hideLoadingModal(context);
|
||||
await showDialog(
|
||||
context: context,
|
||||
builder:
|
||||
(context) => AlertDialog(
|
||||
title: Text('Gift Redeemed!'),
|
||||
content: Text(
|
||||
'You have successfully redeemed the gift. Your new subscription is now active.',
|
||||
),
|
||||
actions: [
|
||||
FilledButton(
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
child: Text('OK'),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
ref.invalidate(accountReceivedGiftsProvider);
|
||||
ref.invalidate(accountStellarSubscriptionProvider);
|
||||
ref.read(userInfoProvider.notifier).fetchUser();
|
||||
} catch (err) {
|
||||
if (context.mounted) hideLoadingModal(context);
|
||||
showErrorAlert(err);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _cancelGift(
|
||||
BuildContext context,
|
||||
WidgetRef ref,
|
||||
SnWalletGift gift,
|
||||
) async {
|
||||
final confirm = await showConfirmAlert(
|
||||
'Cancel Gift',
|
||||
'Are you sure you want to cancel this gift? This action cannot be undone.',
|
||||
);
|
||||
if (!confirm || !context.mounted) return;
|
||||
|
||||
final client = ref.watch(apiClientProvider);
|
||||
try {
|
||||
showLoadingModal(context);
|
||||
await client.post('/id/subscriptions/gifts/${gift.id}/cancel');
|
||||
ref.invalidate(accountSentGiftsProvider);
|
||||
if (context.mounted) {
|
||||
hideLoadingModal(context);
|
||||
showSnackBar('Gift cancelled successfully');
|
||||
}
|
||||
} catch (err) {
|
||||
if (context.mounted) hideLoadingModal(context);
|
||||
showErrorAlert(err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -27,5 +27,426 @@ final accountStellarSubscriptionProvider =
|
||||
// ignore: unused_element
|
||||
typedef AccountStellarSubscriptionRef =
|
||||
AutoDisposeFutureProviderRef<SnWalletSubscription?>;
|
||||
String _$accountSentGiftsHash() => r'32a282ec863023c749d81423704787943110a188';
|
||||
|
||||
/// 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 [accountSentGifts].
|
||||
@ProviderFor(accountSentGifts)
|
||||
const accountSentGiftsProvider = AccountSentGiftsFamily();
|
||||
|
||||
/// See also [accountSentGifts].
|
||||
class AccountSentGiftsFamily extends Family<AsyncValue<List<SnWalletGift>>> {
|
||||
/// See also [accountSentGifts].
|
||||
const AccountSentGiftsFamily();
|
||||
|
||||
/// See also [accountSentGifts].
|
||||
AccountSentGiftsProvider call({int offset = 0, int take = 20}) {
|
||||
return AccountSentGiftsProvider(offset: offset, take: take);
|
||||
}
|
||||
|
||||
@override
|
||||
AccountSentGiftsProvider getProviderOverride(
|
||||
covariant AccountSentGiftsProvider provider,
|
||||
) {
|
||||
return call(offset: provider.offset, take: provider.take);
|
||||
}
|
||||
|
||||
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'accountSentGiftsProvider';
|
||||
}
|
||||
|
||||
/// See also [accountSentGifts].
|
||||
class AccountSentGiftsProvider
|
||||
extends AutoDisposeFutureProvider<List<SnWalletGift>> {
|
||||
/// See also [accountSentGifts].
|
||||
AccountSentGiftsProvider({int offset = 0, int take = 20})
|
||||
: this._internal(
|
||||
(ref) => accountSentGifts(
|
||||
ref as AccountSentGiftsRef,
|
||||
offset: offset,
|
||||
take: take,
|
||||
),
|
||||
from: accountSentGiftsProvider,
|
||||
name: r'accountSentGiftsProvider',
|
||||
debugGetCreateSourceHash:
|
||||
const bool.fromEnvironment('dart.vm.product')
|
||||
? null
|
||||
: _$accountSentGiftsHash,
|
||||
dependencies: AccountSentGiftsFamily._dependencies,
|
||||
allTransitiveDependencies:
|
||||
AccountSentGiftsFamily._allTransitiveDependencies,
|
||||
offset: offset,
|
||||
take: take,
|
||||
);
|
||||
|
||||
AccountSentGiftsProvider._internal(
|
||||
super._createNotifier, {
|
||||
required super.name,
|
||||
required super.dependencies,
|
||||
required super.allTransitiveDependencies,
|
||||
required super.debugGetCreateSourceHash,
|
||||
required super.from,
|
||||
required this.offset,
|
||||
required this.take,
|
||||
}) : super.internal();
|
||||
|
||||
final int offset;
|
||||
final int take;
|
||||
|
||||
@override
|
||||
Override overrideWith(
|
||||
FutureOr<List<SnWalletGift>> Function(AccountSentGiftsRef provider) create,
|
||||
) {
|
||||
return ProviderOverride(
|
||||
origin: this,
|
||||
override: AccountSentGiftsProvider._internal(
|
||||
(ref) => create(ref as AccountSentGiftsRef),
|
||||
from: from,
|
||||
name: null,
|
||||
dependencies: null,
|
||||
allTransitiveDependencies: null,
|
||||
debugGetCreateSourceHash: null,
|
||||
offset: offset,
|
||||
take: take,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
AutoDisposeFutureProviderElement<List<SnWalletGift>> createElement() {
|
||||
return _AccountSentGiftsProviderElement(this);
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return other is AccountSentGiftsProvider &&
|
||||
other.offset == offset &&
|
||||
other.take == take;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode {
|
||||
var hash = _SystemHash.combine(0, runtimeType.hashCode);
|
||||
hash = _SystemHash.combine(hash, offset.hashCode);
|
||||
hash = _SystemHash.combine(hash, take.hashCode);
|
||||
|
||||
return _SystemHash.finish(hash);
|
||||
}
|
||||
}
|
||||
|
||||
@Deprecated('Will be removed in 3.0. Use Ref instead')
|
||||
// ignore: unused_element
|
||||
mixin AccountSentGiftsRef on AutoDisposeFutureProviderRef<List<SnWalletGift>> {
|
||||
/// The parameter `offset` of this provider.
|
||||
int get offset;
|
||||
|
||||
/// The parameter `take` of this provider.
|
||||
int get take;
|
||||
}
|
||||
|
||||
class _AccountSentGiftsProviderElement
|
||||
extends AutoDisposeFutureProviderElement<List<SnWalletGift>>
|
||||
with AccountSentGiftsRef {
|
||||
_AccountSentGiftsProviderElement(super.provider);
|
||||
|
||||
@override
|
||||
int get offset => (origin as AccountSentGiftsProvider).offset;
|
||||
@override
|
||||
int get take => (origin as AccountSentGiftsProvider).take;
|
||||
}
|
||||
|
||||
String _$accountReceivedGiftsHash() =>
|
||||
r'7c0dfcc109f6f50ec326dd64c2d944aaccd9f775';
|
||||
|
||||
/// See also [accountReceivedGifts].
|
||||
@ProviderFor(accountReceivedGifts)
|
||||
const accountReceivedGiftsProvider = AccountReceivedGiftsFamily();
|
||||
|
||||
/// See also [accountReceivedGifts].
|
||||
class AccountReceivedGiftsFamily
|
||||
extends Family<AsyncValue<List<SnWalletGift>>> {
|
||||
/// See also [accountReceivedGifts].
|
||||
const AccountReceivedGiftsFamily();
|
||||
|
||||
/// See also [accountReceivedGifts].
|
||||
AccountReceivedGiftsProvider call({int offset = 0, int take = 20}) {
|
||||
return AccountReceivedGiftsProvider(offset: offset, take: take);
|
||||
}
|
||||
|
||||
@override
|
||||
AccountReceivedGiftsProvider getProviderOverride(
|
||||
covariant AccountReceivedGiftsProvider provider,
|
||||
) {
|
||||
return call(offset: provider.offset, take: provider.take);
|
||||
}
|
||||
|
||||
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'accountReceivedGiftsProvider';
|
||||
}
|
||||
|
||||
/// See also [accountReceivedGifts].
|
||||
class AccountReceivedGiftsProvider
|
||||
extends AutoDisposeFutureProvider<List<SnWalletGift>> {
|
||||
/// See also [accountReceivedGifts].
|
||||
AccountReceivedGiftsProvider({int offset = 0, int take = 20})
|
||||
: this._internal(
|
||||
(ref) => accountReceivedGifts(
|
||||
ref as AccountReceivedGiftsRef,
|
||||
offset: offset,
|
||||
take: take,
|
||||
),
|
||||
from: accountReceivedGiftsProvider,
|
||||
name: r'accountReceivedGiftsProvider',
|
||||
debugGetCreateSourceHash:
|
||||
const bool.fromEnvironment('dart.vm.product')
|
||||
? null
|
||||
: _$accountReceivedGiftsHash,
|
||||
dependencies: AccountReceivedGiftsFamily._dependencies,
|
||||
allTransitiveDependencies:
|
||||
AccountReceivedGiftsFamily._allTransitiveDependencies,
|
||||
offset: offset,
|
||||
take: take,
|
||||
);
|
||||
|
||||
AccountReceivedGiftsProvider._internal(
|
||||
super._createNotifier, {
|
||||
required super.name,
|
||||
required super.dependencies,
|
||||
required super.allTransitiveDependencies,
|
||||
required super.debugGetCreateSourceHash,
|
||||
required super.from,
|
||||
required this.offset,
|
||||
required this.take,
|
||||
}) : super.internal();
|
||||
|
||||
final int offset;
|
||||
final int take;
|
||||
|
||||
@override
|
||||
Override overrideWith(
|
||||
FutureOr<List<SnWalletGift>> Function(AccountReceivedGiftsRef provider)
|
||||
create,
|
||||
) {
|
||||
return ProviderOverride(
|
||||
origin: this,
|
||||
override: AccountReceivedGiftsProvider._internal(
|
||||
(ref) => create(ref as AccountReceivedGiftsRef),
|
||||
from: from,
|
||||
name: null,
|
||||
dependencies: null,
|
||||
allTransitiveDependencies: null,
|
||||
debugGetCreateSourceHash: null,
|
||||
offset: offset,
|
||||
take: take,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
AutoDisposeFutureProviderElement<List<SnWalletGift>> createElement() {
|
||||
return _AccountReceivedGiftsProviderElement(this);
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return other is AccountReceivedGiftsProvider &&
|
||||
other.offset == offset &&
|
||||
other.take == take;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode {
|
||||
var hash = _SystemHash.combine(0, runtimeType.hashCode);
|
||||
hash = _SystemHash.combine(hash, offset.hashCode);
|
||||
hash = _SystemHash.combine(hash, take.hashCode);
|
||||
|
||||
return _SystemHash.finish(hash);
|
||||
}
|
||||
}
|
||||
|
||||
@Deprecated('Will be removed in 3.0. Use Ref instead')
|
||||
// ignore: unused_element
|
||||
mixin AccountReceivedGiftsRef
|
||||
on AutoDisposeFutureProviderRef<List<SnWalletGift>> {
|
||||
/// The parameter `offset` of this provider.
|
||||
int get offset;
|
||||
|
||||
/// The parameter `take` of this provider.
|
||||
int get take;
|
||||
}
|
||||
|
||||
class _AccountReceivedGiftsProviderElement
|
||||
extends AutoDisposeFutureProviderElement<List<SnWalletGift>>
|
||||
with AccountReceivedGiftsRef {
|
||||
_AccountReceivedGiftsProviderElement(super.provider);
|
||||
|
||||
@override
|
||||
int get offset => (origin as AccountReceivedGiftsProvider).offset;
|
||||
@override
|
||||
int get take => (origin as AccountReceivedGiftsProvider).take;
|
||||
}
|
||||
|
||||
String _$accountGiftHash() => r'7169d355f78e4fe3bf6b3ff444350faa46a0d216';
|
||||
|
||||
/// See also [accountGift].
|
||||
@ProviderFor(accountGift)
|
||||
const accountGiftProvider = AccountGiftFamily();
|
||||
|
||||
/// See also [accountGift].
|
||||
class AccountGiftFamily extends Family<AsyncValue<SnWalletGift>> {
|
||||
/// See also [accountGift].
|
||||
const AccountGiftFamily();
|
||||
|
||||
/// See also [accountGift].
|
||||
AccountGiftProvider call(String giftId) {
|
||||
return AccountGiftProvider(giftId);
|
||||
}
|
||||
|
||||
@override
|
||||
AccountGiftProvider getProviderOverride(
|
||||
covariant AccountGiftProvider provider,
|
||||
) {
|
||||
return call(provider.giftId);
|
||||
}
|
||||
|
||||
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'accountGiftProvider';
|
||||
}
|
||||
|
||||
/// See also [accountGift].
|
||||
class AccountGiftProvider extends AutoDisposeFutureProvider<SnWalletGift> {
|
||||
/// See also [accountGift].
|
||||
AccountGiftProvider(String giftId)
|
||||
: this._internal(
|
||||
(ref) => accountGift(ref as AccountGiftRef, giftId),
|
||||
from: accountGiftProvider,
|
||||
name: r'accountGiftProvider',
|
||||
debugGetCreateSourceHash:
|
||||
const bool.fromEnvironment('dart.vm.product')
|
||||
? null
|
||||
: _$accountGiftHash,
|
||||
dependencies: AccountGiftFamily._dependencies,
|
||||
allTransitiveDependencies: AccountGiftFamily._allTransitiveDependencies,
|
||||
giftId: giftId,
|
||||
);
|
||||
|
||||
AccountGiftProvider._internal(
|
||||
super._createNotifier, {
|
||||
required super.name,
|
||||
required super.dependencies,
|
||||
required super.allTransitiveDependencies,
|
||||
required super.debugGetCreateSourceHash,
|
||||
required super.from,
|
||||
required this.giftId,
|
||||
}) : super.internal();
|
||||
|
||||
final String giftId;
|
||||
|
||||
@override
|
||||
Override overrideWith(
|
||||
FutureOr<SnWalletGift> Function(AccountGiftRef provider) create,
|
||||
) {
|
||||
return ProviderOverride(
|
||||
origin: this,
|
||||
override: AccountGiftProvider._internal(
|
||||
(ref) => create(ref as AccountGiftRef),
|
||||
from: from,
|
||||
name: null,
|
||||
dependencies: null,
|
||||
allTransitiveDependencies: null,
|
||||
debugGetCreateSourceHash: null,
|
||||
giftId: giftId,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
AutoDisposeFutureProviderElement<SnWalletGift> createElement() {
|
||||
return _AccountGiftProviderElement(this);
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return other is AccountGiftProvider && other.giftId == giftId;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode {
|
||||
var hash = _SystemHash.combine(0, runtimeType.hashCode);
|
||||
hash = _SystemHash.combine(hash, giftId.hashCode);
|
||||
|
||||
return _SystemHash.finish(hash);
|
||||
}
|
||||
}
|
||||
|
||||
@Deprecated('Will be removed in 3.0. Use Ref instead')
|
||||
// ignore: unused_element
|
||||
mixin AccountGiftRef on AutoDisposeFutureProviderRef<SnWalletGift> {
|
||||
/// The parameter `giftId` of this provider.
|
||||
String get giftId;
|
||||
}
|
||||
|
||||
class _AccountGiftProviderElement
|
||||
extends AutoDisposeFutureProviderElement<SnWalletGift>
|
||||
with AccountGiftRef {
|
||||
_AccountGiftProviderElement(super.provider);
|
||||
|
||||
@override
|
||||
String get giftId => (origin as AccountGiftProvider).giftId;
|
||||
}
|
||||
|
||||
// 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
|
||||
|
@@ -3,11 +3,13 @@ import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:gap/gap.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:island/models/file.dart';
|
||||
import 'package:island/models/post.dart';
|
||||
import 'package:island/models/publisher.dart';
|
||||
import 'package:island/pods/network.dart';
|
||||
import 'package:island/screens/creators/publishers.dart';
|
||||
import 'package:island/screens/creators/publishers_form.dart';
|
||||
import 'package:island/screens/posts/compose.dart';
|
||||
import 'package:island/services/compose_storage_db.dart';
|
||||
import 'package:island/services/responsive.dart';
|
||||
@@ -31,6 +33,7 @@ class PostComposeCard extends HookConsumerWidget {
|
||||
final VoidCallback? onCancel;
|
||||
final Function(SnPost)? onSubmit;
|
||||
final Function(ComposeState)? onStateChanged;
|
||||
final bool isInDialog;
|
||||
|
||||
const PostComposeCard({
|
||||
super.key,
|
||||
@@ -39,6 +42,7 @@ class PostComposeCard extends HookConsumerWidget {
|
||||
this.onCancel,
|
||||
this.onSubmit,
|
||||
this.onStateChanged,
|
||||
this.isInDialog = false,
|
||||
});
|
||||
|
||||
@override
|
||||
@@ -441,7 +445,11 @@ class PostComposeCard extends HookConsumerWidget {
|
||||
),
|
||||
),
|
||||
IconButton(
|
||||
onPressed: state.submitting.value ? null : performSubmit,
|
||||
onPressed:
|
||||
(state.submitting.value ||
|
||||
state.currentPublisher.value == null)
|
||||
? null
|
||||
: performSubmit,
|
||||
icon:
|
||||
state.submitting.value
|
||||
? SizedBox(
|
||||
@@ -515,16 +523,31 @@ class PostComposeCard extends HookConsumerWidget {
|
||||
: null,
|
||||
),
|
||||
onTap: () {
|
||||
showModalBottomSheet(
|
||||
isScrollControlled: true,
|
||||
useRootNavigator: true,
|
||||
context: context,
|
||||
builder: (context) => const PublisherModal(),
|
||||
).then((value) {
|
||||
if (value != null) {
|
||||
state.currentPublisher.value = value;
|
||||
if (state.currentPublisher.value == null) {
|
||||
// No publisher loaded, guide user to create one
|
||||
if (isInDialog) {
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
});
|
||||
context.pushNamed('creatorNew').then((value) {
|
||||
if (value != null) {
|
||||
state.currentPublisher.value =
|
||||
value as SnPublisher;
|
||||
ref.invalidate(publishersManagedProvider);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// Show modal to select from existing publishers
|
||||
showModalBottomSheet(
|
||||
isScrollControlled: true,
|
||||
useRootNavigator: true,
|
||||
context: context,
|
||||
builder: (context) => const PublisherModal(),
|
||||
).then((value) {
|
||||
if (value != null) {
|
||||
state.currentPublisher.value = value;
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
).padding(top: 8),
|
||||
|
||||
@@ -533,8 +556,43 @@ class PostComposeCard extends HookConsumerWidget {
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
if (state.currentPublisher.value == null)
|
||||
Container(
|
||||
padding: const EdgeInsets.all(12),
|
||||
margin: const EdgeInsets.only(bottom: 8),
|
||||
decoration: BoxDecoration(
|
||||
color:
|
||||
theme.colorScheme.surfaceContainerHigh,
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: Row(
|
||||
crossAxisAlignment:
|
||||
CrossAxisAlignment.start,
|
||||
children: [
|
||||
Icon(
|
||||
Symbols.info,
|
||||
size: 16,
|
||||
color: theme.colorScheme.primary,
|
||||
),
|
||||
const Gap(8),
|
||||
Expanded(
|
||||
child: Text(
|
||||
'Tap the avatar to create a publisher and start composing.',
|
||||
style: theme.textTheme.bodySmall
|
||||
?.copyWith(
|
||||
color:
|
||||
theme
|
||||
.colorScheme
|
||||
.onSurfaceVariant,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
TextField(
|
||||
controller: state.titleController,
|
||||
enabled: state.currentPublisher.value != null,
|
||||
decoration: InputDecoration(
|
||||
hintText: 'postTitle'.tr(),
|
||||
border: InputBorder.none,
|
||||
@@ -552,6 +610,7 @@ class PostComposeCard extends HookConsumerWidget {
|
||||
),
|
||||
TextField(
|
||||
controller: state.descriptionController,
|
||||
enabled: state.currentPublisher.value != null,
|
||||
decoration: InputDecoration(
|
||||
hintText: 'postDescription'.tr(),
|
||||
border: InputBorder.none,
|
||||
@@ -573,6 +632,7 @@ class PostComposeCard extends HookConsumerWidget {
|
||||
),
|
||||
TextField(
|
||||
controller: state.contentController,
|
||||
enabled: state.currentPublisher.value != null,
|
||||
style: theme.textTheme.bodyMedium,
|
||||
decoration: InputDecoration(
|
||||
border: InputBorder.none,
|
||||
|
@@ -70,6 +70,7 @@ class PostComposeDialog extends HookConsumerWidget {
|
||||
initialState: restoredInitialState.value ?? initialState,
|
||||
onCancel: () => Navigator.of(context).pop(),
|
||||
onSubmit: (post) => Navigator.of(context).pop(post),
|
||||
isInDialog: true,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
@@ -6,7 +6,7 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:island/models/post.dart';
|
||||
import 'package:island/models/publisher.dart';
|
||||
import 'package:island/pods/network.dart';
|
||||
import 'package:island/screens/creators/publishers.dart';
|
||||
import 'package:island/screens/creators/publishers_form.dart';
|
||||
import 'package:island/screens/posts/compose.dart';
|
||||
import 'package:island/widgets/alert.dart';
|
||||
import 'package:island/widgets/content/cloud_files.dart';
|
||||
|
@@ -5,7 +5,7 @@ import 'package:flutter/material.dart';
|
||||
import 'package:gap/gap.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:island/screens/creators/publishers.dart';
|
||||
import 'package:island/screens/creators/publishers_form.dart';
|
||||
import 'package:island/widgets/content/cloud_files.dart';
|
||||
import 'package:styled_widget/styled_widget.dart';
|
||||
|
||||
@@ -43,13 +43,15 @@ class PublisherModal extends HookConsumerWidget {
|
||||
const Gap(12),
|
||||
ElevatedButton(
|
||||
onPressed: () {
|
||||
context.pushNamed('creatorNew').then((value) {
|
||||
if (value != null) {
|
||||
ref.invalidate(
|
||||
publishersManagedProvider,
|
||||
);
|
||||
}
|
||||
});
|
||||
context.pushNamed('creatorNew').then((
|
||||
value,
|
||||
) {
|
||||
if (value != null) {
|
||||
ref.invalidate(
|
||||
publishersManagedProvider,
|
||||
);
|
||||
}
|
||||
});
|
||||
},
|
||||
child: Text('createPublisher').tr(),
|
||||
),
|
||||
|
Reference in New Issue
Block a user