用户资料

This commit is contained in:
2022-10-26 13:24:11 +08:00
parent 69f0a7a81e
commit afee57df73
12 changed files with 368 additions and 35 deletions

View File

@@ -25,6 +25,10 @@
<category android:name="android.intent.category.LAUNCHER"/> <category android:name="android.intent.category.LAUNCHER"/>
</intent-filter> </intent-filter>
</activity> </activity>
<activity
android:name="com.yalantis.ucrop.UCropActivity"
android:screenOrientation="portrait"
android:theme="@style/Theme.AppCompat.Light.NoActionBar"/>
<!-- Don't delete the meta-data below. <!-- Don't delete the meta-data below.
s used by the Flutter tool to generate GeneratedPluginRegistrant.java --> s used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
<meta-data <meta-data

View File

@@ -0,0 +1,44 @@
import 'package:chat/models/upload_model.dart';
import 'package:chat/models/user_info_model.dart';
import 'package:chat/providers/user_provider.dart';
import 'package:chat/services/auth_service.dart';
import 'package:chat/services/tim/friend_service.dart';
import 'package:chat/utils/ui_tools.dart';
import 'package:get/get.dart';
class UserController extends GetxController {
static UserController get to => Get.find<UserController>();
/// 用户信息,这个数据,在更新用户资料的时候,也应该更新
Rx<UserInfoModel> userInfo = UserInfoModel.empty().obs;
Future<bool> 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<void> 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());
}
}
}
}

View File

@@ -3,6 +3,7 @@ import 'package:chat/configs/themes.dart';
import 'package:chat/controllers/group_controller.dart'; import 'package:chat/controllers/group_controller.dart';
import 'package:chat/controllers/moment_controller.dart'; import 'package:chat/controllers/moment_controller.dart';
import 'package:chat/controllers/private_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_router.dart';
import 'package:chat/routes/app_routes.dart'; import 'package:chat/routes/app_routes.dart';
import 'package:chat/services/auth_service.dart'; import 'package:chat/services/auth_service.dart';
@@ -61,6 +62,10 @@ class MyApp extends StatelessWidget {
() => MomentController(), () => MomentController(),
fenix: true, fenix: true,
); );
Get.lazyPut(
() => UserController(),
fenix: true,
);
}, },
), ),
); );

View File

@@ -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<UploadModel?> 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<bool> updateNickname(String nickname) async {
try {
await Http.put('user/setting/nickname', data: {
'value': nickname,
});
return true;
} catch (e) {
UiTools.toast(e.toString());
return false;
}
}
}

View File

@@ -1,6 +1,5 @@
import 'package:chat/middleware/auth_middleware.dart'; import 'package:chat/middleware/auth_middleware.dart';
import 'package:chat/views/contact/index/index_page.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/index_page.dart';
import 'package:chat/views/user/info/nickname_page.dart'; import 'package:chat/views/user/info/nickname_page.dart';
import 'package:chat/views/user/qr_code/index_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 safe = '/user/safe';
static const String info = '/user/info'; static const String info = '/user/info';
static const String infoNickname = '/user/info/nickname'; static const String infoNickname = '/user/info/nickname';
static const String infoAvatar = '/user/info/avatar';
static GetPage router = GetPage( static GetPage router = GetPage(
name: UserRoutes.index, name: UserRoutes.index,
@@ -51,10 +49,6 @@ abstract class UserRoutes {
name: '/nickname', name: '/nickname',
page: () => const UserInfoNicknamePage(), page: () => const UserInfoNicknamePage(),
), ),
GetPage(
name: '/avatar',
page: () => const UserInfoAvatarPage(),
),
], ],
), ),
], ],

View File

@@ -86,4 +86,17 @@ class AuthService extends GetxService {
isLogin.value = false; isLogin.value = false;
Get.offAllNamed(AuthRoutes.index); Get.offAllNamed(AuthRoutes.index);
} }
Future<void> 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());
}
} }

View File

@@ -57,17 +57,6 @@ class _UserPageState extends State<UserPage> {
Get.toNamed(UserRoutes.setting); Get.toNamed(UserRoutes.setting);
}, },
), ),
const Divider(
height: 0,
color: AppColors.border,
indent: 16,
),
ActionItem(
'检查更新',
onTap: () {
Get.toNamed(UserRoutes.setting);
},
),
const Divider( const Divider(
height: 0, height: 0,
color: AppColors.border, color: AppColors.border,

View File

@@ -2,8 +2,10 @@ import 'package:chat/configs/app_colors.dart';
import 'package:chat/routes/user_routes.dart'; import 'package:chat/routes/user_routes.dart';
import 'package:chat/services/auth_service.dart'; import 'package:chat/services/auth_service.dart';
import 'package:chat/utils/convert.dart'; import 'package:chat/utils/convert.dart';
import 'package:chat/utils/ui_tools.dart';
import 'package:chat/widgets/custom_avatar.dart'; import 'package:chat/widgets/custom_avatar.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
class UserTopBar extends StatelessWidget { class UserTopBar extends StatelessWidget {
@@ -45,7 +47,32 @@ class UserTopBar extends StatelessWidget {
fontWeight: FontWeight.w400, 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()), Expanded(child: Container()),

View File

@@ -1,15 +0,0 @@
import 'package:flutter/material.dart';
class UserInfoAvatarPage extends StatefulWidget {
const UserInfoAvatarPage({Key? key}) : super(key: key);
@override
State<UserInfoAvatarPage> createState() => _UserInfoAvatarPageState();
}
class _UserInfoAvatarPageState extends State<UserInfoAvatarPage> {
@override
Widget build(BuildContext context) {
return Container();
}
}

View File

@@ -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: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 { class UserInfoPage extends StatefulWidget {
const UserInfoPage({Key? key}) : super(key: key); const UserInfoPage({Key? key}) : super(key: key);
@@ -12,8 +21,83 @@ class _UserInfoPageState extends State<UserInfoPage> {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
title: const Text('用户资料'), title: const Text('个人资料'),
),
body: GetX<AuthService>(
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<void> _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);
}
}
} }

View File

@@ -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:flutter/material.dart';
import 'package:get/get.dart';
class UserInfoNicknamePage extends StatefulWidget { class UserInfoNicknamePage extends StatefulWidget {
const UserInfoNicknamePage({Key? key}) : super(key: key); const UserInfoNicknamePage({Key? key}) : super(key: key);
@@ -8,8 +11,84 @@ class UserInfoNicknamePage extends StatefulWidget {
} }
class _UserInfoNicknamePageState extends State<UserInfoNicknamePage> { class _UserInfoNicknamePageState extends State<UserInfoNicknamePage> {
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 @override
Widget build(BuildContext context) { 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,
),
),
),
),
),
),
);
} }
} }

View File

@@ -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),
],
);
}
}