Add system dark mode support
This commit is contained in:
@@ -4,7 +4,7 @@ Luban Imager 是一个基于 Flutter 的一键图片压缩 App,目标是把优
|
||||
|
||||
## 截图
|
||||
|
||||
<img src="docs/images/luban-imager-screenshot.jpg" alt="鲁班压图截图" width="720">
|
||||
<img src="docs/images/luban-imager-screenshot.webp" alt="鲁班压图浅色与暗黑模式截图" width="960">
|
||||
|
||||
## 致谢
|
||||
|
||||
@@ -17,6 +17,7 @@ Luban Imager 是一个基于 Flutter 的一键图片压缩 App,目标是把优
|
||||
- 一键压缩:选择图片后自动执行 Luban 风格压缩,不需要手动填写质量、尺寸等参数。
|
||||
- 多入口导入:支持从 App 内选择图片,也支持从系统分享菜单接收图片。
|
||||
- 压缩结果预览:展示原图、压缩图、前后对比三种视图,并显示尺寸、文件大小和节省比例。
|
||||
- 暗黑模式:支持跟随系统设置自动切换浅色/暗黑模式。
|
||||
- 安全回退:如果压缩结果比原图更大,会自动使用原图,避免负优化。
|
||||
- 分享导出:压缩完成后可通过系统分享面板导出压缩图片。
|
||||
- 原位覆盖:对于系统授权可写入的图片来源,支持覆盖原图;覆盖前会进行两次确认。
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 478 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 648 KiB |
+68
-23
@@ -49,25 +49,65 @@ class _LubanImagerAppState extends State<LubanImagerApp>
|
||||
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<ImagerHomePage> {
|
||||
}
|
||||
|
||||
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<ImagerHomePage> {
|
||||
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<ImagerHomePage> {
|
||||
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(
|
||||
|
||||
Reference in New Issue
Block a user