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:
hailin 2025-12-09 18:50:34 -08:00
parent cee2626a53
commit 91e54946ba
3 changed files with 134 additions and 102 deletions

View File

@ -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';

View File

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

View File

@ -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,