permission_dialog_shell.dart
5.25 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
import 'dart:math' as math;
import 'package:flutter/material.dart';
/// 弹窗标题中使用的应用名称
const String permissionDialogAppName = '班小二';
/// 去掉「权限」后缀,得到归一化后的权限名称(兼容「相机」与「相机权限」两种写法)
String normalizePermissionName(String permission) {
return permission.endsWith('权限') ? permission.substring(0, permission.length - 2) : permission;
}
/// 标题句式中与默认写法不同的权限(相册为「访问」,其余为「使用」)
const Map<String, String> _titleSuffixes = {
'相册': '需要访问您的相册',
};
/// 权限弹窗标题,例如:「班小二APP」需要使用相机、「班小二APP」需要访问您的相册
String permissionDialogTitle(String permission) {
final name = normalizePermissionName(permission);
final suffix = _titleSuffixes[name] ?? '需要使用$name';
return '"${permissionDialogAppName}APP"$suffix';
}
/// 权限类弹窗的纯样式骨架
///
/// 只负责渐变背景、盾牌插画、标题、说明以及主次按钮的排版与视觉,
/// 具体的文案与按钮行为由各业务组件通过参数传入,
/// 以便「首次使用权限说明」与「权限被拒后去设置引导」保持视觉统一。
class PermissionDialogShell extends StatelessWidget {
const PermissionDialogShell({
super.key,
required this.title,
required this.description,
required this.primaryText,
required this.onPrimary,
required this.secondaryText,
required this.onSecondary,
});
/// 弹窗标题,例如:「班小二APP」需要使用相机
final String title;
/// 弹窗说明文案
final String description;
/// 主按钮文案,例如:继续
final String primaryText;
/// 主按钮点击回调
final VoidCallback onPrimary;
/// 次按钮文案,例如:暂不开启
final String secondaryText;
/// 次按钮点击回调
final VoidCallback onSecondary;
/// 盾牌插画资源
static const String shieldAsset = 'assets/images/icon/shield.png';
@override
Widget build(BuildContext context) {
return Dialog(
backgroundColor: Colors.transparent,
elevation: 0,
insetPadding: const EdgeInsets.symmetric(horizontal: 40),
child: Container(
padding: const EdgeInsets.fromLTRB(22, 0, 22, 10),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(20),
gradient: const LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [
Color(0xFFBAC8FC),
Color(0xFFFFFFFF),
],
stops: [0, 0.25],
transform: GradientRotation(20 * math.pi / 180),
),
),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
// 插画
Center(
child: Image.asset(
shieldAsset,
height: 100,
fit: BoxFit.contain,
),
),
const SizedBox(height: 16),
// 标题
Text(
title,
textAlign: TextAlign.center,
style: const TextStyle(
fontSize: 17,
fontWeight: FontWeight.w600,
height: 1.4,
color: Color(0xFF1A1A1A),
),
),
const SizedBox(height: 10),
// 说明
Text(
description,
textAlign: TextAlign.left,
style: const TextStyle(
fontSize: 15,
height: 1.6,
color: Color(0xFF333333),
),
),
const SizedBox(height: 22),
// 主按钮
ClipRRect(
borderRadius: BorderRadius.circular(23),
child: Material(
color: Colors.transparent,
child: Ink(
decoration: const BoxDecoration(
color: Color(0xFF7691FA),
),
child: InkWell(
onTap: onPrimary,
child: SizedBox(
height: 46,
child: Center(
child: Text(
primaryText,
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.w500,
color: Colors.white,
),
),
),
),
),
),
),
),
const SizedBox(height: 8),
// 次按钮
TextButton(
onPressed: onSecondary,
style: TextButton.styleFrom(
minimumSize: const Size.fromHeight(40),
foregroundColor: const Color(0xFF999999),
),
child: Text(
secondaryText,
style: const TextStyle(
fontSize: 16,
color: Color(0xFF999999),
),
),
),
],
),
),
);
}
}