diff --git a/README.md b/README.md
index ea8aaf0..a24fedf 100644
--- a/README.md
+++ b/README.md
@@ -4,7 +4,7 @@ Luban Imager 是一个基于 Flutter 的一键图片压缩 App,目标是把优
## 截图
-
+
## 致谢
@@ -17,6 +17,7 @@ Luban Imager 是一个基于 Flutter 的一键图片压缩 App,目标是把优
- 一键压缩:选择图片后自动执行 Luban 风格压缩,不需要手动填写质量、尺寸等参数。
- 多入口导入:支持从 App 内选择图片,也支持从系统分享菜单接收图片。
- 压缩结果预览:展示原图、压缩图、前后对比三种视图,并显示尺寸、文件大小和节省比例。
+- 暗黑模式:支持跟随系统设置自动切换浅色/暗黑模式。
- 安全回退:如果压缩结果比原图更大,会自动使用原图,避免负优化。
- 分享导出:压缩完成后可通过系统分享面板导出压缩图片。
- 原位覆盖:对于系统授权可写入的图片来源,支持覆盖原图;覆盖前会进行两次确认。
diff --git a/docs/images/luban-imager-screenshot.jpg b/docs/images/luban-imager-screenshot.jpg
deleted file mode 100644
index b354ee0..0000000
Binary files a/docs/images/luban-imager-screenshot.jpg and /dev/null differ
diff --git a/docs/images/luban-imager-screenshot.webp b/docs/images/luban-imager-screenshot.webp
new file mode 100644
index 0000000..c52924b
Binary files /dev/null and b/docs/images/luban-imager-screenshot.webp differ
diff --git a/lib/main.dart b/lib/main.dart
index 67698e2..2cd9363 100644
--- a/lib/main.dart
+++ b/lib/main.dart
@@ -49,25 +49,65 @@ class _LubanImagerAppState extends State
return MaterialApp(
debugShowCheckedModeBanner: false,
title: _appName,
- theme: ThemeData(
- useMaterial3: true,
- colorScheme: ColorScheme.fromSeed(
- seedColor: const Color(0xFF2F6F73),
- brightness: Brightness.light,
- ),
- scaffoldBackgroundColor: const Color(0xFFF7F8F6),
- appBarTheme: const AppBarTheme(
- centerTitle: false,
- backgroundColor: Color(0xFFF7F8F6),
- foregroundColor: Color(0xFF172122),
- elevation: 0,
- ),
- ),
+ theme: AppTheme.light(),
+ darkTheme: AppTheme.dark(),
+ themeMode: ThemeMode.system,
home: ImagerHomePage(appName: _appName),
);
}
}
+class AppTheme {
+ static const _seedColor = Color(0xFF2F6F73);
+ static const _lightBackground = Color(0xFFF7F8F6);
+ static const _darkBackground = Color(0xFF101817);
+
+ static ThemeData light() {
+ final colorScheme = ColorScheme.fromSeed(
+ seedColor: _seedColor,
+ brightness: Brightness.light,
+ );
+ return _build(
+ colorScheme: colorScheme,
+ scaffoldBackgroundColor: _lightBackground,
+ systemOverlayStyle: SystemUiOverlayStyle.dark,
+ );
+ }
+
+ static ThemeData dark() {
+ final colorScheme = ColorScheme.fromSeed(
+ seedColor: _seedColor,
+ brightness: Brightness.dark,
+ );
+ return _build(
+ colorScheme: colorScheme,
+ scaffoldBackgroundColor: _darkBackground,
+ systemOverlayStyle: SystemUiOverlayStyle.light,
+ );
+ }
+
+ static ThemeData _build({
+ required ColorScheme colorScheme,
+ required Color scaffoldBackgroundColor,
+ required SystemUiOverlayStyle systemOverlayStyle,
+ }) {
+ return ThemeData(
+ useMaterial3: true,
+ colorScheme: colorScheme,
+ scaffoldBackgroundColor: scaffoldBackgroundColor,
+ canvasColor: scaffoldBackgroundColor,
+ appBarTheme: AppBarTheme(
+ centerTitle: false,
+ backgroundColor: scaffoldBackgroundColor,
+ foregroundColor: colorScheme.onSurface,
+ surfaceTintColor: Colors.transparent,
+ elevation: 0,
+ systemOverlayStyle: systemOverlayStyle,
+ ),
+ );
+ }
+}
+
class AppName {
static const english = 'Luban Imager';
static const chinese = '鲁班压图';
@@ -529,16 +569,18 @@ class _ImagerHomePageState extends State {
}
Widget _buildEmptyState() {
+ final colorScheme = Theme.of(context).colorScheme;
+
return Center(
child: Padding(
padding: const EdgeInsets.all(32),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
- const Icon(
+ Icon(
Icons.image_search_outlined,
size: 72,
- color: Color(0xFF5D6F70),
+ color: colorScheme.outline,
),
const SizedBox(height: 20),
Text(
@@ -575,6 +617,7 @@ class _ImagerHomePageState extends State {
final compressed = job.compressed;
final canCompare = compressed != null;
final mode = canCompare ? _previewMode : PreviewMode.original;
+ final colorScheme = Theme.of(context).colorScheme;
return LayoutBuilder(
builder: (context, constraints) {
@@ -691,12 +734,12 @@ class _ImagerHomePageState extends State {
child: Container(
clipBehavior: Clip.antiAlias,
decoration: BoxDecoration(
- color: const Color(0xFFEDEFEA),
+ color: colorScheme.surfaceContainerHighest,
borderRadius: BorderRadius.circular(8),
),
foregroundDecoration: BoxDecoration(
borderRadius: BorderRadius.circular(8),
- border: Border.all(color: const Color(0xFFDADFD8)),
+ border: Border.all(color: colorScheme.outlineVariant),
),
child: _buildImagePreview(job, mode),
),
@@ -964,12 +1007,14 @@ class _MetricBlock extends StatelessWidget {
@override
Widget build(BuildContext context) {
+ final colorScheme = Theme.of(context).colorScheme;
+
return Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
- color: Colors.white,
+ color: colorScheme.surface,
borderRadius: BorderRadius.circular(8),
- border: Border.all(color: const Color(0xFFDADFD8)),
+ border: Border.all(color: colorScheme.outlineVariant),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
@@ -978,9 +1023,9 @@ class _MetricBlock extends StatelessWidget {
label,
maxLines: 1,
overflow: TextOverflow.ellipsis,
- style: Theme.of(
- context,
- ).textTheme.labelMedium?.copyWith(color: const Color(0xFF5D6F70)),
+ style: Theme.of(context).textTheme.labelMedium?.copyWith(
+ color: colorScheme.onSurfaceVariant,
+ ),
),
const SizedBox(height: 7),
Text(