This commit is contained in:
mohammad
2024-07-23 16:16:31 +03:00
parent 8a4a158af5
commit e5e839248d
17 changed files with 457 additions and 516 deletions

View File

@ -1,3 +1,4 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
@ -8,6 +9,7 @@ import 'package:syncrow_app/features/auth/model/user_model.dart';
import 'package:syncrow_app/navigation/navigation_service.dart';
import 'package:syncrow_app/navigation/routing_constants.dart';
import 'package:syncrow_app/services/api/authentication_api.dart';
import 'package:syncrow_app/services/api/profile_api.dart';
import 'package:syncrow_app/utils/helpers/shared_preferences_helper.dart';
import 'package:syncrow_app/utils/helpers/snack_bar.dart';
import 'package:syncrow_app/utils/resource_manager/strings_manager.dart';
@ -178,12 +180,18 @@ class AuthCubit extends Cubit<AuthState> {
if (token.accessTokenIsNotEmpty) {
debugPrint('token: ${token.accessToken}');
FlutterSecureStorage storage = const FlutterSecureStorage();
await storage.write(key: Token.loginAccessTokenKey, value: token.accessToken);
await storage.write(
key: Token.loginAccessTokenKey,
value: token.accessToken
);
await fetchUserInfo();
const FlutterSecureStorage().write(
key: UserModel.userUuidKey,
value: Token.decodeToken(token.accessToken)['uuid'].toString());
value: Token.decodeToken(token.accessToken)['uuid'].toString()
).then((value) async {
var uuid = await const FlutterSecureStorage().read(key: UserModel.userUuidKey);
user = await ProfileApi.fetchUserInfo(uuid);
},);
user = UserModel.fromToken(token);
emailController.clear();
passwordController.clear();
@ -277,8 +285,7 @@ class AuthCubit extends Cubit<AuthState> {
try {
emit(AuthTokenLoading());
const storage = FlutterSecureStorage();
final firstLaunch =
await SharedPreferencesHelper.readBoolFromSP(StringsManager.firstLaunch) ?? true;
final firstLaunch = await SharedPreferencesHelper.readBoolFromSP(StringsManager.firstLaunch) ?? true;
if (firstLaunch) {
storage.deleteAll();
@ -311,6 +318,7 @@ class AuthCubit extends Cubit<AuthState> {
}
}
sendToForgetPassword({required String password}) async {
try {
emit(AuthForgetPassLoading());
@ -320,4 +328,18 @@ class AuthCubit extends Cubit<AuthState> {
emit(AuthForgetPassError(message: 'Something went wrong'));
}
}
Future fetchUserInfo() async {
try {
emit(AuthLoading());
var uuid = await const FlutterSecureStorage().read(key: UserModel.userUuidKey);
user = await ProfileApi.fetchUserInfo(uuid);
emit(AuthLoginSuccess());
} catch (e) {
return;
}
}
}

View File

@ -4,34 +4,42 @@ class UserModel {
static String userUuidKey = 'userUuid';
final String? uuid;
final String? email;
final String? name;
final String? photoUrl;
final String? firstName;
final String? lastName;
final String? profilePicture;
final String? phoneNumber;
final bool? isEmailVerified;
final String? regionName;
final String? timeZone;
final bool? isAgreementAccepted;
UserModel({
required this.uuid,
required this.email,
required this.name,
required this.photoUrl,
required this.firstName,
required this.lastName,
required this.profilePicture,
required this.phoneNumber,
required this.isEmailVerified,
required this.isAgreementAccepted,
required this.regionName, // Add this line
required this.timeZone, // Add this line
});
factory UserModel.fromJson(Map<String, dynamic> json) {
return UserModel(
uuid: json['id'],
uuid: json['uuid'],
email: json['email'],
name: json['name'],
photoUrl: json['photoUrl'],
firstName: json['firstName'],
lastName: json['lastName'],
profilePicture: json['profilePicture'],
phoneNumber: json['phoneNumber'],
isEmailVerified: json['isEmailVerified'],
isAgreementAccepted: json['isAgreementAccepted'],
regionName: json['region']?['regionName'], // Extract regionName
timeZone: json['timeZone']?['timeZoneOffset'], // Extract regionName
);
}
@ -44,11 +52,16 @@ class UserModel {
return UserModel(
uuid: tempJson['uuid'].toString(),
email: tempJson['email'],
name: null,
photoUrl: null,
lastName: tempJson['lastName'],
firstName:tempJson['firstName'] ,
profilePicture: tempJson['profilePicture'],
phoneNumber: null,
isEmailVerified: null,
isAgreementAccepted: null,
regionName: tempJson['region']?['regionName'],
timeZone: tempJson['timezone']?['timeZoneOffset'],
);
}
@ -56,8 +69,9 @@ class UserModel {
return {
'id': uuid,
'email': email,
'name': name,
'photoUrl': photoUrl,
'lastName': lastName,
'firstName': firstName,
'photoUrl': profilePicture,
'phoneNumber': phoneNumber,
'isEmailVerified': isEmailVerified,
'isAgreementAccepted': isAgreementAccepted,

View File

@ -9,85 +9,4 @@ class MenuCubit extends Cubit<MenuState> {
String name = '';
// List<MenuListModel> menuLists = [
// MenuListModel(
// label: 'Home Management',
// listItems: [
// ListItemModel(
// label: 'Create a Home',
// ),
// ListItemModel(
// label: 'Join a Home',
// ),
// ListItemModel(
// label: 'Manage Your Home',
// ),
// ],
// ),
// MenuListModel(
// label: 'General Settings',
// listItems: [
// ListItemModel(
// label: 'Voice Assistant',
// ),
// ListItemModel(
// label: 'Temperature unit',
// ),
// ListItemModel(
// label: 'Touch tone on panel',
// ),
// ListItemModel(
// label: 'Language',
// ),
// ListItemModel(
// label: 'Network diagnosis',
// ),
// ListItemModel(
// label: 'Clear cache',
// ),
// ],
// ),
// MenuListModel(
// label: 'Messages Center',
// listItems: [
// ListItemModel(
// label: 'Alerts',
// ),
// ListItemModel(
// label: 'Messages',
// ),
// ListItemModel(
// label: 'FAQs',
// ),
// ListItemModel(
// label: 'Help & Feedback',
// ),
// ],
// ),
// MenuListModel(
// label: 'Security and Privacy',
// listItems: [
// ListItemModel(
// label: 'Security',
// ),
// ListItemModel(
// label: 'privacy',
// ),
// ],
// ),
// MenuListModel(
// label: 'Legal Information',
// listItems: [
// ListItemModel(
// label: 'About',
// ),
// ListItemModel(
// label: 'Privacy Policy',
// ),
// ListItemModel(
// label: 'User Agreement',
// ),
// ],
// ),
// ];
}

View File

@ -1,19 +1,21 @@
import 'dart:convert';
import 'dart:io';
import 'package:device_info_plus/device_info_plus.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:syncrow_app/features/auth/bloc/auth_cubit.dart';
import 'package:syncrow_app/features/menu/bloc/profile_bloc/profile_event.dart';
import 'package:syncrow_app/features/menu/bloc/profile_bloc/profile_state.dart';
import 'package:syncrow_app/features/menu/bloc/profile_bloc/region_model.dart';
import 'package:syncrow_app/features/menu/bloc/profile_bloc/time_zone_model.dart';
import 'package:syncrow_app/services/api/profile_api.dart';
import 'package:syncrow_app/utils/helpers/snack_bar.dart';
import 'package:image_picker/image_picker.dart';
import 'package:permission_handler/permission_handler.dart';
class ProfileBloc extends Bloc<ProfileEvent, ProfileState> {
final String userId;
bool isSaving = false;
bool editName = false;
final FocusNode focusNode = FocusNode();
@ -22,12 +24,13 @@ class ProfileBloc extends Bloc<ProfileEvent, ProfileState> {
String timeZoneSelected = '';
String regionSelected = '';
final TextEditingController searchController = TextEditingController();
final TextEditingController nameController = TextEditingController(text: 'asd alsdkasdd ');
final TextEditingController nameController = TextEditingController(text: '${AuthCubit.user!.firstName} ${AuthCubit.user!.lastName}');
List<TimeZone>? timeZoneList;
List<RegionModel>? regionList;
ProfileBloc({required this.userId}) : super(InitialState()) {
on<InitialEvent>(_fetchUserInfo);
ProfileBloc() : super(InitialState()) {
on<InitialProfileEvent>(_fetchUserInfo);
on<TimeZoneInitialEvent>(_fetchTimeZone);
on<RegionInitialEvent>(_fetchRegion);
on<SaveNameEvent>(saveName);
@ -35,46 +38,18 @@ class ProfileBloc extends Bloc<ProfileEvent, ProfileState> {
on<ChangeNameEvent>(_changeName);
on<SelectTimeZoneEvent>(selectTimeZone);
on<SearchRegionEvent>(searchRegion);
on<SearchTimeZoneEvent>(searchTimeZone);
on<SelectRegionEvent>(selectRegion);
}
List<Map<String, String>> timeZone=
[
{"name": "GMT-12:00", "offset": "-12:00","id":'1'},
{"name": "GMT-11:00", "offset": "-11:00","id":'2'},
{"name": "GMT-10:00", "offset": "-10:00","id":'3'},
{"name": "GMT-09:00", "offset": "-09:00","id":'4'},
{"name": "GMT-08:00", "offset": "-08:00","id":'5'},
{"name": "GMT-07:00", "offset": "-07:00","id":'6'},
{"name": "GMT-06:00", "offset": "-06:00","id":'7'},
{"name": "GMT-05:00", "offset": "-05:00","id":'8'},
{"name": "GMT-04:00", "offset": "-04:00","id":'9'},
];
List<Map<String, String>> region=
[
{"name": "region 1", "id":'1'},
{"name": "region 2", "id":'2'},
{"name": "region 3","id":'3'},
{"name": "region 4", "id":'4'},
{"name": "region 5","id":'5'},
{"name": "region 6","id":'6'},
{"name": "region 7","id":'7'},
{"name": "region 8", "id":'8'},
];
Uint8List? decodeBase64Image(String? base64String) {
if (base64String != null) {
final startIndex = base64String.indexOf('base64,') + 7;
final pureBase64String = base64String.substring(startIndex);
return base64.decode(pureBase64String);
}
return null;
}
void _changeName(ChangeNameEvent event, Emitter<ProfileState> emit) {
emit(LoadingInitialState());
editName = event.value!;
@ -88,30 +63,21 @@ class ProfileBloc extends Bloc<ProfileEvent, ProfileState> {
emit(NameEditingState(editName: editName));
}
void _fetchUserInfo(InitialEvent event, Emitter<ProfileState> emit) async {
void _fetchUserInfo(InitialProfileEvent event, Emitter<ProfileState> emit) async {
try {
emit(LoadingInitialState());
// var response = await ProfileApi.fetchUserInfo(userId);
// List<StatusModel> statusModelList = [];
// for (var status in response['status']) {
// statusModelList.add(StatusModel.fromJson(status));
// }
AuthCubit.user = await ProfileApi.fetchUserInfo(AuthCubit.user!.uuid);
} catch (e) {
emit(FailedState(errorMessage: e.toString()));
return;
}
}
Future _fetchTimeZone(
TimeZoneInitialEvent event, Emitter<ProfileState> emit) async {
Future _fetchTimeZone(TimeZoneInitialEvent event, Emitter<ProfileState> emit) async {
emit(LoadingInitialState());
try {
timeZoneList = timeZone.map((zone) => TimeZone.fromJson(zone)).toList();
// List<StatusModel> statusModelList = [];
// for (var status in response['status']) {
// statusModelList.add(StatusModel.fromJson(status));
// }
emit(UpdateState(timeZoneList: timeZoneList!)); // Make sure UpdateState accepts the list
timeZoneList = await ProfileApi.fetchTimeZone();
emit(UpdateState(timeZoneList: timeZoneList!));
return timeZoneList;
} catch (e) {
emit(FailedState(errorMessage: e.toString()));
@ -119,26 +85,25 @@ class ProfileBloc extends Bloc<ProfileEvent, ProfileState> {
}
}
Future selectTimeZone(SelectTimeZoneEvent event, Emitter<ProfileState> emit) async {
emit(LoadingInitialState());
try {
timeZoneSelected= event.val;
emit(LoadingInitialState());
timeZoneSelected = event.val;
await ProfileApi.saveTimeZone(regionUuid: event.val);
CustomSnackBar.displaySnackBar('Save Successfully');
Navigator.pop(event.context);
return timeZoneList;
emit(SaveState());
} catch (e) {
emit(FailedState(errorMessage: e.toString()));
return;
}
}
Future selectRegion(SelectRegionEvent event, Emitter<ProfileState> emit) async {
emit(LoadingInitialState());
try {
regionSelected= event.val;
emit(LoadingInitialState());
await ProfileApi.saveRegion(regionUuid:event.val );
CustomSnackBar.displaySnackBar('Save Successfully');
Navigator.pop(event.context);
return timeZoneList;
emit(SaveState());
} catch (e) {
emit(FailedState(errorMessage: e.toString()));
return;
@ -146,26 +111,43 @@ class ProfileBloc extends Bloc<ProfileEvent, ProfileState> {
}
Future searchRegion(SearchRegionEvent event, Emitter<ProfileState> emit) async {
emit(LoadingInitialState());
final query = event.query.toLowerCase();
if(event.query.isEmpty){
regionList = region.map((zone) => RegionModel.fromJson(zone)).toList();
}else{
if(event.query.isNotEmpty){
final filteredRegions = regionList?.where((region) {
return region.name.toLowerCase().contains(query);
}).toList() ?? [];
regionList = filteredRegions;// Assume this fetches the regions
emit(RegionsLoadedState(regions: filteredRegions));
}else{
regionList = await ProfileApi.fetchRegion();
emit(RegionsLoadedState(regions: regionList!));
}
}
Future searchTimeZone(SearchTimeZoneEvent event, Emitter<ProfileState> emit) async {
emit(LoadingInitialState());
final query = event.query.toLowerCase();
if(event.query.isNotEmpty){
final filtered = timeZoneList?.where((region) {
return region.name.toLowerCase().contains(query);
}).toList() ?? [];
timeZoneList = filtered;
emit(TimeZoneLoadedState(regions: filtered));
}else{
timeZoneList = await ProfileApi.fetchTimeZone();
emit(UpdateState(timeZoneList: timeZoneList!));
}
}
void _fetchRegion(RegionInitialEvent event, Emitter<ProfileState> emit) async {
try {
emit(LoadingInitialState());
regionList = region.map((zone) => RegionModel.fromJson(zone)).toList();
emit(UpdateState(timeZoneList: timeZoneList!));
regionList = await ProfileApi.fetchRegion();
emit(RegionsLoadedState(regions: regionList!));
} catch (e) {
emit(FailedState(errorMessage: e.toString()));
return;
}
}
@ -182,7 +164,7 @@ class ProfileBloc extends Bloc<ProfileEvent, ProfileState> {
image=null;
CustomSnackBar.displaySnackBar('Image size must be 1 MB or less');
}else{
await saveImage();
await _saveImage();
}
} else {
print('No image selected.');
@ -193,54 +175,15 @@ class ProfileBloc extends Bloc<ProfileEvent, ProfileState> {
}
}
Future<void> saveImage() async {
Future<void> _saveImage() async {
emit(ChangeImageState());
List<int> imageBytes = image!.readAsBytesSync();
String base64Image = base64Encode(imageBytes);
print(base64Image);
var response = await ProfileApi.saveImage(base64Image);
emit(ImageSelectedState());
}
Future<bool> _requestPermission() async {
DeviceInfoPlugin deviceInfo = DeviceInfoPlugin();
AndroidDeviceInfo androidInfo = await deviceInfo.androidInfo;
if (Platform.isAndroid ) {
PermissionStatus status = await Permission.photos.status;
if(androidInfo.version.sdkInt<= 33){
if (status.isDenied) {
PermissionStatus status = await Permission.storage.request();
if (status.isGranted) {
return true;
} else {
return false;
}
}
}else{
if (status.isGranted) {
return true;
} else if (status.isDenied) {
PermissionStatus status = await Permission.photos.request();
if (status.isGranted) {
return true;
} else {
return false;
}
}
}
return false;
} else {
SharedPreferences sharedPreferences =
await SharedPreferences.getInstance();
bool firstClick = sharedPreferences.getBool('firstPermission') ?? true;
await sharedPreferences.setBool('firstPermission', false);
if (firstClick == false) {
var status = await Permission.photos.status;
return status.isGranted;
} else {
return true;
}
}
}
void _showPermissionDeniedDialog(BuildContext context) {
showDialog(
@ -272,129 +215,72 @@ class ProfileBloc extends Bloc<ProfileEvent, ProfileState> {
Future<void> saveName(SaveNameEvent event, Emitter<ProfileState> emit) async {
if (_validateInputs()) return;
try {
add(const ChangeNameEvent(value: false));
isSaving = true;
emit(LoadingSaveState());
final fullName = nameController.text;
final nameParts = fullName.split(' ');
final firstName = nameParts[0];
final lastName = nameParts.length > 1 ? nameParts[1] : '';
var response = await ProfileApi.saveName(firstName: firstName, lastName: lastName);
add(InitialProfileEvent());
AuthCubit.get(event.context).fetchUserInfo();
Navigator.of(event.context).pop(true);
CustomSnackBar.displaySnackBar('Save Successfully');
emit(SaveState());
} catch (_) {
// Handle the error
} finally {
isSaving = false;
}
}
bool _validateInputs() {
if (nameController.text.length < 7) {
CustomSnackBar.displaySnackBar('Password less than 7');
return true;
}
if (nameController.text.isEmpty) {
CustomSnackBar.displaySnackBar('Password required');
return true;
}
if (nameController.text.isEmpty) {
CustomSnackBar.displaySnackBar('Password name required');
if (nameController.text.length < 2) {
CustomSnackBar.displaySnackBar('Name Must More than 2 ');
return true;
}
return false;
}
void showRenameDialog(BuildContext context) {
showDialog(
context: context,
builder: (BuildContext context) {
return Dialog(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(15),
),
child: SizedBox(
height: 200,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Padding(
padding: EdgeInsets.only(
top: 25,
),
child: Text(
'Change Name',
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
),
),
),
const Spacer(),
const Spacer(),
Row(
children: [
Expanded(
child: Container(
decoration: const BoxDecoration(
border: Border(
right: BorderSide(
color: Colors.grey,
width: 0.5,
),
top: BorderSide(
color: Colors.grey,
width: 0.5,
),
),
),
child: TextButton(
onPressed: () {
Navigator.of(context).pop();
},
child: const Text(
'Cancel',
style: TextStyle(
fontSize: 18,
color: Colors.black,
),
),
),
),
),
const VerticalDivider(
thickness: 1,
width: 1,
),
Expanded(
child: Container(
decoration: const BoxDecoration(
border: Border(
right: BorderSide(
color: Colors.grey,
width: 0.5,
),
top: BorderSide(
color: Colors.grey,
width: 0.5,
),
),
),
child: TextButton(
onPressed: () {
// Handle save action
},
child: const Text(
'Save',
style: TextStyle(
fontSize: 18,
color: Colors.grey,
),
),
),
),
),
],
),
],
),
),
);
},
);
Future<bool> _requestPermission() async {
DeviceInfoPlugin deviceInfo = DeviceInfoPlugin();
if (Platform.isAndroid ) {
AndroidDeviceInfo androidInfo = await deviceInfo.androidInfo;
PermissionStatus status = await Permission.photos.status;
if(androidInfo.version.sdkInt<= 33){
if (status.isDenied) {
PermissionStatus status = await Permission.storage.request();
if (status.isGranted) {
return true;
} else {
return false;
}
}
}else{
if (status.isGranted) {
return true;
} else if (status.isDenied) {
PermissionStatus status = await Permission.photos.request();
if (status.isGranted) {
return true;
} else {
return false;
}
}
}
return false;
} else {
SharedPreferences sharedPreferences = await SharedPreferences.getInstance();
bool firstClick = sharedPreferences.getBool('firstPermission') ?? true;
await sharedPreferences.setBool('firstPermission', false);
if (firstClick == false) {
var status = await Permission.photos.status;
return status.isGranted;
} else {
return true;
}
}
}
}

View File

@ -8,7 +8,7 @@ abstract class ProfileEvent extends Equatable {
List<Object> get props => [];
}
class InitialEvent extends ProfileEvent {}
class InitialProfileEvent extends ProfileEvent {}
class TimeZoneInitialEvent extends ProfileEvent {}
@ -20,13 +20,6 @@ class ChangeNameEvent extends ProfileEvent {
class RegionInitialEvent extends ProfileEvent {}
class UpdateLockEvent extends ProfileEvent {
final bool value;
const UpdateLockEvent({required this.value});
@override
List<Object> get props => [value];
}
class SaveNameEvent extends ProfileEvent {
final BuildContext context;
const SaveNameEvent({required this.context});
@ -71,5 +64,13 @@ class SearchRegionEvent extends ProfileEvent {
List<Object> get props => [query];
}
class SearchTimeZoneEvent extends ProfileEvent {
final String query;
const SearchTimeZoneEvent({required this.query});
@override
List<Object> get props => [query];
}

View File

@ -1,5 +1,4 @@
import 'package:equatable/equatable.dart';
import 'package:syncrow_app/features/devices/model/smart_door_model.dart';
import 'package:syncrow_app/features/menu/bloc/profile_bloc/region_model.dart';
import 'package:syncrow_app/features/menu/bloc/profile_bloc/time_zone_model.dart';
@ -26,13 +25,7 @@ class NameEditingState extends ProfileState {
NameEditingState({required this.editName});
}
class LoadingNewSate extends ProfileState {
final SmartDoorModel smartDoorModel;
const LoadingNewSate({required this.smartDoorModel});
@override
List<Object> get props => [smartDoorModel];
}
class FailedState extends ProfileState {
final String errorMessage;
@ -43,20 +36,9 @@ class FailedState extends ProfileState {
List<Object> get props => [errorMessage];
}
class GeneratePasswordState extends ProfileState {}
class ImageSelectedState extends ProfileState {}
class ImageTooLargeState extends ProfileState {}
class IsRepeatState extends ProfileState {}
class IsStartEndState extends ProfileState {}
class ChangeStartTimeState extends ProfileState {}
class ChangeEndTimeState extends ProfileState {}
class ChangeImageState extends ProfileState {}
class SaveState extends ProfileState {}
@ -67,3 +49,9 @@ class RegionsLoadedState extends ProfileState {
const RegionsLoadedState({required this.regions});
}
class TimeZoneLoadedState extends ProfileState {
final List<TimeZone> regions;
const TimeZoneLoadedState({required this.regions});
}

View File

@ -11,15 +11,15 @@ class RegionModel {
factory RegionModel.fromJson(Map<String, dynamic> json) {
return RegionModel(
name: json['name'],
id: json['id'].toString(), // Ensure id is a String
name: json['regionName'],
id: json['uuid'].toString(), // Ensure id is a String
);
}
Map<String, dynamic> toJson() {
return {
'name': name,
'id': id,
'regionName': name,
'uuid': id,
};
}
}

View File

@ -11,9 +11,9 @@ class TimeZone {
factory TimeZone.fromJson(Map<String, dynamic> json) {
return TimeZone(
name: json['name'],
offset: json['offset'],
id: json['id'].toString(), // Ensure id is a String
name: json['cityName'],
offset: json['timeZoneOffset'],
id: json['uuid'].toString(), // Ensure id is a String
);
}

View File

@ -25,7 +25,7 @@ class MenuView extends StatelessWidget {
physics: const BouncingScrollPhysics(),
child: Column(
children: [
ProfileTab(name:profileBloc.name),
ProfileTab(),
for (var section in menuSections)
MenuList(
section: section,

View File

@ -1,25 +1,44 @@
import 'dart:convert';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_app/features/auth/bloc/auth_cubit.dart';
import 'package:syncrow_app/features/menu/view/widgets/profile/profile_view.dart';
import 'package:syncrow_app/features/shared_widgets/default_container.dart';
import 'package:syncrow_app/features/shared_widgets/syncrow_logo.dart';
import 'package:syncrow_app/features/shared_widgets/text_widgets/body_medium.dart';
import 'package:syncrow_app/features/shared_widgets/text_widgets/body_small.dart';
class ProfileTab extends StatelessWidget {
final String? name;
const ProfileTab({super.key,this.name});
const ProfileTab({super.key, this.name});
@override
Widget build(BuildContext context) {
return BlocBuilder<AuthCubit, AuthState>(
builder: (context, state) {
final user = AuthCubit.user;
Uint8List? imageBytes;
if (user!.profilePicture != null) {
final base64String = user.profilePicture!;
final startIndex = base64String.indexOf('base64,') + 7;
final pureBase64String = base64String.substring(startIndex);
imageBytes = base64.decode(pureBase64String);
}
return Padding(
padding: const EdgeInsets.symmetric(vertical: 10),
child: InkWell(
onTap: () {
Navigator.of(context).push(
Navigator.of(context)
.push(
MaterialPageRoute(
builder: (context) => const ProfileView(),
),
);
)
.then((result) {
if (result == true) {
context.read<AuthCubit>().fetchUserInfo();
}
});
},
child: Stack(
children: [
@ -30,12 +49,20 @@ class ProfileTab extends StatelessWidget {
DefaultContainer(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
BodyMedium(
text: name!,
text: '${user.firstName!} ',
fontWeight: FontWeight.bold,
),
BodySmall(text: "Syncrow Account")
BodyMedium(
text: user.lastName!,
fontWeight: FontWeight.bold,
),
],
),
const BodySmall(text: "Syncrow Account"),
],
),
),
@ -50,7 +77,13 @@ class ProfileTab extends StatelessWidget {
child: CircleAvatar(
radius: 37,
backgroundColor: Colors.grey,
child: SyncrowLogo(),
child: ClipOval(
child: Image.memory(
imageBytes!,
fit: BoxFit.cover,
width: 110, // You can adjust the width and height as needed
height: 110,
)),
),
),
),
@ -58,5 +91,8 @@ class ProfileTab extends StatelessWidget {
),
),
);
},
);
}
}

View File

@ -1,6 +1,7 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_app/features/auth/bloc/auth_cubit.dart';
import 'package:syncrow_app/features/menu/bloc/profile_bloc/profile_bloc.dart';
import 'package:syncrow_app/features/menu/bloc/profile_bloc/profile_event.dart';
import 'package:syncrow_app/features/menu/bloc/profile_bloc/profile_state.dart';
@ -13,29 +14,29 @@ import 'package:syncrow_app/utils/resource_manager/color_manager.dart';
class ProfileView extends StatelessWidget {
const ProfileView({super.key});
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (BuildContext context) => ProfileBloc(userId: ''),
child: BlocConsumer<ProfileBloc, ProfileState>(listener: (context, state) {
if (state is FailedState) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(state.errorMessage),
backgroundColor: Colors.red,
),
);
}
}, builder: (context, state) {
create: (BuildContext context) => ProfileBloc()..add(InitialProfileEvent()),
child: BlocConsumer<ProfileBloc, ProfileState>(
listener: (context, state) {},
builder: (context, state) {
final profileBloc = BlocProvider.of<ProfileBloc>(context);
Uint8List? imageBytes = profileBloc.decodeBase64Image(AuthCubit.user!.profilePicture);
return DefaultScaffold(
title: 'Syncrow Account',
child: Column(
children: [
SizedBox(height: MediaQuery.of(context).size.height*0.05,),
SizedBox(
height: MediaQuery.of(context).size.height * 0.05,
),
InkWell(
onTap: () {
profileBloc.add(SelectImageEvent(context: context, isSelected: false));
profileBloc.add(
SelectImageEvent(context: context, isSelected: false));
},
child: SizedBox.square(
dimension: 125,
@ -48,6 +49,18 @@ class ProfileView extends StatelessWidget {
backgroundImage: profileBloc.image == null
? null
: FileImage(profileBloc.image!),
child: profileBloc.image != null
? null
:imageBytes != null
? ClipOval(
child: Image.memory(
imageBytes,
fit: BoxFit.cover,
width: 110,
height: 110,
),
)
: null,
),
),
),
@ -70,7 +83,7 @@ class ProfileView extends StatelessWidget {
controller: profileBloc.nameController,
enabled: profileBloc.editName,
onEditingComplete: () {
profileBloc.add(const ChangeNameEvent(value: false));
profileBloc.add(SaveNameEvent(context: context));
},
decoration: const InputDecoration(
hintText: "Your Name",
@ -98,58 +111,44 @@ class ProfileView extends StatelessWidget {
),
),
const SizedBox(height: 10),
//Info
// Info
DefaultContainer(
padding: const EdgeInsets.symmetric(
horizontal: 25,
vertical: 5,
),
child: Column(
child:
BlocBuilder<AuthCubit, AuthState>(
builder: (context, state) {
final user = AuthCubit.user;
return Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.start,
children: [
// Row(
// mainAxisAlignment: MainAxisAlignment.spaceBetween,
// children: [
// const BodyMedium(text: 'Email '),
// Flexible(
// child: TextField(
// textAlign: TextAlign.end,
// decoration: InputDecoration(
// hintText: ' Test@test.com',
// hintStyle:
// context.bodyMedium.copyWith(color: Colors.grey),
// border: InputBorder.none,
// ),
// ),
// ),
// ],
// ),
// Container(
// height: 1,
// color: ColorsManager.greyColor,
// ),
InkWell(
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) =>
const RegionPage(),
));
builder: (context) => const RegionPage(),
),
).then((result) {
context.read<AuthCubit>().fetchUserInfo();
});
},
child: const Padding(
padding: EdgeInsets.only(top: 20, bottom: 20),
child: Padding(
padding: const EdgeInsets.only(top: 20, bottom: 20),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
BodyMedium(text: 'Region '),
Flexible(
child: BodyMedium(text: 'United Arab Emirates')
),
const BodyMedium(text: 'Region '),
Flexible(child: BodyMedium(text: user!.regionName??'No Region')),
],
),
)),
),
),
Container(
height: 1,
color: ColorsManager.greyColor,
@ -159,28 +158,33 @@ class ProfileView extends StatelessWidget {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) =>
const TimeZoneScreenPage(),
));
builder: (context) => const TimeZoneScreenPage(),
),
).then((result) {
context.read<AuthCubit>().fetchUserInfo();
});
},
child: const Padding(
padding: EdgeInsets.only(top: 15, bottom: 15),
child: Padding(
padding: const EdgeInsets.only(top: 15, bottom: 15),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
BodyMedium(text: 'Time Zone '),
const BodyMedium(text: 'Time Zone '),
Flexible(
child: BodyMedium(text: 'GMT +4')
child: BodyMedium(text: user.timeZone??"No Time Zone"),
),
],
),
),
),
],
)),
);}),
),
],
),
);
}));
},
),
);
}
}

View File

@ -13,7 +13,7 @@ class RegionPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (BuildContext context) => ProfileBloc(userId: '')..add(RegionInitialEvent()),
create: (BuildContext context) => ProfileBloc()..add(RegionInitialEvent()),
child: BlocConsumer<ProfileBloc, ProfileState>(listener: (context, state) {
if (state is FailedState) {
ScaffoldMessenger.of(context).showSnackBar(

View File

@ -15,7 +15,7 @@ class TimeZoneScreenPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (BuildContext context) => ProfileBloc(userId: '')..add(TimeZoneInitialEvent()),
create: (BuildContext context) => ProfileBloc()..add(TimeZoneInitialEvent()),
child:
BlocConsumer<ProfileBloc, ProfileState>(listener: (context, state) {
if (state is FailedState) {
@ -38,6 +38,10 @@ class TimeZoneScreenPage extends StatelessWidget {
: Column(
children: [
TextFormField(
controller:profileBloc.searchController ,
onChanged: (value) {
profileBloc.add(SearchTimeZoneEvent(query: value));
},
decoration: const InputDecoration(
prefixIcon: Icon(Icons.search),
hintText: 'Search',
@ -59,7 +63,6 @@ class TimeZoneScreenPage extends StatelessWidget {
itemBuilder: (context, index) {
return InkWell(
onTap: () {
profileBloc.add(SelectTimeZoneEvent(
val: timeZoneList[index].id,
context: context));

View File

@ -18,6 +18,7 @@ class _SplashViewState extends State<SplashView> {
@override
void initState() {
AuthCubit.get(context).getTokenAndValidate();
AuthCubit.get(context).fetchUserInfo();
super.initState();
}

View File

@ -1,6 +1,7 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_app/features/auth/bloc/auth_cubit.dart';
import 'package:syncrow_app/features/menu/bloc/profile_bloc/profile_bloc.dart';
import 'package:syncrow_app/features/scene/bloc/create_scene/create_scene_bloc.dart';
import 'package:syncrow_app/navigation/navigation_service.dart';
import 'package:syncrow_app/utils/resource_manager/color_manager.dart';
@ -26,6 +27,7 @@ class MyApp extends StatelessWidget {
BlocProvider(create: (context) => AuthCubit()),
BlocProvider(create: (context) => CreateSceneBloc()),
BlocProvider(create: (context) => SceneBloc()),
BlocProvider(create: (context) => ProfileBloc()),
],
child: MaterialApp(

View File

@ -1,5 +1,5 @@
abstract class ApiEndpoints {
static const String baseUrl = 'https://syncrow.azurewebsites.net';
static const String baseUrl = 'https://syncrow-dev.azurewebsites.net';
// static const String baseUrl = 'http://100.107.182.63:4001'; //Localhost
////////////////////////////////////// Authentication ///////////////////////////////
@ -151,4 +151,15 @@ abstract class ApiEndpoints {
//multiple-time offline
static const String deleteTemporaryPassword =
'$baseUrl/door-lock/temporary-password/{doorLockUuid}/{passwordId}';
//user
static const String getUser = '$baseUrl/user/{userUuid}';
static const String saveRegion = '$baseUrl/user/region/{userUuid}';
static const String saveTimeZone = '$baseUrl/user/timezone/{userUuid}';
static const String saveName = '$baseUrl/user/name/{userUuid}';
static const String sendPicture = '$baseUrl/user/profile-picture/{userUuid}';
static const String getRegion = '$baseUrl/region';
static const String getTimezone = '$baseUrl/timezone';
}

View File

@ -1,23 +1,22 @@
import 'dart:async';
import 'dart:io';
import 'package:syncrow_app/features/devices/model/device_category_model.dart';
import 'package:syncrow_app/features/devices/model/device_control_model.dart';
import 'package:syncrow_app/features/devices/model/device_model.dart';
import 'package:syncrow_app/features/devices/model/function_model.dart';
import 'package:syncrow_app/features/auth/bloc/auth_cubit.dart';
import 'package:syncrow_app/features/auth/model/user_model.dart';
import 'package:syncrow_app/features/menu/bloc/profile_bloc/region_model.dart';
import 'package:syncrow_app/features/menu/bloc/profile_bloc/time_zone_model.dart';
import 'package:syncrow_app/services/api/api_links_endpoints.dart';
import 'package:syncrow_app/services/api/http_service.dart';
import '../../features/devices/model/create_temporary_password_model.dart';
class ProfileApi {
static final HTTPService _httpService = HTTPService();
static Future<Map<String, dynamic>> saveName(
String newName, String userId) async {
static Future<Map<String, dynamic>> saveName({String? firstName, String? lastName,}) async {
try {
final response = await _httpService.post(
path: ApiEndpoints.controlDevice.replaceAll('{deviceUuid}', userId),
body: newName,
showServerMessage: false,
final response = await _httpService.put(
path: ApiEndpoints.saveName.replaceAll('{userUuid}', AuthCubit.user!.uuid!),
body: {
"firstName": firstName,
"lastName": lastName
},
expectedResponseModel: (json) {
return json;
},
@ -28,13 +27,29 @@ class ProfileApi {
}
}
static Future<Map<String, dynamic>> saveImage(
File newName, String userId) async {
static Future saveRegion({String? regionUuid,}) async {
try {
final response = await _httpService.post(
path: ApiEndpoints.controlDevice.replaceAll('{deviceUuid}', userId),
body: newName,
showServerMessage: false,
final response = await _httpService.put(
path: ApiEndpoints.saveRegion.replaceAll('{userUuid}', AuthCubit.user!.uuid!),
body: {
"regionUuid": regionUuid,
},
expectedResponseModel: (json) {
return json;
},
);
return response;
} catch (e) {
rethrow;
}
}
static Future saveTimeZone({String? regionUuid,}) async {
try {
final response = await _httpService.put(
path: ApiEndpoints.saveTimeZone.replaceAll('{userUuid}', AuthCubit.user!.uuid!),
body: {
"timezoneUuid": regionUuid,
},
expectedResponseModel: (json) {
return json;
},
@ -45,15 +60,54 @@ class ProfileApi {
}
}
static Future fetchUserInfo(String userId) async {
static Future<Map<String, dynamic>> saveImage(String image) async {
try {
final response = await _httpService.put(
path: ApiEndpoints.sendPicture.replaceAll('{userUuid}', AuthCubit.user!.uuid!),
body: {
"profilePicture": 'data:image/png;base64,$image'
},
expectedResponseModel: (json) {
return json;
},
);
return response;
} catch (e) {
rethrow;
}
}
static Future fetchUserInfo(userId) async {
final response = await _httpService.get(
path: ApiEndpoints.groupBySpace.replaceAll('{unitUuid}', userId),
// queryParameters: params,
showServerMessage: false,
expectedResponseModel: (json) => DevicesCategoryModel.fromJsonList(json),
path: ApiEndpoints.getUser.replaceAll('{userUuid}', userId!),
showServerMessage: true,
expectedResponseModel: (json) =>UserModel.fromJson(json)
);
return response;
}
static Future<List<RegionModel>> fetchRegion() async {
final response = await _httpService.get(
path: ApiEndpoints.getRegion,
showServerMessage: true,
expectedResponseModel: (json) {
return (json as List).map((zone) => RegionModel.fromJson(zone)).toList();
}
);
return response as List<RegionModel>;
}
static Future<List<TimeZone>> fetchTimeZone() async {
final response = await _httpService.get(
path: ApiEndpoints.getTimezone,
showServerMessage: true,
expectedResponseModel: (json) {
return (json as List).map((zone) => TimeZone.fromJson(zone)).toList();
}
);
return response as List<TimeZone>;
}
}