Update create user dialog

This commit is contained in:
2024-05-28 06:24:45 +00:00
committed by GitHub
parent f06dc34c7e
commit bf2795be33
13 changed files with 242 additions and 18 deletions

View File

@@ -2,12 +2,12 @@ import 'dart:convert';
import 'package:cryptography/cryptography.dart';
import 'package:dio/dio.dart';
import 'package:eh_downloader_flutter/api/file.dart';
import 'package:retrofit/retrofit.dart';
import 'api_result.dart';
import 'config.dart';
import 'eh.dart';
import 'file.dart';
import 'gallery.dart';
import 'status.dart';
import 'tags.dart';

View File

@@ -1,4 +1,6 @@
import 'package:enum_flag/enum_flag.dart';
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:json_annotation/json_annotation.dart';
part 'user.g.dart';
@@ -7,16 +9,35 @@ enum UserPermission with EnumFlag {
readGallery,
editGallery,
deleteGallery,
manageTasks,
manageTasks;
String localText(BuildContext context) {
final i18n = AppLocalizations.of(context)!;
switch (this) {
case UserPermission.readGallery:
return i18n.readGallery;
case UserPermission.editGallery:
return i18n.editGallery;
case UserPermission.deleteGallery:
return i18n.deleteGallery;
case UserPermission.manageTasks:
return i18n.manageTasks;
}
}
}
const userPermissionAll = 15;
class UserPermissions {
const UserPermissions(this.code);
final int code;
UserPermissions(this.code);
int code;
bool has(UserPermission permission) => code.hasFlag(permission);
bool get isAll => code == userPermissionAll;
int toJson() => code;
void add(UserPermission flag) {
code |= flag.value;
}
static int toJson2(UserPermissions code) {
return code.code;
}
@@ -25,6 +46,10 @@ class UserPermissions {
return UserPermissions(code);
}
void remove(UserPermission flag) {
code &= ~flag.value;
}
@override
String toString() {
if (code & userPermissionAll == userPermissionAll) return "all";

View File

@@ -1,4 +1,4 @@
import 'package:eh_downloader_flutter/globals.dart';
import '../globals.dart';
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import '../api/file.dart';

View File

@@ -30,10 +30,12 @@ class UserCard extends StatelessWidget {
style: TextStyle(color: cs.secondary))
],
)),
IconButton(
onPressed: () {},
tooltip: i18n.edit,
icon: const Icon(Icons.edit)),
!user.isAdmin || (user.isAdmin && auth.isRoot == true)
? IconButton(
onPressed: () {},
tooltip: i18n.edit,
icon: const Icon(Icons.edit))
: Container(),
!user.isAdmin ||
(user.isAdmin && auth.isRoot == true && user.id != 0)
? IconButton(

View File

@@ -0,0 +1,78 @@
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import '../api/user.dart';
class UserPermissionsChips extends StatefulWidget {
const UserPermissionsChips({super.key, this.permissions, this.onChanged});
final UserPermissions? permissions;
final ValueChanged<UserPermissions>? onChanged;
@override
State<StatefulWidget> createState() => _UserPermissionsChips();
}
class _UserPermissionsChips extends State<UserPermissionsChips> {
late UserPermissions _permissions;
@override
void initState() {
_permissions = widget.permissions ?? UserPermissions(0);
super.initState();
}
void _onChanged() {
if (widget.onChanged != null) {
widget.onChanged!(_permissions);
}
}
Widget _buildChips(BuildContext context) {
final i18n = AppLocalizations.of(context)!;
var list = <FilterChip>[
FilterChip(
label: Text(i18n.allPermissions),
selected: _permissions.isAll,
onSelected: (bool value) {
setState(() {
if (value) {
_permissions.code = userPermissionAll;
} else {
_permissions.code = 0;
}
});
_onChanged();
},
)
];
for (var flag in UserPermission.values) {
list.add(FilterChip(
label: Text(flag.localText(context)),
selected: _permissions.has(flag),
onSelected: (bool value) {
setState(() {
if (value) {
_permissions.add(flag);
} else {
_permissions.remove(flag);
}
});
_onChanged();
},
));
}
return Wrap(spacing: 5.0, children: list);
}
@override
Widget build(BuildContext context) {
final i18n = AppLocalizations.of(context)!;
return Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
Padding(
padding: const EdgeInsets.only(bottom: 8),
child: Row(mainAxisAlignment: MainAxisAlignment.start, children: [
const Icon(Icons.person),
Text(i18n.userPermissions, style: const TextStyle(fontSize: 20)),
])),
_buildChips(context),
]);
}
}

