Custom status

This commit is contained in:
LittleSheep 2024-06-27 11:44:27 +08:00
parent 6caad19365
commit 6260e63b09
5 changed files with 245 additions and 15 deletions

View File

@ -60,6 +60,7 @@ class StatusProvider extends GetConnect {
final payload = { final payload = {
'type': type, 'type': type,
'label': label, 'label': label,
'attitude': attitude,
'is_no_disturb': isSilent, 'is_no_disturb': isSilent,
'is_invisible': isInvisible, 'is_invisible': isInvisible,
'clear_at': clearAt?.toUtc().toIso8601String() 'clear_at': clearAt?.toUtc().toIso8601String()

View File

@ -44,7 +44,7 @@ class _PersonalizeScreenState extends State<PersonalizeScreen> {
if (picked != null && picked != _birthday) { if (picked != null && picked != _birthday) {
setState(() { setState(() {
_birthday = picked; _birthday = picked;
_birthdayController.text = DateFormat('yyyy-MM-dd').format(_birthday!); _birthdayController.text = DateFormat('y/M/d').format(_birthday!);
}); });
} }
} }

View File

@ -226,6 +226,11 @@ class SolianMessages extends Translations {
'accountStatusInvisibleDesc': 'Will show as offline, but all features still remain normal', 'accountStatusInvisibleDesc': 'Will show as offline, but all features still remain normal',
'accountStatusOffline': 'Offline', 'accountStatusOffline': 'Offline',
'accountLastSeenAt': 'Last seen at @date ago', 'accountLastSeenAt': 'Last seen at @date ago',
'accountStatusLabel': 'Status Text',
'accountStatusClearAt': 'Clear At',
'accountStatusNegative': 'Negative',
'accountStatusNeutral': 'Neutral',
'accountStatusPositive': 'Positive',
}, },
'zh_CN': { 'zh_CN': {
'hide': '隐藏', 'hide': '隐藏',
@ -435,6 +440,11 @@ class SolianMessages extends Translations {
'accountStatusInvisibleDesc': '将会在他人界面显示离线,但不影响功能使用', 'accountStatusInvisibleDesc': '将会在他人界面显示离线,但不影响功能使用',
'accountStatusOffline': '离线', 'accountStatusOffline': '离线',
'accountLastSeenAt': '最后上线于 @date 前', 'accountLastSeenAt': '最后上线于 @date 前',
'accountStatusLabel': '状态文字',
'accountStatusClearAt': '清除状态于',
'accountStatusNegative': '负面',
'accountStatusNeutral': '中性',
'accountStatusPositive': '积极',
} }
}; };
} }

View File

@ -32,13 +32,13 @@ class AccountHeadingWidget extends StatelessWidget {
this.onEditStatus, this.onEditStatus,
}); });
void showStatusAction(BuildContext context, bool hasStatus) { void showStatusAction(BuildContext context, Status? current) {
if (onEditStatus == null) return; if (onEditStatus == null) return;
showModalBottomSheet( showModalBottomSheet(
useRootNavigator: true, useRootNavigator: true,
context: context, context: context,
builder: (context) => AccountStatusAction(hasStatus: hasStatus), builder: (context) => AccountStatusAction(currentStatus: current),
).then((val) { ).then((val) {
if (val == true) onEditStatus!(); if (val == true) onEditStatus!();
}); });
@ -129,10 +129,7 @@ class AccountHeadingWidget extends StatelessWidget {
], ],
), ),
onTap: () { onTap: () {
showStatusAction( showStatusAction(context, status.status);
context,
snapshot.data!.body['status'] != null,
);
}, },
); );
}, },

View File

@ -1,13 +1,15 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_animate/flutter_animate.dart'; import 'package:flutter_animate/flutter_animate.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:intl/intl.dart';
import 'package:solian/exts.dart'; import 'package:solian/exts.dart';
import 'package:solian/models/account_status.dart';
import 'package:solian/providers/account_status.dart'; import 'package:solian/providers/account_status.dart';
class AccountStatusAction extends StatefulWidget { class AccountStatusAction extends StatefulWidget {
final bool hasStatus; final Status? currentStatus;
const AccountStatusAction({super.key, this.hasStatus = false}); const AccountStatusAction({super.key, this.currentStatus});
@override @override
State<AccountStatusAction> createState() => _AccountStatusActionState(); State<AccountStatusAction> createState() => _AccountStatusActionState();
@ -18,7 +20,7 @@ class _AccountStatusActionState extends State<AccountStatusAction> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final StatusProvider controller = Get.find(); final StatusProvider provider = Get.find();
return SizedBox( return SizedBox(
child: Column( child: Column(
@ -48,7 +50,7 @@ class _AccountStatusActionState extends State<AccountStatusAction> {
: () async { : () async {
setState(() => _isBusy = true); setState(() => _isBusy = true);
try { try {
await controller.setStatus( await provider.setStatus(
x.key, x.key,
x.value.$2, x.value.$2,
0, 0,
@ -70,13 +72,25 @@ class _AccountStatusActionState extends State<AccountStatusAction> {
.paddingSymmetric(vertical: 16), .paddingSymmetric(vertical: 16),
ListTile( ListTile(
contentPadding: const EdgeInsets.symmetric(horizontal: 24), contentPadding: const EdgeInsets.symmetric(horizontal: 24),
leading: widget.hasStatus leading: widget.currentStatus != null
? const Icon(Icons.edit) ? const Icon(Icons.edit)
: const Icon(Icons.add), : const Icon(Icons.add),
title: Text('accountCustomStatus'.tr), 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( ListTile(
contentPadding: const EdgeInsets.symmetric(horizontal: 24), contentPadding: const EdgeInsets.symmetric(horizontal: 24),
leading: const Icon(Icons.clear), leading: const Icon(Icons.clear),
@ -86,7 +100,7 @@ class _AccountStatusActionState extends State<AccountStatusAction> {
: () async { : () async {
setState(() => _isBusy = true); setState(() => _isBusy = true);
try { try {
await controller.clearStatus(); await provider.clearStatus();
Navigator.of(context).pop(true); Navigator.of(context).pop(true);
} catch (e) { } catch (e) {
context.showErrorDialog(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),
),
],
);
}
}