added team member list
This commit is contained in:
parent
6cd9fbe57b
commit
03082aeea9
@ -1,6 +1,7 @@
|
||||
import 'package:get/get.dart';
|
||||
import 'package:on_field_work/helpers/services/api_service.dart';
|
||||
import 'package:on_field_work/model/infra_project/infra_project_details.dart';
|
||||
import 'package:on_field_work/model/infra_project/infra_team_list_model.dart';
|
||||
|
||||
class InfraProjectDetailsController extends GetxController {
|
||||
final String projectId;
|
||||
@ -9,25 +10,39 @@ class InfraProjectDetailsController extends GetxController {
|
||||
|
||||
var isLoading = true.obs;
|
||||
var projectDetails = Rxn<ProjectData>();
|
||||
var teamList = <ProjectAllocation>[].obs;
|
||||
var teamLoading = true.obs;
|
||||
var errorMessage = ''.obs;
|
||||
|
||||
var teamErrorMessage = ''.obs;
|
||||
@override
|
||||
void onInit() {
|
||||
super.onInit();
|
||||
fetchProjectDetails();
|
||||
fetchProjectTeamList();
|
||||
}
|
||||
|
||||
Map<String, List<ProjectAllocation>> get groupedTeamByRole {
|
||||
final Map<String, List<ProjectAllocation>> map = {};
|
||||
for (final member in teamList) {
|
||||
map.putIfAbsent(member.jobRoleId, () => []).add(member);
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
Future<void> fetchProjectDetails() async {
|
||||
try {
|
||||
isLoading.value = true;
|
||||
final response = await ApiService.getInfraProjectDetails(projectId: projectId);
|
||||
final response =
|
||||
await ApiService.getInfraProjectDetails(projectId: projectId);
|
||||
|
||||
if (response != null && response.success == true && response.data != null) {
|
||||
if (response != null &&
|
||||
response.success == true &&
|
||||
response.data != null) {
|
||||
projectDetails.value = response.data;
|
||||
isLoading.value = false;
|
||||
|
||||
errorMessage.value = '';
|
||||
} else {
|
||||
errorMessage.value = response?.message ?? "Failed to load project details";
|
||||
errorMessage.value =
|
||||
response?.message ?? "Failed to load project details";
|
||||
}
|
||||
} catch (e) {
|
||||
errorMessage.value = "Error fetching project details: $e";
|
||||
@ -35,4 +50,28 @@ class InfraProjectDetailsController extends GetxController {
|
||||
isLoading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> fetchProjectTeamList() async {
|
||||
try {
|
||||
teamLoading.value = true;
|
||||
teamErrorMessage.value = '';
|
||||
|
||||
final response = await ApiService.getInfraProjectTeamListApi(
|
||||
projectId: projectId,
|
||||
includeInactive: false,
|
||||
);
|
||||
|
||||
if (response?.success == true && response!.data.isNotEmpty) {
|
||||
teamList.assignAll(response.data);
|
||||
} else {
|
||||
teamList.clear();
|
||||
teamErrorMessage.value = response?.message ?? "No team members found.";
|
||||
}
|
||||
} catch (e) {
|
||||
teamList.clear();
|
||||
teamErrorMessage.value = "Failed to load team members";
|
||||
} finally {
|
||||
teamLoading.value = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -167,4 +167,6 @@ class ApiEndpoints {
|
||||
// Infra Project Module API Endpoints
|
||||
static const String getInfraProjectsList = "/project/list";
|
||||
static const String getInfraProjectDetail = "/project/details";
|
||||
static const String getInfraProjectTeamList = "/project/allocation";
|
||||
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -51,6 +51,7 @@ import 'package:on_field_work/model/infra_project/infra_project_list.dart';
|
||||
import 'package:on_field_work/model/infra_project/infra_project_details.dart';
|
||||
import 'package:on_field_work/model/dashboard/collection_overview_model.dart';
|
||||
import 'package:on_field_work/model/dashboard/purchase_invoice_model.dart';
|
||||
import 'package:on_field_work/model/infra_project/infra_team_list_model.dart';
|
||||
|
||||
class ApiService {
|
||||
static const bool enableLogs = true;
|
||||
@ -2007,6 +2008,43 @@ class ApiService {
|
||||
label: "Comment Task", returnFullResponse: true);
|
||||
return parsed != null && parsed['success'] == true;
|
||||
}
|
||||
static Future<ProjectAllocationResponse?> getInfraProjectTeamListApi({
|
||||
required String projectId,
|
||||
String? serviceId,
|
||||
bool includeInactive = false,
|
||||
}) async {
|
||||
if (projectId.isEmpty) {
|
||||
_log("projectId must not be empty for getInfraProjectTeamListApi",
|
||||
level: LogLevel.error);
|
||||
return null;
|
||||
}
|
||||
final endpoint = "${ApiEndpoints.getInfraProjectTeamList}/$projectId";
|
||||
|
||||
final queryParams = <String, String>{
|
||||
'includeInactive': includeInactive.toString(),
|
||||
};
|
||||
if (serviceId != null && serviceId.isNotEmpty) {
|
||||
queryParams['serviceId'] = serviceId;
|
||||
}
|
||||
|
||||
final response = await _safeApiCall(
|
||||
endpoint,
|
||||
method: 'GET',
|
||||
queryParams: queryParams,
|
||||
);
|
||||
|
||||
if (response == null) return null;
|
||||
|
||||
final parsedJson = _parseAndDecryptResponse(
|
||||
response,
|
||||
label: "InfraProjectTeamList",
|
||||
returnFullResponse: true,
|
||||
);
|
||||
if (parsedJson == null || parsedJson is! Map<String, dynamic>) {
|
||||
return null;
|
||||
}
|
||||
return ProjectAllocationResponse.fromJson(parsedJson);
|
||||
}
|
||||
|
||||
static Future<Map<String, dynamic>?> getInfraDetails(String projectId,
|
||||
{String? serviceId}) async {
|
||||
|
||||
119
lib/model/infra_project/infra_team_list_model.dart
Normal file
119
lib/model/infra_project/infra_team_list_model.dart
Normal file
@ -0,0 +1,119 @@
|
||||
|
||||
class ProjectAllocationResponse {
|
||||
final bool success;
|
||||
final String message;
|
||||
final List<ProjectAllocation> data;
|
||||
final int statusCode;
|
||||
final String timestamp;
|
||||
|
||||
ProjectAllocationResponse({
|
||||
required this.success,
|
||||
required this.message,
|
||||
required this.data,
|
||||
required this.statusCode,
|
||||
required this.timestamp,
|
||||
});
|
||||
|
||||
factory ProjectAllocationResponse.fromJson(Map<String, dynamic> json) {
|
||||
final List<dynamic>? rawData = json['data'] as List<dynamic>?;
|
||||
|
||||
final List<ProjectAllocation> allocations = rawData
|
||||
?.map((item) => ProjectAllocation.fromJson(item as Map<String, dynamic>))
|
||||
.toList()
|
||||
?? [];
|
||||
|
||||
return ProjectAllocationResponse(
|
||||
success: json['success'] as bool? ?? false,
|
||||
message: json['message'] as String? ?? 'An unknown API error occurred.',
|
||||
data: allocations,
|
||||
statusCode: json['statusCode'] as int? ?? 0,
|
||||
timestamp: json['timestamp'] as String? ?? '',
|
||||
);
|
||||
}
|
||||
|
||||
/// Converts the [ProjectAllocationResponse] object back to a JSON map.
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'success': success,
|
||||
'message': message,
|
||||
'data': data.map((e) => e.toJson()).toList(),
|
||||
'statusCode': statusCode,
|
||||
'timestamp': timestamp,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// --- Allocation Detail Class ---
|
||||
|
||||
class ProjectAllocation {
|
||||
final String id;
|
||||
final String employeeId;
|
||||
final String projectId;
|
||||
final String allocationDate;
|
||||
final bool isActive;
|
||||
final String firstName;
|
||||
final String lastName;
|
||||
final String middleName;
|
||||
final String organizationId;
|
||||
final String organizationName;
|
||||
final String serviceId;
|
||||
final String serviceName;
|
||||
final String jobRoleId;
|
||||
final String jobRoleName;
|
||||
|
||||
ProjectAllocation({
|
||||
required this.id,
|
||||
required this.employeeId,
|
||||
required this.projectId,
|
||||
required this.allocationDate,
|
||||
required this.isActive,
|
||||
required this.firstName,
|
||||
required this.lastName,
|
||||
required this.middleName,
|
||||
required this.organizationId,
|
||||
required this.organizationName,
|
||||
required this.serviceId,
|
||||
required this.serviceName,
|
||||
required this.jobRoleId,
|
||||
required this.jobRoleName
|
||||
});
|
||||
|
||||
|
||||
factory ProjectAllocation.fromJson(Map<String, dynamic> json) {
|
||||
return ProjectAllocation(
|
||||
id: json['id'] as String? ?? '',
|
||||
employeeId: json['employeeId'] as String? ?? '',
|
||||
projectId: json['projectId'] as String? ?? '',
|
||||
allocationDate: json['allocationDate'] as String? ?? '',
|
||||
isActive: json['isActive'] as bool? ?? false,
|
||||
firstName: json['firstName'] as String? ?? '',
|
||||
lastName: json['lastName'] as String? ?? '',
|
||||
middleName: json['middleName'] as String? ?? '',
|
||||
organizationId: json['organizationId'] as String? ?? '',
|
||||
organizationName: json['organizationName'] as String? ?? '',
|
||||
serviceId: json['serviceId'] as String? ?? '',
|
||||
serviceName: json['serviceName'] as String? ?? '',
|
||||
jobRoleId: json['jobRoleId'] as String? ?? '',
|
||||
jobRoleName: json['jobRoleName'] as String? ?? '',
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'id': id,
|
||||
'employeeId': employeeId,
|
||||
'projectId': projectId,
|
||||
'allocationDate': allocationDate,
|
||||
'isActive': isActive,
|
||||
'firstName': firstName,
|
||||
'lastName': lastName,
|
||||
'middleName': middleName,
|
||||
'organizationId': organizationId,
|
||||
'organizationName': organizationName,
|
||||
'serviceId': serviceId,
|
||||
'serviceName': serviceName,
|
||||
'jobRoleId': jobRoleId,
|
||||
'jobRoleName': jobRoleName
|
||||
};
|
||||
}
|
||||
}
|
||||
@ -10,11 +10,14 @@ import 'package:on_field_work/helpers/utils/launcher_utils.dart';
|
||||
import 'package:on_field_work/helpers/widgets/my_spacing.dart';
|
||||
import 'package:on_field_work/helpers/widgets/my_text.dart';
|
||||
import 'package:on_field_work/helpers/widgets/my_refresh_indicator.dart';
|
||||
import 'package:on_field_work/helpers/widgets/avatar.dart';
|
||||
import 'package:on_field_work/view/employees/employee_profile_screen.dart';
|
||||
|
||||
import 'package:on_field_work/controller/dynamicMenu/dynamic_menu_controller.dart';
|
||||
import 'package:on_field_work/controller/infra_project/infra_project_screen_details_controller.dart';
|
||||
import 'package:on_field_work/view/taskPlanning/daily_progress_report.dart';
|
||||
import 'package:on_field_work/view/taskPlanning/daily_task_planning.dart';
|
||||
import 'package:on_field_work/model/infra_project/infra_team_list_model.dart';
|
||||
|
||||
class InfraProjectDetailsScreen extends StatefulWidget {
|
||||
final String projectId;
|
||||
@ -36,17 +39,20 @@ class _InfraProjectDetailsScreenState extends State<InfraProjectDetailsScreen>
|
||||
late final TabController _tabController;
|
||||
final DynamicMenuController menuController =
|
||||
Get.find<DynamicMenuController>();
|
||||
late final InfraProjectDetailsController controller;
|
||||
final List<_InfraTab> _tabs = [];
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
controller =
|
||||
Get.put(InfraProjectDetailsController(projectId: widget.projectId));
|
||||
_prepareTabs();
|
||||
}
|
||||
|
||||
void _prepareTabs() {
|
||||
// Profile tab is always added
|
||||
_tabs.add(_InfraTab(name: "Profile", view: _buildProfileTab()));
|
||||
_tabs.add(_InfraTab(name: "Team", view: _buildTeamTab()));
|
||||
|
||||
final allowedMenu = menuController.menuItems.where((m) => m.available);
|
||||
|
||||
@ -77,10 +83,148 @@ class _InfraProjectDetailsScreenState extends State<InfraProjectDetailsScreen>
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
Widget _buildProfileTab() {
|
||||
final controller =
|
||||
Get.put(InfraProjectDetailsController(projectId: widget.projectId));
|
||||
Widget _buildTeamTab() {
|
||||
return Obx(() {
|
||||
if (controller.teamLoading.value) {
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
}
|
||||
|
||||
if (controller.teamErrorMessage.isNotEmpty) {
|
||||
return Center(
|
||||
child: MyText.bodyMedium(controller.teamErrorMessage.value),
|
||||
);
|
||||
}
|
||||
|
||||
if (controller.teamList.isEmpty) {
|
||||
return const Center(
|
||||
child: Text("No team members allocated to this project."),
|
||||
);
|
||||
}
|
||||
|
||||
final roleGroups = controller.groupedTeamByRole;
|
||||
|
||||
final sortedRoleEntries = roleGroups.entries.toList()
|
||||
..sort((a, b) {
|
||||
final aName = (a.value.isNotEmpty ? a.value.first.jobRoleName : '')
|
||||
.toLowerCase();
|
||||
final bName = (b.value.isNotEmpty ? b.value.first.jobRoleName : '')
|
||||
.toLowerCase();
|
||||
return aName.compareTo(bName);
|
||||
});
|
||||
|
||||
return MyRefreshIndicator(
|
||||
onRefresh: controller.fetchProjectTeamList,
|
||||
backgroundColor: Colors.indigo,
|
||||
color: Colors.white,
|
||||
child: ListView.separated(
|
||||
padding: const EdgeInsets.all(12),
|
||||
itemCount: sortedRoleEntries.length,
|
||||
separatorBuilder: (_, __) => const SizedBox(height: 12),
|
||||
itemBuilder: (context, index) {
|
||||
final teamMembers = sortedRoleEntries[index].value;
|
||||
return _buildRoleCard(teamMembers);
|
||||
},
|
||||
),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
Widget _buildRoleCard(List<ProjectAllocation> teamMembers) {
|
||||
teamMembers.sort((a, b) {
|
||||
final aName = ("${a.firstName} ${a.lastName}").trim().toLowerCase();
|
||||
final bName = ("${b.firstName} ${b.lastName}").trim().toLowerCase();
|
||||
return aName.compareTo(bName);
|
||||
});
|
||||
|
||||
final String roleName =
|
||||
(teamMembers.isNotEmpty ? (teamMembers.first.jobRoleName) : '').trim();
|
||||
|
||||
return Card(
|
||||
elevation: 3,
|
||||
shadowColor: Colors.black26,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// TOP: Job Role name
|
||||
if (roleName.isNotEmpty) ...[
|
||||
MyText.bodyLarge(
|
||||
roleName,
|
||||
fontWeight: 700,
|
||||
),
|
||||
const Divider(height: 20),
|
||||
] else
|
||||
const Divider(height: 20),
|
||||
|
||||
// Team members list
|
||||
...teamMembers.map((allocation) {
|
||||
return InkWell(
|
||||
onTap: () {
|
||||
Get.to(
|
||||
() => EmployeeProfilePage(
|
||||
employeeId: allocation.employeeId,
|
||||
),
|
||||
);
|
||||
},
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 8),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Avatar(
|
||||
firstName: allocation.firstName,
|
||||
lastName: allocation.lastName,
|
||||
size: 32,
|
||||
),
|
||||
MySpacing.width(12),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
MyText.titleMedium(
|
||||
"${allocation.firstName} ${allocation.lastName}",
|
||||
fontWeight: 600,
|
||||
),
|
||||
MyText.bodySmall(
|
||||
allocation.serviceName.isNotEmpty
|
||||
? "Service: ${allocation.serviceName}"
|
||||
: "No Service Assigned",
|
||||
color: Colors.grey[700],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
children: [
|
||||
MyText.bodySmall(
|
||||
"Allocated",
|
||||
color: Colors.grey.shade500,
|
||||
),
|
||||
MyText.bodySmall(
|
||||
DateFormat('d MMM yyyy').format(
|
||||
DateTime.parse(allocation.allocationDate),
|
||||
),
|
||||
fontWeight: 600,
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}).toList(),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildProfileTab() {
|
||||
return Obx(() {
|
||||
if (controller.isLoading.value) {
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
@ -153,35 +297,40 @@ class _InfraProjectDetailsScreenState extends State<InfraProjectDetailsScreen>
|
||||
titleIcon: Icons.info_outline,
|
||||
children: [
|
||||
_buildDetailRow(
|
||||
icon: Icons.location_on_outlined,
|
||||
label: 'Address',
|
||||
value: data.projectAddress ?? "-"),
|
||||
icon: Icons.location_on_outlined,
|
||||
label: 'Address',
|
||||
value: data.projectAddress ?? "-",
|
||||
),
|
||||
_buildDetailRow(
|
||||
icon: Icons.calendar_today_outlined,
|
||||
label: 'Start Date',
|
||||
value: data.startDate != null
|
||||
? DateFormat('d/M/yyyy').format(data.startDate!)
|
||||
: "-"),
|
||||
icon: Icons.calendar_today_outlined,
|
||||
label: 'Start Date',
|
||||
value: data.startDate != null
|
||||
? DateFormat('d/M/yyyy').format(data.startDate!)
|
||||
: "-",
|
||||
),
|
||||
_buildDetailRow(
|
||||
icon: Icons.calendar_today_outlined,
|
||||
label: 'End Date',
|
||||
value: data.endDate != null
|
||||
? DateFormat('d/M/yyyy').format(data.endDate!)
|
||||
: "-"),
|
||||
icon: Icons.calendar_today_outlined,
|
||||
label: 'End Date',
|
||||
value: data.endDate != null
|
||||
? DateFormat('d/M/yyyy').format(data.endDate!)
|
||||
: "-",
|
||||
),
|
||||
_buildDetailRow(
|
||||
icon: Icons.flag_outlined,
|
||||
label: 'Status',
|
||||
value: data.projectStatus?.status ?? "-"),
|
||||
icon: Icons.flag_outlined,
|
||||
label: 'Status',
|
||||
value: data.projectStatus?.status ?? "-",
|
||||
),
|
||||
_buildDetailRow(
|
||||
icon: Icons.person_outline,
|
||||
label: 'Contact Person',
|
||||
value: data.contactPerson ?? "-",
|
||||
isActionable: true,
|
||||
onTap: () {
|
||||
if (data.contactPerson != null) {
|
||||
LauncherUtils.launchPhone(data.contactPerson!);
|
||||
}
|
||||
}),
|
||||
icon: Icons.person_outline,
|
||||
label: 'Contact Person',
|
||||
value: data.contactPerson ?? "-",
|
||||
isActionable: true,
|
||||
onTap: () {
|
||||
if (data.contactPerson != null) {
|
||||
LauncherUtils.launchPhone(data.contactPerson!);
|
||||
}
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
@ -192,22 +341,24 @@ class _InfraProjectDetailsScreenState extends State<InfraProjectDetailsScreen>
|
||||
titleIcon: Icons.business_outlined,
|
||||
children: [
|
||||
_buildDetailRow(
|
||||
icon: Icons.person_outline,
|
||||
label: 'Name',
|
||||
value: promoter.name ?? "-"),
|
||||
icon: Icons.person_outline,
|
||||
label: 'Name',
|
||||
value: promoter.name ?? "-",
|
||||
),
|
||||
_buildDetailRow(
|
||||
icon: Icons.phone_outlined,
|
||||
label: 'Contact',
|
||||
value: promoter.contactNumber ?? "-",
|
||||
isActionable: true,
|
||||
onTap: () =>
|
||||
LauncherUtils.launchPhone(promoter.contactNumber ?? "")),
|
||||
icon: Icons.phone_outlined,
|
||||
label: 'Contact',
|
||||
value: promoter.contactNumber ?? "-",
|
||||
isActionable: true,
|
||||
onTap: () => LauncherUtils.launchPhone(promoter.contactNumber ?? ""),
|
||||
),
|
||||
_buildDetailRow(
|
||||
icon: Icons.email_outlined,
|
||||
label: 'Email',
|
||||
value: promoter.email ?? "-",
|
||||
isActionable: true,
|
||||
onTap: () => LauncherUtils.launchEmail(promoter.email ?? "")),
|
||||
icon: Icons.email_outlined,
|
||||
label: 'Email',
|
||||
value: promoter.email ?? "-",
|
||||
isActionable: true,
|
||||
onTap: () => LauncherUtils.launchEmail(promoter.email ?? ""),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
@ -218,19 +369,24 @@ class _InfraProjectDetailsScreenState extends State<InfraProjectDetailsScreen>
|
||||
titleIcon: Icons.engineering_outlined,
|
||||
children: [
|
||||
_buildDetailRow(
|
||||
icon: Icons.person_outline, label: 'Name', value: pmc.name ?? "-"),
|
||||
icon: Icons.person_outline,
|
||||
label: 'Name',
|
||||
value: pmc.name ?? "-",
|
||||
),
|
||||
_buildDetailRow(
|
||||
icon: Icons.phone_outlined,
|
||||
label: 'Contact',
|
||||
value: pmc.contactNumber ?? "-",
|
||||
isActionable: true,
|
||||
onTap: () => LauncherUtils.launchPhone(pmc.contactNumber ?? "")),
|
||||
icon: Icons.phone_outlined,
|
||||
label: 'Contact',
|
||||
value: pmc.contactNumber ?? "-",
|
||||
isActionable: true,
|
||||
onTap: () => LauncherUtils.launchPhone(pmc.contactNumber ?? ""),
|
||||
),
|
||||
_buildDetailRow(
|
||||
icon: Icons.email_outlined,
|
||||
label: 'Email',
|
||||
value: pmc.email ?? "-",
|
||||
isActionable: true,
|
||||
onTap: () => LauncherUtils.launchEmail(pmc.email ?? "")),
|
||||
icon: Icons.email_outlined,
|
||||
label: 'Email',
|
||||
value: pmc.email ?? "-",
|
||||
isActionable: true,
|
||||
onTap: () => LauncherUtils.launchEmail(pmc.email ?? ""),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
@ -251,7 +407,9 @@ class _InfraProjectDetailsScreenState extends State<InfraProjectDetailsScreen>
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Container(
|
||||
padding: const EdgeInsets.all(8), child: Icon(icon, size: 20)),
|
||||
padding: const EdgeInsets.all(8),
|
||||
child: Icon(icon, size: 20),
|
||||
),
|
||||
MySpacing.width(16),
|
||||
Expanded(
|
||||
child: Column(
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user