mirror of
https://github.com/SyncrowIOT/syncrow-app.git
synced 2025-11-28 00:34:55 +00:00
Merged with dev
This commit is contained in:
@ -7,15 +7,14 @@ import 'package:shared_preferences/shared_preferences.dart';
|
||||
import 'package:syncrow_app/features/app_layout/bloc/home_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/features/menu/model/region_model.dart';
|
||||
import 'package:syncrow_app/features/menu/model/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> {
|
||||
|
||||
bool isSaving = false;
|
||||
bool editName = false;
|
||||
final FocusNode focusNode = FocusNode();
|
||||
@ -24,7 +23,8 @@ class ProfileBloc extends Bloc<ProfileEvent, ProfileState> {
|
||||
String timeZoneSelected = '';
|
||||
String regionSelected = '';
|
||||
final TextEditingController searchController = TextEditingController();
|
||||
final TextEditingController nameController = TextEditingController(text: '${HomeCubit.user!.firstName} ${HomeCubit.user!.lastName}');
|
||||
final TextEditingController nameController = TextEditingController(
|
||||
text: '${HomeCubit.user!.firstName} ${HomeCubit.user!.lastName}');
|
||||
List<TimeZone>? timeZoneList;
|
||||
List<RegionModel>? regionList;
|
||||
|
||||
@ -54,13 +54,11 @@ class ProfileBloc extends Bloc<ProfileEvent, ProfileState> {
|
||||
final lastName = nameParts.length > 1 ? nameParts[1] : '';
|
||||
var response = await ProfileApi.saveName(firstName: firstName, lastName: lastName);
|
||||
add(InitialProfileEvent());
|
||||
final homeCubit = event.context.read<HomeCubit>();
|
||||
await homeCubit.fetchUserInfo();
|
||||
Navigator.of(event.context).pop(true);
|
||||
await HomeCubit.getInstance().fetchUserInfo();
|
||||
CustomSnackBar.displaySnackBar('Save Successfully');
|
||||
emit(SaveState());
|
||||
} catch (_) {
|
||||
// Handle the error
|
||||
} catch (e) {
|
||||
emit(FailedState(errorMessage: e.toString()));
|
||||
} finally {
|
||||
isSaving = false;
|
||||
}
|
||||
@ -102,7 +100,6 @@ class ProfileBloc extends Bloc<ProfileEvent, ProfileState> {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Future selectTimeZone(SelectTimeZoneEvent event, Emitter<ProfileState> emit) async {
|
||||
try {
|
||||
emit(LoadingInitialState());
|
||||
@ -134,7 +131,7 @@ class ProfileBloc extends Bloc<ProfileEvent, ProfileState> {
|
||||
final filteredRegions = regionList?.where((region) {
|
||||
return region.name.toLowerCase().contains(query);
|
||||
}).toList() ?? [];
|
||||
regionList = filteredRegions;// Assume this fetches the regions
|
||||
regionList = filteredRegions;
|
||||
emit(RegionsLoadedState(regions: filteredRegions));
|
||||
}else{
|
||||
regionList = await ProfileApi.fetchRegion();
|
||||
@ -230,20 +227,51 @@ class ProfileBloc extends Bloc<ProfileEvent, ProfileState> {
|
||||
|
||||
|
||||
|
||||
|
||||
bool _validateInputs() {
|
||||
if (nameController.text.length < 2) {
|
||||
CustomSnackBar.displaySnackBar('Name Must More than 2 ');
|
||||
final nameError = fullNameValidator(nameController.text);
|
||||
if (nameError != null) {
|
||||
CustomSnackBar.displaySnackBar(nameError);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
String? fullNameValidator(String? value) {
|
||||
if (value == null) return 'Full name is required';
|
||||
|
||||
final withoutExtraSpaces = value.replaceAll(RegExp(r"\s+"), ' ').trim();
|
||||
|
||||
if (withoutExtraSpaces.length < 2 || withoutExtraSpaces.length > 30) {
|
||||
return 'Full name must be between 2 and 30 characters long';
|
||||
}
|
||||
|
||||
// Test if it contains anything but alphanumeric spaces and single quote
|
||||
|
||||
if (RegExp(r"/[^ a-zA-Z0-9-\']/").hasMatch(withoutExtraSpaces)) {
|
||||
return 'Only alphanumeric characters, space, dash and single quote are allowed';
|
||||
}
|
||||
|
||||
final parts = withoutExtraSpaces.split(' ');
|
||||
|
||||
if (parts.length < 2) return 'Full name must contain first and last names';
|
||||
|
||||
if (parts.length > 3) return 'Full name can at most contain 3 parts';
|
||||
|
||||
if (parts.any((part) => part.length < 2 || part.length > 30)) {
|
||||
return 'Full name parts must be between 2 and 30 characters long';
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
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(androidInfo.version.sdkInt<33){
|
||||
if (status.isDenied) {
|
||||
PermissionStatus status = await Permission.storage.request();
|
||||
if (status.isGranted) {
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import 'package:equatable/equatable.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/features/menu/model/region_model.dart';
|
||||
import 'package:syncrow_app/features/menu/model/time_zone_model.dart';
|
||||
|
||||
class ProfileState extends Equatable {
|
||||
const ProfileState();
|
||||
|
||||
@ -20,7 +20,7 @@ class ProfileTab extends StatelessWidget {
|
||||
Widget _buildProfileContent(BuildContext context) {
|
||||
final homeCubit = context.read<HomeCubit>();
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 10),
|
||||
padding: const EdgeInsets.symmetric(vertical: 10,),
|
||||
child: InkWell(
|
||||
onTap: () {
|
||||
Navigator.of(context)
|
||||
@ -39,23 +39,27 @@ class ProfileTab extends StatelessWidget {
|
||||
children: [
|
||||
const SizedBox(height: 20),
|
||||
DefaultContainer(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
BodyMedium(
|
||||
text: '${HomeCubit.user!.firstName ?? ''} ',
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
BodyMedium(
|
||||
text: HomeCubit.user!.lastName ?? '',
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
],
|
||||
),
|
||||
const BodySmall(text: "Syncrow Account"),
|
||||
],
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(10.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
BodyMedium(
|
||||
text: '${HomeCubit.user!.firstName ?? ''} ',
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
BodyMedium(
|
||||
text: HomeCubit.user!.lastName ?? '',
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
],
|
||||
),
|
||||
SizedBox(height: 5,),
|
||||
const BodySmall(text: "Syncrow Account"),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
|
||||
@ -2,7 +2,6 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:syncrow_app/features/app_layout/bloc/home_cubit.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';
|
||||
@ -37,8 +36,7 @@ class ProfileView extends StatelessWidget {
|
||||
),
|
||||
InkWell(
|
||||
onTap: () {
|
||||
profileBloc.add(
|
||||
SelectImageEvent(context: context, isSelected: false));
|
||||
profileBloc.add(SelectImageEvent(context: context, isSelected: false));
|
||||
},
|
||||
child: SizedBox.square(
|
||||
dimension: 125,
|
||||
@ -47,7 +45,7 @@ class ProfileView extends StatelessWidget {
|
||||
child: SizedBox.square(
|
||||
dimension: 120,
|
||||
child: CircleAvatar(
|
||||
backgroundColor: Colors.grey,
|
||||
backgroundColor: Colors.white,
|
||||
backgroundImage: profileBloc.image == null
|
||||
? null
|
||||
: FileImage(profileBloc.image!),
|
||||
@ -58,8 +56,8 @@ class ProfileView extends StatelessWidget {
|
||||
child: Image.memory(
|
||||
HomeCubit.user!.profilePicture!,
|
||||
fit: BoxFit.cover,
|
||||
width: 110,
|
||||
height: 110,
|
||||
width: 120,
|
||||
height: 120,
|
||||
),
|
||||
)
|
||||
: null,
|
||||
@ -75,23 +73,26 @@ class ProfileView extends StatelessWidget {
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
IntrinsicWidth(
|
||||
child: TextFormField(
|
||||
maxLength: 30,
|
||||
style: const TextStyle(
|
||||
color: Colors.black,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
focusNode: profileBloc.focusNode,
|
||||
controller: profileBloc.nameController,
|
||||
enabled: profileBloc.editName,
|
||||
onEditingComplete: () {
|
||||
profileBloc.add(SaveNameEvent(context: context));
|
||||
},
|
||||
decoration: const InputDecoration(
|
||||
hintText: "Your Name",
|
||||
border: InputBorder.none,
|
||||
fillColor: Colors.white10,
|
||||
counterText: '', // Hides the character count
|
||||
child: ConstrainedBox(
|
||||
constraints: const BoxConstraints(maxWidth: 200),
|
||||
child: TextFormField(
|
||||
maxLength: 30,
|
||||
style: const TextStyle(
|
||||
color: Colors.black,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
focusNode: profileBloc.focusNode,
|
||||
controller: profileBloc.nameController,
|
||||
enabled: profileBloc.editName,
|
||||
onEditingComplete: () {
|
||||
profileBloc.add(SaveNameEvent(context: context));
|
||||
},
|
||||
decoration: const InputDecoration(
|
||||
hintText: "Your Name",
|
||||
border: InputBorder.none,
|
||||
fillColor: Colors.white10,
|
||||
counterText: '',
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
@ -113,18 +114,30 @@ class ProfileView extends StatelessWidget {
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
// Info
|
||||
DefaultContainer(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 25,
|
||||
vertical: 5,
|
||||
),
|
||||
child:
|
||||
|
||||
Column(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 20, bottom: 20),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
const BodyMedium(text: 'email '),
|
||||
Flexible(child: BodyMedium(text: HomeCubit.user!.email ?? 'No Email')),
|
||||
],
|
||||
),
|
||||
),
|
||||
Container(
|
||||
height: 1,
|
||||
color: ColorsManager.greyColor,
|
||||
),
|
||||
|
||||
InkWell(
|
||||
onTap: () {
|
||||
Navigator.push(
|
||||
|
||||
@ -23,6 +23,11 @@ class RegionPage extends StatelessWidget {
|
||||
),
|
||||
);
|
||||
}
|
||||
if (state is SaveState) {
|
||||
new Future.delayed(const Duration(milliseconds: 500), () {
|
||||
Navigator.of(context).pop(true);
|
||||
});
|
||||
}
|
||||
},
|
||||
builder: (context, state) {
|
||||
final profileBloc = BlocProvider.of<ProfileBloc>(context);
|
||||
@ -55,40 +60,37 @@ class RegionPage extends StatelessWidget {
|
||||
topRight: Radius.circular(20),
|
||||
),
|
||||
),
|
||||
child: ListView.builder(
|
||||
child:Padding(
|
||||
padding: const EdgeInsets.only(bottom: 10.0,top: 10.0,left: 15,right: 15),
|
||||
child: ListView.builder(
|
||||
itemCount: regionList.length,
|
||||
itemBuilder: (context, index) {
|
||||
return InkWell(
|
||||
onTap: () {
|
||||
profileBloc.add(SelectRegionEvent(
|
||||
val: regionList[index].id, context: context));
|
||||
profileBloc.add(SelectRegionEvent(val: regionList[index].id, context: context));
|
||||
},
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(10.0),
|
||||
child: Column(
|
||||
child:Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 10),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 10,),
|
||||
child: SizedBox(
|
||||
height: 45,
|
||||
child: ListTile(
|
||||
contentPadding: EdgeInsets.zero,
|
||||
// trailing: BodyMedium(
|
||||
// text: regionList[index].offset,
|
||||
// fontSize: 13,
|
||||
// fontColor: ColorsManager.textGray,),
|
||||
leading: BodyMedium(
|
||||
fontSize: 15,
|
||||
text: regionList[index].name,),),
|
||||
text: regionList[index].name,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
const Divider(color: ColorsManager.textGray), // Divider between items
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
)),
|
||||
),
|
||||
),
|
||||
],
|
||||
|
||||
@ -25,7 +25,11 @@ class TimeZoneScreenPage extends StatelessWidget {
|
||||
backgroundColor: Colors.red,
|
||||
),
|
||||
);
|
||||
}
|
||||
}if (state is SaveState) {
|
||||
new Future.delayed(const Duration(milliseconds: 500), () {
|
||||
Navigator.of(context).pop(true);
|
||||
});
|
||||
}
|
||||
},
|
||||
builder: (context, state) {
|
||||
final profileBloc = BlocProvider.of<ProfileBloc>(context);
|
||||
@ -58,41 +62,40 @@ class TimeZoneScreenPage extends StatelessWidget {
|
||||
topRight: Radius.circular(20),
|
||||
),
|
||||
),
|
||||
child: ListView.builder(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(bottom: 10.0,top: 10.0,left: 15,right: 15),
|
||||
child: ListView.builder(
|
||||
itemCount: timeZoneList.length,
|
||||
itemBuilder: (context, index) {
|
||||
return InkWell(
|
||||
onTap: () {
|
||||
profileBloc.add(SelectTimeZoneEvent(
|
||||
val: timeZoneList[index].id,
|
||||
context: context));
|
||||
profileBloc.add(SelectTimeZoneEvent(val: timeZoneList[index].id, context: context));
|
||||
},
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(10.0),
|
||||
child: Column(
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 10),
|
||||
child: SizedBox(
|
||||
height: 45,
|
||||
child: ListTile(
|
||||
contentPadding: EdgeInsets.zero,
|
||||
trailing: BodyMedium(
|
||||
text: timeZoneList[index].offset,
|
||||
fontSize: 13,
|
||||
fontColor: ColorsManager.textGray,),
|
||||
leading: BodyMedium(
|
||||
fontSize: 15,
|
||||
text: timeZoneList[index].name,),),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 10,),
|
||||
child: SizedBox(
|
||||
height: 45,
|
||||
child: ListTile(
|
||||
contentPadding: EdgeInsets.zero,
|
||||
trailing: BodyMedium(
|
||||
text: timeZoneList[index].offset,
|
||||
fontSize: 13,
|
||||
fontColor: ColorsManager.textGray,),
|
||||
leading: BodyMedium(
|
||||
fontSize: 15,
|
||||
text: timeZoneList[index].name,),),
|
||||
),
|
||||
const Divider(color: ColorsManager.textGray), // Divider between items
|
||||
],
|
||||
),
|
||||
),
|
||||
const Divider(color: ColorsManager.textGray), // Divider between items
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
)),
|
||||
),
|
||||
),
|
||||
],
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
import 'dart:async';
|
||||
import 'package:syncrow_app/features/app_layout/bloc/home_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/features/menu/model/region_model.dart';
|
||||
import 'package:syncrow_app/features/menu/model/time_zone_model.dart';
|
||||
import 'package:syncrow_app/services/api/api_links_endpoints.dart';
|
||||
import 'package:syncrow_app/services/api/http_service.dart';
|
||||
|
||||
@ -43,6 +43,7 @@ class ProfileApi {
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
static Future saveTimeZone({String? regionUuid,}) async {
|
||||
try {
|
||||
final response = await _httpService.put(
|
||||
@ -99,7 +100,6 @@ class ProfileApi {
|
||||
return response as List<RegionModel>;
|
||||
}
|
||||
|
||||
|
||||
static Future<List<TimeZone>> fetchTimeZone() async {
|
||||
final response = await _httpService.get(
|
||||
path: ApiEndpoints.getTimezone,
|
||||
@ -111,5 +111,4 @@ class ProfileApi {
|
||||
return response as List<TimeZone>;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user