✨ Publisher personal & organization management
This commit is contained in:
parent
fc025c6bd3
commit
000caf4dd2
@ -99,6 +99,8 @@
|
||||
"publishersNew": "New Publisher",
|
||||
"publisherNewSubtitle": "Create a new publisher identity.",
|
||||
"publisherSyncWithAccount": "Sync with account",
|
||||
"fieldPublisherBelongToRealm": "Belongs to",
|
||||
"fieldPublisherBelongToRealmUnset": "Unset Publisher Belongs to Realm",
|
||||
"writePostTypeStory": "Post a story",
|
||||
"writePostTypeArticle": "Write an article",
|
||||
"fieldPostPublisher": "Post publisher",
|
||||
|
@ -99,6 +99,8 @@
|
||||
"publishersNew": "新发布者",
|
||||
"publisherNewSubtitle": "创建一个新的公共身份。",
|
||||
"publisherSyncWithAccount": "同步账户信息",
|
||||
"fieldPublisherBelongToRealm": "所属领域",
|
||||
"fieldPublisherBelongToRealmUnset": "未设置发布者所属领域",
|
||||
"writePostTypeStory": "发动态",
|
||||
"writePostTypeArticle": "写文章",
|
||||
"fieldPostPublisher": "帖子发布者",
|
||||
|
@ -267,11 +267,14 @@ class _AccountPublisherEditScreenState
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
TextButton.icon(
|
||||
onPressed: _syncWithAccount,
|
||||
label: Text('publisherSyncWithAccount').tr(),
|
||||
icon: const Icon(Symbols.sync),
|
||||
),
|
||||
if (_publisher?.type == 0)
|
||||
TextButton.icon(
|
||||
onPressed: _syncWithAccount,
|
||||
label: Text('publisherSyncWithAccount').tr(),
|
||||
icon: const Icon(Symbols.sync),
|
||||
)
|
||||
else
|
||||
const SizedBox(),
|
||||
ElevatedButton.icon(
|
||||
onPressed: _isBusy ? null : _performAction,
|
||||
label: Text('apply').tr(),
|
||||
|
@ -1,3 +1,4 @@
|
||||
import 'package:dropdown_button2/dropdown_button2.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:gap/gap.dart';
|
||||
@ -6,6 +7,7 @@ import 'package:provider/provider.dart';
|
||||
import 'package:styled_widget/styled_widget.dart';
|
||||
import 'package:surface/providers/sn_network.dart';
|
||||
import 'package:surface/providers/userinfo.dart';
|
||||
import 'package:surface/types/realm.dart';
|
||||
import 'package:surface/widgets/account/account_image.dart';
|
||||
import 'package:surface/widgets/dialog.dart';
|
||||
|
||||
@ -47,6 +49,7 @@ class _AccountPublisherNewScreenState extends State<AccountPublisherNewScreen> {
|
||||
),
|
||||
switch (mode) {
|
||||
'personal' => const _PublisherNewPersonal(),
|
||||
'organization' => const _PublisherNewOrganization(),
|
||||
_ => const Placeholder(),
|
||||
},
|
||||
],
|
||||
@ -66,6 +69,10 @@ class _PublisherNewPersonal extends StatefulWidget {
|
||||
class _PublisherNewPersonalState extends State<_PublisherNewPersonal> {
|
||||
bool _isBusy = false;
|
||||
|
||||
final TextEditingController _nameController = TextEditingController();
|
||||
final TextEditingController _nickController = TextEditingController();
|
||||
final TextEditingController _descriptionController = TextEditingController();
|
||||
|
||||
void _performAction() async {
|
||||
final sn = context.read<SnNetworkProvider>();
|
||||
final ua = context.read<UserProvider>();
|
||||
@ -74,15 +81,48 @@ class _PublisherNewPersonalState extends State<_PublisherNewPersonal> {
|
||||
setState(() => _isBusy = true);
|
||||
|
||||
try {
|
||||
await sn.client.post('/cgi/co/publishers/personal');
|
||||
await sn.client.post('/cgi/co/publishers/personal', data: {
|
||||
'name': _nameController.text,
|
||||
'nick': _nickController.text,
|
||||
'description': _descriptionController.text,
|
||||
'avatar': ua.user!.avatar,
|
||||
'banner': ua.user!.banner,
|
||||
});
|
||||
if (!mounted) return;
|
||||
Navigator.pop(context, true);
|
||||
} catch (err) {
|
||||
if (!mounted) return;
|
||||
context.showErrorDialog(err);
|
||||
} finally {
|
||||
setState(() => _isBusy = false);
|
||||
}
|
||||
}
|
||||
|
||||
void _syncState() {
|
||||
final ua = context.read<UserProvider>();
|
||||
if (ua.user == null) return;
|
||||
|
||||
_nameController.text = ua.user!.name;
|
||||
_nickController.text = ua.user!.nick;
|
||||
_descriptionController.text = ua.user!.description;
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_syncState();
|
||||
_nameController.addListener(() => setState(() => {}));
|
||||
_nickController.addListener(() => setState(() => {}));
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
super.dispose();
|
||||
_nameController.dispose();
|
||||
_nickController.dispose();
|
||||
_descriptionController.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final ua = context.watch<UserProvider>();
|
||||
@ -90,10 +130,41 @@ class _PublisherNewPersonalState extends State<_PublisherNewPersonal> {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text('preview')
|
||||
.tr()
|
||||
.textStyle(Theme.of(context).textTheme.titleMedium!)
|
||||
.padding(horizontal: 16, vertical: 4),
|
||||
Column(
|
||||
children: [
|
||||
TextField(
|
||||
controller: _nameController,
|
||||
decoration: InputDecoration(
|
||||
labelText: 'fieldUsername'.tr(),
|
||||
helperText: 'fieldUsernameCannotEditHint'.tr(),
|
||||
helperMaxLines: 2,
|
||||
),
|
||||
onTapOutside: (_) =>
|
||||
FocusManager.instance.primaryFocus?.unfocus(),
|
||||
),
|
||||
const Gap(4),
|
||||
TextField(
|
||||
controller: _nickController,
|
||||
decoration: InputDecoration(
|
||||
labelText: 'fieldNickname'.tr(),
|
||||
),
|
||||
onTapOutside: (_) =>
|
||||
FocusManager.instance.primaryFocus?.unfocus(),
|
||||
),
|
||||
const Gap(4),
|
||||
TextField(
|
||||
controller: _descriptionController,
|
||||
minLines: 3,
|
||||
maxLines: null,
|
||||
decoration: InputDecoration(
|
||||
labelText: 'fieldDescription'.tr(),
|
||||
),
|
||||
onTapOutside: (_) =>
|
||||
FocusManager.instance.primaryFocus?.unfocus(),
|
||||
),
|
||||
],
|
||||
).padding(horizontal: 8),
|
||||
const Gap(16),
|
||||
Card(
|
||||
child: SizedBox(
|
||||
width: double.infinity,
|
||||
@ -105,10 +176,254 @@ class _PublisherNewPersonalState extends State<_PublisherNewPersonal> {
|
||||
crossAxisAlignment: CrossAxisAlignment.baseline,
|
||||
textBaseline: TextBaseline.alphabetic,
|
||||
children: [
|
||||
Text(ua.user!.nick)
|
||||
Text(_nickController.text)
|
||||
.textStyle(Theme.of(context).textTheme.titleLarge!),
|
||||
const Gap(4),
|
||||
Text('@${ua.user!.name}')
|
||||
Text('@${_nameController.text}')
|
||||
.textStyle(Theme.of(context).textTheme.bodySmall!),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
).padding(all: 16),
|
||||
),
|
||||
SizedBox(
|
||||
width: double.infinity,
|
||||
child: ElevatedButton.icon(
|
||||
onPressed: _isBusy ? null : _performAction,
|
||||
icon: const Icon(Symbols.add),
|
||||
label: Text('create').tr(),
|
||||
),
|
||||
).padding(horizontal: 2),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _PublisherNewOrganization extends StatefulWidget {
|
||||
const _PublisherNewOrganization({super.key});
|
||||
|
||||
@override
|
||||
State<_PublisherNewOrganization> createState() =>
|
||||
_PublisherNewOrganizationState();
|
||||
}
|
||||
|
||||
class _PublisherNewOrganizationState extends State<_PublisherNewOrganization> {
|
||||
bool _isBusy = false;
|
||||
|
||||
final TextEditingController _nameController = TextEditingController();
|
||||
final TextEditingController _nickController = TextEditingController();
|
||||
final TextEditingController _descriptionController = TextEditingController();
|
||||
|
||||
void _performAction() async {
|
||||
final sn = context.read<SnNetworkProvider>();
|
||||
final ua = context.read<UserProvider>();
|
||||
if (!ua.isAuthorized) return;
|
||||
if (_belongToRealm == null) return;
|
||||
|
||||
setState(() => _isBusy = true);
|
||||
|
||||
try {
|
||||
await sn.client.post('/cgi/co/publishers/organization', data: {
|
||||
'realm': _belongToRealm!.alias,
|
||||
'name': _nameController.text,
|
||||
'nick': _nickController.text,
|
||||
'description': _descriptionController.text,
|
||||
'avatar': _belongToRealm!.avatar,
|
||||
'banner': _belongToRealm!.banner,
|
||||
});
|
||||
if (!mounted) return;
|
||||
Navigator.pop(context, true);
|
||||
} catch (err) {
|
||||
if (!mounted) return;
|
||||
context.showErrorDialog(err);
|
||||
} finally {
|
||||
setState(() => _isBusy = false);
|
||||
}
|
||||
}
|
||||
|
||||
List<SnRealm>? _realms;
|
||||
SnRealm? _belongToRealm;
|
||||
|
||||
Future<void> _fetchRealms() async {
|
||||
setState(() => _isBusy = true);
|
||||
try {
|
||||
final sn = context.read<SnNetworkProvider>();
|
||||
final resp = await sn.client.get('/cgi/id/realms/me/available');
|
||||
_realms = List<SnRealm>.from(
|
||||
resp.data?.map((e) => SnRealm.fromJson(e)) ?? [],
|
||||
);
|
||||
} catch (err) {
|
||||
if (mounted) context.showErrorDialog(err);
|
||||
} finally {
|
||||
setState(() => _isBusy = false);
|
||||
}
|
||||
}
|
||||
|
||||
void _syncState() {
|
||||
if (_belongToRealm == null) return;
|
||||
|
||||
_nameController.text = _belongToRealm!.alias;
|
||||
_nickController.text = _belongToRealm!.name;
|
||||
_descriptionController.text = _belongToRealm!.description;
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_fetchRealms();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
super.dispose();
|
||||
_nameController.dispose();
|
||||
_nickController.dispose();
|
||||
_descriptionController.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
children: [
|
||||
DropdownButtonHideUnderline(
|
||||
child: DropdownButton2<SnRealm>(
|
||||
isExpanded: true,
|
||||
hint: Text(
|
||||
'fieldPublisherBelongToRealm'.tr(),
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).hintColor,
|
||||
),
|
||||
),
|
||||
items: [
|
||||
...(_realms?.map(
|
||||
(SnRealm item) => DropdownMenuItem<SnRealm>(
|
||||
value: item,
|
||||
child: Row(
|
||||
children: [
|
||||
AccountImage(
|
||||
content: item.avatar,
|
||||
radius: 16,
|
||||
fallbackWidget: const Icon(
|
||||
Symbols.group,
|
||||
size: 16,
|
||||
),
|
||||
),
|
||||
const Gap(12),
|
||||
Expanded(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(item.name).textStyle(
|
||||
Theme.of(context).textTheme.bodyMedium!),
|
||||
Text(
|
||||
item.description,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
).textStyle(
|
||||
Theme.of(context).textTheme.bodySmall!),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
) ??
|
||||
[]),
|
||||
DropdownMenuItem<SnRealm>(
|
||||
value: null,
|
||||
child: Row(
|
||||
children: [
|
||||
CircleAvatar(
|
||||
radius: 16,
|
||||
backgroundColor: Colors.transparent,
|
||||
foregroundColor: Theme.of(context).colorScheme.onSurface,
|
||||
child: const Icon(Symbols.clear),
|
||||
),
|
||||
const Gap(12),
|
||||
Expanded(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text('fieldPublisherBelongToRealmUnset')
|
||||
.tr()
|
||||
.textStyle(
|
||||
Theme.of(context).textTheme.bodyMedium!,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
value: _belongToRealm,
|
||||
onChanged: (SnRealm? value) {
|
||||
_belongToRealm = value;
|
||||
_syncState();
|
||||
setState(() {});
|
||||
},
|
||||
buttonStyleData: const ButtonStyleData(
|
||||
padding: EdgeInsets.only(right: 16),
|
||||
height: 60,
|
||||
),
|
||||
menuItemStyleData: const MenuItemStyleData(
|
||||
height: 60,
|
||||
),
|
||||
),
|
||||
),
|
||||
Column(
|
||||
children: [
|
||||
TextField(
|
||||
controller: _nameController,
|
||||
decoration: InputDecoration(
|
||||
labelText: 'fieldUsername'.tr(),
|
||||
helperText: 'fieldUsernameCannotEditHint'.tr(),
|
||||
helperMaxLines: 2,
|
||||
),
|
||||
onTapOutside: (_) =>
|
||||
FocusManager.instance.primaryFocus?.unfocus(),
|
||||
),
|
||||
const Gap(4),
|
||||
TextField(
|
||||
controller: _nickController,
|
||||
decoration: InputDecoration(
|
||||
labelText: 'fieldNickname'.tr(),
|
||||
),
|
||||
onTapOutside: (_) =>
|
||||
FocusManager.instance.primaryFocus?.unfocus(),
|
||||
),
|
||||
const Gap(4),
|
||||
TextField(
|
||||
controller: _descriptionController,
|
||||
minLines: 3,
|
||||
maxLines: null,
|
||||
decoration: InputDecoration(
|
||||
labelText: 'fieldDescription'.tr(),
|
||||
),
|
||||
onTapOutside: (_) =>
|
||||
FocusManager.instance.primaryFocus?.unfocus(),
|
||||
),
|
||||
],
|
||||
).padding(horizontal: 8),
|
||||
const Gap(16),
|
||||
Card(
|
||||
child: SizedBox(
|
||||
width: double.infinity,
|
||||
child: Row(
|
||||
children: [
|
||||
AccountImage(content: _belongToRealm?.avatar, radius: 24),
|
||||
const Gap(16),
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.baseline,
|
||||
textBaseline: TextBaseline.alphabetic,
|
||||
children: [
|
||||
Text(_nickController.text)
|
||||
.textStyle(Theme.of(context).textTheme.titleLarge!),
|
||||
const Gap(4),
|
||||
Text('@${_nameController.text}')
|
||||
.textStyle(Theme.of(context).textTheme.bodySmall!),
|
||||
],
|
||||
),
|
||||
|
Loading…
Reference in New Issue
Block a user