Compare commits

...

4 Commits

Author SHA1 Message Date
Ponshu Developer 17e8d52a67 chore: bump version to 1.0.19+30, update download page to v1.0.19
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-05 13:44:22 +09:00
Ponshu Developer a14dae1afb feat: UI modernization Phase 2 — micro-animation, typography, ambient glow
A-1 Micro-animation: Pressable wrapper widget (AnimatedScale 0.97 on tap)
    applied to LevelTitleCard

A-2 Bold Typography: LevelTitleCard title font 28→36px, 現在の称号 label
    demoted to bodySmall for stronger visual contrast

A-3 Ambient Glow: RadialGradient circle behind My Page (top-left, brandPrimary)
    and Sommelier screen (top-right, brandAccent) via Stack + IgnorePointer

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-05 13:35:03 +09:00
Ponshu Developer 1741e74639 feat: restructure My Page and Sommelier screen layouts
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 <noreply@anthropic.com>
2026-04-05 13:32:52 +09:00
Ponshu Developer 7797f4eed7 feat: hide business mode toggle in consumer APK, update 4-variant build script
- soul_screen.dart: pass showBusinessMode: isBusinessApp to OtherSettingsSection
  so the toggle is hidden when built with IS_BUSINESS_APP=false
- build_4_apks.sh: rewrite for current strategy (consumer/business x maita/eiji)
  Pro variants removed; set_lite_app_id() restores gradle before each build

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-05 10:11:54 +09:00
7 changed files with 322 additions and 93 deletions

102
build_4_apks.sh Normal file
View File

@ -0,0 +1,102 @@
#!/bin/bash
# ============================================================================
# Ponshu Room - 4バリアント一括ビルドスクリプト
# consumer × {maita, eiji} + business × {maita, eiji}
# Pro版は不要のため含まない
# ============================================================================
set -e
TIMESTAMP=$(date +%Y-%m-%d_%H-%M-%S)
OUTPUT_DIR="build/apk_releases/$TIMESTAMP"
mkdir -p "$OUTPUT_DIR"
# APIキー (.env から読み込む)
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
if [ -f "$SCRIPT_DIR/.env" ]; then
export $(grep -v '^#' "$SCRIPT_DIR/.env" | xargs)
fi
MAITA_KEY="${MAITA_API_KEY:?MAITA_API_KEY is not set in .env}"
EIJI_KEY="${EIJI_API_KEY:?EIJI_API_KEY is not set in .env}"
GRADLE_FILE="android/app/build.gradle.kts"
BACKUP_FILE="android/app/build.gradle.kts.backup"
cp "$GRADLE_FILE" "$BACKUP_FILE"
cleanup() {
if [ -f "$BACKUP_FILE" ]; then
cp "$BACKUP_FILE" "$GRADLE_FILE"
rm "$BACKUP_FILE"
echo "Restored build.gradle.kts"
fi
}
trap cleanup EXIT
# applicationId を Lite に固定(バックアップから毎回復元して書き換え)
set_lite_app_id() {
cp "$BACKUP_FILE" "$GRADLE_FILE"
sed -i 's/applicationId = "com.posimai.ponshu_room"/applicationId = "com.posimai.ponshu_room_lite"/' "$GRADLE_FILE"
}
echo "============================================================================"
echo "Ponshu Room - 4 Variant APK Build"
echo "Output: $OUTPUT_DIR"
echo "============================================================================"
echo ""
# --- [1/4] Consumer / Maita ---
echo "[1/4] Building Consumer - Maita..."
set_lite_app_id
flutter build apk --release \
--dart-define=GEMINI_API_KEY=$MAITA_KEY \
--dart-define=IS_BUSINESS_APP=false \
--dart-define=IS_PRO_VERSION=false \
--dart-define=USE_PROXY=false
cp build/app/outputs/flutter-apk/app-release.apk "$OUTPUT_DIR/ponshu_room_consumer_maita.apk"
echo "Saved: ponshu_room_consumer_maita.apk"
echo ""
# --- [2/4] Consumer / Eiji ---
echo "[2/4] Building Consumer - Eiji..."
set_lite_app_id
flutter build apk --release \
--dart-define=GEMINI_API_KEY=$EIJI_KEY \
--dart-define=IS_BUSINESS_APP=false \
--dart-define=IS_PRO_VERSION=false \
--dart-define=USE_PROXY=false
cp build/app/outputs/flutter-apk/app-release.apk "$OUTPUT_DIR/ponshu_room_consumer_eiji.apk"
echo "Saved: ponshu_room_consumer_eiji.apk"
echo ""
# --- [3/4] Business / Maita ---
echo "[3/4] Building Business - Maita..."
set_lite_app_id
flutter build apk --release \
--dart-define=GEMINI_API_KEY=$MAITA_KEY \
--dart-define=IS_BUSINESS_APP=true \
--dart-define=IS_PRO_VERSION=false \
--dart-define=USE_PROXY=false
cp build/app/outputs/flutter-apk/app-release.apk "$OUTPUT_DIR/ponshu_room_business_maita.apk"
echo "Saved: ponshu_room_business_maita.apk"
echo ""
# --- [4/4] Business / Eiji ---
echo "[4/4] Building Business - Eiji..."
set_lite_app_id
flutter build apk --release \
--dart-define=GEMINI_API_KEY=$EIJI_KEY \
--dart-define=IS_BUSINESS_APP=true \
--dart-define=IS_PRO_VERSION=false \
--dart-define=USE_PROXY=false
cp build/app/outputs/flutter-apk/app-release.apk "$OUTPUT_DIR/ponshu_room_business_eiji.apk"
echo "Saved: ponshu_room_business_eiji.apk"
echo ""
echo "============================================================================"
echo "All 4 variants built successfully!"
echo "============================================================================"
ls -lh "$OUTPUT_DIR"
echo ""
echo "Consumer APKs: IS_BUSINESS_APP=false (ビジネスモードトグル非表示)"
echo "Business APKs: IS_BUSINESS_APP=true (デモ・ヒアリング用)"
echo "============================================================================"

