Files
CloudHome-Ops/lib/widgets/branding_editor.dart

174 lines
5.4 KiB
Dart
Raw Normal View History

import 'package:flutter/material.dart';
import 'package:image_picker/image_picker.dart';
/// 品牌配置编辑(启动图 + Logo)。
///
/// 通过 [ImagePicker] 选择本地图片,并用「手机外框」预览:
/// - 启动图为全屏背景;
/// - Logo 为右下角角标。
///
/// [splashUrl]/[logoUrl] 为本地图片路径(占位),由父级保存。
class BrandingEditor extends StatefulWidget {
const BrandingEditor({
super.key,
this.initialSplashUrl,
this.initialLogoUrl,
});
final String? initialSplashUrl;
final String? initialLogoUrl;
@override
State<BrandingEditor> createState() => BrandingEditorState();
}
/// 品牌编辑器状态(公开,供父级通过 GlobalKey 读取所选图片路径)。
class BrandingEditorState extends State<BrandingEditor> {
final ImagePicker _picker = ImagePicker();
String? _splashUrl;
String? _logoUrl;
@override
void initState() {
super.initState();
_splashUrl = widget.initialSplashUrl;
_logoUrl = widget.initialLogoUrl;
}
Future<void> _pickSplash() async {
final xFile = await _picker.pickImage(source: ImageSource.gallery);
if (xFile != null && mounted) {
setState(() => _splashUrl = xFile.path);
}
}
Future<void> _pickLogo() async {
final xFile = await _picker.pickImage(source: ImageSource.gallery);
if (xFile != null && mounted) {
setState(() => _logoUrl = xFile.path);
}
}
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Expanded(
child: FilledButton.tonal(
onPressed: _pickSplash,
child: const Text('选择启动图'),
),
),
const SizedBox(width: 12),
Expanded(
child: FilledButton.tonal(
onPressed: _pickLogo,
child: const Text('选择 Logo'),
),
),
],
),
const SizedBox(height: 12),
// 手机外框预览。
Center(
child: Container(
width: 200,
height: 380,
decoration: BoxDecoration(
color: Colors.black,
borderRadius: BorderRadius.circular(28),
border: Border.all(color: Colors.black87, width: 8),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.2),
blurRadius: 12,
offset: const Offset(0, 6),
),
],
),
clipBehavior: Clip.antiAlias,
child: Stack(
children: [
// 启动图全屏背景。
if (_splashUrl != null && _splashUrl!.isNotEmpty)
Positioned.fill(
child: Image.network(
_splashUrl!,
fit: BoxFit.cover,
errorBuilder: (_, __, ___) => Container(
color: theme.colorScheme.primaryContainer,
),
),
)
else
Positioned.fill(
child: Container(
color: theme.colorScheme.primaryContainer,
child: Center(
child: Text(
'启动图预览',
style: TextStyle(
color: theme.colorScheme.onPrimaryContainer,
),
),
),
),
),
// 顶部状态栏占位。
Positioned(
top: 8,
left: 0,
right: 0,
child: Container(
height: 18,
color: Colors.black.withOpacity(0.25),
),
),
// Logo 右下角角标。
if (_logoUrl != null && _logoUrl!.isNotEmpty)
Positioned(
right: 16,
bottom: 24,
child: Container(
width: 56,
height: 56,
decoration: const BoxDecoration(
color: Colors.white,
shape: BoxShape.circle,
boxShadow: [
BoxShadow(
color: Colors.black26,
blurRadius: 6,
offset: Offset(0, 2),
),
],
),
child: ClipOval(
child: Image.network(
_logoUrl!,
fit: BoxFit.cover,
errorBuilder: (_, __, ___) =>
const Icon(Icons.image, size: 28),
),
),
),
),
],
),
),
),
],
);
}
/// 当前启动图本地路径(未选返回 null)。
String? get splashUrl => _splashUrl;
/// 当前 Logo 本地路径(未选返回 null)。
String? get logoUrl => _logoUrl;
}