738 lines
19 KiB
Markdown
738 lines
19 KiB
Markdown
# Basic UIs 通用UI组件集合模块
|
||
|
||
## 模块概述
|
||
|
||
`basic_uis` 是 OneApp 基础UI模块群中的通用UI组件集合模块,负责整合和统一管理所有的UI组件库。该模块提供了一站式的UI组件解决方案,包含了动画效果、权限管理、分享功能、地图集成等丰富的UI功能组件。
|
||
|
||
### 基本信息
|
||
- **模块名称**: basic_uis
|
||
- **版本**: 0.0.7
|
||
- **描述**: 通用UI组件集合包
|
||
- **Flutter 版本**: >=1.17.0
|
||
- **Dart 版本**: >=3.0.0 <4.0.0
|
||
|
||
## 功能特性
|
||
|
||
### 核心功能
|
||
1. **UI组件整合**
|
||
- 统一的UI组件导出
|
||
- 组件库版本管理
|
||
- 跨模块组件协调
|
||
- 全局UI配置管理
|
||
|
||
2. **高级UI组件**
|
||
- 下拉刷新组件集成
|
||
- Lottie动画支持
|
||
- WebView组件封装
|
||
- 地图视图集成
|
||
|
||
3. **用户交互组件**
|
||
- 权限管理UI组件
|
||
- 分享功能UI集成
|
||
- 图片保存组件
|
||
- 设置页面组件
|
||
|
||
4. **系统集成组件**
|
||
- 位置服务UI
|
||
- 应用设置界面
|
||
- 用户同意组件
|
||
- Toast消息提示
|
||
|
||
## 技术架构
|
||
|
||
### 目录结构
|
||
```
|
||
lib/
|
||
├── basic_uis.dart # 模块入口文件
|
||
├── src/ # 源代码目录
|
||
│ ├── components/ # 通用组件
|
||
│ ├── animations/ # 动画组件
|
||
│ ├── permissions/ # 权限组件
|
||
│ ├── sharing/ # 分享组件
|
||
│ ├── webview/ # WebView组件
|
||
│ ├── location/ # 位置组件
|
||
│ ├── settings/ # 设置组件
|
||
│ └── utils/ # 工具类
|
||
├── widgets/ # Widget导出
|
||
└── themes/ # 主题配置
|
||
```
|
||
|
||
### 依赖关系
|
||
|
||
#### UI框架依赖
|
||
- `easy_refresh: ^3.0.5` - 下拉刷新组件
|
||
- `lottie: ^3.1.0` - Lottie动画
|
||
- `fluttertoast: ^8.2.2` - Toast提示
|
||
- `helpers: ^1.2.3+1` - 辅助工具
|
||
|
||
#### 功能组件依赖
|
||
- `basic_webview: ^0.2.4+4` - WebView组件
|
||
- `permission_handler: ^10.4.5` - 权限管理
|
||
- `image_gallery_saver: any` - 图片保存
|
||
- `share_plus: 7.2.1` - 分享功能
|
||
- `flutter_inappwebview: ^6.0.0` - 应用内WebView
|
||
|
||
#### 业务组件依赖
|
||
- `app_consent: ^0.2.19` - 用户同意组件
|
||
- `ui_mapview: ^0.2.18` - 地图视图组件
|
||
- `app_settings: ^4.3.1` - 应用设置
|
||
- `location: ^6.0.1` - 位置服务
|
||
|
||
#### 内部依赖
|
||
- `basic_utils` - 基础工具(本地路径)
|
||
|
||
## 核心组件分析
|
||
|
||
### 1. 模块入口 (`basic_uis.dart`)
|
||
|
||
**功能职责**:
|
||
- 统一导出所有UI组件
|
||
- 初始化全局UI配置
|
||
- 管理组件生命周期
|
||
|
||
```dart
|
||
library basic_uis;
|
||
|
||
// 基础组件导出
|
||
export 'src/components/enhanced_refresh_indicator.dart';
|
||
export 'src/components/lottie_animation_widget.dart';
|
||
export 'src/components/permission_request_dialog.dart';
|
||
export 'src/components/share_action_sheet.dart';
|
||
|
||
// WebView组件导出
|
||
export 'src/webview/enhanced_webview.dart';
|
||
export 'src/webview/webview_controller.dart';
|
||
|
||
// 位置组件导出
|
||
export 'src/location/location_picker_widget.dart';
|
||
export 'src/location/map_integration_widget.dart';
|
||
|
||
// 设置组件导出
|
||
export 'src/settings/app_settings_page.dart';
|
||
export 'src/settings/permission_settings_widget.dart';
|
||
|
||
// 工具类导出
|
||
export 'src/utils/ui_helpers.dart';
|
||
export 'src/utils/toast_utils.dart';
|
||
|
||
class BasicUIs {
|
||
static bool _isInitialized = false;
|
||
|
||
/// 初始化Basic UIs模块
|
||
static Future<void> initialize() async {
|
||
if (_isInitialized) return;
|
||
|
||
// 初始化Toast配置
|
||
await _initializeToast();
|
||
|
||
// 初始化权限处理器
|
||
await _initializePermissions();
|
||
|
||
// 初始化分享服务
|
||
await _initializeShare();
|
||
|
||
// 初始化WebView配置
|
||
await _initializeWebView();
|
||
|
||
_isInitialized = true;
|
||
}
|
||
}
|
||
```
|
||
|
||
### 2. 增强下拉刷新组件 (`src/components/enhanced_refresh_indicator.dart`)
|
||
|
||
```dart
|
||
class EnhancedRefreshIndicator extends StatefulWidget {
|
||
final Widget child;
|
||
final Future<void> Function() onRefresh;
|
||
final Future<void> Function()? onLoadMore;
|
||
final RefreshIndicatorConfig? config;
|
||
final bool enablePullUp;
|
||
final bool enablePullDown;
|
||
|
||
const EnhancedRefreshIndicator({
|
||
Key? key,
|
||
required this.child,
|
||
required this.onRefresh,
|
||
this.onLoadMore,
|
||
this.config,
|
||
this.enablePullUp = true,
|
||
this.enablePullDown = true,
|
||
}) : super(key: key);
|
||
|
||
@override
|
||
State<EnhancedRefreshIndicator> createState() => _EnhancedRefreshIndicatorState();
|
||
}
|
||
|
||
class _EnhancedRefreshIndicatorState extends State<EnhancedRefreshIndicator> {
|
||
late EasyRefreshController _controller;
|
||
|
||
@override
|
||
Widget build(BuildContext context) {
|
||
return EasyRefresh(
|
||
controller: _controller,
|
||
onRefresh: widget.enablePullDown ? _onRefresh : null,
|
||
onLoad: widget.enablePullUp && widget.onLoadMore != null ? _onLoad : null,
|
||
header: _buildRefreshHeader(),
|
||
footer: _buildLoadFooter(),
|
||
child: widget.child,
|
||
);
|
||
}
|
||
|
||
Widget _buildRefreshHeader() {
|
||
return ClassicHeader(
|
||
dragText: '下拉刷新',
|
||
armedText: '释放刷新',
|
||
readyText: '正在刷新...',
|
||
processingText: '正在刷新...',
|
||
processedText: '刷新完成',
|
||
noMoreText: '没有更多数据',
|
||
failedText: '刷新失败',
|
||
messageText: '最后更新于 %T',
|
||
);
|
||
}
|
||
|
||
Widget _buildLoadFooter() {
|
||
return ClassicFooter(
|
||
dragText: '上拉加载',
|
||
armedText: '释放加载',
|
||
readyText: '正在加载...',
|
||
processingText: '正在加载...',
|
||
processedText: '加载完成',
|
||
noMoreText: '没有更多数据',
|
||
failedText: '加载失败',
|
||
);
|
||
}
|
||
}
|
||
```
|
||
|
||
### 3. Lottie动画组件 (`src/animations/lottie_animation_widget.dart`)
|
||
|
||
```dart
|
||
class LottieAnimationWidget extends StatefulWidget {
|
||
final String assetPath;
|
||
final double? width;
|
||
final double? height;
|
||
final bool repeat;
|
||
final bool autoPlay;
|
||
final AnimationController? controller;
|
||
final VoidCallback? onComplete;
|
||
|
||
const LottieAnimationWidget({
|
||
Key? key,
|
||
required this.assetPath,
|
||
this.width,
|
||
this.height,
|
||
this.repeat = true,
|
||
this.autoPlay = true,
|
||
this.controller,
|
||
this.onComplete,
|
||
}) : super(key: key);
|
||
|
||
@override
|
||
State<LottieAnimationWidget> createState() => _LottieAnimationWidgetState();
|
||
}
|
||
|
||
class _LottieAnimationWidgetState extends State<LottieAnimationWidget>
|
||
with TickerProviderStateMixin {
|
||
late AnimationController _animationController;
|
||
|
||
@override
|
||
void initState() {
|
||
super.initState();
|
||
|
||
_animationController = widget.controller ??
|
||
AnimationController(vsync: this);
|
||
|
||
if (widget.autoPlay) {
|
||
_animationController.forward();
|
||
}
|
||
|
||
_animationController.addStatusListener((status) {
|
||
if (status == AnimationStatus.completed) {
|
||
widget.onComplete?.call();
|
||
if (widget.repeat) {
|
||
_animationController.repeat();
|
||
}
|
||
}
|
||
});
|
||
}
|
||
|
||
@override
|
||
Widget build(BuildContext context) {
|
||
return Lottie.asset(
|
||
widget.assetPath,
|
||
width: widget.width,
|
||
height: widget.height,
|
||
controller: _animationController,
|
||
onLoaded: (composition) {
|
||
_animationController.duration = composition.duration;
|
||
if (widget.autoPlay) {
|
||
_animationController.forward();
|
||
}
|
||
},
|
||
);
|
||
}
|
||
|
||
@override
|
||
void dispose() {
|
||
if (widget.controller == null) {
|
||
_animationController.dispose();
|
||
}
|
||
super.dispose();
|
||
}
|
||
}
|
||
```
|
||
|
||
### 4. 权限请求对话框 (`src/permissions/permission_request_dialog.dart`)
|
||
|
||
```dart
|
||
class PermissionRequestDialog extends StatelessWidget {
|
||
final Permission permission;
|
||
final String title;
|
||
final String description;
|
||
final String? rationale;
|
||
final VoidCallback? onGranted;
|
||
final VoidCallback? onDenied;
|
||
|
||
const PermissionRequestDialog({
|
||
Key? key,
|
||
required this.permission,
|
||
required this.title,
|
||
required this.description,
|
||
this.rationale,
|
||
this.onGranted,
|
||
this.onDenied,
|
||
}) : super(key: key);
|
||
|
||
@override
|
||
Widget build(BuildContext context) {
|
||
return AlertDialog(
|
||
title: Row(
|
||
children: [
|
||
Icon(_getPermissionIcon(), color: Theme.of(context).primaryColor),
|
||
const SizedBox(width: 12),
|
||
Expanded(child: Text(title)),
|
||
],
|
||
),
|
||
content: Column(
|
||
mainAxisSize: MainAxisSize.min,
|
||
crossAxisAlignment: CrossAxisAlignment.start,
|
||
children: [
|
||
Text(description),
|
||
if (rationale != null) ...[
|
||
const SizedBox(height: 16),
|
||
Container(
|
||
padding: const EdgeInsets.all(12),
|
||
decoration: BoxDecoration(
|
||
color: Colors.blue.withOpacity(0.1),
|
||
borderRadius: BorderRadius.circular(8),
|
||
),
|
||
child: Row(
|
||
children: [
|
||
Icon(Icons.info_outline,
|
||
color: Colors.blue, size: 20),
|
||
const SizedBox(width: 8),
|
||
Expanded(
|
||
child: Text(
|
||
rationale!,
|
||
style: TextStyle(
|
||
color: Colors.blue[700],
|
||
fontSize: 14,
|
||
),
|
||
),
|
||
),
|
||
],
|
||
),
|
||
),
|
||
],
|
||
],
|
||
),
|
||
actions: [
|
||
TextButton(
|
||
onPressed: () {
|
||
Navigator.of(context).pop();
|
||
onDenied?.call();
|
||
},
|
||
child: const Text('拒绝'),
|
||
),
|
||
ElevatedButton(
|
||
onPressed: () async {
|
||
Navigator.of(context).pop();
|
||
final status = await permission.request();
|
||
if (status.isGranted) {
|
||
onGranted?.call();
|
||
} else {
|
||
onDenied?.call();
|
||
if (status.isPermanentlyDenied) {
|
||
_showSettingsDialog(context);
|
||
}
|
||
}
|
||
},
|
||
child: const Text('允许'),
|
||
),
|
||
],
|
||
);
|
||
}
|
||
|
||
IconData _getPermissionIcon() {
|
||
switch (permission) {
|
||
case Permission.camera:
|
||
return Icons.camera_alt;
|
||
case Permission.microphone:
|
||
return Icons.mic;
|
||
case Permission.location:
|
||
return Icons.location_on;
|
||
case Permission.storage:
|
||
return Icons.storage;
|
||
case Permission.photos:
|
||
return Icons.photo_library;
|
||
default:
|
||
return Icons.security;
|
||
}
|
||
}
|
||
|
||
void _showSettingsDialog(BuildContext context) {
|
||
showDialog(
|
||
context: context,
|
||
builder: (context) => AlertDialog(
|
||
title: const Text('权限设置'),
|
||
content: const Text('请在设置中手动开启所需权限'),
|
||
actions: [
|
||
TextButton(
|
||
onPressed: () => Navigator.of(context).pop(),
|
||
child: const Text('取消'),
|
||
),
|
||
ElevatedButton(
|
||
onPressed: () {
|
||
Navigator.of(context).pop();
|
||
openAppSettings();
|
||
},
|
||
child: const Text('去设置'),
|
||
),
|
||
],
|
||
),
|
||
);
|
||
}
|
||
}
|
||
```
|
||
|
||
### 5. 分享操作表 (`src/sharing/share_action_sheet.dart`)
|
||
|
||
```dart
|
||
class ShareActionSheet extends StatelessWidget {
|
||
final ShareContent content;
|
||
final List<SharePlatform> platforms;
|
||
final Function(SharePlatform)? onPlatformSelected;
|
||
|
||
const ShareActionSheet({
|
||
Key? key,
|
||
required this.content,
|
||
required this.platforms,
|
||
this.onPlatformSelected,
|
||
}) : super(key: key);
|
||
|
||
@override
|
||
Widget build(BuildContext context) {
|
||
return Container(
|
||
padding: const EdgeInsets.all(20),
|
||
decoration: const BoxDecoration(
|
||
color: Colors.white,
|
||
borderRadius: BorderRadius.vertical(top: Radius.circular(20)),
|
||
),
|
||
child: Column(
|
||
mainAxisSize: MainAxisSize.min,
|
||
children: [
|
||
_buildHandle(),
|
||
const SizedBox(height: 20),
|
||
Text(
|
||
'分享到',
|
||
style: Theme.of(context).textTheme.titleMedium,
|
||
),
|
||
const SizedBox(height: 20),
|
||
_buildPlatformGrid(context),
|
||
const SizedBox(height: 20),
|
||
_buildMoreActions(context),
|
||
],
|
||
),
|
||
);
|
||
}
|
||
|
||
Widget _buildPlatformGrid(BuildContext context) {
|
||
return GridView.builder(
|
||
shrinkWrap: true,
|
||
physics: const NeverScrollableScrollPhysics(),
|
||
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
|
||
crossAxisCount: 4,
|
||
childAspectRatio: 1,
|
||
crossAxisSpacing: 20,
|
||
mainAxisSpacing: 20,
|
||
),
|
||
itemCount: platforms.length,
|
||
itemBuilder: (context, index) {
|
||
final platform = platforms[index];
|
||
return _buildPlatformItem(context, platform);
|
||
},
|
||
);
|
||
}
|
||
|
||
Widget _buildPlatformItem(BuildContext context, SharePlatform platform) {
|
||
return GestureDetector(
|
||
onTap: () {
|
||
Navigator.of(context).pop();
|
||
onPlatformSelected?.call(platform);
|
||
_shareToplatform(platform);
|
||
},
|
||
child: Column(
|
||
children: [
|
||
Container(
|
||
width: 60,
|
||
height: 60,
|
||
decoration: BoxDecoration(
|
||
color: platform.color.withOpacity(0.1),
|
||
borderRadius: BorderRadius.circular(12),
|
||
),
|
||
child: Icon(
|
||
platform.icon,
|
||
color: platform.color,
|
||
size: 30,
|
||
),
|
||
),
|
||
const SizedBox(height: 8),
|
||
Text(
|
||
platform.name,
|
||
style: Theme.of(context).textTheme.bodySmall,
|
||
textAlign: TextAlign.center,
|
||
),
|
||
],
|
||
),
|
||
);
|
||
}
|
||
|
||
Future<void> _shareToplatform(SharePlatform platform) async {
|
||
try {
|
||
switch (platform.type) {
|
||
case SharePlatformType.system:
|
||
await Share.share(content.text, subject: content.title);
|
||
break;
|
||
case SharePlatformType.wechat:
|
||
// 微信分享逻辑
|
||
break;
|
||
case SharePlatformType.weibo:
|
||
// 微博分享逻辑
|
||
break;
|
||
case SharePlatformType.qq:
|
||
// QQ分享逻辑
|
||
break;
|
||
}
|
||
} catch (e) {
|
||
ToastUtils.showError('分享失败: $e');
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
## 工具类
|
||
|
||
### Toast工具类 (`src/utils/toast_utils.dart`)
|
||
|
||
```dart
|
||
class ToastUtils {
|
||
static void showSuccess(String message) {
|
||
Fluttertoast.showToast(
|
||
msg: message,
|
||
toastLength: Toast.LENGTH_SHORT,
|
||
gravity: ToastGravity.CENTER,
|
||
backgroundColor: Colors.green,
|
||
textColor: Colors.white,
|
||
fontSize: 16.0,
|
||
);
|
||
}
|
||
|
||
static void showError(String message) {
|
||
Fluttertoast.showToast(
|
||
msg: message,
|
||
toastLength: Toast.LENGTH_SHORT,
|
||
gravity: ToastGravity.CENTER,
|
||
backgroundColor: Colors.red,
|
||
textColor: Colors.white,
|
||
fontSize: 16.0,
|
||
);
|
||
}
|
||
|
||
static void showInfo(String message) {
|
||
Fluttertoast.showToast(
|
||
msg: message,
|
||
toastLength: Toast.LENGTH_SHORT,
|
||
gravity: ToastGravity.CENTER,
|
||
backgroundColor: Colors.blue,
|
||
textColor: Colors.white,
|
||
fontSize: 16.0,
|
||
);
|
||
}
|
||
|
||
static void showWarning(String message) {
|
||
Fluttertoast.showToast(
|
||
msg: message,
|
||
toastLength: Toast.LENGTH_SHORT,
|
||
gravity: ToastGravity.CENTER,
|
||
backgroundColor: Colors.orange,
|
||
textColor: Colors.white,
|
||
fontSize: 16.0,
|
||
);
|
||
}
|
||
}
|
||
```
|
||
|
||
## 使用示例
|
||
|
||
### 基础使用
|
||
```dart
|
||
class BasicUIsExample extends StatefulWidget {
|
||
@override
|
||
_BasicUIsExampleState createState() => _BasicUIsExampleState();
|
||
}
|
||
|
||
class _BasicUIsExampleState extends State<BasicUIsExample> {
|
||
@override
|
||
void initState() {
|
||
super.initState();
|
||
_initializeBasicUIs();
|
||
}
|
||
|
||
Future<void> _initializeBasicUIs() async {
|
||
await BasicUIs.initialize();
|
||
}
|
||
|
||
@override
|
||
Widget build(BuildContext context) {
|
||
return Scaffold(
|
||
appBar: AppBar(title: Text('Basic UIs Example')),
|
||
body: EnhancedRefreshIndicator(
|
||
onRefresh: _handleRefresh,
|
||
onLoadMore: _handleLoadMore,
|
||
child: ListView(
|
||
children: [
|
||
// Lottie动画示例
|
||
LottieAnimationWidget(
|
||
assetPath: 'assets/animations/loading.json',
|
||
width: 100,
|
||
height: 100,
|
||
),
|
||
|
||
// 权限请求示例
|
||
ListTile(
|
||
title: Text('请求相机权限'),
|
||
onTap: () => _requestCameraPermission(),
|
||
),
|
||
|
||
// 分享功能示例
|
||
ListTile(
|
||
title: Text('分享内容'),
|
||
onTap: () => _showShareSheet(),
|
||
),
|
||
|
||
// Toast示例
|
||
ListTile(
|
||
title: Text('显示Toast'),
|
||
onTap: () => ToastUtils.showSuccess('操作成功'),
|
||
),
|
||
],
|
||
),
|
||
),
|
||
);
|
||
}
|
||
|
||
Future<void> _handleRefresh() async {
|
||
await Future.delayed(Duration(seconds: 2));
|
||
ToastUtils.showSuccess('刷新完成');
|
||
}
|
||
|
||
Future<void> _handleLoadMore() async {
|
||
await Future.delayed(Duration(seconds: 1));
|
||
}
|
||
|
||
void _requestCameraPermission() {
|
||
showDialog(
|
||
context: context,
|
||
builder: (context) => PermissionRequestDialog(
|
||
permission: Permission.camera,
|
||
title: '相机权限',
|
||
description: '需要相机权限来拍照和录制视频',
|
||
rationale: '此权限用于扫描二维码和拍照功能',
|
||
onGranted: () => ToastUtils.showSuccess('权限已授予'),
|
||
onDenied: () => ToastUtils.showError('权限被拒绝'),
|
||
),
|
||
);
|
||
}
|
||
|
||
void _showShareSheet() {
|
||
showModalBottomSheet(
|
||
context: context,
|
||
builder: (context) => ShareActionSheet(
|
||
content: ShareContent(
|
||
title: 'OneApp',
|
||
text: '快来体验OneApp的强大功能!',
|
||
url: 'https://oneapp.com',
|
||
),
|
||
platforms: [
|
||
SharePlatform.system(),
|
||
SharePlatform.wechat(),
|
||
SharePlatform.weibo(),
|
||
SharePlatform.qq(),
|
||
],
|
||
onPlatformSelected: (platform) {
|
||
ToastUtils.showInfo('分享到${platform.name}');
|
||
},
|
||
),
|
||
);
|
||
}
|
||
}
|
||
```
|
||
|
||
## 性能优化
|
||
|
||
### 组件优化
|
||
- **懒加载**: 按需加载重型组件
|
||
- **Widget缓存**: 缓存不变的Widget实例
|
||
- **动画优化**: 合理使用动画避免过度渲染
|
||
- **内存管理**: 及时释放资源和控制器
|
||
|
||
### 集成优化
|
||
- **依赖管理**: 避免重复依赖和版本冲突
|
||
- **包大小**: 优化资源文件减少包大小
|
||
- **初始化**: 异步初始化避免阻塞启动
|
||
- **缓存策略**: 合理使用缓存提升性能
|
||
|
||
## 测试策略
|
||
|
||
### Widget测试
|
||
- **组件渲染测试**: 验证组件正确渲染
|
||
- **交互测试**: 测试用户交互响应
|
||
- **动画测试**: 测试动画效果
|
||
- **权限测试**: 测试权限请求流程
|
||
|
||
### 集成测试
|
||
- **模块集成测试**: 测试模块间协作
|
||
- **第三方服务测试**: 测试外部服务集成
|
||
- **性能测试**: 测试组件性能表现
|
||
- **兼容性测试**: 测试平台兼容性
|
||
|
||
## 最佳实践
|
||
|
||
### 组件使用
|
||
1. **统一入口**: 通过BasicUIs统一初始化
|
||
2. **配置管理**: 集中管理组件配置
|
||
3. **错误处理**: 完善的错误处理机制
|
||
4. **用户体验**: 注重用户交互体验
|
||
|
||
### 开发规范
|
||
1. **代码规范**: 遵循统一的代码规范
|
||
2. **文档完善**: 提供详细的使用文档
|
||
3. **版本管理**: 合理的版本发布策略
|
||
4. **测试覆盖**: 保证充分的测试覆盖
|
||
|
||
## 总结
|
||
|
||
`basic_uis` 模块作为 OneApp 的通用UI组件集合,整合了丰富的UI功能组件和第三方服务,为应用开发提供了一站式的UI解决方案。通过统一的管理和标准化的接口,大大提升了开发效率和用户体验的一致性。模块具有良好的扩展性和可维护性,能够适应不断变化的UI需求。
|