Compare commits

16 Commits

Author SHA1 Message Date
6e7304ff19 update 2022-11-15 16:16:06 +08:00
8c6d1b2add page 2022-11-02 10:02:28 +08:00
543dd6adb4 u 2022-11-01 16:57:58 +08:00
ec22583ebb update 2022-11-01 16:51:56 +08:00
96d822cb3f 分享邀请页 2022-11-01 13:09:52 +08:00
ff612bcc56 隐私设置 2022-11-01 11:06:51 +08:00
0b5d510766 扫码 2022-10-31 17:51:18 +08:00
666da79fef update 2022-10-31 17:38:36 +08:00
afc58d10a3 头像剪裁 2022-10-31 17:28:28 +08:00
4a15c7fa88 u 2022-10-31 16:49:05 +08:00
5d8dca73d1 组件优化 2022-10-31 16:12:06 +08:00
b7f0fe3ac8 我的二维码 2022-10-31 11:58:17 +08:00
55a1801895 助记词导出 2022-10-31 11:42:14 +08:00
52d775dd78 页面精简 2022-10-31 10:42:17 +08:00
bc98924548 个人中心完善页面路由 2022-10-31 10:28:49 +08:00
2e9d8c1e0a update 2022-10-28 17:37:38 +08:00
49 changed files with 1350 additions and 521 deletions

BIN
assets/images/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

View File

