✨ Custom status
This commit is contained in:
parent
6caad19365
commit
6260e63b09
@ -60,6 +60,7 @@ class StatusProvider extends GetConnect {
|
||||
final payload = {
|
||||
'type': type,
|
||||
'label': label,
|
||||
'attitude': attitude,
|
||||
'is_no_disturb': isSilent,
|
||||
'is_invisible': isInvisible,
|
||||
'clear_at': clearAt?.toUtc().toIso8601String()
|
||||
|
@ -44,7 +44,7 @@ class _PersonalizeScreenState extends State<PersonalizeScreen> {
|
||||
if (picked != null && picked != _birthday) {
|
||||
setState(() {
|
||||
_birthday = picked;
|
||||
_birthdayController.text = DateFormat('yyyy-MM-dd').format(_birthday!);
|
||||
_birthdayController.text = DateFormat('y/M/d').format(_birthday!);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -226,6 +226,11 @@ class SolianMessages extends Translations {
|
||||
'accountStatusInvisibleDesc': 'Will show as offline, but all features still remain normal',
|
||||
'accountStatusOffline': 'Offline',
|
||||
'accountLastSeenAt': 'Last seen at @date ago',
|
||||
'accountStatusLabel': 'Status Text',
|
||||
'accountStatusClearAt': 'Clear At',
|
||||
'accountStatusNegative': 'Negative',
|
||||
'accountStatusNeutral': 'Neutral',
|
||||
'accountStatusPositive': 'Positive',
|
||||
},
|
||||
'zh_CN': {
|
||||
'hide': '隐藏',
|
||||
@ -435,6 +440,11 @@ class SolianMessages extends Translations {
|
||||
'accountStatusInvisibleDesc': '将会在他人界面显示离线,但不影响功能使用',
|
||||
'accountStatusOffline': '离线',
|
||||
'accountLastSeenAt': '最后上线于 @date 前',
|
||||
'accountStatusLabel': '状态文字',
|
||||
'accountStatusClearAt': '清除状态于',
|
||||
'accountStatusNegative': '负面',
|
||||
'accountStatusNeutral': '中性',
|
||||
'accountStatusPositive': '积极',
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -32,13 +32,13 @@ class AccountHeadingWidget extends StatelessWidget {
|
||||
this.onEditStatus,
|
||||
});
|
||||
|
||||
void showStatusAction(BuildContext context, bool hasStatus) {
|
||||
void showStatusAction(BuildContext context, Status? current) {
|
||||
if (onEditStatus == null) return;
|
||||
|
||||
showModalBottomSheet(
|
||||
useRootNavigator: true,
|
||||
context: context,
|
||||
builder: (context) => AccountStatusAction(hasStatus: hasStatus),
|
||||
builder: (context) => AccountStatusAction(currentStatus: current),
|
||||
).then((val) {
|
||||
if (val == true) onEditStatus!();
|
||||
});
|
||||
@ -129,10 +129,7 @@ class AccountHeadingWidget extends StatelessWidget {
|
||||
],
|
||||
),
|
||||
onTap: () {
|
||||
showStatusAction(
|
||||
context,
|
||||
snapshot.data!.body['status'] != null,
|
||||
);
|
||||
showStatusAction(context, status.status);
|
||||
},
|
||||
);
|
||||
},
|
||||
|
@ -1,13 +1,15 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_animate/flutter_animate.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:solian/exts.dart';
|
||||
import 'package:solian/models/account_status.dart';
|
||||
import 'package:solian/providers/account_status.dart';
|
||||
|
||||
class AccountStatusAction extends StatefulWidget {
|
||||
final bool hasStatus;
|
||||
final Status? currentStatus;
|
||||
|
||||
const AccountStatusAction({super.key, this.hasStatus = false});
|
||||
const AccountStatusAction({super.key, this.currentStatus});
|
||||
|
||||
@override
|
||||
State<AccountStatusAction> createState() => _AccountStatusActionState();
|
||||
@ -18,7 +20,7 @@ class _AccountStatusActionState extends State<AccountStatusAction> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final StatusProvider controller = Get.find();
|
||||
final StatusProvider provider = Get.find();
|
||||
|
||||
return SizedBox(
|
||||
child: Column(
|
||||
@ -48,7 +50,7 @@ class _AccountStatusActionState extends State<AccountStatusAction> {
|
||||
: () async {
|
||||
setState(() => _isBusy = true);
|
||||
try {
|
||||
await controller.setStatus(
|
||||
await provider.setStatus(
|
||||
x.key,
|
||||
x.value.$2,
|
||||
0,
|
||||
@ -70,13 +72,25 @@ class _AccountStatusActionState extends State<AccountStatusAction> {
|
||||
.paddingSymmetric(vertical: 16),
|
||||
ListTile(
|
||||
contentPadding: const EdgeInsets.symmetric(horizontal: 24),
|
||||
leading: widget.hasStatus
|
||||
leading: widget.currentStatus != null
|
||||
? const Icon(Icons.edit)
|
||||
: const Icon(Icons.add),
|
||||
title: Text('accountCustomStatus'.tr),
|
||||
onTap: _isBusy ? null : () {},
|
||||
onTap: _isBusy
|
||||
? null
|
||||
: () async {
|
||||
final val = await showDialog(
|
||||
context: context,
|
||||
builder: (context) => AccountStatusEditorDialog(
|
||||
currentStatus: widget.currentStatus,
|
||||
),
|
||||
if (widget.hasStatus)
|
||||
);
|
||||
if (val == true) {
|
||||
Navigator.of(context).pop(true);
|
||||
}
|
||||
},
|
||||
),
|
||||
if (widget.currentStatus != null)
|
||||
ListTile(
|
||||
contentPadding: const EdgeInsets.symmetric(horizontal: 24),
|
||||
leading: const Icon(Icons.clear),
|
||||
@ -86,7 +100,7 @@ class _AccountStatusActionState extends State<AccountStatusAction> {
|
||||
: () async {
|
||||
setState(() => _isBusy = true);
|
||||
try {
|
||||
await controller.clearStatus();
|
||||
await provider.clearStatus();
|
||||
Navigator.of(context).pop(true);
|
||||
} catch (e) {
|
||||
context.showErrorDialog(e);
|
||||
@ -99,3 +113,211 @@ class _AccountStatusActionState extends State<AccountStatusAction> {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class AccountStatusEditorDialog extends StatefulWidget {
|
||||
final Status? currentStatus;
|
||||
|
||||
const AccountStatusEditorDialog({super.key, this.currentStatus});
|
||||
|
||||
@override
|
||||
State<AccountStatusEditorDialog> createState() =>
|
||||
_AccountStatusEditorDialogState();
|
||||
}
|
||||
|
||||
class _AccountStatusEditorDialogState extends State<AccountStatusEditorDialog> {
|
||||
bool _isBusy = false;
|
||||
|
||||
int _attitude = 0;
|
||||
DateTime? _clearAt;
|
||||
bool _isInvisible = false;
|
||||
bool _isSlient = false;
|
||||
|
||||
final _labelController = TextEditingController();
|
||||
final _clearAtController = TextEditingController();
|
||||
|
||||
void selectClearAt() async {
|
||||
final DateTime? pickedDate = await showDatePicker(
|
||||
context: context,
|
||||
initialDate: _clearAt?.toLocal() ?? DateTime.now(),
|
||||
firstDate: DateTime.now(),
|
||||
lastDate: DateTime.now().add(const Duration(days: 365)),
|
||||
);
|
||||
if (pickedDate == null) return;
|
||||
final TimeOfDay? pickedTime = await showTimePicker(
|
||||
context: context,
|
||||
initialTime: TimeOfDay.now(),
|
||||
);
|
||||
if (pickedTime == null) return;
|
||||
|
||||
final picked = pickedDate.copyWith(
|
||||
hour: pickedTime.hour,
|
||||
minute: pickedTime.minute,
|
||||
);
|
||||
setState(() {
|
||||
_clearAt = picked;
|
||||
_clearAtController.text = DateFormat('y/M/d HH:mm').format(_clearAt!);
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> applyStatus() async {
|
||||
final StatusProvider provider = Get.find();
|
||||
|
||||
setState(() => _isBusy = true);
|
||||
try {
|
||||
await provider.setStatus(
|
||||
'custom',
|
||||
_labelController.value.text,
|
||||
_attitude,
|
||||
clearAt: _clearAt,
|
||||
isSilent: _isSlient,
|
||||
isInvisible: _isInvisible,
|
||||
isUpdate: widget.currentStatus != null,
|
||||
);
|
||||
Navigator.pop(context, true);
|
||||
} catch (e) {
|
||||
context.showErrorDialog(e);
|
||||
}
|
||||
setState(() => _isBusy = false);
|
||||
}
|
||||
|
||||
void syncWidget() {
|
||||
if (widget.currentStatus != null) {
|
||||
_clearAt = widget.currentStatus!.clearAt;
|
||||
_clearAtController.text = DateFormat('y/M/d HH:mm').format(_clearAt!);
|
||||
_labelController.text = widget.currentStatus!.label;
|
||||
_attitude = widget.currentStatus!.attitude;
|
||||
_isInvisible = widget.currentStatus!.isInvisible;
|
||||
_isSlient = widget.currentStatus!.isNoDisturb;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
syncWidget();
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return AlertDialog(
|
||||
title: Text('accountCustomStatus'.tr),
|
||||
content: Container(
|
||||
constraints: const BoxConstraints(minWidth: 400),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
if (_isBusy)
|
||||
ClipRRect(
|
||||
borderRadius: const BorderRadius.all(Radius.circular(8)),
|
||||
child: const LinearProgressIndicator().animate().scaleX(),
|
||||
),
|
||||
const SizedBox(height: 18),
|
||||
TextField(
|
||||
controller: _labelController,
|
||||
decoration: InputDecoration(
|
||||
isDense: true,
|
||||
prefixIcon: const Icon(Icons.label),
|
||||
border: const OutlineInputBorder(),
|
||||
labelText: 'accountStatusLabel'.tr,
|
||||
),
|
||||
onTapOutside: (_) =>
|
||||
FocusManager.instance.primaryFocus?.unfocus(),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
TextField(
|
||||
controller: _clearAtController,
|
||||
readOnly: true,
|
||||
decoration: InputDecoration(
|
||||
isDense: true,
|
||||
prefixIcon: const Icon(Icons.event_busy),
|
||||
border: const OutlineInputBorder(),
|
||||
labelText: 'accountStatusClearAt'.tr,
|
||||
),
|
||||
onTap: () => selectClearAt(),
|
||||
),
|
||||
const SizedBox(height: 5),
|
||||
SingleChildScrollView(
|
||||
scrollDirection: Axis.horizontal,
|
||||
child: Wrap(
|
||||
spacing: 6,
|
||||
runSpacing: 0,
|
||||
children: [
|
||||
ChoiceChip(
|
||||
avatar: Icon(
|
||||
Icons.radio_button_unchecked,
|
||||
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
),
|
||||
selected: _attitude == 2,
|
||||
label: Text('accountStatusNegative'.tr),
|
||||
onSelected: (val) {
|
||||
if (val) setState(() => _attitude = 2);
|
||||
},
|
||||
),
|
||||
ChoiceChip(
|
||||
avatar: Icon(
|
||||
Icons.contrast,
|
||||
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
),
|
||||
selected: _attitude == 0,
|
||||
label: Text('accountStatusNeutral'.tr),
|
||||
onSelected: (val) {
|
||||
if (val) setState(() => _attitude = 0);
|
||||
},
|
||||
),
|
||||
ChoiceChip(
|
||||
avatar: Icon(
|
||||
Icons.circle,
|
||||
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
),
|
||||
selected: _attitude == 1,
|
||||
label: Text('accountStatusPositive'.tr),
|
||||
onSelected: (val) {
|
||||
if (val) setState(() => _attitude = 1);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
SingleChildScrollView(
|
||||
scrollDirection: Axis.horizontal,
|
||||
child: Wrap(
|
||||
spacing: 6,
|
||||
runSpacing: 0,
|
||||
children: [
|
||||
ChoiceChip(
|
||||
selected: _isSlient,
|
||||
label: Text('accountStatusSilent'.tr),
|
||||
onSelected: (val) {
|
||||
setState(() => _isSlient = val);
|
||||
},
|
||||
),
|
||||
ChoiceChip(
|
||||
selected: _isInvisible,
|
||||
label: Text('accountStatusInvisible'.tr),
|
||||
onSelected: (val) {
|
||||
setState(() => _isInvisible = val);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
actions: <Widget>[
|
||||
TextButton(
|
||||
style: TextButton.styleFrom(
|
||||
foregroundColor:
|
||||
Theme.of(context).colorScheme.onSurface.withOpacity(0.8),
|
||||
),
|
||||
onPressed: _isBusy ? null : () => Navigator.pop(context),
|
||||
child: Text('cancel'.tr),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: _isBusy ? null : () => applyStatus(),
|
||||
child: Text('apply'.tr),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user