feat(ui): iMessage-inspired light default + soft charcoal dark

Default ThemeMode.light with white messenger stage, blue mine bubbles,
and gray peer bubbles. Dark theme uses elevated charcoal instead of
pure black; composer and CTAs follow common messenger patterns.

Co-authored-by: okuma <o0kuma@users.noreply.github.com>
This commit is contained in:
Cursor Agent 2026-08-03 04:28:41 +00:00
parent 0aae8fb578
commit d905d0e5e8
No known key found for this signature in database
10 changed files with 143 additions and 126 deletions

View File

@ -38,8 +38,8 @@ Phase 1 **A~C** 이후 실행 트랙. 작업 단위를 하나씩 처리한다.
- 앱 코드는 클로즈드 베타 직전 수준
- **`https://msn.iykyka.com` 라이브 + N3 완료 + Gemini 실초안 OK + Track A/B 완료**
- 진행 중: **N4 FCM 코드 경로** → Master 시크릿 대기 → Android UI QA
- UI: **Soft Neutral + surface hierarchy** 프로덕션 반영 (`e05a0f0`, 2026-08-03) —
GitHub+Gitea `main` 듀얼 푸시 · `msn.iykyka.com` web 재빌드 완료
- UI: **iMessage-inspired light default** + soft charcoal dark (진행 중 배포) —
기본 `ThemeMode.light`, 내 버블 `#007AFF`, 다크 캔버스 `#141418`
- **발견(2026-07-31): Track C 콘텐츠 갭**`PRD.md` P0 대비 단톡 따라잡기·관계별 페르소나·
스팸 감지 미구현. Master 액션(FCM 시크릿, 실기기 탭, 웹 재배포)과 별개로 지금 바로 코드 착수 가능
- 실 FCM 기기 수신 · Android 실기기 탭 · 사람 PoC 실행은 남음

View File

@ -89,7 +89,8 @@ class _BootstrapState extends State<_Bootstrap> {
debugShowCheckedModeBanner: false,
theme: AppTheme.light(),
darkTheme: AppTheme.dark(),
themeMode: ThemeMode.system,
// Default to light messenger UI; OS dark still maps to soft charcoal.
themeMode: ThemeMode.light,
builder: (context, child) => GradientBackdrop(child: child),
home: const SplashScreen(),
);
@ -112,7 +113,8 @@ class YkavuApp extends StatelessWidget {
debugShowCheckedModeBanner: false,
theme: AppTheme.light(),
darkTheme: AppTheme.dark(),
themeMode: ThemeMode.system,
// Default to light messenger UI; OS dark still maps to soft charcoal.
themeMode: ThemeMode.light,
builder: (context, child) => GradientBackdrop(child: child),
home: Consumer<SessionState>(
builder: (context, s, _) {

View File

@ -405,30 +405,43 @@ class _ChatScreenState extends State<ChatScreen> {
),
_buildL1Panel(context),
SafeArea(
child: Padding(
padding: const EdgeInsets.fromLTRB(12, 8, 12, 12),
child: Container(
padding: const EdgeInsets.fromLTRB(10, 8, 10, 10),
decoration: BoxDecoration(
color: AppTheme.glassFill(theme.brightness),
border: Border(
top: BorderSide(color: AppTheme.glassBorder(theme.brightness).withValues(alpha: 0.7)),
),
),
child: Row(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
IconButton(
tooltip: '초안 요청',
onPressed: _busy ? null : _requestDraft,
icon: const Icon(Icons.auto_awesome_outlined),
),
Expanded(
child: TextField(
controller: _input,
minLines: 1,
maxLines: 4,
textInputAction: TextInputAction.send,
onSubmitted: (_) {
if (!_busy) _sendHuman();
},
decoration: const InputDecoration(hintText: '메시지'),
),
),
const SizedBox(width: 8),
IconButton.filledTonal(
tooltip: '초안 요청',
onPressed: _busy ? null : _requestDraft,
icon: const Icon(Icons.auto_awesome),
),
const SizedBox(width: 4),
const SizedBox(width: 6),
IconButton.filled(
tooltip: '보내기',
onPressed: _busy ? null : _sendHuman,
icon: const Icon(Icons.send),
style: IconButton.styleFrom(
backgroundColor: theme.colorScheme.primary,
foregroundColor: theme.colorScheme.onPrimary,
),
icon: const Icon(Icons.arrow_upward_rounded),
),
],
),

View File

@ -156,16 +156,16 @@ class _ConversationListScreenState extends State<ConversationListScreen> {
Color _avatarColor(BuildContext context, int seed) {
final isDark = Theme.of(context).brightness == Brightness.dark;
final palette = isDark
? const [Color(0xFF1C1C1C), Color(0xFF22262E), Color(0xFF242018)]
: const [Color(0xFFF4F4F5), Color(0xFFE8EEF4), Color(0xFFF3F0EA)];
? const [Color(0xFF2C2C34), Color(0xFF243447), Color(0xFF3A3224)]
: const [Color(0xFFE9E9EB), Color(0xFFD6E8FF), Color(0xFFFFF1D6)];
return palette[seed % palette.length];
}
Color _onAvatarColor(BuildContext context, int seed) {
final isDark = Theme.of(context).brightness == Brightness.dark;
final palette = isDark
? const [Color(0xFFA3A3A3), Color(0xFF8BB4D9), Color(0xFFC4A574)]
: const [Color(0xFF525252), Color(0xFF3B6D9B), Color(0xFF9A7B4F)];
? const [Color(0xFFF2F2F7), Color(0xFF0A84FF), Color(0xFFD4B06A)]
: const [Color(0xFF1C1C1E), Color(0xFF007AFF), Color(0xFFB08A4A)];
return palette[seed % palette.length];
}

View File

@ -150,7 +150,7 @@ class _DemoTestPanel extends StatelessWidget {
Text(
'테스트용 (누구나)',
style: theme.textTheme.labelLarge?.copyWith(
color: theme.colorScheme.onSurfaceVariant,
color: theme.colorScheme.primary,
letterSpacing: 0.15,
fontWeight: FontWeight.w600,
),

View File

@ -36,8 +36,8 @@ class SplashScreen extends StatelessWidget {
width: 22,
height: 22,
child: CircularProgressIndicator(
strokeWidth: 2,
color: scheme.onSurfaceVariant,
strokeWidth: 2.2,
color: scheme.primary,
),
),
],

View File

@ -1,36 +1,36 @@
import 'package:flutter/material.dart';
import 'package:google_fonts/google_fonts.dart';
/// Soft Neutral + surface hierarchy for .
/// Neutrals dominate; accent is thin (mine bubbles / small links only).
/// Radii: input/button 12, bubble 18. No glow, almost no shadow.
/// Messenger UI for iMessage-inspired light default + soft charcoal dark.
/// Light: white stage, blue mine bubbles, gray peer bubbles.
/// Dark: elevated charcoal (not pure black), softer blue accents.
class AppTheme {
AppTheme._();
// Light neutrals
static const canvasLight = Color(0xFFFAFAF9);
// Light iOS Messages / white messenger
static const canvasLight = Color(0xFFF2F2F7);
static const surfaceLight = Color(0xFFFFFFFF);
static const inkLight = Color(0xFF171717);
static const mutedLight = Color(0xFF737373);
static const lineLight = Color(0xFFE5E5E5);
static const softFillLight = Color(0xFFF4F4F5);
static const inkLight = Color(0xFF1C1C1E);
static const mutedLight = Color(0xFF8E8E93);
static const lineLight = Color(0xFFD1D1D6);
static const softFillLight = Color(0xFFE9E9EB);
// Dark neutrals
static const canvasDark = Color(0xFF0A0A0A);
static const surfaceDark = Color(0xFF141414);
static const inkDark = Color(0xFFFAFAFA);
static const mutedDark = Color(0xFFA3A3A3);
static const lineDark = Color(0xFF262626);
static const softFillDark = Color(0xFF1C1C1C);
// Dark soft charcoal (not #000)
static const canvasDark = Color(0xFF141418);
static const surfaceDark = Color(0xFF1C1C22);
static const inkDark = Color(0xFFF2F2F7);
static const mutedDark = Color(0xFF98989F);
static const lineDark = Color(0xFF2C2C34);
static const softFillDark = Color(0xFF2C2C34);
/// Thin accent used for mine bubbles & primary actions, not chrome.
static const accentLight = Color(0xFF3B6D9B);
static const accentDark = Color(0xFF8BB4D9);
static const accentSoftLight = Color(0xFFE8F0F7);
static const accentSoftDark = Color(0xFF1A2836);
/// iMessage-like blue for mine bubbles & primary actions.
static const accentLight = Color(0xFF007AFF);
static const accentDark = Color(0xFF0A84FF);
static const accentSoftLight = Color(0xFFD6E8FF);
static const accentSoftDark = Color(0xFF163A66);
static const twinAmberLight = Color(0xFF9A7B4F);
static const twinAmberDark = Color(0xFFC4A574);
static const twinAmberLight = Color(0xFFB08A4A);
static const twinAmberDark = Color(0xFFD4B06A);
/// Compat for older call sites.
static const teal = accentLight;
@ -44,21 +44,26 @@ class AppTheme {
static LinearGradient backgroundGradient(Brightness b) {
final c = b == Brightness.dark ? canvasDark : canvasLight;
return LinearGradient(colors: [c, c]);
return LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [c, c],
);
}
static Color glassFill(Brightness b) => b == Brightness.dark ? surfaceDark : surfaceLight;
static Color glassBorder(Brightness b) => b == Brightness.dark ? lineDark : lineLight;
static Color glassShadow(Brightness b) => Colors.transparent;
static Color mineBubble(Brightness b) => b == Brightness.dark ? accentSoftDark : accentLight;
static Color mineBubbleFg(Brightness b) => b == Brightness.dark ? inkDark : Colors.white;
/// Mine = solid messenger blue (both modes).
static Color mineBubble(Brightness b) => b == Brightness.dark ? accentDark : accentLight;
static Color mineBubbleFg(Brightness b) => Colors.white;
static Color peerBubble(Brightness b) => b == Brightness.dark ? softFillDark : softFillLight;
static const rInput = 12.0;
static const rButton = 12.0;
static const rBubble = 18.0;
static const rPanel = 12.0;
static const rInput = 20.0;
static const rButton = 14.0;
static const rBubble = 20.0;
static const rPanel = 14.0;
static ThemeData light() => _build(Brightness.light);
static ThemeData dark() => _build(Brightness.dark);
@ -66,11 +71,10 @@ class AppTheme {
static ThemeData _build(Brightness brightness) {
final isDark = brightness == Brightness.dark;
final primary = isDark ? accentDark : accentLight;
final onPrimary = isDark ? canvasDark : Colors.white;
final onPrimary = Colors.white;
final ink = isDark ? inkDark : inkLight;
final muted = isDark ? mutedDark : mutedLight;
final line = isDark ? lineDark : lineLight;
final canvas = isDark ? canvasDark : canvasLight;
final surface = isDark ? surfaceDark : surfaceLight;
final soft = isDark ? softFillDark : softFillLight;
@ -86,7 +90,7 @@ class AppTheme {
onSecondaryContainer: muted,
tertiary: primary,
onTertiary: onPrimary,
error: isDark ? const Color(0xFFF87171) : const Color(0xFFB91C1C),
error: isDark ? const Color(0xFFFF6961) : const Color(0xFFFF3B30),
onError: Colors.white,
surface: surface,
onSurface: ink,
@ -109,12 +113,12 @@ class AppTheme {
textTheme: textTheme,
primaryTextTheme: textTheme,
appBarTheme: AppBarTheme(
backgroundColor: canvas.withValues(alpha: 0.92),
backgroundColor: surface.withValues(alpha: isDark ? 0.94 : 0.88),
foregroundColor: ink,
surfaceTintColor: Colors.transparent,
elevation: 0,
scrolledUnderElevation: 0,
centerTitle: false,
centerTitle: true,
titleTextStyle: textTheme.titleLarge?.copyWith(fontWeight: FontWeight.w600, letterSpacing: -0.2),
),
cardTheme: CardThemeData(
@ -122,32 +126,32 @@ class AppTheme {
color: surface,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(rPanel),
side: BorderSide(color: line),
side: BorderSide(color: line.withValues(alpha: isDark ? 0.9 : 0.65)),
),
margin: EdgeInsets.zero,
),
listTileTheme: ListTileThemeData(
iconColor: muted,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(rPanel)),
contentPadding: const EdgeInsets.symmetric(horizontal: 14, vertical: 2),
minVerticalPadding: 8,
contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 4),
minVerticalPadding: 10,
),
dividerTheme: DividerThemeData(color: line, space: 1, thickness: 1),
dividerTheme: DividerThemeData(color: line, space: 1, thickness: 0.5),
inputDecorationTheme: InputDecorationTheme(
filled: true,
fillColor: surface,
contentPadding: const EdgeInsets.symmetric(horizontal: 14, vertical: 12),
fillColor: isDark ? soft : surface,
contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(rInput),
borderSide: BorderSide(color: line),
borderSide: BorderSide(color: line.withValues(alpha: 0.7)),
),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(rInput),
borderSide: BorderSide(color: line),
borderSide: BorderSide(color: line.withValues(alpha: 0.7)),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(rInput),
borderSide: BorderSide(color: primary, width: 1.2),
borderSide: BorderSide(color: primary, width: 1.4),
),
errorBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(rInput),
@ -155,29 +159,29 @@ class AppTheme {
),
focusedErrorBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(rInput),
borderSide: BorderSide(color: scheme.error, width: 1.2),
borderSide: BorderSide(color: scheme.error, width: 1.4),
),
hintStyle: TextStyle(color: muted.withValues(alpha: 0.8)),
hintStyle: TextStyle(color: muted.withValues(alpha: 0.85)),
labelStyle: TextStyle(color: muted, fontWeight: FontWeight.w500),
),
filledButtonTheme: FilledButtonThemeData(
style: FilledButton.styleFrom(
backgroundColor: isDark ? inkDark : inkLight,
foregroundColor: isDark ? canvasDark : Colors.white,
backgroundColor: primary,
foregroundColor: onPrimary,
padding: const EdgeInsets.symmetric(horizontal: 18, vertical: 12),
minimumSize: const Size(0, 46),
minimumSize: const Size(0, 48),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(rButton)),
textStyle: const TextStyle(fontWeight: FontWeight.w600, fontSize: 15),
textStyle: const TextStyle(fontWeight: FontWeight.w600, fontSize: 16),
elevation: 0,
),
),
outlinedButtonTheme: OutlinedButtonThemeData(
style: OutlinedButton.styleFrom(
foregroundColor: ink,
foregroundColor: primary,
padding: const EdgeInsets.symmetric(horizontal: 18, vertical: 12),
minimumSize: const Size(0, 46),
minimumSize: const Size(0, 48),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(rButton)),
side: BorderSide(color: line),
side: BorderSide(color: primary.withValues(alpha: 0.45)),
),
),
textButtonTheme: TextButtonThemeData(
@ -188,22 +192,22 @@ class AppTheme {
),
iconButtonTheme: IconButtonThemeData(
style: IconButton.styleFrom(
foregroundColor: muted,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)),
foregroundColor: primary,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
),
),
chipTheme: ChipThemeData(
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)),
side: BorderSide(color: line),
backgroundColor: surface,
labelStyle: TextStyle(color: muted, fontWeight: FontWeight.w500, fontSize: 12),
backgroundColor: soft,
labelStyle: TextStyle(color: ink, fontWeight: FontWeight.w500, fontSize: 12),
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2),
),
floatingActionButtonTheme: FloatingActionButtonThemeData(
backgroundColor: isDark ? inkDark : inkLight,
foregroundColor: isDark ? canvasDark : Colors.white,
backgroundColor: primary,
foregroundColor: onPrimary,
elevation: 0,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(rButton)),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(18)),
),
snackBarTheme: SnackBarThemeData(
behavior: SnackBarBehavior.floating,
@ -216,20 +220,20 @@ class AppTheme {
backgroundColor: surface,
elevation: 0,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16),
side: BorderSide(color: line),
borderRadius: BorderRadius.circular(18),
side: BorderSide(color: line.withValues(alpha: 0.8)),
),
),
progressIndicatorTheme: ProgressIndicatorThemeData(color: primary),
bannerTheme: MaterialBannerThemeData(backgroundColor: surface, padding: const EdgeInsets.all(16)),
segmentedButtonTheme: SegmentedButtonThemeData(
style: SegmentedButton.styleFrom(
backgroundColor: surface,
backgroundColor: soft,
foregroundColor: muted,
selectedForegroundColor: ink,
selectedBackgroundColor: soft,
side: BorderSide(color: line),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(rButton)),
selectedForegroundColor: primary,
selectedBackgroundColor: isDark ? accentSoftDark : accentSoftLight,
side: BorderSide.none,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 8),
),
),
@ -241,16 +245,16 @@ class AppTheme {
(t ?? const TextStyle()).copyWith(color: ink, fontWeight: w, fontSize: size, letterSpacing: ls, height: h);
return base.copyWith(
displayLarge: s(base.displayLarge, w: FontWeight.w600, size: 36, ls: -1.0),
displaySmall: s(base.displaySmall, w: FontWeight.w600, size: 28, ls: -0.6),
headlineSmall: s(base.headlineSmall, w: FontWeight.w600, size: 20, ls: -0.2),
displayLarge: s(base.displayLarge, w: FontWeight.w700, size: 34, ls: -0.8),
displaySmall: s(base.displaySmall, w: FontWeight.w700, size: 28, ls: -0.5),
headlineSmall: s(base.headlineSmall, w: FontWeight.w600, size: 22, ls: -0.3),
titleLarge: s(base.titleLarge, w: FontWeight.w600, size: 17, ls: -0.2),
titleMedium: s(base.titleMedium, w: FontWeight.w600, size: 15),
titleSmall: s(base.titleSmall, w: FontWeight.w600, size: 14),
bodyLarge: s(base.bodyLarge, w: FontWeight.w400, size: 15, h: 1.45),
bodyMedium: s(base.bodyMedium, w: FontWeight.w400, size: 14, h: 1.45),
bodySmall: s(base.bodySmall, w: FontWeight.w400, size: 12, h: 1.4),
labelLarge: s(base.labelLarge, w: FontWeight.w500, size: 13),
titleMedium: s(base.titleMedium, w: FontWeight.w600, size: 16),
titleSmall: s(base.titleSmall, w: FontWeight.w600, size: 15),
bodyLarge: s(base.bodyLarge, w: FontWeight.w400, size: 17, h: 1.35),
bodyMedium: s(base.bodyMedium, w: FontWeight.w400, size: 16, h: 1.35),
bodySmall: s(base.bodySmall, w: FontWeight.w400, size: 13, h: 1.35),
labelLarge: s(base.labelLarge, w: FontWeight.w500, size: 15),
labelSmall: s(base.labelSmall, w: FontWeight.w500, size: 11),
);
}

