Solian/lib/screens/realms/realm_organize.dart

356 lines
11 KiB
Dart
Raw Permalink Normal View History

2024-05-28 14:13:23 +00:00
import 'package:flutter/material.dart';
import 'package:get/get.dart';
2024-09-17 06:21:37 +00:00
import 'package:image_cropper/image_cropper.dart';
import 'package:image_picker/image_picker.dart';
2024-05-28 14:13:23 +00:00
import 'package:solian/exts.dart';
2024-09-17 06:21:37 +00:00
import 'package:solian/models/attachment.dart';
2024-05-28 14:13:23 +00:00
import 'package:solian/models/realm.dart';
import 'package:solian/platform.dart';
2024-05-28 14:13:23 +00:00
import 'package:solian/providers/auth.dart';
2024-09-17 06:21:37 +00:00
import 'package:solian/providers/content/attachment.dart';
2024-05-28 14:13:23 +00:00
import 'package:solian/router.dart';
2024-06-22 15:59:11 +00:00
import 'package:solian/theme.dart';
import 'package:solian/widgets/app_bar_leading.dart';
2024-06-22 15:59:11 +00:00
import 'package:solian/widgets/app_bar_title.dart';
2024-10-14 13:13:57 +00:00
import 'package:solian/widgets/loading_indicator.dart';
2024-10-06 09:37:07 +00:00
import 'package:solian/widgets/root_container.dart';
2024-05-28 14:13:23 +00:00
import 'package:uuid/uuid.dart';
class RealmOrganizeArguments {
final Realm? edit;
RealmOrganizeArguments({this.edit});
}
class RealmOrganizeScreen extends StatefulWidget {
final Realm? edit;
const RealmOrganizeScreen({super.key, this.edit});
@override
State<RealmOrganizeScreen> createState() => _RealmOrganizeScreenState();
}
class _RealmOrganizeScreenState extends State<RealmOrganizeScreen> {
bool _isBusy = false;
final _aliasController = TextEditingController();
2024-09-17 06:21:37 +00:00
final _avatarController = TextEditingController();
final _bannerController = TextEditingController();
2024-05-28 14:13:23 +00:00
final _nameController = TextEditingController();
final _descriptionController = TextEditingController();
bool _isCommunity = false;
bool _isPublic = false;
2024-09-17 06:21:37 +00:00
void _applyRealm() async {
2024-05-28 14:13:23 +00:00
final AuthProvider auth = Get.find();
2024-07-24 17:18:47 +00:00
if (auth.isAuthorized.isFalse) return;
2024-05-28 14:13:23 +00:00
2024-09-17 06:21:37 +00:00
if (_aliasController.value.text.isEmpty) _randomizeAlias();
2024-05-28 14:13:23 +00:00
setState(() => _isBusy = true);
2024-09-16 03:57:16 +00:00
final client = await auth.configureClient('auth');
2024-05-28 14:13:23 +00:00
final payload = {
'alias': _aliasController.value.text.toLowerCase(),
'name': _nameController.value.text,
'description': _descriptionController.value.text,
2024-09-17 06:21:37 +00:00
'avatar': _avatarController.value.text,
'banner': _bannerController.value.text,
2024-05-28 14:13:23 +00:00
'is_public': _isPublic,
'is_community': _isCommunity,
};
Response resp;
if (widget.edit != null) {
resp = await client.put('/realms/${widget.edit!.id}', payload);
2024-05-28 14:13:23 +00:00
} else {
resp = await client.post('/realms', payload);
2024-05-28 14:13:23 +00:00
}
if (resp.statusCode != 200) {
context.showErrorDialog(resp.bodyString);
} else {
AppRouter.instance.pop(resp.body);
}
setState(() => _isBusy = false);
}
2024-09-17 06:21:37 +00:00
final _imagePicker = ImagePicker();
Future<void> _editImage(String position) async {
final AuthProvider auth = Get.find();
if (auth.isAuthorized.isFalse) return;
XFile file;
2024-09-17 06:21:37 +00:00
final image = await _imagePicker.pickImage(source: ImageSource.gallery);
if (image == null) return;
if (PlatformInfo.canCropImage) {
CroppedFile? croppedFile = await ImageCropper().cropImage(
sourcePath: image.path,
uiSettings: [
AndroidUiSettings(
toolbarTitle: 'cropImage'.tr,
toolbarColor: Theme.of(context).colorScheme.primary,
toolbarWidgetColor: Theme.of(context).colorScheme.onPrimary,
aspectRatioPresets: [
if (position == 'avatar') CropAspectRatioPreset.square,
if (position == 'banner') _BannerCropAspectRatioPreset(),
],
),
IOSUiSettings(
title: 'cropImage'.tr,
aspectRatioPresets: [
if (position == 'avatar') CropAspectRatioPreset.square,
if (position == 'banner') _BannerCropAspectRatioPreset(),
],
),
WebUiSettings(
context: context,
),
],
);
if (croppedFile == null) return;
file = XFile(croppedFile.path);
} else {
file = XFile(image.path);
}
2024-09-17 06:21:37 +00:00
setState(() => _isBusy = true);
final AttachmentProvider attach = Get.find();
Attachment? attachResult;
try {
attachResult = await attach.createAttachmentDirectly(
await file.readAsBytes(),
file.path,
'avatar',
null,
);
} catch (e) {
setState(() => _isBusy = false);
context.showErrorDialog(e);
return;
}
switch (position) {
case 'avatar':
_avatarController.text = attachResult.rid;
break;
case 'banner':
_bannerController.text = attachResult.rid;
break;
}
setState(() => _isBusy = false);
}
void _randomizeAlias() {
2024-05-28 14:13:23 +00:00
_aliasController.text =
const Uuid().v4().replaceAll('-', '').substring(0, 12);
}
2024-09-17 06:21:37 +00:00
void _syncWidget() {
2024-05-28 14:13:23 +00:00
if (widget.edit != null) {
_aliasController.text = widget.edit!.alias;
_nameController.text = widget.edit!.name;
_descriptionController.text = widget.edit!.description;
2024-09-17 06:21:37 +00:00
_avatarController.text = widget.edit!.avatar ?? '';
_bannerController.text = widget.edit!.banner ?? '';
2024-05-28 14:13:23 +00:00
_isPublic = widget.edit!.isPublic;
_isCommunity = widget.edit!.isCommunity;
}
}
2024-09-17 06:21:37 +00:00
void _cancelAction() {
2024-05-28 14:13:23 +00:00
AppRouter.instance.pop();
}
@override
void initState() {
2024-09-17 06:21:37 +00:00
_syncWidget();
2024-05-28 14:13:23 +00:00
super.initState();
}
2024-09-17 06:21:37 +00:00
@override
void dispose() {
_aliasController.dispose();
_avatarController.dispose();
_bannerController.dispose();
_nameController.dispose();
_descriptionController.dispose();
super.dispose();
}
2024-05-28 14:13:23 +00:00
@override
Widget build(BuildContext context) {
2024-10-06 09:37:07 +00:00
return RootContainer(
2024-05-28 14:13:23 +00:00
child: Scaffold(
appBar: AppBar(
2024-07-12 14:37:58 +00:00
leading: AppBarLeadingButton.adaptive(context),
2024-06-22 15:59:11 +00:00
title: AppBarTitle('realmOrganizing'.tr),
centerTitle: false,
toolbarHeight: AppTheme.toolbarHeight(context),
2024-05-28 14:13:23 +00:00
actions: [
TextButton(
2024-09-17 06:21:37 +00:00
onPressed: _isBusy ? null : () => _applyRealm(),
2024-05-28 14:13:23 +00:00
child: Text('apply'.tr.toUpperCase()),
)
],
),
body: SafeArea(
top: false,
child: Column(
children: [
2024-10-14 13:13:57 +00:00
LoadingIndicator(isActive: _isBusy),
2024-05-28 14:13:23 +00:00
if (widget.edit != null)
MaterialBanner(
leading: const Icon(Icons.edit),
leadingPadding: const EdgeInsets.only(left: 10, right: 20),
dividerColor: Colors.transparent,
content: Text(
'realmEditingNotify'
.trParams({'realm': '#${widget.edit!.alias}'}),
),
actions: [
TextButton(
2024-09-17 06:21:37 +00:00
onPressed: _cancelAction,
2024-05-28 14:13:23 +00:00
child: Text('cancel'.tr),
),
],
2024-05-29 16:05:39 +00:00
).paddingOnly(bottom: 6),
2024-05-28 14:13:23 +00:00
Row(
children: [
Expanded(
child: TextField(
autofocus: true,
controller: _aliasController,
decoration: InputDecoration.collapsed(
hintText: 'realmAlias'.tr,
),
onTapOutside: (_) =>
FocusManager.instance.primaryFocus?.unfocus(),
),
),
TextButton(
style: TextButton.styleFrom(
shape: const CircleBorder(),
visualDensity:
const VisualDensity(horizontal: -2, vertical: -2),
),
2024-09-17 06:21:37 +00:00
onPressed: () => _randomizeAlias(),
2024-05-28 14:13:23 +00:00
child: const Icon(Icons.refresh),
)
],
).paddingSymmetric(horizontal: 16, vertical: 2),
const Divider(thickness: 0.3),
TextField(
autocorrect: true,
controller: _nameController,
decoration: InputDecoration.collapsed(
hintText: 'realmName'.tr,
),
onTapOutside: (_) =>
FocusManager.instance.primaryFocus?.unfocus(),
).paddingSymmetric(horizontal: 16, vertical: 8),
const Divider(thickness: 0.3),
2024-09-17 06:21:37 +00:00
Row(
children: [
Expanded(
child: TextField(
autofocus: true,
controller: _avatarController,
decoration: InputDecoration.collapsed(
hintText: 'realmAvatar'.tr,
),
onTapOutside: (_) =>
FocusManager.instance.primaryFocus?.unfocus(),
),
),
TextButton(
style: TextButton.styleFrom(
shape: const CircleBorder(),
visualDensity:
const VisualDensity(horizontal: -2, vertical: -2),
),
onPressed: _isBusy ? null : () => _editImage('avatar'),
child: const Icon(Icons.upload),
)
],
).paddingSymmetric(horizontal: 16, vertical: 2),
Row(
children: [
Expanded(
child: TextField(
autofocus: true,
controller: _bannerController,
decoration: InputDecoration.collapsed(
hintText: 'realmBanner'.tr,
),
onTapOutside: (_) =>
FocusManager.instance.primaryFocus?.unfocus(),
),
),
TextButton(
style: TextButton.styleFrom(
shape: const CircleBorder(),
visualDensity:
const VisualDensity(horizontal: -2, vertical: -2),
),
onPressed: _isBusy ? null : () => _editImage('banner'),
child: const Icon(Icons.upload),
)
],
).paddingSymmetric(horizontal: 16, vertical: 2),
const Divider(thickness: 0.3),
2024-05-28 14:13:23 +00:00
Expanded(
child: TextField(
minLines: 5,
maxLines: null,
autocorrect: true,
keyboardType: TextInputType.multiline,
controller: _descriptionController,
decoration: InputDecoration.collapsed(
hintText: 'realmDescription'.tr,
),
onTapOutside: (_) =>
FocusManager.instance.primaryFocus?.unfocus(),
).paddingSymmetric(horizontal: 16, vertical: 12),
),
const Divider(thickness: 0.3),
CheckboxListTile(
title: Text('realmPublic'.tr),
value: _isPublic,
onChanged: (newValue) =>
setState(() => _isPublic = newValue ?? false),
controlAffinity: ListTileControlAffinity.leading,
),
CheckboxListTile(
title: Text('realmCommunity'.tr),
value: _isCommunity,
onChanged: (newValue) =>
setState(() => _isCommunity = newValue ?? false),
controlAffinity: ListTileControlAffinity.leading,
),
],
),
),
),
);
}
}
2024-09-17 06:21:37 +00:00
class _BannerCropAspectRatioPreset extends CropAspectRatioPresetData {
@override
(int, int)? get data => (16, 7);
@override
String get name => '16x7';
}