初始化
This commit is contained in:
@@ -0,0 +1,73 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:hgdj/hj_utils/api_service/mine_service.dart';
|
||||
import 'package:hgdj/tools_base/toast.dart';
|
||||
import 'package:hgdj/tools_base/global_store/store.dart';
|
||||
|
||||
import '../../../tools_base/event_bus/event_bus_util.dart';
|
||||
import '../../../tools_base/event_bus/events.dart';
|
||||
import 'bing_phone_page.dart';
|
||||
|
||||
class BindPhoneLogic extends GetxController {
|
||||
late final phoneCtr = TextEditingController();
|
||||
late final codeCtr = TextEditingController();
|
||||
late final countDownNof = ValueNotifier(-1);
|
||||
Timer? _timer;
|
||||
final PhonePageType pageType;
|
||||
|
||||
BindPhoneLogic(this.pageType);
|
||||
|
||||
@override
|
||||
onClose() {
|
||||
super.onClose();
|
||||
_timer?.cancel();
|
||||
}
|
||||
|
||||
onSendCode() async {
|
||||
if (_timer != null) return;
|
||||
if (phoneCtr.text.isEmpty || phoneCtr.text.length < 11) {
|
||||
showToast('请输入正确的手机号~');
|
||||
return;
|
||||
}
|
||||
final res = await MineService.postCaptchaCode(phoneCtr.text, pageType.type);
|
||||
if (res) {
|
||||
_timer?.cancel();
|
||||
_timer = Timer.periodic(Duration(seconds: 1), (timer) {
|
||||
countDownNof.value += 1;
|
||||
if (countDownNof.value == 60) {
|
||||
_timer?.cancel();
|
||||
_timer = null;
|
||||
countDownNof.value = -1;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
onBindPhone() async {
|
||||
if (phoneCtr.text.isEmpty) {
|
||||
showToast('请输入正确的手机号~');
|
||||
return;
|
||||
}
|
||||
if (codeCtr.text.isEmpty) {
|
||||
showToast('请输入验证码~');
|
||||
return;
|
||||
}
|
||||
if (pageType == PhonePageType.bind) {
|
||||
final res = await MineService.bindPhone(phoneCtr.text, codeCtr.text);
|
||||
if (res) {
|
||||
showToast('绑定成功');
|
||||
globalStore.updateUserInfo();
|
||||
Get.back();
|
||||
}
|
||||
} else {
|
||||
final result =
|
||||
await globalStore.loginByMobile(phoneCtr.text, codeCtr.text);
|
||||
if (result != null) {
|
||||
showToast('登录成功');
|
||||
eventBus.emit(ReLoginEvent());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,164 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:hgdj/assets_tool/app_colors.dart';
|
||||
import 'package:hgdj/hj_utils/screen.dart';
|
||||
|
||||
import 'bind_phone_logic.dart';
|
||||
|
||||
enum PhonePageType {
|
||||
bind('绑定手机', 1, '立即绑定'),
|
||||
find('找回账号', 2, '立即找回');
|
||||
|
||||
final int type;
|
||||
final String title;
|
||||
final String buttonTitle;
|
||||
const PhonePageType(this.title, this.type, this.buttonTitle);
|
||||
}
|
||||
|
||||
class BindPhonePage extends StatelessWidget {
|
||||
final PhonePageType pageType;
|
||||
|
||||
const BindPhonePage({super.key, this.pageType = PhonePageType.bind});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return GetBuilder<BindPhoneLogic>(
|
||||
init: BindPhoneLogic(pageType),
|
||||
builder: (controller) => Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text(controller.pageType.title),
|
||||
),
|
||||
body: Padding(
|
||||
padding: EdgeInsets.only(left: 16, right: 16, top: 47),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Container(
|
||||
width: double.infinity,
|
||||
height: 42,
|
||||
padding: EdgeInsets.symmetric(horizontal: 12),
|
||||
child: Row(
|
||||
children: [
|
||||
Text(
|
||||
'手机号',
|
||||
style: TextStyle(
|
||||
color: Colors.white.withValues(alpha: 0.9),
|
||||
fontSize: 16),
|
||||
),
|
||||
12.sizeBoxW,
|
||||
Expanded(
|
||||
child: TextField(
|
||||
controller: controller.phoneCtr,
|
||||
style: TextStyle(
|
||||
color: Colors.white.withValues(alpha: 0.9),
|
||||
fontSize: 14,
|
||||
),
|
||||
maxLength: 11,
|
||||
decoration: InputDecoration(
|
||||
border: InputBorder.none,
|
||||
hintText: '请输入手机号码',
|
||||
isCollapsed: true,
|
||||
contentPadding: EdgeInsets.zero,
|
||||
counterText: '',
|
||||
hintStyle: TextStyle(
|
||||
color: Colors.white.withValues(alpha: 0.5),
|
||||
fontSize: 14)),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
10.sizeBoxH,
|
||||
Divider(
|
||||
height: .5,
|
||||
color: Colors.white.withValues(alpha: .05),
|
||||
),
|
||||
30.sizeBoxH,
|
||||
Container(
|
||||
width: double.infinity,
|
||||
height: 42,
|
||||
padding: EdgeInsets.symmetric(horizontal: 12),
|
||||
child: Row(
|
||||
children: [
|
||||
Text(
|
||||
'验证码',
|
||||
style: TextStyle(
|
||||
color: Colors.white.withValues(alpha: 0.9),
|
||||
fontSize: 16),
|
||||
),
|
||||
12.sizeBoxW,
|
||||
Expanded(
|
||||
child: TextField(
|
||||
controller: controller.codeCtr,
|
||||
style: TextStyle(
|
||||
color: Colors.white.withValues(alpha: 0.9),
|
||||
fontSize: 14,
|
||||
),
|
||||
maxLength: 6,
|
||||
decoration: InputDecoration(
|
||||
border: InputBorder.none,
|
||||
hintText: '请输入手机验证码',
|
||||
isCollapsed: true,
|
||||
contentPadding: EdgeInsets.zero,
|
||||
counterText: '',
|
||||
hintStyle: TextStyle(
|
||||
color: Colors.white.withValues(alpha: 0.5),
|
||||
fontSize: 14)),
|
||||
),
|
||||
),
|
||||
12.sizeBoxW,
|
||||
GestureDetector(
|
||||
onTap: () => controller.onSendCode(),
|
||||
child: ValueListenableBuilder(
|
||||
valueListenable: controller.countDownNof,
|
||||
builder:
|
||||
(BuildContext context, int value, Widget? child) {
|
||||
final iscountdown = value > -1;
|
||||
return Container(
|
||||
padding: EdgeInsets.symmetric(
|
||||
horizontal: 4, vertical: 4),
|
||||
child: Text(
|
||||
iscountdown ? '${60 - value}秒' : '获取验证码',
|
||||
style: TextStyle(
|
||||
color: Colors.white.withValues(alpha: 0.5),
|
||||
fontSize: 12),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
10.sizeBoxH,
|
||||
Divider(
|
||||
height: .5,
|
||||
color: Colors.white.withValues(alpha: .05),
|
||||
),
|
||||
30.sizeBoxH,
|
||||
GestureDetector(
|
||||
onTap: () => controller.onBindPhone(),
|
||||
child: Container(
|
||||
width: double.infinity,
|
||||
height: 44,
|
||||
alignment: Alignment.center,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(3),
|
||||
color: AppColors.actionRed,
|
||||
),
|
||||
child: Text(
|
||||
controller.pageType.buttonTitle,
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.w500),
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:hgdj/hj_utils/api_service/mine_service.dart';
|
||||
import 'package:hgdj/tools_base/toast.dart';
|
||||
import 'package:hgdj/tools_base/global_store/store.dart';
|
||||
|
||||
/// 绑定邀请码逻辑
|
||||
class InviteBindLogic extends GetxController {
|
||||
// 邀请码输入框
|
||||
final codeCtr = TextEditingController();
|
||||
|
||||
/// 立即绑定
|
||||
void bind() async {
|
||||
if (codeCtr.text.isEmpty) {
|
||||
showToast('请输入邀请码~');
|
||||
return;
|
||||
}
|
||||
final res = await MineService.exchangeInviteCode(codeCtr.text);
|
||||
if (res) {
|
||||
showToast('绑定成功');
|
||||
globalStore.updateUserInfo();
|
||||
Get.back();
|
||||
} else {
|
||||
showToast('绑定失败');
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void onClose() {
|
||||
codeCtr.dispose();
|
||||
super.onClose();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:hgdj/assets_tool/app_colors.dart';
|
||||
import 'package:hgdj/hj_utils/screen.dart';
|
||||
|
||||
import 'invite_bind_logic.dart';
|
||||
|
||||
/// 绑定邀请码页
|
||||
class InviteBindPage extends StatelessWidget {
|
||||
const InviteBindPage({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return GetBuilder<InviteBindLogic>(
|
||||
init: InviteBindLogic(),
|
||||
builder: (controller) => Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text('绑定邀请码'),
|
||||
),
|
||||
body: Padding(
|
||||
padding: EdgeInsets.only(left: 28, right: 28, top: 28),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// 标题
|
||||
Text(
|
||||
'输入邀请码',
|
||||
style: TextStyle(
|
||||
color: Colors.white.withValues(alpha: 0.9),
|
||||
fontSize: 30,
|
||||
fontWeight: FontWeight.w500),
|
||||
),
|
||||
12.sizeBoxH,
|
||||
Text(
|
||||
'邀请码只能绑定一次 且不能修改',
|
||||
style: TextStyle(
|
||||
color: Colors.white.withValues(alpha: 0.35),
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w400,
|
||||
),
|
||||
),
|
||||
30.sizeBoxH,
|
||||
// 邀请码输入
|
||||
SizedBox(
|
||||
width: double.infinity,
|
||||
height: 42,
|
||||
child: Row(
|
||||
children: [
|
||||
Text(
|
||||
'邀请码',
|
||||
style: TextStyle(
|
||||
color: Colors.white.withValues(alpha: 0.9),
|
||||
fontSize: 16),
|
||||
),
|
||||
24.sizeBoxW,
|
||||
Expanded(
|
||||
child: TextField(
|
||||
controller: controller.codeCtr,
|
||||
style: TextStyle(
|
||||
color: Colors.white.withValues(alpha: 0.9),
|
||||
fontSize: 14,
|
||||
),
|
||||
maxLength: 11,
|
||||
decoration: InputDecoration(
|
||||
border: InputBorder.none,
|
||||
hintText: '请输入邀请码(字母大写)',
|
||||
isCollapsed: true,
|
||||
contentPadding: EdgeInsets.zero,
|
||||
counterText: '',
|
||||
hintStyle: TextStyle(
|
||||
color: Colors.white.withValues(alpha: 0.35),
|
||||
fontSize: 14)),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
10.sizeBoxH,
|
||||
Divider(
|
||||
height: .35,
|
||||
color: Colors.white.withValues(alpha: .05),
|
||||
),
|
||||
18.sizeBoxH,
|
||||
Text(
|
||||
'邀请1人 获得1天会员',
|
||||
style: TextStyle(
|
||||
color: Colors.white.withValues(alpha: 0.35), fontSize: 14),
|
||||
),
|
||||
30.sizeBoxH,
|
||||
// 立即绑定按钮
|
||||
GestureDetector(
|
||||
onTap: controller.bind,
|
||||
child: Container(
|
||||
width: double.infinity,
|
||||
height: 44,
|
||||
alignment: Alignment.center,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(3),
|
||||
color: AppColors.actionRed,
|
||||
),
|
||||
child: Text(
|
||||
'立即绑定',
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.w500),
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:hgdj/hj_model/mine/exchange_record_model.dart';
|
||||
import 'package:hgdj/hj_utils/api_service/mine_service.dart';
|
||||
import 'package:hgdj/tools_base/toast.dart';
|
||||
import 'package:hgdj/tools_base/debug_log.dart';
|
||||
import 'package:hgdj/tools_base/global_store/store.dart';
|
||||
import 'package:hgdj/tools_base/loading/loading_alert_widget.dart';
|
||||
import 'package:pull_to_refresh/pull_to_refresh.dart';
|
||||
|
||||
class MineExchangeCodeLogic extends GetxController {
|
||||
final int type; //0-填写邀请码 1-填写兑换码
|
||||
TextEditingController controller = TextEditingController();
|
||||
List<ExchangeRecordModel> groupList = [];
|
||||
bool isLoadingHistoryData = true;
|
||||
RefreshController? refreshController; //由 pullYsRefresh 的 onInit 注入,组件负责释放
|
||||
int currentPage = 1;
|
||||
|
||||
MineExchangeCodeLogic({this.type = 0});
|
||||
|
||||
@override
|
||||
onReady() {
|
||||
super.onReady();
|
||||
if (type == 1) loadData();
|
||||
}
|
||||
|
||||
loadData({int page = 1}) async {
|
||||
try {
|
||||
final res = await MineService.getExchangeRecord(page, 20);
|
||||
if (res != null && res.data != null) {
|
||||
if (page == 1) groupList.clear();
|
||||
currentPage = page;
|
||||
groupList.addAll(res.data ?? []);
|
||||
}
|
||||
refreshController?.refreshCompleted();
|
||||
(res?.total ?? 0) > (res?.data?.length ?? 0)
|
||||
? refreshController?.loadComplete()
|
||||
: refreshController?.loadNoData();
|
||||
} catch (e) {
|
||||
refreshController?.refreshCompleted();
|
||||
refreshController?.loadComplete();
|
||||
debugLog(e);
|
||||
}
|
||||
update();
|
||||
isLoadingHistoryData = false;
|
||||
}
|
||||
|
||||
loadMoreData() => loadData(page: currentPage + 1);
|
||||
|
||||
void onExChangeCode() async {
|
||||
if (controller.text.isEmpty) {
|
||||
showToast('请输入${type == 0 ? '邀请码' : '兑换码'}');
|
||||
return;
|
||||
}
|
||||
|
||||
if (type == 1) {
|
||||
try {
|
||||
LoadingAlertWidget.show();
|
||||
bool ret = await MineService.postExchangeCode(controller.text);
|
||||
LoadingAlertWidget.cancel();
|
||||
if (ret == true) {
|
||||
controller.text = "";
|
||||
globalStore.updateUserInfo();
|
||||
showToast("兑换成功");
|
||||
loadData();
|
||||
}
|
||||
} catch (e) {
|
||||
LoadingAlertWidget.cancel();
|
||||
debugLog(e);
|
||||
}
|
||||
} else if (type == 0) {
|
||||
try {
|
||||
LoadingAlertWidget.show();
|
||||
bool ret = await MineService.getProxyBind(controller.text);
|
||||
LoadingAlertWidget.cancel();
|
||||
if (ret == true) {
|
||||
globalStore.meInfo?.inviterCode = controller.text;
|
||||
globalStore.updateUserInfo();
|
||||
showToast("绑定成功");
|
||||
Get.back(result: true);
|
||||
}
|
||||
} catch (e) {
|
||||
LoadingAlertWidget.cancel();
|
||||
debugLog(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,250 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:hgdj/assets_tool/app_colors.dart';
|
||||
import 'package:hgdj/extension/extensions.dart';
|
||||
import 'package:hgdj/hj_model/mine/exchange_record_model.dart';
|
||||
import 'package:hgdj/hj_utils/screen.dart';
|
||||
import 'package:hgdj/tools_base/loading/loading_center_widget.dart';
|
||||
import 'package:hgdj/tools_base/refresh/pull_refresh.dart';
|
||||
|
||||
import 'mine_code_logic.dart';
|
||||
|
||||
//邀请码和兑换码
|
||||
class MineExchangeCodePage extends StatefulWidget {
|
||||
final int type; //0-填写邀请码 1-填写兑换码
|
||||
const MineExchangeCodePage({super.key, this.type = 0});
|
||||
|
||||
@override
|
||||
State<MineExchangeCodePage> createState() => _MineExchangeCodePageState();
|
||||
}
|
||||
|
||||
class _MineExchangeCodePageState extends State<MineExchangeCodePage> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(title: Text(widget.type == 0 ? '邀请码' : '领取兑换')),
|
||||
body: GetBuilder<MineExchangeCodeLogic>(
|
||||
init: MineExchangeCodeLogic(type: widget.type),
|
||||
builder: (_) => Padding(
|
||||
padding: EdgeInsets.symmetric(vertical: 28, horizontal: 16),
|
||||
child: Column(
|
||||
children: [
|
||||
Expanded(
|
||||
child: pullYsRefresh(
|
||||
onInit: (controller) => _.refreshController = controller,
|
||||
onRefresh: (controller) => _.loadData(),
|
||||
onLoading: (controller) => _.loadMoreData(),
|
||||
child: CustomScrollView(
|
||||
keyboardDismissBehavior:
|
||||
ScrollViewKeyboardDismissBehavior.onDrag,
|
||||
slivers: <Widget>[
|
||||
SliverToBoxAdapter(
|
||||
child: _buildContent(_),
|
||||
),
|
||||
if (widget.type == 1) ...[
|
||||
_buildHistoryTable(_),
|
||||
]
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
InkWell(
|
||||
enableFeedback: false,
|
||||
onTap: () => _.onExChangeCode(),
|
||||
child: Container(
|
||||
height: 44,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.all(Radius.circular(3)),
|
||||
color: AppColors.actionRed,
|
||||
),
|
||||
child: Center(
|
||||
child: Text(
|
||||
"立即兑换",
|
||||
style: const TextStyle(
|
||||
color: Colors.white,
|
||||
fontWeight: FontWeight.w600,
|
||||
fontSize: 16,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
)),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildHistoryTable(MineExchangeCodeLogic logic) {
|
||||
if (logic.isLoadingHistoryData) {
|
||||
return SliverToBoxAdapter(child: LoadingCenterWidget());
|
||||
} else if (logic.groupList.isEmpty) {
|
||||
return SliverToBoxAdapter(
|
||||
child: CErrorWidget(errorMsg: "暂无兑换记录"),
|
||||
);
|
||||
} else {
|
||||
return SliverList.separated(
|
||||
itemBuilder: (context, index) {
|
||||
return _buildListItem(logic.groupList[index]);
|
||||
},
|
||||
itemCount: logic.groupList.length,
|
||||
separatorBuilder: (context, index) {
|
||||
return Divider(
|
||||
height: 1,
|
||||
color: Colors.black87.withValues(alpha: 0.1),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
_buildContent(MineExchangeCodeLogic _) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
"输入兑换码",
|
||||
style: TextStyle(
|
||||
color: Colors.white.withValues(alpha: .9),
|
||||
fontWeight: FontWeight.w500,
|
||||
fontSize: 30,
|
||||
),
|
||||
),
|
||||
12.sizeBoxH,
|
||||
Text(
|
||||
"每个兑换码只能输入一次",
|
||||
style: TextStyle(
|
||||
color: Color(0xff525252),
|
||||
fontWeight: FontWeight.w500,
|
||||
fontSize: 16,
|
||||
),
|
||||
),
|
||||
56.sizeBoxH,
|
||||
Container(
|
||||
height: 42,
|
||||
child: Row(
|
||||
children: [
|
||||
Text(
|
||||
"兑换码",
|
||||
style: TextStyle(
|
||||
color: Colors.white.withValues(alpha: .9),
|
||||
fontWeight: FontWeight.w500,
|
||||
fontSize: 14,
|
||||
),
|
||||
),
|
||||
16.sizeBoxW,
|
||||
Expanded(
|
||||
child: TextField(
|
||||
keyboardType: TextInputType.text,
|
||||
autofocus: true,
|
||||
autocorrect: true,
|
||||
cursorColor: Colors.white,
|
||||
textAlign: TextAlign.left,
|
||||
controller: _.controller,
|
||||
style: TextStyle(color: Colors.white, fontSize: 14),
|
||||
decoration: InputDecoration(
|
||||
hintText: '请输入${widget.type == 0 ? '邀请码' : '兑换码'}(字母大写)',
|
||||
hintStyle: TextStyle(color: Color(0xff434c55)),
|
||||
border: InputBorder.none,
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
)),
|
||||
12.sizeBoxH,
|
||||
1.line,
|
||||
18.sizeBoxH,
|
||||
Text(
|
||||
"官方社群领取更多福利",
|
||||
style: TextStyle(
|
||||
color: Color(0xff525252),
|
||||
fontWeight: FontWeight.w500,
|
||||
fontSize: 12,
|
||||
),
|
||||
),
|
||||
36.sizeBoxH,
|
||||
Text(
|
||||
"兑换记录",
|
||||
style: TextStyle(
|
||||
color: Colors.white.withValues(alpha: .9),
|
||||
fontWeight: FontWeight.w600,
|
||||
fontSize: 18,
|
||||
),
|
||||
),
|
||||
12.sizeBoxH,
|
||||
Row(
|
||||
children: [
|
||||
Expanded(child: _buildSectionItem('兑换码')),
|
||||
5.sizeBoxW,
|
||||
Expanded(child: _buildSectionItem('兑换类型')),
|
||||
5.sizeBoxW,
|
||||
Expanded(child: _buildSectionItem('兑换时间')),
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
_buildSectionItem(String title) {
|
||||
return Container(
|
||||
alignment: Alignment.center,
|
||||
height: 42,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white.withValues(alpha: .05),
|
||||
),
|
||||
child: Text(
|
||||
title,
|
||||
style: TextStyle(
|
||||
color: Colors.white.withValues(alpha: .9),
|
||||
fontWeight: FontWeight.w400,
|
||||
fontSize: 14,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildListItem(ExchangeRecordModel item) {
|
||||
return Container(
|
||||
height: 44,
|
||||
child: Row(
|
||||
children: [
|
||||
Flexible(
|
||||
child: Container(
|
||||
height: 44,
|
||||
alignment: Alignment.center,
|
||||
child: Text(
|
||||
item.code ?? '',
|
||||
style: TextStyle(
|
||||
color: Colors.white.withValues(alpha: .55), fontSize: 14),
|
||||
),
|
||||
),
|
||||
),
|
||||
Flexible(
|
||||
child: Container(
|
||||
height: 44,
|
||||
alignment: Alignment.center,
|
||||
child: Text(
|
||||
item.desc ?? '',
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: TextStyle(
|
||||
color: Colors.white.withValues(alpha: .55), fontSize: 14),
|
||||
),
|
||||
),
|
||||
),
|
||||
Flexible(
|
||||
child: Container(
|
||||
height: 44,
|
||||
alignment: Alignment.center,
|
||||
child: Text(
|
||||
'${item.createdAt.utcToYMD(gap: '.')}',
|
||||
style: TextStyle(
|
||||
color: Colors.white.withValues(alpha: .55), fontSize: 14),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:hgdj/hj_utils/screen.dart';
|
||||
|
||||
import '../../../hj_utils/permission_util.dart';
|
||||
import '../../../routers/jump_router.dart';
|
||||
import 'bing_phone_page.dart';
|
||||
import 'mine_scan_login_page.dart';
|
||||
|
||||
class MineFindAccountPage extends StatelessWidget {
|
||||
late final dataSource = [
|
||||
{
|
||||
'title': '手机号找回',
|
||||
'ontap': () =>
|
||||
Get.to(() => const BindPhonePage(pageType: PhonePageType.find)),
|
||||
},
|
||||
{
|
||||
'title': '凭证找回',
|
||||
'ontap': () async {
|
||||
if (await PermissionUtil.checkCameraPermission()) {
|
||||
Get.to(() => const MineScanLoginPage());
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
'title': '联系客服',
|
||||
'ontap': () => pushToCustomService(),
|
||||
}
|
||||
];
|
||||
MineFindAccountPage({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(elevation: 0, title: Text('找回账号')),
|
||||
body: Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 16),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Container(
|
||||
padding: EdgeInsets.symmetric(horizontal: 16),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white.withValues(alpha: .05),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: ListView.separated(
|
||||
physics: NeverScrollableScrollPhysics(),
|
||||
itemCount: dataSource.length,
|
||||
shrinkWrap: true,
|
||||
separatorBuilder: (_, __) => Divider(
|
||||
height: 0.5,
|
||||
color: Colors.white.withValues(alpha: .1),
|
||||
),
|
||||
itemBuilder: (BuildContext context, int index) {
|
||||
final data = dataSource[index];
|
||||
return GestureDetector(
|
||||
behavior: HitTestBehavior.translucent,
|
||||
onTap: data['ontap'] as Function()?,
|
||||
child: Column(
|
||||
children: [
|
||||
16.sizeBoxH,
|
||||
Row(
|
||||
children: [
|
||||
Text(
|
||||
(data['title'] ?? '').toString(),
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
color: Colors.white.withValues(alpha: 0.9)),
|
||||
),
|
||||
Spacer(),
|
||||
Icon(
|
||||
Icons.arrow_forward_ios_rounded,
|
||||
size: 14,
|
||||
color: Color(0xFFDCDCDC),
|
||||
)
|
||||
],
|
||||
),
|
||||
16.sizeBoxH,
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
import 'package:get/get.dart';
|
||||
import 'package:hgdj/hj_page/splash/splash_page.dart';
|
||||
import 'package:hgdj/tools_base/global_store/store.dart';
|
||||
import 'package:hgdj/tools_base/toast.dart';
|
||||
|
||||
/// 手势密码用途:设置 / 关闭 / 进 app 解锁校验
|
||||
enum MinePasswordType { setting, close, check }
|
||||
|
||||
class MinePasswordLogic extends GetxController {
|
||||
//本页是中间件 redirect 的目标(RouteSettings 只能带 arguments),没法改构造传参;
|
||||
//用 is 判断而不是隐式强转:路由栈里读到别的页的 arguments 时只降级不抛 TypeError
|
||||
final MinePasswordType type = _typeFromArgs();
|
||||
|
||||
static MinePasswordType _typeFromArgs() {
|
||||
final args = Get.arguments;
|
||||
return args is MinePasswordType ? args : MinePasswordType.setting;
|
||||
}
|
||||
|
||||
bool get isCheck => type == MinePasswordType.check;
|
||||
|
||||
String get title => isCheck ? '请输入解锁密码' : '请绘制锁屏图形';
|
||||
|
||||
/// 画完手势:设置/关闭成功即退出本页,解锁通过则回启动页重走流程
|
||||
Future<void> onComplete(List<int> result) async {
|
||||
if (result.length < 4) {
|
||||
showToast('请至少链接4个点');
|
||||
return;
|
||||
}
|
||||
switch (type) {
|
||||
case MinePasswordType.setting:
|
||||
if (await globalStore.setLockPassword(result) == true) Get.back();
|
||||
case MinePasswordType.close:
|
||||
if (await globalStore.closeLockPassword(result) == true) Get.back();
|
||||
case MinePasswordType.check:
|
||||
if (await globalStore.checkLockPassword(result) == true)
|
||||
Get.toNamed(SplashPage.routeName);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:hgdj/assets_tool/images.dart';
|
||||
import 'package:hgdj/hj_utils/screen.dart';
|
||||
|
||||
import '../mine_gesture_pwd/widget/gesture_password_widget.dart';
|
||||
import 'mine_password_logic.dart';
|
||||
|
||||
/// 手势密码页:设置 / 关闭 / 解锁校验共用,用途由路由 arguments 传 [MinePasswordType]
|
||||
class MinePasswordPage extends StatelessWidget {
|
||||
static const routeName = '/MinePasswordPage';
|
||||
const MinePasswordPage({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return GetBuilder<MinePasswordLogic>(
|
||||
init: MinePasswordLogic(),
|
||||
//必须 false:type 来自路由 arguments,全局注册会让「关闭」复用上一次「设置/解锁」残留的 logic
|
||||
global: false,
|
||||
builder: (logic) => Scaffold(
|
||||
//解锁校验时还进不了 app,不给返回入口
|
||||
appBar: logic.isCheck ? null : AppBar(title: const Text('手势密码')),
|
||||
//必须给满宽约束:Column 横向是按最宽的子节点收缩的(这里 250),而 Scaffold 的 body
|
||||
//是贴 (0,0) 摆放的,不撑满就整块贴左边;撑满后 Column 默认的居中对齐才真正生效
|
||||
body: SizedBox(
|
||||
width: double.infinity,
|
||||
child: Column(
|
||||
children: [
|
||||
//没有 AppBar 顶着,用锁图标占位
|
||||
if (logic.isCheck) ...[
|
||||
65.sizeBoxH,
|
||||
Image.asset('mine_lock_password.png'.mineImgPath, width: 29),
|
||||
],
|
||||
55.sizeBoxH,
|
||||
Text(
|
||||
logic.title,
|
||||
style: TextStyle(
|
||||
color: Colors.black.withValues(alpha: 0.9), fontSize: 18),
|
||||
),
|
||||
75.sizeBoxH,
|
||||
GesturePasswordWidget(
|
||||
lineColor: const Color(0xFFF52C56),
|
||||
errorLineColor: const Color(0xffDD001B),
|
||||
singleLineCount: 3,
|
||||
identifySize: 60.0,
|
||||
size: 250,
|
||||
minLength: 4,
|
||||
errorItem: Image.asset('error.webp'.mineImgPath,
|
||||
color: const Color(0xFFF52C56)),
|
||||
selectedItem: Image.asset('select.png'.mineImgPath,
|
||||
color: const Color(0xFFF52C56)),
|
||||
normalItem: Image.asset('normal.png'.mineImgPath),
|
||||
onComplete: logic.onComplete,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:image_pickers/image_pickers.dart';
|
||||
import 'package:hgdj/hj_utils/image_util.dart';
|
||||
import 'package:hgdj/tools_base/event_bus/event_bus_util.dart';
|
||||
import 'package:hgdj/tools_base/event_bus/events.dart';
|
||||
import 'package:hgdj/tools_base/global_store/store.dart';
|
||||
import 'package:hgdj/tools_base/loading/loading_helper.dart';
|
||||
import 'package:hgdj/tools_base/net/net_manager.dart';
|
||||
import 'package:hgdj/tools_base/toast.dart';
|
||||
import 'package:qr_code_scanner/qr_code_scanner.dart';
|
||||
|
||||
import '../identity/mine_identity_page.dart';
|
||||
|
||||
class MineScanLginLogic extends GetxController {
|
||||
QRViewController? scanController;
|
||||
|
||||
@override
|
||||
void onClose() {
|
||||
scanController?.dispose();
|
||||
super.onClose();
|
||||
}
|
||||
|
||||
onQRViewCreated(QRViewController controller) {
|
||||
scanController = controller;
|
||||
|
||||
scanController?.scannedDataStream.listen((scanData) {
|
||||
print('扫码结果:${scanData.code}');
|
||||
loginByQrValue(scanData.code ?? '');
|
||||
});
|
||||
}
|
||||
|
||||
/// 开启本地相册
|
||||
openNativePhoto() async {
|
||||
// image_picker 走系统相册 intent,选图不需要存储权限,直接调起
|
||||
final images = await ImagePickers.pickerPaths(
|
||||
uiConfig: UIConfig(uiThemeColor: Colors.white),
|
||||
selectCount: 1,
|
||||
showCamera: false,
|
||||
cropConfig: CropConfig(enableCrop: false),
|
||||
);
|
||||
if (images.isNotEmpty) {
|
||||
final qrValue = await ImageUtil.decodeQr(images[0].path ?? '');
|
||||
if (qrValue == null || qrValue.isEmpty) {
|
||||
showToast('二维码错误~~');
|
||||
return;
|
||||
}
|
||||
loginByQrValue(qrValue);
|
||||
}
|
||||
}
|
||||
|
||||
/// 是否正在处理一次扫码登录。扫码流会按摄像头帧连续 emit,pauseCamera 异步拦不住已缓冲的帧,
|
||||
/// 不加锁会并发触发 N 次 loginByQr → token 被反复清空/轮换,服务端单会话把旧 token 全废,
|
||||
/// 满屏 5009/5005「用户信息已经过期」,最终 token 落空、换号失败。
|
||||
bool _isHandling = false;
|
||||
|
||||
/// 开始二维码登录
|
||||
loginByQrValue(String qrValue) async {
|
||||
if (_isHandling) return; // 一次扫码只处理一次,挡掉连续帧/重复触发
|
||||
if (qrValue.isEmpty) {
|
||||
showToast("二维码为空");
|
||||
return;
|
||||
}
|
||||
_isHandling = true;
|
||||
scanController?.pauseCamera();
|
||||
LoadingHelper.showLoading();
|
||||
var userInfo = await globalStore.loginByQr(qrValue);
|
||||
LoadingHelper.dismissLoading();
|
||||
// 必须拿到 token 才算成功:只有 uid 没 token 会留下空 token,下一个请求立刻被判过期
|
||||
if (userInfo?.uid == null || (userInfo?.token ?? '').isEmpty) {
|
||||
showToast("切换账号失败");
|
||||
scanController?.resumeCamera();
|
||||
_isHandling = false; // 失败放开,允许重试
|
||||
return;
|
||||
}
|
||||
// 刷新ua
|
||||
netManager.refreshUserAgent();
|
||||
showToast('登录成功');
|
||||
eventBus.emit(ReLoginEvent());
|
||||
}
|
||||
|
||||
/// 跳转凭证
|
||||
jumpToCertificate() => Get.to(MineAccountIdentityPage());
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:hgdj/assets_tool/images.dart';
|
||||
import 'package:hgdj/hj_utils/screen.dart';
|
||||
import 'package:qr_code_scanner/qr_code_scanner.dart';
|
||||
|
||||
import 'mine_scan_login_logic.dart';
|
||||
|
||||
class MineScanLoginPage extends StatefulWidget {
|
||||
const MineScanLoginPage({super.key});
|
||||
|
||||
@override
|
||||
State<MineScanLoginPage> createState() => _MineScanLoginPageState();
|
||||
}
|
||||
|
||||
class _MineScanLoginPageState extends State<MineScanLoginPage> {
|
||||
final GlobalKey qrKey = GlobalKey(debugLabel: 'QR');
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return GetBuilder<MineScanLginLogic>(
|
||||
init: MineScanLginLogic(),
|
||||
builder: (controller) => Scaffold(
|
||||
extendBodyBehindAppBar: true,
|
||||
appBar: AppBar(
|
||||
elevation: 0,
|
||||
iconTheme: IconThemeData(color: Colors.white),
|
||||
backgroundColor: Colors.transparent,
|
||||
title: Text(
|
||||
'扫码登录',
|
||||
style: TextStyle(
|
||||
color: Colors.white, fontSize: 18, fontWeight: FontWeight.w500),
|
||||
),
|
||||
),
|
||||
body: Stack(
|
||||
children: [
|
||||
QRView(
|
||||
key: qrKey,
|
||||
onQRViewCreated: controller.onQRViewCreated,
|
||||
overlay: QrScannerOverlayShape(
|
||||
overlayColor: Colors.black,
|
||||
borderColor: Colors.white,
|
||||
borderRadius: 0,
|
||||
borderLength: 20,
|
||||
borderWidth: 5,
|
||||
cutOutSize: 235),
|
||||
),
|
||||
Positioned(
|
||||
bottom: 100,
|
||||
left: 0,
|
||||
right: 0,
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Expanded(
|
||||
child: GestureDetector(
|
||||
onTap: controller.openNativePhoto,
|
||||
child: Column(
|
||||
children: [
|
||||
Image.asset('scan_photo.png'.mineImgPath, width: 48),
|
||||
12.sizeBoxH,
|
||||
Text(
|
||||
'相册',
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.w500),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: GestureDetector(
|
||||
onTap: controller.jumpToCertificate,
|
||||
child: Column(
|
||||
children: [
|
||||
Image.asset('mine_id.png'.mineImgPath, width: 48),
|
||||
12.sizeBoxH,
|
||||
Text(
|
||||
'我的凭证',
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.w500),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,247 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:hgdj/routers/jump_router.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:hgdj/assets_tool/images.dart';
|
||||
import 'package:hgdj/config/config.dart';
|
||||
import 'package:hgdj/tools_base/toast.dart';
|
||||
import 'package:hgdj/hj_utils/screen.dart';
|
||||
import 'package:hgdj/hj_utils/version_util.dart';
|
||||
import 'package:hgdj/hj_utils/video_cache_manager.dart';
|
||||
import 'package:hgdj/tools_base/cache/cache_util.dart';
|
||||
import 'package:hgdj/tools_base/cache/image_cache_manager.dart';
|
||||
import 'package:hgdj/tools_base/global_store/store.dart';
|
||||
import 'package:hgdj/tools_base/image/image_data_handle/image_cache_disk.dart';
|
||||
import 'package:hgdj/tools_base/video_download/video_download_manager.dart';
|
||||
import 'package:hgdj/tools_base/widget/net_image_widget.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:hgdj/tools_base/widget/common_alert.dart';
|
||||
|
||||
import '../../../alert/splash/update_dialog.dart';
|
||||
import '../identity/mine_identity_page.dart';
|
||||
import '../more_question/mine_qa_page.dart';
|
||||
import 'bing_phone_page.dart';
|
||||
import 'invite_bind_page.dart';
|
||||
import 'mine_find_account_page.dart';
|
||||
import 'mine_setting_profile_page.dart';
|
||||
import 'setting_avatar_page.dart';
|
||||
|
||||
class MineSettingPage extends StatefulWidget {
|
||||
const MineSettingPage({super.key});
|
||||
|
||||
@override
|
||||
State<MineSettingPage> createState() => _MineSettingPageState();
|
||||
}
|
||||
|
||||
class _MineSettingPageState extends State<MineSettingPage> {
|
||||
String cacheSize = ''; // 缓存大小
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
getCacheSize();
|
||||
}
|
||||
|
||||
Future<void> getCacheSize() async {
|
||||
try {
|
||||
cacheSize = await loadCache();
|
||||
setState(() {});
|
||||
} catch (e) {
|
||||
debugPrint(e.toString());
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final meInfo = context.watch<GlobalStore>().meInfo;
|
||||
final mobile = meInfo?.mobile ?? '';
|
||||
final inviterCode = meInfo?.inviterCode ?? '';
|
||||
return Scaffold(
|
||||
appBar: AppBar(title: Text('设置中心')),
|
||||
body: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white.withValues(alpha: .05),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
margin: EdgeInsets.only(
|
||||
left: 10,
|
||||
top: 12,
|
||||
right: 10,
|
||||
),
|
||||
child: ListView(
|
||||
children: [
|
||||
_buildItem(
|
||||
title: '头像',
|
||||
avatarUrl: meInfo?.portrait ?? '',
|
||||
onTap: () async {
|
||||
if (!globalStore.isVIP) {
|
||||
if (await CommonAlert.show(
|
||||
content: '您还不是VIP无法修改头像',
|
||||
subContent: '开通会员 即可解锁继续',
|
||||
showCancel: false,
|
||||
confirmText: '开通会员',
|
||||
)) {
|
||||
pushToWalletPage();
|
||||
}
|
||||
return;
|
||||
}
|
||||
Get.to(() => const SettingAvatarPage());
|
||||
}),
|
||||
_buildLine(),
|
||||
_buildItem(
|
||||
title: '昵称',
|
||||
subTitle: meInfo?.name,
|
||||
onTap: () async {
|
||||
if (!globalStore.isVIP) {
|
||||
if (await CommonAlert.show(
|
||||
content: '您还不是VIP无法修改昵称!',
|
||||
subContent: '开通会员 即可解锁继续',
|
||||
showCancel: false,
|
||||
confirmText: '开通会员',
|
||||
)) {
|
||||
pushToWalletPage();
|
||||
}
|
||||
return;
|
||||
}
|
||||
Get.to(() =>
|
||||
const SettingProfilePage(SettingProfileType.nickName));
|
||||
}),
|
||||
_buildLine(),
|
||||
_buildItem(
|
||||
title: '${Config.appName}ID',
|
||||
subTitle: meInfo?.uid.toString(),
|
||||
showCopy: true,
|
||||
onTap: () {}),
|
||||
_buildLine(),
|
||||
_buildItem(
|
||||
title: '手机号码',
|
||||
subTitle: mobile.isEmpty ? '立即绑定' : mobile,
|
||||
onTap: () {
|
||||
Get.to(() => const BindPhonePage());
|
||||
}),
|
||||
_buildLine(),
|
||||
_buildItem(
|
||||
title: '账号找回',
|
||||
onTap: () {
|
||||
Get.to(() => MineFindAccountPage());
|
||||
}),
|
||||
_buildLine(),
|
||||
_buildItem(
|
||||
title: '邀请码',
|
||||
subTitle: inviterCode.isEmpty ? '未设置' : inviterCode,
|
||||
onTap: () {
|
||||
if (inviterCode.isEmpty) {
|
||||
Get.to(() => const InviteBindPage());
|
||||
} else {
|
||||
showToast('您已绑定过邀请码');
|
||||
}
|
||||
}),
|
||||
_buildLine(),
|
||||
_buildItem(
|
||||
title: '账号凭证',
|
||||
onTap: () {
|
||||
Get.to(() => MineAccountIdentityPage());
|
||||
}),
|
||||
_buildLine(),
|
||||
_buildItem(
|
||||
title: '常见问题',
|
||||
onTap: () {
|
||||
Get.to(() => MineQAPage());
|
||||
}),
|
||||
_buildLine(),
|
||||
_buildItem(
|
||||
title: '清除缓存',
|
||||
subTitle: cacheSize,
|
||||
onTap: () async {
|
||||
await VideoDownloadManager.instance.emptyCache();
|
||||
await ImageCacheDisk.emptyCache();
|
||||
await VideoCacheManager().emptyCache();
|
||||
await ImageCacheManager().emptyCache();
|
||||
cacheSize = "0.0KB";
|
||||
if (mounted) setState(() {});
|
||||
showToast('清理缓存成功');
|
||||
}),
|
||||
_buildLine(),
|
||||
_buildItem(
|
||||
title: '检查更新',
|
||||
subTitle: 'V${Config.innerVersion}',
|
||||
onTap: () {
|
||||
//比对启动页拉到的版本配置,确实有新版才弹更新框
|
||||
final target = checkUpdate();
|
||||
target == null
|
||||
? showToast("当前已是最新版!")
|
||||
: UpdateDialog.show(target);
|
||||
}),
|
||||
_buildLine(),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildLine() {
|
||||
return Padding(padding: EdgeInsets.only(left: 16), child: .5.line);
|
||||
}
|
||||
|
||||
Widget _buildItem({
|
||||
String title = '',
|
||||
String? subTitle,
|
||||
bool showCopy = false,
|
||||
String? avatarUrl,
|
||||
VoidCallback? onTap,
|
||||
}) {
|
||||
return GestureDetector(
|
||||
behavior: HitTestBehavior.translucent,
|
||||
onTap: onTap,
|
||||
child: Container(
|
||||
height: 56,
|
||||
padding: EdgeInsets.symmetric(horizontal: 12),
|
||||
child: Row(
|
||||
children: [
|
||||
Text(
|
||||
title,
|
||||
style: TextStyle(
|
||||
color: Colors.white.withValues(alpha: 0.9), fontSize: 14),
|
||||
),
|
||||
Spacer(),
|
||||
if (avatarUrl != null)
|
||||
NetworkImageLoader(
|
||||
imageUrl: avatarUrl,
|
||||
width: 24,
|
||||
height: 24,
|
||||
borderRadius: 12,
|
||||
),
|
||||
if (subTitle != null)
|
||||
Text(
|
||||
subTitle,
|
||||
style: TextStyle(
|
||||
color: Colors.white.withValues(alpha: 0.45), fontSize: 12),
|
||||
),
|
||||
if (showCopy) ...[
|
||||
4.sizeBoxW,
|
||||
GestureDetector(
|
||||
onTap: () {
|
||||
Clipboard.setData(
|
||||
ClipboardData(text: '${globalStore.meInfo?.uid}'));
|
||||
showToast('复制成功');
|
||||
},
|
||||
child: Image.asset(
|
||||
'mine_copy.png'.mineImgPath,
|
||||
width: 24,
|
||||
),
|
||||
),
|
||||
],
|
||||
if (!showCopy) ...[
|
||||
10.sizeBoxW,
|
||||
Icon(
|
||||
Icons.arrow_forward_ios_rounded,
|
||||
size: 14,
|
||||
color: Color(0xFF70708C),
|
||||
),
|
||||
]
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:hgdj/hj_utils/api_service/mine_service.dart';
|
||||
import 'package:hgdj/tools_base/global_store/store.dart';
|
||||
import 'package:hgdj/tools_base/loading/loading_helper.dart';
|
||||
import 'package:hgdj/tools_base/toast.dart';
|
||||
|
||||
import 'mine_setting_profile_page.dart';
|
||||
import 'setting_profile_widget.dart';
|
||||
|
||||
abstract class MineSettingProfilePage extends GetxController {
|
||||
String get title => '';
|
||||
|
||||
late final nickNameTfCtr = TextEditingController();
|
||||
late final sloganTfCtr = TextEditingController();
|
||||
|
||||
onSaveProfile();
|
||||
|
||||
instanceChildItem(int index);
|
||||
|
||||
@override
|
||||
void onClose() {
|
||||
nickNameTfCtr.dispose();
|
||||
sloganTfCtr.dispose();
|
||||
super.onClose();
|
||||
}
|
||||
}
|
||||
|
||||
class SettingNickNameController extends MineSettingProfilePage {
|
||||
@override
|
||||
String get title => '修改昵称';
|
||||
|
||||
@override
|
||||
onSaveProfile() async {
|
||||
if (nickNameTfCtr.text.isEmpty) {
|
||||
showToast('昵称不能为空~');
|
||||
return;
|
||||
}
|
||||
LoadingHelper.showLoading();
|
||||
final res = await MineService.updateUserInfo({'name': nickNameTfCtr.text});
|
||||
LoadingHelper.dismissLoading();
|
||||
if (res) {
|
||||
globalStore.updateUserInfo();
|
||||
Get.back();
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
instanceChildItem(int index) {
|
||||
return SettingNickName();
|
||||
}
|
||||
}
|
||||
|
||||
class SettingSloganController extends MineSettingProfilePage {
|
||||
@override
|
||||
String get title => '个性签名';
|
||||
|
||||
@override
|
||||
onSaveProfile() async {
|
||||
if (sloganTfCtr.text.isEmpty) {
|
||||
showToast('请输入个性签名~');
|
||||
return;
|
||||
}
|
||||
LoadingHelper.showLoading();
|
||||
final res = await MineService.updateUserInfo({'summary': sloganTfCtr.text});
|
||||
LoadingHelper.dismissLoading();
|
||||
if (res) {
|
||||
globalStore.updateUserInfo();
|
||||
Get.back();
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
instanceChildItem(int index) {
|
||||
return SettingSlogan();
|
||||
}
|
||||
}
|
||||
|
||||
MineSettingProfilePage instanceSettingProfileController(
|
||||
SettingProfileType type) {
|
||||
switch (type) {
|
||||
case SettingProfileType.nickName:
|
||||
return SettingNickNameController();
|
||||
case SettingProfileType.slogan:
|
||||
return SettingSloganController();
|
||||
default:
|
||||
throw '';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:hgdj/hj_utils/screen.dart';
|
||||
|
||||
import 'mine_setting_profile_logic.dart';
|
||||
|
||||
enum SettingProfileType {
|
||||
nickName,
|
||||
slogan,
|
||||
}
|
||||
|
||||
class SettingProfilePage extends StatelessWidget {
|
||||
final SettingProfileType type;
|
||||
const SettingProfilePage(this.type, {super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return GetBuilder<MineSettingProfilePage>(
|
||||
init: instanceSettingProfileController(type),
|
||||
builder: (controller) => Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text(controller.title),
|
||||
actions: [
|
||||
GestureDetector(
|
||||
onTap: () => controller.onSaveProfile(),
|
||||
child: Text(
|
||||
'保存',
|
||||
style: TextStyle(color: Color(0xff999999), fontSize: 14),
|
||||
),
|
||||
),
|
||||
16.sizeBoxW
|
||||
],
|
||||
),
|
||||
body: controller.instanceChildItem(0),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
import 'package:get/get.dart';
|
||||
import 'package:hgdj/hj_utils/api_service/mine_service.dart';
|
||||
import 'package:hgdj/tools_base/base_list_controller.dart';
|
||||
import 'package:hgdj/tools_base/global_store/store.dart';
|
||||
import 'package:hgdj/tools_base/toast.dart';
|
||||
|
||||
class SettingAvatarLogic extends ListBaseLogic<String> {
|
||||
int selectIndex = -1;
|
||||
late final int rawIndex;
|
||||
|
||||
@override
|
||||
void onReady() {
|
||||
super.onReady();
|
||||
loadData();
|
||||
}
|
||||
|
||||
void loadData() => fetchData(isRefresh: true, fetch: _fetch);
|
||||
|
||||
//头像列表无分页,单次拉取,hasNext 固定 false
|
||||
Future<(List<String>?, bool)> _fetch(int page) async {
|
||||
final res = await MineService.getPortrait();
|
||||
selectIndex = res.indexOf(globalStore.meInfo?.portrait ?? '');
|
||||
rawIndex = selectIndex;
|
||||
return (res, false);
|
||||
}
|
||||
|
||||
selectAavatar(int index) {
|
||||
selectIndex = index;
|
||||
update();
|
||||
}
|
||||
|
||||
confirmChangeAvatar() async {
|
||||
if (globalStore.meInfo?.urrPortraitStatus == 1) {
|
||||
showToast('头像审核中~');
|
||||
return;
|
||||
}
|
||||
if (selectIndex == rawIndex || selectIndex == -1) {
|
||||
showToast('你都没选择,保存什么~');
|
||||
return;
|
||||
}
|
||||
final res = await MineService.updateUserInfo(
|
||||
{'portrait': dataList![selectIndex], 'isDefaultSource': true});
|
||||
if (res) {
|
||||
globalStore.updateUserInfo();
|
||||
showToast('更新成功');
|
||||
Get.back(result: true);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:hgdj/assets_tool/images.dart';
|
||||
import 'package:hgdj/hj_utils/screen.dart';
|
||||
import 'package:hgdj/tools_base/global_store/store.dart';
|
||||
import 'package:hgdj/tools_base/loading/loading_center_widget.dart';
|
||||
import 'package:hgdj/tools_base/widget/net_image_widget.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
import 'setting_avatar_logic.dart';
|
||||
|
||||
class SettingAvatarPage extends StatelessWidget {
|
||||
const SettingAvatarPage({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return GetBuilder<SettingAvatarLogic>(
|
||||
init: SettingAvatarLogic(),
|
||||
builder: (controller) => Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text('选择头像'),
|
||||
actions: [
|
||||
InkWell(
|
||||
enableFeedback: false,
|
||||
onTap: controller.confirmChangeAvatar,
|
||||
child: Text(
|
||||
'保存',
|
||||
style: TextStyle(
|
||||
color: Colors.white.withValues(alpha: 0.55), fontSize: 12),
|
||||
),
|
||||
),
|
||||
16.sizeBoxW,
|
||||
],
|
||||
),
|
||||
body: () {
|
||||
if (controller.isLoading) return LoadingCenterWidget();
|
||||
if (controller.isEmptyData) return CErrorWidget();
|
||||
final list = controller.dataList!;
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
30.verticalSpace,
|
||||
NetworkImageLoader(
|
||||
imageUrl: context.watch<GlobalStore>().meInfo?.portrait ?? '',
|
||||
width: 90,
|
||||
height: 90,
|
||||
borderRadius: 45,
|
||||
),
|
||||
10.verticalSpace,
|
||||
Text(
|
||||
'当前头像',
|
||||
style: TextStyle(
|
||||
color: Colors.white.withValues(alpha: 0.55), fontSize: 12),
|
||||
),
|
||||
20.verticalSpace,
|
||||
Expanded(
|
||||
child: GridView.builder(
|
||||
padding: EdgeInsets.only(left: 38, right: 38, top: 0),
|
||||
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
|
||||
crossAxisCount: 4,
|
||||
mainAxisSpacing: 20,
|
||||
crossAxisSpacing: 20,
|
||||
childAspectRatio: 1,
|
||||
),
|
||||
itemCount: list.length,
|
||||
itemBuilder: (BuildContext context, int index) {
|
||||
final select = controller.selectIndex == index;
|
||||
return GestureDetector(
|
||||
onTap: () => controller.selectAavatar(index),
|
||||
child: Stack(
|
||||
children: [
|
||||
NetworkImageLoader(
|
||||
imageUrl: list[index],
|
||||
width: double.infinity,
|
||||
height: double.infinity,
|
||||
borderRadius: 100,
|
||||
),
|
||||
if (select)
|
||||
Align(
|
||||
alignment: Alignment.bottomRight,
|
||||
child: Image.asset(
|
||||
'red_checked.png'.mineImgPath,
|
||||
width: 22,
|
||||
height: 22,
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
12.sizeBoxH,
|
||||
],
|
||||
);
|
||||
}(),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,136 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:hgdj/assets_tool/images.dart';
|
||||
import 'package:hgdj/hj_utils/screen.dart';
|
||||
import 'package:hgdj/tools_base/global_store/store.dart';
|
||||
|
||||
import 'mine_setting_profile_logic.dart';
|
||||
|
||||
class SettingNickName extends StatefulWidget {
|
||||
const SettingNickName({super.key});
|
||||
|
||||
@override
|
||||
State<SettingNickName> createState() => _SettingNickNameState();
|
||||
}
|
||||
|
||||
class _SettingNickNameState extends State<SettingNickName> {
|
||||
late final controller = Get.find<MineSettingProfilePage>();
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
controller.nickNameTfCtr.text = globalStore.meInfo?.name ?? '';
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 16),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
60.sizeBoxH,
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: TextField(
|
||||
controller: controller.nickNameTfCtr,
|
||||
maxLength: 20,
|
||||
style: TextStyle(
|
||||
color: Colors.white.withValues(alpha: 0.9), fontSize: 18),
|
||||
decoration: InputDecoration(
|
||||
border: InputBorder.none,
|
||||
hintText: '输入新的昵称',
|
||||
hintStyle: TextStyle(
|
||||
color: Colors.white.withValues(alpha: 0.35),
|
||||
fontSize: 18),
|
||||
counterText: '',
|
||||
isCollapsed: true,
|
||||
contentPadding: EdgeInsets.zero),
|
||||
),
|
||||
),
|
||||
InkWell(
|
||||
enableFeedback: false,
|
||||
onTap: () => controller.nickNameTfCtr.clear(),
|
||||
child: Container(
|
||||
padding: EdgeInsets.all(2),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.black.withValues(alpha: 0.9),
|
||||
borderRadius: BorderRadius.circular(90)),
|
||||
child: Image.asset(
|
||||
'close_button.png'.commonImgPath,
|
||||
width: 24,
|
||||
)),
|
||||
)
|
||||
],
|
||||
),
|
||||
12.sizeBoxH,
|
||||
0.5.line,
|
||||
12.sizeBoxH,
|
||||
Text(
|
||||
'注意*诱导性昵称会被投诉封号',
|
||||
style: TextStyle(
|
||||
color: Colors.white.withValues(alpha: 0.35), fontSize: 12),
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class SettingSlogan extends StatefulWidget {
|
||||
const SettingSlogan({super.key});
|
||||
|
||||
@override
|
||||
State<SettingSlogan> createState() => _SettingSloganState();
|
||||
}
|
||||
|
||||
class _SettingSloganState extends State<SettingSlogan> {
|
||||
late final controller = Get.find<MineSettingProfilePage>();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
margin: EdgeInsets.only(left: 16, top: 12, right: 16),
|
||||
padding: EdgeInsets.all(10),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.black.withValues(alpha: .04),
|
||||
borderRadius: BorderRadius.circular(8)),
|
||||
constraints: BoxConstraints(
|
||||
minHeight: 111,
|
||||
),
|
||||
child: Stack(
|
||||
children: [
|
||||
TextField(
|
||||
maxLines: 10,
|
||||
style: const TextStyle(color: Color(0xff333333), fontSize: 12),
|
||||
maxLength: 150,
|
||||
controller: controller.sloganTfCtr,
|
||||
decoration: InputDecoration(
|
||||
border: InputBorder.none,
|
||||
hintText: '有趣的介绍能让你的逼格提高N个档次!...',
|
||||
hintStyle: TextStyle(color: Color(0xff999999), fontSize: 12),
|
||||
counterText: '',
|
||||
contentPadding: EdgeInsets.zero,
|
||||
isDense: true),
|
||||
),
|
||||
Positioned(
|
||||
bottom: 0,
|
||||
right: 0,
|
||||
// 字数计数:只监听不接管所有权(ChangeNotifierProvider(create:) 会把 ctr 一起 dispose,
|
||||
// 而它归 MineSettingProfilePage.onClose 释放 → 二次释放)
|
||||
child: ValueListenableBuilder(
|
||||
valueListenable: controller.sloganTfCtr,
|
||||
builder: (_, value, __) => Text(
|
||||
'${value.text.length}/150',
|
||||
style: TextStyle(
|
||||
color: Color(0xff666666),
|
||||
fontSize: 12,
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user