Compare commits

...

2 Commits

Author SHA1 Message Date
6260e63b09 Custom status 2024-06-27 11:44:27 +08:00
6caad19365 🎨 Change naming's way 2024-06-27 11:05:15 +08:00
8 changed files with 258 additions and 28 deletions

View File

@@ -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()

View File

@@ -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!);
});
}
}

View File

@@ -51,7 +51,7 @@ class _PostPublishingScreenState extends State<PostPublishingScreen> {
void showAttachments() {
showModalBottomSheet(
context: context,
builder: (context) => AttachmentPublishingPopup(
builder: (context) => AttachmentPublishPopup(
usage: 'i.attachment',
current: _attachments,
onUpdate: (value) => _attachments = value,

View File

@@ -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': '积极',
}
};
}

View File

@@ -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);
},
);
},

View File

@@ -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 (val == true) {
Navigator.of(context).pop(true);
}
},
),
if (widget.hasStatus)
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),
),
],
);
}
}

View File

@@ -11,12 +11,12 @@ import 'package:solian/models/attachment.dart';
import 'package:solian/providers/auth.dart';
import 'package:solian/providers/content/attachment.dart';
class AttachmentPublishingPopup extends StatefulWidget {
class AttachmentPublishPopup extends StatefulWidget {
final String usage;
final List<int> current;
final void Function(List<int> data) onUpdate;
const AttachmentPublishingPopup({
const AttachmentPublishPopup({
super.key,
required this.usage,
required this.current,
@@ -24,11 +24,11 @@ class AttachmentPublishingPopup extends StatefulWidget {
});
@override
State<AttachmentPublishingPopup> createState() =>
_AttachmentPublishingPopupState();
State<AttachmentPublishPopup> createState() =>
_AttachmentPublishPopupState();
}
class _AttachmentPublishingPopupState extends State<AttachmentPublishingPopup> {
class _AttachmentPublishPopupState extends State<AttachmentPublishPopup> {
final _imagePicker = ImagePicker();
bool _isBusy = false;
@@ -273,7 +273,7 @@ class _AttachmentPublishingPopupState extends State<AttachmentPublishingPopup> {
showDialog(
context: context,
builder: (context) {
return AttachmentEditingDialog(
return AttachmentEditorDialog(
item: element,
onDelete: () {
setState(
@@ -353,23 +353,23 @@ class _AttachmentPublishingPopupState extends State<AttachmentPublishingPopup> {
}
}
class AttachmentEditingDialog extends StatefulWidget {
class AttachmentEditorDialog extends StatefulWidget {
final Attachment item;
final Function onDelete;
final Function(Attachment item) onUpdate;
const AttachmentEditingDialog(
const AttachmentEditorDialog(
{super.key,
required this.item,
required this.onDelete,
required this.onUpdate});
@override
State<AttachmentEditingDialog> createState() =>
_AttachmentEditingDialogState();
State<AttachmentEditorDialog> createState() =>
_AttachmentEditorDialogState();
}
class _AttachmentEditingDialogState extends State<AttachmentEditingDialog> {
class _AttachmentEditorDialogState extends State<AttachmentEditorDialog> {
final _ratioController = TextEditingController();
final _altController = TextEditingController();

View File

@@ -46,7 +46,7 @@ class _ChatMessageInputState extends State<ChatMessageInput> {
void showAttachments() {
showModalBottomSheet(
context: context,
builder: (context) => AttachmentPublishingPopup(
builder: (context) => AttachmentPublishPopup(
usage: 'm.attachment',
current: _attachments,
onUpdate: (value) => _attachments = value,