fix(mobile): avatar URL handling + API paths + guide page layout
1. Avatar display: - Detect if _avatarSvg is URL or SVG string - Use Image.network for URLs, SvgPicture.string for SVG 2. API endpoints: - Remove /api/v1 prefix from endpoints (already in baseUrl) - Fixes duplicate /api/v1/api/v1 in requests 3. Guide page: - Restore BoxFit.cover for fullscreen images - Fix page indicator positioning with SafeArea 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
cee2626a53
commit
91e54946ba
|
|
@ -1,38 +1,39 @@
|
|||
/// API 端点常量
|
||||
///
|
||||
/// 注意: 这些路径不包含 /api/v1 前缀,因为 AppConfig.apiBaseUrl 已经包含了
|
||||
/// 例如 baseUrl = 'https://rwaapi.szaiai.com/api/v1'
|
||||
class ApiEndpoints {
|
||||
ApiEndpoints._();
|
||||
|
||||
// Base URL - 生产环境
|
||||
// Base URL - 生产环境 (仅供参考,实际使用 AppConfig)
|
||||
static const String baseUrl = 'https://rwaapi.szaiai.com';
|
||||
// Base URL - 开发环境
|
||||
static const String baseUrlDev = 'https://rwaapi-dev.szaiai.com';
|
||||
|
||||
// API 版本前缀
|
||||
static const String apiPrefix = '/api/v1';
|
||||
|
||||
// Auth (-> Identity Service)
|
||||
static const String auth = '$apiPrefix/auth';
|
||||
static const String auth = '/auth';
|
||||
static const String login = '$auth/login';
|
||||
static const String register = '$auth/register';
|
||||
static const String refreshToken = '$auth/refresh';
|
||||
static const String logout = '$auth/logout';
|
||||
|
||||
// User (-> Identity Service)
|
||||
static const String user = '$apiPrefix/user';
|
||||
static const String user = '/user';
|
||||
static const String autoCreate = '$user/auto-create';
|
||||
static const String userWallet = '$user/wallet'; // 获取钱包状态
|
||||
static const String me = '$apiPrefix/me'; // 获取当前用户完整信息
|
||||
static const String me = '/me'; // 获取当前用户完整信息
|
||||
static const String profile = '$user/profile';
|
||||
static const String updateProfile = '$user/profile/update';
|
||||
static const String updateAvatar = '$user/avatar';
|
||||
|
||||
// Wallet (-> Wallet Service)
|
||||
static const String wallet = '$apiPrefix/wallet';
|
||||
static const String wallet = '/wallet';
|
||||
static const String createWallet = '$wallet/create';
|
||||
static const String importWallet = '$wallet/import';
|
||||
static const String balance = '$wallet/balance';
|
||||
|
||||
// Mining (-> Reward Service)
|
||||
static const String mining = '$apiPrefix/mining';
|
||||
static const String mining = '/mining';
|
||||
static const String miningStatus = '$mining/status';
|
||||
static const String startMining = '$mining/start';
|
||||
static const String stopMining = '$mining/stop';
|
||||
|
|
@ -40,33 +41,33 @@ class ApiEndpoints {
|
|||
static const String hashPower = '$mining/hash-power';
|
||||
|
||||
// Ranking (-> Leaderboard Service)
|
||||
static const String ranking = '$apiPrefix/ranking';
|
||||
static const String ranking = '/ranking';
|
||||
static const String dailyRanking = '$ranking/daily';
|
||||
static const String weeklyRanking = '$ranking/weekly';
|
||||
static const String monthlyRanking = '$ranking/monthly';
|
||||
|
||||
// Trading (-> Wallet Service)
|
||||
static const String trading = '$apiPrefix/trading';
|
||||
static const String trading = '/trading';
|
||||
static const String exchange = '$trading/exchange';
|
||||
static const String settlement = '$trading/settlement';
|
||||
static const String transactions = '$trading/transactions';
|
||||
|
||||
// Deposit (-> Wallet Service)
|
||||
static const String deposit = '$apiPrefix/deposit';
|
||||
static const String deposit = '/deposit';
|
||||
static const String depositAddress = '$deposit/address';
|
||||
static const String confirmDeposit = '$deposit/confirm';
|
||||
static const String depositRecords = '$deposit/records';
|
||||
|
||||
// Planting (-> Planting Service)
|
||||
static const String planting = '$apiPrefix/planting';
|
||||
static const String planting = '/planting';
|
||||
static const String plantingPrice = '$planting/price';
|
||||
static const String submitPlanting = '$planting/submit';
|
||||
static const String provinces = '$planting/provinces';
|
||||
static const String cities = '$planting/cities';
|
||||
|
||||
// Community & Referral (-> Referral Service)
|
||||
static const String community = '$apiPrefix/community';
|
||||
static const String referral = '$apiPrefix/referral';
|
||||
static const String community = '/community';
|
||||
static const String referral = '/referral';
|
||||
static const String referralMe = '$referral/me'; // 获取当前用户推荐信息
|
||||
static const String referralDirects = '$referral/me/direct-referrals'; // 获取直推列表
|
||||
static const String referralList = '$community/referrals';
|
||||
|
|
@ -75,11 +76,11 @@ class ApiEndpoints {
|
|||
static const String generateReferralLink = '$referral/generate-link';
|
||||
|
||||
// Authorization (-> Authorization Service)
|
||||
static const String authorizations = '$apiPrefix/authorizations';
|
||||
static const String authorizations = '/authorizations';
|
||||
static const String myAuthorizations = '$authorizations/my'; // 获取我的授权列表
|
||||
|
||||
// Telemetry (-> Reporting Service)
|
||||
static const String telemetry = '$apiPrefix/telemetry';
|
||||
static const String telemetry = '/telemetry';
|
||||
static const String telemetrySession = '$telemetry/session';
|
||||
static const String telemetryHeartbeat = '$telemetry/heartbeat';
|
||||
static const String telemetryEvents = '$telemetry/events';
|
||||
|
|
|
|||
|
|
@ -146,50 +146,45 @@ class _GuidePageState extends ConsumerState<GuidePage> {
|
|||
}
|
||||
|
||||
/// 构建向导页 (页面1-4) - 全屏背景图片,无文字
|
||||
/// 使用 BoxFit.fitWidth 保持图片原始比例,不拉伸文字
|
||||
/// 使用 BoxFit.cover 填满屏幕,保持宽高比(会裁剪超出部分)
|
||||
Widget _buildGuidePage(GuidePageData data, int index) {
|
||||
debugPrint('[GuidePage] _buildGuidePage() - 页面 ${index + 1}, 图片: ${data.imagePath}');
|
||||
return Container(
|
||||
color: Colors.black, // 如果图片不够高,上下用黑色填充
|
||||
child: Stack(
|
||||
fit: StackFit.expand,
|
||||
children: [
|
||||
// 全屏背景图片 - 使用 fitWidth 保持宽高比
|
||||
if (data.imagePath != null)
|
||||
LayoutBuilder(
|
||||
builder: (context, constraints) {
|
||||
debugPrint('[GuidePage] 页面 ${index + 1} 容器尺寸: ${constraints.maxWidth.toStringAsFixed(1)} x ${constraints.maxHeight.toStringAsFixed(1)}');
|
||||
debugPrint('[GuidePage] 页面 ${index + 1} 使用 BoxFit.fitWidth');
|
||||
return Center(
|
||||
child: Image.asset(
|
||||
data.imagePath!,
|
||||
fit: BoxFit.fitWidth,
|
||||
width: double.infinity,
|
||||
errorBuilder: (context, error, stackTrace) {
|
||||
debugPrint('[GuidePage] 页面 ${index + 1} 图片加载失败: $error');
|
||||
return Container(
|
||||
color: const Color(0xFFFFF8E7),
|
||||
child: _buildPlaceholderImage(index),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
)
|
||||
else
|
||||
Container(
|
||||
color: const Color(0xFFFFF8E7),
|
||||
child: _buildPlaceholderImage(index),
|
||||
),
|
||||
// 底部页面指示器
|
||||
Positioned(
|
||||
left: 0,
|
||||
right: 0,
|
||||
bottom: 80.h,
|
||||
child: _buildPageIndicator(),
|
||||
return Stack(
|
||||
fit: StackFit.expand,
|
||||
children: [
|
||||
// 全屏背景图片 - 使用 cover 填满屏幕
|
||||
if (data.imagePath != null)
|
||||
Image.asset(
|
||||
data.imagePath!,
|
||||
fit: BoxFit.cover,
|
||||
width: double.infinity,
|
||||
height: double.infinity,
|
||||
errorBuilder: (context, error, stackTrace) {
|
||||
debugPrint('[GuidePage] 页面 ${index + 1} 图片加载失败: $error');
|
||||
return Container(
|
||||
color: const Color(0xFFFFF8E7),
|
||||
child: _buildPlaceholderImage(index),
|
||||
);
|
||||
},
|
||||
)
|
||||
else
|
||||
Container(
|
||||
color: const Color(0xFFFFF8E7),
|
||||
child: _buildPlaceholderImage(index),
|
||||
),
|
||||
],
|
||||
),
|
||||
// 底部页面指示器 - 使用 SafeArea 避免被系统 UI 遮挡
|
||||
Positioned(
|
||||
left: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
child: SafeArea(
|
||||
child: Padding(
|
||||
padding: EdgeInsets.only(bottom: 40.h),
|
||||
child: _buildPageIndicator(),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -423,42 +418,39 @@ class _WelcomePageContentState extends ConsumerState<_WelcomePageContent> {
|
|||
Widget build(BuildContext context) {
|
||||
return GestureDetector(
|
||||
onTap: () => FocusScope.of(context).unfocus(),
|
||||
child: Container(
|
||||
color: Colors.black, // 如果图片不够高,上下用黑色填充
|
||||
child: Stack(
|
||||
fit: StackFit.expand,
|
||||
children: [
|
||||
// 全屏背景图片 - 使用 fitWidth 保持宽高比
|
||||
if (widget.backgroundImage != null)
|
||||
Center(
|
||||
child: Image.asset(
|
||||
widget.backgroundImage!,
|
||||
fit: BoxFit.fitWidth,
|
||||
width: double.infinity,
|
||||
errorBuilder: (context, error, stackTrace) {
|
||||
return Container(
|
||||
color: const Color(0xFFFFF8E7),
|
||||
);
|
||||
},
|
||||
),
|
||||
)
|
||||
else
|
||||
Container(
|
||||
color: const Color(0xFFFFF8E7),
|
||||
),
|
||||
// 半透明遮罩,让内容更清晰
|
||||
child: Stack(
|
||||
fit: StackFit.expand,
|
||||
children: [
|
||||
// 全屏背景图片 - 使用 cover 填满屏幕
|
||||
if (widget.backgroundImage != null)
|
||||
Image.asset(
|
||||
widget.backgroundImage!,
|
||||
fit: BoxFit.cover,
|
||||
width: double.infinity,
|
||||
height: double.infinity,
|
||||
errorBuilder: (context, error, stackTrace) {
|
||||
return Container(
|
||||
color: const Color(0xFFFFF8E7),
|
||||
);
|
||||
},
|
||||
)
|
||||
else
|
||||
Container(
|
||||
decoration: BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
begin: Alignment.topCenter,
|
||||
end: Alignment.bottomCenter,
|
||||
colors: [
|
||||
Colors.black.withValues(alpha: 0.3),
|
||||
Colors.black.withValues(alpha: 0.6),
|
||||
],
|
||||
),
|
||||
color: const Color(0xFFFFF8E7),
|
||||
),
|
||||
// 半透明遮罩,让内容更清晰
|
||||
Container(
|
||||
decoration: BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
begin: Alignment.topCenter,
|
||||
end: Alignment.bottomCenter,
|
||||
colors: [
|
||||
Colors.black.withValues(alpha: 0.3),
|
||||
Colors.black.withValues(alpha: 0.6),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
// 内容区域
|
||||
SafeArea(
|
||||
child: LayoutBuilder(
|
||||
|
|
@ -567,7 +559,6 @@ class _WelcomePageContentState extends ConsumerState<_WelcomePageContent> {
|
|||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -637,18 +637,58 @@ class _ProfilePageState extends ConsumerState<ProfilePage> {
|
|||
}
|
||||
|
||||
/// 构建SVG或默认头像
|
||||
/// 注意:_avatarSvg 可能存储的是 URL(用户上传的图片)或 SVG 字符串(随机生成的头像)
|
||||
Widget _buildSvgOrDefaultAvatar() {
|
||||
if (_avatarSvg != null) {
|
||||
debugPrint('[ProfilePage] _buildSvgOrDefaultAvatar() - 使用SVG头像,长度=${_avatarSvg!.length}');
|
||||
debugPrint('[ProfilePage] SVG前50字符: ${_avatarSvg!.substring(0, _avatarSvg!.length > 50 ? 50 : _avatarSvg!.length)}');
|
||||
return SvgPicture.string(
|
||||
_avatarSvg!,
|
||||
width: 80,
|
||||
height: 80,
|
||||
fit: BoxFit.cover,
|
||||
);
|
||||
if (_avatarSvg != null && _avatarSvg!.isNotEmpty) {
|
||||
debugPrint('[ProfilePage] _buildSvgOrDefaultAvatar() - 检查头像内容,长度=${_avatarSvg!.length}');
|
||||
debugPrint('[ProfilePage] 内容前50字符: ${_avatarSvg!.substring(0, _avatarSvg!.length > 50 ? 50 : _avatarSvg!.length)}');
|
||||
|
||||
// 检测是否是 URL(用户上传的头像图片)
|
||||
if (_avatarSvg!.startsWith('http://') || _avatarSvg!.startsWith('https://')) {
|
||||
debugPrint('[ProfilePage] _buildSvgOrDefaultAvatar() - 检测到是URL,使用网络图片');
|
||||
return Image.network(
|
||||
_avatarSvg!,
|
||||
width: 80,
|
||||
height: 80,
|
||||
fit: BoxFit.cover,
|
||||
loadingBuilder: (context, child, loadingProgress) {
|
||||
if (loadingProgress == null) return child;
|
||||
return const Center(
|
||||
child: SizedBox(
|
||||
width: 24,
|
||||
height: 24,
|
||||
child: CircularProgressIndicator(
|
||||
strokeWidth: 2,
|
||||
valueColor: AlwaysStoppedAnimation<Color>(Color(0xFFD4AF37)),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
errorBuilder: (context, error, stackTrace) {
|
||||
debugPrint('[ProfilePage] _buildSvgOrDefaultAvatar() - 网络图片加载失败: $error');
|
||||
return const Icon(
|
||||
Icons.person,
|
||||
size: 40,
|
||||
color: Color(0xFF8B5A2B),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
// 检测是否是 SVG 字符串(随机生成的 SVG 头像)
|
||||
if (_avatarSvg!.contains('<svg') || _avatarSvg!.startsWith('<?xml')) {
|
||||
debugPrint('[ProfilePage] _buildSvgOrDefaultAvatar() - 检测到是SVG字符串,使用SvgPicture');
|
||||
return SvgPicture.string(
|
||||
_avatarSvg!,
|
||||
width: 80,
|
||||
height: 80,
|
||||
fit: BoxFit.cover,
|
||||
);
|
||||
}
|
||||
|
||||
debugPrint('[ProfilePage] _buildSvgOrDefaultAvatar() - 未知格式,使用默认图标');
|
||||
}
|
||||
debugPrint('[ProfilePage] _buildSvgOrDefaultAvatar() - 无SVG,使用默认图标');
|
||||
debugPrint('[ProfilePage] _buildSvgOrDefaultAvatar() - 无头像数据,使用默认图标');
|
||||
return const Icon(
|
||||
Icons.person,
|
||||
size: 40,
|
||||
|
|
|
|||
Loading…
Reference in New Issue