From afee57df735f91fb420f1b652eaa1d8f4745656c Mon Sep 17 00:00:00 2001 From: Jason Date: Wed, 26 Oct 2022 13:24:11 +0800 Subject: [PATCH] =?UTF-8?q?=E7=94=A8=E6=88=B7=E8=B5=84=E6=96=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- android/app/src/main/AndroidManifest.xml | 4 + lib/controllers/user_controller.dart | 44 ++++++++++ lib/main.dart | 5 ++ lib/providers/user_provider.dart | 35 ++++++++ lib/routes/user_routes.dart | 6 -- lib/services/auth_service.dart | 13 +++ lib/views/user/index/index_page.dart | 11 --- .../user/index/widgets/user_top_bar.dart | 29 ++++++- lib/views/user/info/avatar_page.dart | 15 ---- lib/views/user/info/index_page.dart | 86 ++++++++++++++++++- lib/views/user/info/nickname_page.dart | 81 ++++++++++++++++- lib/views/user/widgets/link_action_item.dart | 74 ++++++++++++++++ 12 files changed, 368 insertions(+), 35 deletions(-) create mode 100644 lib/controllers/user_controller.dart create mode 100644 lib/providers/user_provider.dart delete mode 100644 lib/views/user/info/avatar_page.dart create mode 100644 lib/views/user/widgets/link_action_item.dart diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 9faa164..76699a9 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -25,6 +25,10 @@ + Get.find(); + + /// 用户信息,这个数据,在更新用户资料的时候,也应该更新 + Rx userInfo = UserInfoModel.empty().obs; + + Future updateNickname(String nickname) async { + var result = await UserProvider.updateNickname(nickname); + if (result) { + AuthService.to.updateUserInfo('nickname', nickname); + UiTools.toast('昵称修改成功'); + } + + try { + TimFriendService.to.setSelfInfo(nickname: nickname); + } catch (e) { + UiTools.toast(e.toString()); + } + + return result; + } + + Future uploadAvatar(String filePath) async { + UploadModel? result = await UserProvider.uploadAvatar(filePath); + if (result != null) { + AuthService.to.updateUserInfo('avatar', result.url); + UiTools.toast('头像修改成功'); + + try { + TimFriendService.to.setSelfInfo(avatar: result.url); + } catch (e) { + UiTools.toast(e.toString()); + } + } + } +} diff --git a/lib/main.dart b/lib/main.dart index ff3c6b0..dfc4a95 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -3,6 +3,7 @@ import 'package:chat/configs/themes.dart'; import 'package:chat/controllers/group_controller.dart'; import 'package:chat/controllers/moment_controller.dart'; import 'package:chat/controllers/private_controller.dart'; +import 'package:chat/controllers/user_controller.dart'; import 'package:chat/routes/app_router.dart'; import 'package:chat/routes/app_routes.dart'; import 'package:chat/services/auth_service.dart'; @@ -61,6 +62,10 @@ class MyApp extends StatelessWidget { () => MomentController(), fenix: true, ); + Get.lazyPut( + () => UserController(), + fenix: true, + ); }, ), ); diff --git a/lib/providers/user_provider.dart b/lib/providers/user_provider.dart new file mode 100644 index 0000000..c85d4cd --- /dev/null +++ b/lib/providers/user_provider.dart @@ -0,0 +1,35 @@ +import 'package:chat/models/upload_model.dart'; +import 'package:chat/utils/network/http.dart'; +import 'package:chat/utils/ui_tools.dart'; + +class UserProvider { + /// 上传头像 + static Future uploadAvatar(String filePath) async { + try { + var res = await Http.upload('storage/upload', filePath: filePath); + + var model = UploadModel.fromJson(res); + await Http.put('user/setting/avatar', data: { + 'value': model.url, + }); + + return model; + } catch (e) { + UiTools.toast(e.toString()); + return null; + } + } + + static Future updateNickname(String nickname) async { + try { + await Http.put('user/setting/nickname', data: { + 'value': nickname, + }); + + return true; + } catch (e) { + UiTools.toast(e.toString()); + return false; + } + } +} diff --git a/lib/routes/user_routes.dart b/lib/routes/user_routes.dart index db22d18..ecd36ce 100644 --- a/lib/routes/user_routes.dart +++ b/lib/routes/user_routes.dart @@ -1,6 +1,5 @@ import 'package:chat/middleware/auth_middleware.dart'; import 'package:chat/views/contact/index/index_page.dart'; -import 'package:chat/views/user/info/avatar_page.dart'; import 'package:chat/views/user/info/index_page.dart'; import 'package:chat/views/user/info/nickname_page.dart'; import 'package:chat/views/user/qr_code/index_page.dart'; @@ -18,7 +17,6 @@ abstract class UserRoutes { static const String safe = '/user/safe'; static const String info = '/user/info'; static const String infoNickname = '/user/info/nickname'; - static const String infoAvatar = '/user/info/avatar'; static GetPage router = GetPage( name: UserRoutes.index, @@ -51,10 +49,6 @@ abstract class UserRoutes { name: '/nickname', page: () => const UserInfoNicknamePage(), ), - GetPage( - name: '/avatar', - page: () => const UserInfoAvatarPage(), - ), ], ), ], diff --git a/lib/services/auth_service.dart b/lib/services/auth_service.dart index 52577c4..f6eab6c 100644 --- a/lib/services/auth_service.dart +++ b/lib/services/auth_service.dart @@ -86,4 +86,17 @@ class AuthService extends GetxService { isLogin.value = false; Get.offAllNamed(AuthRoutes.index); } + + Future updateUserInfo(String key, String value) async { + // userInfo.value.nickname = nickname; + if (key == 'nickname') { + userInfo.value.nickname = value; + } else if (key == 'avatar') { + userInfo.value.avatar = value; + } + + userInfo.refresh(); + + _box.write('userInfo', userInfo.toJson()); + } } diff --git a/lib/views/user/index/index_page.dart b/lib/views/user/index/index_page.dart index 2e0ac26..9232781 100644 --- a/lib/views/user/index/index_page.dart +++ b/lib/views/user/index/index_page.dart @@ -57,17 +57,6 @@ class _UserPageState extends State { Get.toNamed(UserRoutes.setting); }, ), - const Divider( - height: 0, - color: AppColors.border, - indent: 16, - ), - ActionItem( - '检查更新', - onTap: () { - Get.toNamed(UserRoutes.setting); - }, - ), const Divider( height: 0, color: AppColors.border, diff --git a/lib/views/user/index/widgets/user_top_bar.dart b/lib/views/user/index/widgets/user_top_bar.dart index 0d71c61..4298d7a 100644 --- a/lib/views/user/index/widgets/user_top_bar.dart +++ b/lib/views/user/index/widgets/user_top_bar.dart @@ -2,8 +2,10 @@ import 'package:chat/configs/app_colors.dart'; import 'package:chat/routes/user_routes.dart'; import 'package:chat/services/auth_service.dart'; import 'package:chat/utils/convert.dart'; +import 'package:chat/utils/ui_tools.dart'; import 'package:chat/widgets/custom_avatar.dart'; import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import 'package:get/get.dart'; class UserTopBar extends StatelessWidget { @@ -45,7 +47,32 @@ class UserTopBar extends StatelessWidget { fontWeight: FontWeight.w400, ), ), - Text(Convert.hideCenterStr(_.userInfo.value.address ?? '')), + Row( + children: [ + Text( + Convert.hideCenterStr(_.userInfo.value.address ?? ''), + style: const TextStyle( + color: AppColors.unactive, + ), + ), + const SizedBox(width: 4), + InkWell( + onTap: () { + Clipboard.setData( + ClipboardData( + text: _.userInfo.value.address, + ), + ); + UiTools.toast('地址复制成功'); + }, + child: const Icon( + Icons.copy, + size: 12, + color: AppColors.unactive, + ), + ), + ], + ), ], ), Expanded(child: Container()), diff --git a/lib/views/user/info/avatar_page.dart b/lib/views/user/info/avatar_page.dart deleted file mode 100644 index c1ac02c..0000000 --- a/lib/views/user/info/avatar_page.dart +++ /dev/null @@ -1,15 +0,0 @@ -import 'package:flutter/material.dart'; - -class UserInfoAvatarPage extends StatefulWidget { - const UserInfoAvatarPage({Key? key}) : super(key: key); - - @override - State createState() => _UserInfoAvatarPageState(); -} - -class _UserInfoAvatarPageState extends State { - @override - Widget build(BuildContext context) { - return Container(); - } -} diff --git a/lib/views/user/info/index_page.dart b/lib/views/user/info/index_page.dart index 47f5618..d7f6a8a 100644 --- a/lib/views/user/info/index_page.dart +++ b/lib/views/user/info/index_page.dart @@ -1,4 +1,13 @@ +import 'package:chat/configs/app_colors.dart'; +import 'package:chat/controllers/user_controller.dart'; +import 'package:chat/routes/user_routes.dart'; +import 'package:chat/services/auth_service.dart'; +import 'package:chat/views/user/widgets/link_action_item.dart'; +import 'package:chat/widgets/custom_circle_avatar.dart'; import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:image_cropper/image_cropper.dart'; +import 'package:wechat_assets_picker/wechat_assets_picker.dart'; class UserInfoPage extends StatefulWidget { const UserInfoPage({Key? key}) : super(key: key); @@ -12,8 +21,83 @@ class _UserInfoPageState extends State { Widget build(BuildContext context) { return Scaffold( appBar: AppBar( - title: const Text('用户资料'), + title: const Text('个人资料'), + ), + body: GetX( + builder: (_) { + return SafeArea( + child: Column( + children: [ + LinkActionItem( + title: '头像', + onTap: () async { + final result = await AssetPicker.pickAssets( + Get.context!, + pickerConfig: const AssetPickerConfig( + maxAssets: 1, + requestType: RequestType.image, + ), + ); + + if (result == null) { + return; + } + + _cropImage((await result.first.file)!.path); + }, + trailing: CustomCircleAvatar( + _.userInfo.value.avatar, + size: 44, + borderWidth: 0.8, + borderColor: AppColors.primary, + ), + ), + LinkActionItem( + title: '昵称', + trailing: Text(_.userInfo.value.nickname!), + isLink: true, + onTap: () { + Get.toNamed( + UserRoutes.infoNickname, + arguments: { + 'nickname': _.userInfo.value.nickname, + }, + ); + }, + ), + Expanded(child: Container()), + ], + ), + ); + }, ), ); } + + Future _cropImage(String imagePath) async { + CroppedFile? croppedFile = await ImageCropper().cropImage( + sourcePath: imagePath, + maxHeight: 128, + maxWidth: 128, + compressQuality: 70, + aspectRatio: const CropAspectRatio( + ratioX: 1, + ratioY: 1, + ), + compressFormat: ImageCompressFormat.png, + uiSettings: [ + IOSUiSettings( + title: '头像剪裁', + doneButtonTitle: '完成', + cancelButtonTitle: '取消', + ), + AndroidUiSettings( + toolbarTitle: '头像剪裁', + ), + ], + ); + if (croppedFile != null) { + UserController.to.uploadAvatar(croppedFile.path); + } + } } diff --git a/lib/views/user/info/nickname_page.dart b/lib/views/user/info/nickname_page.dart index 55c7676..6215709 100644 --- a/lib/views/user/info/nickname_page.dart +++ b/lib/views/user/info/nickname_page.dart @@ -1,4 +1,7 @@ +import 'package:chat/configs/app_colors.dart'; +import 'package:chat/controllers/user_controller.dart'; import 'package:flutter/material.dart'; +import 'package:get/get.dart'; class UserInfoNicknamePage extends StatefulWidget { const UserInfoNicknamePage({Key? key}) : super(key: key); @@ -8,8 +11,84 @@ class UserInfoNicknamePage extends StatefulWidget { } class _UserInfoNicknamePageState extends State { + late TextEditingController _editingController; + late String _originNickname; + late String _nickname; + + @override + void initState() { + super.initState(); + _editingController = TextEditingController(text: Get.arguments['nickname']); + _nickname = Get.arguments['nickname']; + _originNickname = Get.arguments['nickname']; + } + + @override + void dispose() { + _editingController.dispose(); + super.dispose(); + } + @override Widget build(BuildContext context) { - return Container(); + return GestureDetector( + onTap: () { + FocusScope.of(context).requestFocus(FocusNode()); + }, + child: Scaffold( + appBar: AppBar( + title: const Text( + '修改昵称', + ), + actions: [ + TextButton( + style: ButtonStyle( + foregroundColor: MaterialStateProperty.resolveWith((states) { + if (states.contains(MaterialState.disabled)) { + return AppColors.unactive; + } else { + return AppColors.active; + } + }), + ), + onPressed: _nickname.length < 2 || _originNickname == _nickname + ? null + : () async { + if (await UserController.to + .updateNickname(_editingController.text)) { + Get.back(); + } + }, + child: const Text( + '保存', + ), + ), + ], + ), + body: Container( + padding: const EdgeInsets.all(32.0), + child: TextField( + controller: _editingController, + onChanged: (e) { + setState(() { + _nickname = e; + }); + }, + decoration: const InputDecoration( + labelText: '昵称', + hintText: '请输入昵称', + labelStyle: TextStyle( + color: AppColors.unactive, + ), + focusedBorder: UnderlineInputBorder( + borderSide: BorderSide( + color: AppColors.unactive, + ), + ), + ), + ), + ), + ), + ); } } diff --git a/lib/views/user/widgets/link_action_item.dart b/lib/views/user/widgets/link_action_item.dart new file mode 100644 index 0000000..73f627c --- /dev/null +++ b/lib/views/user/widgets/link_action_item.dart @@ -0,0 +1,74 @@ +import 'package:chat/configs/app_colors.dart'; +import 'package:chat/configs/app_size.dart'; +import 'package:flutter/material.dart'; + +class LinkActionItem extends StatelessWidget { + final IconData? prefix; + final String title; + final String? cover; + final Widget? trailing; + final bool? isLink; + final GestureTapCallback? onTap; + + const LinkActionItem({ + Key? key, + this.prefix, + this.cover, + required this.title, + this.isLink, + this.trailing, + this.onTap, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + GestureDetector( + onTap: onTap, + child: Container( + color: AppColors.white, + padding: const EdgeInsets.symmetric( + vertical: 20, + horizontal: 16, + ), + child: Row( + children: [ + if (prefix != null) + Icon( + prefix, + color: AppColors.unactive, + size: 18, + ), + const SizedBox(width: 8), + if (cover != null) + Image.asset( + 'assets/user/$cover.png', + width: 24, + height: 24, + ), + const SizedBox(width: 8), + Expanded( + child: Text( + title, + style: const TextStyle( + fontSize: 15, + ), + ), + ), + if (trailing != null) trailing!, + if (isLink == true) + const Icon( + Icons.chevron_right, + color: AppColors.unactive, + ), + ], + ), + ), + ), + const Divider(height: AppSize.dividerHeight), + ], + ); + } +}