diff --git a/assets/icons/editting.svg b/assets/icons/editting.svg
new file mode 100644
index 0000000..b055f45
--- /dev/null
+++ b/assets/icons/editting.svg
@@ -0,0 +1,5 @@
+
diff --git a/lib/core/constants/assets.dart b/lib/core/constants/assets.dart
index abc5a4f..7b1994e 100644
--- a/lib/core/constants/assets.dart
+++ b/lib/core/constants/assets.dart
@@ -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';
diff --git a/lib/core/utils/date_utils.dart b/lib/core/utils/date_utils.dart
index c03b277..77ea623 100644
--- a/lib/core/utils/date_utils.dart
+++ b/lib/core/utils/date_utils.dart
@@ -1,8 +1,9 @@
-String timeConvertor(int time) {
- DateTime date = DateTime.fromMillisecondsSinceEpoch(time);
+import 'package:intl/intl.dart';
- String formattedDate =
- "${date.year}-${date.month.toString().padLeft(2, '0')}-${date.day.toString().padLeft(2, '0')}";
+String timeConvertor(int timestamp) {
+ // Convert seconds β milliseconds
+ final date = DateTime.fromMillisecondsSinceEpoch(timestamp * 1000);
- return formattedDate;
+ // Format to yyyy-MM-dd
+ return DateFormat('yyyy-MM-dd').format(date);
}
diff --git a/lib/data/repositories/profile_repository.dart b/lib/data/repositories/profile_repository.dart
index c5a11b7..3d8941f 100644
--- a/lib/data/repositories/profile_repository.dart
+++ b/lib/data/repositories/profile_repository.dart
@@ -16,4 +16,10 @@ class ProfileRepository {
Future> getCompanyProfile() {
return _profileService.getCompanyProfile();
}
+
+ Future> updateProfileKeyWords({
+ required List keyWords,
+ }) {
+ return _profileService.updateProfileKeyWords(keyWords: keyWords);
+ }
}
diff --git a/lib/data/services/api/profile_api.dart b/lib/data/services/api/profile_api.dart
index c6e2336..6beb9f7 100644
--- a/lib/data/services/api/profile_api.dart
+++ b/lib/data/services/api/profile_api.dart
@@ -1,4 +1,5 @@
class ProfileApi {
static const String profile = '/api/v1/profile';
static const String companyProfile = '/api/v1/companies';
+ static const String updateProfileKeyWords = '/api/v1/profile/keywords';
}
diff --git a/lib/data/services/model/profile_data/profile_data.dart b/lib/data/services/model/profile_data/profile_data.dart
index 7fe2fd7..c7b57df 100644
--- a/lib/data/services/model/profile_data/profile_data.dart
+++ b/lib/data/services/model/profile_data/profile_data.dart
@@ -25,6 +25,7 @@ abstract class ProfileData with _$ProfileData {
@JsonKey(name: 'last_login_at') required int? lastLoginAt,
@JsonKey(name: 'created_at') required int? createdAt,
@JsonKey(name: 'updated_at') required int? updatedAt,
+ List? keywords,
}) = _ProfileData;
factory ProfileData.fromJson(Map json) =>
diff --git a/lib/data/services/model/profile_data/profile_data.freezed.dart b/lib/data/services/model/profile_data/profile_data.freezed.dart
index 7c04a93..9df2dbf 100644
--- a/lib/data/services/model/profile_data/profile_data.freezed.dart
+++ b/lib/data/services/model/profile_data/profile_data.freezed.dart
@@ -15,7 +15,7 @@ T _$identity(T value) => value;
/// @nodoc
mixin _$ProfileData {
- String? get id;@JsonKey(name: 'full_name') String? get fullName; String? get username; String? get email; String? get role; String? get status; String? get department; String? get position; String? get phone; List? get companies;@JsonKey(name: 'profile_image') String? get profileImage;@JsonKey(name: 'is_verified') bool? get isVerified;@JsonKey(name: 'last_login_at') int? get lastLoginAt;@JsonKey(name: 'created_at') int? get createdAt;@JsonKey(name: 'updated_at') int? get updatedAt;
+ String? get id;@JsonKey(name: 'full_name') String? get fullName; String? get username; String? get email; String? get role; String? get status; String? get department; String? get position; String? get phone; List? get companies;@JsonKey(name: 'profile_image') String? get profileImage;@JsonKey(name: 'is_verified') bool? get isVerified;@JsonKey(name: 'last_login_at') int? get lastLoginAt;@JsonKey(name: 'created_at') int? get createdAt;@JsonKey(name: 'updated_at') int? get updatedAt; List? get keywords;
/// Create a copy of ProfileData
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@@ -28,16 +28,16 @@ $ProfileDataCopyWith get copyWith => _$ProfileDataCopyWithImpl Object.hash(runtimeType,id,fullName,username,email,role,status,department,position,phone,const DeepCollectionEquality().hash(companies),profileImage,isVerified,lastLoginAt,createdAt,updatedAt);
+int get hashCode => Object.hash(runtimeType,id,fullName,username,email,role,status,department,position,phone,const DeepCollectionEquality().hash(companies),profileImage,isVerified,lastLoginAt,createdAt,updatedAt,const DeepCollectionEquality().hash(keywords));
@override
String toString() {
- return 'ProfileData(id: $id, fullName: $fullName, username: $username, email: $email, role: $role, status: $status, department: $department, position: $position, phone: $phone, companies: $companies, profileImage: $profileImage, isVerified: $isVerified, lastLoginAt: $lastLoginAt, createdAt: $createdAt, updatedAt: $updatedAt)';
+ return 'ProfileData(id: $id, fullName: $fullName, username: $username, email: $email, role: $role, status: $status, department: $department, position: $position, phone: $phone, companies: $companies, profileImage: $profileImage, isVerified: $isVerified, lastLoginAt: $lastLoginAt, createdAt: $createdAt, updatedAt: $updatedAt, keywords: $keywords)';
}
@@ -48,7 +48,7 @@ abstract mixin class $ProfileDataCopyWith<$Res> {
factory $ProfileDataCopyWith(ProfileData value, $Res Function(ProfileData) _then) = _$ProfileDataCopyWithImpl;
@useResult
$Res call({
- String? id,@JsonKey(name: 'full_name') String? fullName, String? username, String? email, String? role, String? status, String? department, String? position, String? phone, List? companies,@JsonKey(name: 'profile_image') String? profileImage,@JsonKey(name: 'is_verified') bool? isVerified,@JsonKey(name: 'last_login_at') int? lastLoginAt,@JsonKey(name: 'created_at') int? createdAt,@JsonKey(name: 'updated_at') int? updatedAt
+ String? id,@JsonKey(name: 'full_name') String? fullName, String? username, String? email, String? role, String? status, String? department, String? position, String? phone, List? companies,@JsonKey(name: 'profile_image') String? profileImage,@JsonKey(name: 'is_verified') bool? isVerified,@JsonKey(name: 'last_login_at') int? lastLoginAt,@JsonKey(name: 'created_at') int? createdAt,@JsonKey(name: 'updated_at') int? updatedAt, List? keywords
});
@@ -65,7 +65,7 @@ class _$ProfileDataCopyWithImpl<$Res>
/// Create a copy of ProfileData
/// with the given fields replaced by the non-null parameter values.
-@pragma('vm:prefer-inline') @override $Res call({Object? id = freezed,Object? fullName = freezed,Object? username = freezed,Object? email = freezed,Object? role = freezed,Object? status = freezed,Object? department = freezed,Object? position = freezed,Object? phone = freezed,Object? companies = freezed,Object? profileImage = freezed,Object? isVerified = freezed,Object? lastLoginAt = freezed,Object? createdAt = freezed,Object? updatedAt = freezed,}) {
+@pragma('vm:prefer-inline') @override $Res call({Object? id = freezed,Object? fullName = freezed,Object? username = freezed,Object? email = freezed,Object? role = freezed,Object? status = freezed,Object? department = freezed,Object? position = freezed,Object? phone = freezed,Object? companies = freezed,Object? profileImage = freezed,Object? isVerified = freezed,Object? lastLoginAt = freezed,Object? createdAt = freezed,Object? updatedAt = freezed,Object? keywords = freezed,}) {
return _then(_self.copyWith(
id: freezed == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
as String?,fullName: freezed == fullName ? _self.fullName : fullName // ignore: cast_nullable_to_non_nullable
@@ -82,7 +82,8 @@ as String?,isVerified: freezed == isVerified ? _self.isVerified : isVerified //
as bool?,lastLoginAt: freezed == lastLoginAt ? _self.lastLoginAt : lastLoginAt // ignore: cast_nullable_to_non_nullable
as int?,createdAt: freezed == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable
as int?,updatedAt: freezed == updatedAt ? _self.updatedAt : updatedAt // ignore: cast_nullable_to_non_nullable
-as int?,
+as int?,keywords: freezed == keywords ? _self.keywords : keywords // ignore: cast_nullable_to_non_nullable
+as List?,
));
}
@@ -167,10 +168,10 @@ return $default(_that);case _:
/// }
/// ```
-@optionalTypeArgs TResult maybeWhen(TResult Function( String? id, @JsonKey(name: 'full_name') String? fullName, String? username, String? email, String? role, String? status, String? department, String? position, String? phone, List? companies, @JsonKey(name: 'profile_image') String? profileImage, @JsonKey(name: 'is_verified') bool? isVerified, @JsonKey(name: 'last_login_at') int? lastLoginAt, @JsonKey(name: 'created_at') int? createdAt, @JsonKey(name: 'updated_at') int? updatedAt)? $default,{required TResult orElse(),}) {final _that = this;
+@optionalTypeArgs TResult maybeWhen(TResult Function( String? id, @JsonKey(name: 'full_name') String? fullName, String? username, String? email, String? role, String? status, String? department, String? position, String? phone, List? companies, @JsonKey(name: 'profile_image') String? profileImage, @JsonKey(name: 'is_verified') bool? isVerified, @JsonKey(name: 'last_login_at') int? lastLoginAt, @JsonKey(name: 'created_at') int? createdAt, @JsonKey(name: 'updated_at') int? updatedAt, List? keywords)? $default,{required TResult orElse(),}) {final _that = this;
switch (_that) {
case _ProfileData() when $default != null:
-return $default(_that.id,_that.fullName,_that.username,_that.email,_that.role,_that.status,_that.department,_that.position,_that.phone,_that.companies,_that.profileImage,_that.isVerified,_that.lastLoginAt,_that.createdAt,_that.updatedAt);case _:
+return $default(_that.id,_that.fullName,_that.username,_that.email,_that.role,_that.status,_that.department,_that.position,_that.phone,_that.companies,_that.profileImage,_that.isVerified,_that.lastLoginAt,_that.createdAt,_that.updatedAt,_that.keywords);case _:
return orElse();
}
@@ -188,10 +189,10 @@ return $default(_that.id,_that.fullName,_that.username,_that.email,_that.role,_t
/// }
/// ```
-@optionalTypeArgs TResult when(TResult Function( String? id, @JsonKey(name: 'full_name') String? fullName, String? username, String? email, String? role, String? status, String? department, String? position, String? phone, List? companies, @JsonKey(name: 'profile_image') String? profileImage, @JsonKey(name: 'is_verified') bool? isVerified, @JsonKey(name: 'last_login_at') int? lastLoginAt, @JsonKey(name: 'created_at') int? createdAt, @JsonKey(name: 'updated_at') int? updatedAt) $default,) {final _that = this;
+@optionalTypeArgs TResult when(TResult Function( String? id, @JsonKey(name: 'full_name') String? fullName, String? username, String? email, String? role, String? status, String? department, String? position, String? phone, List? companies, @JsonKey(name: 'profile_image') String? profileImage, @JsonKey(name: 'is_verified') bool? isVerified, @JsonKey(name: 'last_login_at') int? lastLoginAt, @JsonKey(name: 'created_at') int? createdAt, @JsonKey(name: 'updated_at') int? updatedAt, List? keywords) $default,) {final _that = this;
switch (_that) {
case _ProfileData():
-return $default(_that.id,_that.fullName,_that.username,_that.email,_that.role,_that.status,_that.department,_that.position,_that.phone,_that.companies,_that.profileImage,_that.isVerified,_that.lastLoginAt,_that.createdAt,_that.updatedAt);case _:
+return $default(_that.id,_that.fullName,_that.username,_that.email,_that.role,_that.status,_that.department,_that.position,_that.phone,_that.companies,_that.profileImage,_that.isVerified,_that.lastLoginAt,_that.createdAt,_that.updatedAt,_that.keywords);case _:
throw StateError('Unexpected subclass');
}
@@ -208,10 +209,10 @@ return $default(_that.id,_that.fullName,_that.username,_that.email,_that.role,_t
/// }
/// ```
-@optionalTypeArgs TResult? whenOrNull(TResult? Function( String? id, @JsonKey(name: 'full_name') String? fullName, String? username, String? email, String? role, String? status, String? department, String? position, String? phone, List? companies, @JsonKey(name: 'profile_image') String? profileImage, @JsonKey(name: 'is_verified') bool? isVerified, @JsonKey(name: 'last_login_at') int? lastLoginAt, @JsonKey(name: 'created_at') int? createdAt, @JsonKey(name: 'updated_at') int? updatedAt)? $default,) {final _that = this;
+@optionalTypeArgs TResult? whenOrNull(TResult? Function( String? id, @JsonKey(name: 'full_name') String? fullName, String? username, String? email, String? role, String? status, String? department, String? position, String? phone, List? companies, @JsonKey(name: 'profile_image') String? profileImage, @JsonKey(name: 'is_verified') bool? isVerified, @JsonKey(name: 'last_login_at') int? lastLoginAt, @JsonKey(name: 'created_at') int? createdAt, @JsonKey(name: 'updated_at') int? updatedAt, List? keywords)? $default,) {final _that = this;
switch (_that) {
case _ProfileData() when $default != null:
-return $default(_that.id,_that.fullName,_that.username,_that.email,_that.role,_that.status,_that.department,_that.position,_that.phone,_that.companies,_that.profileImage,_that.isVerified,_that.lastLoginAt,_that.createdAt,_that.updatedAt);case _:
+return $default(_that.id,_that.fullName,_that.username,_that.email,_that.role,_that.status,_that.department,_that.position,_that.phone,_that.companies,_that.profileImage,_that.isVerified,_that.lastLoginAt,_that.createdAt,_that.updatedAt,_that.keywords);case _:
return null;
}
@@ -223,7 +224,7 @@ return $default(_that.id,_that.fullName,_that.username,_that.email,_that.role,_t
@JsonSerializable()
class _ProfileData implements ProfileData {
- const _ProfileData({required this.id, @JsonKey(name: 'full_name') required this.fullName, required this.username, required this.email, required this.role, required this.status, required this.department, required this.position, required this.phone, required final List? companies, @JsonKey(name: 'profile_image') required this.profileImage, @JsonKey(name: 'is_verified') required this.isVerified, @JsonKey(name: 'last_login_at') required this.lastLoginAt, @JsonKey(name: 'created_at') required this.createdAt, @JsonKey(name: 'updated_at') required this.updatedAt}): _companies = companies;
+ const _ProfileData({required this.id, @JsonKey(name: 'full_name') required this.fullName, required this.username, required this.email, required this.role, required this.status, required this.department, required this.position, required this.phone, required final List? companies, @JsonKey(name: 'profile_image') required this.profileImage, @JsonKey(name: 'is_verified') required this.isVerified, @JsonKey(name: 'last_login_at') required this.lastLoginAt, @JsonKey(name: 'created_at') required this.createdAt, @JsonKey(name: 'updated_at') required this.updatedAt, final List? keywords}): _companies = companies,_keywords = keywords;
factory _ProfileData.fromJson(Map json) => _$ProfileDataFromJson(json);
@override final String? id;
@@ -249,6 +250,15 @@ class _ProfileData implements ProfileData {
@override@JsonKey(name: 'last_login_at') final int? lastLoginAt;
@override@JsonKey(name: 'created_at') final int? createdAt;
@override@JsonKey(name: 'updated_at') final int? updatedAt;
+ final List? _keywords;
+@override List? get keywords {
+ final value = _keywords;
+ if (value == null) return null;
+ if (_keywords is EqualUnmodifiableListView) return _keywords;
+ // ignore: implicit_dynamic_type
+ return EqualUnmodifiableListView(value);
+}
+
/// Create a copy of ProfileData
/// with the given fields replaced by the non-null parameter values.
@@ -263,16 +273,16 @@ Map toJson() {
@override
bool operator ==(Object other) {
- return identical(this, other) || (other.runtimeType == runtimeType&&other is _ProfileData&&(identical(other.id, id) || other.id == id)&&(identical(other.fullName, fullName) || other.fullName == fullName)&&(identical(other.username, username) || other.username == username)&&(identical(other.email, email) || other.email == email)&&(identical(other.role, role) || other.role == role)&&(identical(other.status, status) || other.status == status)&&(identical(other.department, department) || other.department == department)&&(identical(other.position, position) || other.position == position)&&(identical(other.phone, phone) || other.phone == phone)&&const DeepCollectionEquality().equals(other._companies, _companies)&&(identical(other.profileImage, profileImage) || other.profileImage == profileImage)&&(identical(other.isVerified, isVerified) || other.isVerified == isVerified)&&(identical(other.lastLoginAt, lastLoginAt) || other.lastLoginAt == lastLoginAt)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt));
+ return identical(this, other) || (other.runtimeType == runtimeType&&other is _ProfileData&&(identical(other.id, id) || other.id == id)&&(identical(other.fullName, fullName) || other.fullName == fullName)&&(identical(other.username, username) || other.username == username)&&(identical(other.email, email) || other.email == email)&&(identical(other.role, role) || other.role == role)&&(identical(other.status, status) || other.status == status)&&(identical(other.department, department) || other.department == department)&&(identical(other.position, position) || other.position == position)&&(identical(other.phone, phone) || other.phone == phone)&&const DeepCollectionEquality().equals(other._companies, _companies)&&(identical(other.profileImage, profileImage) || other.profileImage == profileImage)&&(identical(other.isVerified, isVerified) || other.isVerified == isVerified)&&(identical(other.lastLoginAt, lastLoginAt) || other.lastLoginAt == lastLoginAt)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&const DeepCollectionEquality().equals(other._keywords, _keywords));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
-int get hashCode => Object.hash(runtimeType,id,fullName,username,email,role,status,department,position,phone,const DeepCollectionEquality().hash(_companies),profileImage,isVerified,lastLoginAt,createdAt,updatedAt);
+int get hashCode => Object.hash(runtimeType,id,fullName,username,email,role,status,department,position,phone,const DeepCollectionEquality().hash(_companies),profileImage,isVerified,lastLoginAt,createdAt,updatedAt,const DeepCollectionEquality().hash(_keywords));
@override
String toString() {
- return 'ProfileData(id: $id, fullName: $fullName, username: $username, email: $email, role: $role, status: $status, department: $department, position: $position, phone: $phone, companies: $companies, profileImage: $profileImage, isVerified: $isVerified, lastLoginAt: $lastLoginAt, createdAt: $createdAt, updatedAt: $updatedAt)';
+ return 'ProfileData(id: $id, fullName: $fullName, username: $username, email: $email, role: $role, status: $status, department: $department, position: $position, phone: $phone, companies: $companies, profileImage: $profileImage, isVerified: $isVerified, lastLoginAt: $lastLoginAt, createdAt: $createdAt, updatedAt: $updatedAt, keywords: $keywords)';
}
@@ -283,7 +293,7 @@ abstract mixin class _$ProfileDataCopyWith<$Res> implements $ProfileDataCopyWith
factory _$ProfileDataCopyWith(_ProfileData value, $Res Function(_ProfileData) _then) = __$ProfileDataCopyWithImpl;
@override @useResult
$Res call({
- String? id,@JsonKey(name: 'full_name') String? fullName, String? username, String? email, String? role, String? status, String? department, String? position, String? phone, List? companies,@JsonKey(name: 'profile_image') String? profileImage,@JsonKey(name: 'is_verified') bool? isVerified,@JsonKey(name: 'last_login_at') int? lastLoginAt,@JsonKey(name: 'created_at') int? createdAt,@JsonKey(name: 'updated_at') int? updatedAt
+ String? id,@JsonKey(name: 'full_name') String? fullName, String? username, String? email, String? role, String? status, String? department, String? position, String? phone, List? companies,@JsonKey(name: 'profile_image') String? profileImage,@JsonKey(name: 'is_verified') bool? isVerified,@JsonKey(name: 'last_login_at') int? lastLoginAt,@JsonKey(name: 'created_at') int? createdAt,@JsonKey(name: 'updated_at') int? updatedAt, List? keywords
});
@@ -300,7 +310,7 @@ class __$ProfileDataCopyWithImpl<$Res>
/// Create a copy of ProfileData
/// with the given fields replaced by the non-null parameter values.
-@override @pragma('vm:prefer-inline') $Res call({Object? id = freezed,Object? fullName = freezed,Object? username = freezed,Object? email = freezed,Object? role = freezed,Object? status = freezed,Object? department = freezed,Object? position = freezed,Object? phone = freezed,Object? companies = freezed,Object? profileImage = freezed,Object? isVerified = freezed,Object? lastLoginAt = freezed,Object? createdAt = freezed,Object? updatedAt = freezed,}) {
+@override @pragma('vm:prefer-inline') $Res call({Object? id = freezed,Object? fullName = freezed,Object? username = freezed,Object? email = freezed,Object? role = freezed,Object? status = freezed,Object? department = freezed,Object? position = freezed,Object? phone = freezed,Object? companies = freezed,Object? profileImage = freezed,Object? isVerified = freezed,Object? lastLoginAt = freezed,Object? createdAt = freezed,Object? updatedAt = freezed,Object? keywords = freezed,}) {
return _then(_ProfileData(
id: freezed == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
as String?,fullName: freezed == fullName ? _self.fullName : fullName // ignore: cast_nullable_to_non_nullable
@@ -317,7 +327,8 @@ as String?,isVerified: freezed == isVerified ? _self.isVerified : isVerified //
as bool?,lastLoginAt: freezed == lastLoginAt ? _self.lastLoginAt : lastLoginAt // ignore: cast_nullable_to_non_nullable
as int?,createdAt: freezed == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable
as int?,updatedAt: freezed == updatedAt ? _self.updatedAt : updatedAt // ignore: cast_nullable_to_non_nullable
-as int?,
+as int?,keywords: freezed == keywords ? _self._keywords : keywords // ignore: cast_nullable_to_non_nullable
+as List?,
));
}
diff --git a/lib/data/services/model/profile_data/profile_data.g.dart b/lib/data/services/model/profile_data/profile_data.g.dart
index 30cfd4b..64c27a6 100644
--- a/lib/data/services/model/profile_data/profile_data.g.dart
+++ b/lib/data/services/model/profile_data/profile_data.g.dart
@@ -25,6 +25,8 @@ _ProfileData _$ProfileDataFromJson(Map json) => _ProfileData(
lastLoginAt: (json['last_login_at'] as num?)?.toInt(),
createdAt: (json['created_at'] as num?)?.toInt(),
updatedAt: (json['updated_at'] as num?)?.toInt(),
+ keywords:
+ (json['keywords'] as List?)?.map((e) => e as String).toList(),
);
Map _$ProfileDataToJson(_ProfileData instance) =>
@@ -44,4 +46,5 @@ Map _$ProfileDataToJson(_ProfileData instance) =>
'last_login_at': instance.lastLoginAt,
'created_at': instance.createdAt,
'updated_at': instance.updatedAt,
+ 'keywords': instance.keywords,
};
diff --git a/lib/data/services/profile_service.dart b/lib/data/services/profile_service.dart
index 8e52722..0515f9c 100644
--- a/lib/data/services/profile_service.dart
+++ b/lib/data/services/profile_service.dart
@@ -1,3 +1,5 @@
+import 'dart:convert';
+
import 'package:tm_app/data/services/api/profile_api.dart';
import 'package:tm_app/data/services/model/company_profile_response/company_profile_response.dart';
import 'package:tm_app/data/services/model/profile_response/profile_response.dart';
@@ -28,4 +30,17 @@ class ProfileService {
);
return result;
}
+
+ Future> updateProfileKeyWords({
+ required List keyWords,
+ }) async {
+ Map data = {'keywords': keyWords};
+ final result = await _networkManager.makeRequest(
+ ProfileApi.updateProfileKeyWords,
+ method: 'PUT',
+ data: jsonEncode(data),
+ (json) => ProfileResponse.fromJson(json),
+ );
+ return result;
+ }
}
diff --git a/lib/gen/assets.gen.dart b/lib/gen/assets.gen.dart
index 11c769e..695fe8f 100644
--- a/lib/gen/assets.gen.dart
+++ b/lib/gen/assets.gen.dart
@@ -77,6 +77,9 @@ class $AssetsIconsGen {
/// File path: assets/icons/edit.svg
String get edit => 'assets/icons/edit.svg';
+ /// File path: assets/icons/editting.svg
+ String get editting => 'assets/icons/editting.svg';
+
/// File path: assets/icons/filter.svg
String get filter => 'assets/icons/filter.svg';
@@ -200,6 +203,7 @@ class $AssetsIconsGen {
danger,
dislike,
edit,
+ editting,
filter,
home,
homeActive,
diff --git a/lib/view_models/profile_view_model.dart b/lib/view_models/profile_view_model.dart
index eb99915..fa2667a 100644
--- a/lib/view_models/profile_view_model.dart
+++ b/lib/view_models/profile_view_model.dart
@@ -94,4 +94,25 @@ class ProfileViewModel with ChangeNotifier {
_loggedOut = false;
notifyListeners();
}
+
+ Future updateProfileKeyWords({required List keyWords}) async {
+ _isLoading = true;
+ _errorMessage = null;
+ notifyListeners();
+ final result = await _profileRepository.updateProfileKeyWords(
+ keyWords: keyWords,
+ );
+ switch (result) {
+ case Ok():
+ _profileData = result.value.data;
+ break;
+ case Error():
+ _errorMessage = result.error.toString();
+ break;
+ }
+ _isLoading = false;
+ notifyListeners();
+ _errorMessage = null;
+ notifyListeners();
+ }
}
diff --git a/lib/views/profile/pages/d_profile_page.dart b/lib/views/profile/pages/d_profile_page.dart
index 0403d93..61ebc05 100644
--- a/lib/views/profile/pages/d_profile_page.dart
+++ b/lib/views/profile/pages/d_profile_page.dart
@@ -1,19 +1,18 @@
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';
+import '../widgets/subject_tab.dart';
class DesktopProfilePage extends StatefulWidget {
const DesktopProfilePage({super.key});
@@ -22,12 +21,15 @@ class DesktopProfilePage extends StatefulWidget {
State createState() => _DesktopProfilePageState();
}
-class _DesktopProfilePageState extends State {
+class _DesktopProfilePageState extends State
+ with SingleTickerProviderStateMixin {
late ProfileViewModel viewModel;
+ late final TabController tabController;
@override
void initState() {
super.initState();
viewModel = context.read();
+ tabController = TabController(length: 3, vsync: this);
viewModel.addListener(_profileListener);
}
@@ -56,150 +58,57 @@ class _DesktopProfilePageState extends State {
const DesktopNavigationWidget(
currentIndex: 4, // Tenders index
),
- Consumer(
- builder: (context, viewModel, child) {
- if (viewModel.isLoading) {
- return const Expanded(
- child: Center(
+ Expanded(
+ child: Consumer(
+ builder: (context, viewModel, child) {
+ if (viewModel.isLoading) {
+ return const Center(
child: CircularProgressIndicator(
color: AppColors.secondary50,
),
- ),
- );
- }
- if (viewModel.errorMessage != null) {
- return Expanded(
- child: Center(child: Text(viewModel.errorMessage!)),
- );
- }
+ );
+ }
+ if (viewModel.errorMessage != null) {
+ return Center(child: Text(viewModel.errorMessage!));
+ }
- if (viewModel.profileData == null) {
- return const Expanded(
- child: Center(child: Text(CommonStrings.noData)),
- );
- }
+ if (viewModel.profileData == null) {
+ return const Center(child: Text(CommonStrings.noData));
+ }
- return Expanded(
- child: SingleChildScrollView(
+ return SizedBox(
+ width: 740,
+ height: double.infinity,
child: Column(
children: [
- SizedBox(
- width: 740,
- child: Column(
+ const SizedBox(height: 46),
+ ProfileTabBar(
+ controller: tabController,
+ titles: [
+ ProfileStrings.mainDataTab,
+ ProfileStrings.membersTab,
+ ProfileStrings.subjectTab,
+ ],
+ ),
+ Expanded(
+ child: TabBarView(
+ controller: tabController,
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,
- ),
- ),
- ),
- ),
+ MainDataTab(
+ profileData: viewModel.profileData!,
+ onLogout: () => viewModel.logout(),
),
+ const MembersTab(),
+ const SubjectTab(),
],
),
),
],
),
- ),
- );
- },
- ),
- ],
- ),
- );
- }
-
- 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),
],
),
);
diff --git a/lib/views/profile/pages/m_profile_page.dart b/lib/views/profile/pages/m_profile_page.dart
index 6ea0337..9b9e616 100644
--- a/lib/views/profile/pages/m_profile_page.dart
+++ b/lib/views/profile/pages/m_profile_page.dart
@@ -1,11 +1,8 @@
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 'package:tm_app/views/profile/widgets/theme_toggle.dart';
-import '../../../core/constants/assets.dart';
import '../../../core/constants/common_strings.dart';
import '../../../core/theme/colors.dart';
import '../../../core/utils/app_toast.dart';
@@ -13,7 +10,10 @@ 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/title_description.dart';
+import '../widgets/main_data_tab.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});
@@ -22,12 +22,15 @@ class MobileProfilePage extends StatefulWidget {
State createState() => _MobileProfilePageState();
}
-class _MobileProfilePageState extends State {
+class _MobileProfilePageState extends State
+ with SingleTickerProviderStateMixin {
late ProfileViewModel viewModel;
+ late final TabController tabController;
@override
void initState() {
super.initState();
viewModel = context.read();
+ tabController = TabController(length: 3, vsync: this);
viewModel.addListener(_profileListener);
// Load notification info saved in SharedPreferences
}
@@ -53,137 +56,56 @@ class _MobileProfilePageState extends State {
return Scaffold(
backgroundColor: AppColors.backgroundColor,
appBar: tenderMobileAppBar(title: ProfileStrings.profileTitle),
- body: Consumer(
- builder: (context, viewModel, child) {
- if (viewModel.isLoading) {
- return const Center(
- child: CircularProgressIndicator(color: AppColors.secondary50),
- );
- }
+ body: SafeArea(
+ child: Consumer(
+ builder: (context, viewModel, child) {
+ if (viewModel.isLoading) {
+ return const Center(
+ child: CircularProgressIndicator(color: AppColors.secondary50),
+ );
+ }
- if (viewModel.errorMessage != null) {
- return Center(child: Text(viewModel.errorMessage!));
- }
+ if (viewModel.errorMessage != null) {
+ return Center(child: Text(viewModel.errorMessage!));
+ }
- if (viewModel.profileData == null) {
- return const Center(child: Text(CommonStrings.noData));
- }
+ if (viewModel.profileData == null) {
+ return const Center(child: Text(CommonStrings.noData));
+ }
- return SafeArea(
- child: Padding(
+ return Padding(
padding: EdgeInsets.symmetric(
horizontal: 24.0.w(),
vertical: 16.0.h(),
),
child: Column(
children: [
- SizedBox(height: 36.0.h()),
- Align(
- alignment: AlignmentDirectional.centerStart,
- child: Text(
- 'Last Notification',
- style: TextStyle(
- fontSize: 18.0.sp(),
- fontWeight: FontWeight.w600,
- color: AppColors.grey70,
- ),
- ),
+ ProfileTabBar(
+ controller: tabController,
+ titles: [
+ ProfileStrings.mainDataTab,
+ ProfileStrings.membersTab,
+ ProfileStrings.subjectTab,
+ ],
),
-
- SizedBox(height: 24.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: 32.0.h()),
- 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()),
- const Spacer(),
- InkWell(
- onTap: () => viewModel.logout(),
- borderRadius: BorderRadius.circular(100),
- child: Container(
- width: double.infinity,
- 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,
+ Expanded(
+ child: TabBarView(
+ controller: tabController,
+ children: [
+ MainDataTab(
+ profileData: viewModel.profileData!,
+ onLogout: () => viewModel.logout(),
),
- ),
+ const MembersTab(),
+ const SubjectTab(),
+ ],
),
),
],
),
- ),
- );
- },
- ),
- );
- }
-
- 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),
- ],
+ );
+ },
+ ),
),
);
}
diff --git a/lib/views/profile/pages/t_profile_page.dart b/lib/views/profile/pages/t_profile_page.dart
index ab3e8fe..83ea665 100644
--- a/lib/views/profile/pages/t_profile_page.dart
+++ b/lib/views/profile/pages/t_profile_page.dart
@@ -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 createState() => _TabletProfilePageState();
}
-class _TabletProfilePageState extends State {
+class _TabletProfilePageState extends State
+ with SingleTickerProviderStateMixin {
late ProfileViewModel viewModel;
+ late final TabController tabController;
@override
void initState() {
super.initState();
viewModel = context.read();
+ tabController = TabController(length: 3, vsync: this);
viewModel.addListener(_profileListener);
}
@@ -84,91 +86,25 @@ class _TabletProfilePageState extends State {
),
child: Column(
children: [
- SizedBox(height: 128.0.h()),
- TitleDescription(
- title: ProfileStrings.fullname,
- description: viewModel.profileData!.fullName ?? '',
+ ProfileTabBar(
+ controller: tabController,
+ titles: [
+ ProfileStrings.mainDataTab,
+ ProfileStrings.membersTab,
+ ProfileStrings.subjectTab,
+ ],
),
- 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),
+ Expanded(
+ child: TabBarView(
+ controller: tabController,
+ children: [
+ MainDataTab(
+ profileData: viewModel.profileData!,
+ onLogout: () => viewModel.logout(),
),
- alignment: Alignment.center,
- child: Text(
- ProfileStrings.logout,
- style: TextStyle(
- fontSize: 14.0.sp(),
- fontWeight: FontWeight.w500,
- color: AppColors.primaryColor,
- ),
- ),
- ),
+ const MembersTab(),
+ const Center(child: Text('Subject Tab Content')),
+ ],
),
),
],
@@ -179,32 +115,4 @@ class _TabletProfilePageState extends State {
},
);
}
-
- 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),
- ],
- ),
- );
- }
}
diff --git a/lib/views/profile/strings/profile_strings.dart b/lib/views/profile/strings/profile_strings.dart
index 5baa79a..ab08801 100644
--- a/lib/views/profile/strings/profile_strings.dart
+++ b/lib/views/profile/strings/profile_strings.dart
@@ -20,4 +20,7 @@ class ProfileStrings {
static const String position = 'position';
static const String phone = 'phone';
static const String fullname = 'full name';
+ static const String mainDataTab = 'Main Data';
+ static const String membersTab = 'Members';
+ static const String subjectTab = 'Subject';
}
diff --git a/lib/views/profile/widgets/edit_key_word_bottom_sheet.dart b/lib/views/profile/widgets/edit_key_word_bottom_sheet.dart
new file mode 100644
index 0000000..1a32b8d
--- /dev/null
+++ b/lib/views/profile/widgets/edit_key_word_bottom_sheet.dart
@@ -0,0 +1,194 @@
+import 'package:flutter/material.dart';
+import 'package:flutter_svg/svg.dart';
+import 'package:go_router/go_router.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 StatefulWidget {
+ const EditKeyWordBottomSheet({
+ required this.keyWords,
+ required this.isDesktop,
+ this.onSubmit,
+ super.key,
+ });
+ final List keyWords;
+ final Future Function(List keyWords)? onSubmit;
+ final bool isDesktop;
+
+ @override
+ State createState() => _EditKeyWordBottomSheetState();
+}
+
+class _EditKeyWordBottomSheetState extends State {
+ final TextEditingController _controller = TextEditingController();
+ final FocusNode _focusNode = FocusNode();
+ late final List _keywords = List.from(widget.keyWords);
+ @override
+ Widget build(BuildContext context) {
+ return Wrap(
+ children: [
+ Container(
+ width: double.infinity,
+ padding: EdgeInsets.only(
+ left: 24.0.w(),
+ right: 24.0.w(),
+ top: 24.0.h(),
+ bottom: MediaQuery.of(context).viewInsets.bottom,
+ ),
+ decoration: BoxDecoration(
+ color: AppColors.backgroundColor,
+ borderRadius: BorderRadius.vertical(
+ top: const Radius.circular(24),
+ bottom:
+ widget.isDesktop ? const Radius.circular(24) : Radius.zero,
+ ),
+ ),
+ child: Column(
+ mainAxisSize: MainAxisSize.min,
+ children: [
+ Row(
+ mainAxisAlignment: MainAxisAlignment.spaceBetween,
+ children: [
+ Text(
+ 'Edit Key Word',
+ style: TextStyle(
+ fontSize: 20.0.sp(),
+ fontWeight: FontWeight.w600,
+ ),
+ ),
+ IconButton(
+ onPressed: () => context.pop(),
+ icon: 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(
+ focusNode: _focusNode,
+ controller: _controller,
+ onSubmitted: (value) {
+ if (value.trim().isEmpty) {
+ return;
+ }
+
+ setState(() {
+ _keywords.add(value);
+ });
+ _controller.clear();
+
+ Future.delayed(const Duration(milliseconds: 50), () {
+ _focusNode.requestFocus();
+ });
+ },
+ 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()),
+ SizedBox(
+ height: 200.0.h(), // Set a fixed height for the container
+ child: SingleChildScrollView(
+ child: Wrap(
+ spacing: 8,
+ runSpacing: 16.0.h(),
+ children:
+ _keywords
+ .map(
+ (e) => _chip(
+ label: e,
+ backgroundColor: Colors.green[200]!,
+ onDelete:
+ () => setState(() {
+ _keywords.remove(e);
+ }),
+ ),
+ )
+ .toList(),
+ // [
+ // _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: () async {
+ await widget.onSubmit?.call(_keywords);
+ if (context.mounted) {
+ context.pop();
+ }
+ },
+ ),
+ SizedBox(height: 24.0.h()),
+ ],
+ ),
+ ),
+ ],
+ );
+ }
+
+ Widget _chip({
+ required String label,
+ required Color backgroundColor,
+ required VoidCallback onDelete,
+ }) {
+ return InputChip(
+ label: Text(label),
+ backgroundColor: backgroundColor,
+ onDeleted: onDelete,
+ shape: RoundedRectangleBorder(
+ borderRadius: BorderRadius.circular(8),
+ side: const BorderSide(color: Colors.transparent),
+ ),
+ );
+ }
+}
diff --git a/lib/views/profile/widgets/location_vottom_sheet.dart b/lib/views/profile/widgets/location_vottom_sheet.dart
new file mode 100644
index 0000000..448851d
--- /dev/null
+++ b/lib/views/profile/widgets/location_vottom_sheet.dart
@@ -0,0 +1,297 @@
+import 'package:flutter/material.dart';
+import 'package:go_router/go_router.dart';
+import 'package:tm_app/core/utils/size_config.dart';
+
+// Note: Ensure your size_config and theme imports are present
+// import 'package:tm_app/core/utils/size_config.dart';
+// import 'package:tm_app/core/theme/colors.dart';
+
+class EditKeyWordBottomSheet extends StatefulWidget {
+ const EditKeyWordBottomSheet({super.key});
+
+ @override
+ State createState() => _EditKeyWordBottomSheetState();
+}
+
+class _EditKeyWordBottomSheetState extends State {
+ final List _keywords = [
+ '1234',
+ 'Lorem ipsum',
+ 'sample',
+ 'Unknown',
+ 'Lorem ipsum 02',
+ ];
+
+ // Location selection state
+ bool _isDropdownOpen = false;
+ final List