Polish workspace theme and add Makefile tasks

This commit is contained in:
Haitao Pan 2026-03-11 15:34:15 +08:00
parent 7cabfa2e05
commit 390e14beef
5 changed files with 177 additions and 80 deletions

47
Makefile Normal file
View File

@ -0,0 +1,47 @@
.DEFAULT_GOAL := help
SHELL := /bin/bash
FLUTTER ?= flutter
PNPM ?= pnpm
DART ?= dart
DEVICE ?= macos
.PHONY: help deps analyze test check format run build-macos build-ios-sim package-mac install-mac clean
help: ## Show available targets
@grep -E '^[a-zA-Z0-9_.-]+:.*?## ' Makefile | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "%-18s %s\n", $$1, $$2}'
deps: ## Install Flutter dependencies
$(FLUTTER) pub get
analyze: ## Run static analysis
$(FLUTTER) analyze
test: ## Run Flutter tests
$(FLUTTER) test
check: analyze test ## Run the standard validation suite
format: ## Format Dart sources
$(DART) format lib test
run: ## Run the app on a device or desktop target (DEVICE=macos by default)
$(FLUTTER) run -d $(DEVICE)
build-macos: ## Build the macOS app in release mode
$(FLUTTER) build macos --release
build-ios-sim: ## Build the iOS app for the simulator
$(FLUTTER) build ios --simulator
package-mac: ## Create the macOS .app and DMG
bash scripts/package-flutter-mac-app.sh
install-mac: ## Package and install the macOS app into /Applications
bash scripts/package-flutter-mac-app.sh
bash scripts/install-flutter-mac-dmg.sh
clean: ## Remove generated artifacts
$(FLUTTER) clean
rm -rf build dist

View File

@ -12,7 +12,9 @@ class AppPalette extends ThemeExtension<AppPalette> {
required this.stroke,
required this.strokeSoft,
required this.accent,
required this.accentHover,
required this.accentMuted,
required this.idle,
required this.success,
required this.warning,
required this.danger,
@ -32,7 +34,9 @@ class AppPalette extends ThemeExtension<AppPalette> {
final Color stroke;
final Color strokeSoft;
final Color accent;
final Color accentHover;
final Color accentMuted;
final Color idle;
final Color success;
final Color warning;
final Color danger;
@ -43,45 +47,49 @@ class AppPalette extends ThemeExtension<AppPalette> {
final Color hover;
static const AppPalette light = AppPalette(
canvas: Color(0xFFF5F6F7),
sidebar: Color(0xFFF3F4F6),
sidebarBorder: Color(0xFFE6E8EB),
canvas: Color(0xFFF8FAFC),
sidebar: Color(0xFFF8FAFC),
sidebarBorder: Color(0xFFE5E7EB),
surfacePrimary: Color(0xFFFFFFFF),
surfaceSecondary: Color(0xFFFAFAFB),
surfaceTertiary: Color(0xFFF2F4F6),
stroke: Color(0xFFE7E9EC),
strokeSoft: Color(0xFFF1F3F5),
accent: Color(0xFF247A66),
accentMuted: Color(0xFFE6F3EF),
success: Color(0xFF228163),
warning: Color(0xFFC88A34),
danger: Color(0xFFD15A5A),
textPrimary: Color(0xFF13161A),
textSecondary: Color(0xFF4F5A68),
textMuted: Color(0xFF78808B),
shadow: Color(0x14111822),
hover: Color(0xFFF0F2F4),
surfaceSecondary: Color(0xFFF8FAFC),
surfaceTertiary: Color(0xFFF1F5F9),
stroke: Color(0xFFE5E7EB),
strokeSoft: Color(0xFFF1F5F9),
accent: Color(0xFF3B82F6),
accentHover: Color(0xFF2563EB),
accentMuted: Color(0xFFDBEAFE),
idle: Color(0xFF94A3B8),
success: Color(0xFF22C55E),
warning: Color(0xFFF59E0B),
danger: Color(0xFFEF4444),
textPrimary: Color(0xFF111827),
textSecondary: Color(0xFF6B7280),
textMuted: Color(0xFF64748B),
shadow: Color(0x0F0F172A),
hover: Color(0xFFEFF6FF),
);
static const AppPalette dark = AppPalette(
canvas: Color(0xFF121416),
sidebar: Color(0xFF15181B),
sidebarBorder: Color(0xFF23272C),
surfacePrimary: Color(0xFF1A1D21),
surfaceSecondary: Color(0xFF20242A),
surfaceTertiary: Color(0xFF262B33),
stroke: Color(0xFF2D333A),
strokeSoft: Color(0xFF21262C),
accent: Color(0xFF3AB08F),
accentMuted: Color(0xFF16372E),
success: Color(0xFF51C397),
warning: Color(0xFFE2A14A),
danger: Color(0xFFFF8585),
textPrimary: Color(0xFFF4F6F8),
textSecondary: Color(0xFFBCC3CC),
textMuted: Color(0xFF9098A4),
canvas: Color(0xFF0B1220),
sidebar: Color(0xFF0F172A),
sidebarBorder: Color(0xFF1E293B),
surfacePrimary: Color(0xFF111827),
surfaceSecondary: Color(0xFF0F172A),
surfaceTertiary: Color(0xFF172033),
stroke: Color(0xFF223046),
strokeSoft: Color(0xFF162033),
accent: Color(0xFF3B82F6),
accentHover: Color(0xFF2563EB),
accentMuted: Color(0xFF142B52),
idle: Color(0xFF94A3B8),
success: Color(0xFF22C55E),
warning: Color(0xFFF59E0B),
danger: Color(0xFFEF4444),
textPrimary: Color(0xFFF8FAFC),
textSecondary: Color(0xFF94A3B8),
textMuted: Color(0xFF64748B),
shadow: Color(0x52000000),
hover: Color(0xFF232830),
hover: Color(0xFF11213A),
);
@override
@ -95,7 +103,9 @@ class AppPalette extends ThemeExtension<AppPalette> {
Color? stroke,
Color? strokeSoft,
Color? accent,
Color? accentHover,
Color? accentMuted,
Color? idle,
Color? success,
Color? warning,
Color? danger,
@ -115,7 +125,9 @@ class AppPalette extends ThemeExtension<AppPalette> {
stroke: stroke ?? this.stroke,
strokeSoft: strokeSoft ?? this.strokeSoft,
accent: accent ?? this.accent,
accentHover: accentHover ?? this.accentHover,
accentMuted: accentMuted ?? this.accentMuted,
idle: idle ?? this.idle,
success: success ?? this.success,
warning: warning ?? this.warning,
danger: danger ?? this.danger,
@ -152,7 +164,9 @@ class AppPalette extends ThemeExtension<AppPalette> {
stroke: Color.lerp(stroke, other.stroke, t) ?? stroke,
strokeSoft: Color.lerp(strokeSoft, other.strokeSoft, t) ?? strokeSoft,
accent: Color.lerp(accent, other.accent, t) ?? accent,
accentHover: Color.lerp(accentHover, other.accentHover, t) ?? accentHover,
accentMuted: Color.lerp(accentMuted, other.accentMuted, t) ?? accentMuted,
idle: Color.lerp(idle, other.idle, t) ?? idle,
success: Color.lerp(success, other.success, t) ?? success,
warning: Color.lerp(warning, other.warning, t) ?? warning,
danger: Color.lerp(danger, other.danger, t) ?? danger,

View File

@ -1,3 +1,4 @@
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'app_palette.dart';
@ -13,6 +14,11 @@ class AppTheme {
required Brightness brightness,
required AppPalette palette,
}) {
final platform = defaultTargetPlatform;
final isDesktop =
platform == TargetPlatform.macOS ||
platform == TargetPlatform.windows ||
platform == TargetPlatform.linux;
final colorScheme =
ColorScheme.fromSeed(
seedColor: palette.accent,
@ -43,47 +49,22 @@ class AppTheme {
final base = ThemeData(
useMaterial3: true,
brightness: brightness,
typography: Typography.material2021(platform: platform),
colorScheme: colorScheme,
scaffoldBackgroundColor: palette.canvas,
extensions: [palette],
);
final tunedTextTheme = _textTheme(
base.textTheme,
palette: palette,
isDesktop: isDesktop,
);
return base.copyWith(
splashFactory: NoSplash.splashFactory,
dividerColor: palette.strokeSoft,
hoverColor: palette.hover,
textTheme: base.textTheme.copyWith(
displaySmall: base.textTheme.displaySmall?.copyWith(
fontWeight: FontWeight.w600,
letterSpacing: -0.9,
),
headlineSmall: base.textTheme.headlineSmall?.copyWith(
fontWeight: FontWeight.w600,
letterSpacing: -0.5,
),
titleLarge: base.textTheme.titleLarge?.copyWith(
fontWeight: FontWeight.w600,
letterSpacing: -0.2,
),
titleMedium: base.textTheme.titleMedium?.copyWith(
fontWeight: FontWeight.w600,
),
bodyLarge: base.textTheme.bodyLarge?.copyWith(
height: 1.45,
color: palette.textPrimary,
),
bodyMedium: base.textTheme.bodyMedium?.copyWith(
height: 1.4,
color: palette.textSecondary,
),
bodySmall: base.textTheme.bodySmall?.copyWith(
height: 1.35,
color: palette.textMuted,
),
labelLarge: base.textTheme.labelLarge?.copyWith(
fontWeight: FontWeight.w600,
),
),
textTheme: tunedTextTheme,
appBarTheme: const AppBarTheme(
backgroundColor: Colors.transparent,
elevation: 0,
@ -121,10 +102,12 @@ class AppTheme {
inputDecorationTheme: InputDecorationTheme(
filled: true,
fillColor: palette.surfaceSecondary,
hintStyle: TextStyle(color: palette.textMuted),
hintStyle: tunedTextTheme.bodyMedium?.copyWith(
color: palette.textMuted,
),
contentPadding: const EdgeInsets.symmetric(
horizontal: 18,
vertical: 18,
horizontal: 16,
vertical: 16,
),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(16),
@ -170,4 +153,60 @@ class AppTheme {
),
);
}
static TextTheme _textTheme(
TextTheme base, {
required AppPalette palette,
required bool isDesktop,
}) {
return base.copyWith(
displaySmall: base.displaySmall?.copyWith(
fontSize: isDesktop ? 32 : 34,
fontWeight: FontWeight.w600,
letterSpacing: -0.9,
),
headlineSmall: base.headlineSmall?.copyWith(
fontSize: isDesktop ? 22 : 24,
fontWeight: FontWeight.w600,
letterSpacing: -0.45,
),
titleLarge: base.titleLarge?.copyWith(
fontSize: isDesktop ? 18 : 20,
fontWeight: FontWeight.w600,
letterSpacing: -0.2,
),
titleMedium: base.titleMedium?.copyWith(
fontSize: isDesktop ? 15 : 16,
fontWeight: FontWeight.w600,
),
titleSmall: base.titleSmall?.copyWith(
fontSize: isDesktop ? 13 : 14,
fontWeight: FontWeight.w600,
),
bodyLarge: base.bodyLarge?.copyWith(
fontSize: isDesktop ? 14 : 15,
height: 1.45,
color: palette.textPrimary,
),
bodyMedium: base.bodyMedium?.copyWith(
fontSize: isDesktop ? 13 : 14,
height: 1.4,
color: palette.textSecondary,
),
bodySmall: base.bodySmall?.copyWith(
fontSize: isDesktop ? 12 : 12,
height: 1.35,
color: palette.textMuted,
),
labelLarge: base.labelLarge?.copyWith(
fontSize: isDesktop ? 13 : 14,
fontWeight: FontWeight.w600,
),
labelMedium: base.labelMedium?.copyWith(
fontSize: isDesktop ? 12 : 12,
fontWeight: FontWeight.w600,
),
labelSmall: base.labelSmall?.copyWith(fontSize: isDesktop ? 11 : 11),
);
}
}

View File

@ -6,9 +6,9 @@ class SurfaceCard extends StatefulWidget {
const SurfaceCard({
super.key,
required this.child,
this.padding = const EdgeInsets.all(20),
this.padding = const EdgeInsets.all(16),
this.onTap,
this.borderRadius = 20,
this.borderRadius = 16,
this.color,
});
@ -43,8 +43,8 @@ class _SurfaceCardState extends State<SurfaceCard> {
boxShadow: [
BoxShadow(
color: palette.shadow.withValues(alpha: _hovered ? 0.12 : 0.07),
blurRadius: _hovered ? 22 : 16,
offset: const Offset(0, 10),
blurRadius: _hovered ? 12 : 8,
offset: const Offset(0, 4),
),
],
),

View File

@ -23,12 +23,9 @@ class TopBar extends StatelessWidget {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(title, style: Theme.of(context).textTheme.headlineSmall),
const SizedBox(height: 8),
const SizedBox(height: 6),
Text(subtitle, style: Theme.of(context).textTheme.bodyLarge),
if (trailing != null) ...[
const SizedBox(height: 16),
trailing!,
],
if (trailing != null) ...[const SizedBox(height: 12), trailing!],
],
);
}
@ -41,13 +38,13 @@ class TopBar extends StatelessWidget {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(title, style: Theme.of(context).textTheme.headlineSmall),
const SizedBox(height: 8),
const SizedBox(height: 6),
Text(subtitle, style: Theme.of(context).textTheme.bodyLarge),
],
),
),
if (trailing != null) ...[
const SizedBox(width: 24),
const SizedBox(width: 16),
Flexible(child: trailing!),
],
],