2026-01-11 08:17:29 +00:00
|
|
|
|
import 'package:flutter/material.dart';
|
|
|
|
|
|
import 'package:google_fonts/google_fonts.dart';
|
2026-01-29 15:54:22 +00:00
|
|
|
|
import 'app_colors.dart'; // Import Extension
|
|
|
|
|
|
|
|
|
|
|
|
enum AppFontStyle {
|
|
|
|
|
|
sans, // Noto Sans JP (ゴシック)
|
|
|
|
|
|
serif, // Noto Serif JP (明朝)
|
|
|
|
|
|
pottaOne, // Potta One (髭文字)
|
|
|
|
|
|
digital, // DotGothic16 (ドット)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
enum ColorVariant {
|
|
|
|
|
|
washiSumiKohaku, // 和紙×墨×琥珀 (Theme A)
|
|
|
|
|
|
current, // Current theme (Theme B)
|
|
|
|
|
|
}
|
2026-01-11 08:17:29 +00:00
|
|
|
|
|
|
|
|
|
|
class AppTheme {
|
|
|
|
|
|
static const Color posimaiBlue = Color(0xFF376495);
|
|
|
|
|
|
|
2026-01-29 15:54:22 +00:00
|
|
|
|
// ===== Theme A: 和紙×墨×琥珀 (Washi × Sumi × Kohaku) =====
|
|
|
|
|
|
// 日本酒の世界観を反映した洗練された配色
|
|
|
|
|
|
static const Color washiWhite = Color(0xFFFDFBF7); // 和紙の温かみのある白
|
|
|
|
|
|
static const Color sumiBlack = Color(0xFF4A3B32); // 墨色(温かみのある焦げ茶)
|
|
|
|
|
|
static const Color kohakuGold = Color(0xFFD4A574); // 琥珀色(酒の黄金色)
|
|
|
|
|
|
static const Color kohakuDeep = Color(0xFFB8860B); // 深い琥珀(アクセント用)
|
|
|
|
|
|
|
|
|
|
|
|
// ===== Theme B: Current (Original) =====
|
|
|
|
|
|
// 既存のPosimai Blueベースのテーマ
|
|
|
|
|
|
|
2026-01-11 08:17:29 +00:00
|
|
|
|
// Padding Constants
|
|
|
|
|
|
static const double spacingEmpty = 0.0;
|
|
|
|
|
|
static const double spacingTiny = 4.0;
|
|
|
|
|
|
static const double spacingSmall = 8.0;
|
|
|
|
|
|
static const double spacingMedium = 16.0;
|
|
|
|
|
|
static const double spacingLarge = 24.0;
|
|
|
|
|
|
static const double spacingXLarge = 32.0;
|
|
|
|
|
|
|
2026-01-29 15:54:22 +00:00
|
|
|
|
static ThemeData createTheme(
|
|
|
|
|
|
AppFontStyle fontStyle,
|
|
|
|
|
|
Brightness brightness,
|
|
|
|
|
|
ColorVariant colorVariant,
|
|
|
|
|
|
) {
|
|
|
|
|
|
/*
|
|
|
|
|
|
Why GoogleFonts is safe:
|
|
|
|
|
|
1. Downloads dynamically (no asset size increase).
|
|
|
|
|
|
2. Caches locally (fast 2nd load).
|
|
|
|
|
|
3. Fallback exists during download.
|
|
|
|
|
|
*/
|
|
|
|
|
|
final TextTheme textTheme;
|
|
|
|
|
|
switch (fontStyle) {
|
|
|
|
|
|
case AppFontStyle.sans:
|
|
|
|
|
|
textTheme = GoogleFonts.notoSansJpTextTheme();
|
|
|
|
|
|
case AppFontStyle.serif:
|
|
|
|
|
|
textTheme = GoogleFonts.notoSerifJpTextTheme();
|
|
|
|
|
|
case AppFontStyle.pottaOne:
|
|
|
|
|
|
textTheme = GoogleFonts.pottaOneTextTheme();
|
|
|
|
|
|
case AppFontStyle.digital:
|
|
|
|
|
|
textTheme = GoogleFonts.dotGothic16TextTheme();
|
|
|
|
|
|
}
|
2026-01-11 08:17:29 +00:00
|
|
|
|
|
2026-01-29 15:54:22 +00:00
|
|
|
|
// Get AppColors for this theme variant
|
|
|
|
|
|
final appColors = _getAppColors(colorVariant, brightness);
|
2026-01-11 08:17:29 +00:00
|
|
|
|
|
2026-01-29 15:54:22 +00:00
|
|
|
|
// Color scheme based on variant
|
|
|
|
|
|
final ColorScheme colorScheme;
|
|
|
|
|
|
|
|
|
|
|
|
if (colorVariant == ColorVariant.washiSumiKohaku) {
|
|
|
|
|
|
// Theme A: 和紙×墨×琥珀
|
|
|
|
|
|
colorScheme = ColorScheme.fromSeed(
|
|
|
|
|
|
seedColor: kohakuGold,
|
|
|
|
|
|
brightness: brightness,
|
|
|
|
|
|
).copyWith(
|
|
|
|
|
|
primary: brightness == Brightness.dark
|
|
|
|
|
|
? kohakuGold // 琥珀色 for dark mode
|
|
|
|
|
|
: sumiBlack, // 墨色 for light mode
|
2026-01-11 08:17:29 +00:00
|
|
|
|
|
2026-01-29 15:54:22 +00:00
|
|
|
|
surface: brightness == Brightness.dark
|
|
|
|
|
|
? const Color(0xFF1E1E1E)
|
|
|
|
|
|
: washiWhite, // 和紙ホワイト
|
|
|
|
|
|
|
|
|
|
|
|
secondary: brightness == Brightness.dark
|
|
|
|
|
|
? kohakuDeep // 深い琥珀 for dark mode
|
|
|
|
|
|
: kohakuGold, // 琥珀色 for light mode
|
|
|
|
|
|
);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// Theme B: Current (Original)
|
|
|
|
|
|
colorScheme = ColorScheme.fromSeed(
|
|
|
|
|
|
seedColor: posimaiBlue,
|
|
|
|
|
|
brightness: brightness,
|
|
|
|
|
|
).copyWith(
|
|
|
|
|
|
primary: brightness == Brightness.dark
|
|
|
|
|
|
? const Color(0xFF8AB4F8) // Bright blue for dark mode
|
|
|
|
|
|
: posimaiBlue, // Original blue for light mode
|
|
|
|
|
|
|
|
|
|
|
|
surface: brightness == Brightness.dark
|
|
|
|
|
|
? const Color(0xFF1E1E1E)
|
|
|
|
|
|
: Colors.white,
|
|
|
|
|
|
|
|
|
|
|
|
secondary: brightness == Brightness.dark
|
|
|
|
|
|
? const Color(0xFFFFB74D) // Warm orange for dark mode accents
|
|
|
|
|
|
: const Color(0xFFFF6F00), // Deep orange for light mode
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
2026-01-11 08:17:29 +00:00
|
|
|
|
|
|
|
|
|
|
return ThemeData(
|
|
|
|
|
|
useMaterial3: true,
|
|
|
|
|
|
colorScheme: colorScheme,
|
|
|
|
|
|
textTheme: textTheme.apply(
|
|
|
|
|
|
bodyColor: (brightness == Brightness.dark) ? Colors.white : Colors.black87,
|
|
|
|
|
|
displayColor: (brightness == Brightness.dark) ? Colors.white : Colors.black87,
|
|
|
|
|
|
).copyWith(
|
|
|
|
|
|
// Ensure headers/labels are visible
|
|
|
|
|
|
titleMedium: TextStyle(color: (brightness == Brightness.dark) ? Colors.white : Colors.black87),
|
|
|
|
|
|
titleSmall: TextStyle(color: (brightness == Brightness.dark) ? Colors.white70 : Colors.black54),
|
|
|
|
|
|
labelLarge: TextStyle(color: (brightness == Brightness.dark) ? Colors.white : Colors.black87),
|
|
|
|
|
|
),
|
2026-01-29 15:54:22 +00:00
|
|
|
|
scaffoldBackgroundColor: (brightness == Brightness.dark)
|
|
|
|
|
|
? const Color(0xFF121212)
|
|
|
|
|
|
: (colorVariant == ColorVariant.washiSumiKohaku
|
|
|
|
|
|
? washiWhite
|
|
|
|
|
|
: const Color(0xFFFAFAFA)),
|
2026-01-11 08:17:29 +00:00
|
|
|
|
|
|
|
|
|
|
cardTheme: CardThemeData(
|
|
|
|
|
|
elevation: 0,
|
|
|
|
|
|
margin: EdgeInsets.zero,
|
2026-01-29 15:54:22 +00:00
|
|
|
|
color: (brightness == Brightness.dark)
|
|
|
|
|
|
? const Color(0xFF1E1E1E)
|
|
|
|
|
|
: (colorVariant == ColorVariant.washiSumiKohaku
|
|
|
|
|
|
? Colors.white
|
|
|
|
|
|
: Colors.white),
|
2026-01-11 08:17:29 +00:00
|
|
|
|
),
|
|
|
|
|
|
|
|
|
|
|
|
appBarTheme: AppBarTheme(
|
2026-02-15 15:13:12 +00:00
|
|
|
|
// UI/UX Consistency: AppBarとNavigationBarの背景色を統一(ダークモードのみ)
|
|
|
|
|
|
backgroundColor: (brightness == Brightness.dark) ? const Color(0xFF1E1E1E) : null,
|
2026-01-11 08:17:29 +00:00
|
|
|
|
foregroundColor: (brightness == Brightness.dark) ? Colors.white : Colors.black87,
|
|
|
|
|
|
actionsIconTheme: IconThemeData(
|
|
|
|
|
|
color: (brightness == Brightness.dark) ? Colors.white : Colors.black87,
|
|
|
|
|
|
),
|
|
|
|
|
|
iconTheme: IconThemeData(
|
|
|
|
|
|
color: (brightness == Brightness.dark) ? Colors.white : Colors.black87,
|
|
|
|
|
|
),
|
|
|
|
|
|
),
|
|
|
|
|
|
|
|
|
|
|
|
iconTheme: IconThemeData(
|
2026-01-29 15:54:22 +00:00
|
|
|
|
color: (brightness == Brightness.dark)
|
|
|
|
|
|
? Colors.white
|
|
|
|
|
|
: (colorVariant == ColorVariant.washiSumiKohaku
|
|
|
|
|
|
? sumiBlack
|
|
|
|
|
|
: posimaiBlue),
|
2026-01-11 08:17:29 +00:00
|
|
|
|
),
|
|
|
|
|
|
|
|
|
|
|
|
navigationBarTheme: NavigationBarThemeData(
|
|
|
|
|
|
backgroundColor: (brightness == Brightness.dark) ? const Color(0xFF1E1E1E) : null,
|
2026-01-29 15:54:22 +00:00
|
|
|
|
indicatorColor: brightness == Brightness.dark
|
|
|
|
|
|
? appColors.brandPrimary.withValues(alpha: 0.4)
|
|
|
|
|
|
: appColors.brandPrimary.withValues(alpha: 0.25),
|
2026-01-11 08:17:29 +00:00
|
|
|
|
),
|
2026-01-29 15:54:22 +00:00
|
|
|
|
extensions: [
|
|
|
|
|
|
_getAppColors(colorVariant, brightness),
|
|
|
|
|
|
],
|
2026-01-11 08:17:29 +00:00
|
|
|
|
);
|
|
|
|
|
|
}
|
2026-01-29 15:54:22 +00:00
|
|
|
|
|
|
|
|
|
|
/// AppColorsインスタンスを取得(テーマバリアントとブライトネスに基づく)
|
|
|
|
|
|
static AppColors _getAppColors(ColorVariant colorVariant, Brightness brightness) {
|
|
|
|
|
|
if (colorVariant == ColorVariant.washiSumiKohaku) {
|
|
|
|
|
|
return brightness == Brightness.dark
|
|
|
|
|
|
? AppColors.washiDark()
|
|
|
|
|
|
: AppColors.washiLight();
|
|
|
|
|
|
} else {
|
|
|
|
|
|
return brightness == Brightness.dark
|
|
|
|
|
|
? AppColors.currentDark()
|
|
|
|
|
|
: AppColors.currentLight();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-01-11 08:17:29 +00:00
|
|
|
|
}
|