View File

@ -91,43 +91,62 @@ class _SommelierScreenState extends ConsumerState<SommelierScreen> {
userProfile.gender
);
return SingleChildScrollView(
return Stack(
children: [
// Ambient Glow
Positioned(
top: -60,
right: -60,
child: IgnorePointer(
child: Container(
width: 280,
height: 280,
decoration: BoxDecoration(
shape: BoxShape.circle,
gradient: RadialGradient(
colors: [
Theme.of(context).extension<AppColors>()!.brandAccent.withValues(alpha: 0.07),
Colors.transparent,
],
),
),
),
),
),
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),
],
),
);
), // SingleChildScrollView
], // Stack children
); // Stack
},
loading: () => const Center(child: CircularProgressIndicator()),
error: (err, stack) => ErrorRetryWidget(
@ -139,6 +158,23 @@ class _SommelierScreenState extends ConsumerState<SommelierScreen> {
);
}
Widget _buildSectionHeader(BuildContext context, String title, IconData icon) {
final appColors = Theme.of(context).extension<AppColors>()!;
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<AppColors>()!;
final isDark = Theme.of(context).brightness == Brightness.dark;
@ -293,26 +329,24 @@ class _SommelierScreenState extends ConsumerState<SommelierScreen> {
Widget _buildActionButtons(BuildContext context) {
final appColors = Theme.of(context).extension<AppColors>()!;
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)),
),
),
);
}

View File

@ -13,6 +13,7 @@ import '../widgets/gamification/badge_case.dart';
import '../widgets/gamification/activity_stats.dart';
import '../theme/app_colors.dart';
import '../services/mbti_types.dart'; // Needed for type title display
import '../main.dart' show isBusinessApp;
class SoulScreen extends ConsumerStatefulWidget {
const SoulScreen({super.key});
@ -33,39 +34,45 @@ class _SoulScreenState extends ConsumerState<SoulScreen> {
title: Text(t['myPage']),
centerTitle: true,
),
body: ListView(
body: Stack(
children: [
// Ambient Glow
Positioned(
top: -80,
left: -60,
child: IgnorePointer(
child: Container(
width: 320,
height: 320,
decoration: BoxDecoration(
shape: BoxShape.circle,
gradient: RadialGradient(
colors: [
appColors.brandPrimary.withValues(alpha: 0.07),
Colors.transparent,
],
),
),
),
),
),
ListView(
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(
@ -85,9 +92,18 @@ class _SoulScreenState extends ConsumerState<SoulScreen> {
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)),
@ -96,7 +112,7 @@ class _SoulScreenState extends ConsumerState<SoulScreen> {
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)),
@ -110,7 +126,6 @@ class _SoulScreenState extends ConsumerState<SoulScreen> {
),
trailing: Icon(LucideIcons.chevronRight, color: appColors.iconSubtle),
onTap: () {
// Navigate to Sommelier Tab (Index 2 in BottomNavBar)
ref.read(currentTabIndexProvider.notifier).setIndex(2);
},
),
@ -127,11 +142,33 @@ class _SoulScreenState extends ConsumerState<SoulScreen> {
// other Settings
OtherSettingsSection(
title: t['otherSettings'],
showBusinessMode: isBusinessApp,
),
const SizedBox(height: 24),
BackupSettingsSection(),
],
), // ListView
], // Stack children
), // Stack
);
}
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,
),
),
],
),
);
}

