profile tabs fixed

This commit is contained in:
amirrezaghabeli
2026-04-19 12:37:39 +03:30
parent c6cb3221ab
commit eb75952b8e
8 changed files with 492 additions and 252 deletions
+5
View File
@@ -0,0 +1,5 @@
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M9.16602 1.66602H7.49935C3.33268 1.66602 1.66602 3.33268 1.66602 7.49935V12.4993C1.66602 16.666 3.33268 18.3327 7.49935 18.3327H12.4993C16.666 18.3327 18.3327 16.666 18.3327 12.4993V10.8327" stroke="#444444" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M13.3675 2.51639L6.80088 9.08306C6.55088 9.33306 6.30088 9.82472 6.25088 10.1831L5.89254 12.6914C5.75921 13.5997 6.40088 14.2331 7.30921 14.1081L9.81754 13.7497C10.1675 13.6997 10.6592 13.4497 10.9175 13.1997L17.4842 6.63306C18.6175 5.49972 19.1509 4.18306 17.4842 2.51639C15.8175 0.849722 14.5009 1.38306 13.3675 2.51639Z" stroke="#444444" stroke-width="1.5" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M12.4258 3.45898C12.9841 5.45065 14.5424 7.00898 16.5424 7.57565" stroke="#444444" stroke-width="1.5" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 1012 B

