Merge pull request 'profile_changes' (#224) from profile_changes into develop
Reviewed-on: https://repo.ravanertebat.com/TM/tm_app/pulls/224
This commit is contained in:
@@ -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 |
@@ -71,6 +71,7 @@ class AssetsManager {
|
|||||||
//profile page
|
//profile page
|
||||||
static const sun = 'assets/icons/sun.svg';
|
static const sun = 'assets/icons/sun.svg';
|
||||||
static const moon = 'assets/icons/moon.svg';
|
static const moon = 'assets/icons/moon.svg';
|
||||||
|
static const editting = 'assets/icons/editting.svg';
|
||||||
|
|
||||||
//Liked tenders
|
//Liked tenders
|
||||||
static const trash = 'assets/icons/trash.svg';
|
static const trash = 'assets/icons/trash.svg';
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
String timeConvertor(int time) {
|
import 'package:intl/intl.dart';
|
||||||
DateTime date = DateTime.fromMillisecondsSinceEpoch(time);
|
|
||||||
|
|
||||||
String formattedDate =
|
String timeConvertor(int timestamp) {
|
||||||
"${date.year}-${date.month.toString().padLeft(2, '0')}-${date.day.toString().padLeft(2, '0')}";
|
// Convert seconds → milliseconds
|
||||||
|
final date = DateTime.fromMillisecondsSinceEpoch(timestamp * 1000);
|
||||||
|
|
||||||
return formattedDate;
|
// Format to yyyy-MM-dd
|
||||||
|
return DateFormat('yyyy-MM-dd').format(date);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,4 +16,10 @@ class ProfileRepository {
|
|||||||
Future<Result<CompanyProfileResponse>> getCompanyProfile() {
|
Future<Result<CompanyProfileResponse>> getCompanyProfile() {
|
||||||
return _profileService.getCompanyProfile();
|
return _profileService.getCompanyProfile();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<Result<ProfileResponse>> updateProfileKeyWords({
|
||||||
|
required List<String> keyWords,
|
||||||
|
}) {
|
||||||
|
return _profileService.updateProfileKeyWords(keyWords: keyWords);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
class ProfileApi {
|
class ProfileApi {
|
||||||
static const String profile = '/api/v1/profile';
|
static const String profile = '/api/v1/profile';
|
||||||
static const String companyProfile = '/api/v1/companies';
|
static const String companyProfile = '/api/v1/companies';
|
||||||
|
static const String updateProfileKeyWords = '/api/v1/profile/keywords';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ abstract class ProfileData with _$ProfileData {
|
|||||||
@JsonKey(name: 'last_login_at') required int? lastLoginAt,
|
@JsonKey(name: 'last_login_at') required int? lastLoginAt,
|
||||||
@JsonKey(name: 'created_at') required int? createdAt,
|
@JsonKey(name: 'created_at') required int? createdAt,
|
||||||
@JsonKey(name: 'updated_at') required int? updatedAt,
|
@JsonKey(name: 'updated_at') required int? updatedAt,
|
||||||
|
List<String>? keywords,
|
||||||
}) = _ProfileData;
|
}) = _ProfileData;
|
||||||
|
|
||||||
factory ProfileData.fromJson(Map<String, Object?> json) =>
|
factory ProfileData.fromJson(Map<String, Object?> json) =>
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ T _$identity<T>(T value) => value;
|
|||||||
/// @nodoc
|
/// @nodoc
|
||||||
mixin _$ProfileData {
|
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<CompanyProfileData>? 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<CompanyProfileData>? 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<String>? get keywords;
|
||||||
/// Create a copy of ProfileData
|
/// Create a copy of ProfileData
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
@@ -28,16 +28,16 @@ $ProfileDataCopyWith<ProfileData> get copyWith => _$ProfileDataCopyWithImpl<Prof
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
bool operator ==(Object other) {
|
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)
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
@override
|
@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
|
@override
|
||||||
String toString() {
|
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;
|
factory $ProfileDataCopyWith(ProfileData value, $Res Function(ProfileData) _then) = _$ProfileDataCopyWithImpl;
|
||||||
@useResult
|
@useResult
|
||||||
$Res call({
|
$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<CompanyProfileData>? 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<CompanyProfileData>? 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<String>? keywords
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
@@ -65,7 +65,7 @@ class _$ProfileDataCopyWithImpl<$Res>
|
|||||||
|
|
||||||
/// Create a copy of ProfileData
|
/// Create a copy of ProfileData
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
/// 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(
|
return _then(_self.copyWith(
|
||||||
id: freezed == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
|
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
|
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 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?,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?,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<String>?,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -167,10 +168,10 @@ return $default(_that);case _:
|
|||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
|
|
||||||
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(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<CompanyProfileData>? 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 extends Object?>(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<CompanyProfileData>? 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<String>? keywords)? $default,{required TResult orElse(),}) {final _that = this;
|
||||||
switch (_that) {
|
switch (_that) {
|
||||||
case _ProfileData() when $default != null:
|
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();
|
return orElse();
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -188,10 +189,10 @@ return $default(_that.id,_that.fullName,_that.username,_that.email,_that.role,_t
|
|||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
|
|
||||||
@optionalTypeArgs TResult when<TResult extends Object?>(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<CompanyProfileData>? 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 extends Object?>(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<CompanyProfileData>? 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<String>? keywords) $default,) {final _that = this;
|
||||||
switch (_that) {
|
switch (_that) {
|
||||||
case _ProfileData():
|
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');
|
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 extends Object?>(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<CompanyProfileData>? 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 extends Object?>(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<CompanyProfileData>? 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<String>? keywords)? $default,) {final _that = this;
|
||||||
switch (_that) {
|
switch (_that) {
|
||||||
case _ProfileData() when $default != null:
|
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;
|
return null;
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -223,7 +224,7 @@ return $default(_that.id,_that.fullName,_that.username,_that.email,_that.role,_t
|
|||||||
@JsonSerializable()
|
@JsonSerializable()
|
||||||
|
|
||||||
class _ProfileData implements ProfileData {
|
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<CompanyProfileData>? 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<CompanyProfileData>? 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<String>? keywords}): _companies = companies,_keywords = keywords;
|
||||||
factory _ProfileData.fromJson(Map<String, dynamic> json) => _$ProfileDataFromJson(json);
|
factory _ProfileData.fromJson(Map<String, dynamic> json) => _$ProfileDataFromJson(json);
|
||||||
|
|
||||||
@override final String? id;
|
@override final String? id;
|
||||||
@@ -249,6 +250,15 @@ class _ProfileData implements ProfileData {
|
|||||||
@override@JsonKey(name: 'last_login_at') final int? lastLoginAt;
|
@override@JsonKey(name: 'last_login_at') final int? lastLoginAt;
|
||||||
@override@JsonKey(name: 'created_at') final int? createdAt;
|
@override@JsonKey(name: 'created_at') final int? createdAt;
|
||||||
@override@JsonKey(name: 'updated_at') final int? updatedAt;
|
@override@JsonKey(name: 'updated_at') final int? updatedAt;
|
||||||
|
final List<String>? _keywords;
|
||||||
|
@override List<String>? 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
|
/// Create a copy of ProfileData
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
@@ -263,16 +273,16 @@ Map<String, dynamic> toJson() {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
bool operator ==(Object other) {
|
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)
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
@override
|
@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
|
@override
|
||||||
String toString() {
|
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;
|
factory _$ProfileDataCopyWith(_ProfileData value, $Res Function(_ProfileData) _then) = __$ProfileDataCopyWithImpl;
|
||||||
@override @useResult
|
@override @useResult
|
||||||
$Res call({
|
$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<CompanyProfileData>? 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<CompanyProfileData>? 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<String>? keywords
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
@@ -300,7 +310,7 @@ class __$ProfileDataCopyWithImpl<$Res>
|
|||||||
|
|
||||||
/// Create a copy of ProfileData
|
/// Create a copy of ProfileData
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
/// 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(
|
return _then(_ProfileData(
|
||||||
id: freezed == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
|
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
|
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 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?,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?,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<String>?,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -25,6 +25,8 @@ _ProfileData _$ProfileDataFromJson(Map<String, dynamic> json) => _ProfileData(
|
|||||||
lastLoginAt: (json['last_login_at'] as num?)?.toInt(),
|
lastLoginAt: (json['last_login_at'] as num?)?.toInt(),
|
||||||
createdAt: (json['created_at'] as num?)?.toInt(),
|
createdAt: (json['created_at'] as num?)?.toInt(),
|
||||||
updatedAt: (json['updated_at'] as num?)?.toInt(),
|
updatedAt: (json['updated_at'] as num?)?.toInt(),
|
||||||
|
keywords:
|
||||||
|
(json['keywords'] as List<dynamic>?)?.map((e) => e as String).toList(),
|
||||||
);
|
);
|
||||||
|
|
||||||
Map<String, dynamic> _$ProfileDataToJson(_ProfileData instance) =>
|
Map<String, dynamic> _$ProfileDataToJson(_ProfileData instance) =>
|
||||||
@@ -44,4 +46,5 @@ Map<String, dynamic> _$ProfileDataToJson(_ProfileData instance) =>
|
|||||||
'last_login_at': instance.lastLoginAt,
|
'last_login_at': instance.lastLoginAt,
|
||||||
'created_at': instance.createdAt,
|
'created_at': instance.createdAt,
|
||||||
'updated_at': instance.updatedAt,
|
'updated_at': instance.updatedAt,
|
||||||
|
'keywords': instance.keywords,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import 'dart:convert';
|
||||||
|
|
||||||
import 'package:tm_app/data/services/api/profile_api.dart';
|
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/company_profile_response/company_profile_response.dart';
|
||||||
import 'package:tm_app/data/services/model/profile_response/profile_response.dart';
|
import 'package:tm_app/data/services/model/profile_response/profile_response.dart';
|
||||||
@@ -28,4 +30,17 @@ class ProfileService {
|
|||||||
);
|
);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<Result<ProfileResponse>> updateProfileKeyWords({
|
||||||
|
required List<String> keyWords,
|
||||||
|
}) async {
|
||||||
|
Map<String, dynamic> data = {'keywords': keyWords};
|
||||||
|
final result = await _networkManager.makeRequest(
|
||||||
|
ProfileApi.updateProfileKeyWords,
|
||||||
|
method: 'PUT',
|
||||||
|
data: jsonEncode(data),
|
||||||
|
(json) => ProfileResponse.fromJson(json),
|
||||||
|
);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -77,6 +77,9 @@ class $AssetsIconsGen {
|
|||||||
/// File path: assets/icons/edit.svg
|
/// File path: assets/icons/edit.svg
|
||||||
String get edit => '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
|
/// File path: assets/icons/filter.svg
|
||||||
String get filter => 'assets/icons/filter.svg';
|
String get filter => 'assets/icons/filter.svg';
|
||||||
|
|
||||||
@@ -200,6 +203,7 @@ class $AssetsIconsGen {
|
|||||||
danger,
|
danger,
|
||||||
dislike,
|
dislike,
|
||||||
edit,
|
edit,
|
||||||
|
editting,
|
||||||
filter,
|
filter,
|
||||||
home,
|
home,
|
||||||
homeActive,
|
homeActive,
|
||||||
|
|||||||
@@ -94,4 +94,25 @@ class ProfileViewModel with ChangeNotifier {
|
|||||||
_loggedOut = false;
|
_loggedOut = false;
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> updateProfileKeyWords({required List<String> keyWords}) async {
|
||||||
|
_isLoading = true;
|
||||||
|
_errorMessage = null;
|
||||||
|
notifyListeners();
|
||||||
|
final result = await _profileRepository.updateProfileKeyWords(
|
||||||
|
keyWords: keyWords,
|
||||||
|
);
|
||||||
|
switch (result) {
|
||||||
|
case Ok<ProfileResponse>():
|
||||||
|
_profileData = result.value.data;
|
||||||
|
break;
|
||||||
|
case Error<ProfileResponse>():
|
||||||
|
_errorMessage = result.error.toString();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
_isLoading = false;
|
||||||
|
notifyListeners();
|
||||||
|
_errorMessage = null;
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,19 +1,18 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_svg/svg.dart';
|
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'package:tm_app/core/routes/app_routes.dart';
|
import 'package:tm_app/core/routes/app_routes.dart';
|
||||||
import 'package:tm_app/view_models/profile_view_model.dart';
|
import 'package:tm_app/view_models/profile_view_model.dart';
|
||||||
|
|
||||||
import '../../../core/constants/assets.dart';
|
|
||||||
import '../../../core/constants/common_strings.dart';
|
import '../../../core/constants/common_strings.dart';
|
||||||
import '../../../core/theme/colors.dart';
|
import '../../../core/theme/colors.dart';
|
||||||
import '../../../core/utils/app_toast.dart';
|
import '../../../core/utils/app_toast.dart';
|
||||||
import '../../../core/utils/size_config.dart';
|
|
||||||
import '../../../view_models/auth_view_model.dart';
|
import '../../../view_models/auth_view_model.dart';
|
||||||
import '../../shared/desktop_navigation_widget.dart';
|
import '../../shared/desktop_navigation_widget.dart';
|
||||||
import '../strings/profile_strings.dart';
|
import '../strings/profile_strings.dart';
|
||||||
import '../widgets/theme_toggle.dart';
|
import '../widgets/main_data_tab.dart';
|
||||||
import '../widgets/title_description.dart';
|
import '../widgets/members_tab.dart';
|
||||||
|
import '../widgets/profile_tab_bar.dart';
|
||||||
|
import '../widgets/subject_tab.dart';
|
||||||
|
|
||||||
class DesktopProfilePage extends StatefulWidget {
|
class DesktopProfilePage extends StatefulWidget {
|
||||||
const DesktopProfilePage({super.key});
|
const DesktopProfilePage({super.key});
|
||||||
@@ -22,12 +21,15 @@ class DesktopProfilePage extends StatefulWidget {
|
|||||||
State<DesktopProfilePage> createState() => _DesktopProfilePageState();
|
State<DesktopProfilePage> createState() => _DesktopProfilePageState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _DesktopProfilePageState extends State<DesktopProfilePage> {
|
class _DesktopProfilePageState extends State<DesktopProfilePage>
|
||||||
|
with SingleTickerProviderStateMixin {
|
||||||
late ProfileViewModel viewModel;
|
late ProfileViewModel viewModel;
|
||||||
|
late final TabController tabController;
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
viewModel = context.read<ProfileViewModel>();
|
viewModel = context.read<ProfileViewModel>();
|
||||||
|
tabController = TabController(length: 3, vsync: this);
|
||||||
viewModel.addListener(_profileListener);
|
viewModel.addListener(_profileListener);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -56,150 +58,57 @@ class _DesktopProfilePageState extends State<DesktopProfilePage> {
|
|||||||
const DesktopNavigationWidget(
|
const DesktopNavigationWidget(
|
||||||
currentIndex: 4, // Tenders index
|
currentIndex: 4, // Tenders index
|
||||||
),
|
),
|
||||||
Consumer<ProfileViewModel>(
|
Expanded(
|
||||||
builder: (context, viewModel, child) {
|
child: Consumer<ProfileViewModel>(
|
||||||
if (viewModel.isLoading) {
|
builder: (context, viewModel, child) {
|
||||||
return const Expanded(
|
if (viewModel.isLoading) {
|
||||||
child: Center(
|
return const Center(
|
||||||
child: CircularProgressIndicator(
|
child: CircularProgressIndicator(
|
||||||
color: AppColors.secondary50,
|
color: AppColors.secondary50,
|
||||||
),
|
),
|
||||||
),
|
);
|
||||||
);
|
}
|
||||||
}
|
if (viewModel.errorMessage != null) {
|
||||||
if (viewModel.errorMessage != null) {
|
return Center(child: Text(viewModel.errorMessage!));
|
||||||
return Expanded(
|
}
|
||||||
child: Center(child: Text(viewModel.errorMessage!)),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (viewModel.profileData == null) {
|
if (viewModel.profileData == null) {
|
||||||
return const Expanded(
|
return const Center(child: Text(CommonStrings.noData));
|
||||||
child: Center(child: Text(CommonStrings.noData)),
|
}
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return Expanded(
|
return SizedBox(
|
||||||
child: SingleChildScrollView(
|
width: 740,
|
||||||
|
height: double.infinity,
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
SizedBox(
|
const SizedBox(height: 46),
|
||||||
width: 740,
|
ProfileTabBar(
|
||||||
child: Column(
|
controller: tabController,
|
||||||
|
titles: [
|
||||||
|
ProfileStrings.mainDataTab,
|
||||||
|
ProfileStrings.membersTab,
|
||||||
|
ProfileStrings.subjectTab,
|
||||||
|
],
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: TabBarView(
|
||||||
|
controller: tabController,
|
||||||
children: [
|
children: [
|
||||||
SizedBox(height: 36.0.h()),
|
MainDataTab(
|
||||||
TitleDescription(
|
profileData: viewModel.profileData!,
|
||||||
title: ProfileStrings.fullname,
|
onLogout: () => viewModel.logout(),
|
||||||
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 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),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,11 +1,8 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_svg/svg.dart';
|
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'package:tm_app/core/routes/app_routes.dart';
|
import 'package:tm_app/core/routes/app_routes.dart';
|
||||||
import 'package:tm_app/view_models/profile_view_model.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/constants/common_strings.dart';
|
||||||
import '../../../core/theme/colors.dart';
|
import '../../../core/theme/colors.dart';
|
||||||
import '../../../core/utils/app_toast.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 '../../../view_models/auth_view_model.dart';
|
||||||
import '../../shared/tender_app_bar.dart';
|
import '../../shared/tender_app_bar.dart';
|
||||||
import '../strings/profile_strings.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 {
|
class MobileProfilePage extends StatefulWidget {
|
||||||
const MobileProfilePage({super.key});
|
const MobileProfilePage({super.key});
|
||||||
@@ -22,12 +22,15 @@ class MobileProfilePage extends StatefulWidget {
|
|||||||
State<MobileProfilePage> createState() => _MobileProfilePageState();
|
State<MobileProfilePage> createState() => _MobileProfilePageState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _MobileProfilePageState extends State<MobileProfilePage> {
|
class _MobileProfilePageState extends State<MobileProfilePage>
|
||||||
|
with SingleTickerProviderStateMixin {
|
||||||
late ProfileViewModel viewModel;
|
late ProfileViewModel viewModel;
|
||||||
|
late final TabController tabController;
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
viewModel = context.read<ProfileViewModel>();
|
viewModel = context.read<ProfileViewModel>();
|
||||||
|
tabController = TabController(length: 3, vsync: this);
|
||||||
viewModel.addListener(_profileListener);
|
viewModel.addListener(_profileListener);
|
||||||
// Load notification info saved in SharedPreferences
|
// Load notification info saved in SharedPreferences
|
||||||
}
|
}
|
||||||
@@ -53,137 +56,56 @@ class _MobileProfilePageState extends State<MobileProfilePage> {
|
|||||||
return Scaffold(
|
return Scaffold(
|
||||||
backgroundColor: AppColors.backgroundColor,
|
backgroundColor: AppColors.backgroundColor,
|
||||||
appBar: tenderMobileAppBar(title: ProfileStrings.profileTitle),
|
appBar: tenderMobileAppBar(title: ProfileStrings.profileTitle),
|
||||||
body: Consumer<ProfileViewModel>(
|
body: SafeArea(
|
||||||
builder: (context, viewModel, child) {
|
child: Consumer<ProfileViewModel>(
|
||||||
if (viewModel.isLoading) {
|
builder: (context, viewModel, child) {
|
||||||
return const Center(
|
if (viewModel.isLoading) {
|
||||||
child: CircularProgressIndicator(color: AppColors.secondary50),
|
return const Center(
|
||||||
);
|
child: CircularProgressIndicator(color: AppColors.secondary50),
|
||||||
}
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if (viewModel.errorMessage != null) {
|
if (viewModel.errorMessage != null) {
|
||||||
return Center(child: Text(viewModel.errorMessage!));
|
return Center(child: Text(viewModel.errorMessage!));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (viewModel.profileData == null) {
|
if (viewModel.profileData == null) {
|
||||||
return const Center(child: Text(CommonStrings.noData));
|
return const Center(child: Text(CommonStrings.noData));
|
||||||
}
|
}
|
||||||
|
|
||||||
return SafeArea(
|
return Padding(
|
||||||
child: Padding(
|
|
||||||
padding: EdgeInsets.symmetric(
|
padding: EdgeInsets.symmetric(
|
||||||
horizontal: 24.0.w(),
|
horizontal: 24.0.w(),
|
||||||
vertical: 16.0.h(),
|
vertical: 16.0.h(),
|
||||||
),
|
),
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
SizedBox(height: 36.0.h()),
|
ProfileTabBar(
|
||||||
Align(
|
controller: tabController,
|
||||||
alignment: AlignmentDirectional.centerStart,
|
titles: [
|
||||||
child: Text(
|
ProfileStrings.mainDataTab,
|
||||||
'Last Notification',
|
ProfileStrings.membersTab,
|
||||||
style: TextStyle(
|
ProfileStrings.subjectTab,
|
||||||
fontSize: 18.0.sp(),
|
],
|
||||||
fontWeight: FontWeight.w600,
|
|
||||||
color: AppColors.grey70,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
|
Expanded(
|
||||||
SizedBox(height: 24.0.h()),
|
child: TabBarView(
|
||||||
TitleDescription(
|
controller: tabController,
|
||||||
title: ProfileStrings.fullname,
|
children: [
|
||||||
description: viewModel.profileData!.fullName ?? '',
|
MainDataTab(
|
||||||
),
|
profileData: viewModel.profileData!,
|
||||||
TitleDescription(
|
onLogout: () => viewModel.logout(),
|
||||||
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,
|
|
||||||
),
|
),
|
||||||
),
|
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),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_svg/svg.dart';
|
|
||||||
import 'package:provider/provider.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/constants/common_strings.dart';
|
||||||
import 'package:tm_app/core/routes/app_routes.dart';
|
import 'package:tm_app/core/routes/app_routes.dart';
|
||||||
import 'package:tm_app/view_models/profile_view_model.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/tablet_navigation_widget.dart';
|
||||||
import '../../shared/tender_app_bar.dart';
|
import '../../shared/tender_app_bar.dart';
|
||||||
import '../strings/profile_strings.dart';
|
import '../strings/profile_strings.dart';
|
||||||
import '../widgets/theme_toggle.dart';
|
import '../widgets/main_data_tab.dart';
|
||||||
import '../widgets/title_description.dart';
|
import '../widgets/members_tab.dart';
|
||||||
|
import '../widgets/profile_tab_bar.dart';
|
||||||
|
|
||||||
class TabletProfilePage extends StatefulWidget {
|
class TabletProfilePage extends StatefulWidget {
|
||||||
const TabletProfilePage({super.key});
|
const TabletProfilePage({super.key});
|
||||||
@@ -23,12 +22,15 @@ class TabletProfilePage extends StatefulWidget {
|
|||||||
State<TabletProfilePage> createState() => _TabletProfilePageState();
|
State<TabletProfilePage> createState() => _TabletProfilePageState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _TabletProfilePageState extends State<TabletProfilePage> {
|
class _TabletProfilePageState extends State<TabletProfilePage>
|
||||||
|
with SingleTickerProviderStateMixin {
|
||||||
late ProfileViewModel viewModel;
|
late ProfileViewModel viewModel;
|
||||||
|
late final TabController tabController;
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
viewModel = context.read<ProfileViewModel>();
|
viewModel = context.read<ProfileViewModel>();
|
||||||
|
tabController = TabController(length: 3, vsync: this);
|
||||||
viewModel.addListener(_profileListener);
|
viewModel.addListener(_profileListener);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -84,91 +86,25 @@ class _TabletProfilePageState extends State<TabletProfilePage> {
|
|||||||
),
|
),
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
SizedBox(height: 128.0.h()),
|
ProfileTabBar(
|
||||||
TitleDescription(
|
controller: tabController,
|
||||||
title: ProfileStrings.fullname,
|
titles: [
|
||||||
description: viewModel.profileData!.fullName ?? '',
|
ProfileStrings.mainDataTab,
|
||||||
|
ProfileStrings.membersTab,
|
||||||
|
ProfileStrings.subjectTab,
|
||||||
|
],
|
||||||
),
|
),
|
||||||
TitleDescription(
|
Expanded(
|
||||||
title: ProfileStrings.username,
|
child: TabBarView(
|
||||||
description: viewModel.profileData!.username ?? '',
|
controller: tabController,
|
||||||
),
|
children: [
|
||||||
TitleDescription(
|
MainDataTab(
|
||||||
title: ProfileStrings.email,
|
profileData: viewModel.profileData!,
|
||||||
description: viewModel.profileData!.email ?? '',
|
onLogout: () => viewModel.logout(),
|
||||||
),
|
|
||||||
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,
|
const MembersTab(),
|
||||||
child: Text(
|
const Center(child: Text('Subject Tab Content')),
|
||||||
ProfileStrings.logout,
|
],
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 14.0.sp(),
|
|
||||||
fontWeight: FontWeight.w500,
|
|
||||||
color: AppColors.primaryColor,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@@ -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),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,4 +20,7 @@ class ProfileStrings {
|
|||||||
static const String position = 'position';
|
static const String position = 'position';
|
||||||
static const String phone = 'phone';
|
static const String phone = 'phone';
|
||||||
static const String fullname = 'full name';
|
static const String fullname = 'full name';
|
||||||
|
static const String mainDataTab = 'Main Data';
|
||||||
|
static const String membersTab = 'Members';
|
||||||
|
static const String subjectTab = 'Subject';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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<String> keyWords;
|
||||||
|
final Future<void> Function(List<String> keyWords)? onSubmit;
|
||||||
|
final bool isDesktop;
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<EditKeyWordBottomSheet> createState() => _EditKeyWordBottomSheetState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _EditKeyWordBottomSheetState extends State<EditKeyWordBottomSheet> {
|
||||||
|
final TextEditingController _controller = TextEditingController();
|
||||||
|
final FocusNode _focusNode = FocusNode();
|
||||||
|
late final List<String> _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),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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<EditKeyWordBottomSheet> createState() => _EditKeyWordBottomSheetState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _EditKeyWordBottomSheetState extends State<EditKeyWordBottomSheet> {
|
||||||
|
final List<String> _keywords = [
|
||||||
|
'1234',
|
||||||
|
'Lorem ipsum',
|
||||||
|
'sample',
|
||||||
|
'Unknown',
|
||||||
|
'Lorem ipsum 02',
|
||||||
|
];
|
||||||
|
|
||||||
|
// Location selection state
|
||||||
|
bool _isDropdownOpen = false;
|
||||||
|
final List<Map<String, String>> _selectedCountries = [
|
||||||
|
{'code': 'NO', 'name': 'Norway', 'flag': '🇳🇴'},
|
||||||
|
{'code': 'SE', 'name': 'Sweden', 'flag': '🇸🇪'},
|
||||||
|
];
|
||||||
|
|
||||||
|
final List<Map<String, String>> _allCountries = [
|
||||||
|
{'code': 'AU', 'name': 'Australia', 'flag': '🇦🇺'},
|
||||||
|
{'code': 'AT', 'name': 'Austria', 'flag': '🇦🇹'},
|
||||||
|
{'code': 'AZ', 'name': 'Azerbaijan', 'flag': '🇦🇿'},
|
||||||
|
{'code': 'BS', 'name': 'Bahamas', 'flag': '🇧🇸'},
|
||||||
|
{'code': 'BH', 'name': 'Bahrain', 'flag': '🇧🇭'},
|
||||||
|
];
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return 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 + 24.0.h(),
|
||||||
|
),
|
||||||
|
decoration: const BoxDecoration(
|
||||||
|
color: Colors.white,
|
||||||
|
borderRadius: BorderRadius.vertical(top: Radius.circular(32)),
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
// KEY WORD SECTION
|
||||||
|
_buildSectionHeader('Key Word'),
|
||||||
|
SizedBox(height: 16.0.h()),
|
||||||
|
Wrap(
|
||||||
|
spacing: 8,
|
||||||
|
runSpacing: 12.0.h(),
|
||||||
|
children: _keywords.map((e) => _buildKeywordChip(e)).toList(),
|
||||||
|
),
|
||||||
|
|
||||||
|
SizedBox(height: 32.0.h()),
|
||||||
|
const Divider(color: Color(0xFFF1F1F1)),
|
||||||
|
SizedBox(height: 24.0.h()),
|
||||||
|
|
||||||
|
// LOCATION SECTION
|
||||||
|
_buildSectionHeader('Location'),
|
||||||
|
SizedBox(height: 16.0.h()),
|
||||||
|
|
||||||
|
Stack(
|
||||||
|
clipBehavior: Clip.none,
|
||||||
|
children: [
|
||||||
|
// 1. Selected Country Chips (Below the input)
|
||||||
|
Padding(
|
||||||
|
padding: EdgeInsets.only(top: 64.0.h()),
|
||||||
|
child: Wrap(
|
||||||
|
spacing: 12,
|
||||||
|
runSpacing: 8,
|
||||||
|
children:
|
||||||
|
_selectedCountries
|
||||||
|
.map((c) => _buildCountryChip(c))
|
||||||
|
.toList(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
// 2. The Pseudo-TextField
|
||||||
|
GestureDetector(
|
||||||
|
onTap: () => setState(() => _isDropdownOpen = !_isDropdownOpen),
|
||||||
|
child: Container(
|
||||||
|
height: 52.0.h(),
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
border: Border.all(color: const Color(0xFFD1E3FF)),
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
'Location',
|
||||||
|
style: TextStyle(
|
||||||
|
color: Colors.grey.shade400,
|
||||||
|
fontSize: 16.0.sp(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Icon(
|
||||||
|
_isDropdownOpen
|
||||||
|
? Icons.keyboard_arrow_up
|
||||||
|
: Icons.keyboard_arrow_down,
|
||||||
|
color: Colors.grey,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
// 3. Floating Dropdown Menu (Positioned ABOVE the input field)
|
||||||
|
if (_isDropdownOpen)
|
||||||
|
Positioned(
|
||||||
|
bottom: 60.0.h(), // Sits above the text field
|
||||||
|
left: 0,
|
||||||
|
right: 0,
|
||||||
|
child: Material(
|
||||||
|
elevation: 12,
|
||||||
|
shadowColor: Colors.black26,
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
color: Colors.white,
|
||||||
|
child: Container(
|
||||||
|
constraints: BoxConstraints(maxHeight: 220.0.h()),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
border: Border.all(color: Colors.grey.shade100),
|
||||||
|
),
|
||||||
|
child: ListView.separated(
|
||||||
|
shrinkWrap: true,
|
||||||
|
padding: EdgeInsets.zero,
|
||||||
|
itemCount: _allCountries.length,
|
||||||
|
separatorBuilder:
|
||||||
|
(context, index) => const Divider(
|
||||||
|
height: 1,
|
||||||
|
color: Color(0xFFF5F5F5),
|
||||||
|
),
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
final country = _allCountries[index];
|
||||||
|
return ListTile(
|
||||||
|
contentPadding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 16,
|
||||||
|
),
|
||||||
|
leading: Text(
|
||||||
|
country['flag']!,
|
||||||
|
style: const TextStyle(fontSize: 22),
|
||||||
|
),
|
||||||
|
title: Text(
|
||||||
|
country['name']!,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 15.0.sp(),
|
||||||
|
color: Colors.black87,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
onTap: () {
|
||||||
|
setState(() {
|
||||||
|
// Add if not already selected
|
||||||
|
if (!_selectedCountries.any(
|
||||||
|
(c) => c['code'] == country['code'],
|
||||||
|
)) {
|
||||||
|
_selectedCountries.add(country);
|
||||||
|
}
|
||||||
|
_isDropdownOpen = false;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
|
||||||
|
SizedBox(height: 40.0.h()),
|
||||||
|
|
||||||
|
// SAVE BUTTON
|
||||||
|
SizedBox(
|
||||||
|
width: double.infinity,
|
||||||
|
height: 54.0.h(),
|
||||||
|
child: ElevatedButton(
|
||||||
|
onPressed: () => context.pop(),
|
||||||
|
style: ElevatedButton.styleFrom(
|
||||||
|
backgroundColor: const Color(
|
||||||
|
0xFFE8F1FF,
|
||||||
|
), // Matches "Save" blue background
|
||||||
|
foregroundColor: const Color(
|
||||||
|
0xFF1A73E8,
|
||||||
|
), // Matches "Save" text color
|
||||||
|
elevation: 0,
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(28),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: Text(
|
||||||
|
'Save',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 16.0.sp(),
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildSectionHeader(String title) {
|
||||||
|
return Row(
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
title,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 18.0.sp(),
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
color: const Color(0xFF616161),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(width: 8.0.w()),
|
||||||
|
Icon(
|
||||||
|
Icons.edit_outlined,
|
||||||
|
size: 20.0.w(),
|
||||||
|
color: const Color(0xFF757575),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildKeywordChip(String label) {
|
||||||
|
// Coloring logic to match screenshot
|
||||||
|
Color bg = const Color(0xFFF5F5F5);
|
||||||
|
Color text = const Color(0xFF424242);
|
||||||
|
|
||||||
|
if (label == '1234') bg = const Color(0xFFE8F5E9);
|
||||||
|
if (label == 'Lorem ipsum') bg = const Color(0xFFFFF3E0);
|
||||||
|
if (label == 'Unknown') bg = const Color(0xFFFFEBEE);
|
||||||
|
if (label == 'Lorem ipsum 02' || label == 'sample')
|
||||||
|
bg = const Color(0xFFECEFF1);
|
||||||
|
|
||||||
|
return Container(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 8),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: bg,
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
),
|
||||||
|
child: Text(
|
||||||
|
label,
|
||||||
|
style: TextStyle(
|
||||||
|
color: text,
|
||||||
|
fontSize: 14.0.sp(),
|
||||||
|
fontWeight: FontWeight.w400,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildCountryChip(Map<String, String> country) {
|
||||||
|
return Container(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 6),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: const Color(0xFFF5F5F5),
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
country['code']!,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 14.0.sp(),
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
color: const Color(0xFF424242),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(width: 6.0.w()),
|
||||||
|
Text(country['flag']!, style: const TextStyle(fontSize: 18)),
|
||||||
|
SizedBox(width: 6.0.w()),
|
||||||
|
GestureDetector(
|
||||||
|
onTap: () => setState(() => _selectedCountries.remove(country)),
|
||||||
|
child: const Icon(Icons.cancel, size: 18, color: Colors.grey),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,133 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_svg/svg.dart';
|
||||||
|
import 'package:tm_app/core/utils/size_config.dart';
|
||||||
|
|
||||||
|
import '../../../core/constants/assets.dart';
|
||||||
|
import '../../../core/theme/colors.dart';
|
||||||
|
import '../../../data/services/model/profile_data/profile_data.dart';
|
||||||
|
import '../strings/profile_strings.dart';
|
||||||
|
import 'theme_toggle.dart';
|
||||||
|
import 'title_description.dart';
|
||||||
|
|
||||||
|
class MainDataTab extends StatelessWidget {
|
||||||
|
const MainDataTab({
|
||||||
|
required this.profileData,
|
||||||
|
required this.onLogout,
|
||||||
|
super.key,
|
||||||
|
});
|
||||||
|
|
||||||
|
final ProfileData profileData;
|
||||||
|
final VoidCallback onLogout;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return SingleChildScrollView(
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
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,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
SizedBox(height: 24.0.h()),
|
||||||
|
TitleDescription(
|
||||||
|
title: ProfileStrings.fullname,
|
||||||
|
description: profileData.fullName ?? '',
|
||||||
|
),
|
||||||
|
TitleDescription(
|
||||||
|
title: ProfileStrings.username,
|
||||||
|
description: profileData.username ?? '',
|
||||||
|
),
|
||||||
|
TitleDescription(
|
||||||
|
title: ProfileStrings.email,
|
||||||
|
description: profileData.email ?? '',
|
||||||
|
),
|
||||||
|
TitleDescription(
|
||||||
|
title: ProfileStrings.phone,
|
||||||
|
description: profileData.phone ?? '',
|
||||||
|
),
|
||||||
|
TitleDescription(
|
||||||
|
title: ProfileStrings.companyName,
|
||||||
|
description: 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()),
|
||||||
|
|
||||||
|
InkWell(
|
||||||
|
onTap: onLogout,
|
||||||
|
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,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
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,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'),
|
||||||
|
];
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:tm_app/core/utils/size_config.dart';
|
||||||
|
|
||||||
|
import '../../../core/theme/colors.dart';
|
||||||
|
|
||||||
|
class ProfileTabBar extends StatefulWidget {
|
||||||
|
const ProfileTabBar({
|
||||||
|
required this.controller,
|
||||||
|
required this.titles,
|
||||||
|
this.onTap,
|
||||||
|
super.key,
|
||||||
|
});
|
||||||
|
|
||||||
|
final TabController controller;
|
||||||
|
final List<String> titles;
|
||||||
|
final ValueChanged<int>? onTap;
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<ProfileTabBar> createState() => _ProfileTabBarState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ProfileTabBarState extends State<ProfileTabBar> {
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return SingleChildScrollView(
|
||||||
|
child: Container(
|
||||||
|
width: double.infinity,
|
||||||
|
height: 51.0.h(),
|
||||||
|
padding: EdgeInsets.symmetric(horizontal: 8.0.w(), vertical: 8.0.h()),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: AppColors.grey10,
|
||||||
|
borderRadius: BorderRadius.circular(10),
|
||||||
|
),
|
||||||
|
alignment: Alignment.center,
|
||||||
|
child: TabBar(
|
||||||
|
controller: widget.controller,
|
||||||
|
onTap: widget.onTap,
|
||||||
|
indicatorSize: TabBarIndicatorSize.tab,
|
||||||
|
dividerColor: Colors.transparent,
|
||||||
|
indicator: BoxDecoration(
|
||||||
|
color: AppColors.primary0,
|
||||||
|
borderRadius: BorderRadius.circular(10),
|
||||||
|
boxShadow: [
|
||||||
|
BoxShadow(
|
||||||
|
color: Colors.black.withValues(alpha: 0.12),
|
||||||
|
blurRadius: 4,
|
||||||
|
offset: const Offset(0, 2),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
labelColor: AppColors.mainBlue,
|
||||||
|
unselectedLabelColor: AppColors.grey60,
|
||||||
|
labelStyle: TextStyle(
|
||||||
|
fontSize: 16.0.sp(),
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
),
|
||||||
|
unselectedLabelStyle: TextStyle(
|
||||||
|
fontSize: 16.0.sp(),
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
),
|
||||||
|
tabs: [
|
||||||
|
Tab(text: widget.titles[0]),
|
||||||
|
Tab(text: widget.titles[1]),
|
||||||
|
Tab(text: widget.titles[2]),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,219 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_svg/flutter_svg.dart';
|
||||||
|
import 'package:go_router/go_router.dart';
|
||||||
|
import 'package:provider/provider.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 '../../../view_models/profile_view_model.dart';
|
||||||
|
import 'edit_key_word_bottom_sheet.dart';
|
||||||
|
|
||||||
|
class SubjectTab extends StatelessWidget {
|
||||||
|
const SubjectTab({super.key});
|
||||||
|
|
||||||
|
final bool isDesktop = true;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final viewModel = context.read<ProfileViewModel>();
|
||||||
|
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: () {
|
||||||
|
if (isDesktop) {
|
||||||
|
showDialog(
|
||||||
|
context: context,
|
||||||
|
builder: (_) {
|
||||||
|
return Dialog(
|
||||||
|
child: SizedBox(
|
||||||
|
width: 560,
|
||||||
|
child: EditKeyWordBottomSheet(
|
||||||
|
isDesktop: true,
|
||||||
|
keyWords: viewModel.profileData?.keywords ?? [],
|
||||||
|
onSubmit: (keyWords) async {
|
||||||
|
context.pop();
|
||||||
|
await viewModel.updateProfileKeyWords(
|
||||||
|
keyWords: keyWords,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
showModalBottomSheet(
|
||||||
|
context: context,
|
||||||
|
isScrollControlled: true,
|
||||||
|
builder: (_) {
|
||||||
|
return EditKeyWordBottomSheet(
|
||||||
|
isDesktop: false,
|
||||||
|
keyWords: viewModel.profileData?.keywords ?? [],
|
||||||
|
onSubmit: (keyWords) async {
|
||||||
|
context.pop();
|
||||||
|
await viewModel.updateProfileKeyWords(keyWords: keyWords);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
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]!),
|
||||||
|
// ],
|
||||||
|
children:
|
||||||
|
viewModel.profileData!.keywords!
|
||||||
|
.map(
|
||||||
|
(e) => _chip(label: e, backgroundColor: Colors.green[200]!),
|
||||||
|
)
|
||||||
|
.toList(),
|
||||||
|
),
|
||||||
|
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(
|
||||||
|
'Key Word',
|
||||||
|
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),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user