✨ Status creation & update & clear
This commit is contained in:
parent
d678cc571a
commit
fb6e65a0fb
@ -36,6 +36,7 @@
|
||||
"editPublisher": "Edit Publisher",
|
||||
"syncPublisher": "Use Account Data",
|
||||
"create": "Create",
|
||||
"update": "Update",
|
||||
"edit": "Edit",
|
||||
"delete": "Delete",
|
||||
"deletePublisher": "Delete Publisher {}",
|
||||
@ -117,5 +118,25 @@
|
||||
"checkInResultLevel4": "Best Luck",
|
||||
"checkInActivityTitle": "{} checked in on {} and got a {}",
|
||||
"eventCalander": "Event Calander",
|
||||
"eventCalanderEmpty": "No events on that day."
|
||||
"eventCalanderEmpty": "No events on that day.",
|
||||
"creatorHub": "Creator Hub",
|
||||
"creatorHubDescription": "Manage posts, analytics, and more.",
|
||||
"developerPortal": "Developer Portal",
|
||||
"developerPortalDescription": "Build with Solar Network™.",
|
||||
"statusCreateHint": "What's on your mind? Add a status.",
|
||||
"statusCreate": "Add a Status",
|
||||
"statusUpdate": "Update Status",
|
||||
"statusLabel": "Status",
|
||||
"statusAttitude": "Attitude",
|
||||
"attitudePositive": "Positive",
|
||||
"attitudeNeutral": "Neutral",
|
||||
"attitudeNegative": "Negative",
|
||||
"statusInvisible": "Invisible",
|
||||
"statusInvisibleDescription": "Your will be showing as offline to others.",
|
||||
"statusNotDisturb": "Do Not Disturb",
|
||||
"statusNotDisturbDescription": "Push notification will be disabled.",
|
||||
"statusClearTime": "Cleared At",
|
||||
"statusNoAutoClear": "Do not auto clear",
|
||||
"online": "Online",
|
||||
"offline": "Offline"
|
||||
}
|
||||
|
@ -42,3 +42,24 @@ abstract class SnAccountProfile with _$SnAccountProfile {
|
||||
factory SnAccountProfile.fromJson(Map<String, dynamic> json) =>
|
||||
_$SnAccountProfileFromJson(json);
|
||||
}
|
||||
|
||||
@freezed
|
||||
abstract class SnAccountStatus with _$SnAccountStatus {
|
||||
const factory SnAccountStatus({
|
||||
required String id,
|
||||
required int attitude,
|
||||
required bool isOnline,
|
||||
required bool isInvisible,
|
||||
required bool isNotDisturb,
|
||||
required bool isCustomized,
|
||||
@Default("") String label,
|
||||
required DateTime? clearedAt,
|
||||
required int accountId,
|
||||
required DateTime createdAt,
|
||||
required DateTime updatedAt,
|
||||
required DateTime? deletedAt,
|
||||
}) = _SnAccountStatus;
|
||||
|
||||
factory SnAccountStatus.fromJson(Map<String, dynamic> json) =>
|
||||
_$SnAccountStatusFromJson(json);
|
||||
}
|
||||
|
@ -401,4 +401,170 @@ $SnCloudFileCopyWith<$Res>? get background {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// @nodoc
|
||||
mixin _$SnAccountStatus {
|
||||
|
||||
String get id; int get attitude; bool get isOnline; bool get isInvisible; bool get isNotDisturb; bool get isCustomized; String get label; DateTime? get clearedAt; int get accountId; DateTime get createdAt; DateTime get updatedAt; DateTime? get deletedAt;
|
||||
/// Create a copy of SnAccountStatus
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@pragma('vm:prefer-inline')
|
||||
$SnAccountStatusCopyWith<SnAccountStatus> get copyWith => _$SnAccountStatusCopyWithImpl<SnAccountStatus>(this as SnAccountStatus, _$identity);
|
||||
|
||||
/// Serializes this SnAccountStatus to a JSON map.
|
||||
Map<String, dynamic> toJson();
|
||||
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is SnAccountStatus&&(identical(other.id, id) || other.id == id)&&(identical(other.attitude, attitude) || other.attitude == attitude)&&(identical(other.isOnline, isOnline) || other.isOnline == isOnline)&&(identical(other.isInvisible, isInvisible) || other.isInvisible == isInvisible)&&(identical(other.isNotDisturb, isNotDisturb) || other.isNotDisturb == isNotDisturb)&&(identical(other.isCustomized, isCustomized) || other.isCustomized == isCustomized)&&(identical(other.label, label) || other.label == label)&&(identical(other.clearedAt, clearedAt) || other.clearedAt == clearedAt)&&(identical(other.accountId, accountId) || other.accountId == accountId)&&(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,attitude,isOnline,isInvisible,isNotDisturb,isCustomized,label,clearedAt,accountId,createdAt,updatedAt,deletedAt);
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'SnAccountStatus(id: $id, attitude: $attitude, isOnline: $isOnline, isInvisible: $isInvisible, isNotDisturb: $isNotDisturb, isCustomized: $isCustomized, label: $label, clearedAt: $clearedAt, accountId: $accountId, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt)';
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract mixin class $SnAccountStatusCopyWith<$Res> {
|
||||
factory $SnAccountStatusCopyWith(SnAccountStatus value, $Res Function(SnAccountStatus) _then) = _$SnAccountStatusCopyWithImpl;
|
||||
@useResult
|
||||
$Res call({
|
||||
String id, int attitude, bool isOnline, bool isInvisible, bool isNotDisturb, bool isCustomized, String label, DateTime? clearedAt, int accountId, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
/// @nodoc
|
||||
class _$SnAccountStatusCopyWithImpl<$Res>
|
||||
implements $SnAccountStatusCopyWith<$Res> {
|
||||
_$SnAccountStatusCopyWithImpl(this._self, this._then);
|
||||
|
||||
final SnAccountStatus _self;
|
||||
final $Res Function(SnAccountStatus) _then;
|
||||
|
||||
/// Create a copy of SnAccountStatus
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? attitude = null,Object? isOnline = null,Object? isInvisible = null,Object? isNotDisturb = null,Object? isCustomized = null,Object? label = null,Object? clearedAt = freezed,Object? accountId = null,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,}) {
|
||||
return _then(_self.copyWith(
|
||||
id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
|
||||
as String,attitude: null == attitude ? _self.attitude : attitude // ignore: cast_nullable_to_non_nullable
|
||||
as int,isOnline: null == isOnline ? _self.isOnline : isOnline // ignore: cast_nullable_to_non_nullable
|
||||
as bool,isInvisible: null == isInvisible ? _self.isInvisible : isInvisible // ignore: cast_nullable_to_non_nullable
|
||||
as bool,isNotDisturb: null == isNotDisturb ? _self.isNotDisturb : isNotDisturb // ignore: cast_nullable_to_non_nullable
|
||||
as bool,isCustomized: null == isCustomized ? _self.isCustomized : isCustomized // ignore: cast_nullable_to_non_nullable
|
||||
as bool,label: null == label ? _self.label : label // ignore: cast_nullable_to_non_nullable
|
||||
as String,clearedAt: freezed == clearedAt ? _self.clearedAt : clearedAt // ignore: cast_nullable_to_non_nullable
|
||||
as DateTime?,accountId: null == accountId ? _self.accountId : accountId // ignore: cast_nullable_to_non_nullable
|
||||
as int,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?,
|
||||
));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/// @nodoc
|
||||
@JsonSerializable()
|
||||
|
||||
class _SnAccountStatus implements SnAccountStatus {
|
||||
const _SnAccountStatus({required this.id, required this.attitude, required this.isOnline, required this.isInvisible, required this.isNotDisturb, required this.isCustomized, this.label = "", required this.clearedAt, required this.accountId, required this.createdAt, required this.updatedAt, required this.deletedAt});
|
||||
factory _SnAccountStatus.fromJson(Map<String, dynamic> json) => _$SnAccountStatusFromJson(json);
|
||||
|
||||
@override final String id;
|
||||
@override final int attitude;
|
||||
@override final bool isOnline;
|
||||
@override final bool isInvisible;
|
||||
@override final bool isNotDisturb;
|
||||
@override final bool isCustomized;
|
||||
@override@JsonKey() final String label;
|
||||
@override final DateTime? clearedAt;
|
||||
@override final int accountId;
|
||||
@override final DateTime createdAt;
|
||||
@override final DateTime updatedAt;
|
||||
@override final DateTime? deletedAt;
|
||||
|
||||
/// Create a copy of SnAccountStatus
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override @JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@pragma('vm:prefer-inline')
|
||||
_$SnAccountStatusCopyWith<_SnAccountStatus> get copyWith => __$SnAccountStatusCopyWithImpl<_SnAccountStatus>(this, _$identity);
|
||||
|
||||
@override
|
||||
Map<String, dynamic> toJson() {
|
||||
return _$SnAccountStatusToJson(this, );
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is _SnAccountStatus&&(identical(other.id, id) || other.id == id)&&(identical(other.attitude, attitude) || other.attitude == attitude)&&(identical(other.isOnline, isOnline) || other.isOnline == isOnline)&&(identical(other.isInvisible, isInvisible) || other.isInvisible == isInvisible)&&(identical(other.isNotDisturb, isNotDisturb) || other.isNotDisturb == isNotDisturb)&&(identical(other.isCustomized, isCustomized) || other.isCustomized == isCustomized)&&(identical(other.label, label) || other.label == label)&&(identical(other.clearedAt, clearedAt) || other.clearedAt == clearedAt)&&(identical(other.accountId, accountId) || other.accountId == accountId)&&(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,attitude,isOnline,isInvisible,isNotDisturb,isCustomized,label,clearedAt,accountId,createdAt,updatedAt,deletedAt);
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'SnAccountStatus(id: $id, attitude: $attitude, isOnline: $isOnline, isInvisible: $isInvisible, isNotDisturb: $isNotDisturb, isCustomized: $isCustomized, label: $label, clearedAt: $clearedAt, accountId: $accountId, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt)';
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract mixin class _$SnAccountStatusCopyWith<$Res> implements $SnAccountStatusCopyWith<$Res> {
|
||||
factory _$SnAccountStatusCopyWith(_SnAccountStatus value, $Res Function(_SnAccountStatus) _then) = __$SnAccountStatusCopyWithImpl;
|
||||
@override @useResult
|
||||
$Res call({
|
||||
String id, int attitude, bool isOnline, bool isInvisible, bool isNotDisturb, bool isCustomized, String label, DateTime? clearedAt, int accountId, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
/// @nodoc
|
||||
class __$SnAccountStatusCopyWithImpl<$Res>
|
||||
implements _$SnAccountStatusCopyWith<$Res> {
|
||||
__$SnAccountStatusCopyWithImpl(this._self, this._then);
|
||||
|
||||
final _SnAccountStatus _self;
|
||||
final $Res Function(_SnAccountStatus) _then;
|
||||
|
||||
/// Create a copy of SnAccountStatus
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? attitude = null,Object? isOnline = null,Object? isInvisible = null,Object? isNotDisturb = null,Object? isCustomized = null,Object? label = null,Object? clearedAt = freezed,Object? accountId = null,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,}) {
|
||||
return _then(_SnAccountStatus(
|
||||
id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
|
||||
as String,attitude: null == attitude ? _self.attitude : attitude // ignore: cast_nullable_to_non_nullable
|
||||
as int,isOnline: null == isOnline ? _self.isOnline : isOnline // ignore: cast_nullable_to_non_nullable
|
||||
as bool,isInvisible: null == isInvisible ? _self.isInvisible : isInvisible // ignore: cast_nullable_to_non_nullable
|
||||
as bool,isNotDisturb: null == isNotDisturb ? _self.isNotDisturb : isNotDisturb // ignore: cast_nullable_to_non_nullable
|
||||
as bool,isCustomized: null == isCustomized ? _self.isCustomized : isCustomized // ignore: cast_nullable_to_non_nullable
|
||||
as bool,label: null == label ? _self.label : label // ignore: cast_nullable_to_non_nullable
|
||||
as String,clearedAt: freezed == clearedAt ? _self.clearedAt : clearedAt // ignore: cast_nullable_to_non_nullable
|
||||
as DateTime?,accountId: null == accountId ? _self.accountId : accountId // ignore: cast_nullable_to_non_nullable
|
||||
as int,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?,
|
||||
));
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
// dart format on
|
||||
|
@ -76,3 +76,41 @@ Map<String, dynamic> _$SnAccountProfileToJson(_SnAccountProfile instance) =>
|
||||
'updated_at': instance.updatedAt.toIso8601String(),
|
||||
'deleted_at': instance.deletedAt?.toIso8601String(),
|
||||
};
|
||||
|
||||
_SnAccountStatus _$SnAccountStatusFromJson(Map<String, dynamic> json) =>
|
||||
_SnAccountStatus(
|
||||
id: json['id'] as String,
|
||||
attitude: (json['attitude'] as num).toInt(),
|
||||
isOnline: json['is_online'] as bool,
|
||||
isInvisible: json['is_invisible'] as bool,
|
||||
isNotDisturb: json['is_not_disturb'] as bool,
|
||||
isCustomized: json['is_customized'] as bool,
|
||||
label: json['label'] as String? ?? "",
|
||||
clearedAt:
|
||||
json['cleared_at'] == null
|
||||
? null
|
||||
: DateTime.parse(json['cleared_at'] as String),
|
||||
accountId: (json['account_id'] as num).toInt(),
|
||||
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> _$SnAccountStatusToJson(_SnAccountStatus instance) =>
|
||||
<String, dynamic>{
|
||||
'id': instance.id,
|
||||
'attitude': instance.attitude,
|
||||
'is_online': instance.isOnline,
|
||||
'is_invisible': instance.isInvisible,
|
||||
'is_not_disturb': instance.isNotDisturb,
|
||||
'is_customized': instance.isCustomized,
|
||||
'label': instance.label,
|
||||
'cleared_at': instance.clearedAt?.toIso8601String(),
|
||||
'account_id': instance.accountId,
|
||||
'created_at': instance.createdAt.toIso8601String(),
|
||||
'updated_at': instance.updatedAt.toIso8601String(),
|
||||
'deleted_at': instance.deletedAt?.toIso8601String(),
|
||||
};
|
||||
|
@ -59,6 +59,7 @@ class WebSocketService {
|
||||
Uri.parse(url),
|
||||
headers: {'Authorization': 'Bearer $atk'},
|
||||
);
|
||||
// TODO Fix the atk is expired when reconnecting
|
||||
await _channel!.ready;
|
||||
_statusStreamController.sink.add(WebSocketState.connected());
|
||||
_channel!.stream.listen(
|
||||
|
@ -9,6 +9,7 @@ import 'package:island/pods/message.dart';
|
||||
import 'package:island/pods/network.dart';
|
||||
import 'package:island/pods/userinfo.dart';
|
||||
import 'package:island/route.gr.dart';
|
||||
import 'package:island/widgets/account/status.dart';
|
||||
import 'package:island/widgets/app_scaffold.dart';
|
||||
import 'package:island/widgets/content/cloud_files.dart';
|
||||
import 'package:material_symbols_icons/symbols.dart';
|
||||
@ -31,68 +32,105 @@ class AccountScreen extends HookConsumerWidget {
|
||||
body: SingleChildScrollView(
|
||||
child: Column(
|
||||
children: <Widget>[
|
||||
GestureDetector(
|
||||
child: Card(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
if (user.value?.profile.background != null)
|
||||
ClipRRect(
|
||||
borderRadius: BorderRadius.only(
|
||||
topLeft: Radius.circular(8),
|
||||
topRight: Radius.circular(8),
|
||||
),
|
||||
child: AspectRatio(
|
||||
aspectRatio: 16 / 7,
|
||||
child: CloudFileWidget(
|
||||
item: user.value!.profile.background!,
|
||||
fit: BoxFit.cover,
|
||||
),
|
||||
Card(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
if (user.value?.profile.background != null)
|
||||
ClipRRect(
|
||||
borderRadius: BorderRadius.only(
|
||||
topLeft: Radius.circular(8),
|
||||
topRight: Radius.circular(8),
|
||||
),
|
||||
child: AspectRatio(
|
||||
aspectRatio: 16 / 7,
|
||||
child: CloudFileWidget(
|
||||
item: user.value!.profile.background!,
|
||||
fit: BoxFit.cover,
|
||||
),
|
||||
),
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
spacing: 16,
|
||||
children: [
|
||||
ProfilePictureWidget(
|
||||
),
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
spacing: 16,
|
||||
children: [
|
||||
GestureDetector(
|
||||
child: ProfilePictureWidget(
|
||||
fileId: user.value?.profile.pictureId,
|
||||
radius: 24,
|
||||
),
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
spacing: 4,
|
||||
crossAxisAlignment: CrossAxisAlignment.baseline,
|
||||
textBaseline: TextBaseline.alphabetic,
|
||||
children: [
|
||||
Text(user.value!.nick).bold().fontSize(16),
|
||||
Text('@${user.value!.name}'),
|
||||
],
|
||||
),
|
||||
Text(
|
||||
user.value!.profile.bio ?? 'No description yet.',
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
).padding(horizontal: 16, vertical: 16),
|
||||
],
|
||||
onTap: () {
|
||||
context.router.push(
|
||||
AccountProfileRoute(name: user.value!.name),
|
||||
);
|
||||
},
|
||||
),
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
spacing: 4,
|
||||
crossAxisAlignment: CrossAxisAlignment.baseline,
|
||||
textBaseline: TextBaseline.alphabetic,
|
||||
children: [
|
||||
Text(user.value!.nick).bold().fontSize(16),
|
||||
Text('@${user.value!.name}'),
|
||||
],
|
||||
),
|
||||
Text(
|
||||
user.value!.profile.bio ?? 'No description yet.',
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
).padding(horizontal: 16, top: 16),
|
||||
AccountStatusCreationWidget(uname: user.value!.name),
|
||||
],
|
||||
),
|
||||
).padding(horizontal: 8),
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Card(
|
||||
child: InkWell(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Icon(Symbols.draw, size: 28).padding(bottom: 8),
|
||||
Text('creatorHub').tr().fontSize(16).bold(),
|
||||
Text('creatorHubDescription').tr(),
|
||||
],
|
||||
).padding(horizontal: 16, vertical: 12),
|
||||
onTap: () {},
|
||||
),
|
||||
),
|
||||
),
|
||||
).padding(horizontal: 8),
|
||||
onTap: () {
|
||||
context.router.push(
|
||||
AccountProfileRoute(name: user.value!.name),
|
||||
);
|
||||
},
|
||||
),
|
||||
Expanded(
|
||||
child: Card(
|
||||
child: InkWell(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Icon(Symbols.code, size: 28).padding(bottom: 8),
|
||||
Text('developerPortal').tr().fontSize(16).bold(),
|
||||
Text('developerPortalDescription').tr(),
|
||||
],
|
||||
).padding(horizontal: 16, vertical: 12),
|
||||
onTap: () {},
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
).padding(horizontal: 8),
|
||||
const Gap(8),
|
||||
ListTile(
|
||||
minTileHeight: 48,
|
||||
leading: const Icon(Symbols.public),
|
||||
trailing: const Icon(Symbols.chevron_right),
|
||||
contentPadding: EdgeInsets.symmetric(horizontal: 24),
|
||||
title: Text('managedPublisher').tr(),
|
||||
title: Text('publishers').tr(),
|
||||
onTap: () {
|
||||
context.router.push(ManagedPublisherRoute());
|
||||
},
|
||||
|
130
lib/widgets/account/status.dart
Normal file
130
lib/widgets/account/status.dart
Normal file
@ -0,0 +1,130 @@
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:island/models/user.dart';
|
||||
import 'package:island/pods/network.dart';
|
||||
import 'package:island/widgets/account/status_creation.dart';
|
||||
import 'package:material_symbols_icons/symbols.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
import 'package:styled_widget/styled_widget.dart';
|
||||
|
||||
part 'status.g.dart';
|
||||
|
||||
@riverpod
|
||||
Future<SnAccountStatus?> accountStatus(Ref ref, String uname) async {
|
||||
final apiClient = ref.watch(apiClientProvider);
|
||||
try {
|
||||
final resp = await apiClient.get('/accounts/$uname/statuses');
|
||||
return SnAccountStatus.fromJson(resp.data);
|
||||
} catch (err) {
|
||||
if (err is DioException) {
|
||||
if (err.response?.statusCode == 404) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
class AccountStatusCreationWidget extends HookConsumerWidget {
|
||||
final String uname;
|
||||
final EdgeInsets? padding;
|
||||
const AccountStatusCreationWidget({
|
||||
super.key,
|
||||
required this.uname,
|
||||
this.padding,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final userStatus = ref.watch(accountStatusProvider(uname));
|
||||
|
||||
return InkWell(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
child: userStatus.when(
|
||||
data:
|
||||
(status) =>
|
||||
(status?.isCustomized ?? false)
|
||||
? AccountStatusWidget(uname: uname)
|
||||
: Padding(
|
||||
padding:
|
||||
padding ??
|
||||
EdgeInsets.symmetric(horizontal: 27, vertical: 4),
|
||||
child: Row(
|
||||
spacing: 4,
|
||||
children: [
|
||||
Icon(Symbols.keyboard_arrow_up),
|
||||
Text('statusCreateHint').tr(),
|
||||
],
|
||||
),
|
||||
).opacity(0.85),
|
||||
error:
|
||||
(error, _) => Padding(
|
||||
padding:
|
||||
padding ?? EdgeInsets.symmetric(horizontal: 26, vertical: 4),
|
||||
child: Row(
|
||||
spacing: 4,
|
||||
children: [Icon(Symbols.close), Text('Error: $error')],
|
||||
),
|
||||
).opacity(0.85),
|
||||
loading:
|
||||
() => Padding(
|
||||
padding:
|
||||
padding ?? EdgeInsets.symmetric(horizontal: 26, vertical: 4),
|
||||
child: Row(
|
||||
spacing: 4,
|
||||
children: [Icon(Symbols.more_vert), Text('loading').tr()],
|
||||
),
|
||||
).opacity(0.85),
|
||||
),
|
||||
onTap: () {
|
||||
showModalBottomSheet(
|
||||
context: context,
|
||||
builder:
|
||||
(context) => AccountStatusCreationSheet(
|
||||
initialStatus:
|
||||
(userStatus.value?.isCustomized ?? false)
|
||||
? userStatus.value
|
||||
: null,
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class AccountStatusWidget extends HookConsumerWidget {
|
||||
final String uname;
|
||||
final EdgeInsets? padding;
|
||||
const AccountStatusWidget({super.key, required this.uname, this.padding});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final userStatus = ref.watch(accountStatusProvider(uname));
|
||||
|
||||
return Padding(
|
||||
padding: padding ?? EdgeInsets.symmetric(horizontal: 27, vertical: 4),
|
||||
child: Row(
|
||||
spacing: 4,
|
||||
children: [
|
||||
if (!(userStatus.value?.isCustomized ?? false))
|
||||
Icon(Symbols.keyboard_arrow_up)
|
||||
else if (userStatus.value!.isOnline)
|
||||
Icon(
|
||||
Symbols.circle,
|
||||
fill: 1,
|
||||
color: Colors.green,
|
||||
size: 16,
|
||||
).padding(all: 4)
|
||||
else
|
||||
Icon(Symbols.circle, color: Colors.grey, size: 16).padding(all: 4),
|
||||
if (userStatus.value?.isCustomized ?? false)
|
||||
Text(userStatus.value?.label ?? 'unknown'.tr())
|
||||
else
|
||||
Text((userStatus.value?.label ?? 'offline').toLowerCase()).tr(),
|
||||
],
|
||||
),
|
||||
).opacity((userStatus.value?.isCustomized ?? false) ? 1 : 0.85);
|
||||
}
|
||||
}
|
153
lib/widgets/account/status.g.dart
Normal file
153
lib/widgets/account/status.g.dart
Normal file
@ -0,0 +1,153 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'status.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// RiverpodGenerator
|
||||
// **************************************************************************
|
||||
|
||||
String _$accountStatusHash() => r'8c3ba5242da1d1e75e3cbf1f2934ff7d5683d0d6';
|
||||
|
||||
/// 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 [accountStatus].
|
||||
@ProviderFor(accountStatus)
|
||||
const accountStatusProvider = AccountStatusFamily();
|
||||
|
||||
/// See also [accountStatus].
|
||||
class AccountStatusFamily extends Family<AsyncValue<SnAccountStatus?>> {
|
||||
/// See also [accountStatus].
|
||||
const AccountStatusFamily();
|
||||
|
||||
/// See also [accountStatus].
|
||||
AccountStatusProvider call(String uname) {
|
||||
return AccountStatusProvider(uname);
|
||||
}
|
||||
|
||||
@override
|
||||
AccountStatusProvider getProviderOverride(
|
||||
covariant AccountStatusProvider provider,
|
||||
) {
|
||||
return call(provider.uname);
|
||||
}
|
||||
|
||||
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'accountStatusProvider';
|
||||
}
|
||||
|
||||
/// See also [accountStatus].
|
||||
class AccountStatusProvider
|
||||
extends AutoDisposeFutureProvider<SnAccountStatus?> {
|
||||
/// See also [accountStatus].
|
||||
AccountStatusProvider(String uname)
|
||||
: this._internal(
|
||||
(ref) => accountStatus(ref as AccountStatusRef, uname),
|
||||
from: accountStatusProvider,
|
||||
name: r'accountStatusProvider',
|
||||
debugGetCreateSourceHash:
|
||||
const bool.fromEnvironment('dart.vm.product')
|
||||
? null
|
||||
: _$accountStatusHash,
|
||||
dependencies: AccountStatusFamily._dependencies,
|
||||
allTransitiveDependencies:
|
||||
AccountStatusFamily._allTransitiveDependencies,
|
||||
uname: uname,
|
||||
);
|
||||
|
||||
AccountStatusProvider._internal(
|
||||
super._createNotifier, {
|
||||
required super.name,
|
||||
required super.dependencies,
|
||||
required super.allTransitiveDependencies,
|
||||
required super.debugGetCreateSourceHash,
|
||||
required super.from,
|
||||
required this.uname,
|
||||
}) : super.internal();
|
||||
|
||||
final String uname;
|
||||
|
||||
@override
|
||||
Override overrideWith(
|
||||
FutureOr<SnAccountStatus?> Function(AccountStatusRef provider) create,
|
||||
) {
|
||||
return ProviderOverride(
|
||||
origin: this,
|
||||
override: AccountStatusProvider._internal(
|
||||
(ref) => create(ref as AccountStatusRef),
|
||||
from: from,
|
||||
name: null,
|
||||
dependencies: null,
|
||||
allTransitiveDependencies: null,
|
||||
debugGetCreateSourceHash: null,
|
||||
uname: uname,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
AutoDisposeFutureProviderElement<SnAccountStatus?> createElement() {
|
||||
return _AccountStatusProviderElement(this);
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return other is AccountStatusProvider && other.uname == uname;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode {
|
||||
var hash = _SystemHash.combine(0, runtimeType.hashCode);
|
||||
hash = _SystemHash.combine(hash, uname.hashCode);
|
||||
|
||||
return _SystemHash.finish(hash);
|
||||
}
|
||||
}
|
||||
|
||||
@Deprecated('Will be removed in 3.0. Use Ref instead')
|
||||
// ignore: unused_element
|
||||
mixin AccountStatusRef on AutoDisposeFutureProviderRef<SnAccountStatus?> {
|
||||
/// The parameter `uname` of this provider.
|
||||
String get uname;
|
||||
}
|
||||
|
||||
class _AccountStatusProviderElement
|
||||
extends AutoDisposeFutureProviderElement<SnAccountStatus?>
|
||||
with AccountStatusRef {
|
||||
_AccountStatusProviderElement(super.provider);
|
||||
|
||||
@override
|
||||
String get uname => (origin as AccountStatusProvider).uname;
|
||||
}
|
||||
|
||||
// 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
|
249
lib/widgets/account/status_creation.dart
Normal file
249
lib/widgets/account/status_creation.dart
Normal file
@ -0,0 +1,249 @@
|
||||
import 'package:dio/dio.dart';
|
||||
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:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:island/models/user.dart';
|
||||
import 'package:island/pods/network.dart';
|
||||
import 'package:island/pods/userinfo.dart';
|
||||
import 'package:island/widgets/account/status.dart';
|
||||
import 'package:island/widgets/alert.dart';
|
||||
import 'package:material_symbols_icons/symbols.dart';
|
||||
|
||||
class AccountStatusCreationSheet extends HookConsumerWidget {
|
||||
final SnAccountStatus? initialStatus;
|
||||
const AccountStatusCreationSheet({super.key, this.initialStatus});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final attitude = useState<int>(initialStatus?.attitude ?? 1);
|
||||
final isInvisible = useState(initialStatus?.isInvisible ?? false);
|
||||
final isNotDisturb = useState(initialStatus?.isNotDisturb ?? false);
|
||||
final clearedAt = useState<DateTime?>(initialStatus?.clearedAt);
|
||||
final labelController = useTextEditingController(
|
||||
text: initialStatus?.label ?? '',
|
||||
);
|
||||
|
||||
final submitting = useState(false);
|
||||
|
||||
Future<void> clearStatus() async {
|
||||
try {
|
||||
submitting.value = true;
|
||||
final user = ref.watch(userInfoProvider);
|
||||
final apiClient = ref.read(apiClientProvider);
|
||||
await apiClient.delete('/accounts/me/statuses');
|
||||
if (!context.mounted) return;
|
||||
ref.invalidate(accountStatusProvider(user.value!.name));
|
||||
Navigator.pop(context);
|
||||
} catch (err) {
|
||||
showErrorAlert(err);
|
||||
} finally {
|
||||
submitting.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> submitStatus() async {
|
||||
try {
|
||||
submitting.value = true;
|
||||
final user = ref.watch(userInfoProvider);
|
||||
final apiClient = ref.read(apiClientProvider);
|
||||
await apiClient.request(
|
||||
'/accounts/me/statuses',
|
||||
data: {
|
||||
'attitude': attitude.value,
|
||||
'is_invisible': isInvisible.value,
|
||||
'is_not_disturb': isNotDisturb.value,
|
||||
'cleared_at': clearedAt.value?.toIso8601String(),
|
||||
if (labelController.text.isNotEmpty) 'label': labelController.text,
|
||||
},
|
||||
options: Options(method: initialStatus == null ? 'POST' : 'PATCH'),
|
||||
);
|
||||
if (user.hasValue) {
|
||||
ref.invalidate(accountStatusProvider(user.value!.name));
|
||||
}
|
||||
if (!context.mounted) return;
|
||||
Navigator.pop(context);
|
||||
} catch (err) {
|
||||
showErrorAlert(err);
|
||||
} finally {
|
||||
submitting.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
return Container(
|
||||
constraints: BoxConstraints(
|
||||
maxHeight: MediaQuery.of(context).size.height * 0.8,
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
Padding(
|
||||
padding: EdgeInsets.only(top: 16, left: 20, right: 16, bottom: 12),
|
||||
child: Row(
|
||||
children: [
|
||||
Text(
|
||||
initialStatus == null
|
||||
? 'statusCreate'.tr()
|
||||
: 'statusUpdate'.tr(),
|
||||
style: Theme.of(context).textTheme.headlineSmall?.copyWith(
|
||||
fontWeight: FontWeight.w600,
|
||||
letterSpacing: -0.5,
|
||||
),
|
||||
),
|
||||
const Spacer(),
|
||||
TextButton.icon(
|
||||
onPressed:
|
||||
submitting.value
|
||||
? null
|
||||
: () {
|
||||
submitStatus();
|
||||
},
|
||||
icon: const Icon(Symbols.upload),
|
||||
label: Text(initialStatus == null ? 'create' : 'update').tr(),
|
||||
style: ButtonStyle(
|
||||
visualDensity: VisualDensity(
|
||||
horizontal: VisualDensity.minimumDensity,
|
||||
),
|
||||
foregroundColor: WidgetStatePropertyAll(
|
||||
Theme.of(context).colorScheme.onSurface,
|
||||
),
|
||||
),
|
||||
),
|
||||
if (initialStatus != null)
|
||||
IconButton(
|
||||
icon: const Icon(Symbols.delete),
|
||||
onPressed: submitting.value ? null : () => clearStatus(),
|
||||
style: IconButton.styleFrom(
|
||||
minimumSize: const Size(36, 36),
|
||||
),
|
||||
),
|
||||
IconButton(
|
||||
icon: const Icon(Symbols.close),
|
||||
onPressed: () => Navigator.pop(context),
|
||||
style: IconButton.styleFrom(minimumSize: const Size(36, 36)),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const Divider(height: 1),
|
||||
Expanded(
|
||||
child: SingleChildScrollView(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 20),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const Gap(24),
|
||||
TextField(
|
||||
controller: labelController,
|
||||
decoration: InputDecoration(
|
||||
labelText: 'statusLabel'.tr(),
|
||||
border: const OutlineInputBorder(
|
||||
borderRadius: BorderRadius.all(Radius.circular(12)),
|
||||
),
|
||||
),
|
||||
onTapOutside:
|
||||
(_) => FocusManager.instance.primaryFocus?.unfocus(),
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
Text(
|
||||
'statusAttitude'.tr(),
|
||||
style: Theme.of(context).textTheme.titleMedium,
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
SegmentedButton(
|
||||
segments: [
|
||||
ButtonSegment(
|
||||
value: 0,
|
||||
icon: const Icon(Symbols.sentiment_satisfied),
|
||||
label: Text('attitudePositive'.tr()),
|
||||
),
|
||||
ButtonSegment(
|
||||
value: 1,
|
||||
icon: const Icon(Symbols.sentiment_stressed),
|
||||
label: Text('attitudeNeutral'.tr()),
|
||||
),
|
||||
ButtonSegment(
|
||||
value: 2,
|
||||
icon: const Icon(Symbols.sentiment_sad),
|
||||
label: Text('attitudeNegative'.tr()),
|
||||
),
|
||||
],
|
||||
selected: {attitude.value},
|
||||
onSelectionChanged: (Set<int> newSelection) {
|
||||
attitude.value = newSelection.first;
|
||||
},
|
||||
),
|
||||
const Gap(12),
|
||||
SwitchListTile(
|
||||
title: Text('statusInvisible'.tr()),
|
||||
subtitle: Text('statusInvisibleDescription'.tr()),
|
||||
value: isInvisible.value,
|
||||
contentPadding: EdgeInsets.symmetric(horizontal: 8),
|
||||
onChanged: (bool value) {
|
||||
isInvisible.value = value;
|
||||
},
|
||||
),
|
||||
SwitchListTile(
|
||||
title: Text('statusNotDisturb'.tr()),
|
||||
subtitle: Text('statusNotDisturbDescription'.tr()),
|
||||
value: isNotDisturb.value,
|
||||
contentPadding: EdgeInsets.symmetric(horizontal: 8),
|
||||
onChanged: (bool value) {
|
||||
isNotDisturb.value = value;
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
Text(
|
||||
'statusClearTime'.tr(),
|
||||
style: Theme.of(context).textTheme.titleMedium,
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
ListTile(
|
||||
title: Text(
|
||||
clearedAt.value == null
|
||||
? 'statusNoAutoClear'.tr()
|
||||
: DateFormat.yMMMd().add_jm().format(
|
||||
clearedAt.value!,
|
||||
),
|
||||
),
|
||||
trailing: const Icon(Symbols.schedule),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
side: BorderSide(
|
||||
color: Theme.of(context).colorScheme.outline,
|
||||
),
|
||||
),
|
||||
onTap: () async {
|
||||
final now = DateTime.now();
|
||||
final date = await showDatePicker(
|
||||
context: context,
|
||||
initialDate: now,
|
||||
firstDate: now,
|
||||
lastDate: now.add(const Duration(days: 365)),
|
||||
);
|
||||
if (date == null) return;
|
||||
if (!context.mounted) return;
|
||||
final time = await showTimePicker(
|
||||
context: context,
|
||||
initialTime: TimeOfDay.now(),
|
||||
);
|
||||
if (time == null) return;
|
||||
clearedAt.value = DateTime(
|
||||
date.year,
|
||||
date.month,
|
||||
date.day,
|
||||
time.hour,
|
||||
time.minute,
|
||||
);
|
||||
},
|
||||
),
|
||||
Gap(MediaQuery.of(context).padding.bottom + 24),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user