From 1741e746398e7527f8e7dcf58d4db884a804a945 Mon Sep 17 00:00:00 2001 From: Ponshu Developer Date: Sun, 5 Apr 2026 13:32:52 +0900 Subject: [PATCH] feat: restructure My Page and Sommelier screen layouts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit My Page: - LevelTitleCard: show nickname above title when set - Add section header 成長記録 above gamification widgets - Split profile card: basic info (nickname/gender) and sake character (MBTI/sake persona) are now separate named sections Sommelier: - Add section headers テイストプロフィール / MBTI風診断 / さけのわ おすすめ - Widen share button to full width with rounded rect shape - Remove Dividers between sections, use SizedBox(32) spacing instead Co-Authored-By: Claude Sonnet 4.6 --- lib/screens/features/sommelier_screen.dart | 98 ++++++++++--------- lib/screens/soul_screen.dart | 77 ++++++++------- .../gamification/level_title_card.dart | 7 ++ 3 files changed, 105 insertions(+), 77 deletions(-) diff --git a/lib/screens/features/sommelier_screen.dart b/lib/screens/features/sommelier_screen.dart index 13005d7..b49bffd 100644 --- a/lib/screens/features/sommelier_screen.dart +++ b/lib/screens/features/sommelier_screen.dart @@ -94,37 +94,32 @@ class _SommelierScreenState extends ConsumerState { return SingleChildScrollView( padding: const EdgeInsets.symmetric(horizontal: 24.0, vertical: 16.0), child: Column( + crossAxisAlignment: CrossAxisAlignment.start, children: [ - /* Greeting Removed */ - // const SizedBox(height: 8), + // テイストプロフィール + _buildSectionHeader(context, 'テイストプロフィール', LucideIcons.activity), + const SizedBox(height: 8), Screenshot( controller: _screenshotController, - child: _buildShukoCard(context, baseProfile, personalizedTitle, userProfile.nickname), // Pass nickname + child: _buildShukoCard(context, baseProfile, personalizedTitle, userProfile.nickname), ), - const SizedBox(height: 16), // Card下 - _buildActionButtons(context), + const SizedBox(height: 16), + _buildActionButtons(context), + const SizedBox(height: 32), - const SizedBox(height: 8), // ボタン下(チラ見せ強化) - const Divider(), - const SizedBox(height: 16), // 区切り線下 - - // --- New: MBTI Diagnosis Section --- - _buildMBTIDiagnosisSection(context, userProfile, sakeList), + // MBTI風診断 + _buildSectionHeader(context, 'MBTI風診断', LucideIcons.brainCircuit), + const SizedBox(height: 8), + _buildMBTIDiagnosisSection(context, userProfile, sakeList), + const SizedBox(height: 32), - const SizedBox(height: 24), - const Divider(), - const SizedBox(height: 16), - - // --- New: New Sake Recommendations Section --- - const SakenowaNewRecommendationSection(displayCount: 5), - - const SizedBox(height: 24), - const Divider(), - const SizedBox(height: 16), - - // --- New: Sakenowa Ranking Section --- - const SakenowaRankingSection(displayCount: 10), - const SizedBox(height: 32), + // さけのわ おすすめ + _buildSectionHeader(context, 'さけのわ おすすめ', LucideIcons.trendingUp), + const SizedBox(height: 8), + const SakenowaNewRecommendationSection(displayCount: 5), + const SizedBox(height: 16), + const SakenowaRankingSection(displayCount: 10), + const SizedBox(height: 32), ], ), ); @@ -139,6 +134,23 @@ class _SommelierScreenState extends ConsumerState { ); } + Widget _buildSectionHeader(BuildContext context, String title, IconData icon) { + final appColors = Theme.of(context).extension()!; + return Row( + children: [ + Icon(icon, size: 20, color: appColors.iconDefault), + const SizedBox(width: 8), + Text( + title, + style: Theme.of(context).textTheme.titleMedium?.copyWith( + fontWeight: FontWeight.bold, + color: appColors.textPrimary, + ), + ), + ], + ); + } + Widget _buildShukoCard(BuildContext context, ShukoProfile profile, ShukoTitle titleInfo, String? nickname) { final appColors = Theme.of(context).extension()!; final isDark = Theme.of(context).brightness == Brightness.dark; @@ -293,26 +305,24 @@ class _SommelierScreenState extends ConsumerState { Widget _buildActionButtons(BuildContext context) { final appColors = Theme.of(context).extension()!; - return Column( - children: [ - // SizedBox(width: double.infinity) removed to allow button to size itself - FilledButton.icon( - onPressed: _isSharing ? null : _shareCard, - icon: _isSharing - ? const SizedBox(width: 20, height: 20, child: CircularProgressIndicator(color: Colors.white, strokeWidth: 2)) - : const Icon(LucideIcons.share2), - label: const Text( - 'シェア', - style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold), - ), - style: FilledButton.styleFrom( - padding: const EdgeInsets.symmetric(horizontal: 48, vertical: 16), // Wide padding for "Pill" look - backgroundColor: appColors.brandPrimary, - foregroundColor: appColors.surfaceSubtle, - shape: const StadiumBorder(), - ), + return SizedBox( + width: double.infinity, + child: FilledButton.icon( + onPressed: _isSharing ? null : _shareCard, + icon: _isSharing + ? const SizedBox(width: 20, height: 20, child: CircularProgressIndicator(color: Colors.white, strokeWidth: 2)) + : const Icon(LucideIcons.share2), + label: const Text( + 'シェア', + style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold), ), - ], + style: FilledButton.styleFrom( + padding: const EdgeInsets.symmetric(vertical: 16), + backgroundColor: appColors.brandPrimary, + foregroundColor: appColors.surfaceSubtle, + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)), + ), + ), ); } diff --git a/lib/screens/soul_screen.dart b/lib/screens/soul_screen.dart index 631dfcc..a2edfd3 100644 --- a/lib/screens/soul_screen.dart +++ b/lib/screens/soul_screen.dart @@ -38,35 +38,19 @@ class _SoulScreenState extends ConsumerState { padding: const EdgeInsets.all(16), children: [ - // Gamification Section (v1.3) - const LevelTitleCard(), - const SizedBox(height: 16), - const ActivityStats(), - const SizedBox(height: 16), - const BadgeCase(), - const SizedBox(height: 16), + // 成長記録セクションヘッダー + _buildSectionHeader(context, '成長記録', LucideIcons.activity, appColors), - // Identity Section - Padding( - padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 4), - child: Row( - children: [ - Icon( - LucideIcons.fingerprint, - size: 20, - color: appColors.iconDefault, - ), - const SizedBox(width: 8), - Text( - t['profile'], - style: Theme.of(context).textTheme.titleMedium?.copyWith( - fontWeight: FontWeight.bold, - color: appColors.textPrimary, - ), - ), - ], - ), - ), + // Gamification Section + const LevelTitleCard(), + const SizedBox(height: 12), + const ActivityStats(), + const SizedBox(height: 12), + const BadgeCase(), + const SizedBox(height: 24), + + // プロフィールセクションヘッダー + _buildSectionHeader(context, t['profile'], LucideIcons.fingerprint, appColors), Card( color: appColors.surfaceSubtle, child: Column( @@ -86,9 +70,18 @@ class _SoulScreenState extends ConsumerState { trailing: Icon(LucideIcons.chevronRight, color: appColors.iconSubtle), onTap: () => _showGenderDialog(context, userProfile.gender, t), ), - - Divider(height: 1, color: appColors.divider), - // 1. Real MBTI (User Input) - Core Value for Recommendation + ], + ), + ), + const SizedBox(height: 24), + + // あなたの日本酒キャラセクションヘッダー + _buildSectionHeader(context, 'あなたの日本酒キャラ', LucideIcons.sparkles, appColors), + Card( + color: appColors.surfaceSubtle, + child: Column( + children: [ + // Real MBTI (User Input) ListTile( leading: Icon(LucideIcons.brainCircuit, color: appColors.iconDefault), title: Text("あなたのMBTI", style: TextStyle(color: appColors.textPrimary)), @@ -97,7 +90,7 @@ class _SoulScreenState extends ConsumerState { onTap: () => _showRealMbtiDialog(context, userProfile.mbti, t), ), Divider(height: 1, color: appColors.divider), - // 2. Sake Persona (AI Diagnosis) - Entertainment Value + // Sake Persona (AI Diagnosis) ListTile( leading: Icon(LucideIcons.sparkles, color: appColors.brandAccent), title: Text(t['mbtiDiagnosis'], style: TextStyle(color: appColors.textPrimary)), @@ -111,7 +104,6 @@ class _SoulScreenState extends ConsumerState { ), trailing: Icon(LucideIcons.chevronRight, color: appColors.iconSubtle), onTap: () { - // Navigate to Sommelier Tab (Index 2 in BottomNavBar) ref.read(currentTabIndexProvider.notifier).setIndex(2); }, ), @@ -138,6 +130,25 @@ class _SoulScreenState extends ConsumerState { ); } + Widget _buildSectionHeader(BuildContext context, String title, IconData icon, AppColors appColors) { + return Padding( + padding: const EdgeInsets.only(bottom: 8, left: 4, right: 4), + child: Row( + children: [ + Icon(icon, size: 20, color: appColors.iconDefault), + const SizedBox(width: 8), + Text( + title, + style: Theme.of(context).textTheme.titleMedium?.copyWith( + fontWeight: FontWeight.bold, + color: appColors.textPrimary, + ), + ), + ], + ), + ); + } + // Simplified Dialog for Real MBTI Selection void _showRealMbtiDialog(BuildContext context, String? current, Translations t) { final appColors = Theme.of(context).extension()!; diff --git a/lib/widgets/gamification/level_title_card.dart b/lib/widgets/gamification/level_title_card.dart index 5303219..e48fad8 100644 --- a/lib/widgets/gamification/level_title_card.dart +++ b/lib/widgets/gamification/level_title_card.dart @@ -40,6 +40,13 @@ class LevelTitleCard extends ConsumerWidget { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ + if (userProfile.nickname != null && userProfile.nickname!.isNotEmpty) ...[ + Text( + userProfile.nickname!, + style: TextStyle(fontSize: 13, color: appColors.textSecondary, letterSpacing: 0.3), + ), + const SizedBox(height: 6), + ], Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [