ops-console v1.0.2 (versionCode 8) 源码入库
This commit is contained in:
173
lib/widgets/branding_editor.dart
Normal file
173
lib/widgets/branding_editor.dart
Normal file
@@ -0,0 +1,173 @@
|
||||
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;
|
||||
}
|
||||
Reference in New Issue
Block a user