Browse Source

编写首页、添加提交头像

ios
Jolie 4 months ago
parent
commit
c986d6a2e0
18 changed files with 927 additions and 158 deletions
  1. BIN
      assets/images/btn_bg_icon.png
  2. BIN
      assets/images/hi_icon.png
  3. BIN
      assets/images/live_icon.png
  4. BIN
      assets/images/online_icon.png
  5. BIN
      assets/images/send_message_icon.png
  6. BIN
      assets/images/tab_change_icon.png
  7. BIN
      assets/images/verified_icon.png
  8. 43
      ios/Podfile
  9. 65
      ios/Podfile.lock
  10. 18
      ios/Runner.xcodeproj/project.pbxproj
  11. 78
      lib/controller/mine/user_info_controller.dart
  12. 7
      lib/generated/assets.dart
  13. 2
      lib/main.dart
  14. 398
      lib/pages/home/home_page.dart
  15. 2
      lib/pages/main/main_page.dart
  16. 174
      lib/pages/mine/login_controller.dart
  17. 18
      lib/pages/mine/user_info_page.dart
  18. 280
      pubspec.lock

BIN
assets/images/btn_bg_icon.png

Before After
Width: 148  |  Height: 56  |  Size: 1.1 KiB

BIN
assets/images/hi_icon.png

Before After
Width: 166  |  Height: 83  |  Size: 2.0 KiB

BIN
assets/images/live_icon.png

Before After
Width: 241  |  Height: 83  |  Size: 4.8 KiB

BIN
assets/images/online_icon.png

Before After
Width: 48  |  Height: 48  |  Size: 718 B

BIN
assets/images/send_message_icon.png

Before After
Width: 166  |  Height: 83  |  Size: 4.3 KiB

BIN
assets/images/tab_change_icon.png

Before After
Width: 63  |  Height: 20  |  Size: 419 B

BIN
assets/images/verified_icon.png

Before After
Width: 32  |  Height: 28  |  Size: 451 B

43
ios/Podfile

@ -0,0 +1,43 @@
# Uncomment this line to define a global platform for your project
# platform :ios, '13.0'
# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
ENV['COCOAPODS_DISABLE_STATS'] = 'true'
project 'Runner', {
'Debug' => :debug,
'Profile' => :release,
'Release' => :release,
}
def flutter_root
generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__)
unless File.exist?(generated_xcode_build_settings_path)
raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first"
end
File.foreach(generated_xcode_build_settings_path) do |line|
matches = line.match(/FLUTTER_ROOT\=(.*)/)
return matches[1].strip if matches
end
raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get"
end
require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root)
flutter_ios_podfile_setup
target 'Runner' do
use_frameworks!
flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__))
target 'RunnerTests' do
inherit! :search_paths
end
end
post_install do |installer|
installer.pods_project.targets.each do |target|
flutter_additional_ios_build_settings(target)
end
end

65
ios/Podfile.lock