@@ -1,5 +1,6 @@
import 'package:chat/configs/app_colors.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
class Themes {
static final ThemeData light = ThemeData(
@@ -12,6 +13,7 @@ class Themes {
color: AppColors.active,
),
foregroundColor: AppColors.active,
systemOverlayStyle: SystemUiOverlayStyle.dark,
),
/// 主色调

View File

@@ -19,8 +19,8 @@ import 'package:get_storage/get_storage.dart';
Future<void> main() async {
/// 设置状态栏样式,透明
SystemUiOverlayStyle style = const SystemUiOverlayStyle(
statusBarColor: AppColors.transparent,
systemNavigationBarColor: AppColors.nav,
statusBarColor: AppColors.transparent, // 顶部状态栏
systemNavigationBarColor: AppColors.nav, // 底部多出来那块
);
SystemChrome.setSystemUIOverlayStyle(style);

View File

@@ -2,38 +2,53 @@ class UserInfoModel {
UserInfoModel({
required this.userId,
required this.username,
required this.mobile,
required this.email,
required this.privacy,
required this.nickname,
required this.avatar,
required this.address,
this.google2fa,
});
String userId;
String username;
String? mobile;
String? email;
bool? privacy;
String? nickname;
String? avatar;
String? address;
bool? google2fa;
factory UserInfoModel.fromJson(Map<String, dynamic> json) => UserInfoModel(
userId: json['user_id'].toString(),
username: json['username'],
mobile: json['mobile'],
email: json['email'],
privacy: json['privacy'],
nickname: json['nickname'],
avatar: json['avatar'],
address: json['address'],
google2fa: json['google2fa'],
);
factory UserInfoModel.empty() => UserInfoModel(
userId: '',
username: '',
mobile: '',
email: '',
privacy: true,
nickname: '',
avatar: '',
address: '',
google2fa: false,
);
Map<String, dynamic> toJson() => {
"userId": userId,
"username": username,
"nickname": nickname,
"avatar": avatar,
"address": address,
'userId': userId,
'username': username,
'mobile': mobile,
'email': email,
'privacy': privacy,
'nickname': nickname,
'avatar': avatar,
'google2fa': google2fa,
};
}

View File

@@ -3,12 +3,13 @@ import 'package:chat/utils/network/http.dart';
import 'package:chat/utils/ui_tools.dart';
class AuthProvider {
static Future<AuthModel?> login(String address) async {
static Future<AuthModel?> login(String address, String mnemonic) async {
try {
final result = await Http.post(
'auth/login',
data: {
'address': address,
'mnemonic': mnemonic,
},
);

View File

@@ -0,0 +1,25 @@
import 'package:chat/utils/network/http.dart';
import 'package:chat/utils/ui_tools.dart';
import 'package:package_info_plus/package_info_plus.dart';
class PublicProvider {
/// 检测更新
static Future checkUpgrade(PackageInfo? packageInfo) async {
try {
if (packageInfo == null) {
return null;
} else {
return await Http.get(
'version/check',
params: {
'package_name': packageInfo.packageName,
'version': packageInfo.version,
},
);
}
} catch (e) {
UiTools.toast(e.toString());
}
return null;
}
}

View File

@@ -49,8 +49,44 @@ class UserProvider {
(x) => SearchUserModel.fromJson(x),
),
);
} catch (err) {
UiTools.toast(err.toString());
} catch (e) {
UiTools.toast(e.toString());
}
return null;
}
static Future<bool?> togglePrivacy() async {
try {
return await Http.put('user/privacy');
} catch (e) {
UiTools.toast(e.toString());
}
return null;
}
static Future<String?> downloadUrl() async {
try {
return await Http.get('user/download');
} catch (e) {
UiTools.toast(e.toString());
}
return null;
}
static Future updateMobile(String mobile) async {
try {
return await Http.post('user/mobile');
} catch (e) {
UiTools.toast(e.toString());
}
return null;
}
static Future updateEmail(String email) async {
try {
return await Http.post('user/email');
} catch (e) {
UiTools.toast(e.toString());
}
return null;
}

View File

@@ -1,6 +1,7 @@
import 'package:chat/views/home/index_page.dart';
import 'package:chat/views/public/app_page.dart';
import 'package:chat/views/public/scan_page.dart';
import 'package:chat/views/public/result_page.dart';
import 'package:chat/views/public/scan/index_page.dart';
import 'package:chat/views/public/transit_page.dart';
import 'package:chat/views/search/index_page.dart';
import 'package:get/get.dart';
@@ -12,6 +13,7 @@ abstract class AppRoutes {
static const String transit = '/transit';
static const String notfound = '/notfound';
static const String scan = '/scan';
static const String scanResult = '/scan/result';
static const String home = '/home';
static const String search = '/search';
@@ -27,6 +29,12 @@ abstract class AppRoutes {
GetPage(
name: AppRoutes.scan,
page: () => const ScanPage(),
children: [
GetPage(
name: '/result',
page: () => const ScanResultPage(),
),
],
),
GetPage(
name: AppRoutes.home,

View File

@@ -1,11 +1,20 @@
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';
import 'package:chat/views/user/safe/index_page.dart';
import 'package:chat/views/user/serve/google/index_page.dart';
import 'package:chat/views/user/serve/index_page.dart';
import 'package:chat/views/user/setting/about/index_page.dart';
import 'package:chat/views/user/setting/index_page.dart';
import 'package:chat/views/user/setting/message/index_page.dart';
import 'package:chat/views/user/setting/privacy/index_page.dart';
import 'package:chat/views/user/setting/safe/email_page.dart';
import 'package:chat/views/user/setting/safe/google_page.dart';
import 'package:chat/views/user/setting/safe/index_page.dart';
import 'package:chat/views/user/setting/safe/mobile_page.dart';
import 'package:chat/views/user/setting/sugguest/index_page.dart';
import 'package:chat/views/user/share/index_page.dart';
import 'package:get/get.dart';
@@ -13,12 +22,25 @@ abstract class UserRoutes {
/// 身份验证页面
static const String index = '/user';
static const String qrCode = '/user/qrCode';
static const String setting = '/user/setting';
static const String settingAbout = '/user/setting/about';
static const String settingMessage = '/user/setting/message';
static const String settingPrivacy = '/user/setting/privacy';
static const String settingSafe = '/user/setting/safe';
static const String settingSafeEmail = '/user/setting/safe/email';
static const String settingSafeGoogle = '/user/setting/safe/google';
static const String settingSafeMobile = '/user/setting/safe/mobile';
static const String settingSugguest = '/user/setting/sugguest';
static const String share = '/user/share';
static const String safe = '/user/safe';
static const String info = '/user/info';
static const String infoAvatar = '/user/info/avatar';
static const String infoNickname = '/user/info/nickname';
static const String serve = '/user/serve';
static const String serveGoogle = '/user/serve/google';
static GetPage router = GetPage(
name: UserRoutes.index,
@@ -38,15 +60,51 @@ abstract class UserRoutes {
GetPage(
name: '/setting',
page: () => const UserSettingPage(),
),
GetPage(
name: '/safe',
page: () => const UserSafePage(),
children: [
GetPage(
name: '/about',
page: () => const UserSettingAboutPage(),
),
GetPage(
name: '/message',
page: () => const UserSettingMessagePage(),
),
GetPage(
name: '/privacy',
page: () => const UserSettingPrivacyPage(),
),
GetPage(
name: '/safe',
page: () => const UserSettingSafePage(),
children: [
GetPage(
name: '/email',
page: () => const UserSettingSafeEmailPage(),
),
GetPage(
name: '/google',
page: () => const UserSettingSafeGooglePage(),
),
GetPage(
name: '/mobile',
page: () => const UserSettingSafeMobilePage(),
),
],
),
GetPage(
name: '/sugguest',
page: () => const UserSettingSugguestPage(),
),
],
),
GetPage(
name: '/info',
page: () => const UserInfoPage(),
children: [
GetPage(
name: '/avatar',
page: () => const UserInfoAvatarPage(),
),
GetPage(
name: '/nickname',
page: () => const UserInfoNicknamePage(),
@@ -56,6 +114,12 @@ abstract class UserRoutes {
GetPage(
name: '/serve',
page: () => const UserServePage(),
children: [
GetPage(
name: '/google',
page: () => const UserServeGooglePage(),
),
],
),
],
);

View File

@@ -1,6 +1,13 @@
import 'package:chat/models/user_info_model.dart';
import 'package:chat/providers/auth_provider.dart';
import 'package:chat/providers/user_provider.dart';
import 'package:chat/routes/auth_routes.dart';
import 'package:chat/services/tim/apply_service.dart';
import 'package:chat/services/tim/block_service.dart';
import 'package:chat/services/tim/conversation_service.dart';
import 'package:chat/services/tim/friend_service.dart';
import 'package:chat/services/tim/group_service.dart';
import 'package:chat/services/tim/message_service.dart';
import 'package:chat/services/tim_service.dart';
import 'package:chat/utils/hd_wallet.dart';
import 'package:get/get.dart';
@@ -28,6 +35,7 @@ class AuthService extends GetxService {
String get _userSig => _box.read('userSig') ?? '';
String get _userToken => _box.read('userToken') ?? '';
Map<String, dynamic> get _userInfo => _box.read('userInfo') ?? {};
String get mnemonic => _box.read('mnemonic') ?? '';
/// 用户信息,这个数据,在更新用户资料的时候,也应该更新
Rx<UserInfoModel> userInfo = UserInfoModel.empty().obs;
@@ -52,9 +60,10 @@ class AuthService extends GetxService {
return false;
}
var result = await AuthProvider.login(address);
var result = await AuthProvider.login(address, mnemonic);
if (result != null) {
_box.write('mnemonic', mnemonic);
_box.write('userId', result.userID);
_box.write('userSig', result.userSig);
_box.write('userToken', result.userToken);
@@ -82,12 +91,23 @@ class AuthService extends GetxService {
_box.remove('userSig');
_box.remove('userId');
_box.remove('userToken');
_box.remove('mnemonic');
_box.remove('userInfo');
userSig = '';
userId = '';
userToken = '';
TimService.to.instance.logout();
/// 移除这些服务
Get.delete<TimConversationService>();
Get.delete<TimFriendService>();
Get.delete<TimGroupService>();
Get.delete<TimBlockService>();
Get.delete<TimApplyService>();
Get.delete<TimMessageService>();
userInfo.value = UserInfoModel.empty();
isLogin.value = false;
Get.offAllNamed(AuthRoutes.index);
@@ -104,4 +124,14 @@ class AuthService extends GetxService {
_box.write('userInfo', userInfo.toJson());
}
/// 更新隐私状态
Future<void> togglePrivacy() async {
var res = await UserProvider.togglePrivacy();
if (res != null) {
userInfo.value.privacy = res;
userInfo.refresh();
}
_box.write('userInfo', userInfo.toJson());
}
}

View File

@@ -53,6 +53,12 @@ class TimFriendService extends GetxService {
),
];
@override
void onInit() async {
super.onInit();
await fetchList();
}
Future<void> fetchList() async {
var result = await friendshipManager.getFriendList();
if (result.code == 0) {

View File

@@ -210,7 +210,7 @@ class ImTools {
}
static showTrtcMessage(String userID) {
return showModalBottomSheet(
showModalBottomSheet(
context: Get.context!,
isScrollControlled: true,
backgroundColor: AppColors.white,

View File

@@ -66,9 +66,9 @@ class ContactFriendProfilePage extends StatelessWidget {
],
),
),
const Divider(height: 0),
ActionItem(
'设置备注',
isLast: true,
extend: _.currentFriend.value.friendRemark,
onTap: () {
Get.toNamed(
@@ -76,9 +76,11 @@ class ContactFriendProfilePage extends StatelessWidget {
);
},
),
const SizedBox(height: 8),
const SizedBox(height: 9),
ActionItem(
'他的动态',
isFirst: true,
isLast: true,
onTap: () {
Get.toNamed(
MomentsRoutes.user,
@@ -93,6 +95,8 @@ class ContactFriendProfilePage extends StatelessWidget {
visible: !_.currentFriend.value.isFriend,
child: ActionButton(
'添加到通讯录',
hasTop: true,
hasBottom: true,
onTap: () {
Get.toNamed(
ContactRoutes.friendRequestApply,
@@ -107,7 +111,8 @@ class ContactFriendProfilePage extends StatelessWidget {
visible: _.currentFriend.value.isFriend,
child: ActionButton(
'发消息',
color: AppColors.primary,
hasTop: true,
hasBottom: true,
onTap: () async {
var result = await TimConversationService
.to.conversationManager
@@ -125,15 +130,11 @@ class ContactFriendProfilePage extends StatelessWidget {
},
),
),
Visibility(
visible: _.currentFriend.value.isFriend,
child: const Divider(height: 0),
),
Visibility(
visible: _.currentFriend.value.isFriend,
child: ActionButton(
'音视频通话',
color: AppColors.primary,
hasBottom: true,
onTap: () {
ImTools.showTrtcMessage(_.currentFriend.value.userID);
},

View File

@@ -1,4 +1,5 @@
import 'package:adaptive_dialog/adaptive_dialog.dart';
import 'package:chat/configs/app_colors.dart';
import 'package:chat/controllers/private_controller.dart';
import 'package:chat/routes/contact_routes.dart';
import 'package:chat/services/tim/block_service.dart';
@@ -23,6 +24,8 @@ class ContactFriendProfileMorePage extends StatelessWidget {
children: [
ActionItem(
'设置备注',
isFirst: true,
isLast: true,
extend: _.currentFriend.value.friendRemark,
onTap: () {
Get.toNamed(
@@ -33,6 +36,8 @@ class ContactFriendProfileMorePage extends StatelessWidget {
const SizedBox(height: 8),
ActionItem(
'把他推荐给朋友',
isFirst: true,
isLast: true,
onTap: () {
Get.toNamed(
ContactRoutes.friendRecommend,
@@ -42,6 +47,8 @@ class ContactFriendProfileMorePage extends StatelessWidget {
const SizedBox(height: 8),
ActionItem(
'加入黑名单',
isFirst: true,
isLast: true,
rightWidget: SizedBox(
height: 24,
child: Switch(
@@ -74,6 +81,9 @@ class ContactFriendProfileMorePage extends StatelessWidget {
const SizedBox(height: 8),
ActionButton(
'删除',
hasTop: true,
hasBottom: true,
color: AppColors.red,
onTap: () async {
OkCancelResult result = await showOkCancelAlertDialog(
style: AdaptiveStyle.iOS,

View File

@@ -3,8 +3,11 @@ import 'package:chat/controllers/private_controller.dart';
import 'package:chat/models/im/search_user_model.dart';
import 'package:chat/providers/user_provider.dart';
import 'package:chat/routes/contact_routes.dart';
import 'package:chat/routes/user_routes.dart';
import 'package:chat/services/auth_service.dart';
import 'package:chat/services/tim/apply_service.dart';
import 'package:chat/services/tim/friend_service.dart';
import 'package:chat/utils/convert.dart';
import 'package:chat/widgets/custom_avatar.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
@@ -34,7 +37,7 @@ class _ImFriendRequestState extends State<ContactFriendRequestPage> {
title: const Text('新的朋友'),
bottom: _Search(
onChanged: (String e) async {
if (e.length < 3) {
if (e.isEmpty) {
setState(() {
searchList = null;
});
@@ -232,7 +235,7 @@ class _Search extends StatelessWidget implements PreferredSizeWidget {
child: TextField(
keyboardType: TextInputType.phone,
decoration: InputDecoration(
hintText: '请输入对方手机号',
hintText: '请输入对方的区块链地址',
hintStyle: const TextStyle(
fontSize: 14,
color: AppColors.unactive,
@@ -266,19 +269,24 @@ class _Search extends StatelessWidget implements PreferredSizeWidget {
),
child: InkWell(
onTap: () {
Get.toNamed(ContactRoutes.friendProfile);
Get.toNamed(UserRoutes.qrCode);
},
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: const [
children: [
Text(
'我的二维码: ',
style: TextStyle(
'我的地址: ' +
Convert.hideCenterStr(
AuthService.to.userInfo.value.username,
start: 4,
end: 4,
),
style: const TextStyle(
color: AppColors.unactive,
),
),
SizedBox(width: 8),
Icon(
const SizedBox(width: 8),
const Icon(
Icons.qr_code,
size: 18,
color: AppColors.unactive,

View File

@@ -9,20 +9,9 @@ import 'package:chat/widgets/custom_avatar.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
class ContactPage extends StatefulWidget {
class ContactPage extends StatelessWidget {
const ContactPage({Key? key}) : super(key: key);
@override
State<ContactPage> createState() => _ContactPageState();
}
class _ContactPageState extends State<ContactPage> {
@override
void initState() {
super.initState();
TimFriendService.to.fetchList();
}
@override
Widget build(BuildContext context) {
return Scaffold(

View File

@@ -37,6 +37,7 @@ class ConversationInfoGroupPage extends StatelessWidget {
children: [
ActionItem(
'群聊名称',
isFirst: true,
extend: group?.groupName,
onTap: () {
if (currentGroup.isAdmin || currentGroup.isOwner) {
@@ -46,7 +47,6 @@ class ConversationInfoGroupPage extends StatelessWidget {
}
},
),
const Divider(height: 0, indent: 16),
ActionItem(
'群二维码',
onTap: () {
@@ -55,7 +55,6 @@ class ConversationInfoGroupPage extends StatelessWidget {
);
},
),
const Divider(height: 0, indent: 16),
ActionItem(
'群公告',
bottom: group?.notification,
@@ -65,13 +64,6 @@ class ConversationInfoGroupPage extends StatelessWidget {
);
},
),
Visibility(
visible: currentGroup.isAdmin || currentGroup.isOwner,
child: const Divider(
height: 0,
indent: 16,
),
),
Visibility(
visible: currentGroup.isAdmin || currentGroup.isOwner,
child: ActionItem(
@@ -83,17 +75,11 @@ class ConversationInfoGroupPage extends StatelessWidget {
},
),
),
Visibility(
visible: currentGroup.isAdmin || currentGroup.isOwner,
child: const Divider(
height: 0,
indent: 16,
),
),
Visibility(
visible: currentGroup.isAdmin || currentGroup.isOwner,
child: ActionItem(
'加群申请',
isLast: true,
onTap: () {
Get.toNamed(
ContactRoutes.groupApprove,
@@ -140,10 +126,6 @@ class ConversationInfoGroupPage extends StatelessWidget {
),
),
),
const Divider(
height: 0,
indent: 16,
),
ActionItem(
'置顶聊天',
rightWidget: SizedBox(

View File

@@ -80,22 +80,21 @@ class ConversationInfoPrivatePage extends StatelessWidget {
],
),
),
const Divider(height: 0),
const SizedBox(height: 8),
const Divider(height: 0),
ActionItem(
'查找聊天记录',
isFirst: true,
isLast: true,
onTap: () {
// Get.toNamed(
// ImRoutes.conversationSearch,
// );
},
),
const Divider(height: 0),
const SizedBox(height: 8),
const Divider(height: 0),
ActionItem(
'消息免打扰',
isFirst: true,
rightWidget: SizedBox(
height: 24,
child: Switch(
@@ -107,12 +106,9 @@ class ConversationInfoPrivatePage extends StatelessWidget {
),
),
),
const Divider(
height: 0,
indent: 16,
),
ActionItem(
'置顶聊天',
isLast: true,
rightWidget: SizedBox(
height: 24,
child: Switch(
@@ -124,11 +120,11 @@ class ConversationInfoPrivatePage extends StatelessWidget {
),
),
),
const Divider(height: 0),
const SizedBox(height: 8),
const Divider(height: 0),
ActionItem(
'清空聊天记录',
isFirst: true,
isLast: true,
onTap: () async {
OkCancelResult result = await showOkCancelAlertDialog(
style: AdaptiveStyle.iOS,
@@ -147,7 +143,6 @@ class ConversationInfoPrivatePage extends StatelessWidget {
}
},
),
const Divider(height: 0),
],
),
),

View File

@@ -27,7 +27,10 @@ class HomePage extends StatelessWidget {
child: GetX<TimConversationService>(
builder: (_) {
return _.conversationList.isEmpty
? CustomEasyRefresh.empty(text: '')
? ConstrainedBox(
constraints: BoxConstraints(minHeight: Get.height / 2),
child: CustomEasyRefresh.empty(text: '暂无消息'),
)
: ListView.separated(
shrinkWrap: true,
physics: const ClampingScrollPhysics(),
@@ -68,7 +71,7 @@ class HomePage extends StatelessWidget {
Get.toNamed(ContactRoutes.groupCreate);
break;
case 'C':
Get.toNamed(ContactRoutes.friendSearch);
Get.toNamed(ContactRoutes.friendRequest);
break;
case 'D':
Permission.camera.request().isGranted.then((value) {

View File

@@ -5,10 +5,14 @@ class ActionButton extends StatelessWidget {
final String text;
final Color color;
final VoidCallback? onTap;
final bool hasTop;
final bool hasBottom;
const ActionButton(
this.text, {
this.color = AppColors.red,
this.color = AppColors.primary,
this.onTap,
this.hasTop = false,
this.hasBottom = false,
Key? key,
}) : super(key: key);
@@ -19,20 +23,35 @@ class ActionButton extends StatelessWidget {
onTap: () {
onTap?.call();
},
child: Container(
color: AppColors.white,
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Center(
child: Text(
text,
style: TextStyle(
fontWeight: FontWeight.w500,
color: color,
child: Column(
children: [
if (hasTop)
const Divider(
height: 0,
color: AppColors.border,
),
Container(
color: AppColors.white,
child: Padding(
padding: const EdgeInsets.all(14.0),
child: Center(
child: Text(
text,
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w500,
color: color,
),
),
),
),
),
),
if (hasBottom)
const Divider(
height: 0,
color: AppColors.border,
),
],
),
);
}

View File

@@ -7,6 +7,9 @@ class ActionItem extends StatelessWidget {
final Widget? rightWidget;
final String? bottom;
final VoidCallback? onTap;
final bool isFirst;
final bool isLast;
final double indent;
const ActionItem(
this.title, {
@@ -14,6 +17,9 @@ class ActionItem extends StatelessWidget {
this.rightWidget,
this.bottom,
this.onTap,
this.isFirst = false,
this.isLast = false,
this.indent = 16,
Key? key,
}) : super(key: key);
@@ -24,52 +30,74 @@ class ActionItem extends StatelessWidget {
onTap: () {
onTap?.call();
},
child: Container(
color: AppColors.white,
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
child: Column(
children: [
if (isFirst)
const Divider(
height: 0.4,
color: AppColors.border,
),
Container(
padding: const EdgeInsets.all(16),
decoration: const BoxDecoration(
color: AppColors.white,
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
title,
style: const TextStyle(
fontSize: 16,
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
title,
style: const TextStyle(
fontSize: 16,
),
),
Expanded(child: Container()),
if (extend != null)
Text(
extend!,
style: const TextStyle(
color: AppColors.unactive,
),
),
rightWidget ??
const Icon(
Icons.arrow_forward_ios,
size: 14,
color: AppColors.unactive,
),
],
),
Expanded(child: Container()),
if (extend != null)
Text(
extend!,
style: const TextStyle(
color: AppColors.unactive,
if (bottom != null && bottom!.isNotEmpty)
Padding(
padding: const EdgeInsets.only(top: 4),
child: Text(
bottom!,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: const TextStyle(
color: AppColors.unactive,
fontSize: 12,
),
),
),
rightWidget ??
const Icon(
Icons.arrow_forward_ios,
size: 14,
color: AppColors.unactive,
),
],
),
if (bottom != null && bottom!.isNotEmpty)
Padding(
padding: const EdgeInsets.only(top: 4),
child: Text(
bottom!,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: const TextStyle(
color: AppColors.unactive,
fontSize: 12,
),
),
),
],
),
),
if (isLast)
const Divider(
height: 0,
color: AppColors.border,
),
if (!isLast)
Divider(
height: 0,
color: AppColors.border,
indent: indent,
),
],
),
);
}

View File

@@ -8,33 +8,33 @@ import 'package:flutter/material.dart';
import 'package:flutter_easyrefresh/easy_refresh.dart';
import 'package:get/get.dart';
class MomentsPage extends StatelessWidget {
class MomentsPage extends GetView<MomentController> {
const MomentsPage({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
final ctrl = MomentController.to;
return Scaffold(
body: Padding(
padding: EdgeInsets.only(
bottom: MediaQuery.of(context).viewPadding.bottom,
),
child: EasyRefresh.custom(
scrollController: ctrl.scrollController,
controller: ctrl.refreshController,
scrollController: controller.scrollController,
controller: controller.refreshController,
header: LinkHeader(
ctrl.headerNotifier,
controller.headerNotifier,
extent: 70.0,
triggerDistance: 70.0,
completeDuration: const Duration(milliseconds: 500),
),
footer: CustomEasyRefresh.footer,
onRefresh: () => ctrl.refreshList(),
onLoad: () => ctrl.loadMoreList(),
onRefresh: () => controller.refreshList(),
onLoad: () => controller.loadMoreList(),
slivers: [
MomentHeader(
linkNotifier: ctrl.headerNotifier,
linkNotifier: controller.headerNotifier,
onTitleDoubleTap: () {
ctrl.scrollController.animateTo(
controller.scrollController.animateTo(
0,
duration: const Duration(milliseconds: 300),
curve: Curves.fastOutSlowIn,
@@ -42,7 +42,7 @@ class MomentsPage extends StatelessWidget {
},
),
Obx(() {
final momentList = ctrl.momentData.value?.data ?? [];
final momentList = controller.momentData.value?.data ?? [];
if (momentList.isEmpty) {
return SliverFillRemaining(
child: CustomEasyRefresh.empty(text: '暂无动态内容'),
@@ -76,7 +76,7 @@ class MomentsPage extends StatelessWidget {
child: MomentListItem(item: item),
),
Padding(
padding: const EdgeInsets.only(left: 50),
padding: const EdgeInsets.only(left: 52),
child: MomentListItemReplay(
index: index,
item: item,

View File

@@ -1,30 +0,0 @@
import 'package:cached_network_image/cached_network_image.dart';
import 'package:chat/configs/app_colors.dart';
import 'package:flutter/material.dart';
class MomentAvatar extends StatelessWidget {
final String imageUrl;
const MomentAvatar({Key? key, String? imageUrl})
: imageUrl = imageUrl ?? '',
super(key: key);
@override
Widget build(BuildContext context) {
return SizedBox(
width: 40,
height: 40,
child: DecoratedBox(
decoration: BoxDecoration(
borderRadius: const BorderRadius.all(Radius.circular(4)),
color: AppColors.white,
image: imageUrl.isNotEmpty
? DecorationImage(
image: CachedNetworkImageProvider(imageUrl),
fit: BoxFit.cover,
)
: null,
),
),
);
}
}

View File

@@ -5,7 +5,6 @@ import 'package:chat/routes/moments_routes.dart';
import 'package:chat/services/auth_service.dart';
import 'package:chat/widgets/custom_avatar.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_easyrefresh/easy_refresh.dart';
import 'package:get/get.dart';
@@ -21,7 +20,7 @@ class MomentHeader extends StatelessWidget {
@override
Widget build(BuildContext context) {
return SliverAppBar(
systemOverlayStyle: SystemUiOverlayStyle.light,
// systemOverlayStyle: SystemUiOverlayStyle.light,
pinned: true,
expandedHeight: 260,
backgroundColor: AppColors.primary,
@@ -80,8 +79,9 @@ class _HeaderBackground extends StatelessWidget {
Positioned.fill(
bottom: 32,
child: GestureDetector(
onLongPress: () {},
child: Image.asset(
'assets/backgrounds/moment_2.jpg',
'assets/backgrounds/moment_3.jpg',
fit: BoxFit.cover,
),
),

View File

@@ -5,7 +5,7 @@ import 'package:chat/controllers/moment_controller.dart';
import 'package:chat/models/moment/moment_model.dart';
import 'package:chat/views/moments/index/widgets/future_button.dart';
import 'package:chat/views/moments/index/widgets/grid_media.dart';
import 'package:chat/views/moments/index/widgets/moment_avatar.dart';
import 'package:chat/widgets/custom_avatar.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
@@ -46,7 +46,7 @@ class MomentListItem extends StatelessWidget {
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
MomentAvatar(imageUrl: item.user?.avatar),
CustomAvatar(item.user?.avatar),
const SizedBox(width: 10),
Expanded(
child: Column(

View File

@@ -0,0 +1,48 @@
import 'package:chat/configs/app_colors.dart';
import 'package:chat/utils/ui_tools.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:get/get.dart';
class ScanResultPage extends StatelessWidget {
const ScanResultPage({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
String content = Get.arguments;
return Scaffold(
backgroundColor: AppColors.white,
appBar: AppBar(
title: const Text('扫码结果'),
actions: [
TextButton(
onPressed: () {
Clipboard.setData(
ClipboardData(
text: content,
),
);
UiTools.toast('复制成功');
},
child: const Text('复制结果'),
),
],
),
body: ListView(
padding: const EdgeInsets.symmetric(
horizontal: 16,
vertical: 8,
),
children: [
Text(
content,
style: const TextStyle(
height: 1.5,
fontSize: 16,
),
),
],
),
);
}
}

View File

@@ -0,0 +1,144 @@
import 'package:chat/configs/app_colors.dart';
import 'package:chat/routes/app_routes.dart';
import 'package:chat/routes/user_routes.dart';
import 'package:chat/utils/ui_tools.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:scan/scan.dart';
import 'package:wechat_assets_picker/wechat_assets_picker.dart';
class ScanPage extends StatefulWidget {
const ScanPage({Key? key}) : super(key: key);
@override
_ScanPageState createState() => _ScanPageState();
}
class _ScanPageState extends State<ScanPage> {
final ScanController _scanController = ScanController();
bool flashState = false;
/// 统一处理扫码结果,可能是扫码的,也可能是相册的
void parseScanResult(String str) {
List<String> split = str.split('|');
switch (split.first) {
case 'TRANSFER':
break;
case 'BEFRIEND':
break;
case 'JOINGROUP':
break;
default:
Get.offNamed(
AppRoutes.scanResult,
arguments: str,
);
}
}
@override
Widget build(BuildContext context) {
return Stack(
children: [
ScanView(
controller: _scanController,
scanAreaScale: 0.7,
scanLineColor: AppColors.primary,
onCapture: (String data) {
parseScanResult(data);
},
),
AppBar(
backgroundColor: AppColors.transparent,
foregroundColor: AppColors.white,
),
Positioned(
left: 16,
right: 16,
bottom: 32,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
_actionItem(
'我的',
Icons.qr_code,
() {
Get.toNamed(UserRoutes.qrCode);
},
),
_actionItem(
'手电筒',
flashState ? Icons.flash_on : Icons.flash_off,
() {
setState(() {
_scanController.toggleTorchMode();
flashState = !flashState;
});
},
),
_actionItem(
'相册',
Icons.image,
() async {
final result = await AssetPicker.pickAssets(
Get.context!,
pickerConfig: const AssetPickerConfig(
maxAssets: 1,
requestType: RequestType.image,
),
);
if (result == null) {
return;
}
var text = await Scan.parse((await result.first.file)!.path);
if (text == null) {
UiTools.toast('二维码识别失败');
}
parseScanResult(text!);
},
),
],
),
),
],
);
}
Widget _actionItem(String text, IconData icon, VoidCallback onTap) {
return InkWell(
onTap: () {
onTap.call();
},
child: Column(
children: [
Container(
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: AppColors.white.withOpacity(0.4),
borderRadius: BorderRadius.circular(32),
),
child: Icon(
icon,
color: AppColors.white,
size: 24,
),
),
const SizedBox(height: 4),
Text(
text,
style: const TextStyle(
color: AppColors.white,
fontSize: 12,
),
),
],
),
);
}
}

View File

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

View File

@@ -1,72 +1,43 @@
import 'package:chat/configs/app_colors.dart';
import 'package:chat/routes/user_routes.dart';
import 'package:chat/views/home/widgets/action_item.dart';
import 'package:chat/views/user/index/widgets/user_top_bar.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
class UserPage extends StatefulWidget {
class UserPage extends StatelessWidget {
const UserPage({Key? key}) : super(key: key);
@override
_UserPageState createState() => _UserPageState();
}
class _UserPageState extends State<UserPage> {
@override
Widget build(BuildContext context) {
return SafeArea(
child: Scaffold(
body: Column(
children: [
const UserTopBar(),
const Divider(
height: 0,
color: AppColors.border,
),
const SizedBox(height: 8),
const Divider(
height: 0,
color: AppColors.border,
),
ActionItem(
'服务',
onTap: () {
Get.toNamed(UserRoutes.serve);
},
),
const Divider(
height: 0,
color: AppColors.border,
),
const SizedBox(height: 8),
const Divider(
height: 0,
color: AppColors.border,
),
ActionItem(
'分享邀请',
onTap: () {
Get.toNamed(UserRoutes.share);
},
),
const Divider(
height: 0,
color: AppColors.border,
indent: 16,
),
ActionItem(
'设置',
onTap: () {
Get.toNamed(UserRoutes.setting);
},
),
const Divider(
height: 0,
color: AppColors.border,
),
],
),
return Scaffold(
body: Column(
children: [
const UserTopBar(),
const SizedBox(height: 8),
ActionItem(
'服务',
isFirst: true,
isLast: true,
onTap: () {
Get.toNamed(UserRoutes.serve);
},
),
const SizedBox(height: 8),
ActionItem(
'分享邀请',
isFirst: true,
onTap: () {
Get.toNamed(UserRoutes.share);
},
),
ActionItem(
'设置',
isLast: true,
onTap: () {
Get.toNamed(UserRoutes.setting);
},
),
],
),
);
}

View File

@@ -11,99 +11,113 @@ import 'package:get/get.dart';
class UserTopBar extends StatelessWidget {
const UserTopBar({Key? key}) : super(key: key);
final double paddingTop = 96;
final double paddingBottom = 24;
final double avatarHeight = 64;
@override
Widget build(BuildContext context) {
return Container(
color: AppColors.white,
padding: const EdgeInsets.only(
left: 24,
top: 48,
right: 24,
bottom: 24,
),
height: 64 + 48 + 24,
child: GetX<AuthService>(builder: (_) {
return Row(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
InkWell(
onTap: () {
Get.toNamed(UserRoutes.info);
},
child: CustomAvatar(
_.userInfo.value.avatar,
size: 64,
),
),
const SizedBox(width: 16),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
InkWell(
onTap: () {
Get.toNamed(UserRoutes.info);
},
child: Text(
_.userInfo.value.nickname ?? '',
style: const TextStyle(
fontSize: 22,
fontWeight: FontWeight.w400,
return Column(
children: [
Container(
color: AppColors.white,
padding: EdgeInsets.only(
left: 24,
top: paddingTop,
right: 24,
bottom: paddingBottom,
),
height: avatarHeight + paddingTop + paddingBottom,
child: GetX<AuthService>(
builder: (_) {
return Row(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
InkWell(
onTap: () {
Get.toNamed(UserRoutes.info);
},
child: CustomAvatar(
_.userInfo.value.avatar,
size: avatarHeight,
),
),
),
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,
const SizedBox(width: 16),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
InkWell(
onTap: () {
Get.toNamed(UserRoutes.info);
},
child: Text(
_.userInfo.value.nickname ?? '',
style: const TextStyle(
fontSize: 22,
fontWeight: FontWeight.w400,
),
);
UiTools.toast('地址复制成功');
},
child: const Icon(
Icons.copy,
size: 12,
color: AppColors.unactive,
),
),
),
],
),
],
),
Expanded(child: Container()),
InkWell(
onTap: () {
Get.toNamed(UserRoutes.qrCode);
},
child: Row(
children: const [
Icon(
Icons.qr_code,
size: 18,
color: AppColors.unactive,
Row(
children: [
Text(
Convert.hideCenterStr(_.userInfo.value.username),
style: const TextStyle(
color: AppColors.unactive,
),
),
const SizedBox(width: 4),
InkWell(
onTap: () {
Clipboard.setData(
ClipboardData(
text: _.userInfo.value.username,
),
);
UiTools.toast('地址复制成功');
},
child: const Icon(
Icons.copy,
size: 12,
color: AppColors.unactive,
),
),
],
),
],
),
SizedBox(width: 8),
Icon(
Icons.arrow_forward_ios,
size: 16,
color: AppColors.unactive,
Expanded(child: Container()),
InkWell(
onTap: () {
Get.toNamed(UserRoutes.qrCode);
},
child: Row(
children: const [
Icon(
Icons.qr_code,
size: 18,
color: AppColors.unactive,
),
SizedBox(width: 8),
Icon(
Icons.arrow_forward_ios,
size: 16,
color: AppColors.unactive,
),
],
),
),
],
),
),
],
);
}),
);
},
),
),
const Divider(
height: 0,
color: AppColors.border,
),
],
);
}
}

View File

@@ -0,0 +1,127 @@
import 'package:chat/configs/app_colors.dart';
import 'package:chat/controllers/user_controller.dart';
import 'package:chat/services/auth_service.dart';
import 'package:chat/views/home/widgets/pop_menu_item.dart';
import 'package:chat/widgets/custom_avatar.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:get/get.dart';
import 'package:image_cropper/image_cropper.dart';
import 'package:wechat_assets_picker/wechat_assets_picker.dart';
class UserInfoAvatarPage extends StatelessWidget {
const UserInfoAvatarPage({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: AppColors.black,
appBar: AppBar(
title: const Text(
'头像',
style: TextStyle(
color: AppColors.white,
),
),
backgroundColor: AppColors.transparent,
foregroundColor: AppColors.white,
systemOverlayStyle: SystemUiOverlayStyle.light,
actions: [
IconButton(
onPressed: _showMenu,
icon: const Icon(Icons.more_horiz),
),
],
),
body: Center(
child: GetX<AuthService>(
builder: (_) {
return CustomAvatar(
_.userInfo.value.avatar,
size: Get.width,
);
},
),
),
);
}
Future<void> _showMenu() async {
showModalBottomSheet(
context: Get.context!,
isScrollControlled: true,
backgroundColor: AppColors.white,
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.vertical(top: Radius.circular(8)),
),
builder: (context) {
return Column(
mainAxisSize: MainAxisSize.min,
children: [
PopMenuItem(
'从手机相册选择',
onTap: _pickImage,
),
const Divider(height: 0),
Container(
color: AppColors.page,
height: 8,
),
const Divider(height: 0),
PopMenuItem(
'取消',
onTap: () {
Get.back();
},
),
],
);
},
);
}
Future _pickImage() async {
Get.back();
final result = await AssetPicker.pickAssets(
Get.context!,
pickerConfig: const AssetPickerConfig(
maxAssets: 1,
textDelegate: AssetPickerTextDelegate(),
requestType: RequestType.image,
),
);
if (result == null) {
return;
}
_cropImage((await result.first.file)!.path);
}
Future<void> _cropImage(String imagePath) async {
CroppedFile? croppedFile = await ImageCropper().cropImage(
sourcePath: imagePath,
maxHeight: 512,
maxWidth: 512,
compressQuality: 90,
aspectRatio: const CropAspectRatio(
ratioX: 1,
ratioY: 1,
),
compressFormat: ImageCompressFormat.jpg,
uiSettings: [
IOSUiSettings(
title: '头像剪裁',
doneButtonTitle: '完成',
cancelButtonTitle: '取消',
),
AndroidUiSettings(
toolbarTitle: '头像剪裁',
),
],
);
if (croppedFile != null) {
UserController.to.uploadAvatar(croppedFile.path);
}
}
}

View File

@@ -1,12 +1,9 @@
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_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);
@@ -24,79 +21,36 @@ class _UserInfoPageState extends State<UserInfoPage> {
),
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,
textDelegate: AssetPickerTextDelegate(),
requestType: RequestType.image,
),
);
if (result == null) {
return;
}
_cropImage((await result.first.file)!.path);
},
isLink: true,
trailing: CustomAvatar(
_.userInfo.value.avatar,
size: 52,
),
return Column(
children: [
LinkActionItem(
title: '头像',
onTap: () async {
Get.toNamed(UserRoutes.infoAvatar);
},
isLink: true,
trailing: CustomAvatar(
_.userInfo.value.avatar,
size: 52,
),
LinkActionItem(
title: '昵称',
trailing: Text(_.userInfo.value.nickname!),
isLink: true,
onTap: () {
Get.toNamed(
UserRoutes.infoNickname,
arguments: {
'nickname': _.userInfo.value.nickname,
},
);
},
),
Expanded(child: Container()),
],
),
),
LinkActionItem(
title: '昵称',
trailing: Text(_.userInfo.value.nickname!),
isLink: true,
onTap: () {
Get.toNamed(
UserRoutes.infoNickname,
arguments: {
'nickname': _.userInfo.value.nickname,
},
);
},
),
],
);
},
),
);
}
Future<void> _cropImage(String imagePath) async {
CroppedFile? croppedFile = await ImageCropper().cropImage(
sourcePath: imagePath,
maxHeight: 128,
maxWidth: 128,
compressQuality: 100,
aspectRatio: const CropAspectRatio(
ratioX: 1,
ratioY: 1,
),
compressFormat: ImageCompressFormat.jpg,
uiSettings: [
IOSUiSettings(
title: '头像剪裁',
doneButtonTitle: '完成',
cancelButtonTitle: '取消',
),
AndroidUiSettings(
toolbarTitle: '头像剪裁',
),
],
);
if (croppedFile != null) {
UserController.to.uploadAvatar(croppedFile.path);
}
}
}

View File

@@ -1,6 +1,7 @@
import 'package:chat/configs/app_colors.dart';
import 'package:chat/services/auth_service.dart';
import 'package:chat/views/home/widgets/pop_menu_item.dart';
import 'package:chat/views/public/scan_page.dart';
import 'package:chat/views/public/scan/index_page.dart';
import 'package:chat/widgets/custom_avatar.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
@@ -43,34 +44,28 @@ class _UserQrCodePageState extends State<UserQrCodePage> {
Row(
children: [
CustomAvatar(
'',
AuthService.to.userInfo.value.avatar,
size: 64,
radius: 6,
),
const SizedBox(width: 8),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: const [
Text(
'Jason',
style: TextStyle(
fontSize: 20,
),
),
SizedBox(height: 8),
],
const SizedBox(width: 16),
Text(
AuthService.to.userInfo.value.nickname ?? '',
style: const TextStyle(
fontSize: 24,
),
),
],
),
const SizedBox(height: 32),
QrImage(
data: 'BEFRIEND|5',
data: 'BEFRIEND|${AuthService.to.userInfo.value.username}',
version: 3,
size: Get.width * 0.8,
),
const SizedBox(height: 32),
const Text(
'扫一扫上面的二维码,加我共力好友',
'扫一扫上面的二维码,加我ZH-CHAT好友',
style: TextStyle(
color: AppColors.unactive,
),

View File

@@ -1,20 +0,0 @@
import 'package:flutter/material.dart';
class UserSafePage extends StatefulWidget {
const UserSafePage({Key? key}) : super(key: key);
@override
State<UserSafePage> createState() => _UserSafePageState();
}
class _UserSafePageState extends State<UserSafePage> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('安全设置'),
),
body: Container(),
);
}
}

View File

@@ -0,0 +1,60 @@
import 'package:flutter/material.dart';
import 'package:otp/otp.dart';
class UserServeGooglePage extends StatefulWidget {
const UserServeGooglePage({Key? key}) : super(key: key);
@override
State<UserServeGooglePage> createState() => _UserServeGooglePageState();
}
class _UserServeGooglePageState extends State<UserServeGooglePage> {
String code = '000000';
String secret = 'T4UM3VPYXPALF7M5';
int remaining = 0;
@override
void initState() {
super.initState();
getCode();
}
void getCode() {
setState(() {
remaining = OTP.remainingSeconds();
code = OTP.generateTOTPCodeString(
secret,
DateTime.now().millisecondsSinceEpoch,
algorithm: Algorithm.SHA1,
isGoogle: true,
);
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('谷歌验证器'),
),
body: Column(
children: [
Text(
code,
style: const TextStyle(
fontSize: 32,
),
),
Text(remaining.toString()),
ElevatedButton(
onPressed: () {
getCode();
},
child: const Text('刷新密码'),
),
],
),
);
}
}

View File

@@ -1,4 +1,7 @@
import 'package:chat/routes/app_routes.dart';
import 'package:chat/views/home/widgets/action_item.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
class UserServePage extends StatefulWidget {
const UserServePage({Key? key}) : super(key: key);
@@ -14,7 +17,25 @@ class _UserServePageState extends State<UserServePage> {
appBar: AppBar(
title: const Text('服务'),
),
body: Container(),
body: Column(
children: [
// ActionItem(
// '谷歌验证器',
// isFirst: true,
// onTap: () {
// Get.toNamed(UserRoutes.serveGoogle);
// },
// ),
ActionItem(
'扫一扫',
isFirst: true,
isLast: true,
onTap: () {
Get.toNamed(AppRoutes.scan);
},
),
],
),
);
}
}

View File

@@ -0,0 +1,20 @@
import 'package:flutter/material.dart';
class UserSettingAboutPage extends StatefulWidget {
const UserSettingAboutPage({Key? key}) : super(key: key);
@override
_UserSettingAboutPageState createState() => _UserSettingAboutPageState();
}
class _UserSettingAboutPageState extends State<UserSettingAboutPage> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('关于ZH-CHAT'),
),
body: Container(),
);
}
}

View File

@@ -1,10 +1,13 @@
import 'package:adaptive_dialog/adaptive_dialog.dart';
import 'package:chat/configs/app_colors.dart';
import 'package:chat/providers/public_provider.dart';
import 'package:chat/routes/user_routes.dart';
import 'package:chat/services/auth_service.dart';
import 'package:chat/views/home/widgets/action_button.dart';
import 'package:chat/views/home/widgets/action_item.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:package_info_plus/package_info_plus.dart';
class UserSettingPage extends StatefulWidget {
const UserSettingPage({Key? key}) : super(key: key);
@@ -14,6 +17,18 @@ class UserSettingPage extends StatefulWidget {
}
class _UserSettingPageState extends State<UserSettingPage> {
PackageInfo? packageInfo;
@override
void initState() {
super.initState();
PackageInfo.fromPlatform().then((value) {
setState(() {
packageInfo = value;
});
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
@@ -24,89 +39,72 @@ class _UserSettingPageState extends State<UserSettingPage> {
children: [
ActionItem(
'账号与安全',
isFirst: true,
isLast: true,
onTap: () {
Get.toNamed(UserRoutes.safe);
Get.toNamed(UserRoutes.settingSafe);
},
),
const Divider(
height: 0,
color: AppColors.border,
),
const SizedBox(height: 8),
const Divider(
height: 0,
color: AppColors.border,
),
ActionItem(
'新消息通知',
isFirst: true,
onTap: () {
Get.toNamed(UserRoutes.safe);
Get.toNamed(UserRoutes.settingMessage);
},
),
const Divider(
height: 0,
color: AppColors.border,
indent: 16,
),
ActionItem(
'隐私权限',
isLast: true,
onTap: () {
Get.toNamed(UserRoutes.safe);
Get.toNamed(UserRoutes.settingPrivacy);
},
),
const Divider(
height: 0,
color: AppColors.border,
),
const SizedBox(height: 8),
ActionItem(
'关于ZH-CHAT',
isFirst: true,
onTap: () {
Get.toNamed(UserRoutes.safe);
Get.toNamed(UserRoutes.settingAbout);
},
),
const Divider(
height: 0,
color: AppColors.border,
indent: 16,
),
ActionItem(
'帮助与反馈',
onTap: () {
Get.toNamed(UserRoutes.safe);
Get.toNamed(UserRoutes.settingSugguest);
},
),
const SizedBox(height: 8),
const Divider(
height: 0,
color: AppColors.border,
),
ActionItem(
'版本更新',
onTap: () {
Get.toNamed(UserRoutes.safe);
extend: packageInfo?.version,
isFirst: true,
isLast: true,
onTap: () async {
PublicProvider.checkUpgrade(packageInfo);
},
),
const Divider(
height: 0,
color: AppColors.border,
),
const SizedBox(height: 8),
const Divider(
height: 0,
color: AppColors.border,
),
ActionButton(
'退出',
color: AppColors.primary,
hasTop: true,
hasBottom: true,
color: AppColors.red,
onTap: () async {
AuthService.to.logout();
OkCancelResult result = await showOkCancelAlertDialog(
style: AdaptiveStyle.iOS,
context: context,
title: '退出登录',
message: '确认您已备份助记词并保存好了么?退出登录后助记词将无法导出。',
okLabel: '确定',
cancelLabel: '取消',
defaultType: OkCancelAlertDefaultType.cancel,
);
if (result == OkCancelResult.ok) {
AuthService.to.logout();
}
},
),
const Divider(
height: 0,
color: AppColors.border,
),
],
),
);

View File

@@ -0,0 +1,20 @@
import 'package:flutter/material.dart';
class UserSettingMessagePage extends StatefulWidget {
const UserSettingMessagePage({Key? key}) : super(key: key);
@override
_UserSettingMessagePageState createState() => _UserSettingMessagePageState();
}
class _UserSettingMessagePageState extends State<UserSettingMessagePage> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('新消息与通知'),
),
body: Container(),
);
}
}

View File

@@ -0,0 +1,40 @@
import 'package:chat/configs/app_colors.dart';
import 'package:chat/services/auth_service.dart';
import 'package:chat/views/user/widgets/link_action_item.dart';
import 'package:flutter/material.dart';
import 'package:get/get_state_manager/get_state_manager.dart';
class UserSettingPrivacyPage extends StatelessWidget {
const UserSettingPrivacyPage({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('隐私权限'),
),
body: Column(
children: [
const Divider(
height: 0,
color: AppColors.border,
),
GetX<AuthService>(
builder: (_) {
return LinkActionItem(
title: '允许通过搜索添加我为好友',
trailing: Switch(
value: _.userInfo.value.privacy ?? true,
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
onChanged: (e) {
_.togglePrivacy();
},
),
);
},
),
],
),
);
}
}

View File

@@ -0,0 +1,15 @@
import 'package:flutter/material.dart';
class UserSettingSafeEmailPage extends StatelessWidget {
const UserSettingSafeEmailPage({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('绑定邮箱'),
),
body: Container(),
);
}
}

View File

@@ -0,0 +1,15 @@
import 'package:flutter/material.dart';
class UserSettingSafeGooglePage extends StatelessWidget {
const UserSettingSafeGooglePage({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('UserSettingSafeGooglePage'),
),
body: Container(),
);
}
}

View File

@@ -0,0 +1,110 @@
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/ui_tools.dart';
import 'package:chat/views/home/widgets/action_item.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:get/get.dart';
class UserSettingSafePage extends StatefulWidget {
const UserSettingSafePage({Key? key}) : super(key: key);
@override
State<UserSettingSafePage> createState() => _UserSettingSafePageState();
}
class _UserSettingSafePageState extends State<UserSettingSafePage> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('安全设置'),
),
body: GetX<AuthService>(builder: (_) {
return Column(
children: [
ActionItem(
'导出助记词',
isFirst: true,
isLast: true,
onTap: _showMnemonic,
),
const SizedBox(height: 8),
ActionItem(
'绑定手机',
extend: _.userInfo.value.mobile ?? '未绑定',
isFirst: true,
onTap: () {
Get.toNamed(UserRoutes.settingSafeMobile);
},
),
ActionItem(
'绑定邮箱',
extend: _.userInfo.value.email ?? '未绑定',
isLast: true,
onTap: () {
Get.toNamed(UserRoutes.settingSafeEmail);
},
),
const SizedBox(height: 8),
ActionItem(
'开启两步验证',
extend: (_.userInfo.value.google2fa ?? false) ? '已开启' : '未开启',
isFirst: true,
isLast: true,
onTap: () {
Get.toNamed(UserRoutes.settingSafeGoogle);
},
),
],
);
}),
);
}
void _showMnemonic() {
showModalBottomSheet(
context: context,
isScrollControlled: true,
backgroundColor: AppColors.white,
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.vertical(top: Radius.circular(8)),
),
builder: (context) {
return Column(
mainAxisSize: MainAxisSize.min,
children: [
const SizedBox(height: 16),
const Text('您的助记词'),
Container(
padding: const EdgeInsets.all(32),
child: Text(
AuthService.to.mnemonic,
style: const TextStyle(
fontSize: 20,
fontWeight: FontWeight.w500,
wordSpacing: 6,
height: 2,
),
),
),
ElevatedButton(
onPressed: () {
Clipboard.setData(
ClipboardData(
text: AuthService.to.mnemonic,
),
);
UiTools.toast('复制成功');
Get.back();
},
child: const Text('复制'),
),
const SizedBox(height: 16),
],
);
},
);
}
}

View File

@@ -0,0 +1,17 @@
import 'package:flutter/material.dart';
class UserSettingSafeMobilePage extends StatelessWidget {
const UserSettingSafeMobilePage({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('绑定手机'),
),
body: Column(
children: const [],
),
);
}
}

View File

@@ -0,0 +1,21 @@
import 'package:flutter/material.dart';
class UserSettingSugguestPage extends StatefulWidget {
const UserSettingSugguestPage({Key? key}) : super(key: key);
@override
_UserSettingSugguestPageState createState() =>
_UserSettingSugguestPageState();
}
class _UserSettingSugguestPageState extends State<UserSettingSugguestPage> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('帮助与反馈'),
),
body: Container(),
);
}
}

View File

@@ -1,4 +1,8 @@
import 'package:chat/configs/app_colors.dart';
import 'package:chat/providers/user_provider.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:qr_flutter/qr_flutter.dart';
class UserSharePage extends StatefulWidget {
const UserSharePage({Key? key}) : super(key: key);
@@ -14,7 +18,33 @@ class _UserSharePageState extends State<UserSharePage> {
appBar: AppBar(
title: const Text('分享邀请'),
),
body: Container(),
body: FutureBuilder(
future: UserProvider.downloadUrl(),
builder: (context, AsyncSnapshot<String?> data) {
return data.data != null
? Center(
child: Container(
decoration: BoxDecoration(
color: AppColors.white,
borderRadius: BorderRadius.circular(8),
),
padding: const EdgeInsets.all(16),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: [
QrImage(
data: '${data.data}',
size: Get.width * 0.8,
),
const Text('扫一扫上面的二维码下载ZH-CHAT'),
],
),
),
)
: Container();
},
),
);
}
}

View File

@@ -35,7 +35,6 @@ class CustomEasyRefresh {
double size = 156.0,
}) {
return Column(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
@@ -43,6 +42,9 @@ class CustomEasyRefresh {
'assets/images/empty/im_emptyIcon_2.png',
width: size,
),
const SizedBox(
height: 16,
),
Text(
text,
style: const TextStyle(

View File

@@ -28,7 +28,7 @@ packages:
name: asn1lib
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.2.0"
version: "1.3.0"
async:
dependency: transitive
description:
@@ -43,6 +43,13 @@ packages:
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.0.0"
base32:
dependency: transitive
description:
name: base32
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.1.3"
bip32:
dependency: "direct main"
description:
@@ -121,7 +128,7 @@ packages:
name: camera_platform_interface
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.2.2"
version: "2.3.0"
camera_web:
dependency: transitive
description:
@@ -206,13 +213,6 @@ packages:
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.1.1"
device_info_plus:
dependency: "direct main"
description:
name: device_info_plus
url: "https://pub.flutter-io.cn"
source: hosted
version: "0.0.1"
dio:
dependency: "direct main"
description:
@@ -336,7 +336,7 @@ packages:
name: flutter_plugin_record_plus
url: "https://pub.flutter-io.cn"
source: hosted
version: "0.0.11"
version: "0.0.15"
flutter_spinkit:
dependency: "direct main"
description:
@@ -437,21 +437,21 @@ packages:
name: image_cropper
url: "https://pub.flutter-io.cn"
source: hosted
version: "3.0.0"
version: "3.0.1"
image_cropper_for_web:
dependency: transitive
description:
name: image_cropper_for_web
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.0.2"
version: "1.0.3"
image_cropper_platform_interface:
dependency: transitive
description:
name: image_cropper_platform_interface
url: "https://pub.flutter-io.cn"
source: hosted
version: "3.0.2"
version: "3.0.3"
intersperse:
dependency: transitive
description:
@@ -487,6 +487,13 @@ packages:
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.0.1"
logging:
dependency: transitive
description:
name: logging
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.1.0"
lpinyin:
dependency: "direct main"
description:
@@ -543,13 +550,55 @@ packages:
url: "https://pub.flutter-io.cn"
source: hosted
version: "3.2.1"
otp:
dependency: "direct main"
description:
name: otp
url: "https://pub.flutter-io.cn"
source: hosted
version: "3.1.3"
package_info_plus:
dependency: "direct main"
description:
name: package_info_plus
url: "https://pub.flutter-io.cn"
source: hosted
version: "0.0.1"
version: "1.4.2"
package_info_plus_linux:
dependency: transitive
description:
name: package_info_plus_linux
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.0.5"
package_info_plus_macos:
dependency: transitive
description:
name: package_info_plus_macos
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.3.0"
package_info_plus_platform_interface:
dependency: transitive
description:
name: package_info_plus_platform_interface
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.0.2"
package_info_plus_web:
dependency: "direct main"
description:
name: package_info_plus_web
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.0.6"
package_info_plus_windows:
dependency: transitive
description:
name: package_info_plus_windows
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.0.5"
path:
dependency: transitive
description:
@@ -570,7 +619,7 @@ packages:
name: path_provider_android
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.0.20"
version: "2.0.21"
path_provider_ios:
dependency: transitive
description:
@@ -738,7 +787,7 @@ packages:
name: rxdart
url: "https://pub.flutter-io.cn"
source: hosted
version: "0.27.5"
version: "0.27.6"
scan:
dependency: "direct main"
description:
@@ -792,7 +841,7 @@ packages:
name: sqflite_common
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.3.0"
version: "2.4.0"
stack_trace:
dependency: transitive
description:
@@ -813,7 +862,7 @@ packages:
name: stream_transform
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.0.1"
version: "2.1.0"
string_scanner:
dependency: transitive
description:
@@ -834,14 +883,14 @@ packages:
name: tencent_im_sdk_plugin
url: "https://pub.flutter-io.cn"
source: hosted
version: "4.1.9"
version: "4.2.0"
tencent_im_sdk_plugin_platform_interface:
dependency: transitive
description:
name: tencent_im_sdk_plugin_platform_interface
url: "https://pub.flutter-io.cn"
source: hosted
version: "0.3.5"
version: "0.3.6"
term_glyph:
dependency: transitive
description:
@@ -883,7 +932,7 @@ packages:
name: uuid
url: "https://pub.flutter-io.cn"
source: hosted
version: "3.0.6"
version: "3.0.7"
vector_math:
dependency: transitive
description:

View File

@@ -16,8 +16,6 @@ dependencies:
lpinyin: ^2.0.3
vibration: ^1.7.6
scan: ^1.6.0
package_info_plus: ^0.0.1
device_info_plus: ^0.0.1
flutter_easyrefresh: ^2.2.2
cached_network_image: ^3.2.0
flutter_spinkit: ^5.1.0
@@ -52,6 +50,9 @@ dependencies:
wechat_camera_picker: ^3.5.0+1
filesize: ^2.0.1
file_picker: ^4.6.1
otp: ^3.1.1
package_info_plus: ^1.4.2
package_info_plus_web: ^1.0.6
dev_dependencies:
flutter_test: