
隐私设置是应用中非常重要的一部分,用户可以在这里管理应用锁、私密相册、照片信息和数据导出等功能。一个完善的隐私设置能让用户更放心地使用应用。今天我们来实现这个功能。
设计思路
隐私设置页面采用分组列表的形式,把相关的设置项放在一起。包括安全设置、相册隐私、照片信息和数据管理四个部分。每个部分都有对应的开关或操作项。
创建页面结构
先搭建基本框架:
dart
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:provider/provider.dart';
import '../providers/settings_provider.dart';
class PrivacyScreen extends StatefulWidget {
const PrivacyScreen({super.key});
@override
State<PrivacyScreen> createState() => _PrivacyScreenState();
}
class _PrivacyScreenState extends State<PrivacyScreen> {
bool _appLock = false;
bool _hidePrivateAlbums = true;
bool _faceRecognition = true;
bool _locationInfo = true;
bool _biometricAuth = false;
@override
void initState() {
super.initState();
_loadSettings();
}
Future<void> _loadSettings() async {
final settings = context.read<SettingsProvider>();
setState(() {
_appLock = settings.appLock;
_hidePrivateAlbums = settings.hidePrivateAlbums;
_faceRecognition = settings.faceRecognition;
_locationInfo = settings.locationInfo;
_biometricAuth = settings.biometricAuth;
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('隐私设置'),
elevation: 0,
),
body: ListView(
children: [
_buildSecuritySection(),
_buildAlbumPrivacySection(),
_buildPhotoInfoSection(),
_buildDataManagementSection(),
],
),
);
}
}
用StatefulWidget来管理多个开关状态,initState里加载保存的设置。ListView包含四个分组,每个分组对应一类隐私设置。
安全设置分组
应用锁和生物识别相关的设置:
dart
Widget _buildSecuritySection() {
return _buildSection(
title: '安全',
children: [
_buildSwitchTile(
icon: Icons.lock_outlined,
iconColor: const Color(0xFFF44336),
title: '应用锁',
subtitle: '启动应用时需要验证',
value: _appLock,
onChanged: (value) {
setState(() => _appLock = value);
context.read<SettingsProvider>().setAppLock(value);
if (value && !_hasPassword()) {
_showSetPasswordDialog();
}
},
),
if (_appLock) ...[
_buildListTile(
icon: Icons.vpn_key_outlined,
iconColor: const Color(0xFF2196F3),
title: '修改密码',
subtitle: '修改应用锁密码',
onTap: () => _showChangePasswordDialog(),
),
_buildSwitchTile(
icon: Icons.fingerprint,
iconColor: const Color(0xFF4CAF50),
title: '生物识别',
subtitle: '使用指纹或面部识别',
value: _biometricAuth,
onChanged: (value) {
setState(() => _biometricAuth = value);
context.read<SettingsProvider>().setBiometricAuth(value);
},
),
],
],
);
}
bool _hasPassword() {
return context.read<SettingsProvider>().hasPassword;
}
应用锁开关打开时,如果还没设置密码就弹出设置密码对话框。开启应用锁后才显示修改密码和生物识别选项。
相册隐私分组
私密相册相关的设置:
dart
Widget _buildAlbumPrivacySection() {
return _buildSection(
title: '相册隐私',
children: [
_buildSwitchTile(
icon: Icons.visibility_off_outlined,
iconColor: const Color(0xFF9C27B0),
title: '隐藏私密相册',
subtitle: '私密相册不在主页显示',
value: _hidePrivateAlbums,
onChanged: (value) {
setState(() => _hidePrivateAlbums = value);
context.read<SettingsProvider>().setHidePrivateAlbums(value);
},
),
_buildListTile(
icon: Icons.folder_special_outlined,
iconColor: const Color(0xFFFF9800),
title: '管理私密相册',
subtitle: _getPrivateAlbumCount(),
onTap: () => _navigateToPrivateAlbums(),
),
],
);
}
String _getPrivateAlbumCount() {
final count = context.read<SettingsProvider>().privateAlbumCount;
return '$count 个私密相册';
}
隐藏私密相册的开关可以控制私密相册是否在主页显示。管理私密相册选项显示当前有多少个私密相册。
照片信息分组
照片识别和位置信息相关的设置:
dart
Widget _buildPhotoInfoSection() {
return _buildSection(
title: '照片信息',
children: [
_buildSwitchTile(
icon: Icons.face_outlined,
iconColor: const Color(0xFF00BCD4),
title: '人脸识别',
subtitle: '自动识别照片中的家人',
value: _faceRecognition,
onChanged: (value) {
setState(() => _faceRecognition = value);
context.read<SettingsProvider>().setFaceRecognition(value);
},
),
_buildSwitchTile(
icon: Icons.location_on_outlined,
iconColor: const Color(0xFF4CAF50),
title: '位置信息',
subtitle: '保存照片的拍摄位置',
value: _locationInfo,
onChanged: (value) {
setState(() => _locationInfo = value);
context.read<SettingsProvider>().setLocationInfo(value);
},
),
_buildListTile(
icon: Icons.info_outlined,
iconColor: const Color(0xFF3F51B5),
title: '照片元数据',
subtitle: '查看和管理照片的详细信息',
onTap: () => _showMetadataInfo(),
),
],
);
}
人脸识别开关控制是否自动识别照片中的家人。位置信息开关控制是否保存照片的拍摄位置。
数据管理分组
数据导出和账户删除等操作:
dart
Widget _buildDataManagementSection() {
return _buildSection(
title: '数据管理',
children: [
_buildListTile(
icon: Icons.download_outlined,
iconColor: const Color(0xFF009688),
title: '导出数据',
subtitle: '导出所有个人数据',
onTap: () => _showExportDialog(),
),
_buildListTile(
icon: Icons.history,
iconColor: const Color(0xFF795548),
title: '数据使用记录',
subtitle: '查看数据访问历史',
onTap: () => _showDataUsageHistory(),
),
_buildListTile(
icon: Icons.delete_forever_outlined,
iconColor: const Color(0xFFF44336),
title: '删除账户',
subtitle: '永久删除账户和所有数据',
onTap: () => _showDeleteAccountDialog(),
),
],
);
}
数据管理分组包含三个重要操作:导出数据、查看数据使用记录和删除账户。删除账户用红色图标表示这是危险操作。
通用组件方法
统一的分组和列表项样式:
dart
Widget _buildSection({
required String title,
required List<Widget> children,
}) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: EdgeInsets.fromLTRB(16.w, 20.h, 16.w, 8.h),
child: Text(
title,
style: TextStyle(
fontSize: 13.sp,
color: Colors.grey[600],
fontWeight: FontWeight.w600,
),
),
),
Container(
decoration: BoxDecoration(
color: Colors.white,
border: Border(
top: BorderSide(color: Colors.grey[200]!, width: 0.5),
bottom: BorderSide(color: Colors.grey[200]!, width: 0.5),
),
),
child: Column(children: children),
),
],
);
}
Widget _buildSwitchTile({
required IconData icon,
required Color iconColor,
required String title,
required String subtitle,
required bool value,
required ValueChanged<bool> onChanged,
}) {
return SwitchListTile(
secondary: Container(
padding: EdgeInsets.all(8.w),
decoration: BoxDecoration(
color: iconColor.withOpacity(0.1),
borderRadius: BorderRadius.circular(8.r),
),
child: Icon(icon, color: iconColor),
),
title: Text(title),
subtitle: Text(subtitle),
value: value,
onChanged: onChanged,
activeColor: const Color(0xFFE91E63),
);
}
Widget _buildListTile({
required IconData icon,
required Color iconColor,
required String title,
required String subtitle,
required VoidCallback onTap,
}) {
return ListTile(
leading: Container(
padding: EdgeInsets.all(8.w),
decoration: BoxDecoration(
color: iconColor.withOpacity(0.1),
borderRadius: BorderRadius.circular(8.r),
),
child: Icon(icon, color: iconColor),
),
title: Text(title),
subtitle: Text(subtitle),
trailing: const Icon(Icons.chevron_right, color: Colors.grey),
onTap: onTap,
);
}
提取通用的组件方法,减少重复代码。分组标题用小号灰色字体,设置项容器用白色背景加上下边框。
密码对话框
设置和修改密码的对话框:
dart
void _showSetPasswordDialog() {
final passwordController = TextEditingController();
final confirmController = TextEditingController();
showDialog(
context: context,
barrierDismissible: false,
builder: (dialogContext) => AlertDialog(
title: const Text('设置密码'),
content: Column(
mainAxisSize: MainAxisSize.min,
children: [
TextField(
controller: passwordController,
obscureText: true,
decoration: InputDecoration(
labelText: '输入密码',
hintText: '6-16位数字或字母',
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(8.r),
),
),
),
SizedBox(height: 12.h),
TextField(
controller: confirmController,
obscureText: true,
decoration: InputDecoration(
labelText: '确认密码',
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(8.r),
),
),
),
],
),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16.r),
),
actions: [
TextButton(
onPressed: () {
setState(() => _appLock = false);
Navigator.pop(dialogContext);
},
child: const Text('取消'),
),
TextButton(
onPressed: () {
final password = passwordController.text;
final confirm = confirmController.text;
if (password.isEmpty || password.length < 6) {
_showError('密码长度至少6位');
return;
}
if (password != confirm) {
_showError('两次输入的密码不一致');
return;
}
context.read<SettingsProvider>().setPassword(password);
Navigator.pop(dialogContext);
_showSuccess('密码设置成功');
},
child: const Text('确定'),
),
],
),
);
}
void _showChangePasswordDialog() {
final oldPasswordController = TextEditingController();
final newPasswordController = TextEditingController();
final confirmController = TextEditingController();
showDialog(
context: context,
builder: (dialogContext) => AlertDialog(
title: const Text('修改密码'),
content: Column(
mainAxisSize: MainAxisSize.min,
children: [
TextField(
controller: oldPasswordController,
obscureText: true,
decoration: InputDecoration(
labelText: '当前密码',
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(8.r),
),
),
),
SizedBox(height: 12.h),
TextField(
controller: newPasswordController,
obscureText: true,
decoration: InputDecoration(
labelText: '新密码',
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(8.r),
),
),
),
SizedBox(height: 12.h),
TextField(
controller: confirmController,
obscureText: true,
decoration: InputDecoration(
labelText: '确认新密码',
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(8.r),
),
),
),
],
),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16.r),
),
actions: [
TextButton(
onPressed: () => Navigator.pop(dialogContext),
child: const Text('取消'),
),
TextButton(
onPressed: () {
final oldPassword = oldPasswordController.text;
final newPassword = newPasswordController.text;
final confirm = confirmController.text;
if (!context.read<SettingsProvider>().verifyPassword(oldPassword)) {
_showError('当前密码错误');
return;
}
if (newPassword.isEmpty || newPassword.length < 6) {
_showError('新密码长度至少6位');
return;
}
if (newPassword != confirm) {
_showError('两次输入的新密码不一致');
return;
}
context.read<SettingsProvider>().setPassword(newPassword);
Navigator.pop(dialogContext);
_showSuccess('密码修改成功');
},
child: const Text('确定'),
),
],
),
);
}
设置密码对话框包含两个输入框,修改密码需要先验证当前密码。密码长度至少6位,两次输入必须一致。
导出数据功能
让用户导出所有个人数据:
dart
void _showExportDialog() {
showDialog(
context: context,
builder: (dialogContext) => AlertDialog(
title: const Text('导出数据'),
content: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text('将导出以下数据:'),
SizedBox(height: 12.h),
_buildCheckItem('照片和视频'),
_buildCheckItem('家人信息'),
_buildCheckItem('活动和回忆'),
_buildCheckItem('相册分类'),
],
),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16.r),
),
actions: [
TextButton(
onPressed: () => Navigator.pop(dialogContext),
child: const Text('取消'),
),
TextButton(
onPressed: () {
Navigator.pop(dialogContext);
_startExport();
},
child: const Text('开始导出'),
),
],
),
);
}
Widget _buildCheckItem(String text) {
return Padding(
padding: EdgeInsets.only(bottom: 8.h),
child: Row(
children: [
const Icon(Icons.check_circle, color: Color(0xFF4CAF50), size: 18),
SizedBox(width: 8.w),
Text(text),
],
),
);
}
void _startExport() {
showDialog(
context: context,
barrierDismissible: false,
builder: (_) => const Center(
child: CircularProgressIndicator(),
),
);
Future.delayed(const Duration(seconds: 2), () {
if (mounted) {
Navigator.pop(context);
_showSuccess('数据导出完成');
}
});
}
导出数据对话框列出将要导出的内容。点击开始导出后显示进度提示,完成后通知用户。
删除账户功能
删除账户需要多重确认:
dart
void _showDeleteAccountDialog() {
showDialog(
context: context,
builder: (dialogContext) => AlertDialog(
title: const Row(
children: [
Icon(Icons.warning_amber_rounded, color: Color(0xFFF44336)),
SizedBox(width: 8),
Text('删除账户'),
],
),
content: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text('此操作不可撤销,将永久删除:'),
SizedBox(height: 12.h),
_buildDeleteItem('所有照片和视频'),
_buildDeleteItem('家人信息和关系'),
_buildDeleteItem('活动和回忆记录'),
_buildDeleteItem('账户和个人资料'),
],
),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16.r),
),
actions: [
TextButton(
onPressed: () => Navigator.pop(dialogContext),
child: const Text('取消'),
),
TextButton(
onPressed: () {
Navigator.pop(dialogContext);
_showFinalConfirmDialog();
},
child: const Text(
'删除',
style: TextStyle(color: Color(0xFFF44336)),
),
),
],
),
);
}
Widget _buildDeleteItem(String text) {
return Padding(
padding: EdgeInsets.only(bottom: 8.h),
child: Row(
children: [
const Icon(Icons.close, color: Color(0xFFF44336), size: 18),
SizedBox(width: 8.w),
Text(text),
],
),
);
}
void _showFinalConfirmDialog() {
final confirmController = TextEditingController();
showDialog(
context: context,
builder: (dialogContext) => AlertDialog(
title: const Text('最后确认'),
content: Column(
mainAxisSize: MainAxisSize.min,
children: [
const Text('请输入"删除账户"以确认操作'),
SizedBox(height: 12.h),
TextField(
controller: confirmController,
decoration: InputDecoration(
hintText: '删除账户',
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(8.r),
),
),
),
],
),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16.r),
),
actions: [
TextButton(
onPressed: () => Navigator.pop(dialogContext),
child: const Text('取消'),
),
TextButton(
onPressed: () {
if (confirmController.text == '删除账户') {
Navigator.pop(dialogContext);
_performDeleteAccount();
} else {
_showError('输入不正确');
}
},
child: const Text(
'确认删除',
style: TextStyle(color: Color(0xFFF44336)),
),
),
],
),
);
}
void _performDeleteAccount() {
showDialog(
context: context,
barrierDismissible: false,
builder: (_) => const Center(
child: CircularProgressIndicator(),
),
);
Future.delayed(const Duration(seconds: 2), () {
if (mounted) {
Navigator.pop(context);
Navigator.pop(context);
_showSuccess('账户已删除');
}
});
}
删除账户需要两次确认,第二次需要输入"删除账户"文字。这样可以防止用户误操作。
辅助方法
通用的提示方法:
dart
void _showError(String message) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(message),
backgroundColor: Colors.red,
behavior: SnackBarBehavior.floating,
),
);
}
void _showSuccess(String message) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(message),
backgroundColor: const Color(0xFF4CAF50),
behavior: SnackBarBehavior.floating,
),
);
}
void _showMetadataInfo() {
showDialog(
context: context,
builder: (_) => AlertDialog(
title: const Text('照片元数据'),
content: const Text('照片元数据包括拍摄时间、地点、设备信息等。'),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: const Text('知道了'),
),
],
),
);
}
void _navigateToPrivateAlbums() {
// 导航到私密相册管理页面
}
void _showDataUsageHistory() {
// 显示数据使用记录
}
提取通用的提示方法,减少重复代码。错误提示用红色,成功提示用绿色。
小结
隐私设置页面通过分组列表的形式,清晰地展示了各类隐私相关的设置。应用锁、私密相册、照片信息和数据管理四个部分覆盖了用户对隐私保护的主要需求。通过开关、对话框和确认流程,确保用户的隐私数据得到妥善保护。
欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net