@ -0,0 +1,65 @@
PODS:
- AgoraInfra_iOS (1.2.13.1)
- Flutter (1.0.0)
- flutter_native_splash (2.4.3):
- Flutter
- HyphenateChat (4.15.0):
- AgoraInfra_iOS (~> 1.2.13)
- im_flutter_sdk_ios (4.14.0):
- Flutter
- HyphenateChat (= 4.15.0)
- image_picker_ios (0.0.1):
- Flutter
- path_provider_foundation (0.0.1):
- Flutter
- FlutterMacOS
- permission_handler_apple (9.3.0):
- Flutter
- shared_preferences_foundation (0.0.1):
- Flutter
- FlutterMacOS
DEPENDENCIES:
- Flutter (from `Flutter`)
- flutter_native_splash (from `.symlinks/plugins/flutter_native_splash/ios`)
- im_flutter_sdk_ios (from `.symlinks/plugins/im_flutter_sdk_ios/ios`)
- image_picker_ios (from `.symlinks/plugins/image_picker_ios/ios`)
- path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`)
- permission_handler_apple (from `.symlinks/plugins/permission_handler_apple/ios`)
- shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`)
SPEC REPOS:
trunk:
- AgoraInfra_iOS
- HyphenateChat
EXTERNAL SOURCES:
Flutter:
:path: Flutter
flutter_native_splash:
:path: ".symlinks/plugins/flutter_native_splash/ios"
im_flutter_sdk_ios:
:path: ".symlinks/plugins/im_flutter_sdk_ios/ios"
image_picker_ios:
:path: ".symlinks/plugins/image_picker_ios/ios"
path_provider_foundation:
:path: ".symlinks/plugins/path_provider_foundation/darwin"
permission_handler_apple:
:path: ".symlinks/plugins/permission_handler_apple/ios"
shared_preferences_foundation:
:path: ".symlinks/plugins/shared_preferences_foundation/darwin"
SPEC CHECKSUMS:
AgoraInfra_iOS: 3691b2b277a1712a35ae96de25af319de0d73d08
Flutter: cabc95a1d2626b1b06e7179b784ebcf0c0cde467
flutter_native_splash: df59bb2e1421aa0282cb2e95618af4dcb0c56c29
HyphenateChat: 4523c7fb2075771c49a2c492b31544d6cc82ff50
im_flutter_sdk_ios: 2348d34baa17e98d8c490d92023410956c8afee1
image_picker_ios: 4f2f91b01abdb52842a8e277617df877e40f905b
path_provider_foundation: 0b743cbb62d8e47eab856f09262bb8c1ddcfe6ba
permission_handler_apple: 9878588469a2b0d0fc1e048d9f43605f92e6cec2
shared_preferences_foundation: 5086985c1d43c5ba4d5e69a4e8083a389e2909e6
PODFILE CHECKSUM: 3c63482e143d1b91d2d2560aee9fb04ecc74ac7e
COCOAPODS: 1.16.2

18
ios/Runner.xcodeproj/project.pbxproj

