✨ Custom status
This commit is contained in:
parent
6caad19365
commit
6260e63b09
@ -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()
|
||||||
|
@ -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!);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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': '积极',
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -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,
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
@ -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 (val == true) {
|
||||||
|
Navigator.of(context).pop(true);
|
||||||
|
}
|
||||||
|
},
|
||||||
),
|
),
|
||||||
if (widget.hasStatus)
|
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),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user