+1
View File
@@ -71,6 +71,7 @@ class AssetsManager {
//profile page
static const sun = 'assets/icons/sun.svg';
static const moon = 'assets/icons/moon.svg';
static const editting = 'assets/icons/editting.svg';
//Liked tenders
static const trash = 'assets/icons/trash.svg';
+31 -123
View File
@@ -1,19 +1,17 @@
import 'package:flutter/material.dart';
import 'package:flutter_svg/svg.dart';
import 'package:provider/provider.dart';
import 'package:tm_app/core/routes/app_routes.dart';
import 'package:tm_app/view_models/profile_view_model.dart';
import '../../../core/constants/assets.dart';
import '../../../core/constants/common_strings.dart';
import '../../../core/theme/colors.dart';
import '../../../core/utils/app_toast.dart';
import '../../../core/utils/size_config.dart';
import '../../../view_models/auth_view_model.dart';
import '../../shared/desktop_navigation_widget.dart';
import '../strings/profile_strings.dart';
import '../widgets/theme_toggle.dart';
import '../widgets/title_description.dart';
import '../widgets/main_data_tab.dart';
import '../widgets/members_tab.dart';
import '../widgets/profile_tab_bar.dart';
class DesktopProfilePage extends StatefulWidget {
const DesktopProfilePage({super.key});
@@ -22,12 +20,15 @@ class DesktopProfilePage extends StatefulWidget {
State<DesktopProfilePage> createState() => _DesktopProfilePageState();
}
class _DesktopProfilePageState extends State<DesktopProfilePage> {
class _DesktopProfilePageState extends State<DesktopProfilePage>
with SingleTickerProviderStateMixin {
late ProfileViewModel viewModel;
late final TabController tabController;
@override
void initState() {
super.initState();
viewModel = context.read<ProfileViewModel>();
tabController = TabController(length: 3, vsync: this);
viewModel.addListener(_profileListener);
}
@@ -56,150 +57,57 @@ class _DesktopProfilePageState extends State<DesktopProfilePage> {
const DesktopNavigationWidget(
currentIndex: 4, // Tenders index
),
Consumer<ProfileViewModel>(
Expanded(
child: Consumer<ProfileViewModel>(
builder: (context, viewModel, child) {
if (viewModel.isLoading) {
return const Expanded(
child: Center(
return const Center(
child: CircularProgressIndicator(
color: AppColors.secondary50,
),
),
);
}
if (viewModel.errorMessage != null) {
return Expanded(
child: Center(child: Text(viewModel.errorMessage!)),
);
return Center(child: Text(viewModel.errorMessage!));
}
if (viewModel.profileData == null) {
return const Expanded(
child: Center(child: Text(CommonStrings.noData)),
);
return const Center(child: Text(CommonStrings.noData));
}
return Expanded(
child: SingleChildScrollView(
child: Column(
children: [
SizedBox(
return SizedBox(
width: 740,
height: double.infinity,
child: Column(
children: [
SizedBox(height: 36.0.h()),
TitleDescription(
title: ProfileStrings.fullname,
description:
viewModel.profileData!.fullName ?? '',
),
TitleDescription(
title: ProfileStrings.username,
description:
viewModel.profileData!.username ?? '',
),
TitleDescription(
title: ProfileStrings.email,
description: viewModel.profileData!.email ?? '',
),
TitleDescription(
title: ProfileStrings.phone,
description: viewModel.profileData!.phone ?? '',
),
TitleDescription(
title: ProfileStrings.companyName,
description:
viewModel
.profileData!
.companies!
.first
.name ??
'',
),
SizedBox(height: 24.0.h()),
// Theme Toggle
Align(
alignment: AlignmentDirectional.centerStart,
child: Text(
ProfileStrings.settings,
style: TextStyle(
fontSize: 20.0.sp(),
fontWeight: FontWeight.w600,
color: AppColors.grey70,
),
),
),
SizedBox(height: 36.0.h()),
_themeChangeRow(),
SizedBox(height: 24.0.h()),
Divider(
color: AppColors.dividerColor,
thickness: 1,
),
SizedBox(height: 24.0.h()),
// Spacer(),
Align(
alignment: AlignmentDirectional.centerEnd,
child: InkWell(
onTap: () => viewModel.logout(),
borderRadius: BorderRadius.circular(100),
child: Container(
width: 156,
height: 56.0.h(),
decoration: BoxDecoration(
color: AppColors.primary20,
borderRadius: BorderRadius.circular(100),
),
alignment: Alignment.center,
child: Text(
ProfileStrings.logout,
style: TextStyle(
fontSize: 14.0.sp(),
fontWeight: FontWeight.w500,
color: AppColors.primaryColor,
),
),
),
const SizedBox(height: 46),
ProfileTabBar(
controller: tabController,
titles: [
ProfileStrings.mainDataTab,
ProfileStrings.membersTab,
ProfileStrings.subjectTab,
],
),
Expanded(
child: TabBarView(
controller: tabController,
children: [
MainDataTab(
profileData: viewModel.profileData!,
onLogout: () => viewModel.logout(),
),
const MembersTab(),
const Center(child: Text('Subject Tab Content')),
],
),
),
],
),
),
);
},
),
],
),
);
}
Widget _themeChangeRow() {
return Padding(
padding: EdgeInsets.symmetric(horizontal: 8.0.w()),
child: Row(
children: [
Text(
ProfileStrings.appTheme,
style: TextStyle(
fontSize: 16.0.sp(),
fontWeight: FontWeight.w400,
color: AppColors.grey70,
),
),
const Spacer(),
SvgPicture.asset(AssetsManager.sun),
SizedBox(width: 12.0.w()),
SizedBox(
width: 52.0.w(),
height: 32.0.h(),
child: const ThemeToggle(),
),
SizedBox(width: 12.0.w()),
SvgPicture.asset(AssetsManager.moon),
],
),
);
+4 -2
View File
@@ -11,7 +11,9 @@ import '../../../core/utils/size_config.dart';
import '../../../view_models/auth_view_model.dart';
import '../../shared/tender_app_bar.dart';
import '../strings/profile_strings.dart';
import '../widgets/members_tab.dart';
import '../widgets/profile_tab_bar.dart';
import '../widgets/subject_tab.dart';
class MobileProfilePage extends StatefulWidget {
const MobileProfilePage({super.key});
@@ -94,8 +96,8 @@ class _MobileProfilePageState extends State<MobileProfilePage>
profileData: viewModel.profileData!,
onLogout: () => viewModel.logout(),
),
const Center(child: Text('Members Tab Content')),
const Center(child: Text('Subject Tab Content')),
const MembersTab(),
const SubjectTab(),
],
),
),
+24 -116
View File
@@ -1,7 +1,5 @@
import 'package:flutter/material.dart';
import 'package:flutter_svg/svg.dart';
import 'package:provider/provider.dart';
import 'package:tm_app/core/constants/assets.dart';
import 'package:tm_app/core/constants/common_strings.dart';
import 'package:tm_app/core/routes/app_routes.dart';
import 'package:tm_app/view_models/profile_view_model.dart';
@@ -13,8 +11,9 @@ import '../../../view_models/auth_view_model.dart';
import '../../shared/tablet_navigation_widget.dart';
import '../../shared/tender_app_bar.dart';
import '../strings/profile_strings.dart';
import '../widgets/theme_toggle.dart';
import '../widgets/title_description.dart';
import '../widgets/main_data_tab.dart';
import '../widgets/members_tab.dart';
import '../widgets/profile_tab_bar.dart';
class TabletProfilePage extends StatefulWidget {
const TabletProfilePage({super.key});
@@ -23,12 +22,15 @@ class TabletProfilePage extends StatefulWidget {
State<TabletProfilePage> createState() => _TabletProfilePageState();
}
class _TabletProfilePageState extends State<TabletProfilePage> {
class _TabletProfilePageState extends State<TabletProfilePage>
with SingleTickerProviderStateMixin {
late ProfileViewModel viewModel;
late final TabController tabController;
@override
void initState() {
super.initState();
viewModel = context.read<ProfileViewModel>();
tabController = TabController(length: 3, vsync: this);
viewModel.addListener(_profileListener);
}
@@ -84,91 +86,25 @@ class _TabletProfilePageState extends State<TabletProfilePage> {
),
child: Column(
children: [
SizedBox(height: 128.0.h()),
TitleDescription(
title: ProfileStrings.fullname,
description: viewModel.profileData!.fullName ?? '',
),
TitleDescription(
title: ProfileStrings.username,
description: viewModel.profileData!.username ?? '',
),
TitleDescription(
title: ProfileStrings.email,
description: viewModel.profileData!.email ?? '',
),
TitleDescription(
title: ProfileStrings.phone,
description: viewModel.profileData!.phone ?? '',
),
TitleDescription(
title: ProfileStrings.companyName,
description:
viewModel.profileData!.companies!.first.name ?? '',
),
// TitleDescription(
// title: ProfileStrings.companyName,
// description: viewModel.companyProfileData!.name!,
// ),
// TitleDescription(
// title: ProfileStrings.nationalId,
// description: viewModel.companyProfileData!.id!,
// ),
// TitleDescription(
// title: ProfileStrings.registrationNumber,
// description:
// viewModel.companyProfileData!.registrationNumber!,
// ),
// const TitleDescription(
// title: ProfileStrings.businessType,
// description: '-',
// ),
// TitleDescription(
// title: ProfileStrings.founded,
// description:
// viewModel.companyProfileData!.foundedYear!.toString(),
// ),
SizedBox(height: 24.0.h()),
// Theme Toggle
Align(
alignment: AlignmentDirectional.centerStart,
child: Text(
ProfileStrings.settings,
style: TextStyle(
fontSize: 20.0.sp(),
fontWeight: FontWeight.w600,
color: AppColors.grey70,
),
),
),
SizedBox(height: 36.0.h()),
_themeChangeRow(),
SizedBox(height: 24.0.h()),
Divider(color: AppColors.dividerColor, thickness: 1),
SizedBox(height: 24.0.h()),
// Spacer(),
Align(
alignment: AlignmentDirectional.centerStart,
child: InkWell(
onTap: () => viewModel.logout(),
borderRadius: BorderRadius.circular(100),
child: Container(
width: 156,
height: 56.0.h(),
decoration: BoxDecoration(
color: AppColors.primary20,
borderRadius: BorderRadius.circular(100),
),
alignment: Alignment.center,
child: Text(
ProfileStrings.logout,
style: TextStyle(
fontSize: 14.0.sp(),
fontWeight: FontWeight.w500,
color: AppColors.primaryColor,
),
ProfileTabBar(
controller: tabController,
titles: [
ProfileStrings.mainDataTab,
ProfileStrings.membersTab,
ProfileStrings.subjectTab,
],
),
Expanded(
child: TabBarView(
controller: tabController,
children: [
MainDataTab(
profileData: viewModel.profileData!,
onLogout: () => viewModel.logout(),
),
const MembersTab(),
const Center(child: Text('Subject Tab Content')),
],
),
),
],
@@ -179,32 +115,4 @@ class _TabletProfilePageState extends State<TabletProfilePage> {
},
);
}
Widget _themeChangeRow() {
return Padding(
padding: EdgeInsets.symmetric(horizontal: 8.0.w()),
child: Row(
children: [
Text(
ProfileStrings.appTheme,
style: TextStyle(
fontSize: 16.0.sp(),
fontWeight: FontWeight.w400,
color: AppColors.grey70,
),
),
const Spacer(),
SvgPicture.asset(AssetsManager.sun),
SizedBox(width: 12.0.w()),
SizedBox(
width: 52.0.w(),
height: 32.0.h(),
child: const ThemeToggle(),
),
SizedBox(width: 12.0.w()),
SvgPicture.asset(AssetsManager.moon),
],
),
);
}
}
@@ -0,0 +1,123 @@
import 'package:flutter/material.dart';
import 'package:flutter_svg/svg.dart';
import 'package:tm_app/core/utils/size_config.dart';
import 'package:tm_app/views/login/widgets/widgets.dart';
import '../../../core/constants/assets.dart';
import '../../../core/theme/colors.dart';
class EditKeyWordBottomSheet extends StatelessWidget {
const EditKeyWordBottomSheet({super.key});
@override
Widget build(BuildContext context) {
return Wrap(
children: [
Container(
width: double.infinity,
padding: EdgeInsets.symmetric(
horizontal: 24.0.w(),
vertical: 24.0.h(),
),
decoration: BoxDecoration(
color: AppColors.backgroundColor,
borderRadius: const BorderRadius.vertical(top: Radius.circular(24)),
),
child: Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
'Edit Key Word',
style: TextStyle(
fontSize: 20.0.sp(),
fontWeight: FontWeight.w600,
),
),
SvgPicture.asset(
AssetsManager.closeCircle,
width: 20.0.w(),
height: 20.0.w(),
fit: BoxFit.cover,
colorFilter: ColorFilter.mode(
AppColors.grey60,
BlendMode.srcIn,
),
),
],
),
SizedBox(height: 16.0.h()),
Divider(color: AppColors.grey20),
SizedBox(height: 24.0.h()),
TextField(
decoration: InputDecoration(
hintText: 'Key Word',
hintStyle: TextStyle(
fontSize: 16.0.sp(),
fontWeight: FontWeight.w400,
color: AppColors.grey60,
),
border: OutlineInputBorder(
borderSide: BorderSide(color: AppColors.grey40),
borderRadius: const BorderRadius.all(Radius.circular(12)),
),
enabledBorder: OutlineInputBorder(
borderSide: BorderSide(color: AppColors.grey40),
borderRadius: const BorderRadius.all(Radius.circular(12)),
),
focusedBorder: OutlineInputBorder(
borderSide: BorderSide(color: AppColors.grey40),
borderRadius: const BorderRadius.all(Radius.circular(12)),
),
errorBorder: const OutlineInputBorder(
borderSide: BorderSide(color: AppColors.errorColor),
borderRadius: BorderRadius.all(Radius.circular(12)),
),
),
),
SizedBox(height: 16.0.h()),
Wrap(
spacing: 8,
runSpacing: 16.0.h(),
children: [
_chip(label: 'UIUX', backgroundColor: Colors.green[200]!),
_chip(
label: 'Lorem ipsum',
backgroundColor: Colors.orange[100]!,
),
_chip(label: 'sample', backgroundColor: Colors.grey[300]!),
_chip(label: 'Unknown', backgroundColor: Colors.red[200]!),
_chip(
label: 'Lorem ipsum 02',
backgroundColor: Colors.blue[100]!,
),
],
),
SizedBox(height: 32.0.h()),
BaseButton(
isEnabled: true,
text: 'Submit Request',
backgroundColor: AppColors.primary20,
textColor: AppColors.mainBlue,
onPressed: () {},
),
],
),
),
],
);
}
Widget _chip({required String label, required Color backgroundColor}) {
return InputChip(
label: Text(label),
backgroundColor: backgroundColor,
onDeleted: () {},
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
side: const BorderSide(color: Colors.transparent),
),
);
}
}
+116
View File
@@ -0,0 +1,116 @@
import 'package:flutter/material.dart';
import 'package:tm_app/core/theme/colors.dart';
import 'package:tm_app/core/utils/size_config.dart';
class MembersTab extends StatelessWidget {
const MembersTab({super.key});
@override
Widget build(BuildContext context) {
return Column(
children: [
SizedBox(height: 36.0.h()),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
_columnName('Full Name'),
_columnName('Position'),
_columnName('Access level'),
],
),
SizedBox(height: 24.0.h()),
Divider(color: AppColors.grey50),
SizedBox(height: 24.0.h()),
Expanded(
child: ListView.separated(
scrollDirection: Axis.vertical,
itemBuilder: (context, index) {
final member = _members[index];
return _member(
fullName: member.fullName,
position: member.position,
accessLevel: member.accessLevel,
);
},
separatorBuilder:
(context, index) =>
Divider(color: AppColors.grey50.withValues(alpha: 0.2)),
itemCount: _members.length,
),
),
],
);
}
Widget _columnName(String text) {
return SizedBox(
width: 100.0.w(),
child: Text(
text,
style: TextStyle(
fontSize: 16.0.sp(),
fontWeight: FontWeight.w400,
color: AppColors.grey70,
),
),
);
}
Widget _member({
required String fullName,
required String position,
required String accessLevel,
}) {
return SizedBox(
height: 43.0.h(),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
_memberDetail(title: fullName),
// SizedBox(width: 40.0.w()),
_memberDetail(title: position),
// SizedBox(width: 40.0.w()),
_memberDetail(title: accessLevel),
],
),
);
}
Widget _memberDetail({required String title}) {
return SizedBox(
width: 100.0.w(),
child: Align(
alignment: Alignment.centerLeft,
child: Text(
title,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle(
fontSize: 16.0.sp(),
fontWeight: FontWeight.w400,
color: AppColors.grey80,
),
),
),
);
}
}
class Member {
final String fullName;
final String position;
final String accessLevel;
Member({
required this.fullName,
required this.position,
required this.accessLevel,
});
}
final List<Member> _members = [
Member(fullName: 'Rick Novak', position: 'Director', accessLevel: 'Level 01'),
Member(fullName: 'Sara Novak', position: 'Director', accessLevel: 'Level 01'),
Member(fullName: 'Sarah Connor', position: 'Chief', accessLevel: 'Level 02'),
];
+177
View File
@@ -0,0 +1,177 @@
import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:tm_app/core/constants/assets.dart';
import 'package:tm_app/core/theme/colors.dart';
import 'package:tm_app/core/utils/size_config.dart';
import 'edit_key_word_bottom_sheet.dart';
class SubjectTab extends StatelessWidget {
const SubjectTab({super.key});
@override
Widget build(BuildContext context) {
return Column(
children: [
SizedBox(height: 36.0.h()),
_companyActivity(companyActivity: 'IT'),
SizedBox(height: 24.0.h()),
_budget(budget: 'Telecom Solutions GmbH'),
SizedBox(height: 24.0.h()),
_keyWord(
onEditPressed: () {
showModalBottomSheet(
context: context,
isScrollControlled: true,
builder: (_) {
return const EditKeyWordBottomSheet();
},
);
},
),
SizedBox(height: 16.0.h()),
Wrap(
spacing: 8,
runSpacing: 16.0.h(),
children: [
_chip(label: 'UIUX', backgroundColor: Colors.green[200]!),
_chip(label: 'Lorem ipsum', backgroundColor: Colors.orange[100]!),
_chip(label: 'sample', backgroundColor: Colors.grey[300]!),
_chip(label: 'Unknown', backgroundColor: Colors.red[200]!),
_chip(label: 'Lorem ipsum 02', backgroundColor: Colors.blue[100]!),
],
),
SizedBox(height: 24.0.h()),
Divider(color: AppColors.grey20),
_location(onEditePressed: () {}),
],
);
}
Widget _companyActivity({required String companyActivity}) {
return Padding(
padding: EdgeInsets.symmetric(horizontal: 8.0.w()),
child: SizedBox(
height: 43.0.h(),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Company Activity ',
style: TextStyle(
fontSize: 16.0.sp(),
fontWeight: FontWeight.w400,
color: AppColors.grey60,
),
),
Text(
companyActivity,
style: TextStyle(
fontSize: 16.0.sp(),
fontWeight: FontWeight.w400,
color: AppColors.grey80,
),
),
],
),
),
);
}
Widget _budget({required String budget}) {
return Padding(
padding: EdgeInsets.symmetric(horizontal: 8.0.w()),
child: SizedBox(
height: 43.0.h(),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Budget',
style: TextStyle(
fontSize: 16.0.sp(),
fontWeight: FontWeight.w400,
color: AppColors.grey60,
),
),
Text(
budget,
style: TextStyle(
fontSize: 16.0.sp(),
fontWeight: FontWeight.w400,
color: AppColors.grey80,
),
),
],
),
),
);
}
Widget _keyWord({required VoidCallback onEditPressed}) {
return Padding(
padding: EdgeInsets.symmetric(horizontal: 8.0.w()),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Text(
'Budget',
style: TextStyle(
fontSize: 16.0.sp(),
fontWeight: FontWeight.w400,
color: AppColors.grey60,
),
),
IconButton(
onPressed: onEditPressed,
icon: SvgPicture.asset(
AssetsManager.editting,
colorFilter: ColorFilter.mode(AppColors.grey70, BlendMode.srcIn),
),
),
],
),
);
}
Widget _chip({required String label, required Color backgroundColor}) {
return Chip(
label: Text(label),
backgroundColor: backgroundColor,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
side: const BorderSide(color: Colors.transparent),
),
);
}
Widget _location({required VoidCallback onEditePressed}) {
return Padding(
padding: EdgeInsets.symmetric(horizontal: 8.0.w()),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Text(
'Location',
style: TextStyle(
fontSize: 16.0.sp(),
fontWeight: FontWeight.w400,
color: AppColors.grey60,
),
),
IconButton(
onPressed: onEditePressed,
icon: SvgPicture.asset(
AssetsManager.editting,
colorFilter: ColorFilter.mode(AppColors.grey70, BlendMode.srcIn),
),
),
],
),
);
}
}