@ -199,6 +199,7 @@
9705A1C41CF9048500538489 /* Embed Frameworks */, 9705A1C41CF9048500538489 /* Embed Frameworks */,
3B06AD1E1E4923F5004D2608 /* Thin Binary */, 3B06AD1E1E4923F5004D2608 /* Thin Binary */,
A36064B7643972D96D7D5F55 /* [CP] Embed Pods Frameworks */, A36064B7643972D96D7D5F55 /* [CP] Embed Pods Frameworks */,
60F9142B6626BDFC64679D81 /* [CP] Copy Pods Resources */,
); );
buildRules = ( buildRules = (
); );
@ -330,6 +331,23 @@
shellPath = /bin/sh; shellPath = /bin/sh;
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin";
}; };
60F9142B6626BDFC64679D81 /* [CP] Copy Pods Resources */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-input-files.xcfilelist",
);
name = "[CP] Copy Pods Resources";
outputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-output-files.xcfilelist",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources.sh\"\n";
showEnvVarsInLog = 0;
};
9740EEB61CF901F6004384FC /* Run Script */ = { 9740EEB61CF901F6004384FC /* Run Script */ = {
isa = PBXShellScriptBuildPhase; isa = PBXShellScriptBuildPhase;
alwaysOutOfDate = 1; alwaysOutOfDate = 1;

78
lib/controller/mine/user_info_controller.dart

@ -2,6 +2,7 @@ import 'dart:io';
import 'package:dating_touchme_app/oss/oss_manager.dart'; import 'package:dating_touchme_app/oss/oss_manager.dart';
import 'package:flustars/flustars.dart'; import 'package:flustars/flustars.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:permission_handler/permission_handler.dart';
import 'package:get_storage/get_storage.dart'; import 'package:get_storage/get_storage.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:image_picker/image_picker.dart'; import 'package:image_picker/image_picker.dart';
@ -16,6 +17,8 @@ class UserInfoController extends GetxController {
final education = ''.obs; final education = ''.obs;
final invitationCode = ''.obs; final invitationCode = ''.obs;
final avatarUrl = ''.obs; final avatarUrl = ''.obs;
//
final avatarLocalPath = ''.obs;
// //
final isSubmitting = false.obs; final isSubmitting = false.obs;
@ -56,12 +59,23 @@ class UserInfoController extends GetxController {
// - // -
Future<void> handleCameraCapture() async { Future<void> handleCameraCapture() async {
try { try {
//
//
final ok = await _ensurePermission(
Permission.camera,
denyToast: '相机权限被拒绝,请在设置中允许访问相机',
);
if (!ok) return;
// /
await _ensurePermission(Permission.microphone, denyToast: '麦克风权限被拒绝');
//
final ImagePicker picker = ImagePicker(); final ImagePicker picker = ImagePicker();
final XFile? photo = await picker.pickImage(source: ImageSource.camera); final XFile? photo = await picker.pickImage(source: ImageSource.camera);
if (photo != null) { if (photo != null) {
await processSelectedImage(File(photo.path));
avatarLocalPath.value = photo.path;
SmartDialog.showToast('已选择照片');
} }
} catch (e) { } catch (e) {
print('拍照失败: $e'); print('拍照失败: $e');
@ -79,12 +93,22 @@ class UserInfoController extends GetxController {
Future<void> handleGallerySelection() async { Future<void> handleGallerySelection() async {
try { try {
// /
// final ok = await _ensurePermission(
// Permission.photos,
// // Android photos storage/mediaLibrarypermission_handler
// denyToast: '相册权限被拒绝,请在设置中允许访问相册',
// );
// if (!ok) return;
// //
final ImagePicker picker = ImagePicker(); final ImagePicker picker = ImagePicker();
final XFile? image = await picker.pickImage(source: ImageSource.gallery); final XFile? image = await picker.pickImage(source: ImageSource.gallery);
if (image != null) { if (image != null) {
await processSelectedImage(File(image.path));
avatarLocalPath.value = image.path;
// SmartDialog.showToast('已选择图片');
} }
} catch (e) { } catch (e) {
print('选择图片失败: $e'); print('选择图片失败: $e');
@ -97,6 +121,27 @@ class UserInfoController extends GetxController {
} }
} }
//
Future<bool> _ensurePermission(Permission permission, {String? denyToast}) async {
var status = await permission.status;
if (status.isGranted) return true;
if (status.isDenied || status.isRestricted || status.isLimited) {
status = await permission.request();
if (status.isGranted) return true;
if (denyToast != null) SmartDialog.showToast(denyToast);
return false;
}
if (status.isPermanentlyDenied) {
if (denyToast != null) SmartDialog.showToast('$denyToast,前往系统设置开启');
//
Future.delayed(const Duration(milliseconds: 300), openAppSettings);
return false;
}
return false;
}
@override @override
void onInit() { void onInit() {
super.onInit(); super.onInit();
@ -108,17 +153,17 @@ class UserInfoController extends GetxController {
Future<void> processSelectedImage(File imageFile) async { Future<void> processSelectedImage(File imageFile) async {
try { try {
// //
SmartDialog.showLoading(msg: '正在处理头像...');
SmartDialog.showLoading(msg: '提交信息中...');
String objectName = '${DateUtil.getNowDateMs()}.${imageFile.path.split('.').last}'; String objectName = '${DateUtil.getNowDateMs()}.${imageFile.path.split('.').last}';
String imageUrl = await OSSManager.instance.uploadFile(imageFile.readAsBytesSync(), objectName); String imageUrl = await OSSManager.instance.uploadFile(imageFile.readAsBytesSync(), objectName);
print('上传成功,图片URL: $imageUrl'); print('上传成功,图片URL: $imageUrl');
avatarUrl.value = imageUrl; avatarUrl.value = imageUrl;
SmartDialog.dismiss(); SmartDialog.dismiss();
SmartDialog.showToast('头像设置成功');
// SmartDialog.showToast('头像设置成功');
} catch (e) { } catch (e) {
SmartDialog.dismiss(); SmartDialog.dismiss();
print('处理图片失败: $e'); print('处理图片失败: $e');
SmartDialog.showToast('处理图片失败');
SmartDialog.showToast('提交失败,请重试');
} }
} }
@ -130,6 +175,12 @@ class UserInfoController extends GetxController {
// //
bool _validateForm() { bool _validateForm() {
if (avatarUrl.value.isEmpty && avatarLocalPath.value.isEmpty) {
SmartDialog.showToast('请先选择头像');
return false;
}
// //
if (nickname.value.isEmpty) { if (nickname.value.isEmpty) {
SmartDialog.showToast('请输入昵称'); SmartDialog.showToast('请输入昵称');
@ -148,6 +199,12 @@ class UserInfoController extends GetxController {
return false; return false;
} }
//
if (avatarUrl.value.isEmpty && avatarLocalPath.value.isEmpty) {
SmartDialog.showToast('请先选择头像');
return false;
}
return true; return true;
} }
@ -190,8 +247,16 @@ class UserInfoController extends GetxController {
isSubmitting.value = true; isSubmitting.value = true;
try { try {
//
if (avatarUrl.value.isEmpty && avatarLocalPath.value.isNotEmpty) {
await processSelectedImage(File(avatarLocalPath.value));
}
// //
final params = _buildSubmitParams(); final params = _buildSubmitParams();
if (avatarUrl.value.isNotEmpty) {
params['avatarUrl'] = avatarUrl.value;
}
// //
print('提交用户信息参数: $params'); print('提交用户信息参数: $params');
@ -209,6 +274,7 @@ class UserInfoController extends GetxController {
'nickname': nickname.value, 'nickname': nickname.value,
'birthday': birthday.value, 'birthday': birthday.value,
'education': education.value, 'education': education.value,
'avatarUrl': avatarUrl.value,
}); });
await storage.write('userInfo', currentUserInfo); await storage.write('userInfo', currentUserInfo);
} }

7
lib/generated/assets.dart

@ -18,5 +18,12 @@ class Assets {
static const String imagesMineNol = 'assets/images/mine_nol.png'; static const String imagesMineNol = 'assets/images/mine_nol.png';
static const String imagesMinePre = 'assets/images/mine_pre.png'; static const String imagesMinePre = 'assets/images/mine_pre.png';
static const String imagesWomenIcon = 'assets/images/women_icon.png'; static const String imagesWomenIcon = 'assets/images/women_icon.png';
static const String imagesBtnBgIcon = 'assets/images/btn_bg_icon.png';
static const String imagesHiIcon = 'assets/images/hi_icon.png';
static const String imagesLiveIcon = 'assets/images/live_icon.png';
static const String imagesOnlineIcon = 'assets/images/online_icon.png';
static const String imagesSendMessageIcon = 'assets/images/send_message_icon.png';
static const String imagesTabChangeIcon = 'assets/images/tab_change_icon.png';
static const String imagesVerifiedIcon = 'assets/images/verified_icon.png';
} }

2
lib/main.dart

@ -75,7 +75,7 @@ class MyApp extends StatelessWidget {
// token不为空token为空 // token不为空token为空
if (token != null && token.isNotEmpty) { if (token != null && token.isNotEmpty) {
return MainPage();
return UserInfoPage();
} else { } else {
return LoginPage(); return LoginPage();
} }

398
lib/pages/home/home_page.dart

@ -1,3 +1,4 @@
import 'package:dating_touchme_app/generated/assets.dart';
import 'package:flutter/cupertino.dart'; import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
@ -8,16 +9,407 @@ class HomePage extends StatefulWidget {
State<HomePage> createState() => _HomePageState(); State<HomePage> createState() => _HomePageState();
} }
class _HomePageState extends State<HomePage> with AutomaticKeepAliveClientMixin{
class _HomePageState extends State<HomePage> with AutomaticKeepAliveClientMixin {
int selectedTabIndex = 0; // 0: 1:
//
final List<_FeedItem> recommendFeed = [
_FeedItem(type: _FeedType.withImages),
_FeedItem(type: _FeedType.plain),
_FeedItem(type: _FeedType.live),
_FeedItem(type: _FeedType.withImages),
];
final List<_FeedItem> nearbyFeed = [
_FeedItem(type: _FeedType.plain),
_FeedItem(type: _FeedType.withImages),
_FeedItem(type: _FeedType.live),
];
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
super.build(context); super.build(context);
return Container(
color: Colors.white,
final List<_FeedItem> dataSource =
selectedTabIndex == 0 ? recommendFeed : nearbyFeed;
return Scaffold(
backgroundColor: Colors.white,
appBar: _buildAppBar(),
body: ListView.separated(
physics: const BouncingScrollPhysics(),
itemBuilder: (context, index) {
final item = dataSource[index];
switch (item.type) {
case _FeedType.withImages:
return ImagesCard();
case _FeedType.plain:
return PlainCard();
case _FeedType.live:
return LiveCard();
}
},
separatorBuilder: (context, index) => const SizedBox(height: 12),
itemCount: dataSource.length,
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 12),
),
);
}
PreferredSizeWidget _buildAppBar() {
return AppBar(
backgroundColor: Colors.white,
elevation: 0,
centerTitle: false,
toolbarHeight: 56,
titleSpacing: 16,
title: Row(
children: [
_buildTabButton(title: '推荐', index: 0),
const SizedBox(width: 16),
_buildTabButton(title: '同城', index: 1),
const Spacer(),
Image.asset(
Assets.imagesOnlineIcon,
width: 20,
height: 20,
),
],
),
bottom: PreferredSize(
preferredSize: const Size.fromHeight(8),
child: const SizedBox(height: 8),
),
); );
} }
Widget _buildTabButton({required String title, required int index}) {
final bool selected = selectedTabIndex == index;
return GestureDetector(
onTap: () {
if (selectedTabIndex != index) {
setState(() {
selectedTabIndex = index;
});
}
},
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
title,
style: TextStyle(
fontSize: 22,
fontWeight: FontWeight.w700,
color: selected ? Colors.black : const Color(0xFF999999),
),
),
const SizedBox(height: 4),
AnimatedContainer(
duration: const Duration(milliseconds: 200),
height: 4,
width: selected ? 28 : 0,
decoration: BoxDecoration(
color: const Color(0xFF6C63FF),
borderRadius: BorderRadius.circular(2),
),
),
],
),
);
}
// WidgetHomePage更简洁
@override @override
bool get wantKeepAlive => true; bool get wantKeepAlive => true;
} }
enum _FeedType { withImages, plain, live }
class _FeedItem {
final _FeedType type;
const _FeedItem({required this.type});
}
// //线//Hi/
class _CardHeader extends StatelessWidget {
final bool showHi;
// +
final bool liveOverlayOnAvatar;
const _CardHeader({this.showHi = false, this.liveOverlayOnAvatar = false});
@override
Widget build(BuildContext context) {
return Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
// + 线
Stack(
children: [
// +
Container(
padding: liveOverlayOnAvatar ? const EdgeInsets.all(2) : EdgeInsets.zero,
decoration: liveOverlayOnAvatar
? BoxDecoration(
borderRadius: BorderRadius.circular(32),
border: Border.all(color: const Color(0xFFB58BFF), width: 2),
)
: null,
child: ClipRRect(
borderRadius: BorderRadius.circular(30),
child: Image.asset(
Assets.imagesAvatarsExample,
width: 60,
height: 60,
fit: BoxFit.cover,
),
),
),
Positioned(
left: 6,
bottom: 6,
child: Container(
width: 12,
height: 12,
decoration: BoxDecoration(
color: const Color(0xFF2ED573),
shape: BoxShape.circle,
boxShadow: const [
BoxShadow(color: Color(0x332ED573), blurRadius: 4, offset: Offset(0, 2)),
],
),
),
),
if (liveOverlayOnAvatar)
Positioned(
left: -6,
bottom: -6,
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4),
decoration: BoxDecoration(
color: const Color(0xFF7A5CFF),
borderRadius: BorderRadius.circular(16),
boxShadow: const [
BoxShadow(color: Color(0x337A5CFF), blurRadius: 6, offset: Offset(0, 3)),
],
),
child: const Text(
'直播中',
style: TextStyle(color: Colors.white, fontSize: 12, fontWeight: FontWeight.w600),
),
),
),
],
),
const SizedBox(width: 10),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
const Text(
'林园园',
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.w700,
color: Color(0xFF333333),
),
),
const SizedBox(width: 6),
// 线
Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2),
decoration: BoxDecoration(
color: const Color(0xFFEFFBF3),
borderRadius: BorderRadius.circular(12),
),
child: const Text(
'在线',
style: TextStyle(fontSize: 12, color: Color(0xFF2ED573)),
),
),
const SizedBox(width: 6),
//
Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2),
decoration: BoxDecoration(
color: const Color(0xFFF3E9FF),
borderRadius: BorderRadius.circular(12),
),
child: Row(
children: [
Image.asset(Assets.imagesVerifiedIcon, width: 12, height: 12),
const SizedBox(width: 4),
const Text(
'实名',
style: TextStyle(fontSize: 12, color: Color(0xFF6C63FF)),
),
],
),
),
],
),
const SizedBox(height: 4),
const Text(
'23岁 · 白云区',
style: TextStyle(fontSize: 16, color: Color(0xFF666666)),
),
],
),
),
if (showHi)
Image.asset(Assets.imagesHiIcon, width: 64, height: 32),
],
);
}
}
class PlainCard extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Container(
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(12),
boxShadow: const [
BoxShadow(
color: Color(0x14000000),
blurRadius: 8,
offset: Offset(0, 4),
),
],
),
padding: const EdgeInsets.all(12),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: const [
_CardHeader(),
SizedBox(height: 8),
Text(
'我想找一个有缘的异性,快来联系我吧快来……',
style: TextStyle(fontSize: 13, color: Color(0xFF666666)),
),
],
),
);
}
}
class ImagesCard extends StatelessWidget {
const ImagesCard();
@override
Widget build(BuildContext context) {
return Container(
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(16),
boxShadow: const [
BoxShadow(
color: Color(0x14000000),
blurRadius: 12,
offset: Offset(0, 6),
),
],
),
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const _CardHeader(showHi: true),
const SizedBox(height: 10),
const Text(
'我想找一个有缘的异性,快来联系我吧快来……',
style: TextStyle(fontSize: 16, color: Color(0xFF666666)),
),
const SizedBox(height: 16),
Row(
children: const [
_BorderedImage(),
SizedBox(width: 12),
_BorderedImage(),
SizedBox(width: 12),
_BorderedImage(),
],
),
],
),
);
}
}
class LiveCard extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Container(
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(12),
boxShadow: const [
BoxShadow(
color: Color(0x14000000),
blurRadius: 8,
offset: Offset(0, 4),
),
],
),
padding: const EdgeInsets.all(12),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const _CardHeader(liveOverlayOnAvatar: true),
const SizedBox(height: 8),
const Text(
'正在直播,快来互动~',
style: TextStyle(fontSize: 13, color: Color(0xFF666666)),
),
const SizedBox(height: 10),
Align(
alignment: Alignment.centerLeft,
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 8),
decoration: BoxDecoration(
gradient: const LinearGradient(
colors: [Color(0xFF2D7CFF), Color(0xFF7A5CFF)],
),
borderRadius: BorderRadius.circular(18),
),
child: const Text(
'进入直播间',
style: TextStyle(color: Colors.white, fontSize: 14, fontWeight: FontWeight.w600),
),
),
),
],
),
);
}
}
// 使
//
class _BorderedImage extends StatelessWidget {
const _BorderedImage();
@override
Widget build(BuildContext context) {
return Expanded(
child: AspectRatio(
aspectRatio: 1.05,
child: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(14),
border: Border.all(color: const Color(0xFF5D7BFF), width: 2),
),
clipBehavior: Clip.antiAlias,
child: Image.asset(
Assets.imagesDiscoverPre,
fit: BoxFit.cover,
),
),
),
);
}
}