View File

@ -2,7 +2,7 @@ import 'package:flutter/material.dart';
import '../theme/app_theme.dart';
/// Soft Neutral brand mark ink monogram, no glow or heavy tile chrome.
/// Compact messenger brand mark blue tile, no glow.
class BrandMark extends StatelessWidget {
const BrandMark({super.key, this.size = 64});
@ -10,21 +10,19 @@ class BrandMark extends StatelessWidget {
@override
Widget build(BuildContext context) {
final isDark = Theme.of(context).brightness == Brightness.dark;
final fill = isDark ? AppTheme.inkDark : AppTheme.inkLight;
final fg = isDark ? AppTheme.canvasDark : Colors.white;
final scheme = Theme.of(context).colorScheme;
return Container(
width: size,
height: size,
decoration: BoxDecoration(
color: fill,
borderRadius: BorderRadius.circular(AppTheme.rPanel),
color: scheme.primary,
borderRadius: BorderRadius.circular(size * 0.28),
),
alignment: Alignment.center,
child: Text(
'Y',
style: TextStyle(
color: fg,
color: scheme.onPrimary,
fontWeight: FontWeight.w700,
fontSize: size * 0.42,
height: 1,

View File

@ -3,7 +3,7 @@ import 'package:flutter/material.dart';
import '../models/models.dart';
import '../theme/app_theme.dart';
/// Twin messages get a warm thin border + label (PRD §3.1 ).
/// iMessage-like bubble. Twin messages keep a warm label + soft tint (PRD §3.1).
class MessageBubble extends StatelessWidget {
const MessageBubble({
super.key,
@ -34,23 +34,20 @@ class MessageBubble extends StatelessWidget {
final Color bg;
final Color fg;
Border? border;
if (retracted) {
bg = scheme.surfaceContainerHigh;
fg = scheme.onSurfaceVariant;
} else if (twin) {
bg = brightness == Brightness.dark
? const Color(0xFF221C14)
: const Color(0xFFFAF6F0);
? const Color(0xFF2A2418)
: const Color(0xFFFFF6E8);
fg = scheme.onSurface;
border = Border.all(color: accent.withValues(alpha: 0.4), width: 1);
} else if (isMine) {
bg = AppTheme.mineBubble(brightness);
fg = AppTheme.mineBubbleFg(brightness);
} else {
bg = AppTheme.peerBubble(brightness);
fg = scheme.onSurface;
border = Border.all(color: scheme.outlineVariant.withValues(alpha: 0.9));
}
final radius = BorderRadius.only(
@ -61,12 +58,12 @@ class MessageBubble extends StatelessWidget {
);
final bubble = Container(
constraints: BoxConstraints(maxWidth: MediaQuery.sizeOf(context).width * 0.76),
padding: const EdgeInsets.fromLTRB(14, 10, 14, 8),
constraints: BoxConstraints(maxWidth: MediaQuery.sizeOf(context).width * 0.74),
padding: const EdgeInsets.fromLTRB(14, 9, 14, 7),
decoration: BoxDecoration(
color: bg,
borderRadius: radius,
border: border,
border: twin ? Border.all(color: accent.withValues(alpha: 0.35)) : null,
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
@ -74,13 +71,13 @@ class MessageBubble extends StatelessWidget {
children: [
if (twin)
Padding(
padding: const EdgeInsets.only(bottom: 4),
padding: const EdgeInsets.only(bottom: 3),
child: Text(
'와카뷰',
style: theme.textTheme.labelSmall?.copyWith(
color: accent,
fontWeight: FontWeight.w700,
letterSpacing: 0.2,
letterSpacing: 0.15,
),
),
),
@ -99,12 +96,15 @@ class MessageBubble extends StatelessWidget {
else
Text(
message.text,
style: theme.textTheme.bodyMedium?.copyWith(color: fg, height: 1.4),
style: theme.textTheme.bodyMedium?.copyWith(color: fg, height: 1.3),
),
const SizedBox(height: 4),
const SizedBox(height: 3),
Text(
_time(message.createdAt),
style: theme.textTheme.labelSmall?.copyWith(color: fg.withValues(alpha: 0.55), fontSize: 10),
style: theme.textTheme.labelSmall?.copyWith(
color: fg.withValues(alpha: isMine && !twin && !retracted ? 0.7 : 0.5),
fontSize: 10,
),
),
],
),
@ -135,7 +135,7 @@ class MessageBubble extends StatelessWidget {
);
return Padding(
padding: const EdgeInsets.symmetric(vertical: 3, horizontal: 12),
padding: const EdgeInsets.symmetric(vertical: 2, horizontal: 10),
child: Align(
alignment: isMine ? Alignment.centerRight : Alignment.centerLeft,
child: content,

View File

@ -2,7 +2,7 @@ import 'package:flutter/material.dart';
import '../theme/app_theme.dart';
/// Primary CTA Soft Neutral ink fill (API name kept for call sites).
/// Primary CTA messenger blue fill (API name kept for call sites).
class PrimaryGradientButton extends StatelessWidget {
const PrimaryGradientButton({
super.key,
@ -17,12 +17,12 @@ class PrimaryGradientButton extends StatelessWidget {
@override
Widget build(BuildContext context) {
final isDark = Theme.of(context).brightness == Brightness.dark;
final bg = isDark ? AppTheme.inkDark : AppTheme.inkLight;
final fg = isDark ? AppTheme.canvasDark : Colors.white;
final scheme = Theme.of(context).colorScheme;
final bg = scheme.primary;
final fg = scheme.onPrimary;
final disabled = onPressed == null || loading;
return SizedBox(
height: 48,
height: 50,
width: double.infinity,
child: FilledButton(
onPressed: disabled && !loading ? null : onPressed,
@ -44,7 +44,7 @@ class PrimaryGradientButton extends StatelessWidget {
)
: Text(
label,
style: TextStyle(color: fg, fontWeight: FontWeight.w600, fontSize: 15),
style: TextStyle(color: fg, fontWeight: FontWeight.w600, fontSize: 16),
),
),
);