added permission based buttons
This commit is contained in:
parent
93f9a6e738
commit
858fe7435d
@ -1,25 +1,94 @@
|
||||
/// Contains all role and permission UUIDs used for access control across the application.
|
||||
class Permissions {
|
||||
// ------------------- Project Management ------------------------------
|
||||
/// Permission to manage master data (like dropdowns, configurations)
|
||||
static const String manageMaster = "588a8824-f924-4955-82d8-fc51956cf323";
|
||||
|
||||
/// Permission to create, edit, delete projects
|
||||
static const String manageProject = "172fc9b6-755b-4f62-ab26-55c34a330614";
|
||||
|
||||
/// Permission to view list of all projects
|
||||
static const String viewProjects = "6ea44136-987e-44ba-9e5d-1cf8f5837ebc";
|
||||
|
||||
/// Permission to assign employees to a project
|
||||
static const String assignToProject = "b94802ce-0689-4643-9e1d-11c86950c35b";
|
||||
|
||||
// ------------------- Employee Management -----------------------------
|
||||
/// Permission to manage employee records
|
||||
static const String manageEmployees = "a97d366a-c2bb-448d-be93-402bd2324566";
|
||||
static const String manageProjectInfra = "f2aee20a-b754-4537-8166-f9507b44585b";
|
||||
static const String viewProjectInfra = "c7b68e33-72f0-474f-bd96-77636427ecc8";
|
||||
|
||||
/// Permission to view all employees
|
||||
static const String viewAllEmployees = "60611762-7f8a-4fb5-b53f-b1139918796b";
|
||||
|
||||
/// Permission to view only team members (subordinate employees)
|
||||
static const String viewTeamMembers = "b82d2b7e-0d52-45f3-997b-c008ea460e7f";
|
||||
|
||||
// ------------------- Project Infrastructure --------------------------
|
||||
/// Permission to manage project infrastructure (e.g., site details)
|
||||
static const String manageProjectInfra = "cf2825ad-453b-46aa-91d9-27c124d63373";
|
||||
|
||||
/// Permission to view infrastructure-related details
|
||||
static const String viewProjectInfra = "8d7cc6e3-9147-41f7-aaa7-fa507e450bd4";
|
||||
|
||||
// ------------------- Attendance Management ---------------------------
|
||||
/// Permission to regularize (edit/update) attendance records
|
||||
static const String regularizeAttendance = "57802c4a-00aa-4a1f-a048-fd2f70dd44b6";
|
||||
static const String assignToProject = "fbd213e0-0250-46f1-9f5f-4b2a1e6e76a3";
|
||||
static const String infrastructure = "9666de86-d7c7-4d3d-acaa-fcd6d6b81f3c";
|
||||
|
||||
// ------------------- Task Management ---------------------------------
|
||||
/// Permission to create and manage tasks
|
||||
static const String manageTask = "08752f33-3b29-4816-b76b-ea8a968ed3c5";
|
||||
|
||||
/// Permission to approve tasks
|
||||
static const String approveTask = "db4e40c5-2ba9-4b6d-b8a6-a16a250ff99c";
|
||||
|
||||
/// Permission to view task lists and details
|
||||
static const String viewTask = "9fcc5f87-25e3-4846-90ac-67a71ab92e3c";
|
||||
|
||||
/// Permission to assign tasks for reporting
|
||||
static const String assignReportTask = "6a32379b-8b3f-49a6-8c48-4b7ac1b55dc2";
|
||||
|
||||
// ------------------- Directory Roles ---------------------------------
|
||||
/// Admin-level directory access
|
||||
static const String directoryAdmin = "4286a13b-bb40-4879-8c6d-18e9e393beda";
|
||||
|
||||
/// Manager-level directory access
|
||||
static const String directoryManager = "62668630-13ce-4f52-a0f0-db38af2230c5";
|
||||
|
||||
// Expense Permissions
|
||||
/// Basic directory user access
|
||||
static const String directoryUser = "0f919170-92d4-4337-abd3-49b66fc871bb";
|
||||
|
||||
// ------------------- Expense Permissions -----------------------------
|
||||
/// View only own expenses
|
||||
static const String expenseViewSelf = "385be49f-8fde-440e-bdbc-3dffeb8dd116";
|
||||
|
||||
/// View all employee expenses
|
||||
static const String expenseViewAll = "01e06444-9ca7-4df4-b900-8c3fa051b92f";
|
||||
|
||||
/// Create/upload new expenses
|
||||
static const String expenseUpload = "0f57885d-bcb2-4711-ac95-d841ace6d5a7";
|
||||
|
||||
/// Review submitted expenses
|
||||
static const String expenseReview = "1f4bda08-1873-449a-bb66-3e8222bd871b";
|
||||
|
||||
/// Approve or reject expenses
|
||||
static const String expenseApprove = "eaafdd76-8aac-45f9-a530-315589c6deca";
|
||||
|
||||
/// Process expenses for payment or final action
|
||||
static const String expenseProcess = "ea5a1529-4ee8-4828-80ea-0e23c9d4dd11";
|
||||
static const String expenseManage = "bdee29a2-b73b-402d-8dd1-c4b1f81ccbc3";
|
||||
|
||||
/// Full access to manage all expense operations
|
||||
static const String expenseManage = "ea5a1529-4ee8-4828-80ea-0e23c9d4dd11";
|
||||
|
||||
/// ID used to track expenses in "Draft" status
|
||||
static const String expenseDraft = "297e0d8f-f668-41b5-bfea-e03b354251c8";
|
||||
|
||||
/// List of user IDs who rejected the expense (used for audit trail)
|
||||
static const List<String> expenseRejectedBy = [
|
||||
"d1ee5eec-24b6-4364-8673-a8f859c60729",
|
||||
"965eda62-7907-4963-b4a1-657fb0b2724b",
|
||||
];
|
||||
|
||||
// ------------------- Application Roles -------------------------------
|
||||
/// Application role ID for users with full expense management rights
|
||||
static const String expenseManagement = "a4e25142-449b-4334-a6e5-22f70e4732d7";
|
||||
}
|
||||
|
@ -8,6 +8,8 @@ import 'package:marco/helpers/widgets/my_spacing.dart';
|
||||
import 'package:marco/helpers/widgets/my_text.dart';
|
||||
import 'package:marco/view/employees/assign_employee_bottom_sheet.dart';
|
||||
import 'package:marco/helpers/utils/launcher_utils.dart';
|
||||
import 'package:marco/controller/permission_controller.dart';
|
||||
import 'package:marco/helpers/utils/permission_constants.dart';
|
||||
|
||||
class EmployeeDetailPage extends StatefulWidget {
|
||||
final String employeeId;
|
||||
@ -21,7 +23,8 @@ class EmployeeDetailPage extends StatefulWidget {
|
||||
class _EmployeeDetailPageState extends State<EmployeeDetailPage> {
|
||||
final EmployeesScreenController controller =
|
||||
Get.put(EmployeesScreenController());
|
||||
|
||||
final PermissionController _permissionController =
|
||||
Get.find<PermissionController>();
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
@ -54,90 +57,90 @@ class _EmployeeDetailPageState extends State<EmployeeDetailPage> {
|
||||
|
||||
/// Row builder with email/phone tap & copy support
|
||||
Widget _buildLabelValueRow(String label, String value,
|
||||
{bool isMultiLine = false}) {
|
||||
final lowerLabel = label.toLowerCase();
|
||||
final isEmail = lowerLabel == 'email';
|
||||
final isPhone = lowerLabel == 'phone number' ||
|
||||
lowerLabel == 'emergency phone number';
|
||||
{bool isMultiLine = false}) {
|
||||
final lowerLabel = label.toLowerCase();
|
||||
final isEmail = lowerLabel == 'email';
|
||||
final isPhone =
|
||||
lowerLabel == 'phone number' || lowerLabel == 'emergency phone number';
|
||||
|
||||
void handleTap() {
|
||||
if (value == 'NA') return;
|
||||
if (isEmail) {
|
||||
LauncherUtils.launchEmail(value);
|
||||
} else if (isPhone) {
|
||||
LauncherUtils.launchPhone(value);
|
||||
void handleTap() {
|
||||
if (value == 'NA') return;
|
||||
if (isEmail) {
|
||||
LauncherUtils.launchEmail(value);
|
||||
} else if (isPhone) {
|
||||
LauncherUtils.launchPhone(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void handleLongPress() {
|
||||
if (value == 'NA') return;
|
||||
LauncherUtils.copyToClipboard(value, typeLabel: label);
|
||||
}
|
||||
void handleLongPress() {
|
||||
if (value == 'NA') return;
|
||||
LauncherUtils.copyToClipboard(value, typeLabel: label);
|
||||
}
|
||||
|
||||
final valueWidget = GestureDetector(
|
||||
onTap: (isEmail || isPhone) ? handleTap : null,
|
||||
onLongPress: (isEmail || isPhone) ? handleLongPress : null,
|
||||
child: Text(
|
||||
value,
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.normal,
|
||||
color: (isEmail || isPhone) ? Colors.indigo : Colors.black54,
|
||||
fontSize: 14,
|
||||
decoration:
|
||||
(isEmail || isPhone) ? TextDecoration.underline : TextDecoration.none,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
if (isMultiLine) ...[
|
||||
Text(
|
||||
label,
|
||||
style: const TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.black87,
|
||||
fontSize: 14,
|
||||
),
|
||||
final valueWidget = GestureDetector(
|
||||
onTap: (isEmail || isPhone) ? handleTap : null,
|
||||
onLongPress: (isEmail || isPhone) ? handleLongPress : null,
|
||||
child: Text(
|
||||
value,
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.normal,
|
||||
color: (isEmail || isPhone) ? Colors.indigo : Colors.black54,
|
||||
fontSize: 14,
|
||||
decoration: (isEmail || isPhone)
|
||||
? TextDecoration.underline
|
||||
: TextDecoration.none,
|
||||
),
|
||||
MySpacing.height(4),
|
||||
valueWidget,
|
||||
] else
|
||||
GestureDetector(
|
||||
onTap: (isEmail || isPhone) ? handleTap : null,
|
||||
onLongPress: (isEmail || isPhone) ? handleLongPress : null,
|
||||
child: RichText(
|
||||
text: TextSpan(
|
||||
text: "$label: ",
|
||||
style: const TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.black87,
|
||||
fontSize: 14,
|
||||
),
|
||||
children: [
|
||||
TextSpan(
|
||||
text: value,
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.normal,
|
||||
color:
|
||||
(isEmail || isPhone) ? Colors.indigo : Colors.black54,
|
||||
decoration: (isEmail || isPhone)
|
||||
? TextDecoration.underline
|
||||
: TextDecoration.none,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
if (isMultiLine) ...[
|
||||
Text(
|
||||
label,
|
||||
style: const TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.black87,
|
||||
fontSize: 14,
|
||||
),
|
||||
),
|
||||
),
|
||||
MySpacing.height(10),
|
||||
Divider(color: Colors.grey[300], height: 1),
|
||||
MySpacing.height(10),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
MySpacing.height(4),
|
||||
valueWidget,
|
||||
] else
|
||||
GestureDetector(
|
||||
onTap: (isEmail || isPhone) ? handleTap : null,
|
||||
onLongPress: (isEmail || isPhone) ? handleLongPress : null,
|
||||
child: RichText(
|
||||
text: TextSpan(
|
||||
text: "$label: ",
|
||||
style: const TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.black87,
|
||||
fontSize: 14,
|
||||
),
|
||||
children: [
|
||||
TextSpan(
|
||||
text: value,
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.normal,
|
||||
color:
|
||||
(isEmail || isPhone) ? Colors.indigo : Colors.black54,
|
||||
decoration: (isEmail || isPhone)
|
||||
? TextDecoration.underline
|
||||
: TextDecoration.none,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
MySpacing.height(10),
|
||||
Divider(color: Colors.grey[300], height: 1),
|
||||
MySpacing.height(10),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
/// Info card
|
||||
Widget _buildInfoCard(employee) {
|
||||
@ -291,6 +294,9 @@ class _EmployeeDetailPageState extends State<EmployeeDetailPage> {
|
||||
);
|
||||
}),
|
||||
floatingActionButton: Obx(() {
|
||||
if (!_permissionController.hasPermission(Permissions.assignToProject)) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
if (controller.isLoadingEmployeeDetails.value ||
|
||||
controller.selectedEmployeeDetails.value == null) {
|
||||
return const SizedBox.shrink();
|
||||
@ -318,4 +324,4 @@ class _EmployeeDetailPageState extends State<EmployeeDetailPage> {
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -13,6 +13,8 @@ import 'package:marco/view/employees/employee_detail_screen.dart';
|
||||
import 'package:marco/model/employee_model.dart';
|
||||
import 'package:marco/helpers/utils/launcher_utils.dart';
|
||||
import 'package:marco/view/employees/assign_employee_bottom_sheet.dart';
|
||||
import 'package:marco/controller/permission_controller.dart';
|
||||
import 'package:marco/helpers/utils/permission_constants.dart';
|
||||
|
||||
class EmployeesScreen extends StatefulWidget {
|
||||
const EmployeesScreen({super.key});
|
||||
@ -22,7 +24,10 @@ class EmployeesScreen extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _EmployeesScreenState extends State<EmployeesScreen> with UIMixin {
|
||||
final EmployeesScreenController _employeeController = Get.put(EmployeesScreenController());
|
||||
final EmployeesScreenController _employeeController =
|
||||
Get.put(EmployeesScreenController());
|
||||
final PermissionController _permissionController =
|
||||
Get.find<PermissionController>();
|
||||
final TextEditingController _searchController = TextEditingController();
|
||||
final RxList<EmployeeModel> _filteredEmployees = <EmployeeModel>[].obs;
|
||||
|
||||
@ -31,7 +36,8 @@ class _EmployeesScreenState extends State<EmployeesScreen> with UIMixin {
|
||||
super.initState();
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
_initEmployees();
|
||||
_searchController.addListener(() => _filterEmployees(_searchController.text));
|
||||
_searchController
|
||||
.addListener(() => _filterEmployees(_searchController.text));
|
||||
});
|
||||
}
|
||||
|
||||
@ -84,11 +90,12 @@ class _EmployeesScreenState extends State<EmployeesScreen> with UIMixin {
|
||||
|
||||
final q = query.toLowerCase();
|
||||
_filteredEmployees.assignAll(
|
||||
employees.where((e) =>
|
||||
e.name.toLowerCase().contains(q) ||
|
||||
e.email.toLowerCase().contains(q) ||
|
||||
e.phoneNumber.toLowerCase().contains(q) ||
|
||||
e.jobRole.toLowerCase().contains(q),
|
||||
employees.where(
|
||||
(e) =>
|
||||
e.name.toLowerCase().contains(q) ||
|
||||
e.email.toLowerCase().contains(q) ||
|
||||
e.phoneNumber.toLowerCase().contains(q) ||
|
||||
e.jobRole.toLowerCase().contains(q),
|
||||
),
|
||||
);
|
||||
}
|
||||
@ -172,7 +179,8 @@ class _EmployeesScreenState extends State<EmployeesScreen> with UIMixin {
|
||||
child: Row(
|
||||
children: [
|
||||
IconButton(
|
||||
icon: const Icon(Icons.arrow_back_ios_new, color: Colors.black, size: 20),
|
||||
icon: const Icon(Icons.arrow_back_ios_new,
|
||||
color: Colors.black, size: 20),
|
||||
onPressed: () => Get.offNamed('/dashboard'),
|
||||
),
|
||||
MySpacing.width(8),
|
||||
@ -180,14 +188,18 @@ class _EmployeesScreenState extends State<EmployeesScreen> with UIMixin {
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
MyText.titleLarge('Employees', fontWeight: 700, color: Colors.black),
|
||||
MyText.titleLarge('Employees',
|
||||
fontWeight: 700, color: Colors.black),
|
||||
MySpacing.height(2),
|
||||
GetBuilder<ProjectController>(
|
||||
builder: (projectController) {
|
||||
final projectName = projectController.selectedProject?.name ?? 'Select Project';
|
||||
final projectName =
|
||||
projectController.selectedProject?.name ??
|
||||
'Select Project';
|
||||
return Row(
|
||||
children: [
|
||||
const Icon(Icons.work_outline, size: 14, color: Colors.grey),
|
||||
const Icon(Icons.work_outline,
|
||||
size: 14, color: Colors.grey),
|
||||
MySpacing.width(4),
|
||||
Expanded(
|
||||
child: MyText.bodySmall(
|
||||
@ -212,6 +224,11 @@ class _EmployeesScreenState extends State<EmployeesScreen> with UIMixin {
|
||||
}
|
||||
|
||||
Widget _buildFloatingActionButton() {
|
||||
if (!_permissionController.hasPermission(Permissions.manageEmployees)) {
|
||||
return const SizedBox
|
||||
.shrink();
|
||||
}
|
||||
|
||||
return InkWell(
|
||||
onTap: _onAddNewEmployee,
|
||||
borderRadius: BorderRadius.circular(28),
|
||||
@ -220,7 +237,10 @@ class _EmployeesScreenState extends State<EmployeesScreen> with UIMixin {
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.red,
|
||||
borderRadius: BorderRadius.circular(28),
|
||||
boxShadow: const [BoxShadow(color: Colors.black26, blurRadius: 6, offset: Offset(0, 3))],
|
||||
boxShadow: const [
|
||||
BoxShadow(
|
||||
color: Colors.black26, blurRadius: 6, offset: Offset(0, 3))
|
||||
],
|
||||
),
|
||||
child: const Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
@ -257,9 +277,11 @@ class _EmployeesScreenState extends State<EmployeesScreen> with UIMixin {
|
||||
style: const TextStyle(fontSize: 13, height: 1.2),
|
||||
decoration: InputDecoration(
|
||||
isDense: true,
|
||||
contentPadding: const EdgeInsets.symmetric(horizontal: 8, vertical: 8),
|
||||
contentPadding:
|
||||
const EdgeInsets.symmetric(horizontal: 8, vertical: 8),
|
||||
prefixIcon: const Icon(Icons.search, size: 18, color: Colors.grey),
|
||||
prefixIconConstraints: const BoxConstraints(minWidth: 32, minHeight: 32),
|
||||
prefixIconConstraints:
|
||||
const BoxConstraints(minWidth: 32, minHeight: 32),
|
||||
hintText: 'Search contacts...',
|
||||
hintStyle: const TextStyle(fontSize: 13, color: Colors.grey),
|
||||
filled: true,
|
||||
@ -303,6 +325,10 @@ class _EmployeesScreenState extends State<EmployeesScreen> with UIMixin {
|
||||
}
|
||||
|
||||
Widget _buildPopupMenu() {
|
||||
if (!_permissionController.hasPermission(Permissions.viewAllEmployees)) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
|
||||
return PopupMenuButton<String>(
|
||||
icon: Stack(
|
||||
clipBehavior: Clip.none,
|
||||
@ -315,7 +341,8 @@ class _EmployeesScreenState extends State<EmployeesScreen> with UIMixin {
|
||||
child: Container(
|
||||
width: 10,
|
||||
height: 10,
|
||||
decoration: const BoxDecoration(color: Colors.red, shape: BoxShape.circle),
|
||||
decoration: const BoxDecoration(
|
||||
color: Colors.red, shape: BoxShape.circle),
|
||||
),
|
||||
)
|
||||
: const SizedBox.shrink()),
|
||||
@ -341,7 +368,9 @@ class _EmployeesScreenState extends State<EmployeesScreen> with UIMixin {
|
||||
activeColor: Colors.red,
|
||||
side: const BorderSide(color: Colors.black, width: 1.5),
|
||||
fillColor: MaterialStateProperty.resolveWith<Color>(
|
||||
(states) => states.contains(MaterialState.selected) ? Colors.red : Colors.white),
|
||||
(states) => states.contains(MaterialState.selected)
|
||||
? Colors.red
|
||||
: Colors.white),
|
||||
),
|
||||
const Text('All Employees'),
|
||||
],
|
||||
@ -370,7 +399,8 @@ class _EmployeesScreenState extends State<EmployeesScreen> with UIMixin {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(top: 60),
|
||||
child: Center(
|
||||
child: MyText.bodySmall("No Employees Found", fontWeight: 600, color: Colors.grey[700]),
|
||||
child: MyText.bodySmall("No Employees Found",
|
||||
fontWeight: 600, color: Colors.grey[700]),
|
||||
),
|
||||
);
|
||||
}
|
||||
@ -398,19 +428,37 @@ class _EmployeesScreenState extends State<EmployeesScreen> with UIMixin {
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
MyText.titleSmall(e.name, fontWeight: 600, overflow: TextOverflow.ellipsis),
|
||||
MyText.titleSmall(e.name,
|
||||
fontWeight: 600, overflow: TextOverflow.ellipsis),
|
||||
if (e.jobRole.isNotEmpty)
|
||||
MyText.bodySmall(e.jobRole, color: Colors.grey[700], overflow: TextOverflow.ellipsis),
|
||||
MyText.bodySmall(e.jobRole,
|
||||
color: Colors.grey[700],
|
||||
overflow: TextOverflow.ellipsis),
|
||||
MySpacing.height(8),
|
||||
if (e.email.isNotEmpty && e.email != '-')
|
||||
_buildLinkRow(icon: Icons.email_outlined, text: e.email, onTap: () => LauncherUtils.launchEmail(e.email), onLongPress: () => LauncherUtils.copyToClipboard(e.email, typeLabel: 'Email')),
|
||||
if (e.email.isNotEmpty && e.email != '-') MySpacing.height(6),
|
||||
_buildLinkRow(
|
||||
icon: Icons.email_outlined,
|
||||
text: e.email,
|
||||
onTap: () => LauncherUtils.launchEmail(e.email),
|
||||
onLongPress: () => LauncherUtils.copyToClipboard(
|
||||
e.email,
|
||||
typeLabel: 'Email')),
|
||||
if (e.email.isNotEmpty && e.email != '-')
|
||||
MySpacing.height(6),
|
||||
if (e.phoneNumber.isNotEmpty)
|
||||
_buildLinkRow(icon: Icons.phone_outlined, text: e.phoneNumber, onTap: () => LauncherUtils.launchPhone(e.phoneNumber), onLongPress: () => LauncherUtils.copyToClipboard(e.phoneNumber, typeLabel: 'Phone')),
|
||||
_buildLinkRow(
|
||||
icon: Icons.phone_outlined,
|
||||
text: e.phoneNumber,
|
||||
onTap: () =>
|
||||
LauncherUtils.launchPhone(e.phoneNumber),
|
||||
onLongPress: () => LauncherUtils.copyToClipboard(
|
||||
e.phoneNumber,
|
||||
typeLabel: 'Phone')),
|
||||
],
|
||||
),
|
||||
),
|
||||
const Icon(Icons.arrow_forward_ios, color: Colors.grey, size: 16),
|
||||
const Icon(Icons.arrow_forward_ios,
|
||||
color: Colors.grey, size: 16),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
Loading…
x
Reference in New Issue
Block a user