2
lib/pages/main/main_page.dart

@ -46,7 +46,7 @@ class _MainPageState extends State<MainPage> {
userController.getHxUserToken(); userController.getHxUserToken();
// token并调用获取婚姻信息详情的方法 // token并调用获取婚姻信息详情的方法
checkTokenAndFetchMarriageInfo();
// checkTokenAndFetchMarriageInfo();
} }
// token并获取婚姻信息详情 // token并获取婚姻信息详情

174
lib/pages/mine/login_controller.dart

@ -0,0 +1,174 @@
import 'dart:async';
import 'package:dating_touchme_app/pages/main/main_page.dart';
import 'package:get/get.dart';
import 'package:get_storage/get_storage.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:dating_touchme_app/network/user_api.dart';
import 'package:dating_touchme_app/pages/mine/user_info_page.dart';
class LoginController extends GetxController {
//
final phoneNumber = ''.obs;
//
final verificationCode = ''.obs;
//
final isSendingCode = false.obs;
//
final countdownSeconds = 0.obs;
//
final isLoggingIn = false.obs;
// GetX依赖注入中获取UserApi实例
late UserApi _userApi;
// GetStorage实例token等信息
final storage = GetStorage();
@override
void onInit() {
super.onInit();
// UserApi
_userApi = Get.find<UserApi>();
}
//
Future<void> getVerificationCode() async {
//
if (phoneNumber.value.isEmpty || phoneNumber.value.length != 11) {
SmartDialog.showToast('请输入正确的手机号');
return;
}
isSendingCode.value = true;
try {
//
final params = {
'purpose': 1, //
'verifiableAccount': phoneNumber.value,
'verifiableAccountType': 1, //
};
// UserApi中的验证码接口
final response = await _userApi.getVerificationCode(params);
//
if (response.data.isSuccess) {
// 使
// print('验证码发送成功');
//
startCountdown();
} else {
SmartDialog.showToast(response.data.message);
}
} catch (e) {
SmartDialog.showToast('网络请求失败,请重试');
} finally {
isSendingCode.value = false;
}
}
//
void startCountdown() {
countdownSeconds.value = 60;
Timer.periodic(const Duration(seconds: 1), (timer) {
countdownSeconds.value--;
if (countdownSeconds.value <= 0) {
timer.cancel();
}
});
}
// - 使SmartDialog
// void clearErrorMessage() {}
//
Future<void> login() async {
//
if (phoneNumber.value.isEmpty || phoneNumber.value.length != 11) {
SmartDialog.showToast('请输入正确的手机号');
return;
}
if (verificationCode.value.isEmpty) {
SmartDialog.showToast('请输入验证码');
return;
}
isLoggingIn.value = true;
try {
//
final params = {
'account': phoneNumber.value,
'accountType': 2, //
'captcha': verificationCode.value,
};
//
final response = await _userApi.login(params);
//
if (response.data.isSuccess) {
// token和用户信息
if (response.data.data != null) {
final loginData = response.data.data!;
await storage.write('token', loginData.token);
await storage.write('userId', loginData.userId);
//
await storage.write('userInfo', loginData.toJson());
//
await _getBaseUserInfo(loginData.userId);
}
} else {
SmartDialog.showToast(response.data.message);
}
} catch (e) {
SmartDialog.showToast('网络请求失败,请检查网络连接');
} finally {
isLoggingIn.value = false;
}
}
//
Future<void> _getBaseUserInfo(String userId) async {
try {
final response = await _userApi.getBaseUserInfo(userId);
if (response.data.isSuccess && response.data.data != null) {
//
await _getMarriageInformationDetail();
} else {
SmartDialog.showToast(response.data.message);
}
} catch (e) {
//
SmartDialog.showToast('获取用户信息失败');
}
}
//
Future<void> _getMarriageInformationDetail() async {
try {
final response = await _userApi.getMarriageInformationDetail();
if (response.data.isSuccess) {
// data是否为null或者是空对象
if(response.data.data == null){
//
SmartDialog.showToast('转到完善信息');
//
Get.offAll(() => UserInfoPage());
}else{
Get.offAll(MainPage());
}
} else {
//
}
} catch (e) {
//
print('获取婚姻信息异常: $e');
}
}
}

18
lib/pages/mine/user_info_page.dart

@ -270,18 +270,22 @@ class UserInfoPage extends StatelessWidget {
color: Colors.grey, color: Colors.grey,
), ),
child: ClipOval( child: ClipOval(
// avatarUrl显示头像
child: controller.avatarUrl.value.isNotEmpty
child: controller.avatarLocalPath.value.isNotEmpty
? Image.file( ? Image.file(
File(controller.avatarUrl.value),
File(controller.avatarLocalPath.value),
fit: BoxFit.cover, fit: BoxFit.cover,
width: 85, width: 85,
height: 85, height: 85,
) )
: Image.asset(
Assets.imagesAvatarsExample,
fit: BoxFit.cover,
),
: (controller.avatarUrl.value.startsWith('http')
? Image.network(
controller.avatarUrl.value,
fit: BoxFit.cover,
)
: Image.asset(
Assets.imagesAvatarsExample,
fit: BoxFit.cover,
)),
), ),
), ),
Positioned( Positioned(

280
pubspec.lock
File diff suppressed because it is too large
View File

Loading…
Cancel
Save