mirror of
https://github.com/lifegpc/eh_downloader_flutter.git
synced 2026-07-08 01:30:28 +08:00
Add user managemant page
This commit is contained in:
@@ -3,7 +3,6 @@ import 'dart:convert';
|
|||||||
import 'package:cryptography/cryptography.dart';
|
import 'package:cryptography/cryptography.dart';
|
||||||
import 'package:dio/dio.dart';
|
import 'package:dio/dio.dart';
|
||||||
import 'package:eh_downloader_flutter/api/file.dart';
|
import 'package:eh_downloader_flutter/api/file.dart';
|
||||||
import 'package:retrofit/dio.dart';
|
|
||||||
import 'package:retrofit/retrofit.dart';
|
import 'package:retrofit/retrofit.dart';
|
||||||
|
|
||||||
import 'api_result.dart';
|
import 'api_result.dart';
|
||||||
@@ -82,6 +81,12 @@ abstract class _EHApi {
|
|||||||
{@Query("id") int? id,
|
{@Query("id") int? id,
|
||||||
@Query("username") String? username,
|
@Query("username") String? username,
|
||||||
@CancelRequest() CancelToken? cancel});
|
@CancelRequest() CancelToken? cancel});
|
||||||
|
@GET('/user/list')
|
||||||
|
Future<ApiResult<List<BUser>>> getUsers(
|
||||||
|
{@Query("all") bool? all,
|
||||||
|
@Query("offset") int? offset,
|
||||||
|
@Query("limit") int? limit,
|
||||||
|
@CancelRequest() CancelToken? cancel});
|
||||||
|
|
||||||
@GET('/status')
|
@GET('/status')
|
||||||
Future<ApiResult<ServerStatus>> getStatus(
|
Future<ApiResult<ServerStatus>> getStatus(
|
||||||
|
|||||||
@@ -116,6 +116,51 @@ class __EHApi implements _EHApi {
|
|||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<ApiResult<List<BUser>>> getUsers({
|
||||||
|
bool? all,
|
||||||
|
int? offset,
|
||||||
|
int? limit,
|
||||||
|
CancelToken? cancel,
|
||||||
|
}) async {
|
||||||
|
final _extra = <String, dynamic>{};
|
||||||
|
final queryParameters = <String, dynamic>{
|
||||||
|
r'all': all,
|
||||||
|
r'offset': offset,
|
||||||
|
r'limit': limit,
|
||||||
|
};
|
||||||
|
queryParameters.removeWhere((k, v) => v == null);
|
||||||
|
final _headers = <String, dynamic>{};
|
||||||
|
const Map<String, dynamic>? _data = null;
|
||||||
|
final _result = await _dio.fetch<Map<String, dynamic>>(
|
||||||
|
_setStreamType<ApiResult<List<BUser>>>(Options(
|
||||||
|
method: 'GET',
|
||||||
|
headers: _headers,
|
||||||
|
extra: _extra,
|
||||||
|
)
|
||||||
|
.compose(
|
||||||
|
_dio.options,
|
||||||
|
'/user/list',
|
||||||
|
queryParameters: queryParameters,
|
||||||
|
data: _data,
|
||||||
|
cancelToken: cancel,
|
||||||
|
)
|
||||||
|
.copyWith(
|
||||||
|
baseUrl: _combineBaseUrls(
|
||||||
|
_dio.options.baseUrl,
|
||||||
|
baseUrl,
|
||||||
|
))));
|
||||||
|
final value = ApiResult<List<BUser>>.fromJson(
|
||||||
|
_result.data!,
|
||||||
|
(json) => json is List<dynamic>
|
||||||
|
? json
|
||||||
|
.map<BUser>((i) => BUser.fromJson(i as Map<String, dynamic>))
|
||||||
|
.toList()
|
||||||
|
: List.empty(),
|
||||||
|
);
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<ApiResult<ServerStatus>> getStatus({CancelToken? cancel}) async {
|
Future<ApiResult<ServerStatus>> getStatus({CancelToken? cancel}) async {
|
||||||
final _extra = <String, dynamic>{};
|
final _extra = <String, dynamic>{};
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ class AuthInfo {
|
|||||||
bool _isChecking = false;
|
bool _isChecking = false;
|
||||||
bool get isChecking => _isChecking;
|
bool get isChecking => _isChecking;
|
||||||
bool? get isAdmin => _user?.isAdmin;
|
bool? get isAdmin => _user?.isAdmin;
|
||||||
|
bool? get isRoot => _user != null ? _user!.id == 0 : null;
|
||||||
bool? get isDocker => _status?.isDocker;
|
bool? get isDocker => _status?.isDocker;
|
||||||
bool? get canReadGallery =>
|
bool? get canReadGallery =>
|
||||||
_user?.permissions.has(UserPermission.readGallery);
|
_user?.permissions.has(UserPermission.readGallery);
|
||||||
|
|||||||
46
lib/components/user_card.dart
Normal file
46
lib/components/user_card.dart
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||||
|
import '../api/user.dart';
|
||||||
|
import '../globals.dart';
|
||||||
|
|
||||||
|
class UserCard extends StatelessWidget {
|
||||||
|
const UserCard(this.user, {super.key});
|
||||||
|
final BUser user;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final i18n = AppLocalizations.of(context)!;
|
||||||
|
final cs = Theme.of(context).colorScheme;
|
||||||
|
return Card.outlined(
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||||
|
child: Row(children: [
|
||||||
|
Expanded(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
SelectableText(
|
||||||
|
user.username,
|
||||||
|
maxLines: 1,
|
||||||
|
style: TextStyle(
|
||||||
|
fontWeight: FontWeight.bold, color: cs.primary),
|
||||||
|
),
|
||||||
|
Text(user.isAdmin ? i18n.admin : i18n.user,
|
||||||
|
style: TextStyle(color: cs.secondary))
|
||||||
|
],
|
||||||
|
)),
|
||||||
|
IconButton(
|
||||||
|
onPressed: () {},
|
||||||
|
tooltip: i18n.edit,
|
||||||
|
icon: const Icon(Icons.edit)),
|
||||||
|
!user.isAdmin ||
|
||||||
|
(user.isAdmin && auth.isRoot == true && user.id != 0)
|
||||||
|
? IconButton(
|
||||||
|
onPressed: () {},
|
||||||
|
tooltip: i18n.delete,
|
||||||
|
icon: const Icon(Icons.delete))
|
||||||
|
: Container(),
|
||||||
|
])));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -13,6 +13,8 @@ class DownloadZipPage extends StatefulWidget {
|
|||||||
const DownloadZipPage(this.gid, {super.key});
|
const DownloadZipPage(this.gid, {super.key});
|
||||||
final int gid;
|
final int gid;
|
||||||
|
|
||||||
|
static const routeName = '/dialog/download/zip/:gid';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<DownloadZipPage> createState() => _DownloadZipPage();
|
State<DownloadZipPage> createState() => _DownloadZipPage();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -42,6 +42,8 @@ class GalleryDetailsPage extends StatefulWidget {
|
|||||||
final int gid;
|
final int gid;
|
||||||
final GMeta? meta;
|
final GMeta? meta;
|
||||||
|
|
||||||
|
static const routeName = '/dialog/gallery/details/:gid';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<GalleryDetailsPage> createState() => _GalleryDetailsPage();
|
State<GalleryDetailsPage> createState() => _GalleryDetailsPage();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,6 +16,8 @@ class NewDownloadTaskPage extends StatefulWidget {
|
|||||||
final int? gid;
|
final int? gid;
|
||||||
final String? token;
|
final String? token;
|
||||||
|
|
||||||
|
static const routeName = "/dialog/new_download_task";
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<NewDownloadTaskPage> createState() => _NewDownloadTaskPage();
|
State<NewDownloadTaskPage> createState() => _NewDownloadTaskPage();
|
||||||
}
|
}
|
||||||
|
|||||||
124
lib/dialog/new_user_page.dart
Normal file
124
lib/dialog/new_user_page.dart
Normal file
@@ -0,0 +1,124 @@
|
|||||||
|
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 '../components/labeled_checkbox.dart';
|
||||||
|
import '../globals.dart';
|
||||||
|
|
||||||
|
class NewUserPage extends StatefulWidget {
|
||||||
|
const NewUserPage({super.key});
|
||||||
|
|
||||||
|
static const routeName = "/dialog/user/new";
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<StatefulWidget> createState() => _NewUserPage();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _NewUserPage extends State<NewUserPage> {
|
||||||
|
final _formKey = GlobalKey<FormState>();
|
||||||
|
String _username = "";
|
||||||
|
String _password = "";
|
||||||
|
bool _isAdmin = false;
|
||||||
|
bool _passwordVisible = false;
|
||||||
|
|
||||||
|
Widget _buildWithVecticalPadding(Widget child) {
|
||||||
|
return Container(
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 8),
|
||||||
|
child: child,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
if (!tryInitApi(context)) {
|
||||||
|
return Container();
|
||||||
|
}
|
||||||
|
if (auth.isAdmin == false) {
|
||||||
|
SchedulerBinding.instance.addPostFrameCallback((_) {
|
||||||
|
context.go("/");
|
||||||
|
});
|
||||||
|
return Container();
|
||||||
|
}
|
||||||
|
final i18n = AppLocalizations.of(context)!;
|
||||||
|
final maxWidth = MediaQuery.of(context).size.width;
|
||||||
|
return Container(
|
||||||
|
padding: maxWidth < 400
|
||||||
|
? const EdgeInsets.symmetric(vertical: 20, horizontal: 5)
|
||||||
|
: const EdgeInsets.all(20),
|
||||||
|
width: maxWidth < 810 ? null : 800,
|
||||||
|
decoration: BoxDecoration(borderRadius: BorderRadius.circular(10)),
|
||||||
|
child: SingleChildScrollView(
|
||||||
|
child: Form(
|
||||||
|
key: _formKey,
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
Stack(
|
||||||
|
alignment: Alignment.center,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
i18n.createNewUser,
|
||||||
|
style: Theme.of(context).textTheme.headlineSmall,
|
||||||
|
),
|
||||||
|
Align(
|
||||||
|
alignment: Alignment.centerRight,
|
||||||
|
child: IconButton(
|
||||||
|
onPressed: () => context.canPop()
|
||||||
|
? context.pop()
|
||||||
|
: context.go("/users"),
|
||||||
|
icon: const Icon(Icons.close),
|
||||||
|
)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
_buildWithVecticalPadding(TextFormField(
|
||||||
|
decoration: InputDecoration(
|
||||||
|
border: const OutlineInputBorder(),
|
||||||
|
labelText: i18n.username,
|
||||||
|
),
|
||||||
|
initialValue: _username,
|
||||||
|
onChanged: (value) {
|
||||||
|
setState(() {
|
||||||
|
_username = value;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
)),
|
||||||
|
_buildWithVecticalPadding(TextFormField(
|
||||||
|
decoration: InputDecoration(
|
||||||
|
border: const OutlineInputBorder(),
|
||||||
|
labelText: i18n.password,
|
||||||
|
suffixIcon: IconButton(
|
||||||
|
icon: Icon(
|
||||||
|
_passwordVisible
|
||||||
|
? Icons.visibility
|
||||||
|
: Icons.visibility_off,
|
||||||
|
color: Theme.of(context).primaryColorDark,
|
||||||
|
),
|
||||||
|
onPressed: () {
|
||||||
|
setState(() {
|
||||||
|
_passwordVisible = !_passwordVisible;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
initialValue: _password,
|
||||||
|
onChanged: (value) {
|
||||||
|
setState(() {
|
||||||
|
_password = value;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
obscureText: !_passwordVisible,
|
||||||
|
)),
|
||||||
|
_buildWithVecticalPadding(LabeledCheckbox(
|
||||||
|
value: _isAdmin,
|
||||||
|
onChanged: (b) {
|
||||||
|
if (b != null) {
|
||||||
|
setState(() {
|
||||||
|
_isAdmin = b;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
label: Text(i18n.admin))),
|
||||||
|
],
|
||||||
|
))),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -39,6 +39,8 @@ class TaskPage extends StatefulWidget {
|
|||||||
const TaskPage(this.id, {super.key});
|
const TaskPage(this.id, {super.key});
|
||||||
final int id;
|
final int id;
|
||||||
|
|
||||||
|
static const routeName = "/dialog/task/:id";
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<StatefulWidget> createState() => _TaskPage();
|
State<StatefulWidget> createState() => _TaskPage();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -355,8 +355,10 @@ void checkAuth(BuildContext context) {
|
|||||||
if (!auth.isAuthed && !auth.checked && !auth.isChecking) {
|
if (!auth.isAuthed && !auth.checked && !auth.isChecking) {
|
||||||
auth.checkAuth().then((re) {
|
auth.checkAuth().then((re) {
|
||||||
if (!re) {
|
if (!re) {
|
||||||
if (auth.status!.noUser && prefs.getBool("skipCreateRootUser") == true)
|
if (auth.status!.noUser &&
|
||||||
|
prefs.getBool("skipCreateRootUser") == true) {
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
final loc = auth.status!.noUser ? "/create_root_user" : "/login";
|
final loc = auth.status!.noUser ? "/create_root_user" : "/login";
|
||||||
final path = GoRouterState.of(context).path;
|
final path = GoRouterState.of(context).path;
|
||||||
if (path != loc) {
|
if (path != loc) {
|
||||||
|
|||||||
@@ -200,5 +200,11 @@
|
|||||||
"cachedFileSize": "Cached file size",
|
"cachedFileSize": "Cached file size",
|
||||||
"update": "Update",
|
"update": "Update",
|
||||||
"updateFileSize": "Update file size",
|
"updateFileSize": "Update file size",
|
||||||
"clearCaches": "Clear caches"
|
"clearCaches": "Clear caches",
|
||||||
|
"userManagemant": "User Management",
|
||||||
|
"admin": "Administrator",
|
||||||
|
"user": "User",
|
||||||
|
"edit": "Edit",
|
||||||
|
"delete": "Delete",
|
||||||
|
"createNewUser": "Create new user"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -200,5 +200,11 @@
|
|||||||
"cachedFileSize": "已缓存文件大小",
|
"cachedFileSize": "已缓存文件大小",
|
||||||
"update": "更新",
|
"update": "更新",
|
||||||
"updateFileSize": "更新文件大小",
|
"updateFileSize": "更新文件大小",
|
||||||
"clearCaches": "清除缓存"
|
"clearCaches": "清除缓存",
|
||||||
|
"userManagemant": "用户管理",
|
||||||
|
"admin": "管理员",
|
||||||
|
"user": "用户",
|
||||||
|
"edit": "编辑",
|
||||||
|
"delete": "删除",
|
||||||
|
"createNewUser": "新建用户"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import 'dialog/dialog_page.dart';
|
|||||||
import 'dialog/download_zip_page.dart';
|
import 'dialog/download_zip_page.dart';
|
||||||
import 'dialog/gallery_details_page.dart';
|
import 'dialog/gallery_details_page.dart';
|
||||||
import 'dialog/new_download_task_page.dart';
|
import 'dialog/new_download_task_page.dart';
|
||||||
|
import 'dialog/new_user_page.dart';
|
||||||
import 'dialog/task_page.dart';
|
import 'dialog/task_page.dart';
|
||||||
import 'globals.dart';
|
import 'globals.dart';
|
||||||
import 'logs/file.dart';
|
import 'logs/file.dart';
|
||||||
@@ -23,6 +24,7 @@ import 'pages/server_settings.dart';
|
|||||||
import 'pages/set_server.dart';
|
import 'pages/set_server.dart';
|
||||||
import 'pages/settings.dart';
|
import 'pages/settings.dart';
|
||||||
import 'pages/task_manager.dart';
|
import 'pages/task_manager.dart';
|
||||||
|
import 'pages/users.dart';
|
||||||
import 'utils.dart';
|
import 'utils.dart';
|
||||||
import 'viewer/single.dart';
|
import 'viewer/single.dart';
|
||||||
|
|
||||||
@@ -97,7 +99,7 @@ final _router = GoRouter(
|
|||||||
redirect: (context, state) => "/galleries",
|
redirect: (context, state) => "/galleries",
|
||||||
),
|
),
|
||||||
GoRoute(
|
GoRoute(
|
||||||
path: '/dialog/download/zip/:gid',
|
path: DownloadZipPage.routeName,
|
||||||
pageBuilder: (context, state) => DialogPage(
|
pageBuilder: (context, state) => DialogPage(
|
||||||
key: state.pageKey,
|
key: state.pageKey,
|
||||||
builder: (context) {
|
builder: (context) {
|
||||||
@@ -140,7 +142,7 @@ final _router = GoRouter(
|
|||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
GoRoute(
|
GoRoute(
|
||||||
path: '/dialog/gallery/details/:gid',
|
path: GalleryDetailsPage.routeName,
|
||||||
pageBuilder: (context, state) {
|
pageBuilder: (context, state) {
|
||||||
final extra = state.extra as GalleryDetailsPageExtra?;
|
final extra = state.extra as GalleryDetailsPageExtra?;
|
||||||
return DialogPage(
|
return DialogPage(
|
||||||
@@ -169,7 +171,7 @@ final _router = GoRouter(
|
|||||||
builder: (context, state) => TaskManagerPage(key: state.pageKey),
|
builder: (context, state) => TaskManagerPage(key: state.pageKey),
|
||||||
),
|
),
|
||||||
GoRoute(
|
GoRoute(
|
||||||
path: "/dialog/new_download_task",
|
path: NewDownloadTaskPage.routeName,
|
||||||
pageBuilder: (context, state) {
|
pageBuilder: (context, state) {
|
||||||
int? gid;
|
int? gid;
|
||||||
String? token;
|
String? token;
|
||||||
@@ -186,7 +188,7 @@ final _router = GoRouter(
|
|||||||
});
|
});
|
||||||
}),
|
}),
|
||||||
GoRoute(
|
GoRoute(
|
||||||
path: "/dialog/task/:id",
|
path: TaskPage.routeName,
|
||||||
pageBuilder: (context, state) {
|
pageBuilder: (context, state) {
|
||||||
return DialogPage(
|
return DialogPage(
|
||||||
key: state.pageKey,
|
key: state.pageKey,
|
||||||
@@ -203,6 +205,19 @@ final _router = GoRouter(
|
|||||||
return "/task_manager";
|
return "/task_manager";
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
|
GoRoute(
|
||||||
|
path: UsersPage.routeName,
|
||||||
|
builder: (context, state) => const UsersPage(),
|
||||||
|
),
|
||||||
|
GoRoute(
|
||||||
|
path: NewUserPage.routeName,
|
||||||
|
pageBuilder: (context, state) {
|
||||||
|
return DialogPage(
|
||||||
|
key: state.pageKey,
|
||||||
|
builder: (context) {
|
||||||
|
return const NewUserPage();
|
||||||
|
});
|
||||||
|
}),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ class HomeDrawer extends StatelessWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
final i18n = AppLocalizations.of(context)!;
|
||||||
return Drawer(
|
return Drawer(
|
||||||
child: ListView(
|
child: ListView(
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
@@ -26,7 +27,7 @@ class HomeDrawer extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
ListTile(
|
ListTile(
|
||||||
leading: const Icon(Icons.collections),
|
leading: const Icon(Icons.collections),
|
||||||
title: Text(AppLocalizations.of(context)!.galleries),
|
title: Text(i18n.galleries),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
Scaffold.of(context).closeDrawer();
|
Scaffold.of(context).closeDrawer();
|
||||||
context.push("/galleries");
|
context.push("/galleries");
|
||||||
@@ -35,7 +36,7 @@ class HomeDrawer extends StatelessWidget {
|
|||||||
auth.isAdmin == true
|
auth.isAdmin == true
|
||||||
? ListTile(
|
? ListTile(
|
||||||
leading: const Icon(Icons.admin_panel_settings),
|
leading: const Icon(Icons.admin_panel_settings),
|
||||||
title: Text(AppLocalizations.of(context)!.serverSettings),
|
title: Text(i18n.serverSettings),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
Scaffold.of(context).closeDrawer();
|
Scaffold.of(context).closeDrawer();
|
||||||
context.push("/server_settings");
|
context.push("/server_settings");
|
||||||
@@ -45,13 +46,22 @@ class HomeDrawer extends StatelessWidget {
|
|||||||
auth.canManageTasks == true
|
auth.canManageTasks == true
|
||||||
? ListTile(
|
? ListTile(
|
||||||
leading: const Icon(Icons.task),
|
leading: const Icon(Icons.task),
|
||||||
title: Text(AppLocalizations.of(context)!.taskManager),
|
title: Text(i18n.taskManager),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
Scaffold.of(context).closeDrawer();
|
Scaffold.of(context).closeDrawer();
|
||||||
context.push("/task_manager");
|
context.push("/task_manager");
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
: Container(),
|
: Container(),
|
||||||
|
auth.isAdmin == true
|
||||||
|
? ListTile(
|
||||||
|
leading: const Icon(Icons.manage_accounts),
|
||||||
|
title: Text(i18n.userManagemant),
|
||||||
|
onTap: () {
|
||||||
|
Scaffold.of(context).closeDrawer();
|
||||||
|
context.push("/users");
|
||||||
|
})
|
||||||
|
: Container(),
|
||||||
ListTile(
|
ListTile(
|
||||||
leading: const Icon(Icons.settings),
|
leading: const Icon(Icons.settings),
|
||||||
title: Text(AppLocalizations.of(context)!.settings),
|
title: Text(AppLocalizations.of(context)!.settings),
|
||||||
|
|||||||
156
lib/pages/users.dart
Normal file
156
lib/pages/users.dart
Normal file
@@ -0,0 +1,156 @@
|
|||||||
|
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/user_card.dart';
|
||||||
|
import '../globals.dart';
|
||||||
|
|
||||||
|
final _log = Logger("UsersPage");
|
||||||
|
|
||||||
|
class UsersPage extends StatefulWidget {
|
||||||
|
const UsersPage({super.key});
|
||||||
|
|
||||||
|
static const String routeName = '/users';
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<UsersPage> createState() => _UsersPage();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _UsersPage extends State<UsersPage> with ThemeModeWidget, IsTopWidget2 {
|
||||||
|
List<BUser>? _users;
|
||||||
|
bool _isLoading = false;
|
||||||
|
CancelToken? _cancel;
|
||||||
|
Object? _error;
|
||||||
|
Future<void> _fetchData() async {
|
||||||
|
try {
|
||||||
|
_cancel = CancelToken();
|
||||||
|
_isLoading = true;
|
||||||
|
final users = (await api.getUsers(all: true)).unwrap();
|
||||||
|
if (!_cancel!.isCancelled) {
|
||||||
|
setState(() {
|
||||||
|
_users = users;
|
||||||
|
_isLoading = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
if (!_cancel!.isCancelled) {
|
||||||
|
_log.severe("Failed to load user list:", e);
|
||||||
|
setState(() {
|
||||||
|
_error = e;
|
||||||
|
_isLoading = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
if (!tryInitApi(context)) {
|
||||||
|
return Container();
|
||||||
|
}
|
||||||
|
if (auth.isAdmin == false) {
|
||||||
|
SchedulerBinding.instance.addPostFrameCallback((_) {
|
||||||
|
context.go("/");
|
||||||
|
});
|
||||||
|
return Container();
|
||||||
|
}
|
||||||
|
final isLoading = _users == null && _error == null;
|
||||||
|
if (isLoading && !_isLoading) _fetchData();
|
||||||
|
final i18n = AppLocalizations.of(context)!;
|
||||||
|
final th = Theme.of(context);
|
||||||
|
if (isTop(context)) {
|
||||||
|
setCurrentTitle(i18n.userManagemant, th.primaryColor.value);
|
||||||
|
}
|
||||||
|
return Scaffold(
|
||||||
|
appBar: _users == null
|
||||||
|
? AppBar(
|
||||||
|
leading: IconButton(
|
||||||
|
icon: const Icon(Icons.arrow_back),
|
||||||
|
onPressed: () {
|
||||||
|
context.canPop() ? context.pop() : context.go("/");
|
||||||
|
},
|
||||||
|
),
|
||||||
|
title: Text(i18n.userManagemant),
|
||||||
|
actions: [
|
||||||
|
buildThemeModeIcon(context),
|
||||||
|
buildMoreVertSettingsButon(context),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
: null,
|
||||||
|
body: isLoading
|
||||||
|
? const Center(child: CircularProgressIndicator())
|
||||||
|
: _users != null
|
||||||
|
? _buildMain(context)
|
||||||
|
: Center(
|
||||||
|
child: Text("Error: $_error"),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildMain(BuildContext context) {
|
||||||
|
final size = MediaQuery.of(context).size;
|
||||||
|
return Stack(children: [
|
||||||
|
_buildUserList(context),
|
||||||
|
Positioned(
|
||||||
|
bottom: size.height / 10,
|
||||||
|
right: size.width / 10,
|
||||||
|
child: _buildIconList(context)),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildAddIcon(BuildContext context) {
|
||||||
|
final i18n = AppLocalizations.of(context)!;
|
||||||
|
return IconButton(
|
||||||
|
onPressed: () {
|
||||||
|
context.push("/dialog/user/new");
|
||||||
|
},
|
||||||
|
tooltip: i18n.create,
|
||||||
|
icon: const Icon(Icons.add));
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildIconList(BuildContext context) {
|
||||||
|
return Row(children: [
|
||||||
|
_buildAddIcon(context),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildUserList(BuildContext context) {
|
||||||
|
final i18n = AppLocalizations.of(context)!;
|
||||||
|
return CustomScrollView(slivers: [
|
||||||
|
SliverAppBar(
|
||||||
|
leading: IconButton(
|
||||||
|
icon: const Icon(Icons.arrow_back),
|
||||||
|
onPressed: () {
|
||||||
|
context.canPop() ? context.pop() : context.go("/");
|
||||||
|
},
|
||||||
|
),
|
||||||
|
title: Text(i18n.userManagemant),
|
||||||
|
actions: [
|
||||||
|
buildThemeModeIcon(context),
|
||||||
|
buildMoreVertSettingsButon(context),
|
||||||
|
],
|
||||||
|
floating: true,
|
||||||
|
),
|
||||||
|
_buildSliverGrid(context),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildSliverGrid(BuildContext context) {
|
||||||
|
return SliverGrid(
|
||||||
|
gridDelegate: const SliverGridDelegateWithMaxCrossAxisExtent(
|
||||||
|
maxCrossAxisExtent: 360.0,
|
||||||
|
mainAxisSpacing: 10.0,
|
||||||
|
crossAxisSpacing: 10.0,
|
||||||
|
childAspectRatio: 4.0,
|
||||||
|
),
|
||||||
|
delegate: SliverChildBuilderDelegate(
|
||||||
|
(BuildContext context, int index) {
|
||||||
|
return UserCard(_users![index]!);
|
||||||
|
},
|
||||||
|
childCount: _users!.length,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -62,7 +62,6 @@ dev_dependencies:
|
|||||||
build_runner: ^2.4.6
|
build_runner: ^2.4.6
|
||||||
retrofit_generator: ^8.1.0
|
retrofit_generator: ^8.1.0
|
||||||
json_serializable: ^6.7.1
|
json_serializable: ^6.7.1
|
||||||
sqflite_common_ffi: ^2.3.3
|
|
||||||
|
|
||||||
flutter:
|
flutter:
|
||||||
generate: true
|
generate: true
|
||||||
|
|||||||
Reference in New Issue
Block a user