2 Commits

Author SHA1 Message Date
呈祥 e3b80af6ad Bump app version 2026-05-18 15:21:25 +08:00
呈祥 ac7372f848 Add system dark mode support 2026-05-18 15:18:05 +08:00
5 changed files with 71 additions and 25 deletions
+2 -1
View File
@@ -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
View File
@@ -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(
+1 -1
View File
@@ -16,7 +16,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
# In Windows, build-name is used as the major, minor, and patch parts
# of the product and file versions while build-number is used as the build suffix.
version: 1.0.0+1
version: 1.0.1+2
environment:
sdk: ^3.11.5