View File

@@ -58,7 +58,7 @@ class _GalleryDetailsPage extends State<GalleryDetailsPage> {
try {
_cancel = CancelToken();
_isLoading = true;
final data = (await api.getGallery(widget.gid)).unwrap();
final data = (await api.getGallery(widget.gid, cancel: _cancel)).unwrap();
if (!_cancel!.isCancelled) {
setState(() {
_meta = data.meta;

View File

@@ -1,10 +1,10 @@
import 'package:dio/dio.dart';
import 'package:eh_downloader_flutter/api/task.dart';
import 'package:eh_downloader_flutter/components/labeled_checkbox.dart';
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:go_router/go_router.dart';
import 'package:logging/logging.dart';
import '../api/task.dart';
import '../components/labeled_checkbox.dart';
import '../components/number_field.dart';
import '../globals.dart';
import '../utils/parse_url.dart';

View File

@@ -1,10 +1,16 @@
import 'package:dio/dio.dart';
import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:go_router/go_router.dart';
import 'package:logging/logging.dart';
import '../api/user.dart';
import '../components/labeled_checkbox.dart';
import '../components/user_permissions_chips.dart';
import '../globals.dart';
final _log = Logger("NewUserPage");
class NewUserPage extends StatefulWidget {
const NewUserPage({super.key});
@@ -19,7 +25,12 @@ class _NewUserPage extends State<NewUserPage> {
String _username = "";
String _password = "";
bool _isAdmin = false;
UserPermissions _permissions =
UserPermissions(UserPermission.readGallery.value);
bool _passwordVisible = false;
CancelToken? _cancel;
bool _isRequesting = false;
int? _newUserId;
Widget _buildWithVecticalPadding(Widget child) {
return Container(
@@ -28,6 +39,34 @@ class _NewUserPage extends State<NewUserPage> {
);
}
Future<void> _request() async {
setState(() {
_isRequesting = true;
});
try {
_cancel = CancelToken();
// 关闭对话框不中断连接
_newUserId = (await api.createUser(_username, _password,
isAdmin: _isAdmin,
permissions: _isAdmin ? null : _permissions.code))
.unwrap();
if (!_cancel!.isCancelled) {
setState(() {
_isRequesting = false;
});
}
listener.tryEmit("new_user", _newUserId);
} catch (e) {
_log.severe("Failed to create new user: $e");
}
}
@override
void dispose() {
_cancel?.cancel();
super.dispose();
}
@override
Widget build(BuildContext context) {
if (!tryInitApi(context)) {
@@ -39,6 +78,12 @@ class _NewUserPage extends State<NewUserPage> {
});
return Container();
}
if (_newUserId != null) {
SchedulerBinding.instance.addPostFrameCallback((_) {
context.canPop() ? context.pop() : context.go("/users");
});
return Container();
}
final i18n = AppLocalizations.of(context)!;
final maxWidth = MediaQuery.of(context).size.width;
return Container(
@@ -51,6 +96,7 @@ class _NewUserPage extends State<NewUserPage> {
child: Form(
key: _formKey,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Stack(
alignment: Alignment.center,
@@ -117,6 +163,26 @@ class _NewUserPage extends State<NewUserPage> {
}
},
label: Text(i18n.admin))),
!_isAdmin
? _buildWithVecticalPadding(UserPermissionsChips(
permissions: _permissions,
onChanged: (v) {
setState(() {
_permissions = v;
});
}))
: Container(),
Row(mainAxisAlignment: MainAxisAlignment.center, children: [
ElevatedButton(
onPressed: !_isRequesting &&
_username.isNotEmpty &&
_password.isNotEmpty
? () {
_request();
}
: null,
child: Text(i18n.create))
]),
],
))),
);

View File

@@ -206,5 +206,11 @@
"user": "User",
"edit": "Edit",
"delete": "Delete",
"createNewUser": "Create new user"
"createNewUser": "Create new user",
"readGallery": "Read gallery",
"editGallery": "Edit gallery",
"deleteGallery": "Delete gallery",
"manageTasks": "Manage tasks",
"allPermissions": "All permissions",
"userPermissions": "User permissions"
}

View File

@@ -206,5 +206,11 @@
"user": "用户",
"edit": "编辑",
"delete": "删除",
"createNewUser": "新建用户"
"createNewUser": "新建用户",
"readGallery": "查看画廊",
"editGallery": "编辑画廊",
"deleteGallery": "删除画廊",
"manageTasks": "管理任务",
"allPermissions": "所有权限",
"userPermissions": "用户权限"
}

View File

@@ -64,7 +64,7 @@ class _GalleryPage extends State<GalleryPage>
try {
_cancel = CancelToken();
_isLoading = true;
final data = (await api.getGallery(_gid)).unwrap();
final data = (await api.getGallery(_gid, cancel: _cancel)).unwrap();
_data = data;
final fileData = (await api.getFiles(
data.pages.map((e) => e.token).toList(),

View File

@@ -23,12 +23,14 @@ class _UsersPage extends State<UsersPage> with ThemeModeWidget, IsTopWidget2 {
List<BUser>? _users;
bool _isLoading = false;
CancelToken? _cancel;
CancelToken? _cancel2;
Object? _error;
Future<void> _fetchData() async {
_cancel2?.cancel();
try {
_cancel = CancelToken();
_isLoading = true;
final users = (await api.getUsers(all: true)).unwrap();
final users = (await api.getUsers(all: true, cancel: _cancel)).unwrap();
if (!_cancel!.isCancelled) {
setState(() {
_users = users;
@@ -153,4 +155,38 @@ class _UsersPage extends State<UsersPage> with ThemeModeWidget, IsTopWidget2 {
),
);
}
@override
void dispose() {
_cancel?.cancel();
_cancel2?.cancel();
listener.removeEventListener("new_user", _onNewUser);
super.dispose();
}
@override
void initState() {
listener.on("new_user", _onNewUser);
super.initState();
}
void _onNewUser(dynamic arg) async {
int uid = arg as int;
try {
_cancel2 = CancelToken();
final u = (await api.getUser(id: uid, cancel: _cancel2)).unwrap();
if (!_cancel2!.isCancelled) {
setState(() {
_users?.add(u);
});
}
} catch (e) {
if (!_cancel2!.isCancelled) {
_log.severe("Failed to load user $uid:", e);
setState(() {
_error = e;
});
}
}
}
}

View File

@@ -27,7 +27,11 @@ class SinglePageViewerExtra {
class SinglePageViewer extends StatefulWidget {
const SinglePageViewer(
{super.key, required this.gid, required this.index, this.data, this.files});
{super.key,
required this.gid,
required this.index,
this.data,
this.files});
final GalleryData? data;
final EhFiles? files;
final int gid;
@@ -89,7 +93,8 @@ class _SinglePageViewer extends State<SinglePageViewer>
_cancel = CancelToken();
_isLoading = true;
if (_data == null) {
final data = (await api.getGallery(widget.gid)).unwrap();
final data =
(await api.getGallery(widget.gid, cancel: _cancel)).unwrap();
_data = data;
}
final fileData = (await api.getFiles(