From 048c54e080573488153a32dd8b381981f21bb246 Mon Sep 17 00:00:00 2001 From: debuggerx Date: Fri, 31 Dec 2021 18:05:13 +0800 Subject: [PATCH] feat: add upload logic. --- api/bin/migrate.dart | 9 +-- api/lib/apis.dart | 11 ++++ api/lib/models.dart | 3 +- api/lib/src/config/plugins/jwt.dart | 2 +- api/lib/src/models/scheme.dart | 35 +++++++++++ api/lib/src/models/user.dart | 4 ++ .../src/routes/controllers/auth_controllers.dart | 13 ++++ .../routes/controllers/controller_extensions.dart | 17 +++++- api/lib/src/routes/controllers/middlewares.dart | 7 +-- .../src/routes/controllers/scheme_controllers.dart | 60 +++++++++++++++++++ api/lib/src/routes/routes.dart | 2 + app/lib/http/api.dart | 38 ++++++++++-- app/lib/main.dart | 14 ++++- app/lib/models/scheme.dart | 2 +- app/lib/pages/gesture_editor.dart | 26 +++++++- app/lib/pages/market_or_me.dart | 30 +--------- app/lib/utils/helper.dart | 2 + app/lib/widgets/login.dart | 6 ++ app/lib/widgets/me.dart | 69 ++++++++++++++++++++++ app/resources/langs/en.json | 8 +++ app/resources/langs/zh-CN.json | 8 +++ 21 files changed, 317 insertions(+), 49 deletions(-) create mode 100644 api/lib/src/models/scheme.dart create mode 100644 api/lib/src/routes/controllers/scheme_controllers.dart create mode 100644 app/lib/widgets/me.dart diff --git a/api/bin/migrate.dart b/api/bin/migrate.dart index 8963f56..eec2b33 100644 --- a/api/bin/migrate.dart +++ b/api/bin/migrate.dart @@ -1,10 +1,10 @@ -import 'package:angel3_migration/angel3_migration.dart'; -import 'package:angel3_orm_postgres/angel3_orm_postgres.dart'; -import 'package:dde_gesture_manager_api/src/config/plugins/orm.dart'; -import 'package:dde_gesture_manager_api/models.dart'; import 'package:angel3_configuration/angel3_configuration.dart'; +import 'package:angel3_migration/angel3_migration.dart'; import 'package:angel3_migration_runner/angel3_migration_runner.dart'; import 'package:angel3_migration_runner/postgres.dart'; +import 'package:angel3_orm_postgres/angel3_orm_postgres.dart'; +import 'package:dde_gesture_manager_api/models.dart'; +import 'package:dde_gesture_manager_api/src/config/plugins/orm.dart'; import 'package:file/local.dart'; import 'package:logging/logging.dart'; @@ -28,6 +28,7 @@ void main(List args) async { var migrationRunner = PostgresMigrationRunner(connection, migrations: [ UserMigration(), UserSeed(), + SchemeMigration(), ]); await runMigrations(migrationRunner, args); } diff --git a/api/lib/apis.dart b/api/lib/apis.dart index 3f1ba2f..3692087 100644 --- a/api/lib/apis.dart +++ b/api/lib/apis.dart @@ -7,6 +7,7 @@ class Apis { static final system = SystemApis(); static final auth = AuthApis(); + static final scheme = SchemeApis(); } class AuthApis { @@ -15,6 +16,8 @@ class AuthApis { String get loginOrSignup => [path, 'login_or_signup'].joinPath(); String confirmSignup({required StringParam accessKey}) => [path, 'confirm_sign_up', accessKey].joinPath(); + + String get status => [path, 'status'].joinPath(); } class SystemApis { @@ -23,6 +26,14 @@ class SystemApis { String get appVersion => [path, 'app-version'].joinPath(); } +class SchemeApis { + static final String path = '/scheme'; + + String get upload => [path, 'upload'].joinPath(); + + String get userUploads => [path, 'user', 'uploads'].joinPath(); +} + final _paramsMap = { 'IntParam': IntParam.nameOnRoute, 'DoubleParam': DoubleParam.nameOnRoute, diff --git a/api/lib/models.dart b/api/lib/models.dart index c608c7a..04f6864 100644 --- a/api/lib/models.dart +++ b/api/lib/models.dart @@ -1,3 +1,4 @@ export 'src/models/user.dart'; export 'src/models/app_version.dart'; -export 'src/models/login_success.dart'; \ No newline at end of file +export 'src/models/login_success.dart'; +export 'src/models/scheme.dart'; \ No newline at end of file diff --git a/api/lib/src/config/plugins/jwt.dart b/api/lib/src/config/plugins/jwt.dart index 8cf8700..e209066 100644 --- a/api/lib/src/config/plugins/jwt.dart +++ b/api/lib/src/config/plugins/jwt.dart @@ -9,7 +9,7 @@ Future configureServer(Angel app) async { allowCookie: false, deserializer: (p) async => (UserQuery()..where!.id.equals(int.parse(p))) .getOne(app.container!.make()) - .then((value) => value.value), + .then((value) => value.isNotEmpty ? value.value : User(email: '')), serializer: (p) => p.id ?? '', ); await auth.configureServer(app); diff --git a/api/lib/src/models/scheme.dart b/api/lib/src/models/scheme.dart new file mode 100644 index 0000000..49e3516 --- /dev/null +++ b/api/lib/src/models/scheme.dart @@ -0,0 +1,35 @@ +import 'package:angel3_orm/angel3_orm.dart'; +import 'package:angel3_serialize/angel3_serialize.dart'; +import 'package:dde_gesture_manager_api/src/models/base_model.dart'; +import 'package:angel3_migration/angel3_migration.dart'; +import 'package:optional/optional.dart'; + +part 'scheme.g.dart'; + +@serializable +@orm +abstract class _Scheme extends BaseModel { + @Column(isNullable: false, indexType: IndexType.unique) + @SerializableField(isNullable: false) + String? get uuid; + + @Column(isNullable: false) + @SerializableField(isNullable: false) + String? get name; + + @Column(isNullable: false, indexType: IndexType.standardIndex) + @SerializableField(isNullable: true, exclude: true) + int? uid; + + @Column(type: ColumnType.text) + String? description; + + @Column(isNullable: false, indexType: IndexType.standardIndex) + @SerializableField(defaultValue: false, isNullable: false) + bool? get shared; + + @Column(type: ColumnType.jsonb) + @SerializableField() + @DefaultsTo([]) + List? get gestures; +} diff --git a/api/lib/src/models/user.dart b/api/lib/src/models/user.dart index c34c7ef..8aab835 100644 --- a/api/lib/src/models/user.dart +++ b/api/lib/src/models/user.dart @@ -20,5 +20,9 @@ abstract class _User extends BaseModel { @SerializableField(isNullable: true, exclude: true) String? get password; + @Column(isNullable: false) + @SerializableField(defaultValue: false) + bool? get blocked; + String secret(String salt) => base64.encode(Hmac(sha256, salt.codeUnits).convert((password ?? '').codeUnits).bytes); } diff --git a/api/lib/src/routes/controllers/auth_controllers.dart b/api/lib/src/routes/controllers/auth_controllers.dart index aae33e1..35d8823 100644 --- a/api/lib/src/routes/controllers/auth_controllers.dart +++ b/api/lib/src/routes/controllers/auth_controllers.dart @@ -5,6 +5,7 @@ import 'package:angel3_auth/angel3_auth.dart'; import 'package:angel3_framework/angel3_framework.dart'; import 'package:dde_gesture_manager_api/apis.dart'; import 'package:dde_gesture_manager_api/models.dart'; +import 'package:dde_gesture_manager_api/src/routes/controllers/middlewares.dart'; import 'package:mailer/mailer.dart'; import 'package:mailer/smtp_server.dart'; import 'package:uuid/uuid.dart'; @@ -48,6 +49,8 @@ Future configureServer(Angel app) async { return res.notFound(); } else if (user.value.password != userParams.password) { return res.unauthorized(); + } else if (user.value.blocked == true) { + return res.forbidden(); } else { var angelAuth = req.container!.make(); await angelAuth.loginById(user.value.id!, req, res); @@ -75,4 +78,14 @@ Future configureServer(Angel app) async { } return res.render('sign_up_result.html', {'success': false}); }); + + app.get( + Apis.auth.status, + chain( + [ + jwtMiddleware(), + (req, res) => req.user.blocked == false ? res.noContent() : res.forbidden(), + ], + ), + ); } diff --git a/api/lib/src/routes/controllers/controller_extensions.dart b/api/lib/src/routes/controllers/controller_extensions.dart index f13140e..b33549b 100644 --- a/api/lib/src/routes/controllers/controller_extensions.dart +++ b/api/lib/src/routes/controllers/controller_extensions.dart @@ -2,6 +2,7 @@ import 'dart:io'; import 'package:angel3_framework/angel3_framework.dart'; import 'package:angel3_orm/angel3_orm.dart' as orm; +import 'package:dde_gesture_manager_api/models.dart'; import 'package:dde_gesture_manager_api/src/config/plugins/redis_cache.dart'; import 'package:neat_cache/neat_cache.dart'; @@ -20,6 +21,16 @@ extension ResponseNoContent on ResponseContext { statusCode = HttpStatus.unauthorized; return close(); } + + forbidden() { + statusCode = HttpStatus.forbidden; + return close(); + } + + unProcessableEntity() { + statusCode = HttpStatus.unprocessableEntity; + return close(); + } } extension QueryWhereId on orm.Query { @@ -32,6 +43,10 @@ extension QueryExecutor on RequestContext { orm.QueryExecutor get queryExecutor => container!.make(); } -extension RedisExecutor on RequestContext { +extension RedisClient on RequestContext { Cache get cache => container!.make().cache; } + +extension JWTUserInstance on RequestContext { + User get user => container!.make(); +} diff --git a/api/lib/src/routes/controllers/middlewares.dart b/api/lib/src/routes/controllers/middlewares.dart index 84c9b63..2ea42ff 100644 --- a/api/lib/src/routes/controllers/middlewares.dart +++ b/api/lib/src/routes/controllers/middlewares.dart @@ -2,16 +2,15 @@ import 'package:angel3_auth/angel3_auth.dart'; import 'package:angel3_framework/angel3_framework.dart'; import 'package:dde_gesture_manager_api/models.dart'; +import '../controllers/controller_extensions.dart'; RequestHandler jwtMiddleware() { return (RequestContext req, ResponseContext res, {bool throwError = true}) async { bool _reject(ResponseContext res) { if (throwError) { - res.statusCode = 403; - throw AngelHttpException.forbidden(); - } else { - return false; + res.forbidden(); } + return false; } if (req.container != null) { diff --git a/api/lib/src/routes/controllers/scheme_controllers.dart b/api/lib/src/routes/controllers/scheme_controllers.dart new file mode 100644 index 0000000..4d4a1ca --- /dev/null +++ b/api/lib/src/routes/controllers/scheme_controllers.dart @@ -0,0 +1,60 @@ +import 'dart:async'; + +import 'package:angel3_framework/angel3_framework.dart'; +import 'package:dde_gesture_manager_api/apis.dart'; +import 'package:dde_gesture_manager_api/src/models/scheme.dart'; +import 'package:dde_gesture_manager_api/src/routes/controllers/middlewares.dart'; +import 'package:logging/logging.dart'; +import 'controller_extensions.dart'; + +Future configureServer(Angel app) async { + final _log = Logger('scheme_controller'); + + app.post( + Apis.scheme.upload, + chain( + [ + jwtMiddleware(), + (req, res) async { + try { + var scheme = SchemeSerializer.fromMap(req.bodyAsMap); + var schemeQuery = SchemeQuery(); + schemeQuery.where!.uuid.equals(scheme.uuid!); + var one = await schemeQuery.getOne(req.queryExecutor); + schemeQuery = SchemeQuery(); + schemeQuery.values.copyFrom(scheme); + schemeQuery.values.uid = int.parse(req.user.id!); + if (one.isEmpty) { + await schemeQuery.insert(req.queryExecutor); + } else { + schemeQuery.whereId = int.parse(one.value.id!); + await schemeQuery.updateOne(req.queryExecutor); + } + } catch (e) { + _log.severe(e); + return res.unProcessableEntity(); + } + return res.noContent(); + }, + ], + ), + ); + + app.get( + Apis.scheme.userUploads, + chain( + [ + jwtMiddleware(), + (req, res) async { + var schemeQuery = SchemeQuery(); + schemeQuery.where!.uid.equals(int.parse(req.user.id!)); + schemeQuery.orderBy(SchemeFields.updatedAt, descending: true); + return schemeQuery.get(req.queryExecutor).then((value) => value.map((e) => { + 'name': e.name, + 'description': e.description, + }).toList()); + }, + ], + ), + ); +} \ No newline at end of file diff --git a/api/lib/src/routes/routes.dart b/api/lib/src/routes/routes.dart index 4b86464..83f4a8e 100644 --- a/api/lib/src/routes/routes.dart +++ b/api/lib/src/routes/routes.dart @@ -2,6 +2,7 @@ import 'package:angel3_framework/angel3_framework.dart'; import 'package:file/file.dart'; import 'controllers/auth_controllers.dart' as auth_controllers; import 'controllers/system_controllers.dart' as system_controllers; +import 'controllers/scheme_controllers.dart' as scheme_controllers; /// Put your app routes here! /// @@ -22,6 +23,7 @@ AngelConfigurer configureServer(FileSystem fileSystem) { // Typically, you want to mount controllers first, after any global middleware. await app.configure(system_controllers.configureServerWithFileSystem(fileSystem)); await app.configure(auth_controllers.configureServer); + await app.configure(scheme_controllers.configureServer); // Throw a 404 if no route matched the request. app.fallback((req, res) => throw AngelHttpException.notFound()); diff --git a/app/lib/http/api.dart b/app/lib/http/api.dart index 44fcafa..b3264fe 100644 --- a/app/lib/http/api.dart +++ b/app/lib/http/api.dart @@ -3,6 +3,7 @@ import 'dart:io'; import 'package:dde_gesture_manager/constants/sp_keys.dart'; import 'package:dde_gesture_manager/extensions.dart'; +import 'package:dde_gesture_manager/models/scheme.dart' as AppScheme; import 'package:dde_gesture_manager/utils/helper.dart'; import 'package:dde_gesture_manager/utils/notificator.dart'; import 'package:dde_gesture_manager_api/apis.dart'; @@ -13,7 +14,12 @@ typedef T BeanBuilder(Map res); typedef T HandleRespBuild(http.Response resp); -getStatusCodeFunc(Map resp) => resp["statusCode"]; +typedef int GetStatusCodeFunc(Map resp); + +int getStatusCodeFunc(Map resp) => resp["statusCode"] as int; + +BeanBuilder> listRespBuilderWrap(BeanBuilder builder) => + (Map resp) => (resp['list'] as List).map((e) => builder(e)).toList(); class HttpErrorCode extends Error { int statusCode; @@ -40,11 +46,13 @@ class Api { } static HandleRespBuild _handleRespBuild(BeanBuilder builder) => (http.Response resp) { - if (builder == getStatusCodeFunc) return builder({"statusCode": resp.statusCode}); + if (builder is GetStatusCodeFunc) return builder({"statusCode": resp.statusCode}); T res; try { - res = builder(json.decode(resp.body)); + var decodeBody = json.decode(utf8.decode(resp.bodyBytes)); + res = decodeBody is Map ? builder(decodeBody) : builder({'list': decodeBody}); } catch (e) { + e.sout(); throw HttpErrorCode(resp.statusCode, message: resp.body); } return res; @@ -67,7 +75,7 @@ class Api { path: path, ), headers: { - HttpHeaders.contentTypeHeader: ContentType.json.value, + HttpHeaders.contentTypeHeader: ContentType.json.toString(), }..addAll( ignoreToken ? {} : {HttpHeaders.authorizationHeader: 'Bearer ${H().sp.getString(SPKeys.accessToken)}'}), ) @@ -98,7 +106,7 @@ class Api { ), body: jsonEncode(body), headers: { - HttpHeaders.contentTypeHeader: ContentType.json.value, + HttpHeaders.contentTypeHeader: ContentType.json.toString(), }..addAll( ignoreToken ? {} : {HttpHeaders.authorizationHeader: 'Bearer ${H().sp.getString(SPKeys.accessToken)}'}), ) @@ -132,4 +140,24 @@ class Api { ignoreToken: true, ignoreErrorHandle: ignoreErrorHandle, ); + + static Future checkAuthStatus() => _get(Apis.auth.status, getStatusCodeFunc, ignoreErrorHandle: true) + .then((value) => value == HttpStatus.noContent); + + static Future uploadScheme({required AppScheme.Scheme scheme, required bool share}) => _post( + Apis.scheme.upload, + getStatusCodeFunc, + body: SchemeSerializer.toMap( + Scheme( + name: scheme.name, + uuid: scheme.id, + description: scheme.description, + gestures: scheme.gestures, + shared: share, + ), + ), + ).then((value) => value == HttpStatus.noContent); + + static Future> userUploads() => + _get(Apis.scheme.userUploads, listRespBuilderWrap(SchemeSerializer.fromMap)); } diff --git a/app/lib/main.dart b/app/lib/main.dart index 74cf9c3..a507d48 100644 --- a/app/lib/main.dart +++ b/app/lib/main.dart @@ -3,6 +3,7 @@ import 'package:dde_gesture_manager/constants/sp_keys.dart'; import 'package:dde_gesture_manager/constants/supported_locales.dart'; import 'package:dde_gesture_manager/extensions.dart'; import 'package:dde_gesture_manager/generated/codegen_loader.g.dart'; +import 'package:dde_gesture_manager/http/api.dart'; import 'package:dde_gesture_manager/models/configs.dart'; import 'package:dde_gesture_manager/models/configs.provider.dart'; import 'package:dde_gesture_manager/models/settings.provider.dart'; @@ -68,7 +69,18 @@ class MyApp extends StatelessWidget { ], ), firstChild: Builder(builder: (context) { - Future.microtask(() => initEvents(context)); + Future.microtask(() { + initEvents(context); + if (H().lastCheckAuthStatusTime != null && + H().lastCheckAuthStatusTime!.difference(DateTime.now()) < Duration(minutes: 10)) return; + if (context.read().accessToken.notNull) { + Api.checkAuthStatus().then((value) { + if (!value) context.read().setProps(email: '', accessToken: ''); + }); + } else { + H().lastCheckAuthStatusTime = DateTime.now(); + } + }); return Container(); }), secondChild: HomePage(), diff --git a/app/lib/models/scheme.dart b/app/lib/models/scheme.dart index 5ffea37..6d2a385 100644 --- a/app/lib/models/scheme.dart +++ b/app/lib/models/scheme.dart @@ -131,7 +131,7 @@ class Scheme { @ProviderModelProp() List? gestures; - bool get readOnly => uploaded == true || fromMarket == true || id == Uuid.NAMESPACE_NIL; + bool get readOnly => fromMarket == true || id == Uuid.NAMESPACE_NIL; Scheme.parse(scheme) { if (scheme is String) scheme = json.decode(scheme); diff --git a/app/lib/pages/gesture_editor.dart b/app/lib/pages/gesture_editor.dart index 38c2f2d..78a89a1 100644 --- a/app/lib/pages/gesture_editor.dart +++ b/app/lib/pages/gesture_editor.dart @@ -1,6 +1,7 @@ import 'package:adaptive_scrollbar/adaptive_scrollbar.dart'; import 'package:dde_gesture_manager/constants/constants.dart'; import 'package:dde_gesture_manager/extensions.dart'; +import 'package:dde_gesture_manager/http/api.dart'; import 'package:dde_gesture_manager/models/configs.provider.dart'; import 'package:dde_gesture_manager/models/content_layout.provider.dart'; import 'package:dde_gesture_manager/models/local_schemes_provider.dart'; @@ -297,14 +298,13 @@ class GestureEditor extends StatelessWidget { Padding( padding: const EdgeInsets.only(left: 10), child: DButton.upload( - enabled: schemeProvider.uploaded == false, + enabled: schemeProvider.readOnly == false, onTap: () async { if (context.read().accessToken.isNull) { return Notificator.showAlert( title: LocaleKeys.info_login_for_upload_title.tr(), description: LocaleKeys.info_login_for_upload_description.tr(), ).then((value) { - value.sout(); if (value == CustomButton.positiveButton) { context .read() @@ -317,7 +317,27 @@ class GestureEditor extends StatelessWidget { description: LocaleKeys.info_upload_and_share_description.tr(), positiveButtonTitle: LocaleKeys.str_share.tr(), negativeButtonTitle: LocaleKeys.str_cancel.tr(), - ); + ).then((value) { + bool? _share; + if (value == CustomButton.positiveButton) + _share = true; + else if (value == CustomButton.negativeButton) _share = false; + + if (_share != null) { + Api.uploadScheme(scheme: schemeProvider, share: _share).then((value) { + if (value) { + Notificator.success(context, title: LocaleKeys.info_upload_success.tr()); + var localSchemesProvider = context.read(); + var localSchemeEntry = localSchemesProvider.schemes! + .firstWhere((ele) => ele.scheme.id == schemeProvider.id); + localSchemeEntry.scheme.uploaded = true; + localSchemeEntry.save(localSchemesProvider); + } else { + Notificator.error(context, title: LocaleKeys.info_upload_failed.tr()); + } + }); + } + }); }, ), ), diff --git a/app/lib/pages/market_or_me.dart b/app/lib/pages/market_or_me.dart index e769260..00e1a00 100644 --- a/app/lib/pages/market_or_me.dart +++ b/app/lib/pages/market_or_me.dart @@ -1,10 +1,10 @@ -import 'package:auto_size_text/auto_size_text.dart'; import 'package:dde_gesture_manager/constants/constants.dart'; import 'package:dde_gesture_manager/extensions.dart'; import 'package:dde_gesture_manager/models/configs.provider.dart'; import 'package:dde_gesture_manager/models/content_layout.provider.dart'; import 'package:dde_gesture_manager/widgets/dde_button.dart'; import 'package:dde_gesture_manager/widgets/login.dart'; +import 'package:dde_gesture_manager/widgets/me.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; @@ -82,33 +82,7 @@ class MarketOrMe extends StatelessWidget { Widget buildMeContent(BuildContext context) { var accessToken = context.watch().accessToken; if (accessToken.isNull) return LoginWidget(); - - return Padding( - padding: EdgeInsets.symmetric(vertical: 10), - child: Column( - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Icon(Icons.person, size: defaultButtonHeight), - Flexible( - child: AutoSizeText( - context.watch().email ?? '', - style: TextStyle( - fontSize: 18, - ), - maxLines: 1, - ), - ), - DButton.logout( - enabled: true, - onTap: () => context.read().setProps(accessToken: '', email: ''), - ), - ], - ), - ], - ), - ); + return MeWidget(); } Widget buildMarketContent(BuildContext context) { diff --git a/app/lib/utils/helper.dart b/app/lib/utils/helper.dart index 88c9b19..b4b1ad3 100644 --- a/app/lib/utils/helper.dart +++ b/app/lib/utils/helper.dart @@ -34,6 +34,8 @@ class H { BuildContext get topContext => _topContext; + DateTime? lastCheckAuthStatusTime; + initTopContext(BuildContext context) { _topContext = context; } diff --git a/app/lib/widgets/login.dart b/app/lib/widgets/login.dart index 20e1386..ef648ce 100644 --- a/app/lib/widgets/login.dart +++ b/app/lib/widgets/login.dart @@ -54,6 +54,12 @@ class _LoginWidgetState extends State { title: LocaleKeys.info_sign_up_hint_title.tr(), description: LocaleKeys.info_sign_up_hint_description.tr(), ); + else if (code == HttpStatus.forbidden) + Notificator.info( + context, + title: LocaleKeys.info_user_blocked_hint_title.tr(), + description: LocaleKeys.info_user_blocked_hint_description.tr(), + ); else throw e; } diff --git a/app/lib/widgets/me.dart b/app/lib/widgets/me.dart new file mode 100644 index 0000000..3cd6663 --- /dev/null +++ b/app/lib/widgets/me.dart @@ -0,0 +1,69 @@ +import 'package:auto_size_text/auto_size_text.dart'; +import 'package:dde_gesture_manager/constants/constants.dart'; +import 'package:dde_gesture_manager/http/api.dart'; +import 'package:dde_gesture_manager/models/configs.provider.dart'; +import 'package:dde_gesture_manager_api/models.dart'; +import 'package:flutter/material.dart'; +import 'package:dde_gesture_manager/extensions.dart'; + +import 'dde_button.dart'; + +class MeWidget extends StatefulWidget { + const MeWidget({Key? key}) : super(key: key); + + @override + _MeWidgetState createState() => _MeWidgetState(); +} + +class _MeWidgetState extends State { + List uploads = []; + + @override + void initState() { + super.initState(); + Api.userUploads().then((value) { + if (mounted) + setState(() { + uploads = value; + }); + }); + } + + @override + Widget build(BuildContext context) { + return Padding( + padding: EdgeInsets.symmetric(vertical: 10), + child: Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Icon(Icons.person, size: defaultButtonHeight), + Flexible( + child: AutoSizeText( + context.watch().email ?? '', + style: TextStyle( + fontSize: 18, + ), + maxLines: 1, + ), + ), + DButton.logout( + enabled: true, + onTap: () => context.read().setProps(accessToken: '', email: ''), + ), + ], + ), + Text('我的上传'), + Container( + height: 400, + child: ListView.builder( + itemBuilder: (context, index) => Text(uploads[index].name ?? ''), + itemCount: uploads.length, + ), + ), + ], + ), + ); + } +} diff --git a/app/resources/langs/en.json b/app/resources/langs/en.json index f0eb2bf..5fbc48c 100644 --- a/app/resources/langs/en.json +++ b/app/resources/langs/en.json @@ -138,6 +138,14 @@ "upload_and_share": { "title": "Share the scheme at the same time?", "description": "If you select [Share], other users can see this scheme and download it;\nIf you select [Cancel], you can still find this scheme in the [My Upload] list and share." + }, + "user_blocked_hint": { + "title": "The account has been blocked!", + "description": "If you have any questions, please contact me using email." + }, + "upload": { + "success": "Upload success ~", + "failed": "Upload failed.." } }, "me": { diff --git a/app/resources/langs/zh-CN.json b/app/resources/langs/zh-CN.json index 737f8c1..d613146 100644 --- a/app/resources/langs/zh-CN.json +++ b/app/resources/langs/zh-CN.json @@ -138,6 +138,14 @@ "upload_and_share": { "title": "是否同时分享到方案市场?", "description": "如果选择[分享],其他用户可以看到本方案并下载使用;\n如果选择[放弃],您仍可以稍后在[我的上传]列表中找到本方案进行操作。" + }, + "user_blocked_hint": { + "title": "该账号已被封禁!", + "description": "如有疑问请通过发送邮件联系" + }, + "upload": { + "success": "上传成功~", + "failed": "上传失败。。" } }, "me": {