View File

@ -0,0 +1,43 @@
import 'package:flutter/material.dart';
///
/// GestureDetector / InkWell 使
class Pressable extends StatefulWidget {
const Pressable({
super.key,
required this.child,
this.onTap,
this.scale = 0.97,
this.duration = const Duration(milliseconds: 100),
});
final Widget child;
final VoidCallback? onTap;
final double scale;
final Duration duration;
@override
State<Pressable> createState() => _PressableState();
}
class _PressableState extends State<Pressable> {
bool _pressed = false;
@override
Widget build(BuildContext context) {
return GestureDetector(
onTapDown: (_) => setState(() => _pressed = true),
onTapUp: (_) {
setState(() => _pressed = false);
widget.onTap?.call();
},
onTapCancel: () => setState(() => _pressed = false),
child: AnimatedScale(
scale: _pressed ? widget.scale : 1.0,
duration: widget.duration,
curve: Curves.easeOut,
child: widget.child,
),
);
}
}

View File

@ -4,6 +4,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../../providers/theme_provider.dart';
import 'package:google_fonts/google_fonts.dart';
import '../contextual_help_icon.dart';
import '../common/pressable.dart';
import '../../theme/app_colors.dart';
class LevelTitleCard extends ConsumerWidget {
@ -20,7 +21,8 @@ class LevelTitleCard extends ConsumerWidget {
final progress = userProfile.nextLevelProgress;
final expToNext = userProfile.expToNextLevel;
return Container(
return Pressable(
child: Container(
width: double.infinity,
padding: const EdgeInsets.all(20),
decoration: BoxDecoration(
@ -40,6 +42,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: [
@ -51,8 +60,10 @@ class LevelTitleCard extends ConsumerWidget {
children: [
Text(
'現在の称号',
style: Theme.of(context).textTheme.titleSmall?.copyWith(
fontWeight: FontWeight.bold,
style: Theme.of(context).textTheme.bodySmall?.copyWith(
fontWeight: FontWeight.w500,
color: appColors.textSecondary,
letterSpacing: 0.5,
),
),
const SizedBox(width: 4),
@ -70,9 +81,10 @@ class LevelTitleCard extends ConsumerWidget {
child: Text(
title,
style: GoogleFonts.zenOldMincho(
fontSize: 28,
fontSize: 36,
fontWeight: FontWeight.bold,
color: appColors.brandPrimary,
height: 1.1,
),
),
),
@ -135,9 +147,10 @@ class LevelTitleCard extends ConsumerWidget {
),
],
),
);
), // Container
); // Pressable
}
static Widget _buildLevelHelpContent(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,

View File

@ -16,7 +16,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
# In Windows, build-name is used as the major, minor, and patch parts
# of the product and file versions while build-number is used as the build suffix.
version: 1.0.18+29
version: 1.0.19+30
environment:
sdk: ^3.10.1

View File

@ -1,21 +1,21 @@
{
{
"date": "2026-04-05",
"name": "Ponshu Room 1.0.18 (2026-04-05)",
"version": "v1.0.18",
"name": "Ponshu Room 1.0.19 (2026-04-05)",
"version": "v1.0.19",
"apks": {
"eiji": {
"lite": {
"url": "https://posimai-lab.tail72e846.ts.net/mai/ponshu-room-lite/releases/download/v1.0.18/ponshu_room_consumer_eiji.apk",
"url": "https://posimai-lab.tail72e846.ts.net/mai/ponshu-room-lite/releases/download/v1.0.19/ponshu_room_consumer_eiji.apk",
"size_mb": 89,
"filename": "ponshu_room_consumer_eiji.apk"
}
},
"maita": {
"lite": {
"url": "https://posimai-lab.tail72e846.ts.net/mai/ponshu-room-lite/releases/download/v1.0.18/ponshu_room_consumer_maita.apk",
"url": "https://posimai-lab.tail72e846.ts.net/mai/ponshu-room-lite/releases/download/v1.0.19/ponshu_room_consumer_maita.apk",
"size_mb": 89,
"filename": "ponshu_room_consumer_maita.apk"
}
}
}
}
}