form pro - sin imagenes

This commit is contained in:
Felipe
2024-03-14 22:07:46 -05:00
parent 66125235dd
commit a3c50cf2c8
123 changed files with 2077 additions and 18345 deletions
+4
View File
@@ -3,6 +3,7 @@ import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:injector/injector.dart';
import 'package:prosappco/blocs/authentication_bloc/authentication_bloc.dart';
import 'package:prosappco/blocs/my_user_bloc/my_user_bloc.dart';
import 'package:prosappco/blocs/professional_bloc/professional_bloc.dart';
import 'package:prosappco/blocs/profile_bloc/profile_bloc.dart';
import 'app_view.dart';
@@ -23,6 +24,9 @@ class MainApp extends StatelessWidget {
BlocProvider<ProfileBloc>(
create: (context) => Injector.appInstance.get<ProfileBloc>(),
),
BlocProvider<ProfessionalBloc>(
create: (context) => Injector.appInstance.get<ProfessionalBloc>(),
),
],
child: BlocBuilder<MyUserBloc, MyUserState>(
builder: (context, state) {
@@ -0,0 +1,63 @@
import 'dart:developer';
// ignore: depend_on_referenced_packages
import 'package:bloc/bloc.dart';
import 'package:equatable/equatable.dart';
import 'package:professional_repository/professional_repository.dart';
import 'package:user_repository/user_repository.dart';
part 'professional_event.dart';
part 'professional_state.dart';
class ProfessionalBloc extends Bloc<ProfessionalEvent, ProfessionalState> {
final FirebaseProfessionalRepository _professionalRepository;
final UserRepository _userRepository;
ProfessionalBloc(
{required FirebaseProfessionalRepository professionalRepository,
required UserRepository userRepository})
: _professionalRepository = professionalRepository,
_userRepository = userRepository,
super(ProfessionalInitial()) {
bool isProModeActive = _professionalRepository.isProModeActive;
emit(LoadedModeProState(isProModeActive));
_professionalRepository.sreamIsProModeActive().listen((isProModeActive) {
log('xdd despues de escuchar');
add(UpdateProfessionalEvent(isProModeActive: isProModeActive));
});
on<SwitchProModeEvent>((event, emit) async {
await _professionalRepository.switchProMode();
});
on<UpdateProfessionalEvent>((event, emit) async {
emit(LoadedModeProState(event.isProModeActive));
});
on<SendProfessionalToReviewEvent>((event, emit) async {
final myUser = await _userRepository.lastUser();
if (myUser == null) return;
await _professionalRepository.saveProfessionalInfo(ProfessionalEntity(
id: event.id,
identification: event.identification,
identificationPicture: event.identificationPicture,
address: '',
profession: event.profession,
specializations: event.specializations,
specializationsPictures: event.specializationsPictures,
certificatePicture: event.certificatePicture,
latitude: '',
longitude: '',
rate: '',
location: '',
schedules: Schedules.empty,
paymentMethods: PaymentMethodEntity.empty,
));
_userRepository
.updateUserInfo(myUser.copyWith(proState: ProState.pending));
});
}
}
@@ -0,0 +1,53 @@
part of 'professional_bloc.dart';
abstract class ProfessionalEvent extends Equatable {
const ProfessionalEvent();
@override
List<Object> get props => [];
}
class UpdateProfessionalEvent extends ProfessionalEvent {
final bool isProModeActive;
const UpdateProfessionalEvent({
required this.isProModeActive,
});
}
class SwitchProModeEvent extends ProfessionalEvent {
const SwitchProModeEvent();
}
class SendProfessionalToReviewEvent extends ProfessionalEvent {
final String id;
final String identification;
final String identificationPicture;
final String profession;
final String certificatePicture;
final List<String> specializations;
final List<String> specializationsPictures;
const SendProfessionalToReviewEvent({
required this.id,
required this.identification,
required this.identificationPicture,
required this.profession,
required this.certificatePicture,
required this.specializations,
required this.specializationsPictures,
});
@override
List<Object> get props => [
id,
identification,
identificationPicture,
profession,
certificatePicture,
specializations,
specializationsPictures
];
}
@@ -0,0 +1,19 @@
part of 'professional_bloc.dart';
abstract class ProfessionalState extends Equatable {
const ProfessionalState();
@override
List<Object> get props => [];
}
class ProfessionalInitial extends ProfessionalState {}
class LoadedModeProState extends ProfessionalState {
final bool isProModeActive;
const LoadedModeProState(this.isProModeActive);
@override
List<Object> get props => [isProModeActive];
}
+157 -90
View File
@@ -1,12 +1,19 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:prosappco/blocs/my_user_bloc/my_user_bloc.dart';
import 'package:prosappco/blocs/professional_bloc/professional_bloc.dart';
import 'package:prosappco/components/general_drawer_header.dart';
import 'package:prosappco/components/general_drawer_item.dart';
import 'package:prosappco/screens/configuration/configuration_screen.dart';
import 'package:prosappco/screens/configuration/configuration_support_screen.dart';
import 'package:prosappco/screens/professional/professional_denied_screen.dart';
import 'package:prosappco/screens/professional/professional_form_screen.dart';
import 'package:prosappco/screens/professional/professional_pending_screen.dart';
import 'package:prosappco/screens/web/web_view_screen.dart';
import 'package:url_launcher/url_launcher.dart';
import 'package:user_repository/user_repository.dart';
class GeneralDrawer extends StatelessWidget {
const GeneralDrawer({super.key});
@@ -25,104 +32,164 @@ class GeneralDrawer extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Drawer(
backgroundColor: Theme.of(context).colorScheme.background,
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
const GeneralDrawerHeader(),
Divider(
color: Theme.of(context).colorScheme.onSurface.withOpacity(0.1),
thickness: 0.5,
height: 1,
),
Expanded(
child: SingleChildScrollView(
return BlocBuilder<MyUserBloc, MyUserState>(
builder: (context, userState) {
return BlocBuilder<ProfessionalBloc, ProfessionalState>(
builder: (context, professionalState) {
return Drawer(
backgroundColor: Theme.of(context).colorScheme.background,
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
GeneralDrawerItem(
leading: Icons.history,
label: 'Mis servicios',
onTap: () {
Navigator.pop(context);
},
const GeneralDrawerHeader(),
Divider(
color: Theme.of(context)
.colorScheme
.onSurface
.withOpacity(0.1),
thickness: 0.5,
height: 1,
),
GeneralDrawerItem(
leading: Icons.settings_outlined,
label: 'Configuración',
onTap: () {
Navigator.pop(context);
Navigator.push(
context,
CupertinoPageRoute(
builder: (context) => const ConfigurationScreen(),
),
);
},
),
GeneralDrawerItem(
leading: Icons.help_outline,
label: 'Soporte',
onTap: () {
Navigator.pop(context);
Navigator.push(
context,
CupertinoPageRoute(
builder: (context) =>
const ConfigurationSupportScreen(),
),
);
},
),
GeneralDrawerItem(
leading: Icons.campaign_outlined,
label: 'Sugerencias',
onTap: () {
if (kIsWeb) {
_irSugerencias();
} else {
Navigator.push(
context,
CupertinoPageRoute(
builder: (BuildContext context) {
return WebViewScreen(
label: 'Sugerencias',
link: 'https://admin.prosapp.co/sugerencias');
Expanded(
child: SingleChildScrollView(
child: Column(
children: [
GeneralDrawerItem(
leading: Icons.history,
label: 'Mis servicios',
onTap: () {
Navigator.pop(context);
},
),
);
}
},
GeneralDrawerItem(
leading: Icons.settings_outlined,
label: 'Configuración',
onTap: () {
Navigator.push(
context,
CupertinoPageRoute(
builder: (context) =>
const ConfigurationScreen(),
),
);
},
),
GeneralDrawerItem(
leading: Icons.help_outline,
label: 'Soporte',
onTap: () {
Navigator.push(
context,
CupertinoPageRoute(
builder: (context) =>
const ConfigurationSupportScreen(),
),
);
},
),
GeneralDrawerItem(
leading: Icons.campaign_outlined,
label: 'Sugerencias',
onTap: () {
if (kIsWeb) {
_irSugerencias();
} else {
Navigator.push(
context,
CupertinoPageRoute(
builder: (BuildContext context) {
return WebViewScreen(
label: 'Sugerencias',
link:
'https://admin.prosapp.co/sugerencias');
},
),
);
}
},
),
],
),
),
),
Divider(
color: Theme.of(context)
.colorScheme
.onSurface
.withOpacity(0.1),
thickness: 0.5,
height: 1,
),
const SizedBox(height: 15),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 20),
child: buttonOfState(context, professionalState),
),
const SizedBox(height: 15),
],
),
),
),
Divider(
color: Theme.of(context).colorScheme.onSurface.withOpacity(0.1),
thickness: 0.5,
height: 1,
),
const SizedBox(height: 15),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 20),
child: ElevatedButton(
onPressed: () {},
style: ElevatedButton.styleFrom(
backgroundColor: Theme.of(context).colorScheme.primary,
padding: const EdgeInsets.symmetric(vertical: 15),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10),
)),
child: const Text(
'Modo Profesional',
style: TextStyle(color: Colors.white, fontSize: 18),
),
),
),
const SizedBox(height: 15),
],
),
);
},
);
},
);
}
buttonOfState(BuildContext context, ProfessionalState state) {
return ElevatedButton(
onPressed: () {
// get state of bloc by context
final myUserState = context.read<MyUserBloc>().state;
if (myUserState.status == MyUserStatus.success) {
final user = myUserState.user!;
switch (user.proState) {
case ProState.active:
// Get boc and add event
context
.read<ProfessionalBloc>()
.add(const SwitchProModeEvent());
break;
case ProState.inactive:
Navigator.push(
context,
CupertinoPageRoute(
builder: (context) => const ProfessionalFormScreen(),
),
);
break;
case ProState.pending:
Navigator.push(
context,
CupertinoPageRoute(
builder: (context) => const ProfessionalPendingScreen(),
),
);
break;
case ProState.denied:
Navigator.push(
context,
CupertinoPageRoute(
builder: (context) => const ProfessionalDeniedScreen(),
),
);
break;
}
} else {}
},
style: ElevatedButton.styleFrom(
backgroundColor: Theme.of(context).colorScheme.primary,
padding: const EdgeInsets.symmetric(vertical: 15),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10),
)),
child: (state is LoadedModeProState)
? Text(
state.isProModeActive ? 'Modo cliente' : 'Modo profesional',
style: const TextStyle(color: Colors.white, fontSize: 18),
)
: const CircularProgressIndicator(
color: Colors.white,
));
}
}
@@ -2,7 +2,6 @@ import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:prosappco/blocs/my_user_bloc/my_user_bloc.dart';
import 'package:prosappco/screens/city/city_screen.dart';
import 'package:prosappco/screens/profile/profile_screen.dart';
class GeneralDrawerHeader extends StatelessWidget {
+16
View File
@@ -1,8 +1,11 @@
import 'package:firebase_auth/firebase_auth.dart';
import 'package:injector/injector.dart';
import 'package:profession_repository/profession_repository.dart';
import 'package:professional_repository/professional_repository.dart';
import 'package:prosappco/blocs/auth_bloc/auth_bloc.dart';
import 'package:prosappco/blocs/authentication_bloc/authentication_bloc.dart';
import 'package:prosappco/blocs/my_user_bloc/my_user_bloc.dart';
import 'package:prosappco/blocs/professional_bloc/professional_bloc.dart';
import 'package:prosappco/blocs/profile_bloc/profile_bloc.dart';
import 'package:prosappco/blocs/setting_bloc/setting_bloc.dart';
import 'package:prosappco/blocs/sign_up_bloc/sign_up_bloc.dart';
@@ -20,9 +23,16 @@ class AppDI {
injector.registerSingleton<CityRepository>(() => FirebaseCityRepository());
injector.registerSingleton<ProfessionRepository>(
() => FirebaseProfessionRepository());
injector.registerSingleton<SettingRepository>(
() => FirebaseSettingRepository());
injector.registerSingleton(
() => FirebaseProfessionalRepository(),
);
injector.registerSingleton<AuthenticationBloc>((() =>
AuthenticationBloc(myUserRepository: injector.get<UserRepository>())));
@@ -32,6 +42,12 @@ class AppDI {
injector.registerSingleton<ProfileBloc>(
(() => ProfileBloc(userRepository: injector.get<UserRepository>())));
injector.registerSingleton(
() => ProfessionalBloc(
professionalRepository: injector.get(),
userRepository: injector.get<UserRepository>()),
);
injector.registerDependency<SignInBloc>(
(() => SignInBloc(userRepository: injector.get<UserRepository>())));
+57 -66
View File
@@ -1,8 +1,7 @@
import 'package:city_repository/city_repository.dart';
import 'package:flutter/material.dart';
import 'package:city_repository/city_repository.dart';
import 'package:injector/injector.dart';
import 'package:intl_phone_field/helpers.dart';
import 'package:prosappco/src/components/pop_appbar.dart';
import 'package:shimmer/shimmer.dart';
class CityScreen extends StatefulWidget {
@@ -17,7 +16,6 @@ class _CityScreenState extends State<CityScreen> {
final cityRepository = Injector.appInstance.get<CityRepository>();
List<CityUi>? _cities;
List<CityUi>? _filteredCities;
bool _isLoading = true;
@override
@@ -38,74 +36,67 @@ class _CityScreenState extends State<CityScreen> {
@override
Widget build(BuildContext context) {
return SafeArea(
child: Scaffold(
appBar: PopAppbar(
onPressed: () {
Navigator.pop(context);
},
label: 'Ciudad',
),
body: Column(
children: [
TextField(
controller: _searchController,
onChanged: (value) {
_filterCities(value);
},
decoration: const InputDecoration(
hintText: 'Busca una ciudad',
prefixIcon: Icon(Icons.search),
enabledBorder: UnderlineInputBorder(
borderSide: BorderSide(color: Colors.grey),
),
focusedBorder: UnderlineInputBorder(
borderSide: BorderSide(color: Colors.grey),
),
return Scaffold(
appBar: AppBar(
title: const Text('Ciudad'),
),
body: Column(
children: [
TextField(
controller: _searchController,
onChanged: (value) {
_filterCities(value);
},
decoration: const InputDecoration(
hintText: 'Busca una ciudad',
prefixIcon: Icon(Icons.search),
enabledBorder: UnderlineInputBorder(
borderSide: BorderSide(color: Colors.grey),
),
focusedBorder: UnderlineInputBorder(
borderSide: BorderSide(color: Colors.grey),
),
),
_isLoading
? _buildShimmerEffect()
: Expanded(
child: _filteredCities != null &&
_filteredCities!.isNotEmpty
? ListView.builder(
itemCount: _filteredCities!.length,
itemBuilder: (BuildContext context, int index) {
return Container(
decoration: BoxDecoration(
border: Border(
bottom: BorderSide(
color: Colors.grey.withOpacity(0.2)),
),
_isLoading
? _buildShimmerEffect()
: Expanded(
child: _filteredCities != null && _filteredCities!.isNotEmpty
? ListView.builder(
itemCount: _filteredCities!.length,
itemBuilder: (BuildContext context, int index) {
return Container(
decoration: BoxDecoration(
border: Border(
bottom: BorderSide(
color: Colors.grey.withOpacity(0.2)),
),
),
child: ListTile(
title: Text(
_filteredCities![index].cityName,
style: const TextStyle(
fontWeight: FontWeight.bold,
color: Colors.black87,
),
),
child: ListTile(
title: Text(
_filteredCities![index].cityName,
style: const TextStyle(
fontWeight: FontWeight.bold,
color: Colors.black87,
),
),
subtitle: Text(
"${_filteredCities![index].stateOfCity}, ${_filteredCities![index].countryOfCity}",
style:
const TextStyle(color: Colors.black54),
),
onTap: () {
Navigator.pop(context,
_filteredCities![index].cityName);
},
subtitle: Text(
"${_filteredCities![index].stateOfCity}, ${_filteredCities![index].countryOfCity}",
style: const TextStyle(color: Colors.black54),
),
);
},
)
: const Center(
child: Text('No se encontraron ciudades.'),
),
),
],
),
onTap: () {
Navigator.pop(context,
_filteredCities![index].cityName);
},
),
);
},
)
: const Center(
child: Text('No se encontraron coincidencias'),
),
),
],
),
);
}
@@ -0,0 +1,164 @@
import 'dart:developer';
import 'package:flutter/material.dart';
import 'package:injector/injector.dart';
import 'package:intl_phone_field/helpers.dart';
import 'package:profession_repository/profession_repository.dart';
import 'package:shimmer/shimmer.dart';
class ProfessionScreen extends StatefulWidget {
const ProfessionScreen({super.key});
@override
State<ProfessionScreen> createState() => _ProfessionScreenState();
}
class _ProfessionScreenState extends State<ProfessionScreen> {
final _searchController = TextEditingController();
final professionRepository = Injector.appInstance.get<ProfessionRepository>();
List<String>? _professions;
List<String>? _filteredProfessions;
bool _isLoading = true;
@override
void initState() {
super.initState();
_loadProfessions();
}
void _loadProfessions() {
professionRepository.getProfessions().then((Professions element) {
setState(() {
log(element.toString());
_professions = element.professions;
_filteredProfessions = _professions;
_isLoading = false;
});
});
}
void _filterProfessions(String query) {
if (_professions != null) {
setState(() {
_filteredProfessions = _professions!
.where((profession) => removeDiacritics(profession.toLowerCase())
.contains(removeDiacritics(query.toLowerCase())))
.toList();
});
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Profesiones'),
),
body: Column(
children: [
TextField(
controller: _searchController,
onChanged: (value) {
_filterProfessions(value);
},
decoration: const InputDecoration(
hintText: 'Busca una profesión',
prefixIcon: Icon(Icons.search),
enabledBorder: UnderlineInputBorder(
borderSide: BorderSide(color: Colors.grey),
),
focusedBorder: UnderlineInputBorder(
borderSide: BorderSide(color: Colors.grey),
),
),
),
_isLoading
? _buildShimmerEffect()
: Expanded(
child: _filteredProfessions != null &&
_filteredProfessions!.isNotEmpty
? ListView.builder(
itemCount: _filteredProfessions!.length,
itemBuilder: (BuildContext context, int index) {
final profession = _filteredProfessions![index];
return ListTile(
title: Text(profession),
onTap: () {
Navigator.pop(
context, _filteredProfessions![index]);
},
);
},
)
: const Center(
child: Text('No se encontraron coincidencias'),
),
)
],
),
);
}
Widget _buildShimmerEffect() {
return Expanded(
child: Shimmer.fromColors(
baseColor: Colors.grey[300]!,
highlightColor: Colors.grey[100]!,
child: ListView.builder(
itemCount: 8,
itemBuilder: (_, __) => const ListTileShimmer(),
),
),
);
}
}
class ListTileShimmer extends StatelessWidget {
const ListTileShimmer({super.key});
@override
Widget build(BuildContext context) {
return Container(
decoration: BoxDecoration(
border: Border(
bottom: BorderSide(color: Colors.grey.withOpacity(0.4)),
),
),
child: ListTile(
title: Row(
children: [
Container(
width: MediaQuery.of(context).size.width * 0.8,
height: 20.0,
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(8),
),
),
],
),
subtitle: Row(
children: [
Container(
width: MediaQuery.of(context).size.width * 0.3,
height: 15.0,
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(8),
),
),
const SizedBox(width: 5),
Container(
width: MediaQuery.of(context).size.width * 0.3,
height: 15.0,
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(8),
),
)
],
),
),
);
}
}
@@ -0,0 +1,13 @@
import 'package:flutter/material.dart';
class ProfessionalDeniedScreen extends StatelessWidget {
const ProfessionalDeniedScreen({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Perfil profesional')),
body: const Center(child: Text('Cuenta denegada')),
);
}
}
@@ -0,0 +1,337 @@
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:injector/injector.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:image_picker/image_picker.dart';
import 'package:prosappco/blocs/auth_bloc/auth_bloc.dart';
import 'package:prosappco/blocs/my_user_bloc/my_user_bloc.dart';
import 'package:prosappco/blocs/professional_bloc/professional_bloc.dart';
import 'package:prosappco/screens/profession/profession_screen.dart';
class ProfessionalFormScreen extends StatefulWidget {
const ProfessionalFormScreen({super.key});
@override
State<ProfessionalFormScreen> createState() => _ProfessionalFormScreenState();
}
class _ProfessionalFormScreenState extends State<ProfessionalFormScreen> {
final TextEditingController _cedulaController = TextEditingController();
final TextEditingController _professionController = TextEditingController();
final TextEditingController _controller = TextEditingController();
final List<String> _items = [];
XFile? _imageFile;
late final AuthBloc authBloc;
@override
void initState() {
super.initState();
authBloc = Injector.appInstance.get<AuthBloc>();
}
@override
void dispose() {
_cedulaController.dispose();
_professionController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return BlocProvider<AuthBloc>(
create: (context) => authBloc,
child: Scaffold(
appBar: AppBar(
title: const Text('Perfil profesional'),
),
body: BlocBuilder<MyUserBloc, MyUserState>(
builder: (context, state) {
return SingleChildScrollView(
child: Padding(
padding:
const EdgeInsets.symmetric(horizontal: 40, vertical: 10),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
pictureWidget(state, context),
const SizedBox(height: 30),
TextFormField(
controller: _cedulaController,
decoration: const InputDecoration(
labelText: 'Cedula',
prefixIcon: Icon(Icons.assignment_ind),
hintText: 'Ingresa tu cedula',
border: OutlineInputBorder(
borderRadius: BorderRadius.all(
Radius.circular(10.0),
)),
errorBorder: OutlineInputBorder(
borderSide: BorderSide(color: Colors.red),
),
focusedErrorBorder: OutlineInputBorder(
borderSide: BorderSide(color: Colors.red, width: 2.0),
),
),
validator: (value) {
if (value == null || value.isEmpty) {
return 'Por favor, ingrese su nombre';
}
return null;
},
),
const SizedBox(height: 20.0),
TextFormField(
controller: _professionController,
readOnly: true,
onTap: () async {
final professionName = await Navigator.push(
context,
CupertinoPageRoute(
builder: (BuildContext context) {
return const ProfessionScreen();
},
),
);
if (professionName != null) {
_professionController.text = professionName;
}
},
decoration: const InputDecoration(
labelText: 'Profesión',
prefixIcon: Icon(Icons.work_rounded),
hintText: 'Elige tu profesión',
border: OutlineInputBorder(
borderRadius: BorderRadius.all(
Radius.circular(10.0),
)),
errorBorder: OutlineInputBorder(
borderSide: BorderSide(color: Colors.red),
),
focusedErrorBorder: OutlineInputBorder(
borderSide: BorderSide(color: Colors.red, width: 2.0),
),
),
validator: (value) {
if (value == null || value.isEmpty) {
return 'Por favor, ingrese su nombre';
}
return null;
},
),
const SizedBox(height: 20.0),
TextField(
controller: _controller,
onSubmitted: (value) {
_addItemToList();
},
decoration: InputDecoration(
labelText: 'Especializaciones',
prefixIcon: const Icon(Icons.assignment_rounded),
suffixIcon: IconButton(
onPressed: () {
_addItemToList();
},
icon: const Icon(Icons.add)),
hintText: 'Ingresa tus especializaciones',
border: const OutlineInputBorder(
borderRadius: BorderRadius.all(
Radius.circular(10.0),
)),
errorBorder: const OutlineInputBorder(
borderSide: BorderSide(color: Colors.red),
),
focusedErrorBorder: const OutlineInputBorder(
borderSide: BorderSide(color: Colors.red, width: 2.0),
),
),
),
const SizedBox(height: 10.0),
Wrap(
spacing: 8.0,
runSpacing: 4.0,
children: _items
.map((item) => Chip(
label: Text(item),
backgroundColor: Theme.of(context).primaryColor,
labelStyle:
const TextStyle(color: Colors.white),
deleteIconColor: Colors.white,
onDeleted: () {
_removeItemFromList(item);
},
))
.toList(),
),
const SizedBox(height: 60.0),
saveButton(state, context),
],
),
),
);
},
),
),
);
}
void _addItemToList() {
setState(() {
String newItem = _controller.text.trim();
if (newItem.isNotEmpty) {
_items.add(newItem);
_controller.clear();
}
});
}
void _removeItemFromList(String item) {
setState(() {
_items.remove(item);
});
}
Widget saveButton(MyUserState state, BuildContext context) {
return ElevatedButton(
onPressed: () {
final userId = context.read<MyUserBloc>().state.user!.id;
context.read<ProfessionalBloc>().add(SendProfessionalToReviewEvent(
id: userId,
identification: _cedulaController.text,
identificationPicture: _cedulaController.text,
profession: _professionController.text,
certificatePicture: _professionController.text,
specializations: _items,
specializationsPictures: _items,
));
// if (isLoading) {
// return;
// }
// if (_nameController.text.isEmpty) {
// ScaffoldMessenger.of(context).clearSnackBars();
// ScaffoldMessenger.of(context).showSnackBar(
// const SnackBar(content: Text('Por favor, ingrese su nombre')));
// return;
// }
// if (_cityController.text.isEmpty) {
// ScaffoldMessenger.of(context).clearSnackBars();
// ScaffoldMessenger.of(context).showSnackBar(
// const SnackBar(content: Text('Por favor, ingrese su ciudad')));
// }
// final myUser = state.user!.copyWith(
// name: _nameController.text,
// city: _cityController.text,
// nickname: _nameController.text.trim().toLowerCase(),
// email: _emailController.text,
// phone: _phoneController.text,
// birthday: _birthdayController.text,
// gender: _genderController.text,
// );
// context
// .read<ProfileBloc>()
// .add(UpdateUserInfo(myUser: myUser, filePicture: _imageFile?.path));
ScaffoldMessenger.of(context).clearSnackBars();
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Información actualizada...')),
);
Navigator.pop(context);
},
style: ElevatedButton.styleFrom(
backgroundColor: Colors.blue,
padding: const EdgeInsets.symmetric(vertical: 5),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(50),
),
shadowColor: Colors.grey,
// elevation: 0,
),
child: Container(
constraints: const BoxConstraints(maxWidth: 300.0, minHeight: 50.0),
alignment: Alignment.center,
child: const Text(
'Actualizar',
style: TextStyle(
color: Colors.white,
fontSize: 16,
fontWeight: FontWeight.bold,
),
),
),
);
}
Widget pictureWidget(MyUserState state, BuildContext context) {
final pictureUrl = state.user?.picture;
final pathImageFile = _imageFile?.path;
ImageProvider<Object>? imageProvider;
if (pathImageFile != null && pathImageFile.isNotEmpty) {
imageProvider = FileImage(File(pathImageFile));
} else if (pictureUrl != null && pictureUrl.isNotEmpty) {
imageProvider = NetworkImage(pictureUrl);
}
return GestureDetector(
onTap: () async {
final ImagePicker picker = ImagePicker();
final XFile? image = await picker.pickImage(
source: ImageSource.gallery,
maxHeight: 500,
maxWidth: 500,
imageQuality: 40,
);
if (image != null) {
setState(() {
_imageFile = image;
});
}
},
child: Hero(
tag: 'picture-profile',
child: pictureContainerWidget(imageProvider),
),
);
}
Widget pictureContainerWidget(ImageProvider<Object>? imageProvider) {
final image = imageProvider == null
? null
: DecorationImage(
image: imageProvider,
fit: BoxFit.contain,
);
final widget = image == null
? Icon(
CupertinoIcons.person,
color: Colors.grey.shade400,
size: 40,
)
: null;
return Container(
width: 120,
height: 120,
decoration: BoxDecoration(
color: Colors.grey.shade300,
shape: BoxShape.circle,
image: image,
),
child: widget,
);
}
}
@@ -0,0 +1,81 @@
import 'package:flutter/material.dart';
class ProfessionalPendingScreen extends StatelessWidget {
const ProfessionalPendingScreen({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Perfil profesional')),
body: SingleChildScrollView(
child: Padding(
padding: const EdgeInsets.only(top: 40),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
Icons.access_time,
color: Color(0xFF2BA4EC),
),
SizedBox(width: 8),
Text(
'Información en revisión',
style: TextStyle(
color: Color(0xFF2BA4EC),
fontSize: 17,
fontWeight: FontWeight.w500,
),
),
],
),
Image(
image: const AssetImage('images/checklist.gif'),
width: MediaQuery.of(context).size.width * 0.7,
),
Container(
margin: const EdgeInsets.only(left: 40, right: 40, top: 20),
decoration: BoxDecoration(
color: const Color(0xFFD6F4FF),
borderRadius: BorderRadius.circular(30),
boxShadow: [
BoxShadow(
color: Colors.grey.withOpacity(0.5),
spreadRadius: 2,
blurRadius: 5,
offset: const Offset(0, 3),
),
],
),
padding:
const EdgeInsets.symmetric(vertical: 15, horizontal: 25),
child: Column(
children: [
SizedBox(
width: MediaQuery.of(context).size.width * 0.8,
child: const Column(
children: [
Text(
'Gracias por proporcionar tu información. Actualmente, estamos revisando tus datos y una vez aprobados, podrás acceder al perfil profesional sin problemas. Te notificaremos tan pronto como tu cuenta esté lista.',
style: TextStyle(fontSize: 14),
),
SizedBox(height: 10),
Text(
'¡Gracias por tu paciencia!',
style: TextStyle(fontSize: 14),
),
],
),
),
],
),
),
],
),
),
),
);
}
}
@@ -10,7 +10,6 @@ import 'package:prosappco/blocs/auth_bloc/auth_bloc.dart';
import 'package:prosappco/blocs/my_user_bloc/my_user_bloc.dart';
import 'package:prosappco/components/general_input_decoration.dart';
import 'package:prosappco/components/general_primary_button.dart';
import 'package:prosappco/screens/authentication/otp_auth_screen.dart';
class ProfileRegisterPhoneScreen extends StatefulWidget {
const ProfileRegisterPhoneScreen({super.key});
@@ -22,8 +21,6 @@ class ProfileRegisterPhoneScreen extends StatefulWidget {
class _ProfileRegisterPhoneScreenState
extends State<ProfileRegisterPhoneScreen> {
final TextEditingController _actualPasswordController =
TextEditingController();
late final AuthBloc authBloc;
late String verificationCode;
+2 -6
View File
@@ -1,5 +1,4 @@
import 'package:flutter/material.dart';
import 'package:prosappco/src/components/pop_appbar.dart';
import 'package:webview_flutter/webview_flutter.dart';
class WebViewScreen extends StatefulWidget {
@@ -56,11 +55,8 @@ class _WebViewScreenState extends State<WebViewScreen> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: PopAppbar(
onPressed: () {
Navigator.pop(context);
},
label: widget.label,
appBar: AppBar(
title: Text(widget.label),
),
body: Stack(
children: [
-1
View File
@@ -1,5 +1,4 @@
import 'dart:developer';
import 'package:flutter_bloc/flutter_bloc.dart';
class SimpleBlocObserver extends BlocObserver {
@@ -1,342 +0,0 @@
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter/foundation.dart';
import 'package:get/get.dart';
import 'package:google_sign_in/google_sign_in.dart';
import 'package:prosappco/src/authentication/exceptions/register_failed.dart';
import 'package:prosappco/src/presentation/screens/login/login.dart';
// import 'package:prosappco/src/presentation/screens/service.dart';
import 'package:prosappco/src/presentation/screens/map/service.dart';
import 'package:prosappco/src/presentation/screens/service_web.dart';
class AuthenticationRepository extends GetxController {
static AuthenticationRepository get instance => Get.find();
//Variables
final _auth = FirebaseAuth.instance;
late final Rx<User?> firebaseUser;
final firebase = FirebaseFirestore.instance;
final GoogleSignIn googleSignIn = GoogleSignIn();
// final _userRef = firebase
var verificationId = ''.obs;
@override
void onReady() {
// Future.delayed(const Duration(seconds: 6));
firebaseUser = Rx<User?>(_auth.currentUser);
firebaseUser.bindStream(_auth.userChanges());
ever(firebaseUser, _setInitialScreen);
}
_setInitialScreen(User? user) {
user == null
? Get.offAll(const LoginScreen())
: kIsWeb
? Get.offAll(const ServiceWebScreen())
: Get.offAll(const ServiceScreen());
}
Future<void> phoneAuthentication(String phoneNo) async {
await _auth.verifyPhoneNumber(
phoneNumber: phoneNo,
verificationCompleted: (credential) async {
await _auth.signInWithCredential(credential);
},
codeSent: (verificationId, resendToken) {
this.verificationId.value = verificationId;
},
codeAutoRetrievalTimeout: (verificationId) {
this.verificationId.value = verificationId;
},
verificationFailed: (e) {
if (e.code == 'invalid-phone-number') {
Get.snackbar('Error', 'El numero no es valido.');
} else {
Get.snackbar('Error', 'Algo ha ido mal. Inténtalo de nuevo. $e');
}
},
);
}
Future<bool> verifyOTP(String otp) async {
var credentials = await _auth.signInWithCredential(
PhoneAuthProvider.credential(
verificationId: verificationId.value, smsCode: otp));
return credentials.user != null ? true : false;
}
Future<void> updatePhoneNumber(String verificationId, String smsCode) async {
try {
PhoneAuthCredential credential = PhoneAuthProvider.credential(
verificationId: verificationId, smsCode: smsCode);
await FirebaseAuth.instance.currentUser!.updatePhoneNumber(credential);
print("Phone number updated successfully");
} catch (e) {
print("Error updating phone number: $e");
}
}
Future<void> createUserWithEmailAndPassword(
String email, String password) async {
try {
await _auth.createUserWithEmailAndPassword(
email: email, password: password);
firebaseUser.value != null
? kIsWeb
? Get.offAll(const ServiceWebScreen())
: Get.to(const ServiceScreen())
: Get.to(const LoginScreen());
} on FirebaseAuthException catch (e) {
final ex = SignUpWithEmailAndPasswordFailure.code(e.code);
Get.snackbar(
'Correo ya registrado',
'Por favor pruebe con otro.',
snackPosition: SnackPosition.BOTTOM,
);
} catch (_) {
const ex = SignUpWithEmailAndPasswordFailure();
print('EXCEPTION - ${ex.message}');
throw ex;
}
}
Future<void> loginWithEmailAndPassword(String email, String password) async {
try {
await _auth.signInWithEmailAndPassword(email: email, password: password);
} on FirebaseAuthException catch (e) {
if (e.code == 'wrong-password') {
Get.snackbar(
'Contraseña incorrecta',
'Por favor intentelo de nuevo.',
snackPosition: SnackPosition.BOTTOM,
);
}
if (e.code == 'invalid-email') {
Get.snackbar(
'Ingrese un email valido',
'Por favor pruebe con otro.',
snackPosition: SnackPosition.BOTTOM,
);
}
if (e.code == 'user-not-found') {
Get.snackbar(
'Email no encontrado',
'Este correo no se encuentra registrado.',
snackPosition: SnackPosition.BOTTOM,
);
}
} catch (_) {}
}
Future<void> signInWithGoogle() async {
try {
final GoogleSignInAccount? googleUser = await GoogleSignIn().signIn();
if (googleUser != null) {
final GoogleSignInAuthentication googleAuth =
await googleUser.authentication;
final OAuthCredential credential = GoogleAuthProvider.credential(
accessToken: googleAuth.accessToken,
idToken: googleAuth.idToken,
);
await FirebaseAuth.instance.signInWithCredential(credential);
// Continúa con el flujo de la aplicación después del inicio de sesión exitoso
// Por ejemplo, redirecciona a la siguiente pantalla
firebaseUser.value != null
? kIsWeb
? Get.offAll(const ServiceWebScreen())
: Get.to(const ServiceScreen())
: Get.to(const LoginScreen());
} else {
// El usuario canceló el inicio de sesión con Google
// Puedes manejarlo según tus necesidades
print('Error');
}
} catch (e) {
print('Error - $e');
}
}
Future<void> logout(String uid) async {
try {
await FirebaseFirestore.instance
.collection('users')
.doc(uid)
.update({'token': FieldValue.delete()});
} catch (e) {
print(e);
}
_auth.signOut();
}
String? getCurrentUserPhone() {
final User? user = _auth.currentUser;
return user?.phoneNumber;
}
String? getCurrentUserUid() {
final User? user = _auth.currentUser;
return user?.uid;
}
Future<String> getCity(String uid) async {
String city = '';
try {
final snapshot =
await FirebaseFirestore.instance.collection('users').doc(uid).get();
final Map<String, dynamic>? data = snapshot.data();
city = data?['city'] ?? '';
} catch (e) {
print('Error getting city: $e');
}
return city;
}
Future<String> getGender(String uid) async {
String gender = '';
try {
final snapshot =
await FirebaseFirestore.instance.collection('users').doc(uid).get();
final Map<String, dynamic>? data = snapshot.data();
gender = data?['gender'] ?? '';
} catch (e) {
print('Error getting gender: $e');
}
return gender;
}
Future<String> getBirthday(String uid) async {
String birthday = '';
try {
final snapshot =
await FirebaseFirestore.instance.collection('users').doc(uid).get();
final Map<String, dynamic>? data = snapshot.data();
birthday = data?['birth_date'] ?? '';
} catch (e) {
print('Error getting birthday: $e');
}
return birthday;
}
Future<String> getCoordsOfCity(String uid) async {
String coords = '';
try {
final snapshot =
await FirebaseFirestore.instance.collection('users').doc(uid).get();
final Map<String, dynamic>? data = snapshot.data();
coords = data?['coordsOfCity'] ?? '';
} catch (e) {
print('Error getting coords of city: $e');
}
print('Error getting coords of city: $coords');
return coords;
}
Future<String> getAddress(String uid) async {
String address = '';
try {
final snapshot =
await FirebaseFirestore.instance.collection('users').doc(uid).get();
final Map<String, dynamic>? data = snapshot.data();
address = data?['address'] ?? '';
} catch (e) {
print('Error getting address: $e');
}
return address;
}
Future<String> getUbicacion(String uid) async {
String location = '';
try {
final snapshot =
await FirebaseFirestore.instance.collection('users').doc(uid).get();
final Map<String, dynamic>? data = snapshot.data();
location = data?['ubicacion'] ?? '';
} catch (e) {
print('Error getting address: $e');
}
return location;
}
Future<String> getOpcionalAddress(String uid) async {
String opcional_location = '';
try {
final snapshot =
await FirebaseFirestore.instance.collection('users').doc(uid).get();
final Map<String, dynamic>? data = snapshot.data();
opcional_location = data?['opcional_address'] ?? '';
} catch (e) {
print('Error getting address: $e');
}
return opcional_location;
}
Future<int> getTarifa(String uid) async {
int tarifa = 0;
try {
final snapshot =
await FirebaseFirestore.instance.collection('users').doc(uid).get();
final Map<String, dynamic>? data = snapshot.data();
tarifa = data?['tarifas'] ?? 0;
} catch (e) {
print('Error getting tarifa: $e');
}
return tarifa;
}
Future<String> getPhoto(String uid) async {
String photo = '';
try {
final snapshot =
await FirebaseFirestore.instance.collection('users').doc(uid).get();
final Map<String, dynamic>? data = snapshot.data();
photo = data?['photo'] ?? '';
} catch (e) {
print('Error getting photo: $e');
}
return photo;
}
Future<String> getBanner(String uid) async {
String photo = '';
try {
final snapshot =
await FirebaseFirestore.instance.collection('users').doc(uid).get();
final Map<String, dynamic>? data = snapshot.data();
photo = data?['banner'] ?? '';
} catch (e) {
print('Error getting banner: $e');
}
return photo;
}
Future<String> getProfession(String uid) async {
String profession = '';
try {
final snapshot =
await FirebaseFirestore.instance.collection('users').doc(uid).get();
final Map<String, dynamic>? data = snapshot.data();
profession = data?['profesion'] ?? '';
} catch (e) {
print('Error getting profesion: $e');
}
return profession;
}
Future<String> getState(String uid) async {
String state = '';
try {
final snapshot =
await FirebaseFirestore.instance.collection('users').doc(uid).get();
final Map<String, dynamic>? data = snapshot.data();
state = data?['estado'] ?? '';
} catch (e) {
print('Error getting estado: $e');
}
return state;
}
}
@@ -1,19 +0,0 @@
class SignUpWithEmailAndPasswordFailure {
final String message;
const SignUpWithEmailAndPasswordFailure(
[this.message = "An Unknown error ocurred."]);
factory SignUpWithEmailAndPasswordFailure.code(String code) {
switch (code) {
case 'weak-password':
return const SignUpWithEmailAndPasswordFailure(
'Please enter a stronger password.');
case 'email-alredy-in-use':
return const SignUpWithEmailAndPasswordFailure(
'An account alredy exists for that email.');
default:
return const SignUpWithEmailAndPasswordFailure();
}
}
}
-156
View File
@@ -1,156 +0,0 @@
import 'dart:io';
import 'package:firebase_storage/firebase_storage.dart';
import 'package:flutter/material.dart';
import 'package:flutter_animate/flutter_animate.dart';
import 'package:prosappco/src/components/column_padding.dart';
const double photoSize = 150;
class ReferenceBannerPhoto extends StatelessWidget {
Reference? ref;
double size;
double sizeCircle;
ReferenceBannerPhoto({
super.key,
required this.ref,
this.size = photoSize,
this.sizeCircle = photoSize,
});
Future<Widget> downloadImage() async {
try {
if (ref != null) {
final imageData = await ref!.getData();
if (imageData != null) {
return Image.memory(
imageData,
width: double.infinity,
height: size,
fit: BoxFit.fill,
);
}
}
// ignore: empty_catches
} catch (e) {}
return DefaultPhoto(
sizeDefault: sizeCircle,
);
}
@override
Widget build(BuildContext context) {
return FutureBuilder<Widget>(
future: downloadImage(),
builder: (BuildContext context, AsyncSnapshot<Widget> snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
// mientras la llamada asíncrona está en proceso, muestra un mensaje de carga
return DefaultPhoto();
} else if (snapshot.connectionState == ConnectionState.done &&
snapshot.hasData) {
return snapshot.data!;
} else {
return DefaultPhoto();
}
});
}
}
class DefaultPhoto extends StatelessWidget {
double sizeDefault;
DefaultPhoto({
super.key,
this.sizeDefault = photoSize,
});
@override
Widget build(BuildContext context) {
return Container(
decoration: BoxDecoration(
color: Colors.white,
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.15),
blurRadius: 5,
offset: const Offset(0, 1),
),
],
),
height: 150,
child: ColumnPadding(
alineacion: MainAxisAlignment.spaceAround,
padding: const EdgeInsets.symmetric(horizontal: 70),
children: [
Container(
decoration: BoxDecoration(
color: const Color(0xFFD6F4FF),
borderRadius: BorderRadius.circular(50),
boxShadow: [
BoxShadow(
color: Colors.grey.withOpacity(0.3),
spreadRadius: 2,
blurRadius: 5,
offset: const Offset(0, 3), // changes position of shadow
),
],
),
constraints: const BoxConstraints(minWidth: 250, minHeight: 50),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: const [
Expanded(
child: Center(
child: Text(
'Foto portada',
style: TextStyle(
color: Color(0xFF2BA4EC),
fontSize: 17,
),
),
),
),
Padding(
padding: EdgeInsets.only(right: 15),
child: Icon(
Icons.file_upload_outlined,
color: Color(0xFF2BA4EC),
size: 30,
),
),
],
),
),
],
),
);
}
}
class LocalPhoto extends StatelessWidget {
File file;
LocalPhoto({super.key, required this.file});
@override
Widget build(BuildContext context) {
return Container(
decoration: BoxDecoration(
color: Colors.white,
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.15),
blurRadius: 8,
offset: const Offset(0, 2),
),
],
),
child: Image.file(
file,
width: double.infinity,
height: photoSize,
fit: BoxFit.fill,
),
);
}
}
-69
View File
@@ -1,69 +0,0 @@
import 'package:flutter/material.dart';
import 'package:animate_do/animate_do.dart';
class BottomSheetExpanded extends StatelessWidget {
final List<Widget> children;
final double horizontalPadding;
const BottomSheetExpanded({
Key? key,
required this.children,
this.horizontalPadding = 35,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
width: double.infinity,
decoration: const BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topCenter,
colors: [
Color.fromARGB(255, 139, 224, 255),
Color.fromARGB(255, 152, 228, 255),
Color(0xFFD6F4FF),
],
),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
const SizedBox(height: 50),
Center(
child: FadeInLeft(
duration: const Duration(milliseconds: 1000),
child: const Image(
image: AssetImage('images/logo_prosapp.png'),
),
),
),
const SizedBox(height: 30),
Expanded(
child: FadeInUpBig(
duration: const Duration(milliseconds: 1000),
child: Container(
decoration: const BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.only(
topLeft: Radius.circular(60),
topRight: Radius.circular(60),
),
),
child: Padding(
padding: EdgeInsets.symmetric(
horizontal: horizontalPadding,
vertical: 15,
),
child: SingleChildScrollView(
child: Column(children: children)),
),
),
),
),
],
),
),
);
}
}
-21
View File
@@ -1,21 +0,0 @@
import 'package:flutter/material.dart';
class ColumnPadding extends StatelessWidget {
final List<Widget> children;
final EdgeInsetsGeometry padding;
final MainAxisAlignment alineacion;
const ColumnPadding({
super.key,
required this.children,
required this.padding,
required this.alineacion,
});
@override
Widget build(BuildContext context) {
return Padding(
padding: padding,
child: Column(mainAxisAlignment: alineacion, children: children));
}
}
-434
View File
@@ -1,434 +0,0 @@
import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_rating_bar/flutter_rating_bar.dart';
import 'package:prosappco/screens/web/web_view_screen.dart';
import 'package:prosappco/src/authentication/authentication_repository.dart';
import 'package:prosappco/src/components/photo_view.dart';
import 'package:prosappco/src/models/user_model.dart';
import 'package:prosappco/src/presentation/screens/calendar.dart';
import 'package:prosappco/src/presentation/screens/my_services_pro.dart';
import 'package:prosappco/src/presentation/screens/profile/profile.dart';
import 'package:prosappco/src/presentation/screens/profile/profile_pro.dart';
import 'package:prosappco/src/presentation/screens/profile/profile_pro_web.dart';
import 'package:prosappco/src/presentation/screens/reputacion_pro.dart';
import 'package:prosappco/src/presentation/screens/web_view.dart';
import '../models/scores_model.dart';
import '../presentation/screens/configuracion.dart';
import '../presentation/screens/support.dart';
import 'package:url_launcher/url_launcher.dart';
class DrawerProfessional extends StatefulWidget {
@override
State<DrawerProfessional> createState() => _DrawerProfessionalState();
}
class _DrawerProfessionalState extends State<DrawerProfessional> {
final uid = AuthenticationRepository.instance.getCurrentUserUid();
UserModel? user;
ScoresModel? scoresModel;
Future<void> _irSugerencias() async {
const url = 'https://admin.prosapp.co/sugerencias';
if (await canLaunch(url)) {
await launch(url);
} else {
throw 'No se pudo abrir la URL $url';
}
}
@override
void initState() {
super.initState();
if (user == null) {
UserModel.getUser(uid.toString()).then(
(UserModel s) => setState(() => user = s),
);
}
if (scoresModel == null) {
ScoresModel.scoreTo(uid.toString(), true, false).then(
(ScoresModel s) => setState(() => scoresModel = s),
);
}
}
@override
Widget build(BuildContext context) {
final User? currentUser = FirebaseAuth.instance.currentUser;
return Drawer(
child: Container(
color: const Color(0xFFE9F9FF),
child: Column(
children: [
Container(
color: Colors.white,
child: Column(
children: [
ListTile(
onTap: () {
Navigator.push(
context,
CupertinoPageRoute(
builder: (BuildContext context) {
return const ProfileScreen();
},
),
);
},
title: Text(user?.name ?? '',
style: const TextStyle(fontWeight: FontWeight.bold)),
subtitle: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
currentUser?.phoneNumber ?? '',
style: const TextStyle(fontSize: 12),
),
Text(
user?.city ?? '',
style: const TextStyle(fontSize: 12),
),
],
),
leading: ReferencePhoto(
ref: user?.photo,
size: 55,
sizeCircle: 60,
),
trailing: const Icon(Icons.keyboard_arrow_right,
color: Colors.black),
contentPadding: const EdgeInsets.symmetric(
vertical: 20, horizontal: 16),
),
],
),
),
Container(
decoration: BoxDecoration(
boxShadow: [
BoxShadow(
color: Colors.grey.withOpacity(0.3),
spreadRadius: 1,
blurRadius: 3,
offset: const Offset(0, 0), // changes position of shadow
),
],
),
child: Divider(
height: 0,
color: Colors.grey[300],
),
),
Expanded(
child: Column(
children: [
ListTile(
onTap: () {
Navigator.of(context).push(
CupertinoPageRoute(
builder: (BuildContext context) {
return MyServicesProScreen();
},
),
);
},
leading: const Icon(
Icons.history,
color: Colors.black,
),
title: const Text(
'Mis servicios',
style: TextStyle(fontSize: 15),
),
),
ListTile(
onTap: () {
Navigator.push(
context,
CupertinoPageRoute(
builder: (BuildContext context) {
if (kIsWeb) {
return const ProfileProWebScreen();
} else {
return const ProfileProScreen();
}
},
),
);
},
leading: const Icon(
Icons.person_outline,
color: Colors.black,
),
title: const Text(
'Perfil profesional',
style: TextStyle(fontSize: 15),
),
),
ListTile(
onTap: () {
Navigator.push(
context,
CupertinoPageRoute(
builder: (BuildContext context) {
return const ConfiguracionScreen();
},
),
);
},
leading: const Icon(
Icons.construction_outlined,
color: Colors.black,
),
title: const Text(
'Configuración',
style: TextStyle(fontSize: 15),
),
),
ListTile(
onTap: () {
Navigator.push(
context,
CupertinoPageRoute(
builder: (BuildContext context) {
return const SupportScreen();
},
),
);
},
leading: const Icon(
Icons.question_mark_rounded,
color: Colors.black,
),
title: const Text(
'Soporte',
style: TextStyle(fontSize: 15),
),
),
ListTile(
onTap: () {
if (kIsWeb) {
_irSugerencias();
} else {
Navigator.push(
context,
CupertinoPageRoute(
builder: (BuildContext context) {
return WebViewScreen(
label: 'Sugerencias',
link: 'https://admin.prosapp.co/sugerencias');
},
),
);
}
},
leading: const Icon(
Icons.campaign_outlined,
color: Colors.black,
),
title: const Text(
'Sugerencias',
style: TextStyle(fontSize: 15),
),
),
// ListTile(
// onTap: () {
// Navigator.of(context).push(
// CupertinoPageRoute(
// builder: (BuildContext context) {
// return const MessagesScreen();
// },
// ),
// );
// },
// leading: const Icon(
// Icons.messenger_outline,
// color: Colors.black,
// ),
// title: const Text(
// 'Mensajes',
// style: TextStyle(fontSize: 15),
// ),
// ),
ListTile(
onTap: () {
Navigator.push(
context,
CupertinoPageRoute(
builder: (BuildContext context) {
return const CalendarScreen();
},
),
);
},
leading: const Icon(
Icons.calendar_month,
color: Colors.black,
),
title: const Text(
'Calendario',
style: TextStyle(fontSize: 15),
),
),
Builder(builder: (BuildContext context) {
return Container(
color: const Color(0xFF2BA4EC),
child: ListTile(
onTap: () {
if (ModalRoute.of(context)?.settings.name !=
'/solicitud') {
Navigator.pushNamed(context, '/solicitud');
} else {
Scaffold.of(context).openEndDrawer();
}
},
trailing: const Icon(
Icons.keyboard_arrow_right,
color: Colors.white,
),
title: const Text(
'Solicitudes',
style: TextStyle(
color: Colors.white,
fontSize: 17,
fontWeight: FontWeight.bold),
),
contentPadding: const EdgeInsets.symmetric(
vertical: 5, horizontal: 16),
),
);
}),
Container(
decoration: BoxDecoration(
boxShadow: [
BoxShadow(
color: Colors.grey.withOpacity(0.5),
spreadRadius: 2,
blurRadius: 3,
offset:
const Offset(0, 2), // changes position of shadow
),
],
),
child: Container(
color: Colors.white,
child: ListTile(
onTap: () async {
Navigator.push(
context,
CupertinoPageRoute(
builder: (BuildContext context) {
return const ReputationProScreen();
},
),
);
},
trailing: const Icon(Icons.keyboard_arrow_right,
color: Colors.black),
title: const Text(
'Reputación',
style: TextStyle(color: Colors.black),
),
subtitle: Row(
children: [
RatingBar.builder(
initialRating: scoresModel?.average ?? 0,
minRating: 1,
direction: Axis.horizontal,
allowHalfRating: true,
itemCount: 5,
itemSize: 25,
maxRating: 5,
itemPadding:
const EdgeInsets.symmetric(horizontal: 0),
itemBuilder: (context, _) => const Icon(
Icons.star,
color: Color(0xFF2BA4EC),
),
onRatingUpdate: (rating) {},
ignoreGestures: true,
),
const SizedBox(width: 5),
Text(
'(${scoresModel?.total.toString()}) ${scoresModel?.average.toStringAsFixed(1)}'),
],
),
),
),
),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
'Prosapp',
style: TextStyle(fontSize: 10, color: Colors.grey[700]),
),
Padding(
padding:
const EdgeInsets.only(top: 7, left: 3, right: 3),
child: Text(
'®',
style:
TextStyle(fontSize: 25, color: Colors.grey[700]),
),
),
Text(
'todos los derechos reservados',
style: TextStyle(fontSize: 10, color: Colors.grey[700]),
),
],
),
],
),
),
ElevatedButton(
onPressed: () {
Navigator.pushReplacementNamed(context, '/servicio');
},
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFF2BA4EC),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(50),
),
elevation: 0,
minimumSize: const Size(230, 45),
),
child: const Text(
'Modo usuario',
style: TextStyle(
color: Colors.white,
fontSize: 15,
),
),
),
const SizedBox(height: 5),
ElevatedButton(
onPressed: () {
AuthenticationRepository.instance.logout(uid!);
},
style: ElevatedButton.styleFrom(
backgroundColor: Colors.red,
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(20)),
),
minimumSize: const Size(230, 40),
),
child: const Text(
'Cerrar Sesión',
style: TextStyle(
color: Colors.white,
fontSize: 15,
),
),
),
const SizedBox(height: 5),
],
),
),
);
}
}
-17
View File
@@ -1,17 +0,0 @@
import 'package:flutter/cupertino.dart';
import 'package:http/http.dart' as http;
class NetworkUtility {
static Future<String?> fetchUrl(Uri uri,
{Map<String, String>? headers}) async {
try {
final response = await http.get(uri, headers: headers);
if (response.statusCode == 200) {
return response.body;
}
} catch (e) {
debugPrint('error - ${e.toString()}');
}
return null;
}
}
-125
View File
@@ -1,125 +0,0 @@
import 'dart:io';
import 'package:firebase_storage/firebase_storage.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_animate/flutter_animate.dart';
const double photoSize = 100;
const double iconSize = 35;
class ReferencePhoto extends StatelessWidget {
Reference? ref;
double size;
double sizeIcon;
double sizeCircle;
ReferencePhoto({
super.key,
required this.ref,
this.sizeIcon = iconSize,
this.size = photoSize,
this.sizeCircle = photoSize,
});
Future<Widget> downloadImage() async {
try {
if (ref != null) {
final imageData = await ref!.getData();
if (imageData != null) {
return ClipOval(
child: Image.memory(
imageData,
width: size,
height: size,
fit: BoxFit.cover,
),
);
}
}
// ignore: empty_catches
} catch (e) {}
return DefaultPhoto(
sizeDefault: sizeCircle,
iconDefault: sizeIcon,
);
}
@override
Widget build(BuildContext context) {
return FutureBuilder<Widget>(
future: downloadImage(),
builder: (BuildContext context, AsyncSnapshot<Widget> snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
// mientras la llamada asíncrona está en proceso, muestra un mensaje de carga
return SizedBox(
width: size,
height: size,
child: Center(
child: DefaultPhoto(),
),
);
} else if (snapshot.connectionState == ConnectionState.done &&
snapshot.hasData) {
return snapshot.data!;
} else {
return DefaultPhoto();
}
});
}
}
class DefaultPhoto extends StatelessWidget {
double sizeDefault;
double iconDefault;
DefaultPhoto({
super.key,
this.sizeDefault = photoSize,
this.iconDefault = iconSize,
});
@override
Widget build(BuildContext context) {
return Container(
width: sizeDefault,
height: sizeDefault,
decoration: BoxDecoration(
color: const Color(0xFF2BA4EC),
borderRadius: BorderRadius.circular(50),
),
child: Icon(
Icons.person,
color: const Color.fromARGB(255, 255, 255, 255),
size: iconDefault,
),
);
}
}
class LocalPhoto extends StatelessWidget {
File file;
LocalPhoto({super.key, required this.file});
@override
Widget build(BuildContext context) {
if (kIsWeb) {
return ClipOval(
child: Image.network(
file.path,
width: photoSize,
height: photoSize,
fit: BoxFit.cover,
),
);
}
return ClipOval(
child: Image.file(
file,
width: photoSize,
height: photoSize,
fit: BoxFit.cover,
),
);
}
}
-118
View File
@@ -1,118 +0,0 @@
import 'package:firebase_storage/firebase_storage.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_animate/flutter_animate.dart';
const double photoSize = 100;
const double iconSize = 55;
class ReferencePhotoWeb extends StatelessWidget {
Reference? ref;
double size;
double sizeIcon;
double sizeCircle;
ReferencePhotoWeb({
super.key,
required this.ref,
this.sizeIcon = iconSize,
this.size = photoSize,
this.sizeCircle = photoSize,
});
Future<Widget> downloadImage() async {
try {
if (ref != null) {
final imageData = await ref!.getData();
if (imageData != null) {
return ClipOval(
child: Image.memory(
imageData,
width: size,
height: size,
fit: BoxFit.cover,
),
);
}
}
// ignore: empty_catches
} catch (e) {}
return DefaultPhotoWeb(
sizeDefault: sizeCircle,
iconDefault: sizeIcon,
);
}
@override
Widget build(BuildContext context) {
return FutureBuilder<Widget>(
future: downloadImage(),
builder: (BuildContext context, AsyncSnapshot<Widget> snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
// mientras la llamada asíncrona está en proceso, muestra un mensaje de carga
return SizedBox(
width: size,
height: size,
child: const Center(
child: CircularProgressIndicator(),
),
);
} else if (snapshot.connectionState == ConnectionState.done &&
snapshot.hasData) {
return snapshot.data!;
} else {
return DefaultPhotoWeb();
}
});
}
}
class DefaultPhotoWeb extends StatelessWidget {
double sizeDefault;
double iconDefault;
DefaultPhotoWeb({
super.key,
this.sizeDefault = photoSize,
this.iconDefault = iconSize,
});
@override
Widget build(BuildContext context) {
return Container(
width: sizeDefault,
height: sizeDefault,
decoration: BoxDecoration(
color: const Color(0xFF2BA4EC),
borderRadius: BorderRadius.circular(50),
),
child: Icon(
Icons.person,
color: const Color.fromARGB(255, 255, 255, 255),
size: iconDefault,
),
);
}
}
class LocalPhotoWeb extends StatelessWidget {
Uint8List? file;
LocalPhotoWeb({super.key, required this.file});
@override
Widget build(BuildContext context) {
if (file == null) {
return DefaultPhotoWeb();
} else {
return ClipOval(
child: Image.memory(
file!,
width: photoSize,
height: photoSize,
fit: BoxFit.cover,
),
);
}
}
}
-35
View File
@@ -1,35 +0,0 @@
import 'package:flutter/material.dart';
class PopAppbar extends StatelessWidget implements PreferredSizeWidget {
final VoidCallback onPressed;
final String label;
const PopAppbar({
super.key,
required this.onPressed,
required this.label,
});
@override
Size get preferredSize => Size.fromHeight(kToolbarHeight);
@override
Widget build(BuildContext context) {
return AppBar(
backgroundColor: Colors.white,
leading: IconButton(
icon: const Icon(Icons.arrow_back),
onPressed: onPressed,
),
iconTheme: const IconThemeData(
color: Colors.black,
),
title: Text(
label,
style: const TextStyle(
color: Colors.black,
),
),
);
}
}
-46
View File
@@ -1,46 +0,0 @@
import 'package:flutter/material.dart';
class PrimaryButtom extends StatelessWidget {
final VoidCallback onPressed;
final String label;
final bool
isEnabled; // Nuevo parámetro para indicar si el botón está habilitado
const PrimaryButtom({
Key? key,
required this.onPressed,
required this.label,
this.isEnabled = true, // Valor predeterminado: habilitado
}) : super(key: key);
@override
Widget build(BuildContext context) {
return ElevatedButton(
onPressed: isEnabled
? onPressed
: null, // Habilita/deshabilita el botón según isEnabled
style: ElevatedButton.styleFrom(
backgroundColor: isEnabled
? const Color(0xFF2BA4EC)
: Colors.grey, // Cambia el color de fondo
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(50),
),
elevation: isEnabled
? 0
: 0, // Cambia la elevación para dar una sensación de clickeabilidad
minimumSize: const Size(230, 60),
),
child: Text(
label,
style: TextStyle(
color: isEnabled
? Colors.white
: Colors.black, // Cambia el color del texto
fontWeight: FontWeight.bold,
fontSize: 18,
),
),
);
}
}
-212
View File
@@ -1,212 +0,0 @@
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
typedef TimeCallback = void Function(TimeOfDay? pickedTime);
class SchedulePicker extends StatefulWidget {
final String name;
Schedule schedule;
SchedulePicker({super.key, required this.name, required this.schedule});
@override
State<SchedulePicker> createState() => _SchedulePickerState();
}
class _SchedulePickerState extends State<SchedulePicker> {
@override
Widget build(BuildContext context) {
return Column(
children: [
customSwitch(widget.name, widget.schedule.habilitado, (value) {
widget.schedule.habilitado = value;
}),
...datePickers(widget.schedule.habilitado),
const Divider(
height: 15,
color: Colors.grey,
),
],
);
}
List<Widget> datePickers(bool value) {
if (!value) return [];
return [
customSwitch(
'Jornada continua',
widget.schedule.jornadaContinua,
(value) {
widget.schedule.jornadaContinua = value;
},
),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 25),
child: Row(children: [
datePicker(widget.schedule.range1Hour1,
(pickedTime) => widget.schedule.range1Hour1 = pickedTime),
const Text('-'),
...(!widget.schedule.jornadaContinua
? [
datePicker(widget.schedule.range1Hour2,
(pickedTime) => widget.schedule.range1Hour2 = pickedTime),
const Text(' '),
]
: []),
...(!widget.schedule.jornadaContinua
? [
datePicker(widget.schedule.range2Hour1,
(pickedTime) => widget.schedule.range2Hour1 = pickedTime),
const Text('-'),
]
: []),
datePicker(widget.schedule.range2Hour2,
(pickedTime) => widget.schedule.range2Hour2 = pickedTime),
]),
),
];
}
datePicker(TimeOfDay? time, TimeCallback callback) {
return Expanded(
child: TextFormField(
textAlign: TextAlign.center,
onTap: () async {
final TimeOfDay? pickedTime = await showTimePicker(
context: context,
initialTime: TimeOfDay.now(),
);
if (pickedTime != null) {
setState(() {
callback(pickedTime);
});
}
},
readOnly: true,
decoration: const InputDecoration(
hintText: 'Hora',
),
controller: TextEditingController(
text: time == null ? '' : time.format(context),
),
style: const TextStyle(fontSize: 15),
),
);
}
customSwitch(String text, bool switchValue, ValueChanged<bool> onChanged) {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 30),
child: SizedBox(
height: 40,
child: Row(
children: [
Expanded(
child: Text(
text,
style: const TextStyle(
fontSize: 15,
color: Colors.black,
),
)),
Transform.scale(
scale: 1.2,
child: Switch(
value: switchValue,
onChanged: (bool newValue) {
setState(() {
onChanged(newValue);
});
},
),
),
],
),
),
);
}
}
class Schedule {
bool habilitado;
bool jornadaContinua;
TimeOfDay? range1Hour1;
TimeOfDay? range1Hour2;
TimeOfDay? range2Hour1;
TimeOfDay? range2Hour2;
Schedule(
this.habilitado,
this.jornadaContinua,
this.range1Hour1,
this.range1Hour2,
this.range2Hour1,
this.range2Hour2,
);
static Schedule fromJson(Map<String, dynamic> json) {
return Schedule(
json['habilitado'],
json['jornadaContinua'],
_parseTime(json['range1Hour1']),
_parseTime(json['range1Hour2']),
_parseTime(json['range2Hour1']),
_parseTime(json['range2Hour2']),
);
}
static TimeOfDay? _parseTime(String? time) {
if (time == null) return null;
final components = time.split(' ');
final hourMinutes = components[0].split(':');
final hour = int.parse(hourMinutes[0]);
final minutes = int.parse(hourMinutes[1]);
if (components[1] == 'PM' && hour < 12) {
return TimeOfDay(hour: hour + 12, minute: minutes);
} else if (components[1] == 'AM' && hour == 12) {
return TimeOfDay(hour: 0, minute: minutes);
}
return TimeOfDay(hour: hour, minute: minutes);
}
static TimeOfDay stringToTimeOfDay(String? tod) {
if (tod == null) {
return TimeOfDay.now();
}
final format = DateFormat.jm();
return TimeOfDay.fromDateTime(format.parse(tod));
}
@override
String toString() {
return 'Schedule(habilitado: $habilitado, jornadaContinua: $jornadaContinua, '
'range1Hour1: $range1Hour1, range1Hour2: $range1Hour2, '
'range2Hour1: $range2Hour1, range2Hour2: $range2Hour2)';
}
static Future<Map<String, Schedule>> getHorarios(String uid) async {
try {
final snapshot =
await FirebaseFirestore.instance.collection('users').doc(uid).get();
final Map<String, dynamic>? data = snapshot.data();
final Map<String, dynamic>? horarioData = data?['horario'];
final horarios = <String, Schedule>{};
horarioData?.forEach((key, value) {
horarios[key] = Schedule.fromJson(value);
});
return horarios;
} catch (e) {
print('Error getting user: $e');
return {
"1": Schedule(false, false, null, null, null, null),
"2": Schedule(false, false, null, null, null, null),
"3": Schedule(false, false, null, null, null, null),
"4": Schedule(false, false, null, null, null, null),
"5": Schedule(false, false, null, null, null, null),
"6": Schedule(false, false, null, null, null, null),
"7": Schedule(false, false, null, null, null, null),
};
}
}
}
@@ -1,10 +0,0 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
class NameEmailCityController extends GetxController {
static NameEmailCityController get instance => Get.find();
final name = TextEditingController();
final email = TextEditingController();
final city = TextEditingController();
}
@@ -1,10 +0,0 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
class InforProfessionalController extends GetxController {
static InforProfessionalController get instance => Get.find();
final cedula = TextEditingController();
final profesion = TextEditingController();
final especializacion = TextEditingController();
}
@@ -1,15 +0,0 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:prosappco/src/authentication/authentication_repository.dart';
class LoginEmailController extends GetxController {
static LoginEmailController get instance => Get.find();
final email = TextEditingController();
final password = TextEditingController();
Future<void> loginUser(String email, String password) async {
await AuthenticationRepository.instance
.loginWithEmailAndPassword(email, password);
}
}
@@ -1,116 +0,0 @@
import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
class NewPhoneController extends GetxController {
final FirebaseAuth _auth = FirebaseAuth.instance;
final newPhoneNo = TextEditingController(text: '');
final otpCode = TextEditingController(text: '');
Future<void> updatePhoneNumber(newPhoneNo) async {
final currentUser = _auth.currentUser;
if (currentUser?.phoneNumber == newPhoneNo) {
Get.snackbar(
'Ya estas registrado',
'Este es tu numero actual.',
snackPosition: SnackPosition.BOTTOM,
);
} else {
try {
final PhoneVerificationCompleted verificationCompleted =
(PhoneAuthCredential credential) async {
await currentUser?.updatePhoneNumber(credential);
Get.snackbar(
'Número de teléfono actualizado',
'El número de teléfono se ha actualizado correctamente.',
snackPosition: SnackPosition.BOTTOM,
);
};
final PhoneVerificationFailed verificationFailed =
(FirebaseAuthException e) {
Get.snackbar(
'Ingresa un numero de telefono valido',
'verifica que el campo tenga todos los caracteres o intentalo de nuevo ${e}',
snackPosition: SnackPosition.BOTTOM,
);
};
final PhoneCodeSent codeSent =
(String verificationId, [int? forceResendingToken]) {
Get.defaultDialog(
title: 'Ingrese el código de verificación',
content: Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
children: [
TextField(
controller: otpCode,
decoration: InputDecoration(
labelText: 'Código de verificación',
),
),
],
),
),
actions: [
TextButton(
onPressed: () {
Get.back();
},
child: Text('Cancelar'),
),
ElevatedButton(
onPressed: () async {
try {
final PhoneAuthCredential credential =
PhoneAuthProvider.credential(
verificationId: verificationId,
smsCode: otpCode.text,
);
await currentUser?.updatePhoneNumber(credential);
Get.back();
Get.snackbar(
'Número de teléfono actualizado',
'El número de teléfono se ha actualizado correctamente.',
snackPosition: SnackPosition.BOTTOM,
);
} catch (e) {
Get.snackbar(
'Numero ya registrado',
'El numero de telefono ingresado ya se encuentra registrado',
snackPosition: SnackPosition.BOTTOM,
);
}
},
child: Text('Actualizar'),
),
],
);
};
final PhoneCodeAutoRetrievalTimeout codeAutoRetrievalTimeout =
(String verificationId) {
// Aquí puedes hacer algo si se agota el tiempo de espera para ingresar el código de verificación automáticamente.
};
await _auth.verifyPhoneNumber(
phoneNumber: newPhoneNo,
verificationCompleted: verificationCompleted,
verificationFailed: verificationFailed,
codeSent: codeSent,
codeAutoRetrievalTimeout: codeAutoRetrievalTimeout,
);
} catch (e) {
print('Error actualizando el número de teléfono: $e');
Get.snackbar(
'Error actualizando el número de teléfono',
'Ha ocurrido un error al actualizar el número de teléfono: $e',
snackPosition: SnackPosition.BOTTOM,
);
}
}
}
}
-19
View File
@@ -1,19 +0,0 @@
import 'package:flutter/foundation.dart';
import 'package:get/get.dart';
import 'package:prosappco/src/authentication/authentication_repository.dart';
// import 'package:prosappco/src/presentation/screens/service.dart';
import 'package:prosappco/src/presentation/screens/map/service.dart';
import 'package:prosappco/src/presentation/screens/service_web.dart';
class OTPController extends GetxController {
static OTPController get instance => Get.find();
Future<void> verifyOTP(String otp) async {
var isVerified = AuthenticationRepository.instance.verifyOTP(otp);
await isVerified
? kIsWeb
? Get.offAll(const ServiceWebScreen())
: Get.to(const ServiceScreen())
: Get.back();
}
}
@@ -1,13 +0,0 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:prosappco/src/authentication/authentication_repository.dart';
class PhoneAuthController extends GetxController {
static PhoneAuthController get instance => Get.find();
final phoneNo = TextEditingController();
Future<void> phoneAuthentication(String phoneNo) async {
await AuthenticationRepository.instance.phoneAuthentication(phoneNo);
}
}
@@ -1,15 +0,0 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:prosappco/src/authentication/authentication_repository.dart';
class RegisterController extends GetxController {
static RegisterController get instance => Get.find();
final email = TextEditingController();
final password = TextEditingController();
Future<void> registerUser(String email, String password) async {
await AuthenticationRepository.instance
.createUserWithEmailAndPassword(email, password);
}
}
-114
View File
@@ -1,114 +0,0 @@
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:prosappco/src/models/user_model.dart';
class ChatModel {
List<MessageModel> messages;
String professional_id;
String user_id;
UserModel? user;
UserModel? professional;
String id;
ChatModel(
{required this.messages,
required this.professional_id,
required this.user_id,
this.user,
this.professional,
required this.id});
static Future<ChatModel> fromDocumentSnapshot2(
DocumentSnapshot<Map<String, dynamic>> snapshot,
bool fillUserModel) async {
try {
List<MessageModel> messages = [];
List<dynamic> messagesData = snapshot.get('message') ?? [];
for (var data in messagesData) {
messages.add(MessageModel(
user: data['user'] ?? '',
content: data['content'] ?? '',
timestamp: (data['timestamp'] ?? '' as Timestamp).toDate(),
));
}
return ChatModel(
messages: messages,
professional_id: snapshot.get('professional_id') ?? '',
user_id: snapshot.get('user_id') ?? '',
user: fillUserModel
? await UserModel.getUser(snapshot.get('user_id') ?? '')
: null,
professional: fillUserModel
? await UserModel.getUser(snapshot.get('professional_id') ?? '')
: null,
id: snapshot.id);
} catch (e) {
print('error $e');
return ChatModel(messages: [], professional_id: '', user_id: '', id: '');
}
}
static ChatModel fromDocumentSnapshot(
DocumentSnapshot<Map<String, dynamic>> snapshot,
) {
try {
List<MessageModel> messages = [];
List<dynamic> messagesData = snapshot.get('message') ?? [];
for (var data in messagesData) {
messages.add(MessageModel(
user: data['user'] ?? '',
content: data['content'] ?? '',
timestamp: (data['timestamp'] as Timestamp).toDate(),
));
}
return ChatModel(
messages: messages,
professional_id: snapshot.get('professional_id'),
user_id: snapshot.get('user_id'),
id: snapshot.id);
} catch (e) {
print('error $e');
return ChatModel(messages: [], professional_id: '', user_id: '', id: '');
}
}
static Future<List<ChatModel>> getChatsByProId(String userReceived) async {
final receivedScoresQuery = FirebaseFirestore.instance
.collection('chats')
.where('professional_id', isEqualTo: userReceived);
final receivedScoresSnapshot = await receivedScoresQuery.get();
final receivedScores = await Future.wait(receivedScoresSnapshot.docs
.map((doc) async => await fromDocumentSnapshot2(doc, true))
.toList());
return receivedScores;
}
static Future<List<ChatModel>> getChatsByUserId(String userReceived) async {
final receivedScoresQuery = FirebaseFirestore.instance
.collection('chats')
.where('user_id', isEqualTo: userReceived);
final receivedScoresSnapshot = await receivedScoresQuery.get();
final receivedScores = await Future.wait(receivedScoresSnapshot.docs
.map((doc) async => await fromDocumentSnapshot2(doc, true))
.toList());
return receivedScores;
}
}
class MessageModel {
String user;
String content;
DateTime timestamp;
MessageModel(
{required this.user, required this.content, required this.timestamp});
}
-259
View File
@@ -1,259 +0,0 @@
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:prosappco/src/authentication/authentication_repository.dart';
import 'package:prosappco/src/models/scores_model.dart';
final uid = AuthenticationRepository.instance.getCurrentUserUid();
class EventoService {
Future<String?> createEvent(
String title,
String description,
String day,
String range1Hour1,
String range1Hour2,
String professionalId,
String ubicacion,
String address,
double latitude,
double longitude,
String status,
int? tarifa,
bool professionalScored,
bool userScored,
) async {
try {
DateTime ahora = DateTime.now();
final eventId =
await FirebaseFirestore.instance.collection('services').add({
'user_id': uid,
'title': title,
'description': description,
'day': day,
'range1Hour1': range1Hour1,
'range1Hour2': range1Hour2,
'professional_id': professionalId,
'ubicacion': ubicacion,
'address': address,
'latitude': latitude,
'longitude': longitude,
'status': status,
'Timestamp': ahora,
'tarifa': tarifa ?? 0,
'professional_scored': professionalScored,
'user_scored': userScored,
}).then((value) {
FirebaseFirestore.instance.collection('users').doc(uid).update({
'services': FieldValue.arrayUnion([value.id])
});
return value.id;
});
return eventId;
} catch (e) {
print('Evento $e');
}
return null;
}
}
Future<List<Event>> getByProId(String day) async {
try {
var snapshot = await FirebaseFirestore.instance
.collection('services')
// .where('professional_id', isEqualTo: uid)
.where('day', isEqualTo: day.toString())
.where('status', isEqualTo: 'aprobado')
// .orderBy('Timestamp', descending: true)
.get();
List<Event> eventos = [];
for (var element in snapshot.docs) {
final event = Event.fromJson(element.data());
event.scoresModel = await ScoresModel.scoreTo(event.userId, false, false);
event.id = element.id;
eventos.add(event);
}
return eventos;
} catch (e) {
print('Error getByProId $e');
return [];
}
}
Future<List<Event>> getByProIdAll(String state1, String state2) async {
try {
var snapshot = await FirebaseFirestore.instance
.collection('services')
.where('professional_id', isEqualTo: uid)
.where('status', whereIn: [state1, state2]).get();
List<Event> eventos = [];
for (var element in snapshot.docs) {
final event = Event.fromJson(element.data());
event.scoresModel = await ScoresModel.scoreTo(event.userId, false, false);
event.id = element.id;
eventos.add(event);
}
return eventos;
} catch (e) {
print('Error getByProId $e');
return [];
}
}
Future<List<Event>> getByUserIdAll(String state1, String state2) async {
try {
var snapshot = await FirebaseFirestore.instance
.collection('services')
.where('user_id', isEqualTo: uid)
.where('status', whereIn: [state1, state2]).get();
List<Event> eventos = [];
for (var element in snapshot.docs) {
final event = Event.fromJson(element.data());
event.scoresModel = await ScoresModel.scoreTo(event.userId, false, false);
event.id = element.id;
eventos.add(event);
}
return eventos;
} catch (e) {
print('Error getByUserId $e');
return [];
}
}
class Event {
String? id;
String title;
String? description;
String day;
String range1Hour1;
String? range1Hour2;
String userId;
String professionalId;
String? ubicacion;
String? address;
double? longitud;
double? latitud;
String status;
ScoresModel? scoresModel;
int? tarifa;
bool professionalScored;
bool userScored;
Timestamp? timeStamp;
Event({
this.id,
required this.title,
this.description,
required this.day,
required this.range1Hour1,
this.range1Hour2,
required this.userId,
required this.professionalId,
this.ubicacion,
this.address,
this.longitud,
this.latitud,
this.status = 'pendiente',
this.timeStamp,
this.tarifa,
this.professionalScored = false,
this.userScored = false,
});
factory Event.fromJson(Map<String, dynamic> json) {
return Event(
id: json['id'] ?? '',
title: json['title'],
description: json['description'],
day: json['day'],
range1Hour1: json['range1Hour1'],
range1Hour2: json['range1Hour2'],
userId: json['user_id'],
professionalId: json['professional_id'],
ubicacion: json['ubicacion'] ?? '',
address: json['address'] ?? '',
longitud: json['longitude'] ?? 0,
latitud: json['latitude'] ?? 0,
status: json['status'],
timeStamp: json['Timestamp'] ?? 0,
tarifa: json['tarifas'] ?? 0,
professionalScored: json['professional_scored'],
userScored: json['user_scored'],
);
}
static Future<Event> getEventById(String uid) async {
try {
final snapshot = await FirebaseFirestore.instance
.collection('services')
.doc(uid)
.get();
final Map<String, dynamic>? data = snapshot.data();
return Event.fromJson(data!);
} catch (e) {
print('Error getting user: $e');
return Event(
title: '', day: '', range1Hour1: '', userId: '', professionalId: '');
}
}
static Future<List<Event>> getEventsAllById(String uid) async {
try {
var snapshot = await FirebaseFirestore.instance
.collection('services')
.where('professional_id', isEqualTo: uid)
.get();
List<Event> eventos = [];
for (var element in snapshot.docs) {
eventos.add(Event.fromJson(element.data()));
}
return eventos;
} catch (e) {
print('Error getByProId $e');
return [];
}
}
static Future<List<Event>> getEventsAllByIdAndStatus(String uid) async {
try {
var snapshot = await FirebaseFirestore.instance
.collection('services')
.where('professional_id', isEqualTo: uid)
.where('status', whereIn: ['aprobado', 'pendiente']).get();
List<Event> eventos = [];
for (var element in snapshot.docs) {
eventos.add(Event.fromJson(element.data()));
}
return eventos;
} catch (e) {
print('Error getByProId $e');
return [];
}
}
static Future<List<Event>> getEventsAllByIdStatus(
String uid, String state) async {
try {
var snapshot = await FirebaseFirestore.instance
.collection('services')
.where('professional_id', isEqualTo: uid)
.where('status', isEqualTo: state)
.get();
List<Event> eventos = [];
for (var element in snapshot.docs) {
eventos.add(Event.fromJson(element.data()));
}
return eventos;
} catch (e) {
print('Error getByProId $e');
return [];
}
}
}
-87
View File
@@ -1,87 +0,0 @@
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_storage/firebase_storage.dart';
import 'package:prosappco/src/authentication/authentication_repository.dart';
import 'package:prosappco/src/models/scores_model.dart';
class Professional {
final String id;
final Reference professionalRef;
final String name;
final String professionName;
final String cityName;
final String ubicacion;
final String realAddress;
final double latitude;
final double longitude;
final List<String> professionalEspecializado;
final ScoresModel scores;
final int? tarifa;
final String? token;
Professional({
required this.id,
required this.professionalRef,
required this.name,
required this.professionName,
required this.cityName,
required this.ubicacion,
required this.professionalEspecializado,
required this.scores,
required this.realAddress,
required this.latitude,
required this.longitude,
this.tarifa,
this.token,
});
String getEspecializaciones() {
return professionalEspecializado.join(',\n');
}
@override
String toString() {
return 'Professional { professionalRef: $professionalRef, name: $name, professionName: $professionName, cityName: $cityName, ubicacion: $ubicacion, realAddress: $realAddress, latitude: $latitude, longitude: $longitude, professionalEspecializado: ${getEspecializaciones()} tarifa: $tarifa token: $token, }';
}
static Future<Professional?> getProfessional(String uid) async {
var photo = '...';
final FirebaseStorage storage = FirebaseStorage.instance;
try {
DocumentSnapshot user =
await FirebaseFirestore.instance.collection('users').doc(uid).get();
Map<String, dynamic> data = user.data() as Map<String, dynamic>;
photo = await AuthenticationRepository.instance.getPhoto(user.id);
if (data['estado'] == 'activo') {
List<String> especializaciones;
especializaciones = (data['especializaciones'] as List<dynamic>)
.map((e) => e.toString())
.toList();
Professional professional = Professional(
id: user.id,
name: data['name'],
professionName: data['profesion'],
cityName: data['city'],
professionalRef: storage.ref().child(photo),
professionalEspecializado: especializaciones,
ubicacion: data['ubicacion'] ?? '',
realAddress: data['address'] ?? '',
latitude: data['latitude'] ?? 0,
longitude: data['longitude'] ?? 0,
scores: await ScoresModel.scoreFrom(uid, true, true),
tarifa: data['tarifas'] ?? 0,
token: data['token'] ?? '',
);
return professional;
}
} catch (e) {
print('Error al obtener profesionales: $e');
}
return null;
}
}
-112
View File
@@ -1,112 +0,0 @@
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_storage/firebase_storage.dart';
import 'package:prosappco/src/models/user_model.dart';
class ScoresModel {
late int total;
late double average;
final List<ScoreDetailModel> details;
ScoresModel(this.details) {
total = details.length;
average = averageScore(details);
}
@override
String toString() {
return 'ScoresModel{total: $total, average: $average, details: $details}';
}
double averageScore(List<ScoreDetailModel> details) {
if (details.isEmpty) {
return 0.0;
}
final sum = details.map((detail) => detail.score).reduce((a, b) => a + b);
return sum / details.length;
}
// Me
static Future<ScoresModel> scoreTo(
String? userReceived, bool isFromClient, bool userInfo) async {
final receivedScoresQuery = FirebaseFirestore.instance
.collection('scores')
.where('is_from_professional', isEqualTo: isFromClient)
.where('to_user', isEqualTo: userReceived);
final receivedScoresSnapshot = await receivedScoresQuery.get();
final receivedScores = await Future.wait(receivedScoresSnapshot.docs
.map((doc) async =>
await ScoreDetailModel.fromDocumentSnapshot(doc, userInfo))
.toList());
return ScoresModel(receivedScores);
}
// You
static Future<ScoresModel> scoreFrom(
String userGiven, bool isFromClient, bool userInfo) async {
final givenScoresQuery = FirebaseFirestore.instance
.collection('scores')
.where('is_from_professional', isEqualTo: isFromClient)
.where('from_user', isEqualTo: userGiven);
final givenScoresSnapshot = await givenScoresQuery.get();
final givenScores = await Future.wait(givenScoresSnapshot.docs
.map((doc) async =>
await ScoreDetailModel.fromDocumentSnapshot(doc, userInfo))
.toList());
return ScoresModel(givenScores);
}
}
class ScoreDetailModel {
final String id;
final double score;
final String fromUser;
final String toUser;
final String comment;
final bool isFromClient;
final String name;
final Reference? avatar;
ScoreDetailModel({
required this.id,
required this.score,
required this.fromUser,
required this.toUser,
required this.comment,
required this.isFromClient,
required this.name,
required this.avatar,
});
@override
String toString() {
return 'ScoreDetailModel{id: $id, score: $score, fromUser: $fromUser, toUser: $toUser, comment: $comment, isFromClient: $isFromClient, name: $name, avatar: $avatar}';
}
static Future<ScoreDetailModel> fromDocumentSnapshot(
DocumentSnapshot<Map<String, dynamic>> snapshot, bool userInfo) async {
try {
final Map<String, dynamic> data = snapshot.data()!;
final user = userInfo ? await UserModel.getUser(data['from_user']) : null;
return ScoreDetailModel(
id: snapshot.id,
score: double.parse(data['score'].toString()),
fromUser: data['from_user'],
toUser: data['to_user'],
comment: data['comment'],
isFromClient: data['is_from_professional'],
name: user?.name ?? "...",
avatar: user?.photo);
} catch (e) {
print('error en score $e');
rethrow;
}
}
}
-110
View File
@@ -1,110 +0,0 @@
import 'package:cloud_firestore/cloud_firestore.dart';
class SettingModel {
final bool domicilios;
final bool google;
final bool tarifas;
final String titulo;
final String parrafo;
final String numero;
final String email;
final String dias;
final String horas;
final String version;
final String proliticasPrivacidad;
final String terminosCondiciones;
SettingModel(
this.domicilios,
this.google,
this.tarifas,
this.titulo,
this.parrafo,
this.numero,
this.email,
this.dias,
this.horas,
this.version,
this.proliticasPrivacidad,
this.terminosCondiciones,
);
static Future<SettingModel> fromJson(Map<String, dynamic>? json) async {
try {
if (json == null) {
return SettingModel(
false,
false,
false,
'',
'',
'',
'',
'',
'',
'',
'',
'',
);
}
return SettingModel(
json['domicilios'] ?? false,
json['google'] ?? false,
json['tarifas'] ?? false,
json['titulo_soporte'] ?? '',
json['parrafo_soporte'] ?? '',
json['numero_soporte'] ?? '',
json['email_soporte'] ?? '',
json['dias_soporte'] ?? '',
json['horas_soporte'] ?? '',
json['version'] ?? '', // Asegúrate de manejar nulos aquí
json['politicas_privacidad'] ?? '',
json['terminos_condiciones'] ?? '',
);
} catch (e) {
print('Error settings: $e');
return SettingModel(
false,
false,
false,
'',
'',
'',
'',
'',
'',
'',
'',
'',
);
}
}
static Future<SettingModel> getSettings() async {
try {
final DocumentSnapshot<Map<String, dynamic>> snapshot =
await FirebaseFirestore.instance
.collection('settings')
.doc('global')
.get();
final Map<String, dynamic>? data = snapshot.data();
return fromJson(data!);
} catch (e) {
print('Error getting settings: $e');
return SettingModel(
false, false, false, '', '', '', '', '', '', '', '', '');
}
}
@override
String toString() {
return 'SettingModel { domicilios: $domicilios,google: $google, tarifas: $tarifas, '
'titulo: $titulo, parrafo: $parrafo, numero: $numero, '
'email: $email, dias: $dias, horas: $horas, '
'version: $version, politicasPrivacidad: $proliticasPrivacidad, '
'terminosCondiciones: $terminosCondiciones }';
}
}
-75
View File
@@ -1,75 +0,0 @@
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_storage/firebase_storage.dart';
import 'package:prosappco/src/presentation/screens/professional.dart';
class UserModel {
final String name;
final String city;
final String? profession;
final String? state;
final Reference? photo;
final int? tarifa;
final String? phoneNumber;
final String? token;
UserModel(this.name, this.city, this.profession, this.state, this.photo,
this.tarifa, this.phoneNumber, this.token);
static Future<UserModel> fromJson(
Map<String, dynamic>? json, String uid) async {
try {
if (json == null) return UserModel('', '', '', null, null, 0, '', '');
String? avatar = json['photo'];
return UserModel(
json['name'],
json['city'],
json['profesion'],
json['estado'],
avatar != null ? storage.ref().child(avatar) : null,
json['tarifas'] ?? 0,
json['phoneNumber'],
json['token'],
);
} catch (e) {
print('$e');
return UserModel('', '', '', null, null, 0, '', '');
}
}
@override
String toString() {
return 'UserModel(name: $name, city: $city, profession: $profession, state: $state, tarifa: $tarifa, phoneNumber: $phoneNumber, token: $token)';
}
static UserModel fromFirestore(Map<String, dynamic> firestoreMap) {
try {
String? avatar = firestoreMap['photo'];
return UserModel(
firestoreMap['name'] ?? 'Sin nombre',
firestoreMap['city'] ?? 'Sin ciudad',
firestoreMap['profesion'],
firestoreMap['estado'],
avatar != null ? storage.ref().child(avatar) : null,
firestoreMap['tarifas'] ?? 0,
firestoreMap['phoneNumber'] ?? 'Sin numero',
firestoreMap['token'],
);
} catch (e) {
print('DesdeProvider $e');
return UserModel('', '', '', null, null, 0, '', '');
}
}
static Future<UserModel> getUser(String uid) async {
try {
final snapshot =
await FirebaseFirestore.instance.collection('users').doc(uid).get();
final Map<String, dynamic>? data = snapshot.data();
return fromJson(data, uid);
} catch (e) {
print('Error getting user: $e');
return UserModel('', '', '', null, null, 0, '', '');
}
}
}
-101
View File
@@ -1,101 +0,0 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:prosappco/src/components/pop_appbar.dart';
import 'package:prosappco/src/models/setting_model.dart';
import 'package:prosappco/src/presentation/screens/web_view.dart';
import 'package:url_launcher/url_launcher.dart';
class AboutScreen extends StatefulWidget {
const AboutScreen({super.key});
@override
State<AboutScreen> createState() => _AboutScreenState();
}
class _AboutScreenState extends State<AboutScreen> {
SettingModel? settings;
@override
void initState() {
super.initState();
if (settings == null) {
SettingModel.getSettings().then(
(SettingModel value) => setState(() {
settings = value;
}),
);
}
}
void _launchURL(String url) async {
if (await canLaunch(url)) {
await launch(url, forceSafariVC: false, forceWebView: false);
} else {
throw 'No se pudo abrir el enlace $url';
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: PopAppbar(
onPressed: () {
Navigator.pop(context);
},
label: 'Acerca de la aplicación'),
body: ListView(
children: [
ListTile(
onTap: () {
if (kIsWeb) {
_launchURL(settings?.proliticasPrivacidad ?? '');
} else {
Navigator.push(
context,
CupertinoPageRoute(
builder: (BuildContext context) {
return WebViewScreen(
label: 'Políticas de privacidad',
link: settings?.proliticasPrivacidad ?? '',
);
},
),
);
}
},
title: const Text('Políticas de privacidad'),
trailing:
const Icon(Icons.keyboard_arrow_right, color: Colors.black),
),
ListTile(
onTap: () {
if (kIsWeb) {
_launchURL(settings?.terminosCondiciones ?? '');
} else {
Navigator.push(
context,
CupertinoPageRoute(
builder: (BuildContext context) {
return WebViewScreen(
label: 'Términos y condiciones',
link: settings?.terminosCondiciones ?? '',
);
},
),
);
}
},
title: const Text('Términos y condiciones'),
trailing:
const Icon(Icons.keyboard_arrow_right, color: Colors.black),
),
ListTile(
title: const Text('Versión de la aplicación'),
subtitle: Text(settings?.version ?? ''),
),
],
),
);
}
}
-496
View File
@@ -1,496 +0,0 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_rating_bar/flutter_rating_bar.dart';
import 'package:prosappco/src/authentication/authentication_repository.dart';
import 'package:prosappco/src/components/pop_appbar.dart';
import 'package:prosappco/src/components/primary_btn.dart';
import 'package:prosappco/src/models/event_model.dart';
import 'package:prosappco/src/presentation/screens/cita.dart';
import 'package:prosappco/src/presentation/widgets/shared/loading_item_list.dart';
import 'package:table_calendar/table_calendar.dart';
import 'package:intl/intl.dart';
class CalendarScreen extends StatefulWidget {
const CalendarScreen({super.key});
@override
State<CalendarScreen> createState() => _CalendarScreenState();
}
class _CalendarScreenState extends State<CalendarScreen> {
final uid = AuthenticationRepository.instance.getCurrentUserUid();
List<Event>? _events;
final _titleController = TextEditingController();
final _descriptionController = TextEditingController();
EventoService eventoService = EventoService();
CalendarFormat _calendarFormat = CalendarFormat.month;
DateTime today = DateTime.now();
DateTime now = DateTime.now();
TimeOfDay? _selectedTime1;
TimeOfDay? _selectedTime2;
Future<TimeOfDay?> _selectTime1(BuildContext context) async {
final TimeOfDay? pickedTime1 = await showTimePicker(
context: context,
initialTime: TimeOfDay.now(),
);
if (pickedTime1 != null) {
setState(() {
_selectedTime1 = pickedTime1;
});
}
return pickedTime1;
}
Future<TimeOfDay?> _selectTime2(BuildContext context) async {
final TimeOfDay? pickedTime2 = await showTimePicker(
context: context,
initialTime: TimeOfDay.now(),
);
if (pickedTime2 != null) {
setState(() {
_selectedTime2 = pickedTime2;
});
}
return pickedTime2;
}
@override
void initState() {
super.initState();
today = DateTime.utc(today.year, today.month, today.day);
if (_events == null) {
Event.getEventsAllByIdStatus(uid ?? "", 'aprobado')
.then((value) => setState(() {
_events = value;
}));
}
}
void _onDaySelected(DateTime day, DateTime focusedDay) {
setState(() {
today = day;
});
}
void _onFormatChange(CalendarFormat format) {
setState(() {
_calendarFormat = format;
});
}
@override
Widget build(BuildContext context) {
if (_events == null) {
return const Scaffold(
body: Center(
child: CircularProgressIndicator(),
),
);
}
var events = _events!;
// DateTime firstDay = today.subtract(Duration(days: 365));
DateTime lastDay = today.add(const Duration(days: 365));
return Scaffold(
floatingActionButtonLocation: kIsWeb
? FloatingActionButtonLocation.startFloat
: FloatingActionButtonLocation.endFloat,
resizeToAvoidBottomInset: false,
appBar: PopAppbar(
onPressed: () {
Navigator.pop(context);
},
label: 'Calendario',
),
body: Column(
children: [
Container(
color: const Color.fromARGB(255, 224, 247, 255),
child: TableCalendar(
locale: 'es_MX',
firstDay: DateTime.utc(2010, 10, 16),
lastDay: lastDay,
focusedDay: today,
availableGestures: AvailableGestures.all,
onDaySelected: _onDaySelected,
selectedDayPredicate: (day) => isSameDay(day, today),
calendarFormat: _calendarFormat,
onFormatChanged: _onFormatChange,
eventLoader: (date) {
return events
.where((element) {
DateTime day = DateTime.parse(element.day);
return (date.year == day.year &&
date.month == day.month &&
date.day == day.day);
})
.map((e) => e.description)
.toList();
},
availableCalendarFormats: const {
CalendarFormat.month: 'Mes',
CalendarFormat.week: 'Semana',
CalendarFormat.twoWeeks: '2 Semanas',
},
),
),
SizedBox(
width: double.infinity,
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 15, vertical: 8),
child: Text(DateFormat('dd MMMM yyyy', 'es').format(today),
style: const TextStyle(
color: Colors.black,
fontSize: 16,
fontWeight: FontWeight.w600,
)),
),
),
const Divider(
height: 0,
),
Expanded(child: SingleChildScrollView(child: _eventList()))
],
),
floatingActionButton: FloatingActionButton(
onPressed: _showDialog,
child: const Icon(Icons.add),
),
);
}
void _showDialog() {
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(15),
),
content: StatefulBuilder(
builder: (BuildContext context, StateSetter setStateDialog) {
return SizedBox(
height: 800,
child: Column(
children: [
Padding(
padding: const EdgeInsets.symmetric(
horizontal: 0, vertical: 15),
child: Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
Text(
_selectedTime1 == null
? ''
: _selectedTime1!.format(context),
style: TextStyle(
color: Colors.grey[500],
fontSize: 12,
),
),
_selectedTime2 != null && _selectedTime1 != null
? Text(
' - ',
style: TextStyle(
color: Colors.grey[500],
fontSize: 12,
),
)
: const SizedBox(),
Text(
_selectedTime2 == null
? ''
: _selectedTime2!.format(context),
style: TextStyle(
color: Colors.grey[500],
fontSize: 12,
),
),
Text(
' | ',
style: TextStyle(
color: Colors.grey[500],
),
),
Text(
DateFormat('dd MMMM yyyy', 'es').format(today),
style: const TextStyle(
fontSize: 13,
),
)
],
),
),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 10),
child: TextFormField(
controller: _titleController,
decoration: const InputDecoration(hintText: 'Titulo'),
),
),
Padding(
padding: const EdgeInsets.symmetric(
horizontal: 10, vertical: 20),
child: TextFormField(
controller: _descriptionController,
decoration:
const InputDecoration(hintText: 'Descripción'),
),
),
Padding(
padding: const EdgeInsets.only(bottom: 40),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
SizedBox(
width: 80,
child: TextFormField(
textAlign: TextAlign.center,
onTap: () async {
var value = await _selectTime1(context);
setStateDialog(() {
_selectedTime1 = value;
});
},
readOnly: true,
decoration: const InputDecoration(
hintText: 'Hora',
),
controller: TextEditingController(
text: _selectedTime1 == null
? ''
: ' ${_selectedTime1!.format(context)}'),
style: const TextStyle(fontSize: 15),
),
),
const Text(' - '),
SizedBox(
width: 80,
child: TextFormField(
textAlign: TextAlign.center,
onTap: () async {
var value = await _selectTime2(context);
setStateDialog(() {
_selectedTime2 = value;
});
},
readOnly: true,
decoration: const InputDecoration(
hintText: 'Hora',
),
controller: TextEditingController(
text: _selectedTime2 == null
? ''
: ' ${_selectedTime2!.format(context)}'),
style: const TextStyle(fontSize: 15),
),
),
],
),
),
PrimaryButtom(
onPressed: () async {
final DateTime combinedDate1 = DateTime(
today.year,
today.month,
today.day,
_selectedTime1!.hour,
_selectedTime1!.minute,
);
final DateTime combinedDate2 = DateTime(
today.year,
today.month,
today.day,
_selectedTime2!.hour,
_selectedTime2!.minute,
);
await eventoService
.createEvent(
_titleController.text,
_descriptionController.text,
DateFormat('yyyy-MM-dd HH:mm:ss.SSS').format(today),
'$combinedDate1',
'$combinedDate2',
uid.toString(),
'sitio',
'',
0,
0,
'aprobado',
0,
false,
false,
)
.then((value) {
Navigator.pop(context);
_titleController.text = '';
_descriptionController.text = '';
});
Event.getEventsAllByIdStatus(uid ?? "", 'aprobado')
.then(
(value) => setState(() {
_events = value;
}),
);
},
label: 'Añadir evento',
),
],
),
);
},
),
);
},
);
}
Widget _eventList() {
return FutureBuilder(
future: getByProId(DateFormat("yyyy-MM-dd 00:00:00.000").format(today)),
builder: (BuildContext context, AsyncSnapshot<List<Event>> snapshot) {
List<Event> eventos = [];
if (snapshot.connectionState == ConnectionState.waiting) {
return const Column(
children: [
LoadingItemList(useCircleAvatar: false),
LoadingItemList(useCircleAvatar: false),
LoadingItemList(useCircleAvatar: false),
LoadingItemList(useCircleAvatar: false),
LoadingItemList(useCircleAvatar: false),
],
);
}
try {
snapshot.data!.sort((a, b) {
String? range1Hour1A = a.range1Hour1;
String? range1Hour1B = b.range1Hour1;
DateTime dateTimeA = DateTime.parse(range1Hour1A);
DateTime dateTimeB = DateTime.parse(range1Hour1B);
return dateTimeB.compareTo(dateTimeA);
});
eventos.addAll(snapshot.data!);
} catch (e) {
print("Error al cargar eventos: inflar $e");
}
if (eventos.isEmpty) {
return const Padding(
padding: EdgeInsets.only(top: 30),
child: Center(
child: Text(
'No tienes citas',
style: TextStyle(
color: Colors.black,
fontSize:
18, // Tamaño de fuente ajustado según tus preferencias
fontWeight:
FontWeight.w500, // Puedes ajustar el peso de la fuente
fontStyle: FontStyle.italic, // Puedes agregar estilo italic
// Otros estilos según tus preferencias
),
),
),
);
}
return Column(
children: [
...eventos.map(
(e) => ListTile(
onTap: () {
Navigator.push(
context,
CupertinoPageRoute(
builder: (BuildContext context) {
return CitaScreen(evento: e);
},
),
);
},
leading: Text(
TimeOfDay.fromDateTime(DateTime.parse(e.range1Hour1))
.format(context)),
title: RichText(
text: TextSpan(
children: [
TextSpan(
text: '${e.title}, ',
style: const TextStyle(
color: Colors.black,
fontWeight: FontWeight.bold,
fontSize: 16,
),
),
TextSpan(
text: DateFormat('dd MMM', 'es')
.format(DateTime.parse(e.day)),
style: const TextStyle(
color: Colors.grey,
fontSize: 16,
),
),
],
),
),
subtitle: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
e.professionalId == e.userId
? const SizedBox()
: RatingBar.builder(
initialRating: e.scoresModel?.average ?? 0,
minRating: 1,
direction: Axis.horizontal,
allowHalfRating: true,
itemCount: 5,
itemSize: 25,
maxRating: 5,
itemPadding:
const EdgeInsets.symmetric(horizontal: 0),
itemBuilder: (context, _) => const Icon(
Icons.star,
color: Color(0xFF2BA4EC),
),
onRatingUpdate: (rating) {},
ignoreGestures: true,
),
const SizedBox(width: 5),
e.professionalId == e.userId
? const SizedBox()
: Text(
'(${e.scoresModel?.total.toString()}) ${e.scoresModel?.average.toStringAsFixed(1)}'),
],
),
Text(
'" ${e.description} "',
style: const TextStyle(fontStyle: FontStyle.italic),
),
],
),
trailing: const Icon(Icons.keyboard_arrow_right),
),
),
],
);
},
);
}
}
@@ -1,315 +0,0 @@
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:prosappco/src/authentication/authentication_repository.dart';
import 'package:prosappco/src/components/pop_appbar.dart';
import 'package:prosappco/src/components/schedule_picker.dart';
import 'package:prosappco/src/models/event_model.dart';
import 'package:prosappco/src/models/professional_model.dart';
import 'package:prosappco/src/presentation/widgets/shared/warning_snackbar.dart';
import 'package:prosappco/src/utils/time_of_day_utils.dart';
import 'package:table_calendar/table_calendar.dart';
import 'package:intl/intl.dart';
class CalendarProScreen extends StatefulWidget {
final Professional professional;
const CalendarProScreen({super.key, required this.professional});
@override
State<CalendarProScreen> createState() => _CalendarProScreenState();
}
class _CalendarProScreenState extends State<CalendarProScreen> {
final uid = AuthenticationRepository.instance.getCurrentUserUid();
EventoService eventoService = EventoService();
CalendarFormat _calendarFormat = CalendarFormat.month;
DateTime today = DateTime.now();
DateTime now = DateTime.now();
late int numDay;
List<Event>? _events;
Map<String, Schedule>? _horarios;
@override
void initState() {
super.initState();
if (_horarios == null) {
Schedule.getHorarios(widget.professional.id.toString()).then(
(Map<String, Schedule> data) {
setState(() {
_horarios = data;
});
},
);
}
if (_events == null) {
Event.getEventsAllByIdAndStatus(widget.professional.id.toString())
.then((value) {
setState(() {
_events = value;
});
});
}
today = DateTime.utc(today.year, today.month, today.day);
numDay = today.weekday;
}
void _onDaySelected(DateTime day, DateTime focusedDay) {
setState(() {
today = day;
numDay = today.weekday;
});
}
void _onFormatChange(CalendarFormat format) {
setState(() {
_calendarFormat = format;
});
}
@override
Widget build(BuildContext context) {
if (_events == null) {
return const Scaffold(
body: Center(
child: CircularProgressIndicator(),
),
);
}
DateTime lastDay = today.add(const Duration(days: 365));
return Scaffold(
floatingActionButtonLocation: kIsWeb
? FloatingActionButtonLocation.startFloat
: FloatingActionButtonLocation.endFloat,
resizeToAvoidBottomInset: false,
appBar: PopAppbar(
onPressed: () {
Navigator.pop(context);
},
label: 'Calendario',
),
body: Column(
children: [
Container(
color: const Color.fromARGB(255, 224, 247, 255),
child: TableCalendar(
locale: 'es_MX',
firstDay: DateTime.now(),
lastDay: lastDay,
focusedDay: today,
availableGestures: AvailableGestures.all,
onDaySelected: _onDaySelected,
selectedDayPredicate: (day) => isSameDay(day, today),
calendarFormat: _calendarFormat,
onFormatChanged: _onFormatChange,
availableCalendarFormats: const {
CalendarFormat.month: 'Mes',
CalendarFormat.week: 'Semana',
CalendarFormat.twoWeeks: '2 Semanas',
},
),
),
SizedBox(
width: double.infinity,
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 15, vertical: 8),
child: Text(DateFormat('dd MMMM yyyy', 'es').format(today),
style: const TextStyle(
color: Colors.black,
fontSize: 16,
fontWeight: FontWeight.w600,
)),
),
),
const Divider(
height: 0,
),
Expanded(
child: SingleChildScrollView(
padding: const EdgeInsets.only(bottom: 15),
child: Column(
children: [...rangesItems(_horarios?[numDay.toString()])],
),
))
],
),
);
}
List<Widget> rangesItems(Schedule? schedule) {
if (schedule == null) {
return const [
Padding(
padding: EdgeInsets.only(top: 20, left: 30, right: 30),
child: Text(
'El profesional no acepta turnos este día',
style: TextStyle(fontSize: 15, fontWeight: FontWeight.w500),
textAlign: TextAlign.center,
),
),
];
}
if (!schedule.habilitado) {
return const [
Padding(
padding: EdgeInsets.only(top: 20, left: 30, right: 30),
child: Text(
'El profesional no acepta turnos este día',
style: TextStyle(fontSize: 15, fontWeight: FontWeight.w500),
textAlign: TextAlign.center,
),
)
];
}
if (schedule.jornadaContinua) {
List<TimeOfDay> ranges = TimeOfDayUtils.genRanges(
schedule.range1Hour1!,
schedule.range2Hour2!,
);
return rangesItemList(ranges, _events);
} else {
List<TimeOfDay> ranges1 = TimeOfDayUtils.genRanges(
schedule.range1Hour1!,
schedule.range1Hour2!,
);
List<TimeOfDay> ranges2 = TimeOfDayUtils.genRanges(
schedule.range2Hour1!,
schedule.range2Hour2!,
);
return [
...rangesItemList(ranges1, _events),
...rangesItemList(ranges2, _events),
];
}
}
List<Widget> rangesItemList(List<TimeOfDay> ranges, List<Event>? events) {
return ranges.map((time) {
if (_isHora1Ocupada(time, events)) {
return Card(
elevation: 4,
margin: const EdgeInsets.symmetric(vertical: 5, horizontal: 10),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10),
),
child: ListTile(
onTap: () {
WarningSnackbar.show(
title: 'Ocupado',
message: 'Este horário ya se encuentra ocupado',
);
},
contentPadding: const EdgeInsets.all(16),
leading: Container(
width: 40,
height: 40,
decoration: const BoxDecoration(
gradient: LinearGradient(
colors: [Colors.yellow, Colors.red, Colors.red],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
shape: BoxShape.circle,
),
child: const Center(
child: Icon(
Icons.access_time,
color: Colors.white,
),
),
),
title: Text(
time.format(context),
style: const TextStyle(fontSize: 15, fontWeight: FontWeight.bold),
),
subtitle: const Text(
'Ocupado',
style: TextStyle(
color: Colors.red, fontSize: 13, fontWeight: FontWeight.bold),
),
trailing: const Icon(
Icons.arrow_forward_ios,
color: Colors.grey,
),
),
);
} else {
return Card(
elevation: 4,
margin: const EdgeInsets.symmetric(vertical: 5, horizontal: 10),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10),
),
child: ListTile(
onTap: () {
Navigator.pop(context, [today, time, widget.professional]);
},
contentPadding: const EdgeInsets.all(16),
leading: Container(
width: 40,
height: 40,
decoration: const BoxDecoration(
gradient: LinearGradient(
colors: [Colors.blue, Colors.green],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
shape: BoxShape.circle,
),
child: const Center(
child: Icon(
Icons.access_time,
color: Colors.white,
),
),
),
title: Text(
time.format(context),
style: const TextStyle(fontSize: 15, fontWeight: FontWeight.bold),
),
subtitle: const Text(
'Disponible',
style: TextStyle(
color: Colors.green,
fontSize: 13,
fontWeight: FontWeight.bold),
),
trailing: const Icon(
Icons.arrow_forward_ios,
color: Colors.grey,
),
),
);
}
}).toList();
}
bool _isHora1Ocupada(TimeOfDay hora1, List<Event>? events) {
if (events != null) {
for (Event event in events) {
DateTime time1 = DateTime(
today.year,
today.month,
today.day,
hora1.hour,
hora1.minute,
);
if (event.range1Hour1 == time1.toString()) {
return true;
}
}
}
return false;
}
}
-396
View File
@@ -1,396 +0,0 @@
import 'dart:convert';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
import 'package:prosappco/src/authentication/authentication_repository.dart';
import 'package:prosappco/src/components/photo_view.dart';
import 'package:prosappco/src/components/pop_appbar.dart';
import 'package:prosappco/src/models/chat_model.dart';
import 'package:prosappco/src/models/event_model.dart';
import 'package:prosappco/src/models/professional_model.dart';
import 'package:prosappco/src/models/user_model.dart';
import 'package:prosappco/src/presentation/screens/professional_info.dart';
import 'package:http/http.dart' as http;
class ChatScreen extends StatefulWidget {
final String? eventoId;
const ChatScreen({super.key, this.eventoId});
@override
State<ChatScreen> createState() => _ChatScreenState();
}
class _ChatScreenState extends State<ChatScreen> {
final _textController = TextEditingController();
final uid = AuthenticationRepository.instance.getCurrentUserUid();
UserModel? user;
Professional? professional;
bool pro = false;
Future<void> sendPushNotification(String token) async {
try {
http.Response response = await http.post(
Uri.parse('https://fcm.googleapis.com/fcm/send'),
headers: <String, String>{
'Content-Type': 'application/json; charset=UTF-8',
'Authorization':
'key=AAAAORdR-xU:APA91bF_wblg86jHAC-uexrXPHavYRlk5wge1Gf46m56V4J2D2L37Cp_hf46JZUzpvsWPSpqc5ewHelKI9LTifUG_s2mciMI6e5VLKo7E1R8btbNo7iaM9do2ctoyHKUm1atlZBdKaN2',
},
body: jsonEncode(
<String, dynamic>{
'notification': <String, dynamic>{
'body': 'Tienes un nuevo mensaje',
'title': 'Nuevo mensaje',
},
'priority': 'high',
'data': <String, dynamic>{
'click_action': 'FLUTTER_NOTIFICATION_CLICK',
'id': '1',
'status': 'done'
},
'to': token,
},
),
);
response;
} catch (e) {
print('error al enviar notificacion $e');
}
}
@override
void initState() {
super.initState();
Event.getEventById(widget.eventoId!).then((event) {
if (user == null) {
if (uid != event.userId) {
UserModel.getUser(event.userId).then(
(UserModel s) => setState(() => user = s),
);
} else {
Professional.getProfessional(event.professionalId)
.then((value) => {professional = value});
UserModel.getUser(event.professionalId).then(
(UserModel s) => setState(() => {user = s, pro = true}),
);
}
}
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: PopAppbar(
onPressed: () {
Navigator.pop(context);
},
label: 'Chat'),
body: Column(
children: [
Container(
decoration: BoxDecoration(
boxShadow: [
BoxShadow(
color: Colors.grey.withOpacity(0.3),
spreadRadius: 2,
blurRadius: 3,
offset: const Offset(0, 2),
),
],
),
child: Container(
padding: const EdgeInsets.symmetric(vertical: 8),
color: const Color(0xFFD6F4FF),
alignment: Alignment.topCenter,
child: ListTile(
leading: GestureDetector(
onTap: () {
if (pro) {
Navigator.of(context).push(
CupertinoPageRoute(
builder: (BuildContext context) {
return ProfessionalInfoScreen(
professional: professional!,
);
},
),
);
}
},
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 10),
child: ReferencePhoto(
ref: user?.photo,
size: 55,
sizeCircle: 60,
),
),
),
title: Text(
'${user?.name}',
style: const TextStyle(
color: Colors.black, fontWeight: FontWeight.w600),
),
subtitle: Text(user?.profession ?? ''),
trailing: const Icon(Icons.keyboard_arrow_right),
),
),
),
Expanded(
child: SingleChildScrollView(
padding: const EdgeInsets.only(top: 10),
reverse: true,
child: streamB(uid!),
),
),
Container(
alignment: Alignment.bottomCenter,
width: MediaQuery.of(context).size.width,
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 18),
width: MediaQuery.of(context).size.width,
child: Row(
children: [
Expanded(
child: TextFormField(
controller: _textController,
style: const TextStyle(color: Colors.black),
decoration: InputDecoration(
hintText: 'Mensaje',
hintStyle:
TextStyle(color: Colors.grey[600], fontSize: 16),
border: OutlineInputBorder(
borderSide: const BorderSide(
color: Colors.grey, width: 1.0),
borderRadius: BorderRadius.circular(50)),
focusedBorder: OutlineInputBorder(
borderSide: const BorderSide(
color: Colors.grey, width: 1.0),
borderRadius: BorderRadius.circular(50)),
contentPadding: const EdgeInsets.symmetric(
horizontal: 20, vertical: 15),
filled: true,
fillColor: Colors.grey[200],
),
onFieldSubmitted: (value) async {
String muestra = _textController.text.trim();
if (muestra.isNotEmpty) {
final nuevoMensaje = MessageModel(
user: uid!,
content:
_textController.text.trimLeft().trimRight(),
timestamp: DateTime.now());
final nuevoMensajeMap = {
'user': nuevoMensaje.user,
'content': nuevoMensaje.content,
'timestamp': nuevoMensaje.timestamp,
};
FirebaseFirestore.instance
.collection('chats')
.doc(widget.eventoId)
.update({
'message': FieldValue.arrayUnion([nuevoMensajeMap])
});
if (user?.token != '') {
sendPushNotification(user!.token!);
}
// if (user?.token != '') {
// final mensajesQuerySnapshot =
// await FirebaseFirestore.instance
// .collection('chats')
// .doc(widget.eventoId)
// .get();
// final mensajes =
// mensajesQuerySnapshot.data()?['message'];
// if (mensajes != null && mensajes.isNotEmpty) {
// final ultimoMensaje = mensajes.last;
// final ultimoMensajeUser = ultimoMensaje['user'];
// if (ultimoMensajeUser == uid) {
// // El último mensaje fue enviado por ti, no se envía la notificación
// } else {
// sendPushNotification(user!.token!);
// }
// } else {
// sendPushNotification(user!.token!);
// }
// }
_textController.clear();
}
},
),
),
const SizedBox(width: 12),
GestureDetector(
onTap: () async {
String muestra = _textController.text.trim();
if (muestra.isNotEmpty) {
final nuevoMensaje = MessageModel(
user: uid!,
content:
_textController.text.trimLeft().trimRight(),
timestamp: DateTime.now());
final nuevoMensajeMap = {
'user': nuevoMensaje.user,
'content': nuevoMensaje.content,
'timestamp': nuevoMensaje.timestamp,
};
FirebaseFirestore.instance
.collection('chats')
.doc(widget.eventoId)
.update({
'message': FieldValue.arrayUnion([nuevoMensajeMap])
});
if (user?.token != '') {
final mensajesQuerySnapshot = await FirebaseFirestore
.instance
.collection('chats')
.doc(widget.eventoId)
.get();
final mensajes =
mensajesQuerySnapshot.data()?['message'];
if (mensajes != null && mensajes.isNotEmpty) {
final ultimoMensaje = mensajes.last;
final ultimoMensajeUser = ultimoMensaje['user'];
if (ultimoMensajeUser == uid) {
} else {
sendPushNotification(user!.token!);
}
} else {
sendPushNotification(user!.token!);
}
}
_textController.clear();
}
},
child: Container(
height: 50,
width: 50,
decoration: BoxDecoration(
color: Theme.of(context).primaryColor,
borderRadius: BorderRadius.circular(30),
),
child: const Center(
child: Icon(
Icons.send,
color: Colors.white,
),
),
),
)
],
),
),
),
],
),
);
}
StreamBuilder<DocumentSnapshot<Map<String, dynamic>>> streamB(String uid) {
return StreamBuilder(
stream: FirebaseFirestore.instance
.collection('chats')
.doc(widget.eventoId)
.snapshots(),
builder: (context, snapshot) {
if (!snapshot.hasData) {
return const Center(child: CircularProgressIndicator());
}
final data = snapshot.data!;
final chat = ChatModel.fromDocumentSnapshot(data);
return Column(
children: [
...chat.messages.map(
(e) => uid != e.user
? ListTile(
title: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
margin: const EdgeInsets.only(right: 60),
padding: const EdgeInsets.symmetric(
vertical: 10, horizontal: 16),
decoration: BoxDecoration(
color: Colors.grey.shade200,
borderRadius: const BorderRadius.only(
topRight: Radius.circular(20),
bottomLeft: Radius.circular(20),
bottomRight: Radius.circular(20),
),
),
child: Text(
e.content,
style: const TextStyle(fontSize: 16),
),
),
const SizedBox(width: 5),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 10),
child: Text(
DateFormat('h:mm a').format(e.timestamp),
style: const TextStyle(
color: Colors.grey, fontSize: 12),
),
),
],
),
)
: ListTile(
title: Column(
mainAxisAlignment: MainAxisAlignment.end,
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Container(
margin: const EdgeInsets.only(left: 60),
padding: const EdgeInsets.symmetric(
vertical: 10,
horizontal: 16,
),
decoration: const BoxDecoration(
color: Color(0xFFD5EFFF),
borderRadius: BorderRadius.only(
topLeft: Radius.circular(20),
bottomLeft: Radius.circular(20),
bottomRight: Radius.circular(20),
),
),
child: Text(
e.content,
style: const TextStyle(fontSize: 16),
),
),
const SizedBox(width: 5),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 10),
child: Text(
DateFormat('h:mm a').format(e.timestamp),
style: const TextStyle(
color: Colors.grey,
fontSize: 12,
),
),
),
],
),
),
)
],
);
},
);
}
}
-799
View File
@@ -1,799 +0,0 @@
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:firebase_storage/firebase_storage.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_rating_bar/flutter_rating_bar.dart';
import 'package:get/get.dart';
import 'package:intl/intl.dart';
import 'package:prosappco/src/authentication/authentication_repository.dart';
import 'package:prosappco/src/components/photo_view.dart';
import 'package:prosappco/src/components/pop_appbar.dart';
import 'package:prosappco/src/models/event_model.dart';
import 'package:prosappco/src/models/scores_model.dart';
import 'package:prosappco/src/models/setting_model.dart';
import 'package:prosappco/src/models/user_model.dart';
import 'package:prosappco/src/presentation/screens/chat.dart';
import 'package:prosappco/src/presentation/screens/score.dart';
import 'package:url_launcher/url_launcher.dart';
import 'package:community_material_icon/community_material_icon.dart';
import 'package:http/http.dart' as http;
import 'dart:convert';
class CitaScreen extends StatefulWidget {
final Event evento;
const CitaScreen({super.key, required this.evento});
@override
State<CitaScreen> createState() => _CitaScreenState();
}
class _CitaScreenState extends State<CitaScreen> {
final uid = AuthenticationRepository.instance.getCurrentUserUid();
UserModel? user;
DateTime today = DateTime.now();
String nombre = '';
String userToken = '';
String numberPhone = '';
int tarifa = 0;
Reference? ref_photo;
ScoresModel? scoresModel;
bool? ver = true;
bool? pro;
String proName = '';
late final FirebaseAuth _auth;
String formatCurrency(int number) {
final formatter =
NumberFormat.currency(locale: 'es_CO', decimalDigits: 0, symbol: '');
return '\$${formatter.format(number)}';
}
Future<void> sendPushNotification(
String user, String accion, String proName) async {
try {
http.Response response = await http.post(
Uri.parse('https://fcm.googleapis.com/fcm/send'),
headers: <String, String>{
'Content-Type': 'application/json; charset=UTF-8',
'Authorization':
'key=AAAAORdR-xU:APA91bF_wblg86jHAC-uexrXPHavYRlk5wge1Gf46m56V4J2D2L37Cp_hf46JZUzpvsWPSpqc5ewHelKI9LTifUG_s2mciMI6e5VLKo7E1R8btbNo7iaM9do2ctoyHKUm1atlZBdKaN2',
},
body: jsonEncode(
<String, dynamic>{
'notification': <String, dynamic>{
'body': accion == 'rechazo'
? '$proName a rechazado tu solicitud de servicio'
: '$proName a aprobado tu solicitud de servicio',
'title': '$proName $accion',
},
'priority': 'high',
'data': <String, dynamic>{
'click_action': 'FLUTTER_NOTIFICATION_CLICK',
'id': '1',
'status': 'done',
'screen': 'misservicios',
},
'to': user
},
),
);
response;
} catch (e) {
print('error al enviar notificacion $e');
}
}
Future<void> _openMap(double lat, double lng) async {
final Uri _url =
Uri.parse('https://www.google.com/maps/search/?api=1&query=$lat,$lng');
if (!await launchUrl(_url)) {
throw Exception('Could not launch $_url');
}
}
Future<void> _sendWhatsapp(String phoneNumber) async {
final whatsappUrl =
'https://wa.me/$phoneNumber?text=${Uri.parse('Hola! me contactaste por Prossapp')}';
if (!await launch(whatsappUrl)) {
throw Exception('Could not launch $whatsappUrl');
}
}
SettingModel? settings;
@override
void initState() {
super.initState();
final uid = AuthenticationRepository.instance.getCurrentUserUid();
_auth = FirebaseAuth.instance;
final currentUser = _auth.currentUser;
if (currentUser != null && currentUser.displayName != null) {
proName = currentUser.displayName!;
}
if (settings == null) {
SettingModel.getSettings().then(
(SettingModel value) => setState(() {
settings = value;
}),
);
}
if (scoresModel == null) {
if (uid != widget.evento.userId) {
ScoresModel.scoreTo(widget.evento.userId, false, false).then(
(ScoresModel s) => setState(() {
scoresModel = s;
pro = true;
}),
);
} else {
ScoresModel.scoreTo(widget.evento.professionalId, true, false).then(
(ScoresModel s) => setState(() {
scoresModel = s;
pro = false;
}),
);
}
}
}
@override
Widget build(BuildContext context) {
today.difference(DateTime.parse(widget.evento.range1Hour1));
final eventDate = DateFormat('yyyy-MM-dd').parse(widget.evento.day);
if (nombre == '') {
if (uid != widget.evento.userId) {
UserModel.getUser(widget.evento.userId).then((value) {
UserModel.getUser(uid.toString()).then((me) {
setState(() {
nombre = value.name;
ref_photo = value.photo;
userToken = value.token ?? '';
numberPhone = value.phoneNumber ?? '';
});
});
});
} else {
UserModel.getUser(widget.evento.professionalId).then((value) {
setState(() {
nombre = value.name;
ref_photo = value.photo;
numberPhone = value.phoneNumber ?? '';
});
});
}
}
return Scaffold(
appBar: PopAppbar(
onPressed: () {
Navigator.pop(context);
},
label: 'Servicio'),
body: Column(
children: [
Expanded(
child: Column(
children: [
ListTile(
leading: ReferencePhoto(
ref: ref_photo,
size: 50,
sizeCircle: 50,
sizeIcon: 35,
),
title: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
nombre,
style: const TextStyle(
color: Colors.black,
fontWeight: FontWeight.bold,
fontSize: 16,
),
),
Text(
'${DateFormat('dd MMMM', 'es').format(DateTime.parse(widget.evento.day))} ${DateFormat('h:mm a').format(DateTime.parse(widget.evento.range1Hour1))}',
style: const TextStyle(
color: Colors.grey,
fontSize: 16,
),
)
],
),
subtitle: Row(
children: [
RatingBar.builder(
initialRating: scoresModel?.average ?? 0,
minRating: 1,
direction: Axis.horizontal,
allowHalfRating: true,
itemCount: 5,
itemSize: 25,
maxRating: 5,
itemPadding: const EdgeInsets.symmetric(horizontal: 0),
itemBuilder: (context, _) => const Icon(
Icons.star,
color: Color(0xFF2BA4EC),
),
onRatingUpdate: (rating) {},
ignoreGestures: true,
),
const SizedBox(width: 5),
Text(
'(${scoresModel?.total.toString()}) ${scoresModel?.average.toStringAsFixed(1)}'),
],
),
),
widget.evento.userId == widget.evento.professionalId
? const SizedBox()
: Container(
margin: const EdgeInsets.only(
left: 40, right: 40, top: 20, bottom: 20),
padding: const EdgeInsets.symmetric(
horizontal: 20, vertical: 15),
decoration: BoxDecoration(
color: const Color(0xFFD6F4FF),
borderRadius: BorderRadius.circular(20),
boxShadow: [
BoxShadow(
color: Colors.grey.withOpacity(0.5),
spreadRadius: 1,
blurRadius: 5,
offset: const Offset(1, 3),
),
],
),
child: Row(
children: [
const Icon(
Icons.error_outline,
size: 27,
color: Colors.black54,
),
const SizedBox(width: 15),
widget.evento.ubicacion != 'sitio'
? const Text(
'Servicio a domicilio.',
style: TextStyle(
color: Colors.black, fontSize: 14),
)
: const Text(
'Servicio en su sitio / consultorio',
style: TextStyle(
color: Colors.black, fontSize: 14),
),
],
),
),
settings?.tarifas == true && widget.evento.tarifa != 0
? Column(
children: [
Text(
formatCurrency(widget.evento.tarifa ?? 0),
style: const TextStyle(
fontWeight: FontWeight.w600, fontSize: 25),
),
const Text('Tarifa consulta',
style: TextStyle(fontSize: 15)),
],
)
: const SizedBox(),
const SizedBox(height: 15),
// Text('${widget.evento.range1Hour1} - ${DateTime.now()}'),
Text(
textAlign: TextAlign.center,
'"${widget.evento.description?.trim()}"',
style: const TextStyle(
color: Colors.grey, fontStyle: FontStyle.italic),
),
widget.evento.userId == widget.evento.professionalId
? const SizedBox()
: widget.evento.status == 'aprobado' ||
widget.evento.status == 'iniciado'
? const Padding(
padding: EdgeInsets.symmetric(vertical: 20),
child: Text(
'Medios de comunicación con el usuario.',
style: TextStyle(color: Color(0xFF2BA4EC)),
),
)
: const SizedBox(height: 10),
widget.evento.userId == widget.evento.professionalId
? const SizedBox()
: widget.evento.status == 'terminado'
? pro == true
? widget.evento.professionalScored == true
? const SizedBox()
: Column(
children: [
const SizedBox(height: 120),
ElevatedButton(
onPressed: () {
Navigator.pushReplacement(
context,
CupertinoPageRoute(
builder: (BuildContext context) {
return ScoreScreen(
evento: widget.evento,
pro: pro!,
);
},
),
);
},
style: ElevatedButton.styleFrom(
backgroundColor:
const Color(0xFF2BA4EC),
shape: RoundedRectangleBorder(
borderRadius:
BorderRadius.circular(50),
),
elevation: 0,
minimumSize: const Size(230, 60),
),
child: const Text(
'Puntuar servicio',
style: TextStyle(
color: Colors.white,
fontWeight: FontWeight.bold,
fontSize: 18,
),
),
),
],
)
: widget.evento.userScored == true
? const SizedBox()
: Column(
children: [
const SizedBox(height: 120),
ElevatedButton(
onPressed: () {
Navigator.pushReplacement(
context,
CupertinoPageRoute(
builder: (BuildContext context) {
return ScoreScreen(
evento: widget.evento,
pro: pro!,
);
},
),
);
},
style: ElevatedButton.styleFrom(
backgroundColor:
const Color(0xFF2BA4EC),
shape: RoundedRectangleBorder(
borderRadius:
BorderRadius.circular(50),
),
elevation: 0,
minimumSize: const Size(230, 60),
),
child: const Text(
'Puntuar servicio',
style: TextStyle(
color: Colors.white,
fontWeight: FontWeight.bold,
fontSize: 18,
),
),
),
],
)
: widget.evento.status == 'aprobado' ||
widget.evento.status == 'iniciado'
? Row(
children: [
const Expanded(child: SizedBox()),
ElevatedButton(
onPressed: () => launch("tel:$numberPhone"),
style: ElevatedButton.styleFrom(
foregroundColor: const Color(0xFF2BA4EC),
backgroundColor: Colors.white,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(50),
side: const BorderSide(
color: Color(0xFF2BA4EC),
width: 2,
),
),
),
child: const Padding(
padding: EdgeInsets.symmetric(
vertical: 18, horizontal: 0),
child: Icon(
Icons.phone_android,
size: 30,
color: Color(0xFF2BA4EC),
),
),
),
const SizedBox(width: 20),
ElevatedButton(
onPressed: () async {
if (widget.evento.status != 'pendiente') {
final chatDoc = FirebaseFirestore
.instance
.collection('chats')
.doc(widget.evento.id);
final chatSnapshot =
await chatDoc.get();
if (!chatSnapshot.exists ||
chatSnapshot.data()!['message'] ==
null) {
await chatDoc.set(
{
'professional_id':
widget.evento.professionalId,
'user_id': widget.evento.userId,
'message': [],
},
SetOptions(merge: true),
).catchError((error) => print(
'Error al crear el documento: $error'));
}
Navigator.push(
context,
CupertinoPageRoute(
builder: (BuildContext context) {
return ChatScreen(
eventoId: widget.evento.id);
},
),
);
}
// if (widget.evento.status != 'pendiente') {
// await FirebaseFirestore.instance
// .collection('chats')
// .doc(widget.evento.id)
// .set(
// {
// 'professional_id':
// widget.evento.professionalId,
// 'user_id': widget.evento.userId,
// 'message': [],
// },
// SetOptions(
// merge:
// true)).catchError((error) => print(
// 'Error al crear el documento: $error'));
// Navigator.push(
// context,
// CupertinoPageRoute(
// builder: (BuildContext context) {
// return ChatScreen(
// eventoId: widget.evento.id);
// },
// ),
// );
// }
else {
Get.snackbar(
'El profesional aun no ha aceptado tu solicitud',
'Debes esperar a que el profesional acepte tu solicitud para poder iniciar un chat.',
snackPosition: SnackPosition.BOTTOM,
);
}
},
style: ElevatedButton.styleFrom(
foregroundColor: const Color(0xFF2BA4EC),
backgroundColor: Colors.white,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(50),
side: const BorderSide(
color: Color(0xFF2BA4EC),
width: 2,
),
),
),
child: const Padding(
padding: EdgeInsets.symmetric(
vertical: 18, horizontal: 0),
child: Icon(
Icons.message,
size: 30,
color: Color(0xFF2BA4EC),
),
),
),
const SizedBox(width: 20),
ElevatedButton(
onPressed: () {
_sendWhatsapp(numberPhone);
},
style: ElevatedButton.styleFrom(
foregroundColor: const Color(0xFF2BA4EC),
backgroundColor: Colors.white,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(50),
side: const BorderSide(
color: Color(0xFF2BA4EC),
width: 2,
),
),
),
child: const Padding(
padding: EdgeInsets.symmetric(
vertical: 18, horizontal: 0),
child: Icon(
CommunityMaterialIcons.whatsapp,
size: 30,
color: Color(0xFF2BA4EC),
),
),
),
const Expanded(child: SizedBox()),
],
)
: SizedBox(),
widget.evento.ubicacion == 'sitio'
? const SizedBox()
: Padding(
padding: const EdgeInsets.only(top: 40),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
ElevatedButton(
onPressed: () {
_openMap(widget.evento.latitud!,
widget.evento.longitud!);
},
style: ElevatedButton.styleFrom(
foregroundColor: const Color(0xFF2BA4EC),
backgroundColor: const Color(0xFF2BA4EC),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(50),
side: const BorderSide(
color: Color(0xFF2BA4EC),
width: 2,
),
),
),
child: const Padding(
padding: EdgeInsets.symmetric(
vertical: 18, horizontal: 0),
child: Icon(
Icons.near_me,
size: 30,
color: Color(0xFFFFFFFF),
),
),
),
const SizedBox(width: 20),
SizedBox(
width: 200,
child: Text('${widget.evento.address}'),
)
],
),
),
],
),
),
widget.evento.status == 'aprobado'
? Padding(
padding: const EdgeInsets.only(bottom: 30),
child: Column(
children: [
Padding(
padding: const EdgeInsets.only(bottom: 20),
child: eventDate.year == today.year &&
eventDate.month == today.month &&
eventDate.day == today.day
? (DateTime.now()
.difference(DateTime.parse(
widget.evento.range1Hour1))
.abs() <=
const Duration(minutes: 30) &&
ver == true)
? ElevatedButton(
onPressed: () {
FirebaseFirestore.instance
.collection("services")
.doc('${widget.evento.id}')
.update({"status": "iniciado"}).then(
(value) {
setState(() {
ver = false;
});
});
},
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFF2BA4EC),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(50),
),
elevation: 0,
minimumSize: const Size(230, 60),
),
child: const Text(
'Iniciar servicio',
style: TextStyle(
color: Colors.white,
fontWeight: FontWeight.bold,
fontSize: 18,
),
),
)
: const SizedBox()
: const SizedBox(),
),
widget.evento.professionalId == uid
? ElevatedButton(
onPressed: () {
FirebaseFirestore.instance
.collection("services")
.doc('${widget.evento.id}')
.update({"status": "denegado"}).then(
(value) {
if (userToken != '') {
sendPushNotification(
userToken, 'rechazo', proName);
}
Navigator.pushReplacementNamed(
context, '/solicitud');
});
},
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFFEC2B2B),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(50),
),
elevation: 0,
minimumSize: const Size(230, 60),
),
child: const Text(
'Cancelar servicio',
style: TextStyle(
color: Colors.white,
fontWeight: FontWeight.bold,
fontSize: 18,
),
),
)
: const SizedBox(),
],
),
)
: const SizedBox(),
widget.evento.status == 'pendiente'
? Padding(
padding: const EdgeInsets.only(bottom: 30),
child: Column(
children: [
Padding(
padding: const EdgeInsets.only(bottom: 20),
child: widget.evento.professionalId == uid
? ElevatedButton(
onPressed: () {
FirebaseFirestore.instance
.collection("services")
.doc('${widget.evento.id}')
.update({"status": "aprobado"}).then(
(value) {
if (userToken != '') {
sendPushNotification(
userToken, 'acepto', proName);
}
Navigator.pushReplacementNamed(
context, '/solicitud');
});
},
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFF2BA4EC),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(50),
),
elevation: 0,
minimumSize: const Size(230, 60),
),
child: const Text(
'Aceptar',
style: TextStyle(
color: Colors.white,
fontWeight: FontWeight.bold,
fontSize: 18,
),
),
)
: const SizedBox(),
),
ElevatedButton(
onPressed: () {
FirebaseFirestore.instance
.collection("services")
.doc('${widget.evento.id}')
.update({"status": "denegado"}).then((value) {
if (userToken != '') {
sendPushNotification(
userToken, 'rechazo', proName);
}
Navigator.pushReplacementNamed(
context, '/solicitud');
});
},
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFFEC2B2B),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(50),
),
elevation: 0,
minimumSize: const Size(230, 60),
),
child: const Text(
'Cancelar servicio',
style: TextStyle(
color: Colors.white,
fontWeight: FontWeight.bold,
fontSize: 18,
),
),
),
],
),
)
: const SizedBox(),
widget.evento.status == 'iniciado' || ver == false
? Padding(
padding: const EdgeInsets.only(bottom: 30),
child: Column(
children: [
ElevatedButton(
onPressed: () {
FirebaseFirestore.instance
.collection("services")
.doc('${widget.evento.id}')
.update({"status": "terminado"}).then((value) {
Navigator.pushReplacement(
context,
CupertinoPageRoute(
builder: (BuildContext context) {
return ScoreScreen(
evento: widget.evento,
pro: pro!,
);
},
),
);
});
},
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFF2BA4EC),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(50),
),
elevation: 0,
minimumSize: const Size(230, 60),
),
child: const Text(
'Terminar servicio',
style: TextStyle(
color: Colors.white,
fontWeight: FontWeight.bold,
fontSize: 18,
),
),
),
],
),
)
: const SizedBox(),
],
),
);
}
}
-206
View File
@@ -1,206 +0,0 @@
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:diacritic/diacritic.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter/material.dart';
import 'package:prosappco/src/authentication/authentication_repository.dart';
import 'package:prosappco/src/components/pop_appbar.dart';
class CityScreen extends StatefulWidget {
const CityScreen({super.key});
@override
State<CityScreen> createState() => _CityScreenState();
}
final CollectionReference countriesCollection =
FirebaseFirestore.instance.collection('countries');
class City {
String? cityName;
String? coordsOfCity;
String? stateOfCity;
String? countryOfCity;
City({
this.cityName,
this.coordsOfCity,
this.stateOfCity,
this.countryOfCity,
});
@override
String toString() {
return "${cityName ?? ""}, ${coordsOfCity ?? ""}, ${stateOfCity ?? ""}, ${countryOfCity ?? ""}";
}
}
Future<List<City>> getCountries() async {
List<City> citys = [];
try {
QuerySnapshot countries = await countriesCollection.get();
for (DocumentSnapshot country in countries.docs) {
String countryName = country.id;
Map<String, dynamic> data = country.data() as Map<String, dynamic>;
Map<String, Map<String, String>> states = {};
for (var entry in data.entries) {
String key = entry.key;
Map<String, String> cityData = Map<String, String>.from(entry.value);
states[key] = cityData;
}
for (var state in states.entries) {
var citysState = state.value.entries.map((city) => City(
cityName: city.key,
coordsOfCity: city.value,
stateOfCity: state.key,
countryOfCity: countryName,
));
citys.addAll(citysState);
}
}
} catch (e) {
print('$e');
}
return citys;
}
class _CityScreenState extends State<CityScreen> {
List<City>? filteredCities;
TextEditingController searchController = TextEditingController();
final User? user = FirebaseAuth.instance.currentUser;
final uid = AuthenticationRepository.instance.getCurrentUserUid();
List<City>? _cities;
@override
void initState() {
super.initState();
searchController.addListener(() {
setState(() {
if (_cities != null) {
if (searchController.text.isEmpty) {
filteredCities = _cities!;
} else {
filteredCities = _cities!
.where((city) => removeDiacritics(city.cityName!)
.toLowerCase()
.contains(
removeDiacritics(searchController.text.toLowerCase())))
.toList();
}
}
});
});
if (_cities == null) {
getCountries().then((List<City> element) => setState(() {
_cities = element;
filteredCities = element;
}));
}
}
Future<void> updateCity(String cityName, String coordsCity) async {
try {
await FirebaseFirestore.instance
.collection('users')
.doc(uid)
.update({'city': cityName});
await FirebaseFirestore.instance
.collection('users')
.doc(uid)
.update({'coordsOfCity': coordsCity});
} catch (e) {
try {
await FirebaseFirestore.instance
.collection('users')
.doc(uid)
.set({'city': cityName});
await FirebaseFirestore.instance
.collection('users')
.doc(uid)
.set({'coordsOfCity': coordsCity});
} catch (e) {
print('Error al agregar la ciudad: $e');
}
print('Error al actualizar la ciudad: $e');
}
}
@override
Widget build(BuildContext context) {
if (filteredCities == null) {
return const Center(
child: CircularProgressIndicator(
valueColor: AlwaysStoppedAnimation<Color>(Color(0xFF2BA4EC)),
),
);
}
var citys = filteredCities!;
return SafeArea(
child: Scaffold(
appBar: PopAppbar(
onPressed: () {
Navigator.pop(context);
},
label: 'Selecciona tu ciudad'),
body: Column(
children: [
Padding(
padding: const EdgeInsets.only(left: 10, right: 10, top: 10),
child: TextField(
controller: searchController,
decoration: const InputDecoration(
hintText: 'Busca una ciudad',
prefixIcon: Icon(Icons.near_me),
),
),
),
Expanded(
child: ListView.builder(
itemCount: citys.length,
itemBuilder: (BuildContext context, int index) {
return ListTile(
title: RichText(
text: TextSpan(
style: const TextStyle(
fontSize: 18.0,
color: Colors.black,
),
children: [
TextSpan(
text: '${citys[index].cityName ?? ""}, ',
style: const TextStyle(fontWeight: FontWeight.bold),
),
TextSpan(
text:
"${citys[index].stateOfCity ?? ""}, ${citys[index].countryOfCity ?? ""}",
style: TextStyle(color: Colors.grey[600]),
),
],
),
),
onTap: () {
updateCity(citys[index].cityName ?? "",
citys[index].coordsOfCity ?? "");
Navigator.pop(context, citys[index].cityName ?? "");
},
);
},
),
),
],
),
),
);
}
}
@@ -1,274 +0,0 @@
import 'package:flutter/material.dart';
import 'package:flutter_otp_text_field/flutter_otp_text_field.dart';
import 'package:get/get.dart';
import 'package:prosappco/src/components/bottom_sheet.dart';
import 'package:prosappco/src/components/column_padding.dart';
import 'package:prosappco/src/components/primary_btn.dart';
import 'package:prosappco/src/controllers/otp_controller.dart';
import 'package:prosappco/src/controllers/phone_auth_controller.dart';
import 'package:responsive_builder/responsive_builder.dart';
class CodeValidationScreen extends StatelessWidget {
CodeValidationScreen({super.key, this.phoneNumber});
String? phoneNumber;
var otp;
final controller = Get.put(OTPController());
@override
Widget build(BuildContext context) {
return ScreenTypeLayout.builder(
mobile: (BuildContext context) => _mobileView(context),
tablet: (BuildContext context) => _mobileView(context),
desktop: (BuildContext context) => _desktopView(context),
);
}
Widget _mobileView(BuildContext context) {
return BottomSheetExpanded(
horizontalPadding: 10,
children: [
Row(
children: [
IconButton(
icon: const Icon(
Icons.arrow_back,
size: 30,
),
onPressed: () {
Navigator.pop(context);
},
),
const Text(
'Valida el código',
style: TextStyle(
color: Color(0xFF262626),
fontSize: 30.0,
fontWeight: FontWeight.bold,
),
),
],
),
ColumnPadding(
alineacion: MainAxisAlignment.start,
padding: const EdgeInsets.symmetric(horizontal: 25),
children: [
const SizedBox(height: 10),
const SizedBox(
width: double.infinity,
child: Text(
'Numero de celular',
style: TextStyle(
fontSize: 18.0,
color: Color(0xFF65676B),
),
),
),
const SizedBox(height: 10),
Row(
children: [
Expanded(
child: TextField(
onChanged: (value) {
phoneNumber = value;
},
controller: TextEditingController(text: phoneNumber ?? ''),
decoration: const InputDecoration(
border: InputBorder.none,
hintText: '',
suffixIcon: Icon(Icons.edit),
),
),
),
TextButton(
child: const Text('Reenviar código'),
onPressed: () {
if (phoneNumber!.isNotEmpty) {
PhoneAuthController.instance.phoneAuthentication(
phoneNumber!,
);
}
},
),
],
),
const SizedBox(height: 10),
const SizedBox(
width: double.infinity,
child: Text(
'Codigo',
textAlign: TextAlign.left,
style: TextStyle(
fontSize: 18.0,
color: Color(0xFF65676B),
),
),
),
const SizedBox(height: 10),
OtpTextField(
numberOfFields: 6,
focusedBorderColor: Colors.blue,
fillColor: Colors.black.withOpacity(0.1),
filled: true,
keyboardType: TextInputType.number,
onSubmit: (code) {
otp = code;
OTPController.instance.verifyOTP(otp);
},
),
const SizedBox(height: 40),
PrimaryButtom(
onPressed: () {
OTPController.instance.verifyOTP(otp);
},
label: 'Valida el código',
),
const SizedBox(height: 30),
],
),
],
);
}
Widget _desktopView(BuildContext context) {
double height = MediaQuery.of(context).size.height;
double width = MediaQuery.of(context).size.width;
return Scaffold(
backgroundColor: const Color(0xFFD6F4FF),
body: SizedBox(
height: height,
width: width,
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start,
children: [
Expanded(
child: SizedBox(
height: height,
child: const Center(
child: Image(
image: AssetImage('images/logo_prosapp.png'),
),
),
),
),
Expanded(
child: Container(
padding: EdgeInsets.symmetric(horizontal: width * 0.07),
color: Colors.white,
height: height,
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Row(
children: [
IconButton(
icon: const Icon(
Icons.arrow_back,
size: 30,
),
onPressed: () {
Navigator.pop(context);
},
),
SizedBox(width: width * 0.01),
const Text(
'Validar código',
style: TextStyle(
color: Color(0xFF262626),
fontSize: 38.0,
fontWeight: FontWeight.bold,
),
),
],
),
ColumnPadding(
alineacion: MainAxisAlignment.start,
padding: const EdgeInsets.symmetric(horizontal: 25),
children: [
const SizedBox(height: 10),
const SizedBox(
width: double.infinity,
child: Text(
'Numero de celular',
style: TextStyle(
fontSize: 18.0,
color: Color(0xFF65676B),
),
),
),
const SizedBox(height: 10),
Row(
children: [
Expanded(
child: TextField(
onChanged: (value) {
phoneNumber = value;
},
controller: TextEditingController(
text: phoneNumber ?? ''),
decoration: const InputDecoration(
border: InputBorder.none,
hintText: '',
suffixIcon: Icon(Icons.edit),
),
),
),
TextButton(
child: const Text('Reenviar código'),
onPressed: () {
if (phoneNumber!.isNotEmpty) {
PhoneAuthController.instance
.phoneAuthentication(
phoneNumber!,
);
}
},
),
],
),
const SizedBox(height: 10),
const SizedBox(
width: double.infinity,
child: Text(
'Codigo',
textAlign: TextAlign.left,
style: TextStyle(
fontSize: 18.0,
color: Color(0xFF65676B),
),
),
),
const SizedBox(height: 10),
OtpTextField(
numberOfFields: 6,
focusedBorderColor: Colors.blue,
fillColor: Colors.black.withOpacity(0.1),
filled: true,
keyboardType: TextInputType.number,
onSubmit: (code) {
otp = code;
OTPController.instance.verifyOTP(otp);
},
),
const SizedBox(height: 40),
PrimaryButtom(
onPressed: () {
OTPController.instance.verifyOTP(otp);
},
label: 'Valida el código',
),
const SizedBox(height: 30),
],
),
],
),
),
),
],
),
),
);
}
}
@@ -1,126 +0,0 @@
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:prosappco/src/components/pop_appbar.dart';
import 'package:prosappco/src/presentation/screens/about.dart';
class ConfiguracionScreen extends StatefulWidget {
const ConfiguracionScreen({super.key});
@override
State<ConfiguracionScreen> createState() => _ConfiguracionScreenState();
}
class _ConfiguracionScreenState extends State<ConfiguracionScreen> {
late final FirebaseAuth _auth;
@override
void initState() {
super.initState();
_auth = FirebaseAuth.instance;
}
Future<void> deleteAccount() async {
try {
final currentUser = _auth.currentUser;
if (currentUser != null) {
final uid = currentUser.uid;
await FirebaseFirestore.instance.collection('users').doc(uid).delete();
await currentUser.delete();
await _auth.signOut();
Get.snackbar(
'Cuenta Eliminada',
'Tu cuenta ha sido eliminada con éxito.',
snackPosition: SnackPosition.BOTTOM,
);
}
} catch (e) {
Get.snackbar(
'Error al Eliminar Cuenta',
'Hubo un error al eliminar tu cuenta. Por favor, inténtalo de nuevo más tarde.',
snackPosition: SnackPosition.BOTTOM,
);
}
}
Future<void> _showDeleteAccountConfirmationDialog(
BuildContext context) async {
return showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: const Text('Eliminar Cuenta'),
content: const Text(
'¿Estás seguro de que deseas eliminar tu cuenta? Esta acción no se puede deshacer.'),
actions: [
TextButton(
onPressed: () {
Navigator.of(context).pop();
},
child: const Text('Cancelar'),
),
TextButton(
onPressed: () {
deleteAccount();
Navigator.of(context).pop();
},
child: const Text(
'Eliminar',
style:
TextStyle(color: Colors.red, fontWeight: FontWeight.w600),
),
),
],
);
},
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: PopAppbar(
onPressed: () {
Navigator.pop(context);
},
label: 'Configuración'),
body: ListView(
children: [
ListTile(
onTap: () {
Navigator.push(
context,
CupertinoPageRoute(
builder: (BuildContext context) {
return const AboutScreen();
},
),
);
},
title: const Text('Acerca de la aplicación'),
trailing: const Icon(
Icons.keyboard_arrow_right,
color: Colors.black,
),
),
ListTile(
onTap: () {
_showDeleteAccountConfirmationDialog(context);
},
title: const Text(
'Eliminar cuenta',
style: TextStyle(color: Colors.red),
),
),
],
),
);
}
}
-202
View File
@@ -1,202 +0,0 @@
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
import 'package:prosappco/src/authentication/authentication_repository.dart';
import 'package:prosappco/src/components/pop_appbar.dart';
import 'package:prosappco/src/components/primary_btn.dart';
import '../../components/schedule_picker.dart';
class HorarioScreen extends StatelessWidget {
Map<String, Schedule> horarios;
HorarioScreen({super.key, required this.horarios});
final uid = AuthenticationRepository.instance.getCurrentUserUid();
bool lunesValue = false;
bool martesValue = false;
bool miercolesValue = false;
bool juevesValue = false;
bool viernesValue = false;
bool sabadoValue = false;
bool domingoValue = false;
bool jornadaContinuaLunes = false;
Future<void> updateHorario(BuildContext context) async {
try {
Map<String, dynamic> horariosMap = {};
horarios.forEach((key, value) {
if (value.habilitado && !value.jornadaContinua) {
if (value.range1Hour1 == null ||
value.range1Hour2 == null ||
value.range2Hour1 == null ||
value.range2Hour2 == null) {
value.habilitado = false;
value.jornadaContinua = false;
}
}
if (value.habilitado && value.jornadaContinua) {
if (value.range1Hour1 == null || value.range2Hour2 == null) {
value.habilitado = false;
value.jornadaContinua = false;
}
}
horariosMap[key] = {
'habilitado': value.habilitado,
'jornadaContinua': value.jornadaContinua,
'range1Hour1': formatTimeOfDay(value.range1Hour1),
'range1Hour2': formatTimeOfDay(value.range1Hour2),
'range2Hour1': formatTimeOfDay(value.range2Hour1),
'range2Hour2': formatTimeOfDay(value.range2Hour2),
};
});
await FirebaseFirestore.instance.collection('users').doc(uid).update({
'horario': horariosMap,
});
} catch (e) {
print('Error al actualizar el horario: $e');
}
}
// Future<void> updateHorario(BuildContext context) async {
// try {
// Map<String, dynamic> horariosMap = {};
// horarios.forEach((key, value) {
// if (value.habilitado && !value.jornadaContinua) {
// if (value.range1Hour1 == null ||
// value.range1Hour2 == null ||
// value.range2Hour1 == null ||
// value.range2Hour2 == null) {
// value.habilitado = false;
// value.jornadaContinua = false;
// return;
// }
// }
// if (value.habilitado && value.jornadaContinua) {
// if (value.range1Hour1 == null || value.range2Hour2 == null) {
// value.habilitado = false;
// value.jornadaContinua = false;
// }
// }
// horariosMap[key] = {
// 'habilitado': value.habilitado,
// 'jornadaContinua': value.jornadaContinua,
// 'range1Hour1': formatTimeOfDay(value.range1Hour1),
// 'range1Hour2': formatTimeOfDay(value.range1Hour2),
// 'range2Hour1': formatTimeOfDay(value.range2Hour1),
// 'range2Hour2': formatTimeOfDay(value.range2Hour2),
// };
// });
// await FirebaseFirestore.instance.collection('users').doc(uid).update({
// 'horario': horariosMap,
// });
// } catch (e) {
// print('Error al actualizar el horario: $e');
// }
// }
String? formatTimeOfDay(TimeOfDay? time) {
if (time != null) {
final now = DateTime.now();
final dateTime =
DateTime(now.year, now.month, now.day, time.hour, time.minute);
final format = DateFormat.jm();
return format.format(dateTime);
}
return null;
}
int _dayOfWeekToInt(String dayOfWeek) {
switch (dayOfWeek) {
case '1':
return 1;
case '2':
return 2;
case '3':
return 3;
case '4':
return 4;
case '5':
return 5;
case '6':
return 6;
case '7':
return 7;
default:
throw ArgumentError('Invalid day of week: $dayOfWeek');
}
}
String _stringToDayOfWeek(String dayOfWeek) {
switch (dayOfWeek) {
case '1':
return 'Lunes';
case '2':
return 'Martes';
case '3':
return 'Miércoles';
case '4':
return 'Jueves';
case '5':
return 'Viernes';
case '6':
return 'Sábado';
case '7':
return 'Domingo';
default:
throw ArgumentError('Invalid day of week: $dayOfWeek');
}
}
@override
Widget build(BuildContext context) {
final sortedHorarios = Map.fromEntries(
horarios.entries.toList()
..sort(
(a, b) => _dayOfWeekToInt(a.key).compareTo(_dayOfWeekToInt(b.key))),
);
return SafeArea(
child: Scaffold(
appBar: PopAppbar(
onPressed: () {
Navigator.pop(context);
},
label: 'Horario'),
body: SingleChildScrollView(
child: Column(
children: [
const Divider(
height: 5,
),
Column(
children: sortedHorarios.entries.map<Widget>(
(entry) {
return SchedulePicker(
name: _stringToDayOfWeek(entry.key),
schedule: entry.value,
);
},
).toList(),
),
Padding(
padding: const EdgeInsets.only(top: 30, bottom: 30),
child: PrimaryButtom(
onPressed: () async {
await updateHorario(context);
Navigator.pop(context);
},
label: 'Guardar',
),
)
],
),
),
),
);
}
}
@@ -1,452 +0,0 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:get/get.dart';
import 'package:intl_phone_field/intl_phone_field.dart';
import 'package:prosappco/src/components/bottom_sheet.dart';
import 'package:prosappco/src/presentation/widgets/shared/primary_button.dart';
import 'package:prosappco/src/controllers/phone_auth_controller.dart';
import 'package:prosappco/src/models/setting_model.dart';
import 'package:prosappco/src/providers/user_provider.dart';
import 'package:prosappco/src/presentation/screens/code_validation.dart';
import 'package:prosappco/src/presentation/screens/web_view.dart';
import 'package:provider/provider.dart';
import 'package:responsive_builder/responsive_builder.dart';
import 'package:url_launcher/url_launcher.dart';
class LoginScreen extends StatefulWidget {
const LoginScreen({super.key});
@override
State<LoginScreen> createState() => _LoginScreenState();
}
class _LoginScreenState extends State<LoginScreen> {
final controller = Get.put(PhoneAuthController());
final _formKey = GlobalKey<FormState>();
String completePhoneNumber = '';
bool _isChecked = false;
SettingModel? settings;
void _clearPhoneNumber() {
if (mounted) {
setState(() {
controller.phoneNo.text = '';
});
}
}
void _launchURL(String url) async {
if (await canLaunch(url)) {
await launch(url, forceSafariVC: false, forceWebView: false);
} else {
throw 'No se pudo abrir el enlace $url';
}
}
@override
void initState() {
super.initState();
if (settings == null) {
SettingModel.getSettings().then((SettingModel value) {
if (mounted) {
setState(() {
settings = value;
});
}
});
}
}
@override
Widget build(BuildContext context) {
return ScreenTypeLayout.builder(
mobile: (BuildContext context) => _mobileView(context),
tablet: (BuildContext context) => _mobileView(context),
desktop: (BuildContext context) => _desktopView(context),
);
}
Widget _mobileView(BuildContext context) {
const inputDecoration = InputDecoration(
border: OutlineInputBorder(
borderSide: BorderSide(color: Color(0xFFECECEC)),
borderRadius: BorderRadius.all(
Radius.circular(50),
)),
errorBorder: OutlineInputBorder(
borderSide: BorderSide(color: Color.fromARGB(255, 184, 0, 0)),
borderRadius: BorderRadius.all(
Radius.circular(50),
)),
enabledBorder: OutlineInputBorder(
borderSide: BorderSide(color: Color(0xFFECECEC)),
borderRadius: BorderRadius.all(
Radius.circular(50),
)),
focusedBorder: OutlineInputBorder(
borderSide: BorderSide(color: Color(0xFFECECEC)),
borderRadius: BorderRadius.all(
Radius.circular(50),
)),
fillColor: Color.fromARGB(255, 239, 239, 239),
filled: true,
);
return BottomSheetExpanded(
children: [
const SizedBox(
width: double.infinity,
child: Text(
'Iniciar sesión',
style: TextStyle(
color: Color(0xFF262626),
fontSize: 30.0,
fontWeight: FontWeight.bold,
),
),
),
const SizedBox(height: 10),
const SizedBox(
width: double.infinity,
child: Text(
'Numero de celular',
style: TextStyle(
fontSize: 18.0,
color: Color(0xFF65676B),
),
),
),
Form(
key: _formKey,
child: IntlPhoneField(
controller: controller.phoneNo,
initialCountryCode: 'CO',
keyboardType: TextInputType.number,
inputFormatters: [FilteringTextInputFormatter.digitsOnly],
onChanged: (phoneNo) {
completePhoneNumber = phoneNo.completeNumber;
},
decoration: inputDecoration,
),
),
const Text(
'Un código será enviado a este numero de celular.',
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 13.0,
color: Color(0xFF65676B),
),
),
Padding(
padding: const EdgeInsets.symmetric(vertical: 20),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Checkbox(
value: _isChecked,
onChanged: (value) {
setState(() {
_isChecked = value!;
});
},
),
GestureDetector(
onTap: () {
if (kIsWeb) {
_launchURL(settings?.terminosCondiciones ?? '');
} else {
Navigator.push(
context,
CupertinoPageRoute(
builder: (BuildContext context) {
return WebViewScreen(
label: 'Términos y condiciones',
link: settings?.terminosCondiciones ?? '',
);
},
),
);
}
},
child: const Text(
'Acepto los términos y condiciones.',
style: TextStyle(
fontSize: 13.0,
color: Color(0xFF65676B),
decoration: TextDecoration.underline,
),
),
),
],
),
),
PrimaryButton(
onPressed: () async {
if (_formKey.currentState!.validate()) {
PhoneAuthController.instance.phoneAuthentication(
completePhoneNumber.trim(),
);
_clearPhoneNumber();
await Get.to(
() => CodeValidationScreen(
phoneNumber: completePhoneNumber.trim(),
),
);
Provider.of<UserProvider>(context, listen: false)
.initUserProvider();
}
},
text: 'Enviar código',
isEnabled: _isChecked,
),
const SizedBox(height: 20),
GestureBottom(clearPhoneNumber: _clearPhoneNumber),
const SizedBox(height: 20),
const RichTxTBottom(),
const SizedBox(height: 20),
],
);
}
Widget _desktopView(BuildContext context) {
double height = MediaQuery.of(context).size.height;
double width = MediaQuery.of(context).size.width;
return Scaffold(
backgroundColor: const Color(0xFFD6F4FF),
body: SizedBox(
height: height,
width: width,
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start,
children: [
Expanded(
child: SizedBox(
height: height,
child: const Center(
child: Image(
image: AssetImage('images/logo_prosapp.png'),
),
),
),
),
Expanded(
child: Container(
padding: EdgeInsets.symmetric(horizontal: width * 0.1),
color: Colors.white,
height: height,
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: [
const SizedBox(
width: double.infinity,
child: Text(
'Iniciar sesión',
style: TextStyle(
color: Color(0xFF262626),
fontSize: 30.0,
fontWeight: FontWeight.bold,
),
),
),
const SizedBox(height: 20),
const SizedBox(
width: double.infinity,
child: Text(
'Numero de celular',
style: TextStyle(
fontSize: 18.0,
color: Color(0xFF65676B),
),
),
),
Form(
key: _formKey,
child: IntlPhoneField(
controller: controller.phoneNo,
initialCountryCode: 'CO',
keyboardType: TextInputType.number,
inputFormatters: [
FilteringTextInputFormatter.digitsOnly
],
onChanged: (phoneNo) {
completePhoneNumber = phoneNo.completeNumber;
},
decoration: const InputDecoration(
border: OutlineInputBorder(
borderSide: BorderSide(color: Color(0xFFECECEC)),
borderRadius: BorderRadius.all(
Radius.circular(50),
)),
errorBorder: OutlineInputBorder(
borderSide: BorderSide(
color: Color.fromARGB(255, 184, 0, 0)),
borderRadius: BorderRadius.all(
Radius.circular(50),
)),
enabledBorder: OutlineInputBorder(
borderSide: BorderSide(color: Color(0xFFECECEC)),
borderRadius: BorderRadius.all(
Radius.circular(50),
)),
focusedBorder: OutlineInputBorder(
borderSide: BorderSide(color: Color(0xFFECECEC)),
borderRadius: BorderRadius.all(
Radius.circular(50),
)),
fillColor: Color.fromARGB(255, 239, 239, 239),
filled: true,
),
),
),
const Text(
'Se enviará un código a este número de celular.',
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 13.0,
color: Color(0xFF65676B),
),
),
Padding(
padding: const EdgeInsets.symmetric(vertical: 20),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Checkbox(
value: _isChecked,
onChanged: (value) {
setState(() {
_isChecked = value!;
});
},
),
GestureDetector(
onTap: () {
if (kIsWeb) {
_launchURL(settings?.terminosCondiciones ?? '');
} else {
Navigator.push(
context,
CupertinoPageRoute(
builder: (BuildContext context) {
return WebViewScreen(
label: 'Términos y condiciones',
link:
settings?.terminosCondiciones ?? '',
);
},
),
);
}
},
child: const Text(
'Acepto los términos y condiciones.',
style: TextStyle(
fontSize: 13.0,
color: Color(0xFF65676B),
decoration: TextDecoration
.underline, // Add underline style
),
),
),
],
),
),
PrimaryButton(
onPressed: () async {
if (_formKey.currentState!.validate()) {
PhoneAuthController.instance.phoneAuthentication(
completePhoneNumber.trim(),
);
await Get.to(
() => CodeValidationScreen(
phoneNumber: completePhoneNumber.trim(),
),
);
_clearPhoneNumber();
Provider.of<UserProvider>(context, listen: false)
.initUserProvider();
}
},
text: 'Enviar código',
isEnabled: _isChecked,
),
const SizedBox(height: 20),
GestureBottom(clearPhoneNumber: _clearPhoneNumber),
const SizedBox(height: 20),
const RichTxTBottom(),
],
),
),
),
],
),
),
);
}
}
class GestureBottom extends StatelessWidget {
final VoidCallback clearPhoneNumber;
GestureBottom({
super.key,
required this.clearPhoneNumber,
});
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: () {
clearPhoneNumber();
Navigator.pushNamed(context, '/login');
},
child: const Text(
'Inicia sesión con tu correo electrónico',
style: TextStyle(
fontSize: 15.0, color: Color(0xFF65676B),
decoration: TextDecoration.underline, // Subrayado
),
),
);
}
}
class RichTxTBottom extends StatelessWidget {
const RichTxTBottom({
super.key,
});
@override
Widget build(BuildContext context) {
return RichText(
text: TextSpan(
style: const TextStyle(
fontSize: 16.0,
color: Color(0xFF65676B),
fontFamily: 'Poppins',
),
children: [
const TextSpan(text: '¿No estás registrado? '),
WidgetSpan(
child: GestureDetector(
onTap: () {
Navigator.pushNamed(context, '/register');
},
child: const Text(
'Regístrate',
style: TextStyle(
fontSize: 16.0,
color: Color(0xFF2BA4EC),
fontWeight: FontWeight.w600,
),
),
),
),
],
),
);
}
}
@@ -1,552 +0,0 @@
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:get/get.dart';
import 'package:prosappco/src/authentication/authentication_repository.dart';
import 'package:prosappco/src/components/bottom_sheet.dart';
import 'package:prosappco/src/presentation/widgets/shared/primary_button.dart';
import 'package:prosappco/src/controllers/login_email_controller.dart';
import 'package:prosappco/src/models/setting_model.dart';
import 'package:prosappco/src/providers/user_provider.dart';
import 'package:provider/provider.dart';
import 'package:responsive_builder/responsive_builder.dart';
class LoginEmailScreen extends StatefulWidget {
const LoginEmailScreen({super.key});
@override
State<LoginEmailScreen> createState() => _LoginEmailScreenState();
}
class _LoginEmailScreenState extends State<LoginEmailScreen> {
bool _obscureText = true;
final controller = Get.put(LoginEmailController());
final _formKey = GlobalKey<FormState>();
SettingModel? settings;
@override
void initState() {
super.initState();
if (settings == null) {
SettingModel.getSettings().then(
(SettingModel value) => setState(() {
settings = value;
}),
);
}
}
@override
Widget build(BuildContext context) {
return ScreenTypeLayout.builder(
mobile: (BuildContext context) => _mobileView(context),
tablet: (BuildContext context) => _mobileView(context),
desktop: (BuildContext context) => _desktopView(context),
);
}
Widget _mobileView(BuildContext context) {
bool isIOS = Theme.of(context).platform == TargetPlatform.iOS;
return BottomSheetExpanded(
horizontalPadding: 10,
children: [
Row(
children: <Widget>[
IconButton(
icon: const Icon(
Icons.arrow_back,
size: 30,
),
onPressed: () {
Navigator.pop(context);
},
),
const Text(
'Iniciar sesión',
style: TextStyle(
color: Color(0xFF262626),
fontSize: 30.0,
fontWeight: FontWeight.bold,
),
textAlign: TextAlign.right,
),
],
),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 25),
child: Form(
key: _formKey,
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 0, vertical: 20),
child: Column(
children: [
!isIOS && !kIsWeb && settings?.google == true
? Padding(
padding: const EdgeInsets.only(bottom: 20),
child: ElevatedButton(
onPressed: () async {
await AuthenticationRepository.instance
.signInWithGoogle()
.then((value) => {
Provider.of<UserProvider>(context,
listen: false)
.initUserProvider()
});
},
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFF2BA4EC),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(50),
),
elevation: 0,
minimumSize: const Size(230, 60),
),
child: const Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
'Entra con Google ',
style: TextStyle(
color: Colors.white,
fontWeight: FontWeight.bold,
fontSize: 18,
),
),
SizedBox(width: 5),
FaIcon(FontAwesomeIcons.google),
],
)),
)
: const SizedBox(),
!isIOS && !kIsWeb && settings?.google == true
? const Padding(
padding: EdgeInsets.symmetric(vertical: 5),
child: Row(
children: [
Expanded(
child: Divider(
color: Colors.black38,
thickness: 1,
),
),
Padding(
padding: EdgeInsets.symmetric(horizontal: 10),
child: Text("ó"),
),
Expanded(
child: Divider(
color: Colors.black38,
thickness: 1,
),
),
],
),
)
: const SizedBox(),
const Padding(
padding: EdgeInsets.only(bottom: 5),
child: Align(
alignment: Alignment.topLeft,
child: Text('Email',
style: TextStyle(
fontSize: 18.0, color: Color(0xFF65676B))),
),
),
FormEmail(controller: controller),
const Padding(
padding: EdgeInsets.only(bottom: 5),
child: Align(
alignment: Alignment.topLeft,
child: Text('Password',
style: TextStyle(
fontSize: 18.0, color: Color(0xFF65676B))),
),
),
TextFormField(
controller: controller.password,
obscureText: _obscureText,
validator: (value) {
if (value == null || value.isEmpty) {
return 'Por favor, ingresa una contraseña';
}
return null;
},
decoration: InputDecoration(
enabledBorder: const OutlineInputBorder(
borderSide: BorderSide(color: Color(0xFFECECEC)),
borderRadius: BorderRadius.all(
Radius.circular(50),
),
),
focusedBorder: const OutlineInputBorder(
borderSide: BorderSide(color: Color(0xFFECECEC)),
borderRadius: BorderRadius.all(
Radius.circular(50),
),
),
border: const OutlineInputBorder(
borderSide: BorderSide(color: Color(0xFFECECEC)),
borderRadius: BorderRadius.all(
Radius.circular(50),
),
),
errorBorder: const OutlineInputBorder(
borderSide:
BorderSide(color: Color.fromARGB(255, 184, 0, 0)),
borderRadius: BorderRadius.all(
Radius.circular(50),
),
),
hintText: 'Contraseña',
fillColor: const Color.fromARGB(255, 239, 239, 239),
filled: true,
prefixIcon: const Icon(Icons.lock_outline),
suffixIcon: IconButton(
icon: Icon(
_obscureText
? Icons.visibility
: Icons.visibility_off,
color: Colors.grey,
),
onPressed: () {
setState(() {
_obscureText = !_obscureText;
});
},
),
hintStyle: const TextStyle(
color: Colors.grey,
),
),
),
Padding(
padding: const EdgeInsets.symmetric(vertical: 10),
child: TextButton(
onPressed: () {
Navigator.pushNamed(context, '/resetpassword');
},
child: const Text(
'Olvidé la contraseña',
style: TextStyle(
color: Colors.blue,
),
),
),
),
PaddingButtomBottom(
formKey: _formKey,
controller: controller,
),
const RichTxtBottom()
],
),
),
),
),
],
);
}
Widget _desktopView(BuildContext context) {
double height = MediaQuery.of(context).size.height;
double width = MediaQuery.of(context).size.width;
return Scaffold(
backgroundColor: const Color(0xFFD6F4FF),
body: SizedBox(
height: height,
width: width,
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start,
children: [
Expanded(
child: SizedBox(
height: height,
child: const Center(
child: Image(
image: AssetImage('images/logo_prosapp.png'),
),
),
),
),
Expanded(
child: Container(
padding: EdgeInsets.symmetric(horizontal: width * 0.07),
color: Colors.white,
height: height,
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Row(
children: <Widget>[
IconButton(
icon: const Icon(
Icons.arrow_back,
size: 30,
),
onPressed: () {
Navigator.pop(context);
},
),
SizedBox(width: width * 0.01),
const Text(
'Iniciar sesión',
style: TextStyle(
color: Color(0xFF262626),
fontSize: 30.0,
fontWeight: FontWeight.bold,
),
textAlign: TextAlign.right,
),
],
),
Form(
key: _formKey,
child: Container(
padding: const EdgeInsets.symmetric(
horizontal: 0, vertical: 20),
child: Column(children: [
const Padding(
padding: EdgeInsets.only(bottom: 5),
child: Align(
alignment: Alignment.topLeft,
child: Text('Email',
style: TextStyle(
fontSize: 18.0,
color: Color(0xFF65676B))),
)),
FormEmail(controller: controller),
const Padding(
padding: EdgeInsets.only(bottom: 5),
child: Align(
alignment: Alignment.topLeft,
child: Text('Password',
style: TextStyle(
fontSize: 18.0,
color: Color(0xFF65676B))),
),
),
Padding(
padding: const EdgeInsets.only(bottom: 50),
child: TextFormField(
controller: controller.password,
obscureText: _obscureText,
validator: (value) {
if (value == null || value.isEmpty) {
return 'Por favor, ingresa una contraseña';
}
return null;
},
decoration: InputDecoration(
enabledBorder: const OutlineInputBorder(
borderSide:
BorderSide(color: Color(0xFFECECEC)),
borderRadius: BorderRadius.all(
Radius.circular(50),
)),
focusedBorder: const OutlineInputBorder(
borderSide:
BorderSide(color: Color(0xFFECECEC)),
borderRadius: BorderRadius.all(
Radius.circular(50),
)),
border: const OutlineInputBorder(
borderSide:
BorderSide(color: Color(0xFFECECEC)),
borderRadius: BorderRadius.all(
Radius.circular(50),
)),
errorBorder: const OutlineInputBorder(
borderSide: BorderSide(
color: Color.fromARGB(255, 184, 0, 0)),
borderRadius: BorderRadius.all(
Radius.circular(50),
)),
hintText: 'Contraseña',
hintStyle: const TextStyle(
color: Colors.grey,
),
fillColor:
const Color.fromARGB(255, 239, 239, 239),
filled: true,
prefixIcon: const Icon(Icons.lock_outline),
suffixIcon: IconButton(
icon: Icon(
_obscureText
? Icons.visibility
: Icons.visibility_off,
color: Colors.grey,
),
onPressed: () {
setState(() {
_obscureText = !_obscureText;
});
},
),
),
),
),
Padding(
padding: const EdgeInsets.symmetric(vertical: 20),
child: TextButton(
onPressed: () {
Navigator.pushNamed(context, '/resetpassword');
},
child: const Text(
'Olvidé la contraseña',
style: TextStyle(
color: Colors.blue,
),
),
),
),
PaddingButtomBottom(
formKey: _formKey, controller: controller),
const RichTxtBottom()
]),
),
),
],
),
),
),
],
),
),
);
}
}
class FormEmail extends StatelessWidget {
const FormEmail({
super.key,
required this.controller,
});
final LoginEmailController controller;
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.only(bottom: 20),
child: TextFormField(
controller: controller.email,
validator: (String? value) {
if (value == null || value.isEmpty) {
return 'Por favor, ingresa un Email';
}
final RegExp emailRegExp =
RegExp(r'^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$');
if (!emailRegExp.hasMatch(value)) {
return 'Por favor, ingresa un Email válido';
}
return null;
},
decoration: const InputDecoration(
border: OutlineInputBorder(
borderSide: BorderSide(color: Color(0xFFECECEC)),
borderRadius: BorderRadius.all(
Radius.circular(50),
)),
enabledBorder: OutlineInputBorder(
borderSide: BorderSide(color: Color(0xFFECECEC)),
borderRadius: BorderRadius.all(
Radius.circular(50),
)),
focusedBorder: OutlineInputBorder(
borderSide: BorderSide(color: Color(0xFFECECEC)),
borderRadius: BorderRadius.all(
Radius.circular(50),
)),
errorBorder: OutlineInputBorder(
borderSide: BorderSide(color: Color.fromARGB(255, 184, 0, 0)),
borderRadius: BorderRadius.all(
Radius.circular(50),
)),
hintText: 'Hello@gmail.com',
fillColor: Color.fromARGB(255, 239, 239, 239),
filled: true,
prefixIcon: Icon(Icons.email_outlined),
hintStyle: TextStyle(
color: Colors.grey,
),
),
),
);
}
}
class PaddingButtomBottom extends StatelessWidget {
const PaddingButtomBottom({
super.key,
required GlobalKey<FormState> formKey,
required this.controller,
}) : _formKey = formKey;
final GlobalKey<FormState> _formKey;
final LoginEmailController controller;
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.only(bottom: 25),
child: Center(
child: PrimaryButton(
onPressed: () {
if (_formKey.currentState!.validate()) {
LoginEmailController.instance
.loginUser(
controller.email.text.trim(),
controller.password.text.trim(),
)
.then((value) {
Provider.of<UserProvider>(context, listen: false)
.initUserProvider();
});
}
},
text: 'Iniciar',
),
),
);
}
}
class RichTxtBottom extends StatelessWidget {
const RichTxtBottom({
super.key,
});
@override
Widget build(BuildContext context) {
return RichText(
text: TextSpan(
style: const TextStyle(
fontSize: 16.0,
color: Color(0xFF65676B),
fontFamily: 'Poppins',
),
children: [
const TextSpan(text: '¿No estás registrado? '),
WidgetSpan(
child: GestureDetector(
onTap: () {
Navigator.pushReplacementNamed(context, '/register');
},
child: const Text(
'Registrarse',
style: TextStyle(
fontSize: 16.0,
color: Color(0xFF2BA4EC),
fontWeight: FontWeight.w600,
),
),
),
),
],
),
);
}
}
File diff suppressed because it is too large Load Diff
-104
View File
@@ -1,104 +0,0 @@
import 'package:community_material_icon/community_material_icon.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:prosappco/src/authentication/authentication_repository.dart';
import 'package:prosappco/src/components/photo_view.dart';
import 'package:prosappco/src/components/pop_appbar.dart';
import 'package:prosappco/src/models/chat_model.dart';
import 'package:prosappco/src/models/event_model.dart';
import 'package:prosappco/src/presentation/screens/chat.dart';
class MessagesScreen extends StatefulWidget {
const MessagesScreen({super.key});
@override
State<MessagesScreen> createState() => _MessagesScreenState();
}
class _MessagesScreenState extends State<MessagesScreen> {
final uid = AuthenticationRepository.instance.getCurrentUserUid();
List<ChatModel> list = [];
@override
void initState() {
super.initState();
ChatModel.getChatsByProId(uid!).then(
(List<ChatModel> s) => setState(() {
list = s;
}),
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: PopAppbar(
onPressed: () {
Navigator.pop(context);
},
label: 'Mensajes'),
body: Column(
children: [
const Padding(
padding: EdgeInsets.all(10),
child: TextField(
// controller: searchController,
decoration: InputDecoration(
hintText: 'Escribe un nombre',
prefixIcon: Icon(CommunityMaterialIcons.stethoscope),
),
),
),
Expanded(
child: ListView.builder(
itemCount: list.length,
itemBuilder: (BuildContext context, int index) {
if (list[index].messages.isNotEmpty) {
final user = list[index].user;
final lastMsg = list[index].messages.last;
return ListTile(
title: Text(
(user?.name ?? ''),
),
subtitle: Text(
'" ${lastMsg.content} "',
style: const TextStyle(fontStyle: FontStyle.italic),
),
leading: ReferencePhoto(
ref: user?.photo,
size: 55,
sizeCircle: 60,
),
trailing: const Column(
children: [
SizedBox(height: 8),
Icon(
Icons.keyboard_arrow_right,
color: Colors.black,
),
],
),
onTap: () {
Event.getEventById(list[index].id).then((value) {
Navigator.push(
context,
CupertinoPageRoute(
builder: (BuildContext context) {
return ChatScreen(eventoId: list[index].id);
},
),
);
});
},
);
} else {
return const SizedBox();
}
},
),
),
],
),
);
}
}
@@ -1,113 +0,0 @@
import 'package:community_material_icon/community_material_icon.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
import 'package:prosappco/src/authentication/authentication_repository.dart';
import 'package:prosappco/src/components/photo_view.dart';
import 'package:prosappco/src/components/pop_appbar.dart';
import 'package:prosappco/src/models/chat_model.dart';
import 'package:prosappco/src/models/event_model.dart';
import 'package:prosappco/src/presentation/screens/chat.dart';
class MessagesUserScreen extends StatefulWidget {
const MessagesUserScreen({super.key});
@override
State<MessagesUserScreen> createState() => _MessagesUserScreenState();
}
class _MessagesUserScreenState extends State<MessagesUserScreen> {
final uid = AuthenticationRepository.instance.getCurrentUserUid();
List<ChatModel> list = [];
@override
void initState() {
super.initState();
ChatModel.getChatsByUserId(uid!).then(
(List<ChatModel> s) => setState(() {
list = s;
}),
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: PopAppbar(
onPressed: () {
Navigator.pop(context);
},
label: 'Mensajes'),
body: Column(
children: [
const Padding(
padding: EdgeInsets.all(10),
child: TextField(
// controller: searchController,
decoration: InputDecoration(
hintText: 'Escribe un nombre',
prefixIcon: Icon(CommunityMaterialIcons.stethoscope),
),
),
),
Expanded(
child: ListView.builder(
itemCount: list.length,
itemBuilder: (BuildContext context, int index) {
if (list[index].messages.isNotEmpty) {
final pro = list[index].professional;
final lastMsg = list[index].messages.last;
return ListTile(
title: Text(
(pro?.name ?? ''),
),
subtitle: Text(
'" ${lastMsg.content} "',
style: const TextStyle(fontStyle: FontStyle.italic),
),
leading: ReferencePhoto(
ref: pro?.photo,
size: 55,
sizeCircle: 60,
),
trailing: Column(
children: [
SizedBox(height: 8),
const Icon(
Icons.keyboard_arrow_right,
color: Colors.black,
),
Text(
lastMsg.timestamp.day >= DateTime.now().day
? DateFormat('h:mm a').format(lastMsg.timestamp)
: DateFormat('dd/MM/yyyy', 'es')
.format(lastMsg.timestamp),
style:
const TextStyle(color: Colors.grey, fontSize: 12),
)
],
),
onTap: () {
Event.getEventById(list[index].id).then((value) {
Navigator.push(
context,
CupertinoPageRoute(
builder: (BuildContext context) {
return ChatScreen(eventoId: list[index].id);
},
),
);
});
},
);
} else {
return const SizedBox();
}
},
),
),
],
),
);
}
}
@@ -1,302 +0,0 @@
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_rating_bar/flutter_rating_bar.dart';
import 'package:intl/intl.dart';
import 'package:prosappco/src/authentication/authentication_repository.dart';
import 'package:prosappco/src/components/drawer_professional.dart';
import 'package:prosappco/src/components/pop_appbar.dart';
import 'package:prosappco/src/models/event_model.dart';
import 'package:prosappco/src/models/scores_model.dart';
import 'package:prosappco/src/presentation/screens/cita.dart';
import 'package:prosappco/src/presentation/screens/score.dart';
class MyServicesScreen extends StatelessWidget {
MyServicesScreen({super.key});
DateTime today = DateTime.now();
final uid = AuthenticationRepository.instance.getCurrentUserUid();
@override
Widget build(BuildContext context) {
return SafeArea(
child: Scaffold(
appBar: PopAppbar(
onPressed: () {
Navigator.pop(context);
},
label: 'Mis servicios'),
drawer: DrawerProfessional(),
body: SingleChildScrollView(
child: Column(
children: [
_eventList(),
],
),
),
),
);
}
Widget _eventList() {
return StreamBuilder<List<Event>>(
stream: FirebaseFirestore.instance
.collection('services')
.where('user_id', isEqualTo: uid)
.where('status', whereIn: [
'aprobado',
'denegado',
'iniciado',
'terminado',
'pendiente'
])
.snapshots()
.asyncMap((snapshot) async {
try {
List<Event> eventos = [];
for (var element in snapshot.docs) {
final event = Event.fromJson(element.data());
event.scoresModel =
await ScoresModel.scoreTo(event.userId, false, false);
event.id = element.id;
eventos.add(event);
}
return eventos;
} catch (e) {
print('Error getByProId $e');
return [];
}
}),
builder: (BuildContext context, AsyncSnapshot<List<Event>> snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return const Center(
child: CircularProgressIndicator(),
);
}
List<Event> eventos = [];
try {
eventos.addAll(snapshot.data!);
eventos.sort((a, b) => a.timeStamp!.compareTo(b.timeStamp!));
print('snapshot mi b ${eventos}');
} catch (e) {
print("Error snapshot" + e.toString());
}
if (eventos.isEmpty) {
return const Padding(
padding: EdgeInsets.symmetric(vertical: 50),
child: Center(child: Text('No tienes citas')),
);
}
return Column(
children: [
...eventos
.where((event) => event.professionalId != event.userId)
.map(
(event) => FutureBuilder<DocumentSnapshot>(
future: FirebaseFirestore.instance
.collection('users')
.doc(event.professionalId)
.get(),
builder: (BuildContext context,
AsyncSnapshot<DocumentSnapshot> profSnapshot) {
if (profSnapshot.connectionState ==
ConnectionState.waiting) {
return const CircularProgressIndicator();
}
if (profSnapshot.hasError) {
return const Text(
'Error al obtener los datos del profesional',
);
}
final professionalData = profSnapshot.data;
final professionalName =
professionalData?['name'] ?? 'N/D';
return ListTile(
tileColor: event.status == 'denegado'
? Colors.red[100]
: Colors.blue[100],
onTap: () {
if (event.status == 'terminado') {
if (event.userId == uid) {
if (event.userScored) {
Navigator.push(
context,
CupertinoPageRoute(
builder: (BuildContext context) {
return CitaScreen(evento: event);
},
),
);
} else {
Navigator.push(
context,
CupertinoPageRoute(
builder: (BuildContext context) {
return ScoreScreen(
evento: event, pro: false);
},
),
);
}
}
} else {
Navigator.push(
context,
CupertinoPageRoute(
builder: (BuildContext context) {
return CitaScreen(evento: event);
},
),
);
}
},
leading: event.status == 'aprobado'
? const Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
Icons.check,
color: Colors.blue,
size: 30,
),
Text('Aceptado',
style: TextStyle(fontSize: 12)),
],
)
: event.status == 'iniciado'
? const Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
Icons.access_time,
color: Colors.blue,
size: 30,
),
Text('Iniciado',
style: TextStyle(fontSize: 12)),
],
)
: event.status == 'pendiente'
? const Column(
mainAxisAlignment:
MainAxisAlignment.center,
children: [
Icon(
Icons.access_time_outlined,
color: Colors.blue,
size: 30,
),
Text('Pendiente',
style: TextStyle(fontSize: 12)),
],
)
: event.status == 'terminado'
? const Column(
mainAxisAlignment:
MainAxisAlignment.center,
children: [
Icon(
Icons.rocket_launch,
color: Colors.blue,
size: 30,
),
Text('Finalizado',
style:
TextStyle(fontSize: 12)),
],
)
: const Column(
mainAxisAlignment:
MainAxisAlignment.center,
children: [
Icon(
Icons.close,
color: Colors.red,
size: 30,
),
Text('Cancelado',
style:
TextStyle(fontSize: 12)),
],
),
title: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'$professionalName',
style: const TextStyle(
color: Colors.black,
fontWeight: FontWeight.bold,
fontSize: 16,
),
),
Text(
'${DateFormat('dd MMMM', 'es').format(DateTime.parse(event.day))} - ${DateFormat('h:mm a').format(DateTime.parse(event.range1Hour1))}',
style: TextStyle(
color: Colors.grey[600],
fontSize: 16,
),
),
],
),
subtitle: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
RatingBar.builder(
initialRating:
event.scoresModel?.average ?? 0,
minRating: 1,
direction: Axis.horizontal,
allowHalfRating: true,
itemCount: 5,
itemSize: 25,
maxRating: 5,
itemPadding:
const EdgeInsets.symmetric(horizontal: 0),
itemBuilder: (context, _) => const Icon(
Icons.star,
color: Color(0xFF2BA4EC),
),
onRatingUpdate: (rating) {},
ignoreGestures: true,
),
const SizedBox(width: 5),
Text(
'(${event.scoresModel?.total.toString()}) ${event.scoresModel?.average.toStringAsFixed(1)}'),
],
),
Text(
'"${event.description}"',
style:
const TextStyle(fontStyle: FontStyle.italic),
),
],
),
trailing: const Column(
mainAxisAlignment: MainAxisAlignment.end,
children: [
Icon(Icons.keyboard_arrow_right),
],
),
);
},
),
),
],
);
},
);
}
}
@@ -1,250 +0,0 @@
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_rating_bar/flutter_rating_bar.dart';
import 'package:intl/intl.dart';
import 'package:prosappco/src/authentication/authentication_repository.dart';
import 'package:prosappco/src/components/drawer_professional.dart';
import 'package:prosappco/src/components/pop_appbar.dart';
import 'package:prosappco/src/models/event_model.dart';
import 'package:prosappco/src/models/scores_model.dart';
import 'package:prosappco/src/presentation/screens/cita.dart';
import 'package:prosappco/src/presentation/screens/score.dart';
class MyServicesProScreen extends StatelessWidget {
MyServicesProScreen({super.key});
DateTime today = DateTime.now();
final uid = AuthenticationRepository.instance.getCurrentUserUid();
@override
Widget build(BuildContext context) {
return SafeArea(
child: Scaffold(
appBar: PopAppbar(
onPressed: () {
Navigator.pop(context);
},
label: 'Mis servicios',
),
drawer: DrawerProfessional(),
body: SingleChildScrollView(
child: Column(
children: [
_eventList(),
],
),
),
),
);
}
Widget _eventList() {
return StreamBuilder<List<Event>>(
stream: FirebaseFirestore.instance
.collection('services')
.where('professional_id', isEqualTo: uid)
.where('status', whereIn: [
'aprobado',
'denegado',
'iniciado',
'terminado',
])
.snapshots()
.asyncMap((snapshot) async {
try {
List<Event> eventos = [];
for (var element in snapshot.docs) {
final event = Event.fromJson(element.data());
event.scoresModel =
await ScoresModel.scoreTo(event.userId, false, false);
event.id = element.id;
eventos.add(event);
}
return eventos;
} catch (e) {
print('Error getByProId $e');
return [];
}
}),
builder: (BuildContext context, AsyncSnapshot<List<Event>> snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return const Center(
child: CircularProgressIndicator(),
);
}
List<Event> eventos = [];
try {
eventos.addAll(snapshot.data!);
eventos.sort((a, b) => a.timeStamp!.compareTo(b.timeStamp!));
} catch (e) {
print("Error" + e.toString());
}
if (eventos.isEmpty) {
return const Padding(
padding: EdgeInsets.symmetric(vertical: 50),
child: Center(child: Text('No tienes citas')),
);
}
return Column(
children: [
...eventos.map(
(event) => ListTile(
tileColor: event.status == 'denegado'
? Colors.red[100]
: Colors.blue[100],
onTap: () {
if (event.status == 'terminado') {
if (event.professionalId == uid) {
if (event.professionalScored) {
Navigator.push(
context,
CupertinoPageRoute(
builder: (BuildContext context) {
return CitaScreen(evento: event);
},
),
);
} else {
Navigator.push(
context,
CupertinoPageRoute(
builder: (BuildContext context) {
return ScoreScreen(evento: event, pro: true);
},
),
);
}
}
} else {
Navigator.push(
context,
CupertinoPageRoute(
builder: (BuildContext context) {
return CitaScreen(evento: event);
},
),
);
}
},
leading: event.status == 'aprobado'
? const Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
Icons.check,
color: Colors.blue,
size: 30,
),
Text('Aceptado', style: TextStyle(fontSize: 12)),
],
)
: event.status == 'iniciado'
? const Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
Icons.access_time,
color: Colors.blue,
size: 30,
),
Text('Iniciado', style: TextStyle(fontSize: 12)),
],
)
: event.status == 'terminado'
? const Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
Icons.rocket_launch,
color: Colors.blue,
size: 30,
),
Text('Finalizado',
style: TextStyle(fontSize: 12)),
],
)
: const Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
Icons.close,
color: Colors.red,
size: 30,
),
Text('Cancelado',
style: TextStyle(fontSize: 12)),
],
),
title: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
event.title,
style: const TextStyle(
color: Colors.black,
fontWeight: FontWeight.bold,
fontSize: 16,
),
),
Text(
'${DateFormat('dd MMMM', 'es').format(DateTime.parse(event.day))} - ${DateFormat('h:mm a').format(DateTime.parse(event.range1Hour1))}',
style: TextStyle(
color: Colors.grey[600],
fontSize: 16,
),
),
],
),
subtitle: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
RatingBar.builder(
initialRating: event.scoresModel?.average ?? 0,
minRating: 1,
direction: Axis.horizontal,
allowHalfRating: true,
itemCount: 5,
itemSize: 25,
maxRating: 5,
itemPadding:
const EdgeInsets.symmetric(horizontal: 0),
itemBuilder: (context, _) => const Icon(
Icons.star,
color: Color(0xFF2BA4EC),
),
onRatingUpdate: (rating) {},
ignoreGestures: true,
),
const SizedBox(width: 5),
Text(
'(${event.scoresModel?.total.toString()}) ${event.scoresModel?.average.toStringAsFixed(1)}'),
],
),
Text(
'"${event.description}"',
style: const TextStyle(fontStyle: FontStyle.italic),
),
],
),
trailing: const Column(
mainAxisAlignment: MainAxisAlignment.end,
children: [
Icon(Icons.keyboard_arrow_right),
],
),
),
),
],
);
},
);
}
}
@@ -1,275 +0,0 @@
import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:intl_phone_field/intl_phone_field.dart';
import 'package:prosappco/src/components/pop_appbar.dart';
import 'package:prosappco/src/components/primary_btn.dart';
import 'package:prosappco/src/controllers/new_phone_controller.dart';
class NewNumberScreen extends StatefulWidget {
const NewNumberScreen({super.key});
@override
State<NewNumberScreen> createState() => _NewNumberScreenState();
}
// Actualizar número de teléfono en Firebase
Future<void> updatePhoneNumber(String verificationId, String smsCode) async {
try {
PhoneAuthCredential credential = PhoneAuthProvider.credential(
verificationId: verificationId, smsCode: smsCode);
await FirebaseAuth.instance.currentUser!.updatePhoneNumber(credential);
print("Phone number updated successfully");
} catch (e) {
print("Error updating phone number: $e");
}
}
class _NewNumberScreenState extends State<NewNumberScreen> {
final controller = Get.put(NewPhoneController());
String completePhoneNumber = '';
final _formKey = GlobalKey<FormState>();
@override
Widget build(BuildContext context) {
return SafeArea(
child: Scaffold(
resizeToAvoidBottomInset: false,
appBar: PopAppbar(
onPressed: () {
_formKey.currentState!.reset();
Navigator.pop(context);
},
label: 'Añadir numero',
),
body: !kIsWeb
? Container(
padding:
const EdgeInsets.symmetric(horizontal: 0, vertical: 20),
margin: const EdgeInsets.only(top: 30, left: 50, right: 50),
child: Column(
children: [
const Padding(
padding: EdgeInsets.only(bottom: 5),
child: Align(
alignment: Alignment.topLeft,
child: Text('Numero de celular',
style: TextStyle(
fontSize: 18.0, color: Color(0xFF65676B))),
)),
Form(
key: _formKey,
child: Padding(
padding: const EdgeInsets.only(bottom: 5),
child: IntlPhoneField(
controller: controller.newPhoneNo,
initialCountryCode: 'CO',
onChanged: (newPhoneNo) {
completePhoneNumber = newPhoneNo.completeNumber;
},
decoration: const InputDecoration(
border: OutlineInputBorder(
borderSide:
BorderSide(color: Color(0xFFECECEC)),
borderRadius: BorderRadius.all(
Radius.circular(50),
)),
errorBorder: OutlineInputBorder(
borderSide: BorderSide(
color: Color.fromARGB(255, 184, 0, 0)),
borderRadius: BorderRadius.all(
Radius.circular(50),
)),
enabledBorder: OutlineInputBorder(
borderSide:
BorderSide(color: Color(0xFFECECEC)),
borderRadius: BorderRadius.all(
Radius.circular(50),
)),
focusedBorder: OutlineInputBorder(
borderSide:
BorderSide(color: Color(0xFFECECEC)),
borderRadius: BorderRadius.all(
Radius.circular(50),
)),
fillColor: Color.fromARGB(255, 239, 239, 239),
filled: true,
),
),
),
),
const Padding(
padding: EdgeInsets.only(bottom: 30),
child: Text(
'Se enviará un código a este número de celular',
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 13.0, color: Color(0xFF65676B))),
),
Container(
margin: const EdgeInsets.only(top: 10, bottom: 30),
padding: const EdgeInsets.symmetric(
horizontal: 20, vertical: 15),
decoration: BoxDecoration(
color: const Color(0xFFD6F4FF),
borderRadius: BorderRadius.circular(20),
boxShadow: [
BoxShadow(
color: Colors.grey.withOpacity(0.5),
spreadRadius: 1,
blurRadius: 5,
offset: const Offset(1, 3),
),
],
),
child: const Row(
children: [
Icon(
Icons.error_outline,
size: 27,
color: Colors.black54,
),
SizedBox(width: 15),
Expanded(
child: Text(
'¡Al actualizar tu número, se cerrará la sesión para confirmar que eres tú!.',
style:
TextStyle(color: Colors.black, fontSize: 14),
),
),
],
),
),
Padding(
padding: const EdgeInsets.only(bottom: 30),
child: Center(
child: PrimaryButtom(
onPressed: () {
controller.updatePhoneNumber(
completePhoneNumber.toString());
},
label: 'Enviar código'),
),
),
],
),
)
: Center(
child: Container(
padding:
const EdgeInsets.symmetric(horizontal: 0, vertical: 20),
width: 400,
margin: const EdgeInsets.only(top: 30, left: 50, right: 50),
child: Column(
children: [
const Padding(
padding: EdgeInsets.only(bottom: 5),
child: Align(
alignment: Alignment.topLeft,
child: Text('Numero de celular',
style: TextStyle(
fontSize: 18.0, color: Color(0xFF65676B))),
)),
Form(
key: _formKey,
child: Padding(
padding: const EdgeInsets.only(bottom: 5),
child: IntlPhoneField(
controller: controller.newPhoneNo,
initialCountryCode: 'CO',
onChanged: (newPhoneNo) {
completePhoneNumber = newPhoneNo.completeNumber;
},
decoration: const InputDecoration(
border: OutlineInputBorder(
borderSide:
BorderSide(color: Color(0xFFECECEC)),
borderRadius: BorderRadius.all(
Radius.circular(50),
)),
errorBorder: OutlineInputBorder(
borderSide: BorderSide(
color: Color.fromARGB(255, 184, 0, 0)),
borderRadius: BorderRadius.all(
Radius.circular(50),
)),
enabledBorder: OutlineInputBorder(
borderSide:
BorderSide(color: Color(0xFFECECEC)),
borderRadius: BorderRadius.all(
Radius.circular(50),
)),
focusedBorder: OutlineInputBorder(
borderSide:
BorderSide(color: Color(0xFFECECEC)),
borderRadius: BorderRadius.all(
Radius.circular(50),
)),
fillColor: Color.fromARGB(255, 239, 239, 239),
filled: true,
),
),
),
),
const Padding(
padding: EdgeInsets.only(bottom: 30),
child: Text(
'Se enviará un código a este número de celular',
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 13.0, color: Color(0xFF65676B))),
),
Container(
margin: const EdgeInsets.only(top: 10, bottom: 30),
padding: const EdgeInsets.symmetric(
horizontal: 20, vertical: 15),
decoration: BoxDecoration(
color: const Color(0xFFD6F4FF),
borderRadius: BorderRadius.circular(20),
boxShadow: [
BoxShadow(
color: Colors.grey.withOpacity(0.5),
spreadRadius: 1,
blurRadius: 5,
offset: const Offset(1, 3),
),
],
),
child: const Row(
children: [
Icon(
Icons.error_outline,
size: 27,
color: Colors.black54,
),
SizedBox(width: 15),
Expanded(
child: Text(
'¡Al actualizar tu número, se cerrará la sesión para confirmar que eres tú!.',
style: TextStyle(
color: Colors.black, fontSize: 14),
),
),
],
),
),
Padding(
padding: const EdgeInsets.only(bottom: 30),
child: Center(
child: PrimaryButtom(
onPressed: () {
controller.updatePhoneNumber(
completePhoneNumber.toString());
},
label: 'Enviar código'),
),
),
],
),
),
),
),
);
}
}
@@ -1,153 +0,0 @@
import 'package:flutter/material.dart';
import 'package:flutter_otp_text_field/flutter_otp_text_field.dart';
import 'package:prosappco/src/controllers/otp_controller.dart';
class NewNumberValidationScreen extends StatefulWidget {
const NewNumberValidationScreen({super.key});
@override
State<NewNumberValidationScreen> createState() =>
_NewNumberValidationScreenState();
}
class _NewNumberValidationScreenState extends State<NewNumberValidationScreen> {
dynamic otp;
@override
Widget build(BuildContext context) {
return SafeArea(
child: Scaffold(
resizeToAvoidBottomInset: false,
backgroundColor: const Color(0xFFD6F4FF),
body: Stack(
children: [
Container(
margin: const EdgeInsets.only(top: 280),
width: double.infinity,
height: 600,
decoration: const BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.only(
topRight: Radius.circular(50),
topLeft: Radius.circular(50))),
),
Container(
margin: const EdgeInsets.only(top: 120, left: 70, right: 70),
child: const Image(image: AssetImage('images/logo_prosapp.png')),
),
Container(
padding: const EdgeInsets.symmetric(horizontal: 0, vertical: 20),
margin: const EdgeInsets.only(top: 280, left: 25),
child: Row(
children: <Widget>[
IconButton(
icon: const Icon(
Icons.arrow_back,
size: 30,
),
onPressed: () {
Navigator.pop(context);
},
),
const Text(
'Valida el código',
style: TextStyle(
color: Color(0xFF262626),
fontSize: 30.0,
fontWeight: FontWeight.bold,
),
textAlign: TextAlign.right,
),
],
),
),
Container(
padding: const EdgeInsets.symmetric(horizontal: 0, vertical: 20),
margin: const EdgeInsets.only(top: 350, left: 50, right: 50),
child: Column(children: [
const Padding(
padding: EdgeInsets.only(bottom: 5),
child: Align(
alignment: Alignment.topLeft,
child: Text('Numero de celular',
style: TextStyle(
fontSize: 18.0, color: Color(0xFF65676B))),
)),
Padding(
padding: const EdgeInsets.only(bottom: 20),
child: Row(
children: [
const Expanded(
child: TextField(
decoration: InputDecoration(
border: InputBorder.none,
hintText: '+57',
suffixIcon: Icon(Icons.edit),
),
),
),
TextButton(
onPressed: () {
// Acción a realizar cuando se hace clic en el texto
},
child: const Text('Reenviar código'),
),
],
),
),
const Align(
alignment: Alignment.topLeft,
child: Padding(
padding: EdgeInsets.only(bottom: 5),
child: Text('Código',
textAlign: TextAlign.left,
style:
TextStyle(fontSize: 18.0, color: Color(0xFF65676B))),
),
),
Padding(
padding: const EdgeInsets.only(bottom: 20),
child: OtpTextField(
numberOfFields: 6,
focusedBorderColor: Colors.blue,
fillColor: Colors.black.withOpacity(0.1),
filled: true,
keyboardType: TextInputType.number,
onSubmit: (code) {
otp = code;
OTPController.instance.verifyOTP(otp);
},
),
),
Padding(
padding: const EdgeInsets.only(bottom: 40),
child: Center(
child: ElevatedButton(
onPressed: () {
OTPController.instance.verifyOTP(otp);
},
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFF2BA4EC), // Color del botón
shape: RoundedRectangleBorder(
borderRadius:
BorderRadius.circular(50), // Bordes redondeados
),
elevation: 0,
minimumSize: const Size(230, 60), // Tamaño mínimo del botón
),
child: const Text(
'Valida el código',
style: TextStyle(
color: Colors.white,
fontWeight: FontWeight.bold,
fontSize: 18,
),
),
)),
),
]),
),
],
),
));
}
}
@@ -1,160 +0,0 @@
import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:prosappco/src/components/pop_appbar.dart';
import 'package:prosappco/src/components/primary_btn.dart';
class NewPasswordScreen extends StatefulWidget {
NewPasswordScreen({super.key});
@override
State<NewPasswordScreen> createState() => _NewPasswordScreenState();
}
class _NewPasswordScreenState extends State<NewPasswordScreen> {
final _currentPasswordController = TextEditingController();
final _newPasswordController = TextEditingController();
bool _obscureText = true;
bool _obscureText2 = true;
final FirebaseAuth _auth = FirebaseAuth.instance;
Future<void> updatePassword(
String currentPassword, String newPassword) async {
final User user = _auth.currentUser!;
final credential = EmailAuthProvider.credential(
email: user.email!,
password: currentPassword,
);
try {
if (newPassword == currentPassword) {
Get.snackbar(
'Misma contraseña',
'La nueva contraseña debe ser distinta a la contraseña actual.',
snackPosition: SnackPosition.BOTTOM,
);
return;
} else if (newPassword.isEmpty) {
Get.snackbar(
'Ingrese una contraseña valida',
'La nueva contraseña no puede estar vacia.',
snackPosition: SnackPosition.BOTTOM,
);
return;
} else {
await user.reauthenticateWithCredential(credential);
try {
await user.updatePassword(newPassword);
// Muestra un mensaje de éxito
Get.snackbar(
'Contraseña actualizada',
'Tu contraseña ha sido cambiada con éxito.',
snackPosition: SnackPosition.BOTTOM,
);
} catch (e) {
print("Error al verificar la contraseña actual: $e");
}
}
} catch (e) {
Get.snackbar(
'Contraseña incorrecta',
'Ha ocurrido un error al actualizar la contraseña. Asegúrate de ingresar correctamente la contraseña actual.',
snackPosition: SnackPosition.BOTTOM,
);
return;
}
}
@override
void initState() {
super.initState();
}
@override
Widget build(BuildContext context) {
return SafeArea(
child: Scaffold(
appBar: PopAppbar(
onPressed: () {
Navigator.pop(context);
},
label: ' Cambia tu contraseña',
),
body: Center(
child: SizedBox(
width: 300,
child: Padding(
padding: const EdgeInsets.only(top: 20),
child: Column(
children: [
const Text(
"Ten en cuenta que al cambiar tu contraseña, se cerrará automáticamente tu sesión.",
textAlign: TextAlign.center,
style: TextStyle(color: Colors.grey),
),
const SizedBox(height: 40),
TextFormField(
controller: _currentPasswordController,
obscureText: _obscureText,
decoration: InputDecoration(
prefixIcon: const Icon(Icons.lock_outline),
suffixIcon: IconButton(
icon: Icon(
_obscureText
? Icons.visibility
: Icons.visibility_off,
color: Colors.grey,
),
onPressed: () {
setState(() {
_obscureText = !_obscureText;
});
},
),
hintText: 'Contraseña (Actual)'),
),
const SizedBox(height: 40),
TextFormField(
controller: _newPasswordController,
obscureText: _obscureText2,
decoration: InputDecoration(
prefixIcon: const Icon(Icons.lock_outline),
suffixIcon: IconButton(
icon: Icon(
_obscureText2
? Icons.visibility
: Icons.visibility_off,
color: Colors.grey,
),
onPressed: () {
setState(() {
_obscureText2 = !_obscureText2;
});
},
),
hintText: 'Contraseña (Nueva)'),
),
const SizedBox(height: 80),
PrimaryButtom(
onPressed: () {
updatePassword(_currentPasswordController.text.trim(),
_newPasswordController.text.trim());
},
label: 'Actualizar contraseña'),
const SizedBox(height: 30),
const Text(
"Esta contraseña es valida si el inicio de sesión es por email.",
textAlign: TextAlign.center,
style: TextStyle(color: Colors.grey),
),
],
),
),
),
),
),
);
}
}
@@ -1,248 +0,0 @@
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:diacritic/diacritic.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter/material.dart';
import 'package:prosappco/src/authentication/authentication_repository.dart';
import 'package:prosappco/src/components/pop_appbar.dart';
final CollectionReference professionsCollection =
FirebaseFirestore.instance.collection('professions');
Future<List<String>> getProfessions() async {
try {
DocumentSnapshot<Object?> profession =
await professionsCollection.doc('professions').get();
Map<String, dynamic> data = profession.data() as Map<String, dynamic>;
var professionsList = (data['professions'] as List<dynamic>)
.map((e) => e.toString())
.toList();
return professionsList;
} catch (e) {
print('$e');
}
return [];
}
class ProfessionScreen extends StatefulWidget {
const ProfessionScreen({super.key});
@override
State<ProfessionScreen> createState() => _ProfessionScreenState();
}
class _ProfessionScreenState extends State<ProfessionScreen> {
List<String>? filteredProfessions;
TextEditingController searchController = TextEditingController();
final User? user = FirebaseAuth.instance.currentUser;
List<String>? _professions;
final ScrollController _scrollController = ScrollController();
final uid = AuthenticationRepository.instance.getCurrentUserUid();
bool isNewProfessionAdded = false;
@override
void initState() {
super.initState();
searchController.addListener(() {
setState(() {
if (_professions != null) {
if (searchController.text.isEmpty) {
filteredProfessions = _professions!;
} else {
filteredProfessions = _professions!
.where((profession) => removeDiacritics(profession)
.toLowerCase()
.contains(
removeDiacritics(searchController.text.toLowerCase())))
.toList();
}
}
});
});
if (_professions == null) {
getProfessions().then((List<String> element) => setState(() {
_professions = element;
filteredProfessions = element;
}));
}
}
Future<void> updateProfession(String profession) async {
try {
await FirebaseFirestore.instance
.collection('users')
.doc(uid)
.update({'profesion': profession});
} catch (e) {
try {
await FirebaseFirestore.instance
.collection('users')
.doc(uid)
.set({'profesion': profession});
} catch (e) {
print('Error al agregar la profesion: $e');
}
print('Error al actualizar la profesion: $e');
}
}
Future<void> saveProfession(String newProfession) async {
final DocumentReference professionsDocRef =
professionsCollection.doc('professions');
try {
final DocumentSnapshot<Object?> profession =
await professionsDocRef.get();
Map<String, dynamic> data = profession.data() as Map<String, dynamic>;
List<String> professions = [];
if (data['professions'] != null) {
professions = List<String>.from(data['professions']);
}
professions.add(newProfession);
await professionsDocRef.set({
'professions': professions,
}, SetOptions(merge: true));
setState(() {
getProfessions().then((List<String> element) => setState(() {
_professions = element;
filteredProfessions = element;
int newIndex = professions.indexOf(newProfession);
if (newIndex != -1) {
_scrollController.animateTo(
newIndex * 50.0,
duration: const Duration(milliseconds: 600),
curve: Curves.easeIn,
);
}
isNewProfessionAdded = true;
Future.delayed(const Duration(seconds: 2), () {
setState(() {
isNewProfessionAdded = false;
});
});
}));
});
} catch (e) {
print('Error al guardar la profesión: $e');
}
}
@override
Widget build(BuildContext context) {
if (filteredProfessions == null) {
return const Center(
child: CircularProgressIndicator(
valueColor: AlwaysStoppedAnimation<Color>(Color(0xFF2BA4EC)),
),
);
}
var professions = filteredProfessions!;
return SafeArea(
child: Scaffold(
appBar: PopAppbar(
onPressed: () {
Navigator.pop(context);
},
label: 'Seleccione su profesión',
),
body: Column(
children: [
GestureDetector(
onTap: () {
String newProfession = "";
showDialog(
context: context,
builder: (context) {
return AlertDialog(
title: const Text("Agregar una profesión"),
content: TextField(
controller: TextEditingController(),
onChanged: (value) {
newProfession = value;
},
),
actions: [
ElevatedButton(
onPressed: () {
if (newProfession.isNotEmpty) {
saveProfession(newProfession);
Navigator.pop(context);
}
},
child: const Text("Guardar"),
),
],
);
},
);
},
child: Container(
padding: const EdgeInsets.all(10),
child: const Text(
'Si no vez tu profesion, presiona aquí',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
color: Colors.blue,
),
textAlign: TextAlign.center,
),
),
),
Padding(
padding: const EdgeInsets.only(left: 10, right: 10, top: 0),
child: TextField(
controller: searchController,
decoration: const InputDecoration(
hintText: 'Busca tu profesión',
prefixIcon: Icon(Icons.assignment_ind_rounded),
),
),
),
Expanded(
child: ListView.builder(
controller: _scrollController,
itemCount: professions.length,
itemBuilder: (BuildContext context, int index) {
return ListTile(
title: Text(
professions[index],
style: TextStyle(
fontSize: 18.0,
color: isNewProfessionAdded &&
index == professions.length - 1
? Colors.white
: Colors.black,
),
),
tileColor:
isNewProfessionAdded && index == professions.length - 1
? Colors.blue
: null,
onTap: () {
updateProfession(professions[index]);
Navigator.pop(context, professions[index]);
},
);
},
),
),
],
),
),
);
}
}
@@ -1,445 +0,0 @@
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:diacritic/diacritic.dart';
import 'package:firebase_storage/firebase_storage.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
import 'package:prosappco/src/authentication/authentication_repository.dart';
import 'package:prosappco/src/components/photo_view.dart';
import 'package:prosappco/src/components/pop_appbar.dart';
import 'package:prosappco/src/models/professional_model.dart';
import 'package:prosappco/src/models/setting_model.dart';
import 'package:prosappco/src/models/user_model.dart';
import 'package:prosappco/src/presentation/screens/calendar_pro.dart';
import 'package:prosappco/src/presentation/screens/professional_info.dart';
import 'package:prosappco/src/presentation/widgets/shared/loading_item_list.dart';
import '../../models/scores_model.dart';
class ProfessionalScreen extends StatefulWidget {
final String profession;
const ProfessionalScreen({
super.key,
required this.profession,
});
@override
State<ProfessionalScreen> createState() => _ProfessionalScreenState();
}
var _photo = '.../images/perfil-2.png';
final FirebaseStorage storage = FirebaseStorage.instance;
final CollectionReference usersCollection =
FirebaseFirestore.instance.collection('users');
class _ProfessionalScreenState extends State<ProfessionalScreen> {
UserModel? userme;
SettingModel? settings;
@override
void initState() {
super.initState();
if (userme == null) {
UserModel.getUser(uid.toString()).then(
(UserModel s) => setState(() => userme = s),
);
}
if (settings == null) {
SettingModel.getSettings().then(
(SettingModel value) => setState(() {
settings = value;
print('initState settings: $settings');
}),
);
}
searchController.addListener(() {
setState(() {
if (_professionals != null) {
if (searchController.text.isEmpty) {
filteredProfessionals = _professionals!
.where((professional) => professional.id != uid)
.toList();
} else {
filteredProfessionals = _professionals!
.where((professional) =>
removeDiacritics(professional.name).toLowerCase().contains(
removeDiacritics(
searchController.text.toLowerCase())) &&
professional.id != uid)
.toList();
}
}
});
});
if (_professionals == null) {
getProfessionals().then((List<Professional> element) => setState(() {
_professionals = element;
filteredProfessionals = element;
}));
}
}
var photo = '.../images/perfil-2.png';
String formatCurrency(int number) {
final formatter =
NumberFormat.currency(locale: 'es_CO', decimalDigits: 0, symbol: '');
return '\$${formatter.format(number)}';
}
Future<String?> _showChoiceDialog(BuildContext context) async {
String? selectedOption = await showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
content: SingleChildScrollView(
child: ListBody(
children: [
GestureDetector(
child: const Text(
textAlign: TextAlign.center,
"A domicilio",
style: TextStyle(color: Color(0xFF2BA4EC)),
),
onTap: () {
Navigator.of(context).pop("domicilio");
},
),
const Divider(color: Colors.black54),
GestureDetector(
child: const Text(
textAlign: TextAlign.center,
"En sitio",
style: TextStyle(color: Color(0xFF2BA4EC)),
),
onTap: () {
Navigator.of(context).pop("sitio");
},
),
],
),
),
);
},
);
return selectedOption;
}
List<Professional>? filteredProfessionals;
TextEditingController searchController = TextEditingController();
final uid = AuthenticationRepository.instance.getCurrentUserUid();
List<Professional>? _professionals;
Future<List<Professional>> getProfessionals() async {
List<Professional> professionals = [];
try {
QuerySnapshot users = await usersCollection.get();
for (DocumentSnapshot user in users.docs) {
Map<String, dynamic> data = user.data() as Map<String, dynamic>;
_photo = await AuthenticationRepository.instance.getPhoto(user.id);
if (data['estado'] == 'activo') {
if (user.id != uid) {
List<String> especializaciones;
especializaciones = (data['especializaciones'] as List<dynamic>)
.map((e) => e.toString())
.toList();
if (settings?.domicilios == false) {
if (data['ubicacion'] == 'ambos' ||
data['ubicacion'] == 'sitio') {
if (widget.profession == data['profesion'] ||
(widget.profession == '' &&
userme?.city == data['city'] &&
data['ubicacion'] != null)) {
Professional professional = Professional(
id: user.id,
name: data['name'],
professionName: data['profesion'],
cityName: data['city'],
professionalRef: storage.ref().child(_photo),
professionalEspecializado: especializaciones,
ubicacion: data['ubicacion'] ?? '',
realAddress: data['address'] ?? '',
latitude: data['latitude'] ?? 0,
longitude: data['longitude'] ?? 0,
scores: await ScoresModel.scoreTo(user.id, true, true),
tarifa: data['tarifas'] ?? 0,
token: data['token'] ?? '',
);
professionals.add(professional);
}
}
} else {
if (widget.profession == data['profesion'] ||
(widget.profession == '' &&
userme?.city == data['city'] &&
data['ubicacion'] != null)) {
Professional professional = Professional(
id: user.id,
name: data['name'],
professionName: data['profesion'],
cityName: data['city'],
professionalRef: storage.ref().child(_photo),
professionalEspecializado: especializaciones,
ubicacion: data['ubicacion'] ?? '',
realAddress: data['address'] ?? '',
latitude: data['latitude'] ?? 0,
longitude: data['longitude'] ?? 0,
scores: await ScoresModel.scoreTo(user.id, true, true),
tarifa: data['tarifas'] ?? 0,
token: data['token'] ?? '',
);
professionals.add(professional);
}
}
}
}
}
} catch (e) {
print('Error al obtener profesionales: $e');
}
return professionals;
}
@override
Widget build(BuildContext context) {
if (filteredProfessionals == null) {
return SafeArea(
child: Scaffold(
appBar: PopAppbar(
onPressed: () {
Navigator.pop(context);
},
label: 'Seleccione un profesional',
),
body: Column(
children: [
const Padding(
padding: EdgeInsets.all(10),
child: TextField(
readOnly: true,
decoration: InputDecoration(
hintText: 'Escriba un nombre',
prefixIcon: Icon(Icons.assignment_ind_rounded),
),
),
),
Expanded(
child: ListView.builder(
itemCount: 8,
itemBuilder: (BuildContext context, int index) {
return const LoadingItemList(useCircleAvatar: true);
},
),
),
],
),
),
);
}
if (_photo == null || _photo.isEmpty) {
_photo = '.../images/perfil-2.png';
}
var professionals = filteredProfessionals!;
return SafeArea(
child: Scaffold(
appBar: PopAppbar(
onPressed: () {
Navigator.pop(context);
},
label: 'Seleccione un profesional'),
body: Column(
children: [
Padding(
padding: const EdgeInsets.all(10),
child: TextField(
controller: searchController,
decoration: const InputDecoration(
hintText: 'Escriba un nombre',
prefixIcon: Icon(Icons.assignment_ind_rounded),
),
),
),
professionals.isEmpty
? Expanded(
child: Padding(
padding:
const EdgeInsets.only(left: 20, right: 20, top: 50),
child:
Text('Aún no tenemos ningun(a) ${widget.profession}'),
))
: Expanded(
child: ListView.builder(
itemCount: professionals.length,
itemBuilder: (BuildContext context, int index) {
return ListTile(
leading: GestureDetector(
onTap: () {
Navigator.of(context).push(
CupertinoPageRoute(
builder: (BuildContext context) {
return ProfessionalInfoScreen(
professional: professionals[index],
);
},
),
);
},
child: ReferencePhoto(
ref: professionals[index].professionalRef,
sizeCircle: 50,
size: 50,
sizeIcon: 35,
),
),
trailing: GestureDetector(
child: const Icon(Icons.keyboard_arrow_right),
onTap: () {
Navigator.of(context).push(
CupertinoPageRoute(
builder: (BuildContext context) {
return ProfessionalInfoScreen(
professional: professionals[index],
);
},
),
);
},
),
title: RichText(
text: TextSpan(
style: const TextStyle(
fontSize: 15.0,
color: Colors.black,
),
children: <TextSpan>[
TextSpan(
text: '${professionals[index].name}, ',
style: const TextStyle(
fontWeight: FontWeight.bold),
),
TextSpan(
text:
"${professionals[index].professionName}, ${professionals[index].cityName}",
style: TextStyle(color: Colors.grey[600]),
),
],
),
),
subtitle: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
settings?.tarifas == true
? professionals[index].tarifa == 0
? const SizedBox()
: Container(
padding: const EdgeInsets.symmetric(
horizontal: 10, vertical: 5),
decoration: BoxDecoration(
borderRadius:
BorderRadius.circular(20.0),
color: const Color(0xFFD6F4FF),
),
child: Text(
formatCurrency(
professionals[index].tarifa ??
0),
style: TextStyle(
color: Colors.grey[850],
fontWeight: FontWeight.w600,
),
),
)
: const SizedBox(),
professionals[index].ubicacion == 'ambos' &&
settings?.domicilios == true
? const Text(
'Disponibilidad a domicilio y en sitio',
style: TextStyle(
color: Colors.blue,
),
)
: professionals[index].ubicacion == 'sitio' ||
settings?.domicilios == false
? const Text(
'Disponibilidad en sitio',
style: TextStyle(
color: Colors.blue,
),
)
: const Text(
'Disponibilidad a domicilio',
style: TextStyle(
color: Colors.blue,
),
),
],
),
onTap: () async {
if (professionals[index].ubicacion == 'ambos' &&
settings?.domicilios == true) {
_showChoiceDialog(context)
.then((String? value) async {
if (value != null) {
var datos = await Navigator.of(context).push(
CupertinoPageRoute(
builder: (BuildContext context) {
return CalendarProScreen(
professional: professionals[index],
);
},
),
);
if (datos != null) {
datos.add(value);
Navigator.pop(context, datos);
}
}
});
} else if (professionals[index].ubicacion ==
'sitio' ||
settings?.domicilios == false) {
var datos = await Navigator.of(context).push(
CupertinoPageRoute(
builder: (BuildContext context) {
return CalendarProScreen(
professional: professionals[index],
);
},
),
);
if (datos != null) {
datos.add('sitio');
Navigator.pop(context, datos);
}
} else {
Navigator.pop(
context, [professionals[index], 'domicilio']);
}
},
);
},
),
),
],
),
),
);
}
}
@@ -1,255 +0,0 @@
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:geocoding/geocoding.dart';
import 'package:geolocator/geolocator.dart';
import 'package:get/get.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart';
import 'package:prosappco/src/authentication/authentication_repository.dart';
import 'package:prosappco/src/components/pop_appbar.dart';
import 'package:prosappco/src/components/primary_btn.dart';
class ProfessionalDireccionScreen extends StatefulWidget {
const ProfessionalDireccionScreen({super.key});
@override
State<ProfessionalDireccionScreen> createState() =>
_ProfessionalDireccionScreenState();
}
class _ProfessionalDireccionScreenState
extends State<ProfessionalDireccionScreen> {
final TextEditingController _locationController = TextEditingController();
final String _locationPosition = '';
final uid = AuthenticationRepository.instance.getCurrentUserUid();
late GoogleMapController googleMapController;
static const CameraPosition initialCameraPosition = CameraPosition(
target: LatLng(7.8939100, -72.5078200),
zoom: 14.4746,
);
Set<Marker> markers = {};
Future<Position> _determinePosition() async {
bool serviceEnabled;
LocationPermission permission;
serviceEnabled = await Geolocator.isLocationServiceEnabled();
if (!serviceEnabled) {
return Future.error('Location services are disabled');
}
permission = await Geolocator.checkPermission();
if (permission == LocationPermission.denied) {
permission = await Geolocator.requestPermission();
if (permission == LocationPermission.denied) {
return Future.error('Location permission denied');
}
}
if (permission == LocationPermission.deniedForever) {
return Future.error('Location permissions are permanently denied');
}
Position position = await Geolocator.getCurrentPosition();
return position;
}
@override
void initState() {
super.initState();
}
late String lat;
late String long;
var coordinates;
Future<String> getLocationName(double latitude, double longitude) async {
String address;
List<Placemark> placemarks =
await placemarkFromCoordinates(latitude, longitude);
Placemark place = placemarks[0];
if (place.thoroughfare != '' || place.subThoroughfare != '') {
address =
"${place.thoroughfare} ${place.subThoroughfare} ${place.subLocality}, ${place.locality}, ${place.administrativeArea}";
} else {
address = '';
}
return address;
}
Future<void> updateAddress(
String addressName, double latitude, double longitude) async {
try {
await FirebaseFirestore.instance.collection('users').doc(uid).update({
'address': addressName,
'latitude': latitude,
'longitude': longitude,
});
} catch (e) {
try {
await FirebaseFirestore.instance.collection('users').doc(uid).set({
'address': addressName,
'latitude': latitude,
'longitude': longitude,
});
} catch (e) {
print('Error al agregar la ciudad: $e');
}
print('Error al actualizar la ciudad: $e');
}
}
@override
Widget build(BuildContext context) {
return SafeArea(
child: Scaffold(
appBar: PopAppbar(
onPressed: () {
Navigator.pop(context);
},
label: 'Ubicación'),
backgroundColor: const Color(0xFFD6F4FF),
body: Stack(
children: [
GoogleMap(
mapType: MapType.normal,
initialCameraPosition: initialCameraPosition,
markers: markers,
zoomControlsEnabled: false,
onMapCreated: (GoogleMapController controller) {
googleMapController = controller;
},
onCameraIdle: () {
if (coordinates != null) {
getLocationName(coordinates.latitude, coordinates.longitude)
.then((locationName) {
setState(() {
_locationController.text = locationName;
});
});
}
},
onCameraMove: (position) {
setState(() {
coordinates = position.target;
});
},
gestureRecognizers: <Factory<OneSequenceGestureRecognizer>>{
Factory<OneSequenceGestureRecognizer>(
() => EagerGestureRecognizer(),
),
},
),
Container(
decoration: BoxDecoration(
color: Colors.white,
boxShadow: [
BoxShadow(
color: Colors.grey.withOpacity(0.5),
spreadRadius: 1,
blurRadius: 5,
offset: const Offset(0, 2),
),
],
),
child: Padding(
padding: const EdgeInsets.only(
left: 35, right: 35, bottom: 15, top: 5),
child: TextFormField(
controller: _locationController,
decoration: const InputDecoration(
prefixIcon: Icon(Icons.near_me),
hintText: 'Dirección',
),
),
),
),
const Positioned(
bottom: 10,
right: 0,
left: 0,
top: 0,
child: Icon(
Icons.location_on,
size: 40,
color: Colors.red,
),
),
Positioned(
bottom: 130,
right: 20,
child: FloatingActionButton(
onPressed: () async {
try {
Position position = await _determinePosition();
googleMapController.animateCamera(
CameraUpdate.newCameraPosition(
CameraPosition(
target: LatLng(
position.latitude,
position.longitude,
),
zoom: 17),
),
);
setState(() {});
} catch (e) {
Get.snackbar(
'Ubicación desactivada',
'Por favor activa la ubicacion de tu telefono.',
snackPosition: SnackPosition.TOP,
);
}
// markers.clear();
// markers.add(Marker(
// markerId: const MarkerId('currentLocation'),
// position:
// LatLng(position.latitude, position.longitude)));
},
elevation: 0,
child: const Icon(
Icons.gps_fixed,
size: 30,
),
),
),
Positioned(
bottom: 30,
left: 0,
right: 0,
child: SizedBox(
width: MediaQuery.of(context).size.width,
child: Align(
alignment: Alignment.center,
child: PrimaryButtom(
onPressed: () {
updateAddress(
_locationController.text,
coordinates.latitude,
coordinates.longitude,
);
Navigator.pop(context, _locationController.text);
},
label: 'Guardar'),
),
),
)
],
),
),
);
}
}
@@ -1,361 +0,0 @@
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_rating_bar/flutter_rating_bar.dart';
import 'package:intl/intl.dart';
import 'package:prosappco/src/components/photo_view.dart';
import 'package:prosappco/src/components/pop_appbar.dart';
import 'package:prosappco/src/models/professional_model.dart';
import 'package:prosappco/src/models/setting_model.dart';
import 'package:prosappco/src/presentation/screens/reputation.dart';
import '../../models/scores_model.dart';
class ProfessionalInfoScreen extends StatefulWidget {
Professional professional;
ProfessionalInfoScreen({super.key, required this.professional});
@override
State<ProfessionalInfoScreen> createState() => _ProfessionalInfoScreenState();
}
class _ProfessionalInfoScreenState extends State<ProfessionalInfoScreen> {
SettingModel? settings;
String formatCurrency(int number) {
final formatter =
NumberFormat.currency(locale: 'es_CO', decimalDigits: 0, symbol: '');
return '\$${formatter.format(number)}';
}
@override
void initState() {
super.initState();
if (settings == null) {
SettingModel.getSettings().then(
(SettingModel value) => setState(() {
settings = value;
}),
);
}
loadPaymentMethods();
}
Map<String, bool> paymentMethods = {};
String _formatPaymentMethods(Map<String, bool> paymentMethods) {
List<String> enabledMethods = paymentMethods.entries
.where((entry) => entry.value)
.map((entry) => entry.key)
.toList();
return enabledMethods.join(', ');
}
void loadPaymentMethods() {
FirebaseFirestore.instance
.collection('users')
.doc(widget.professional.id)
.get()
.then((doc) {
if (doc.exists) {
setState(() {
paymentMethods = Map<String, bool>.from(doc['paymentMethods'] ?? {});
});
}
});
}
@override
Widget build(BuildContext context) {
return SafeArea(
child: Scaffold(
appBar: PopAppbar(
onPressed: () {
Navigator.pop(context);
},
label: widget.professional.name),
body: SingleChildScrollView(
child: Stack(
children: [
Column(
children: [
Container(
width: double.infinity,
height: 140,
decoration: BoxDecoration(
color: const Color(0xFFD6F4FF),
boxShadow: [
BoxShadow(
color: Colors.grey.withOpacity(0.5),
spreadRadius: 1,
blurRadius: 7,
offset: const Offset(0, 2),
),
],
),
),
Padding(
padding: const EdgeInsets.only(left: 50),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const SizedBox(
height: 5,
),
Text(
widget.professional.name,
style: const TextStyle(fontWeight: FontWeight.w500),
),
Text(
widget.professional.professionName,
style: const TextStyle(color: Color(0xFF1688C9)),
),
widget.professional.getEspecializaciones().isEmpty
? const SizedBox()
: Text(
'Especializado/a en ${widget.professional.getEspecializaciones()}',
style: const TextStyle(color: Colors.black54),
),
Text(
widget.professional.cityName,
),
],
),
),
widget.professional.ubicacion == 'domicilio'
? Container(
margin: const EdgeInsets.only(
left: 40, right: 40, top: 20, bottom: 20),
padding: const EdgeInsets.symmetric(
horizontal: 20, vertical: 15),
decoration: BoxDecoration(
color: const Color(0xFFD6F4FF),
borderRadius: BorderRadius.circular(20),
boxShadow: [
BoxShadow(
color: Colors.grey.withOpacity(0.5),
spreadRadius: 1,
blurRadius: 5,
offset: const Offset(1, 3),
),
],
),
child: const Row(
children: [
Icon(
Icons.error_outline,
size: 27,
color: Colors.black54,
),
SizedBox(width: 15),
Text(
'Este profesional solo atiende en\nsu dirección de trabajo.',
style: TextStyle(
color: Colors.black, fontSize: 13),
),
],
),
)
: const SizedBox(),
const SizedBox(height: 10),
settings?.tarifas == true
? RichText(
text: TextSpan(
children: [
const TextSpan(
text: 'Tarifa consulta ',
style: TextStyle(
color: Colors.black,
),
),
TextSpan(
text: formatCurrency(
widget.professional.tarifa ?? 0),
style: const TextStyle(
color: Colors.black,
fontSize: 16,
fontWeight: FontWeight.w600,
),
),
],
),
)
: const SizedBox(),
const SizedBox(height: 5),
paymentMethods.containsValue(true)
? Container(
width: MediaQuery.of(context).size.width * 0.8,
padding: const EdgeInsets.symmetric(
horizontal: 16, vertical: 8),
decoration: BoxDecoration(
color: Colors.blue.withOpacity(0.1),
borderRadius: BorderRadius.circular(10),
),
child: RichText(
text: TextSpan(
style: const TextStyle(
fontSize: 12,
color: Colors.blue,
),
children: [
const TextSpan(
text: 'Métodos de pago recibidos: ',
style:
TextStyle(fontWeight: FontWeight.normal),
),
TextSpan(
text: _formatPaymentMethods(paymentMethods),
style: const TextStyle(
fontWeight: FontWeight.bold),
),
],
),
))
: const SizedBox(),
const SizedBox(height: 5),
const Text(
'Se unió el 02 de abril del 2023',
style: TextStyle(fontSize: 12),
),
const SizedBox(height: 15),
Container(
decoration: BoxDecoration(
color: const Color(0xFFD6F4FF),
boxShadow: [
BoxShadow(
color: Colors.grey.withOpacity(0.2),
spreadRadius: 3,
blurRadius: 5,
offset: const Offset(0, 3),
),
],
),
child: ListTile(
onTap: () {
Navigator.of(context).push(
CupertinoPageRoute(
builder: (BuildContext context) {
return const ReputationScreen();
},
),
);
},
trailing: const Icon(Icons.keyboard_arrow_right,
color: Colors.black),
title: const Text(
'Reputación',
style: TextStyle(color: Colors.black),
),
subtitle: Row(
children: [
RatingBar.builder(
initialRating: widget.professional.scores.average,
minRating: 1,
direction: Axis.horizontal,
allowHalfRating: true,
itemCount: 5,
itemSize: 25,
maxRating: 5,
itemPadding:
const EdgeInsets.symmetric(horizontal: 0),
itemBuilder: (context, _) => const Icon(
Icons.star,
color: Color(0xFF2BA4EC),
),
onRatingUpdate: (rating) {},
ignoreGestures: true,
),
const SizedBox(width: 5),
Text(
'(${widget.professional.scores.total.toString()}) ${widget.professional.scores.average.toStringAsFixed(1)}'),
],
),
),
),
..._scoresList(widget.professional.scores.details),
const SizedBox(
height: 10,
),
],
),
Container(
padding: const EdgeInsets.only(
top: 110,
left: 10,
),
child: ReferencePhoto(
ref: widget.professional.professionalRef,
size: 100,
sizeCircle: 100,
sizeIcon: 50,
),
)
],
),
),
),
);
}
List<Widget> _scoresList(List<ScoreDetailModel> list) {
return list.map((e) => _scoreItem(e)).toList();
}
Widget _scoreItem(ScoreDetailModel scoreDetails) {
return ListTile(
onTap: () {},
leading: ReferencePhoto(
ref: scoreDetails.avatar,
size: 50,
sizeCircle: 50,
sizeIcon: 35,
),
title: Row(
children: [
RatingBar.builder(
initialRating: scoreDetails.score,
minRating: 1,
direction: Axis.horizontal,
allowHalfRating: true,
itemCount: 5,
itemSize: 22,
maxRating: 5,
itemPadding: const EdgeInsets.symmetric(horizontal: 0),
itemBuilder: (context, _) => const Icon(
Icons.star,
color: Color(0xFF2BA4EC),
),
onRatingUpdate: (rating) {},
ignoreGestures: true,
),
Text(
' (${scoreDetails.score})',
style: const TextStyle(color: Colors.black54, fontSize: 13),
)
],
),
subtitle: Row(
children: [
Expanded(
child: Text.rich(
TextSpan(
children: [
TextSpan(
text: '${scoreDetails.name}, ',
style: const TextStyle(fontSize: 15, color: Colors.black),
),
TextSpan(
text: '"${scoreDetails.comment}"',
style: const TextStyle(fontSize: 15, color: Colors.grey),
),
],
),
),
),
],
));
}
}
@@ -1,857 +0,0 @@
import 'dart:io';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_storage/firebase_storage.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:intl/intl.dart';
import 'package:prosappco/src/authentication/authentication_repository.dart';
import 'package:prosappco/src/components/photo_view.dart';
import 'package:prosappco/src/components/pop_appbar.dart';
import 'package:prosappco/src/controllers/info_%20professional.dart';
import 'package:prosappco/src/presentation/widgets/shared/warning_snackbar.dart';
import 'package:prosappco/src/services/select_image_profile.dart';
import 'package:file_picker/file_picker.dart';
class ProfessionalProfileScreen extends StatefulWidget {
const ProfessionalProfileScreen({super.key});
@override
State<ProfessionalProfileScreen> createState() =>
ProfessionalProfileScreenState();
}
class ProfessionalProfileScreenState extends State<ProfessionalProfileScreen> {
File? imagen_to_upload;
File? image_cedula;
File? image_certificado;
final FirebaseStorage storage = FirebaseStorage.instance;
final uid = AuthenticationRepository.instance.getCurrentUserUid();
final _formKey = GlobalKey<FormState>();
final controller = Get.put(InforProfessionalController());
final _cedulaController = TextEditingController();
final _especializacionController = TextEditingController();
List<File> images_especializacion = [];
var _profession = '...';
var photoTemp = '';
var photoCedulaTemp = '';
var photoCertificadoTemp = '';
var _photo = '...';
@override
void initState() {
super.initState();
if (_photo == '...') {
AuthenticationRepository.instance
.getPhoto(uid.toString())
.then((String s) => setState(() {
_photo = s;
}));
}
if (_profession == '...') {
AuthenticationRepository.instance
.getProfession(uid.toString())
.then((String s) => setState(() {
_profession = s;
}));
}
}
Future<File?> getPdf() async {
FilePickerResult? result = await FilePicker.platform.pickFiles(
type: FileType.custom,
allowedExtensions: ['pdf'],
);
if (result != null) {
File file = File(result.files.single.path!);
return file;
} else {
return null;
}
}
Future<void> _showChoiceDialog(BuildContext context) async {
return showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
content: SingleChildScrollView(
child: ListBody(
children: [
GestureDetector(
child: const Text(
textAlign: TextAlign.center,
"Tomar foto",
style: TextStyle(color: Color(0xFF2BA4EC)),
),
onTap: () async {
final imagen = await getImage(1);
setState(() {
imagen_to_upload = File(imagen[0]!.path);
});
Navigator.of(context).pop();
},
),
const Divider(color: Colors.black54),
GestureDetector(
child: const Text(
textAlign: TextAlign.center,
"Abrir Galería",
style: TextStyle(color: Color(0xFF2BA4EC)),
),
onTap: () async {
final imagen = await getImage(2);
setState(() {
imagen_to_upload = File(imagen[0]!.path);
});
Navigator.of(context).pop();
},
),
],
),
),
);
},
);
}
Future<void> _showChoiceDialogCedula(BuildContext context) async {
return showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
content: SingleChildScrollView(
child: ListBody(
children: [
GestureDetector(
child: const Text(
textAlign: TextAlign.center,
"Abrir Galería",
style: TextStyle(color: Color(0xFF2BA4EC)),
),
onTap: () async {
final imagen = await getPdf();
setState(() {
image_cedula = File(imagen!.path);
});
Navigator.of(context).pop();
},
),
],
),
),
);
},
);
}
Future<void> _showChoiceDialogCertificado(BuildContext context) async {
return showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
content: SingleChildScrollView(
child: ListBody(
children: [
GestureDetector(
child: const Text(
textAlign: TextAlign.center,
"Abrir Galería",
style: TextStyle(color: Color(0xFF2BA4EC)),
),
onTap: () async {
final imagen = await getPdf();
setState(() {
image_certificado = File(imagen!.path);
});
Navigator.of(context).pop();
},
),
],
),
),
);
},
);
}
Future<bool> uploadCedula(File image) async {
final now = DateTime.now();
final formattedDate = DateFormat('HHmmssddMMyyyy').format(now);
final milliseconds = (now.microsecondsSinceEpoch / 1000).round();
final random = 'c$formattedDate$milliseconds';
Reference ref =
storage.ref().child('users').child(uid!).child('cedula').child(random);
final UploadTask uploadTask = ref.putFile(image, metadata);
final TaskSnapshot snapshot = await uploadTask.whenComplete(() => true);
photoCedulaTemp = ref.fullPath;
if (snapshot.state == TaskState.success) {
updateImageCedula(photoCedulaTemp);
return true;
} else {
return false;
}
}
Future<void> updateImageCedula(image) async {
try {
final userRef = FirebaseFirestore.instance.collection('users').doc(uid);
final userSnapshot = await userRef.get();
if (userSnapshot.exists) {
await userRef.update({'imgCedula': image});
} else {
await userRef.set({'imgCedula': image});
}
} catch (e) {
print('Error al agregar o actualizar la imagen de cédula: $e');
}
}
final metadata = SettableMetadata(
contentType: 'application/pdf',
);
Future<bool> uploadCertificado(File image) async {
final now = DateTime.now();
final formattedDate = DateFormat('HHmmssddMMyyyy').format(now);
final milliseconds = (now.microsecondsSinceEpoch / 1000).round();
final random = 'f$formattedDate$milliseconds';
Reference ref = storage
.ref()
.child('users')
.child(uid!)
.child('certificado_profesional')
.child(random);
final UploadTask uploadTask = ref.putFile(image, metadata);
final TaskSnapshot snapshot = await uploadTask.whenComplete(() => true);
photoCertificadoTemp = ref.fullPath;
if (snapshot.state == TaskState.success) {
updateImageCertificado(photoCertificadoTemp);
return true;
} else {
return false;
}
}
Future<void> updateImageCertificado(image) async {
try {
final userRef = FirebaseFirestore.instance.collection('users').doc(uid);
final userSnapshot = await userRef.get();
if (userSnapshot.exists) {
await userRef.update({'imgCertificado': image});
} else {
await userRef.set({'imgCertificado': image});
}
} catch (e) {
print('Error al agregar o actualizar la imagen de certificado: $e');
}
}
Future<void> updateImage(image) async {
try {
await FirebaseFirestore.instance
.collection('users')
.doc(uid)
.update({'photo': image});
} catch (e) {
try {
await FirebaseFirestore.instance
.collection('users')
.doc(uid)
.set({'photo': image});
} catch (e) {
print('Error al agregar la imagen de perfil: $e');
}
print('Error al actualizar la imagen de perfil: $e');
}
}
Future<bool> uploadImage(File image) async {
try {
final String namefile = image.path.split('/').last;
Reference ref = storage
.ref()
.child('users')
.child(uid!)
.child('profile')
.child(namefile);
final UploadTask uploadTask = ref.putFile(image);
final TaskSnapshot snapshot = await uploadTask;
if (snapshot.state == TaskState.success) {
// Obtén la URL de descarga de la imagen y actualiza en Firestore
String downloadURL = await ref.getDownloadURL();
await updateImage(downloadURL);
return true;
} else {
return false;
}
} catch (e) {
print('Error al cargar la imagen: $e');
return false;
}
}
Future<void> _showChoiceDialogEspecializaciones(BuildContext context) async {
return showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
content: SingleChildScrollView(
child: ListBody(
children: [
GestureDetector(
child: const Text(
textAlign: TextAlign.center,
"Abrir Galería",
style: TextStyle(color: Color(0xFF2BA4EC)),
),
onTap: () async {
final List<File>? images = await getPdfs();
if (images != null) {
setState(() {
images_especializacion = images;
});
}
Navigator.of(context).pop();
},
),
],
),
),
);
},
);
}
Future<List<File>?> getPdfs() async {
FilePickerResult? result = await FilePicker.platform.pickFiles(
type: FileType.custom,
allowedExtensions: ['pdf'],
allowMultiple: true,
);
if (result != null) {
List<File> files = result.files.map((file) => File(file.path!)).toList();
return files;
} else {
return null;
}
}
Future<List<String>> uploadEspecializaciones(List<File> images) async {
List<String> photoPaths = [];
for (File image in images) {
Reference ref = storage
.ref()
.child('users')
.child(uid!)
.child('especializaciones')
.child('e${DateTime.now().millisecondsSinceEpoch}.pdf');
final UploadTask uploadTask = ref.putFile(image, metadata);
final TaskSnapshot snapshot = await uploadTask.whenComplete(() => true);
if (snapshot.state == TaskState.success) {
photoPaths.add(ref.fullPath);
} else {
updateImagesEspecializaciones(photoPaths);
return [];
}
}
updateImagesEspecializaciones(photoPaths);
return photoPaths;
}
Future<void> updateImagesEspecializaciones(List<String> photoPaths) async {
try {
final userRef = FirebaseFirestore.instance.collection('users').doc(uid);
final userSnapshot = await userRef.get();
if (userSnapshot.exists) {
await userRef.update({'imgEspecializaciones': photoPaths});
} else {
await userRef.set({'imgEspecializaciones': photoPaths});
}
} catch (e) {
print(
'Error al agregar o actualizar las imágenes de especializaciones: $e');
}
}
void _showCustomSnackBar(BuildContext context, String message) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Container(
height: 50,
child: Center(
child: Text(
message,
style: TextStyle(fontSize: 18),
),
),
),
duration: Duration(seconds: 3),
backgroundColor: Colors.red, // Personaliza el color de fondo
behavior: SnackBarBehavior.floating,
),
);
}
Future<void> sendInfo() async {
final String cedula = _cedulaController.text.trim();
final String especializacion = _especializacionController.text.trim();
final List<String> especializaciones =
especializacion.split(',').map((e) => e.trim()).toList();
if (cedula.isEmpty) {
WarningSnackbar.show(
title: 'Te faltan campos!!',
message: 'Por favor, ingresa tu cedula',
);
return;
}
if (image_cedula == null) {
WarningSnackbar.show(
title: 'Te faltan archivos!!',
message: 'Por favor, adjunta el documento PDF de tu cedula',
);
return;
}
if (_profession.isEmpty) {
WarningSnackbar.show(
title: 'Te falta elegir una profesión!!',
message:
'Por favor, elige tu profesión antes de enviar la información.',
);
return;
}
if (image_certificado == null) {
WarningSnackbar.show(
title: 'Te faltan archivos!!',
message: 'Por favor, adjunta el documento PDF de tu certificado',
);
return;
}
if (imagen_to_upload == null && _photo == '...') {
WarningSnackbar.show(
title: 'Sube una foto de perfil',
message: 'Para continuar debes subir una imagen de perfil',
);
return;
} else {
// Actualiza la imagen de perfil si hay cambios
updateImage(photoTemp);
}
// Actualiza los datos del usuario en Firestore
await FirebaseFirestore.instance.collection('users').doc(uid).update({
'cedula': cedula,
'estado': 'revision',
'especializaciones': especializaciones
});
// Sube las imágenes al storage de Firebase
uploadCedula(image_cedula!);
uploadCertificado(image_certificado!);
uploadEspecializaciones(images_especializacion);
Navigator.pushReplacementNamed(context, '/solicitudEnviada');
}
void showSnackBar(String message) {
ScaffoldMessenger.of(context)
.showSnackBar(SnackBar(content: Text(message)));
}
Future<Widget> downloadImage(Reference ref) async {
try {
if (_photo == '...' || _photo.isEmpty) {
return GestureDetector(
onTap: () {
_showChoiceDialog(context);
},
child: Container(
margin: const EdgeInsets.symmetric(vertical: 50),
width: 100,
height: 100,
decoration: BoxDecoration(
color: const Color(0xFF2BA4EC),
borderRadius: BorderRadius.circular(50),
),
child: const Icon(
Icons.person,
color: Colors.white,
size: 90,
),
),
);
} else {
final imageData = await ref.getData();
if (imageData != null) {
// final widgetImage = Image.memory(imageData);
final widgetImage = GestureDetector(
onTap: () {
_showChoiceDialog(context);
},
child: Container(
margin: const EdgeInsets.symmetric(vertical: 50),
child: ClipOval(
child: Image.memory(
imageData,
width: 60,
height: 60,
fit: BoxFit.cover,
),
),
),
);
return widgetImage;
} else {
return GestureDetector(
onTap: () {
_showChoiceDialog(context);
},
child: Container(
margin: const EdgeInsets.symmetric(vertical: 50),
width: 100,
height: 100,
decoration: BoxDecoration(
color: const Color(0xFF2BA4EC),
borderRadius: BorderRadius.circular(50),
),
child: const Icon(
Icons.person,
color: Colors.white,
size: 90,
),
),
);
}
}
} catch (e) {
print('$e');
return GestureDetector(
onTap: () {
_showChoiceDialog(context);
},
child: Container(
margin: const EdgeInsets.symmetric(vertical: 50),
width: 100,
height: 100,
decoration: BoxDecoration(
color: const Color(0xFF2BA4EC),
borderRadius: BorderRadius.circular(50),
),
child: const Icon(
Icons.person,
color: Colors.white,
size: 90,
),
),
);
}
}
@override
Widget build(BuildContext context) {
String profession = _profession.toString();
double _space = 10;
return SafeArea(
child: Scaffold(
appBar: PopAppbar(
onPressed: () {
Navigator.pop(context);
},
label: 'Perfil profesional',
),
body: SingleChildScrollView(
reverse: true,
child: Center(
child: Column(
children: [
GestureDetector(
onTap: () {
_showChoiceDialog(context);
},
child: Container(
margin: const EdgeInsets.symmetric(vertical: 25),
child: (imagen_to_upload != null)
? LocalPhoto(
file: imagen_to_upload!,
)
: ReferencePhoto(
ref: storage.ref().child(_photo),
size: 100,
sizeCircle: 100,
),
),
),
Container(
width: 300,
padding: const EdgeInsets.only(top: 0),
child: Form(
key: _formKey,
child: Column(
children: [
TextFormField(
keyboardType: TextInputType.number,
controller: _cedulaController,
validator: (String? value) {
if (value == null || value.isEmpty) {
return 'Ingrese una cedula válida';
}
return null;
},
decoration: const InputDecoration(
prefixIcon: Icon(Icons.person_outline),
hintText: 'Cedula (Obligatorio)'),
),
SizedBox(height: _space),
ElevatedButton(
onPressed: () {
_showChoiceDialogCedula(context);
},
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFFD6F4FF),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(50),
),
elevation: 0,
minimumSize: const Size(250, 50),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Text(
'Cedula',
style: TextStyle(
color: Color(0xFF2BA4EC),
fontSize: 17,
),
),
const SizedBox(width: 15),
Icon(
image_cedula != null
? Icons.check
: Icons.file_upload_outlined,
color: const Color(0xFF2BA4EC),
size: 30,
),
],
),
),
SizedBox(height: _space),
TextFormField(
readOnly: true,
onTap: () async {
final String? profesion = (await Navigator.pushNamed(
context, '/profession')) as String?;
if (profesion != null) {
setState(() {
_profession = profesion;
});
}
},
decoration: InputDecoration(
prefixIcon:
const Icon(Icons.assignment_ind_rounded),
suffixIcon: const Icon(Icons.arrow_drop_down),
hintStyle: profession == ''
? const TextStyle()
: const TextStyle(color: Colors.black87),
hintText: profession == ''
? 'Profesión (Obligatorio)'
: profession),
),
SizedBox(height: _space),
ElevatedButton(
onPressed: () {
_showChoiceDialogCertificado(context);
},
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFFD6F4FF),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(50),
),
elevation: 0,
minimumSize: const Size(250, 50),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Text(
'Certificado profesional',
style: TextStyle(
color: Color(0xFF2BA4EC),
fontSize: 17,
),
),
const SizedBox(width: 15),
Icon(
image_certificado != null
? Icons.check
: Icons.file_upload_outlined,
color: const Color(0xFF2BA4EC),
size: 30,
),
],
),
),
SizedBox(height: _space),
TextFormField(
controller: _especializacionController,
decoration: const InputDecoration(
prefixIcon: Icon(Icons.assignment_ind_rounded),
hintText: 'Especialización'),
),
SizedBox(height: _space),
ElevatedButton(
onPressed: () async {
_showChoiceDialogEspecializaciones(context);
},
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFFD6F4FF),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(50),
),
elevation: 0,
minimumSize: const Size(250, 50),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Text(
'Especialización',
style: TextStyle(
color: Color(0xFF2BA4EC),
fontSize: 17,
),
),
const SizedBox(width: 15),
Icon(
images_especializacion.isEmpty
? Icons.file_upload_outlined
: Icons.check,
color: const Color(0xFF2BA4EC),
size: 30,
),
],
),
),
],
),
),
),
Container(
margin: const EdgeInsets.only(
left: 40, right: 40, top: 40, bottom: 0),
padding:
const EdgeInsets.symmetric(horizontal: 20, vertical: 15),
decoration: BoxDecoration(
color: const Color(0xFFD6F4FF),
borderRadius: BorderRadius.circular(20),
boxShadow: [
BoxShadow(
color: Colors.grey.withOpacity(0.5),
spreadRadius: 1,
blurRadius: 5,
offset: const Offset(1, 3),
),
],
),
child: const Row(
children: [
Icon(
Icons.error_outline,
size: 27,
color: Colors.black54,
),
SizedBox(width: 15),
Expanded(
child: Text(
'Si tienes más de una especialidad, por favor, adjunta un archivo con el diploma de respaldo para cada una de ellas y sepáralos por comas. ¡Gracias!',
style: TextStyle(color: Colors.black, fontSize: 14),
),
)
],
),
),
Container(
alignment: Alignment.bottomCenter,
margin: const EdgeInsets.only(
top: 80, right: 20, left: 20, bottom: 30),
child: ElevatedButton(
onPressed: () {
if (_formKey.currentState!.validate()) {
sendInfo();
}
},
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFF2BA4EC),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(50),
),
elevation: 0,
minimumSize: const Size(250, 50),
maximumSize: const Size(350, 50),
),
child: const Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
'Enviar información',
style: TextStyle(
color: Colors.white,
fontWeight: FontWeight.bold,
fontSize: 17,
),
),
SizedBox(width: 15),
Icon(
Icons.send,
color: Colors.white,
size: 20,
),
],
),
),
),
],
),
),
),
));
}
}
@@ -1,705 +0,0 @@
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:file_picker/file_picker.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:firebase_storage/firebase_storage.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:get/get.dart';
import 'package:image_picker/image_picker.dart';
import 'package:prosappco/src/authentication/authentication_repository.dart';
import 'package:prosappco/src/components/photo_view_web.dart';
import 'package:prosappco/src/components/pop_appbar.dart';
import 'package:intl/intl.dart';
import 'dart:io';
class ProfessionalProfileWebScreen extends StatefulWidget {
const ProfessionalProfileWebScreen({super.key});
@override
State<ProfessionalProfileWebScreen> createState() =>
_ProfessionalProfileWebScreenState();
}
class _ProfessionalProfileWebScreenState
extends State<ProfessionalProfileWebScreen> {
final uid = AuthenticationRepository.instance.getCurrentUserUid();
final FirebaseStorage storage = FirebaseStorage.instance;
// variables imagen
String selectedImage = '';
String selectedCedulaImage = '';
String selectedCertificadoImage = '';
List selectedEspecializacionImages = [];
List<Uint8List> imagesEspecializacionsBytes = [];
XFile? file;
Uint8List? selectedImagInBytes;
XFile? image_cedula;
Uint8List? imageCedulaBytes;
XFile? image_certificado;
Uint8List? imageCertificadoBytes;
XFile? image_especializaciones;
Uint8List? imageEspecializacionesBytes;
// controllers
final _formKey = GlobalKey<FormState>();
final TextEditingController _cedulaController = TextEditingController();
final TextEditingController _especializacionController =
TextEditingController();
// variables
bool _isLoading = false;
String _profession = '...';
String photoTemp = '';
String photoCedulaTemp = '';
String photoCertificadoTemp = '';
String _photo = '...';
@override
void initState() {
super.initState();
if (_photo == '...') {
AuthenticationRepository.instance.getPhoto(uid.toString()).then(
(String s) => setState(() {
_photo = s;
}),
);
}
if (_profession == '...') {
AuthenticationRepository.instance.getProfession(uid.toString()).then(
(String s) => setState(() {
_profession = s;
}),
);
}
}
_selectFile(bool imageFrom) async {
FilePickerResult? fileResult = await FilePicker.platform.pickFiles();
if (fileResult != null) {
setState(() {
selectedImage = fileResult.files.first.name;
selectedImagInBytes = fileResult.files.first.bytes;
});
}
}
_selectFileCedula(bool imageFrom) async {
FilePickerResult? fileResult = await FilePicker.platform.pickFiles();
if (fileResult != null) {
setState(() {
selectedCedulaImage = fileResult.files.first.name;
imageCedulaBytes = fileResult.files.first.bytes;
});
}
}
_selectFileCertificado(bool imageFrom) async {
FilePickerResult? fileResult = await FilePicker.platform.pickFiles();
if (fileResult != null) {
setState(() {
selectedCertificadoImage = fileResult.files.first.name;
imageCertificadoBytes = fileResult.files.first.bytes;
});
}
}
_selectFilesEspecializaciones(bool imageFrom) async {
FilePickerResult? fileResult =
await FilePicker.platform.pickFiles(allowMultiple: true);
try {
if (fileResult != null) {
List<Uint8List> selectedFileBytes = [];
for (var file in fileResult.files) {
Uint8List? bytes = file.bytes;
if (bytes != null) {
selectedFileBytes.add(bytes);
}
}
setState(() {
imagesEspecializacionsBytes = selectedFileBytes;
});
}
} catch (e) {
print('$e');
}
}
Future<void> sendInfo() async {
setState(() {
_isLoading = true;
});
final String cedula = _cedulaController.text.trim();
final String especializacion = _especializacionController.text.trim();
final List<String> especializaciones =
especializacion.split(',').map((e) => e.trim()).toList();
if (cedula.isEmpty) {
showSnackBar('Cedula invalida', 'Ingrese una cedula válida');
return;
}
if (imageCedulaBytes == null) {
showSnackBar('Cedula', 'Ingrese una imagen de su cedula');
return;
}
if (imageCertificadoBytes == null) {
showSnackBar('Certificado', 'Ingrese una imagen de su certificado');
return;
}
// Actualiza los datos del usuario en Firestore
await FirebaseFirestore.instance.collection('users').doc(uid).update({
'cedula': cedula,
'estado': 'revision',
'especializaciones': especializaciones
});
// Sube las imágenes al storage de Firebase
await uploadCedula();
await uploadCertificado();
List<String> uploadedPhotoPaths =
await uploadEspecializaciones(imagesEspecializacionsBytes);
if (uploadedPhotoPaths.isNotEmpty) {
// Las imágenes se cargaron correctamente
// Actualiza las imágenes en Firestore
await updateFilesEspecializaciones(uploadedPhotoPaths);
// Navega a la siguiente pantalla
Navigator.pushReplacementNamed(context, '/solicitudEnviada');
} else {
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: const Text('Error'),
content: const Text('Ocurrió un error al cargar las imágenes.'),
actions: [
TextButton(
onPressed: () {
Navigator.pop(context);
},
child: const Text('Aceptar'),
),
],
);
},
);
}
// Actualiza la imagen de perfil si hay cambios
if (selectedImagInBytes != null) {
await uploadFile();
await updateImage(photoTemp);
}
setState(() {
_isLoading = false;
});
// Navega a la siguiente pantalla
Navigator.pushReplacementNamed(context, '/solicitudEnviada');
}
uploadFile() async {
try {
final now = DateTime.now();
final formattedDate = DateFormat('HHmmssddMMyyyy').format(now);
final milliseconds = (now.microsecondsSinceEpoch / 1000).round();
final random = '$formattedDate$milliseconds';
final Reference ref = FirebaseStorage.instance
.ref()
.child('users')
.child(uid!)
.child('profile')
.child(random);
final metaData = SettableMetadata(contentType: 'image/jpeg');
final UploadTask uploadTask = ref.putData(selectedImagInBytes!, metaData);
final TaskSnapshot snapshot = await uploadTask.whenComplete(() => true);
photoTemp = ref.fullPath;
if (snapshot.state == TaskState.success) {
return true;
} else {
return false;
}
} catch (e) {
print('web image error - $e');
}
}
uploadCedula() async {
try {
final now = DateTime.now();
final formattedDate = DateFormat('HHmmssddMMyyyy').format(now);
final milliseconds = (now.microsecondsSinceEpoch / 1000).round();
final random = 'c$formattedDate$milliseconds';
final Reference ref = FirebaseStorage.instance
.ref()
.child('users')
.child(uid!)
.child('cedula')
.child(random);
final metaData = SettableMetadata(contentType: 'application/pdf');
final UploadTask uploadTask = ref.putData(imageCedulaBytes!, metaData);
final TaskSnapshot snapshot = await uploadTask.whenComplete(() => true);
photoCedulaTemp = ref.fullPath;
if (snapshot.state == TaskState.success) {
updateImageCedula(photoCedulaTemp);
return true;
} else {
return false;
}
} catch (e) {
print('web image cedula error - $e');
}
}
uploadCertificado() async {
try {
final now = DateTime.now();
final formattedDate = DateFormat('HHmmssddMMyyyy').format(now);
final milliseconds = (now.microsecondsSinceEpoch / 1000).round();
final random = 'f$formattedDate$milliseconds';
final Reference ref = FirebaseStorage.instance
.ref()
.child('users')
.child(uid!)
.child('certificado_profesional')
.child(random);
final metaData = SettableMetadata(contentType: 'application/pdf');
final UploadTask uploadTask =
ref.putData(imageCertificadoBytes!, metaData);
final TaskSnapshot snapshot = await uploadTask.whenComplete(() => true);
photoCertificadoTemp = ref.fullPath;
if (snapshot.state == TaskState.success) {
updateImageCertificado(photoCertificadoTemp);
return true;
} else {
return false;
}
} catch (e) {
print('web image certificado error - $e');
}
}
Future<List<String>> uploadEspecializaciones(List<Uint8List> files) async {
List<String> filePaths = [];
for (Uint8List fileBytes in files) {
final Reference ref = FirebaseStorage.instance
.ref()
.child('users')
.child(uid!)
.child('especializaciones')
.child('e${DateTime.now().millisecondsSinceEpoch}.pdf');
final SettableMetadata metaData =
SettableMetadata(contentType: 'application/pdf');
final UploadTask uploadTask = ref.putData(fileBytes, metaData);
final TaskSnapshot snapshot = await uploadTask.whenComplete(() => true);
if (snapshot.state == TaskState.success) {
filePaths.add(ref.fullPath);
} else {
await updateFilesEspecializaciones(filePaths);
return [];
}
}
await updateFilesEspecializaciones(filePaths);
return filePaths;
}
Future<void> updateImageCedula(image) async {
try {
final userRef = FirebaseFirestore.instance.collection('users').doc(uid);
final userSnapshot = await userRef.get();
if (userSnapshot.exists) {
await userRef.update({'imgCedula': image});
} else {
await userRef.set({'imgCedula': image});
}
} catch (e) {
print('Error al agregar o actualizar la imagen de cédula: $e');
}
}
Future<void> updateImageCertificado(image) async {
try {
final userRef = FirebaseFirestore.instance.collection('users').doc(uid);
final userSnapshot = await userRef.get();
if (userSnapshot.exists) {
await userRef.update({'imgCertificado': image});
} else {
await userRef.set({'imgCertificado': image});
}
} catch (e) {
print('Error al agregar o actualizar la imagen de certificado: $e');
}
}
Future<void> updateImage(image) async {
try {
await FirebaseFirestore.instance
.collection('users')
.doc(uid)
.update({'photo': image});
} catch (e) {
try {
await FirebaseFirestore.instance
.collection('users')
.doc(uid)
.set({'photo': image});
} catch (e) {
print('Error al agregar la imagen de perfil: $e');
}
print('Error al actualizar la imagen de perfil: $e');
}
}
Future<void> updateFilesEspecializaciones(List<String> filePaths) async {
try {
final userRef = FirebaseFirestore.instance.collection('users').doc(uid);
final userSnapshot = await userRef.get();
if (userSnapshot.exists) {
await userRef.update({'imgEspecializaciones': filePaths});
} else {
await userRef.set({'imgEspecializaciones': filePaths});
}
} catch (e) {
print('Error al actualizar los archivos de especializaciones: $e');
}
}
void showSnackBar(String title, String message) {
Get.snackbar(
title,
message,
snackPosition: SnackPosition.TOP,
);
}
@override
Widget build(BuildContext context) {
String profession = _profession.toString();
double _space = 10;
return Scaffold(
backgroundColor: const Color(0xFFD6F4FF),
appBar: PopAppbar(
onPressed: () {
Navigator.pop(context);
},
label: 'Perfil profesional'),
body: SingleChildScrollView(
child: Center(
child: SizedBox(
width: 350,
child: Card(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10),
),
color: Colors.white,
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 20.0),
child: _isLoading
? const Padding(
padding: EdgeInsets.symmetric(vertical: 30),
child: CircularProgressIndicator(),
)
: Column(
children: [
Container(
padding: const EdgeInsets.only(top: 20),
child: (selectedImagInBytes != null)
? LocalPhotoWeb(file: selectedImagInBytes)
: ReferencePhotoWeb(
ref: storage.ref().child(_photo)),
),
SizedBox(
width: 300,
child: Form(
key: _formKey,
child: Column(
children: [
TextFormField(
keyboardType: TextInputType.number,
controller: _cedulaController,
inputFormatters: [
FilteringTextInputFormatter
.digitsOnly // Solo permite caracteres numéricos
],
validator: (String? value) {
if (value == null || value.isEmpty) {
return 'Ingrese una cedula válida';
}
return null;
},
decoration: const InputDecoration(
prefixIcon: Icon(Icons.person_outline),
hintText: 'Cedula (Obligatorio)',
),
),
SizedBox(height: _space),
ElevatedButton(
onPressed: () {
_selectFileCedula(true);
},
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFFD6F4FF),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(50),
),
elevation: 0,
minimumSize: const Size(250, 50),
),
child: Row(
mainAxisAlignment:
MainAxisAlignment.center,
children: [
const Text(
'Cedula',
style: TextStyle(
color: Color(0xFF2BA4EC),
fontSize: 17,
),
),
const SizedBox(width: 15),
Icon(
imageCedulaBytes != null
? Icons.check
: Icons.file_upload_outlined,
color: const Color(0xFF2BA4EC),
size: 30,
),
],
),
),
SizedBox(height: _space),
TextFormField(
readOnly: true,
onTap: () async {
final String? profesion =
(await Navigator.pushNamed(
context, '/profession'))
as String?;
if (profesion != null) {
setState(() {
_profession = profesion;
});
}
},
decoration: InputDecoration(
prefixIcon: const Icon(
Icons.assignment_ind_rounded),
suffixIcon:
const Icon(Icons.arrow_drop_down),
hintStyle: profession == ''
? const TextStyle()
: const TextStyle(
color: Colors.black87),
hintText: profession == ''
? 'Profesión (Obligatorio)'
: profession),
),
SizedBox(height: _space),
ElevatedButton(
onPressed: () {
_selectFileCertificado(true);
},
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFFD6F4FF),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(50),
),
elevation: 0,
minimumSize: const Size(250, 50),
),
child: Row(
mainAxisAlignment:
MainAxisAlignment.center,
children: [
const Text(
'Certificado profesional',
style: TextStyle(
color: Color(0xFF2BA4EC),
fontSize: 17,
),
),
const SizedBox(width: 15),
Icon(
imageCertificadoBytes != null
? Icons.check
: Icons.file_upload_outlined,
color: const Color(0xFF2BA4EC),
size: 30,
),
],
),
),
SizedBox(height: _space),
TextFormField(
controller: _especializacionController,
decoration: const InputDecoration(
prefixIcon:
Icon(Icons.assignment_ind_rounded),
hintText: 'Especialización'),
),
SizedBox(height: _space),
ElevatedButton(
onPressed: () async {
_selectFilesEspecializaciones(true);
},
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFFD6F4FF),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(50),
),
elevation: 0,
minimumSize: const Size(250, 50),
),
child: Row(
mainAxisAlignment:
MainAxisAlignment.center,
children: [
const Text(
'Especialización',
style: TextStyle(
color: Color(0xFF2BA4EC),
fontSize: 17,
),
),
const SizedBox(width: 15),
Icon(
imagesEspecializacionsBytes.isEmpty
? Icons.file_upload_outlined
: Icons.check,
color: const Color(0xFF2BA4EC),
size: 30,
),
],
),
),
],
),
),
),
Container(
margin: const EdgeInsets.only(top: 40),
padding: const EdgeInsets.symmetric(
horizontal: 20, vertical: 15),
decoration: BoxDecoration(
color: const Color(0xFFD6F4FF),
borderRadius: BorderRadius.circular(20),
boxShadow: [
BoxShadow(
color: Colors.grey.withOpacity(0.5),
spreadRadius: 1,
blurRadius: 5,
offset: const Offset(1, 3),
),
],
),
child: const Row(
children: [
Icon(
Icons.error_outline,
size: 27,
color: Colors.black54,
),
SizedBox(width: 15),
Expanded(
child: Text(
'Si tienes más de una especialidad, por favor, adjunta un archivo con el diploma de respaldo para cada una de ellas y sepáralos por comas. ¡Gracias!',
style: TextStyle(
color: Colors.black, fontSize: 14),
),
),
],
),
),
Container(
alignment: Alignment.bottomCenter,
margin: const EdgeInsets.only(
top: 30, right: 20, left: 20, bottom: 30),
child: ElevatedButton(
onPressed: () {
if (_formKey.currentState!.validate()) {
sendInfo();
}
},
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFF2BA4EC),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(50),
),
elevation: 0,
minimumSize: const Size(250, 50),
maximumSize: const Size(350, 50),
),
child: const Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
'Enviar información',
style: TextStyle(
color: Colors.white,
fontWeight: FontWeight.bold,
fontSize: 17,
),
),
SizedBox(width: 15),
Icon(
Icons.send,
color: Colors.white,
size: 20,
),
],
),
),
),
],
),
),
),
),
),
),
);
}
}
@@ -1,95 +0,0 @@
import 'package:flutter/material.dart';
import 'package:prosappco/src/components/pop_appbar.dart';
class ProfessionalRevisionScreen extends StatefulWidget {
const ProfessionalRevisionScreen({super.key});
@override
State<ProfessionalRevisionScreen> createState() =>
_ProfessionalRevisionScreenState();
}
class _ProfessionalRevisionScreenState
extends State<ProfessionalRevisionScreen> {
@override
Widget build(BuildContext context) {
return SafeArea(
child: Scaffold(
appBar: PopAppbar(
onPressed: () {
Navigator.pop(context);
},
label: 'Perfil profesional',
),
body: SingleChildScrollView(
child: Container(
color: Colors.white,
child: Padding(
padding: const EdgeInsets.only(top: 40),
child: Column(
children: [
const Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
Icons.access_time,
color: Color(0xFF2BA4EC),
),
SizedBox(width: 8),
Text('Información en revisión.',
style: TextStyle(
color: Color(0xFF2BA4EC),
fontSize: 17,
fontWeight: FontWeight.w500)),
],
),
const Image(
image: AssetImage('images/checklist.gif'),
width: 300,
),
Container(
margin: const EdgeInsets.symmetric(horizontal: 40),
decoration: BoxDecoration(
color: const Color(0xFFD6F4FF),
borderRadius: BorderRadius.circular(30),
boxShadow: [
BoxShadow(
color: Colors.grey.withOpacity(0.5),
spreadRadius: 2,
blurRadius: 5,
offset:
const Offset(0, 3), // changes position of shadow
),
],
),
padding:
const EdgeInsets.symmetric(vertical: 15, horizontal: 25),
child: const Wrap(
alignment: WrapAlignment.start, // Centra el contenido
children: [
SizedBox(
width: 350,
child: Row(
children: [
Expanded(
child: Text(
'Gracias por proporcionar tu información. Actualmente, estamos revisando tus datos y una vez aprobados, podrás acceder al perfil profesional sin problemas. Te notificaremos tan pronto como tu cuenta esté lista. ¡Gracias por tu paciencia!',
style: TextStyle(
fontSize: 14,
),
),
),
],
),
),
],
),
),
],
),
),
),
),
));
}
}
@@ -1,981 +0,0 @@
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:firebase_storage/firebase_storage.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:get/get.dart';
import 'dart:io';
import 'package:intl/intl.dart';
import 'package:prosappco/src/authentication/authentication_repository.dart';
import 'package:prosappco/src/components/pop_appbar.dart';
import 'package:prosappco/src/controllers/add_name_email_city.dart';
import 'package:prosappco/src/presentation/widgets/profile/birth_date_picker.dart';
import 'package:prosappco/src/presentation/screens/city.dart';
import 'package:prosappco/src/presentation/screens/new_number.dart';
import 'package:prosappco/src/presentation/screens/new_password.dart';
import 'package:prosappco/src/presentation/widgets/shared/primary_checkbox.dart';
import 'package:prosappco/src/presentation/widgets/shared/warning_snackbar.dart';
import 'package:prosappco/src/providers/user_provider.dart';
import 'package:prosappco/src/services/select_image_profile.dart';
import 'package:prosappco/src/presentation/widgets/profile/gender_dropdown.dart';
import 'package:prosappco/src/presentation/widgets/shared/primary_button.dart';
import 'package:provider/provider.dart';
import '../../../components/photo_view.dart';
import 'package:universal_html/html.dart' as html;
class ProfileScreen extends StatefulWidget {
const ProfileScreen({Key? key}) : super(key: key);
@override
State<ProfileScreen> createState() => _ProfileScreenState();
}
class _ProfileScreenState extends State<ProfileScreen> {
File? imagen_to_upload;
final DateFormat formatter = DateFormat('dd/MM/yyyy');
final uid = AuthenticationRepository.instance.getCurrentUserUid();
bool _obscureText = true;
final _formKey = GlobalKey<FormState>();
final controller = Get.put(NameEmailCityController());
final _phoneNumberController = TextEditingController();
final _nameController = TextEditingController();
final _emailController = TextEditingController();
final _passwordController = TextEditingController();
late final FirebaseAuth _auth;
final FirebaseStorage storage = FirebaseStorage.instance;
var photoTemp = '';
var _ciudad = '...';
var _photo = '..../images/perfil-2.png';
String? _email = '';
String gender = '';
String genderDb = '';
DateTime? birthDate;
String birthDateDb = '';
@override
void initState() {
super.initState();
_auth = FirebaseAuth.instance;
final currentUser = _auth.currentUser;
if (currentUser != null && currentUser.phoneNumber != null) {
_phoneNumberController.text = currentUser.phoneNumber!;
}
if (currentUser != null && currentUser.displayName != null) {
_nameController.text = currentUser.displayName!;
}
if (currentUser != null && currentUser.email != null) {
_emailController.text = currentUser.email!;
}
if (gender.isEmpty) {
AuthenticationRepository.instance.getGender(uid.toString()).then(
(String s) => setState(() {
genderDb = s;
}),
);
}
if (birthDate == null) {
AuthenticationRepository.instance.getBirthday(uid.toString()).then(
(String s) => setState(() {
if (s.isNotEmpty) {
birthDateDb = s;
}
}),
);
}
_email = currentUser?.email;
if (_ciudad == '...') {
AuthenticationRepository.instance.getCity(uid.toString()).then(
(String s) => setState(() {
_ciudad = s;
}),
);
}
if (_photo == '...') {
AuthenticationRepository.instance.getPhoto(uid.toString()).then(
(String s) => setState(() {
_photo = s;
}),
);
}
}
Future<void> updateImage(image) async {
try {
await FirebaseFirestore.instance
.collection('users')
.doc(uid)
.update({'photo': image});
} catch (e) {
try {
await FirebaseFirestore.instance
.collection('users')
.doc(uid)
.set({'photo': image});
} catch (e) {
print('Error al agregar la imagen de perfil: $e');
}
print('Error al actualizar la imagen de perfil: $e');
}
}
Future<bool> uploadImage(File image) async {
final now = DateTime.now();
final formattedDate = DateFormat('HHmmssddMMyyyy').format(now);
final milliseconds = (now.microsecondsSinceEpoch / 1000).round();
final random = '$formattedDate$milliseconds';
Reference ref =
storage.ref().child('users').child(uid!).child('profile').child(random);
final UploadTask uploadTask = ref.putFile(image);
final TaskSnapshot snapshot = await uploadTask.whenComplete(() => true);
photoTemp = ref.fullPath;
if (snapshot.state == TaskState.success) {
return true;
} else {
return false;
}
}
Future<Widget> downloadImage(Reference ref) async {
try {
if (_photo == '...' || _photo.isEmpty) {
return GestureDetector(
onTap: () {
_showChoiceDialog(context);
},
child: Container(
margin: const EdgeInsets.symmetric(vertical: 50),
width: 100,
height: 100,
decoration: BoxDecoration(
color: const Color(0xFF2BA4EC),
borderRadius: BorderRadius.circular(50),
),
child: const Icon(
Icons.person,
color: Colors.white,
size: 90,
),
),
);
} else {
final imageData = await ref.getData();
if (imageData != null) {
final widgetImage = GestureDetector(
onTap: () {
_showChoiceDialog(context);
},
child: Container(
margin: const EdgeInsets.symmetric(vertical: 50),
child: ClipOval(
child: Image.memory(
imageData,
width: 60,
height: 60,
fit: BoxFit.cover,
),
),
),
);
return widgetImage;
} else {
return GestureDetector(
onTap: () {
_showChoiceDialog(context);
},
child: Container(
margin: const EdgeInsets.symmetric(vertical: 50),
width: 100,
height: 100,
decoration: BoxDecoration(
color: const Color(0xFF2BA4EC),
borderRadius: BorderRadius.circular(50),
),
child: const Icon(
Icons.person,
color: Colors.white,
size: 90,
),
),
);
}
}
} catch (e) {
return GestureDetector(
onTap: () {
_showChoiceDialog(context);
},
child: Container(
margin: const EdgeInsets.symmetric(vertical: 50),
width: 100,
height: 100,
decoration: BoxDecoration(
color: const Color(0xFF2BA4EC),
borderRadius: BorderRadius.circular(50),
),
child: const Icon(
Icons.person,
color: Colors.white,
size: 90,
),
),
);
}
}
Future<void> updateInfo() async {
final currentUser = _auth.currentUser;
final currentPhoneNumber = _auth.currentUser!.phoneNumber;
String newName = _nameController.text.trim();
String newEmail = _emailController.text.trim();
String newPassword = _passwordController.text.trim();
if (gender.isNotEmpty) {
try {
await FirebaseFirestore.instance
.collection('users')
.doc(uid)
.set({'gender': gender}, SetOptions(merge: true));
genderDb = gender;
} catch (e) {
print('Error al actualizar el genero: $e');
}
}
if (birthDate != null) {
try {
await FirebaseFirestore.instance
.collection('users')
.doc(uid)
.set({'birth_date': birthDate.toString()}, SetOptions(merge: true));
birthDateDb = birthDate.toString();
} catch (e) {
print('Error al actualizar la fecha de nacimiento: $e');
}
}
setState(() {});
if (newName.isEmpty) {
Get.snackbar(
'Nombre Invalido',
'Ingresa un nombre válido.',
snackPosition: SnackPosition.BOTTOM,
);
return;
}
if (enableLoginWithEmail) {
if (newEmail.isEmpty) {
Get.snackbar(
'Correo Invalido',
'Ingresa un email válido.',
snackPosition: SnackPosition.BOTTOM,
);
return;
}
}
if (currentUser?.displayName != newName) {
try {
await FirebaseAuth.instance.currentUser!.updateDisplayName(newName);
await FirebaseFirestore.instance
.collection('users')
.doc(uid)
.update({'name': newName, 'lowerName': newName.toLowerCase()});
} catch (e) {
print('Error al actualizar el nombre: $e');
}
}
String? selectedCity = _ciudad;
if (kIsWeb) {
try {
await FirebaseFirestore.instance
.collection('users')
.doc(uid)
.update({'city': selectedCity});
} catch (e) {
print('Error al actualizar la ciudad: $e');
}
}
if (enableLoginWithEmail) {
if (currentUser?.email != newEmail) {
if (newPassword.isNotEmpty) {
bool updateEmailSuccess =
await updateEmailAndPassword(newEmail, newPassword);
if (updateEmailSuccess) {
await FirebaseFirestore.instance
.collection('users')
.doc(uid)
.update({'email': newEmail});
} else {
return;
}
} else {
Get.snackbar(
'Contraseña Invalida',
'Por favor ingresa una contraseña.',
snackPosition: SnackPosition.BOTTOM,
);
}
}
}
try {
await FirebaseFirestore.instance
.collection('users')
.doc(uid)
.update({'phoneNumber': currentPhoneNumber});
} catch (e) {
print('$e');
}
try {
if (imagen_to_upload == null) {
} else {
final uploaded = await uploadImage(imagen_to_upload!);
updateImage(photoTemp);
}
} catch (e) {
print('Error al actualizar la imagen de perfil $e');
}
WarningSnackbar.show(
title: 'Informacion actualizada',
message: 'Tu informacion ha sido actualizada con exito.',
icon: const Icon(
Icons.check,
color: Colors.white,
),
backgroundColor: Colors.green,
);
}
Future<bool> updateEmailAndPassword(String email, String password) async {
final User? user = FirebaseAuth.instance.currentUser;
if (user != null) {
try {
await user.updateEmail(email);
await user.updatePassword(password);
return true;
} catch (e) {
WarningSnackbar.show(
title: 'Inicia sesión de nuevo',
message: 'Inicia la sesión de nuevo para guardar los cambios.',
);
AuthenticationRepository.instance.logout(uid!);
}
}
return false;
}
Future<void> _updateEmailAndPassword(
String newEmail, String currentPassword) async {
final user = _auth.currentUser;
if (user!.email! == newEmail) {
Get.snackbar(
'No se puede actualizar',
'El correo actual no puede ser actualizado.',
snackPosition: SnackPosition.BOTTOM,
);
return;
}
if (!newEmail.contains('@') || !newEmail.contains('.')) {
Get.snackbar(
'No se puede actualizar',
'Ingresa un correo electrónico valido.',
snackPosition: SnackPosition.BOTTOM,
);
return;
}
final emailExistsQuery = await FirebaseFirestore.instance
.collection('users')
.where('email', isEqualTo: newEmail)
.get();
if (emailExistsQuery.docs.isNotEmpty) {
Get.snackbar(
'No se puede actualizar',
'El nuevo correo electrónico ya está en uso.',
snackPosition: SnackPosition.BOTTOM,
);
return;
}
try {
final credential = EmailAuthProvider.credential(
email: user.email!, password: currentPassword);
await user.reauthenticateWithCredential(credential);
await user.updateEmail(newEmail);
await FirebaseFirestore.instance
.collection('users')
.doc(uid)
.update({'email': newEmail});
setState(() {
_email = newEmail;
});
WarningSnackbar.show(
title: 'Actualizado exitosamente',
message: 'Correo electronico actualizado correctamente.',
icon: const Icon(Icons.check, color: Colors.white),
backgroundColor: Colors.green,
);
if (Navigator.canPop(context)) {
Navigator.of(context).pop();
}
} catch (e) {
WarningSnackbar.show(
title: 'No se pudo actualizar el correo',
message:
'Verifica tu contraseña actual y asegúrate de que el nuevo correo electrónico no se haya utilizado previamente.',
);
}
}
Future<void> _showEmailUpdateDialog(BuildContext context) async {
TextEditingController emailController = TextEditingController();
TextEditingController passwordController = TextEditingController();
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: const Text('Actualizar Email'),
content: Column(
mainAxisSize: MainAxisSize.min,
children: [
TextField(
controller: emailController,
decoration: const InputDecoration(labelText: 'Nuevo Email'),
),
TextField(
controller: passwordController,
decoration:
const InputDecoration(labelText: 'Contraseña Actual'),
obscureText: true,
),
],
),
actions: [
TextButton(
onPressed: () {
Navigator.of(context).pop();
},
child: const Text(
'Cancelar',
style: TextStyle(color: Colors.grey),
),
),
TextButton(
onPressed: () {
String newEmail = emailController.text.trim();
String currentPassword = passwordController.text.trim();
if (newEmail.isNotEmpty && currentPassword.isNotEmpty) {
_updateEmailAndPassword(newEmail, currentPassword);
}
},
child: const Text(
'Guardar',
style: TextStyle(
color: Colors.blue,
fontWeight: FontWeight.w600,
),
),
),
],
);
},
);
}
bool enableLoginWithEmail = false;
Future<List<City>> _getCities() async {
List<City> citys = [];
if (kIsWeb) {
try {
QuerySnapshot countries = await countriesCollection.get();
for (DocumentSnapshot country in countries.docs) {
String countryName = country.id;
Map<String, dynamic> data = country.data() as Map<String, dynamic>;
Map<String, Map<String, String>> states = {};
for (var entry in data.entries) {
String key = entry.key;
Map<String, String> cityData =
Map<String, String>.from(entry.value);
states[key] = cityData;
}
for (var state in states.entries) {
var citysState = state.value.entries.map((city) => City(
cityName: city.key,
coordsOfCity: city.value,
stateOfCity: state.key,
countryOfCity: countryName,
));
citys.addAll(citysState);
}
}
} catch (e) {
print('Error obteniendo las ciudades: $e');
}
}
return citys;
}
Future<void> _showChoiceDialog(BuildContext context) async {
return showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
content: SingleChildScrollView(
child: ListBody(
children: [
GestureDetector(
child: const Text(
textAlign: TextAlign.center,
"Tomar foto",
style: TextStyle(color: Color(0xFF2BA4EC)),
),
onTap: () async {
final imagen = await getImage(1);
setState(() {
imagen_to_upload = File(imagen[0]!.path);
});
Navigator.of(context).pop();
},
),
const Divider(color: Colors.black54),
GestureDetector(
child: const Text(
textAlign: TextAlign.center,
"Abrir Galería",
style: TextStyle(color: Color(0xFF2BA4EC)),
),
onTap: () async {
final imagen = await getImage(2);
setState(() {
imagen_to_upload = File(imagen[0]!.path);
});
Navigator.of(context).pop();
},
),
],
),
),
);
},
);
}
@override
Widget build(BuildContext context) {
String city = _ciudad.toString();
final userProvider = Provider.of<UserProvider>(context);
return Scaffold(
appBar: PopAppbar(
onPressed: () {
Navigator.pop(context);
},
label: 'Perfil',
),
body: SingleChildScrollView(
reverse: true,
child: Center(
child: Column(
children: [
GestureDetector(
onTap: () {
_showChoiceDialog(context);
},
child: Container(
margin: const EdgeInsets.symmetric(vertical: 20),
child: (imagen_to_upload != null)
? LocalPhoto(file: imagen_to_upload!)
: ReferencePhoto(ref: storage.ref().child(_photo))),
),
Container(
width: 300,
padding: const EdgeInsets.only(top: 0),
child: Form(
key: _formKey,
child: Column(
children: [
TextFormField(
controller: _nameController,
maxLength: 50,
inputFormatters: [
FilteringTextInputFormatter.deny(RegExp(r'\s{2,}')),
],
validator: (value) {
if (value == null || value.trim().isEmpty) {
return 'Porfavor ingrese un nombre.';
}
if (value.trim().length < 5) {
return 'Debe tener al menos 5 caracteres.';
}
return null;
},
decoration: const InputDecoration(
prefixIcon: Icon(Icons.person_outline),
hintText: 'Nombre (Obligatorio)',
),
),
const SizedBox(),
kIsWeb
? FutureBuilder<List<City>>(
future: _getCities(),
builder: (context, snapshot) {
if (snapshot.connectionState ==
ConnectionState.waiting) {
return const Center(
child: CircularProgressIndicator(),
);
} else if (snapshot.hasError) {
return const Center(
child:
Text('Error al obtener las ciudades'),
);
} else {
List<City> filteredCities = snapshot.data!;
return DropdownButtonFormField<String>(
value: _ciudad,
onChanged: (String? newValue) {
setState(() {
_ciudad = newValue!;
});
},
items: filteredCities.map((City city) {
return DropdownMenuItem<String>(
value: city.cityName,
child: Text(city.cityName ?? ''),
);
}).toList(),
decoration: InputDecoration(
prefixIcon: const Icon(Icons.near_me),
hintText: _ciudad,
),
);
}
},
)
: TextFormField(
readOnly: true,
onTap: () async {
final String? ciudad = await Navigator.push(
context,
CupertinoPageRoute(
builder: (BuildContext context) {
return const CityScreen();
},
),
) as String?;
if (ciudad != null) {
setState(() {
_ciudad = ciudad;
});
}
},
decoration: InputDecoration(
prefixIcon: const Icon(Icons.near_me),
suffixIcon: const Icon(Icons.arrow_drop_down),
hintStyle: city == ''
? const TextStyle()
: const TextStyle(color: Colors.black87),
hintText: city == '' ? 'Ciudad' : city,
),
),
const SizedBox(height: 20.0),
TextFormField(
controller: _phoneNumberController,
readOnly: true,
onTap: () {
Navigator.of(context).push(
CupertinoPageRoute(
builder: (BuildContext context) {
return const NewNumberScreen();
},
),
);
},
decoration: const InputDecoration(
prefixIcon: Icon(Icons.phone_android),
suffixIcon: Icon(Icons.edit_outlined),
hintText: '+57',
),
),
genderDb == ''
? const SizedBox(height: 20.0)
: const SizedBox(),
genderDb == ''
? GenderDropdown(
onChanged: (selectedGender) {
setState(() {
gender = selectedGender;
});
},
)
: const SizedBox(),
genderDb == ''
? const SizedBox(height: 20.0)
: const SizedBox(),
birthDateDb == ''
? BirthDatePicker(
onDateSelected: (birthDay) {
setState(() {
birthDate = birthDay;
});
},
controller: TextEditingController(
text: birthDate == null
? ''
: formatter.format(birthDate!),
),
)
: const SizedBox(),
_email != null && _email != ''
? const SizedBox(height: 20.0)
: const SizedBox(),
_email != null && _email != ''
? TextFormField(
onTap: () {
_showEmailUpdateDialog(context);
},
readOnly: true,
controller: _emailController,
decoration: const InputDecoration(
prefixIcon: Icon(Icons.email_outlined),
hintText: 'Email (Obligatorio)',
),
)
: const SizedBox(),
const SizedBox(height: 20),
_email == null || _email == ''
? PrimaryCheckbox(
text:
'Habilitar inicio de sesión con correo (Opcional)',
initialValue: enableLoginWithEmail,
onChanged: (value) {
setState(() {
enableLoginWithEmail = value;
});
},
)
: const SizedBox(),
const SizedBox(height: 15),
enableLoginWithEmail
? Container(
decoration: BoxDecoration(
border: Border.all(
color: Colors.blue,
width: 0.5,
),
borderRadius: BorderRadius.circular(10),
),
padding: const EdgeInsets.all(10),
child: Column(
children: [
TextFormField(
controller: _emailController,
validator: (String? value) {
if (enableLoginWithEmail) {
if (value == null || value.isEmpty) {
return 'Por favor ingrese un email';
}
final RegExp emailRegExp = RegExp(
r'^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$');
if (!emailRegExp.hasMatch(value)) {
return 'Por favor ingrese un email válido';
}
return null;
} else {
return null;
}
},
decoration: const InputDecoration(
prefixIcon: Icon(Icons.email_outlined),
hintText: 'Email',
),
),
const SizedBox(height: 20.0),
_email != null && _email != ''
? const SizedBox.shrink()
: TextFormField(
controller: _passwordController,
obscureText: _obscureText,
validator: (value) {
if (enableLoginWithEmail) {
if (value == null ||
value.isEmpty) {
return 'Por favor ingrese una contraseña.';
}
if (value.length < 5) {
return 'Debe tener al menos 5 caracteres.';
}
return null;
} else {
return null;
}
},
decoration: InputDecoration(
prefixIcon: const Icon(
Icons.lock_outline),
suffixIcon: IconButton(
icon: Icon(
_obscureText
? Icons.visibility
: Icons.visibility_off,
color: Colors.grey,
),
onPressed: () {
setState(() {
_obscureText =
!_obscureText;
});
},
),
hintText: 'Contraseña'),
),
_email != null && _email != ''
? const SizedBox.shrink()
: const SizedBox(height: 20),
Container(
margin: const EdgeInsets.only(
left: 5,
right: 5,
top: 5,
bottom: 5,
),
padding: const EdgeInsets.symmetric(
horizontal: 10, vertical: 8),
decoration: BoxDecoration(
color: const Color(0xFFD6F4FF),
borderRadius: BorderRadius.circular(20),
boxShadow: [
BoxShadow(
color: Colors.grey.withOpacity(0.5),
spreadRadius: 1,
blurRadius: 5,
offset: const Offset(1, 3),
),
],
),
child: const Row(
children: [
Icon(
Icons.error_outline,
size: 20,
color: Colors.black54,
),
SizedBox(width: 10),
Expanded(
child: Text(
'Al habilitar el inicio de sesión con correo, se cerrara la sesión actual.',
style: TextStyle(
color: Colors.black54,
fontSize: 13,
),
),
),
],
),
)
],
),
)
: const SizedBox(),
],
),
),
),
Container(
alignment: Alignment.bottomCenter,
margin: const EdgeInsets.only(top: 35),
padding: const EdgeInsets.only(bottom: 30),
child: Column(
children: [
_email != null && _email != ''
? PrimaryButton(
onPressed: () {
Navigator.push(
context,
CupertinoPageRoute(
builder: (BuildContext context) {
return NewPasswordScreen();
},
),
);
},
text: 'Cambiar Contraseña',
minWidth: 300,
minHeight: 45,
)
: const SizedBox(height: 20),
const SizedBox(height: 40),
PrimaryButton(
onPressed: () async {
if (_formKey.currentState!.validate()) {
if (kIsWeb) {
await updateInfo().whenComplete(() {
html.window.location.reload();
});
} else {
await updateInfo();
}
}
await userProvider.updateUserDataAndScores();
},
text: 'Guardar',
),
],
),
),
],
),
),
),
);
}
}
@@ -1,811 +0,0 @@
import 'dart:io';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_storage/firebase_storage.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_animate/flutter_animate.dart';
import 'package:get/get.dart';
import 'package:prosappco/src/authentication/authentication_repository.dart';
import 'package:prosappco/src/components/banner_photo.dart';
import 'package:prosappco/src/components/pop_appbar.dart';
import 'package:prosappco/src/components/schedule_picker.dart';
import 'package:prosappco/src/models/setting_model.dart';
import 'package:prosappco/src/presentation/screens/horario.dart';
import 'package:prosappco/src/presentation/screens/professional.dart';
import 'package:prosappco/src/presentation/screens/professional_direccion.dart';
import 'package:prosappco/src/presentation/widgets/shared/primary_button.dart';
import 'package:prosappco/src/presentation/widgets/shared/primary_checkbox.dart';
import 'package:prosappco/src/services/select_image_profile.dart';
class ProfileProScreen extends StatefulWidget {
const ProfileProScreen({super.key});
@override
State<ProfileProScreen> createState() => _ProfileProScreenState();
}
class _ProfileProScreenState extends State<ProfileProScreen> {
final uid = AuthenticationRepository.instance.getCurrentUserUid();
final TextEditingController _opcionalAddressController =
TextEditingController();
final TextEditingController _tarifaController = TextEditingController();
File? image_portada;
File? imagen_to_upload;
bool tarifaValue = false;
bool domicilioValue = false;
bool sitioValue = false;
var photoTemp = '';
var _photo = '...';
var _direccion = '...';
var _ubicacion = '...';
var _opcionalAddress = '...';
int _tarifa = 0;
SettingModel? settings;
bool nequiValue = false;
bool banktransferValue = false;
bool datafoneValue = false;
Map<String, Schedule>? _horarios;
Map<String, bool> paymentMethods = {
'Nequi': false,
'Transferencia Bancaria': false,
'Datafono': false,
};
@override
void initState() {
super.initState();
if (settings == null) {
SettingModel.getSettings().then((SettingModel value) => setState(
() => settings = value,
));
}
final uid = AuthenticationRepository.instance.getCurrentUserUid();
if (_photo == '...') {
AuthenticationRepository.instance.getBanner(uid.toString()).then(
(String s) => setState(
() {
_photo = s;
},
),
);
}
if (_horarios == null) {
Schedule.getHorarios(uid.toString()).then(
(Map<String, Schedule> data) {
setState(() {
_horarios = data;
});
},
);
}
if (_direccion == '...') {
AuthenticationRepository.instance
.getAddress(uid.toString())
.then((String s) => setState(() {
_direccion = s;
}));
}
if (_ubicacion == '...') {
AuthenticationRepository.instance.getUbicacion(uid.toString()).then(
(String s) => setState(
() {
_ubicacion = s;
if (_ubicacion == 'ambos') {
sitioValue = true;
domicilioValue = true;
} else if (_ubicacion == 'sitio') {
sitioValue = true;
} else if (_ubicacion == 'domicilio') {
domicilioValue = true;
}
},
),
);
}
if (_opcionalAddress == '...') {
AuthenticationRepository.instance.getOpcionalAddress(uid.toString()).then(
(String s) => setState(
() {
_opcionalAddress = s;
if (_opcionalAddress != '...') {
_opcionalAddressController.text = _opcionalAddress;
}
},
),
);
}
if (_tarifa == 0) {
AuthenticationRepository.instance.getTarifa(uid.toString()).then(
(s) => setState(
() {
_tarifa = s;
if (_tarifa != 0) {
tarifaValue = true;
_tarifaController.text = _tarifa.toString();
}
},
),
);
}
loadPaymentMethods();
}
void createSchedules() async {
if (_horarios == null || _horarios!.isEmpty) {
final defaultSchedule = {
'1': {
'habilitado': false,
'jornadaContinua': false,
'range1Hour1': null,
'range1Hour2': null,
'range2Hour1': null,
'range2Hour2': null
},
'2': {
'habilitado': false,
'jornadaContinua': false,
'range1Hour1': null,
'range1Hour2': null,
'range2Hour1': null,
'range2Hour2': null
},
'3': {
'habilitado': false,
'jornadaContinua': false,
'range1Hour1': null,
'range1Hour2': null,
'range2Hour1': null,
'range2Hour2': null
},
'4': {
'habilitado': false,
'jornadaContinua': false,
'range1Hour1': null,
'range1Hour2': null,
'range2Hour1': null,
'range2Hour2': null
},
'5': {
'habilitado': false,
'jornadaContinua': false,
'range1Hour1': null,
'range1Hour2': null,
'range2Hour1': null,
'range2Hour2': null
},
'6': {
'habilitado': false,
'jornadaContinua': false,
'range1Hour1': null,
'range1Hour2': null,
'range2Hour1': null,
'range2Hour2': null
},
'7': {
'habilitado': false,
'jornadaContinua': false,
'range1Hour1': null,
'range1Hour2': null,
'range2Hour1': null,
'range2Hour2': null
}
};
try {
await FirebaseFirestore.instance
.collection('users')
.doc(uid)
.update({'horario': defaultSchedule});
Navigator.pop(context);
} catch (e) {
print(e);
}
} else {
Navigator.pushReplacement(
context,
CupertinoPageRoute(
builder: (BuildContext context) {
return HorarioScreen(
horarios: _horarios!,
);
},
),
);
}
}
Future<void> updateInfo() async {
if (_opcionalAddressController.text.isNotEmpty) {
FirebaseFirestore.instance
.collection('users')
.doc(uid)
.update({'opcional_address': _opcionalAddressController.text});
}
if (tarifaValue && _tarifaController.text.isNotEmpty) {
FirebaseFirestore.instance
.collection('users')
.doc(uid)
.update({'tarifa': int.parse(_tarifaController.text)});
} else {
FirebaseFirestore.instance
.collection('users')
.doc(uid)
.update({'tarifa': 0});
}
if (settings?.domicilios == false) {
if (sitioValue) {
FirebaseFirestore.instance
.collection('users')
.doc(uid)
.update({'ubicacion': 'sitio'});
} else {
Get.snackbar(
'Elige como vas a dar tu servicio',
'Selecciona si tu servicio es a domicilio o en tu consultorio.',
snackPosition: SnackPosition.TOP,
backgroundColor: Colors.black.withOpacity(0.2),
messageText: const Text(
'Selecciona si tu servicio es a domicilio o en tu consultorio.',
style: TextStyle(color: Colors.white),
),
);
FirebaseFirestore.instance
.collection('users')
.doc(uid)
.update({'ubicacion': null});
return;
}
} else {
if (domicilioValue && sitioValue) {
FirebaseFirestore.instance
.collection('users')
.doc(uid)
.update({'ubicacion': 'ambos'});
} else if (domicilioValue) {
FirebaseFirestore.instance
.collection('users')
.doc(uid)
.update({'ubicacion': 'domicilio'});
} else if (sitioValue) {
FirebaseFirestore.instance
.collection('users')
.doc(uid)
.update({'ubicacion': 'sitio'});
} else {
Get.snackbar(
'Elige como vas a dar tu servicio',
'Selecciona si tu servicio es a domicilio o en tu consultorio.',
snackPosition: SnackPosition.TOP,
backgroundColor: Colors.black.withOpacity(0.2),
messageText: const Text(
'Selecciona si tu servicio es a domicilio o en tu consultorio.',
style: TextStyle(color: Colors.white),
),
);
return;
}
}
try {
if (imagen_to_upload == null) {
} else {
updateImage(photoTemp);
//image
}
} catch (e) {
print('Error al actualizar la imagen de perfil $e');
}
updatePaymentMethods();
if (settings?.domicilios == false && sitioValue == false) {
Get.defaultDialog(
title: 'Donde vas a dar tu servicio?',
middleText:
'Si no eliges servicio en sitio, no serás visible para los usuarios.',
actions: [
ElevatedButton(
onPressed: () {
Get.back();
},
child: const Text('Entendido'),
),
],
);
} else {
Get.snackbar(
'Información actualizada',
'Tu información ha sido actualizada con éxito.',
snackPosition: SnackPosition.TOP,
);
Navigator.pop(context);
}
return;
}
Future<void> updateImage(image) async {
try {
await FirebaseFirestore.instance
.collection('users')
.doc(uid)
.update({'banner': image});
} catch (e) {
try {
await FirebaseFirestore.instance
.collection('users')
.doc(uid)
.set({'banner': image});
} catch (e) {
print('Error al agregar la imagen de perfil: $e');
}
print('Error al actualizar la imagen de perfil: $e');
}
}
Future<bool> uploadImage(File image) async {
final String namefile = image.path.split('/').last;
Reference ref = storage
.ref()
.child('users')
.child(uid!)
.child('profile')
.child(namefile);
final UploadTask uploadTask = ref.putFile(image);
final TaskSnapshot snapshot = await uploadTask.whenComplete(() => true);
photoTemp = ref.fullPath;
if (snapshot.state == TaskState.success) {
return true;
} else {
return false;
}
}
Future<void> _showChoiceDialog(BuildContext context) async {
return showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
content: SingleChildScrollView(
child: ListBody(
children: [
GestureDetector(
child: const Text(
textAlign: TextAlign.center,
"Tomar foto",
style: TextStyle(color: Color(0xFF2BA4EC)),
),
onTap: () async {
final imagen = await getImage(1);
setState(() {
imagen_to_upload = File(imagen[0]!.path);
});
Navigator.of(context).pop();
},
),
const Divider(color: Colors.black54),
GestureDetector(
child: const Text(
textAlign: TextAlign.center,
"Abrir Galería",
style: TextStyle(color: Color(0xFF2BA4EC)),
),
onTap: () async {
final imagen = await getImage(2);
setState(() {
imagen_to_upload = File(imagen[0]!.path);
});
Navigator.of(context).pop();
},
),
],
),
),
);
},
);
}
void toggleDomicilio(bool newValue) {
setState(() {
domicilioValue = newValue;
if (newValue == false && sitioValue == false) {
sitioValue = true;
}
});
}
void toggleSitio(bool newValue) {
setState(() {
sitioValue = newValue;
if (newValue == false && domicilioValue == false) {
domicilioValue = true;
}
});
}
void updatePaymentMethods() {
FirebaseFirestore.instance.collection('users').doc(uid).update({
'paymentMethods': paymentMethods,
});
}
void loadPaymentMethods() {
FirebaseFirestore.instance.collection('users').doc(uid).get().then((doc) {
if (doc.exists) {
setState(() {
paymentMethods = Map<String, bool>.from(doc['paymentMethods'] ?? {});
});
}
});
}
@override
Widget build(BuildContext context) {
String address = _direccion.toString();
return Scaffold(
appBar: PopAppbar(
onPressed: () {
Navigator.pop(context);
},
label: 'Perfil profesional'),
body: SingleChildScrollView(
reverse: true,
child: Column(
children: [
GestureDetector(
onTap: () {
_showChoiceDialog(context);
},
child: Container(
child: (imagen_to_upload != null)
? LocalPhoto(
file: imagen_to_upload!,
)
: ReferenceBannerPhoto(
ref: storage.ref().child(_photo),
),
),
),
if (settings?.tarifas == true)
const Divider(
color: Colors.white,
height: 12,
),
if (settings?.tarifas == true)
customSwitch(
'Tarifa',
false,
(value) {
tarifaValue = value;
},
),
tarifaValue
? Padding(
padding:
const EdgeInsets.only(left: 40, right: 40, bottom: 15),
child: Column(
children: [
TextFormField(
controller: _tarifaController,
keyboardType: TextInputType.number,
decoration: const InputDecoration(
prefixIcon: Icon(Icons.attach_money),
hintText: 'COP'),
inputFormatters: [
FilteringTextInputFormatter.digitsOnly,
],
),
],
)
.animate()
.moveY(duration: const Duration(milliseconds: 100)),
)
: const SizedBox(),
if (settings?.domicilios == true)
const Divider(
color: Colors.white,
height: 12,
),
if (settings?.domicilios == true)
customSwitch(
'Servicio a domicilio', domicilioValue, toggleDomicilio),
const Divider(),
customSwitch('Servicio en sitio', sitioValue, toggleSitio),
sitioValue
? Padding(
padding:
const EdgeInsets.only(left: 40, right: 40, bottom: 15),
child: Column(
children: [
TextFormField(
readOnly: true,
onTap: () async {
final String? direccion = await Navigator.push(
context,
CupertinoPageRoute(
builder: (BuildContext context) {
return const ProfessionalDireccionScreen();
},
),
) as String?;
if (direccion != null) {
setState(() {
_direccion = direccion;
});
}
},
decoration: InputDecoration(
prefixIcon: const Icon(Icons.near_me),
hintStyle: address == ''
? const TextStyle()
: const TextStyle(color: Colors.black87),
hintText: address == '' ? 'Dirección' : address),
),
const SizedBox(height: 10),
TextFormField(
controller: _opcionalAddressController,
decoration: const InputDecoration(
hintText: 'Oficina / Piso / Conjunto'),
),
],
)
.animate()
.moveY(duration: const Duration(milliseconds: 100)),
)
: const SizedBox(),
const Divider(),
Padding(
padding: const EdgeInsets.only(left: 30, right: 30, bottom: 30),
child: SizedBox(
width: double.infinity,
child: Column(
children: [
const Text(
'Metodos de pago',
style: TextStyle(
color: Colors.black,
fontSize: 17,
),
),
for (var entry in paymentMethods.entries)
PrimaryCheckbox(
text: entry.key,
initialValue: entry.value,
onChanged: (value) {
setState(() {
paymentMethods[entry.key] = value;
});
},
),
],
),
),
),
const Padding(
padding: EdgeInsets.symmetric(horizontal: 30),
child: SizedBox(
width: double.infinity,
child: Text(
'Horario estandar',
style: TextStyle(
color: Colors.black,
fontSize: 17,
),
),
),
),
GestureDetector(
onTap: () {
createSchedules();
},
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 10),
child: Table(
defaultColumnWidth: const IntrinsicColumnWidth(),
children: [
TableRow(
children: [
TableCell(
child: Container(
padding: const EdgeInsets.all(8.0),
child: const Text('Lunes'),
),
),
TableCell(
child: Container(
padding: const EdgeInsets.all(8.0),
child: Text(timeList(_horarios?['1'], context)),
),
),
],
),
TableRow(
children: [
TableCell(
child: Container(
padding: const EdgeInsets.all(8.0),
child: const Text('Martes'),
),
),
TableCell(
child: Container(
padding: const EdgeInsets.all(8.0),
child: Text(timeList(_horarios?['2'], context)),
),
),
],
),
TableRow(
children: [
TableCell(
child: Container(
padding: const EdgeInsets.all(8.0),
child: const Text('Miercoles'),
),
),
TableCell(
child: Container(
padding: const EdgeInsets.all(8.0),
child: Text(timeList(_horarios?['3'], context)),
),
),
],
),
TableRow(
children: [
TableCell(
child: Container(
padding: const EdgeInsets.all(8.0),
child: const Text('Jueves'),
),
),
TableCell(
child: Container(
padding: const EdgeInsets.all(8.0),
child: Text(timeList(_horarios?['4'], context)),
),
),
],
),
TableRow(
children: [
TableCell(
child: Container(
padding: const EdgeInsets.all(8.0),
child: const Text('Viernes'),
),
),
TableCell(
child: Container(
padding: const EdgeInsets.all(8.0),
child: Text(timeList(_horarios?['5'], context)),
),
),
],
),
TableRow(
children: [
TableCell(
child: Container(
padding: const EdgeInsets.all(8.0),
child: const Text('Sabado'),
),
),
TableCell(
child: Container(
padding: const EdgeInsets.all(8.0),
child: Text(timeList(_horarios?['6'], context)),
),
),
],
),
TableRow(
children: [
TableCell(
child: Container(
padding: const EdgeInsets.all(8.0),
child: const Text('Domingo'),
),
),
TableCell(
child: Container(
padding: const EdgeInsets.all(8.0),
child: Text(timeList(_horarios?['7'], context)),
),
),
],
),
],
),
),
),
const SizedBox(height: 10),
PrimaryButton(
onPressed: () {
updateInfo();
},
text: 'Guardar',
),
const SizedBox(height: 20),
],
),
),
);
}
Widget customSwitch(
String text,
bool switchValue,
ValueChanged<bool> onChanged,
) {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 30),
child: SizedBox(
height: 40,
child: Row(
children: [
Expanded(
child: Text(
text,
style: const TextStyle(
fontSize: 17,
fontWeight: FontWeight.w500,
color: Colors.black,
),
),
),
Transform.scale(
scale: 1.2,
child: Switch(
value: switchValue,
onChanged: onChanged,
),
),
],
),
),
);
}
String timeList(Schedule? schedule, BuildContext context) {
if (schedule == null) {
return 'N/A';
}
if (!schedule.habilitado) {
return 'N/A';
}
if (schedule.jornadaContinua) {
return '${schedule.range1Hour1?.format(context).toString()} - ${schedule.range2Hour2?.format(context).toString()}';
} else {
return '${schedule.range1Hour1?.format(context).toString()} - ${schedule.range1Hour2?.format(context).toString()}; ${schedule.range2Hour1?.format(context).toString()} - ${schedule.range2Hour2?.format(context).toString()}';
}
}
}
@@ -1,678 +0,0 @@
import 'dart:io';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_storage/firebase_storage.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_animate/flutter_animate.dart';
import 'package:get/get.dart';
import 'package:prosappco/src/authentication/authentication_repository.dart';
import 'package:prosappco/src/components/pop_appbar.dart';
import 'package:prosappco/src/components/primary_btn.dart';
import 'package:prosappco/src/components/schedule_picker.dart';
import 'package:prosappco/src/models/setting_model.dart';
import 'package:prosappco/src/presentation/screens/horario.dart';
import 'package:prosappco/src/presentation/screens/professional.dart';
import 'package:prosappco/src/presentation/screens/ubicacion.dart';
class ProfileProWebScreen extends StatefulWidget {
const ProfileProWebScreen({super.key});
@override
State<ProfileProWebScreen> createState() => _ProfileProWebScreenState();
}
class _ProfileProWebScreenState extends State<ProfileProWebScreen> {
final uid = AuthenticationRepository.instance.getCurrentUserUid();
final TextEditingController _opcionalAddressController =
TextEditingController();
final TextEditingController _tarifaController = TextEditingController();
final TextEditingController _ubicationController = TextEditingController();
double latUser = 0.0;
double lngUser = 0.0;
bool domicilioValue = true;
bool tarifaValue = false;
bool sitioValue = false;
var photoTemp = '';
var _direccion = '...';
var _ubicacion = '...';
var _opcionalAddress = '...';
int _tarifa = 0;
SettingModel? settings;
Map<String, Schedule>? _horarios;
@override
void initState() {
super.initState();
if (settings == null) {
SettingModel.getSettings().then(
(SettingModel value) => setState(() => settings = value),
);
}
final uid = AuthenticationRepository.instance.getCurrentUserUid();
if (_horarios == null) {
Schedule.getHorarios(uid.toString()).then(
(Map<String, Schedule> data) {
setState(() {
_horarios = data;
});
},
);
}
if (_direccion == '...') {
AuthenticationRepository.instance
.getAddress(uid.toString())
.then((String s) => setState(() {
_direccion = s;
_ubicationController.text = _direccion;
}));
}
if (_ubicacion == '...') {
AuthenticationRepository.instance.getUbicacion(uid.toString()).then(
(String s) => setState(
() {
_ubicacion = s;
if (_ubicacion == 'ambos') {
sitioValue = true;
domicilioValue = true;
} else if (_ubicacion == 'sitio') {
sitioValue = true;
} else if (_ubicacion == 'domicilio') {
domicilioValue = true;
}
},
),
);
}
if (_opcionalAddress == '...') {
AuthenticationRepository.instance.getOpcionalAddress(uid.toString()).then(
(String s) => setState(
() {
_opcionalAddress = s;
if (_opcionalAddress != '...') {
_opcionalAddressController.text = _opcionalAddress;
}
},
),
);
}
if (_tarifa == 0) {
AuthenticationRepository.instance.getTarifa(uid.toString()).then(
(s) => setState(
() {
_tarifa = s;
if (_tarifa != 0) {
tarifaValue = true;
_tarifaController.text = _tarifa.toString();
}
},
),
);
}
}
Future<void> updateInfo() async {
if (_ubicationController.text.isNotEmpty) {
FirebaseFirestore.instance.collection('users').doc(uid).update({
'address': _ubicationController.text,
if (latUser != 0.0) 'latitude': latUser,
if (latUser != 0.0) 'longitude': lngUser,
});
}
if (_opcionalAddressController.text.isNotEmpty) {
FirebaseFirestore.instance
.collection('users')
.doc(uid)
.update({'opcional_address': _opcionalAddressController.text});
}
if (tarifaValue && _tarifaController.text.isNotEmpty) {
FirebaseFirestore.instance
.collection('users')
.doc(uid)
.update({'tarifa': int.parse(_tarifaController.text)});
} else {
FirebaseFirestore.instance
.collection('users')
.doc(uid)
.update({'tarifa': 0});
}
if (settings?.domicilios == false) {
if (sitioValue) {
FirebaseFirestore.instance
.collection('users')
.doc(uid)
.update({'ubicacion': 'sitio'});
} else {
Get.snackbar(
'Elige como vas a dar tu servicio',
'Selecciona si tu servicio es a domicilio o en tu consultorio.',
snackPosition: SnackPosition.TOP,
backgroundColor: Colors.black.withOpacity(0.2),
messageText: const Text(
'Selecciona si tu servicio es a domicilio o en tu consultorio.',
style: TextStyle(color: Colors.white),
),
);
FirebaseFirestore.instance
.collection('users')
.doc(uid)
.update({'ubicacion': null});
return;
}
} else {
if (domicilioValue && sitioValue) {
FirebaseFirestore.instance
.collection('users')
.doc(uid)
.update({'ubicacion': 'ambos'});
} else if (domicilioValue) {
FirebaseFirestore.instance
.collection('users')
.doc(uid)
.update({'ubicacion': 'domicilio'});
} else if (sitioValue) {
FirebaseFirestore.instance
.collection('users')
.doc(uid)
.update({'ubicacion': 'sitio'});
} else {
Get.snackbar(
'Elige como vas a dar tu servicio',
'Selecciona si tu servicio es a domicilio o en tu consultorio.',
snackPosition: SnackPosition.TOP,
backgroundColor: Colors.black.withOpacity(0.2),
messageText: const Text(
'Selecciona si tu servicio es a domicilio o en tu consultorio.',
style: TextStyle(color: Colors.white),
),
);
return;
}
}
if (settings?.domicilios == false && sitioValue == false) {
Get.defaultDialog(
title: 'Donde vas a dar tu servicio?',
middleText:
'Si no eliges servicio en sitio, no serás visible para los usuarios.',
actions: [
ElevatedButton(
onPressed: () {
Get.back();
},
child: const Text('Entendido'),
),
],
);
} else {
Get.snackbar(
'Información actualizada',
'Tu información ha sido actualizada con éxito.',
snackPosition: SnackPosition.TOP,
);
Navigator.pop(context);
}
return;
}
Future<void> updateImage(image) async {
try {
await FirebaseFirestore.instance
.collection('users')
.doc(uid)
.update({'banner': image});
} catch (e) {
try {
await FirebaseFirestore.instance
.collection('users')
.doc(uid)
.set({'banner': image});
} catch (e) {
print('Error al agregar la imagen de perfil: $e');
}
print('Error al actualizar la imagen de perfil: $e');
}
}
void createSchedules() async {
if (_horarios == null || _horarios!.isEmpty) {
final defaultSchedule = {
'1': {
'habilitado': false,
'jornadaContinua': false,
'range1Hour1': null,
'range1Hour2': null,
'range2Hour1': null,
'range2Hour2': null
},
'2': {
'habilitado': false,
'jornadaContinua': false,
'range1Hour1': null,
'range1Hour2': null,
'range2Hour1': null,
'range2Hour2': null
},
'3': {
'habilitado': false,
'jornadaContinua': false,
'range1Hour1': null,
'range1Hour2': null,
'range2Hour1': null,
'range2Hour2': null
},
'4': {
'habilitado': false,
'jornadaContinua': false,
'range1Hour1': null,
'range1Hour2': null,
'range2Hour1': null,
'range2Hour2': null
},
'5': {
'habilitado': false,
'jornadaContinua': false,
'range1Hour1': null,
'range1Hour2': null,
'range2Hour1': null,
'range2Hour2': null
},
'6': {
'habilitado': false,
'jornadaContinua': false,
'range1Hour1': null,
'range1Hour2': null,
'range2Hour1': null,
'range2Hour2': null
},
'7': {
'habilitado': false,
'jornadaContinua': false,
'range1Hour1': null,
'range1Hour2': null,
'range2Hour1': null,
'range2Hour2': null
}
};
try {
await FirebaseFirestore.instance
.collection('users')
.doc(uid)
.update({'horario': defaultSchedule});
Navigator.pop(context);
} catch (e) {
print(e);
}
} else {
Navigator.pushReplacement(
context,
CupertinoPageRoute(
builder: (BuildContext context) {
return HorarioScreen(
horarios: _horarios!,
);
},
),
);
}
}
Future<bool> uploadImage(File image) async {
final String namefile = image.path.split('/').last;
Reference ref = storage
.ref()
.child('users')
.child(uid!)
.child('profile')
.child(namefile);
final UploadTask uploadTask = ref.putFile(image);
final TaskSnapshot snapshot = await uploadTask.whenComplete(() => true);
photoTemp = ref.fullPath;
if (snapshot.state == TaskState.success) {
return true;
} else {
return false;
}
}
void toggleDomicilio(bool newValue) {
setState(() {
domicilioValue = newValue;
if (newValue == false && sitioValue == false) {
sitioValue = true;
}
});
}
void toggleSitio(bool newValue) {
setState(() {
sitioValue = newValue;
if (newValue == false && domicilioValue == false) {
domicilioValue = true;
}
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: PopAppbar(
onPressed: () {
Navigator.pop(context);
},
label: 'Perfil profesional'),
body: SingleChildScrollView(
reverse: true,
child: Column(
children: [
if (settings?.tarifas == true)
const Divider(
color: Colors.white,
height: 12,
),
if (settings?.tarifas == true)
customSwitch(
'Tarifa',
tarifaValue,
(value) {
tarifaValue = value;
},
),
tarifaValue
? Padding(
padding:
const EdgeInsets.only(left: 40, right: 40, bottom: 15),
child: Column(
children: [
TextFormField(
controller: _tarifaController,
keyboardType: TextInputType.number,
decoration: const InputDecoration(
prefixIcon: Icon(Icons.attach_money),
hintText: 'COP'),
inputFormatters: [
FilteringTextInputFormatter.digitsOnly,
],
),
],
)
.animate()
.moveY(duration: const Duration(milliseconds: 100)),
)
: const SizedBox(),
if (settings?.domicilios == true)
const Divider(
color: Colors.white,
height: 12,
),
if (settings?.domicilios == true)
customSwitch(
'Servicio a domicilio', domicilioValue, toggleDomicilio),
const Divider(),
customSwitch('Servicio en sitio', sitioValue, toggleSitio),
sitioValue
? Padding(
padding:
const EdgeInsets.only(left: 40, right: 40, bottom: 15),
child: Column(
children: [
TextFormField(
controller: _ubicationController,
readOnly: true,
onTap: () async {
final List<dynamic> datos = await Navigator.push(
context,
CupertinoPageRoute(
builder: (BuildContext context) {
return const UbicacionScreen();
},
),
);
if (datos.length == 3) {
final formattedAddress = datos[0];
final lat = datos[1];
final lng = datos[2];
setState(() {
_ubicationController.text = formattedAddress;
latUser = lat;
lngUser = lng;
});
}
},
decoration: const InputDecoration(
hintText: 'Escribe tu ubicación',
prefixIcon: Icon(Icons.near_me),
),
),
const SizedBox(height: 10),
TextFormField(
controller: _opcionalAddressController,
decoration: const InputDecoration(
hintText: 'Oficina / Piso / Conjunto'),
),
],
)
.animate()
.moveY(duration: const Duration(milliseconds: 100)),
)
: const SizedBox(),
const Divider(),
const Padding(
padding: EdgeInsets.symmetric(horizontal: 30),
child: SizedBox(
width: double.infinity,
child: Text(
'Horario estandar',
style: TextStyle(
color: Colors.black,
fontSize: 15,
),
),
),
),
GestureDetector(
onTap: () {
createSchedules();
},
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 10),
child: Table(
defaultColumnWidth: const IntrinsicColumnWidth(),
children: [
TableRow(
children: [
TableCell(
child: Container(
padding: const EdgeInsets.all(8.0),
child: const Text('Lunes'),
),
),
TableCell(
child: Container(
padding: const EdgeInsets.all(8.0),
child: Text(timeList(_horarios?['1'], context)),
),
),
],
),
TableRow(
children: [
TableCell(
child: Container(
padding: const EdgeInsets.all(8.0),
child: const Text('Martes'),
),
),
TableCell(
child: Container(
padding: const EdgeInsets.all(8.0),
child: Text(timeList(_horarios?['2'], context)),
),
),
],
),
TableRow(
children: [
TableCell(
child: Container(
padding: const EdgeInsets.all(8.0),
child: const Text('Miercoles'),
),
),
TableCell(
child: Container(
padding: const EdgeInsets.all(8.0),
child: Text(timeList(_horarios?['3'], context)),
),
),
],
),
TableRow(
children: [
TableCell(
child: Container(
padding: const EdgeInsets.all(8.0),
child: const Text('Jueves'),
),
),
TableCell(
child: Container(
padding: const EdgeInsets.all(8.0),
child: Text(timeList(_horarios?['4'], context)),
),
),
],
),
TableRow(
children: [
TableCell(
child: Container(
padding: const EdgeInsets.all(8.0),
child: const Text('Viernes'),
),
),
TableCell(
child: Container(
padding: const EdgeInsets.all(8.0),
child: Text(timeList(_horarios?['5'], context)),
),
),
],
),
TableRow(
children: [
TableCell(
child: Container(
padding: const EdgeInsets.all(8.0),
child: const Text('Sabado'),
),
),
TableCell(
child: Container(
padding: const EdgeInsets.all(8.0),
child: Text(timeList(_horarios?['6'], context)),
),
),
],
),
TableRow(
children: [
TableCell(
child: Container(
padding: const EdgeInsets.all(8.0),
child: const Text('Domingo'),
),
),
TableCell(
child: Container(
padding: const EdgeInsets.all(8.0),
child: Text(timeList(_horarios?['7'], context)),
),
),
],
),
],
),
),
),
PrimaryButtom(
onPressed: () {
updateInfo();
},
label: 'Guardar'),
const SizedBox(
height: 20,
)
],
),
),
);
}
Widget customSwitch(
String text, bool switchValue, ValueChanged<bool> onChanged) {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 30),
child: SizedBox(
height: 40,
child: Row(
children: [
Expanded(
child: Text(
text,
style: const TextStyle(
fontSize: 15,
color: Colors.black,
),
),
),
Transform.scale(
scale: 1.2,
child: Switch(
value: switchValue,
onChanged: onChanged,
),
),
],
),
),
);
}
String timeList(Schedule? schedule, BuildContext context) {
if (schedule == null) {
return 'N/A';
}
if (!schedule.habilitado) {
return 'N/A';
}
if (schedule.jornadaContinua) {
return '${schedule.range1Hour1?.format(context).toString()} - ${schedule.range2Hour2?.format(context).toString()}';
} else {
return '${schedule.range1Hour1?.format(context).toString()} - ${schedule.range1Hour2?.format(context).toString()}; ${schedule.range2Hour1?.format(context).toString()} - ${schedule.range2Hour2?.format(context).toString()}';
}
}
}
@@ -1,693 +0,0 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:get/get.dart';
import 'package:prosappco/src/authentication/authentication_repository.dart';
import 'package:prosappco/src/components/bottom_sheet.dart';
import 'package:prosappco/src/components/column_padding.dart';
import 'package:prosappco/src/presentation/widgets/shared/primary_button.dart';
import 'package:prosappco/src/controllers/register_controller.dart';
import 'package:prosappco/src/models/setting_model.dart';
import 'package:prosappco/src/providers/user_provider.dart';
import 'package:prosappco/src/presentation/screens/web_view.dart';
import 'package:provider/provider.dart';
import 'package:responsive_builder/responsive_builder.dart';
import 'package:url_launcher/url_launcher.dart';
class RegisterScreen extends StatefulWidget {
const RegisterScreen({super.key});
@override
State<RegisterScreen> createState() => _RegisterScreenState();
}
class _RegisterScreenState extends State<RegisterScreen> {
bool _obscureText = true;
final controller = Get.put(RegisterController());
final _formKey = GlobalKey<FormState>();
bool _isChecked = false;
SettingModel? settings;
void _launchURL(String url) async {
if (await canLaunch(url)) {
await launch(url, forceSafariVC: false, forceWebView: false);
} else {
throw 'No se pudo abrir el enlace $url';
}
}
@override
void initState() {
super.initState();
if (settings == null) {
SettingModel.getSettings().then(
(SettingModel value) => setState(() {
settings = value;
}),
);
}
}
@override
Widget build(BuildContext context) {
return ScreenTypeLayout.builder(
mobile: (BuildContext context) => _mobileView(context),
tablet: (BuildContext context) => _mobileView(context),
desktop: (BuildContext context) => _desktopView(context),
);
}
Widget _mobileView(BuildContext context) {
bool isIOS = Theme.of(context).platform == TargetPlatform.iOS;
return BottomSheetExpanded(
horizontalPadding: 10,
children: [
Row(
children: <Widget>[
IconButton(
icon: const Icon(
Icons.arrow_back,
size: 30,
),
onPressed: () {
Navigator.pop(context);
},
),
const Text(
'Registro',
style: TextStyle(
color: Color(0xFF262626),
fontSize: 30.0,
fontWeight: FontWeight.bold,
),
textAlign: TextAlign.right,
),
],
),
ColumnPadding(
alineacion: MainAxisAlignment.start,
padding: const EdgeInsets.symmetric(horizontal: 25, vertical: 20),
children: [
!isIOS && !kIsWeb && settings?.google == true
? Padding(
padding: const EdgeInsets.only(bottom: 20),
child: ElevatedButton(
onPressed: () async {
await AuthenticationRepository.instance
.signInWithGoogle();
},
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFF2BA4EC),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(50),
),
elevation: 0,
minimumSize: const Size(230, 60),
),
child: const Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
'Entrar con Google ',
style: TextStyle(
color: Colors.white,
fontWeight: FontWeight.bold,
fontSize: 18,
),
),
SizedBox(width: 5),
FaIcon(FontAwesomeIcons.google),
],
)),
)
: const SizedBox(),
!isIOS && !kIsWeb && settings?.google == true
? const Padding(
padding: EdgeInsets.symmetric(vertical: 0),
child: Row(
children: <Widget>[
Expanded(
child: Divider(
color: Colors.black38,
thickness: 1,
),
),
Padding(
padding: EdgeInsets.symmetric(horizontal: 10),
child: Text("ó"),
),
Expanded(
child: Divider(
color: Colors.black38,
thickness: 1,
),
),
],
),
)
: const SizedBox(),
Form(
key: _formKey,
child: Column(
children: [
const Padding(
padding: EdgeInsets.only(bottom: 5),
child: Align(
alignment: Alignment.topLeft,
child: Text('Email',
style: TextStyle(
fontSize: 18.0, color: Color(0xFF65676B))),
)),
Padding(
padding: const EdgeInsets.only(bottom: 18),
child: TextFormField(
controller: controller.email,
validator: (String? value) {
if (value == null || value.isEmpty) {
return 'Por favor ingresa un email';
}
final RegExp emailRegExp =
RegExp(r'^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$');
if (!emailRegExp.hasMatch(value)) {
return 'Por favor ingresa un email válido';
}
return null;
},
decoration: const InputDecoration(
border: OutlineInputBorder(
borderSide: BorderSide(color: Color(0xFFECECEC)),
borderRadius: BorderRadius.all(
Radius.circular(50),
),
),
enabledBorder: OutlineInputBorder(
borderSide: BorderSide(color: Color(0xFFECECEC)),
borderRadius: BorderRadius.all(
Radius.circular(50),
),
),
focusedBorder: OutlineInputBorder(
borderSide: BorderSide(color: Color(0xFFECECEC)),
borderRadius: BorderRadius.all(
Radius.circular(50),
),
),
errorBorder: OutlineInputBorder(
borderSide:
BorderSide(color: Color.fromARGB(255, 184, 0, 0)),
borderRadius: BorderRadius.all(
Radius.circular(50),
),
),
hintText: 'Hello@gmail.com',
fillColor: Color.fromARGB(255, 239, 239, 239),
filled: true,
prefixIcon: Icon(Icons.email_outlined),
hintStyle: TextStyle(
color: Colors.grey,
),
),
),
),
const Padding(
padding: EdgeInsets.only(bottom: 5),
child: Align(
alignment: Alignment.topLeft,
child: Text('Password',
style: TextStyle(
fontSize: 18.0, color: Color(0xFF65676B))),
)),
Padding(
padding: const EdgeInsets.only(bottom: 25),
child: TextFormField(
controller: controller.password,
obscureText: _obscureText,
validator: (value) {
if (value == null || value.isEmpty) {
return 'Por favor ingresa una contraseña';
}
if (value.length <= 6) {
return 'Contraseña muy corta';
}
return null;
},
decoration: InputDecoration(
errorBorder: const OutlineInputBorder(
borderSide: BorderSide(
color: Color.fromARGB(255, 184, 0, 0)),
borderRadius: BorderRadius.all(
Radius.circular(50),
)),
enabledBorder: const OutlineInputBorder(
borderSide: BorderSide(color: Color(0xFFECECEC)),
borderRadius: BorderRadius.all(
Radius.circular(50),
)),
border: const OutlineInputBorder(
borderSide: BorderSide(color: Color(0xFFECECEC)),
borderRadius: BorderRadius.all(
Radius.circular(50),
)),
focusedBorder: const OutlineInputBorder(
borderSide: BorderSide(color: Color(0xFFECECEC)),
borderRadius: BorderRadius.all(
Radius.circular(50),
)),
hintText: 'Contraseña',
hintStyle: const TextStyle(
color: Colors.grey,
),
fillColor: const Color.fromARGB(255, 239, 239, 239),
filled: true,
prefixIcon: const Icon(Icons.lock_outline),
suffixIcon: IconButton(
icon: Icon(
_obscureText
? Icons.visibility
: Icons.visibility_off,
color: Colors.grey,
),
onPressed: () {
setState(() {
_obscureText = !_obscureText;
});
},
),
),
),
),
],
),
),
Padding(
padding: const EdgeInsets.only(bottom: 20),
child: Row(
mainAxisAlignment:
MainAxisAlignment.center, // Centra horizontalmente
children: <Widget>[
Checkbox(
value: _isChecked,
onChanged: (value) {
setState(() {
_isChecked = value!;
});
},
),
GestureDetector(
onTap: () {
if (kIsWeb) {
_launchURL(settings?.terminosCondiciones ?? '');
} else {
Navigator.push(
context,
CupertinoPageRoute(
builder: (BuildContext context) {
return WebViewScreen(
label: 'Políticas de privacidad',
link: settings?.terminosCondiciones ?? '',
);
},
),
);
}
},
child: const Text(
'Acepto los términos y condiciones.',
style: TextStyle(
color: Color(0xFF65676B),
decoration: TextDecoration.underline,
),
),
),
],
),
),
Padding(
padding: const EdgeInsets.only(bottom: 25),
child: Center(
child: PrimaryButton(
onPressed: () {
if (_formKey.currentState!.validate()) {
RegisterController.instance
.registerUser(
controller.email.text.trim(),
controller.password.text.trim(),
)
.then((value) =>
Provider.of<UserProvider>(context, listen: false)
.initUserProvider());
}
},
text: 'Registrarme',
isEnabled: _isChecked,
),
),
),
RichText(
text: TextSpan(
style: const TextStyle(
fontSize: 16.0,
color: Color(0xFF65676B),
fontFamily: 'Poppins',
),
children: [
const TextSpan(text: 'Ya estas registrado? '),
WidgetSpan(
child: GestureDetector(
onTap: () {
Navigator.pushReplacementNamed(context, '/login');
},
child: const Text(
'Iniciar Sesión',
style: TextStyle(
fontSize: 16.0,
color: Color(0xFF2BA4EC),
fontWeight: FontWeight.w600,
),
),
),
),
],
),
)
],
),
],
);
}
Widget _desktopView(BuildContext context) {
double height = MediaQuery.of(context).size.height;
double width = MediaQuery.of(context).size.width;
return Scaffold(
backgroundColor: const Color(0xFFD6F4FF),
body: SizedBox(
height: height,
width: width,
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start,
children: [
Expanded(
child: SizedBox(
height: height,
child: const Center(
child: Image(
image: AssetImage('images/logo_prosapp.png'),
),
),
),
),
Expanded(
child: Container(
padding: EdgeInsets.symmetric(horizontal: width * 0.07),
color: Colors.white,
height: height,
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Container(
padding: const EdgeInsets.symmetric(
horizontal: 0, vertical: 20),
child: Row(
children: <Widget>[
IconButton(
icon: const Icon(
Icons.arrow_back,
size: 30,
),
onPressed: () {
Navigator.pop(context);
},
),
SizedBox(width: width * 0.01),
const Text(
'Registro',
style: TextStyle(
color: Color(0xFF262626),
fontSize: 30.0,
fontWeight: FontWeight.bold,
),
textAlign: TextAlign.right,
),
],
),
),
Container(
padding: const EdgeInsets.symmetric(
horizontal: 0, vertical: 20),
child: Column(
children: [
Form(
key: _formKey,
child: Column(
children: [
const Padding(
padding: EdgeInsets.only(bottom: 5),
child: Align(
alignment: Alignment.topLeft,
child: Text('Email',
style: TextStyle(
fontSize: 18.0,
color: Color(0xFF65676B))),
)),
Padding(
padding: const EdgeInsets.only(bottom: 18),
child: TextFormField(
controller: controller.email,
validator: (String? value) {
if (value == null || value.isEmpty) {
return 'Por favor ingresa un email';
}
final RegExp emailRegExp = RegExp(
r'^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$');
if (!emailRegExp.hasMatch(value)) {
return 'Por favor ingresa un email válido';
}
return null;
},
decoration: const InputDecoration(
border: OutlineInputBorder(
borderSide: BorderSide(
color: Color(0xFFECECEC)),
borderRadius: BorderRadius.all(
Radius.circular(50),
)),
enabledBorder: OutlineInputBorder(
borderSide: BorderSide(
color: Color(0xFFECECEC)),
borderRadius: BorderRadius.all(
Radius.circular(50),
)),
focusedBorder: OutlineInputBorder(
borderSide: BorderSide(
color: Color(0xFFECECEC)),
borderRadius: BorderRadius.all(
Radius.circular(50),
)),
errorBorder: OutlineInputBorder(
borderSide: BorderSide(
color: Color.fromARGB(
255, 184, 0, 0)),
borderRadius: BorderRadius.all(
Radius.circular(50),
)),
hintText: 'Hello@gmail.com',
fillColor:
Color.fromARGB(255, 239, 239, 239),
filled: true,
prefixIcon: Icon(Icons.email_outlined),
hintStyle: TextStyle(
color: Colors.grey,
),
),
),
),
const Padding(
padding: EdgeInsets.only(bottom: 5),
child: Align(
alignment: Alignment.topLeft,
child: Text('Password',
style: TextStyle(
fontSize: 18.0,
color: Color(0xFF65676B))),
)),
Padding(
padding: const EdgeInsets.only(bottom: 25),
child: TextFormField(
controller: controller.password,
obscureText: _obscureText,
validator: (value) {
if (value == null || value.isEmpty) {
return 'Por favor ingresa una contraseña';
}
if (value.length <= 6) {
return 'Contraseña muy corta';
}
return null;
},
decoration: InputDecoration(
errorBorder: const OutlineInputBorder(
borderSide: BorderSide(
color: Color.fromARGB(
255, 184, 0, 0)),
borderRadius: BorderRadius.all(
Radius.circular(50),
)),
enabledBorder: const OutlineInputBorder(
borderSide: BorderSide(
color: Color(0xFFECECEC)),
borderRadius: BorderRadius.all(
Radius.circular(50),
)),
border: const OutlineInputBorder(
borderSide: BorderSide(
color: Color(0xFFECECEC)),
borderRadius: BorderRadius.all(
Radius.circular(50),
)),
focusedBorder: const OutlineInputBorder(
borderSide: BorderSide(
color: Color(0xFFECECEC)),
borderRadius: BorderRadius.all(
Radius.circular(50),
)),
hintText: 'Contraseña',
hintStyle: const TextStyle(
color: Colors.grey,
),
fillColor: const Color.fromARGB(
255, 239, 239, 239),
filled: true,
prefixIcon:
const Icon(Icons.lock_outline),
suffixIcon: IconButton(
icon: Icon(
_obscureText
? Icons.visibility
: Icons.visibility_off,
color: Colors.grey,
),
onPressed: () {
setState(() {
_obscureText = !_obscureText;
});
},
),
),
),
),
],
),
),
Padding(
padding: const EdgeInsets.symmetric(vertical: 20),
child: Row(
mainAxisAlignment: MainAxisAlignment
.center, // Centra horizontalmente
children: <Widget>[
Checkbox(
value: _isChecked,
onChanged: (value) {
setState(() {
_isChecked = value!;
});
},
),
GestureDetector(
onTap: () {
if (kIsWeb) {
_launchURL(
settings?.terminosCondiciones ?? '');
} else {
Navigator.push(
context,
CupertinoPageRoute(
builder: (BuildContext context) {
return WebViewScreen(
label: 'Políticas de privacidad',
link: settings
?.terminosCondiciones ??
'',
);
},
),
);
}
},
child: const Text(
'Acepto los términos y condiciones.',
style: TextStyle(
color: Color(0xFF65676B),
decoration: TextDecoration.underline,
),
),
),
],
),
),
Padding(
padding: const EdgeInsets.only(bottom: 25),
child: Center(
child: PrimaryButton(
onPressed: () {
if (_formKey.currentState!.validate()) {
RegisterController.instance
.registerUser(
controller.email.text.trim(),
controller.password.text.trim(),
)
.then((value) =>
Provider.of<UserProvider>(context,
listen: false)
.initUserProvider());
}
},
text: 'Registrarme',
isEnabled: _isChecked,
)),
),
RichText(
text: TextSpan(
style: const TextStyle(
fontSize: 16.0,
color: Color(0xFF65676B),
fontFamily: 'Poppins',
),
children: [
const TextSpan(text: 'Ya estas registrado? '),
WidgetSpan(
child: GestureDetector(
onTap: () {
Navigator.pushReplacementNamed(
context, '/login');
},
child: const Text(
'Iniciar Sesión',
style: TextStyle(
fontSize: 16.0,
color: Color(0xFF2BA4EC),
fontWeight: FontWeight.w600,
),
),
),
),
],
),
)
],
),
),
],
),
),
),
],
),
),
);
}
}
@@ -1,115 +0,0 @@
import 'package:flutter/material.dart';
import 'package:flutter_rating_bar/flutter_rating_bar.dart';
import 'package:prosappco/src/authentication/authentication_repository.dart';
import 'package:prosappco/src/components/photo_view.dart';
import 'package:prosappco/src/components/pop_appbar.dart';
import 'package:prosappco/src/models/scores_model.dart';
class ReputationProScreen extends StatefulWidget {
const ReputationProScreen({
super.key,
});
@override
State<ReputationProScreen> createState() => _ReputationProScreenState();
}
class _ReputationProScreenState extends State<ReputationProScreen> {
ScoresModel? scoresModel;
@override
void initState() {
super.initState();
final uid = AuthenticationRepository.instance.getCurrentUserUid();
if (scoresModel == null) {
ScoresModel.scoreTo(uid.toString(), true, true).then(
(ScoresModel s) => setState(() => scoresModel = s),
);
}
}
Widget _scoreList(List<ScoreDetailModel> list) {
if (list.isEmpty) {
return const Padding(
padding: EdgeInsets.symmetric(vertical: 40),
child: Center(
child: Text('Sin calificaciones'),
),
);
} else {
return Column(
children: list.map((e) => _scoreItem(e)).toList(),
);
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: PopAppbar(
onPressed: () {
Navigator.pop(context);
},
label: 'Reputación'),
body: SingleChildScrollView(
child: _scoreList(scoresModel?.details ?? []),
),
);
}
Widget _scoreItem(ScoreDetailModel scoreDetails) {
return ListTile(
onTap: () {},
leading: ReferencePhoto(
ref: scoreDetails.avatar,
size: 50,
sizeCircle: 50,
sizeIcon: 35,
),
title: Row(
children: [
RatingBar.builder(
initialRating: scoreDetails.score,
minRating: 1,
direction: Axis.horizontal,
allowHalfRating: true,
itemCount: 5,
itemSize: 22,
maxRating: 5,
itemPadding: const EdgeInsets.symmetric(horizontal: 0),
itemBuilder: (context, _) => const Icon(
Icons.star,
color: Color(0xFF2BA4EC),
),
onRatingUpdate: (rating) {},
ignoreGestures: true,
),
Text(
' (${scoreDetails.score})',
style: const TextStyle(color: Colors.black54, fontSize: 13),
)
],
),
subtitle: Row(
children: [
Expanded(
child: Text.rich(
TextSpan(
children: [
TextSpan(
text: '${scoreDetails.name}, ',
style: const TextStyle(fontSize: 15, color: Colors.black),
),
TextSpan(
text: '"${scoreDetails.comment}"',
style: const TextStyle(fontSize: 15, color: Colors.grey),
),
],
),
),
),
],
));
}
}
@@ -1,115 +0,0 @@
import 'package:flutter/material.dart';
import 'package:flutter_rating_bar/flutter_rating_bar.dart';
import 'package:prosappco/src/authentication/authentication_repository.dart';
import 'package:prosappco/src/components/photo_view.dart';
import 'package:prosappco/src/components/pop_appbar.dart';
import 'package:prosappco/src/models/scores_model.dart';
class ReputationScreen extends StatefulWidget {
const ReputationScreen({
super.key,
});
@override
State<ReputationScreen> createState() => _ReputationScreenState();
}
class _ReputationScreenState extends State<ReputationScreen> {
ScoresModel? scoresModel;
@override
void initState() {
super.initState();
final uid = AuthenticationRepository.instance.getCurrentUserUid();
if (scoresModel == null) {
ScoresModel.scoreTo(uid.toString(), false, true).then(
(ScoresModel s) => setState(() => scoresModel = s),
);
}
}
Widget _scoreList(List<ScoreDetailModel> list) {
if (list.isEmpty) {
return const Padding(
padding: EdgeInsets.symmetric(vertical: 40),
child: Center(
child: Text('Sin calificaciones'),
),
);
} else {
return Column(
children: list.map((e) => _scoreItem(e)).toList(),
);
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: PopAppbar(
onPressed: () {
Navigator.pop(context);
},
label: 'Reputación'),
body: SingleChildScrollView(
child: _scoreList(scoresModel?.details ?? []),
),
);
}
Widget _scoreItem(ScoreDetailModel scoreDetails) {
return ListTile(
onTap: () {},
leading: ReferencePhoto(
ref: scoreDetails.avatar,
size: 50,
sizeCircle: 50,
sizeIcon: 35,
),
title: Row(
children: [
RatingBar.builder(
initialRating: scoreDetails.score,
minRating: 1,
direction: Axis.horizontal,
allowHalfRating: true,
itemCount: 5,
itemSize: 22,
maxRating: 5,
itemPadding: const EdgeInsets.symmetric(horizontal: 0),
itemBuilder: (context, _) => const Icon(
Icons.star,
color: Color(0xFF2BA4EC),
),
onRatingUpdate: (rating) {},
ignoreGestures: true,
),
Text(
' (${scoreDetails.score})',
style: const TextStyle(color: Colors.black54, fontSize: 13),
)
],
),
subtitle: Row(
children: [
Expanded(
child: Text.rich(
TextSpan(
children: [
TextSpan(
text: '${scoreDetails.name}, ',
style: const TextStyle(fontSize: 15, color: Colors.black),
),
TextSpan(
text: '"${scoreDetails.comment}"',
style: const TextStyle(fontSize: 15, color: Colors.grey),
),
],
),
),
),
],
));
}
}
@@ -1,82 +0,0 @@
import 'package:flutter/material.dart';
import 'package:prosappco/src/components/pop_appbar.dart';
import 'package:prosappco/src/components/primary_btn.dart';
class RequestSentScreen extends StatelessWidget {
const RequestSentScreen({super.key});
@override
Widget build(BuildContext context) {
return SafeArea(
child: Scaffold(
appBar: PopAppbar(
onPressed: () {
Navigator.pushReplacementNamed(context, '/servicio');
},
label: 'Solicitud enviada',
),
body: Center(
child: Column(
children: [
const Padding(
padding: EdgeInsets.only(top: 30, bottom: 30),
child: Icon(
Icons.check_circle_outline,
size: 35,
color: Color(0xFF35A8ED),
),
),
const Padding(
padding: EdgeInsets.only(bottom: 0, left: 30, right: 30),
child: Text(
'Información enviada con éxito.',
textAlign: TextAlign.center,
style: TextStyle(color: Color(0xFF2BA4EC), fontSize: 20),
),
),
Container(
margin: const EdgeInsets.only(
left: 40, right: 40, top: 50, bottom: 100),
padding:
const EdgeInsets.symmetric(horizontal: 20, vertical: 15),
decoration: BoxDecoration(
color: const Color(0xFFD6F4FF),
borderRadius: BorderRadius.circular(20),
boxShadow: [
BoxShadow(
color: Colors.grey.withOpacity(0.5),
spreadRadius: 1,
blurRadius: 5,
offset: const Offset(1, 3),
),
],
),
child: const SizedBox(
width: 350,
child: Row(
children: [
Expanded(
child: Text(
"¡Gracias por suministrar tu información! Revisaremos los datos proporcionados y, una vez confirmados, podrás convertirte en un profesional registrado en ProsApp. ¡Esperamos contar contigo pronto!",
style: TextStyle(color: Colors.black, fontSize: 14),
),
)
],
),
),
),
const Expanded(child: SizedBox()),
PrimaryButtom(
onPressed: () {
Navigator.pushReplacementNamed(context, '/servicio');
},
label: 'Inicio',
),
const SizedBox(height: 20),
],
),
),
),
);
}
}
@@ -1,126 +0,0 @@
import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:prosappco/src/components/pop_appbar.dart';
import 'package:prosappco/src/components/primary_btn.dart';
import 'package:prosappco/src/controllers/login_email_controller.dart';
class ResetPasswordScreen extends StatelessWidget {
const ResetPasswordScreen({super.key});
@override
Widget build(BuildContext context) {
final controller = Get.put(LoginEmailController());
return Scaffold(
appBar: PopAppbar(
onPressed: () {
Navigator.pop(context);
},
label: 'Restablecer Contraseña'),
body: SafeArea(
child: GestureDetector(
onTap: () => FocusScope.of(context).unfocus(),
child: Center(
child: Container(
padding: const EdgeInsets.all(15),
color: Colors.transparent,
width: MediaQuery.of(context).size.width * 0.9,
child: Column(
children: [
const SizedBox(height: 20),
const Text(
'Restablecer contraseña',
style: TextStyle(fontWeight: FontWeight.w800, fontSize: 25),
textAlign: TextAlign.center,
),
const SizedBox(height: 20),
const Text(
'Ingresa ingresa tu correo electrónico y te enviaremos un enlace para restablecer tu contraseña',
style: TextStyle(color: Colors.grey, fontSize: 12),
textAlign: TextAlign.center,
),
const SizedBox(height: 40),
const Padding(
padding: EdgeInsets.only(bottom: 5),
child: Align(
alignment: Alignment.topLeft,
child: Text('Email',
style: TextStyle(
fontSize: 18.0, color: Color(0xFF65676B))),
),
),
TextFormField(
controller: controller.email,
validator: (String? value) {
if (value == null || value.isEmpty) {
return 'Por favor, ingresa un Email';
}
final RegExp emailRegExp =
RegExp(r'^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$');
if (!emailRegExp.hasMatch(value)) {
return 'Por favor, ingresa un Email válido';
}
return null;
},
decoration: const InputDecoration(
border: OutlineInputBorder(
borderSide: BorderSide(color: Color(0xFFECECEC)),
borderRadius: BorderRadius.all(
Radius.circular(50),
)),
enabledBorder: OutlineInputBorder(
borderSide: BorderSide(color: Color(0xFFECECEC)),
borderRadius: BorderRadius.all(
Radius.circular(50),
)),
focusedBorder: OutlineInputBorder(
borderSide: BorderSide(color: Color(0xFFECECEC)),
borderRadius: BorderRadius.all(
Radius.circular(50),
)),
errorBorder: OutlineInputBorder(
borderSide:
BorderSide(color: Color.fromARGB(255, 184, 0, 0)),
borderRadius: BorderRadius.all(
Radius.circular(50),
)),
hintText: 'Hello@gmail.com',
fillColor: Color.fromARGB(255, 239, 239, 239),
filled: true,
prefixIcon: Icon(Icons.email_outlined),
hintStyle: TextStyle(
color: Colors.grey,
),
),
),
const SizedBox(height: 80),
PrimaryButtom(
onPressed: () async {
try {
await FirebaseAuth.instance.sendPasswordResetEmail(
email: controller.email.text.trim());
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text(
'Se ha enviado un enlace de restablecimiento de contraseña a tu correo electrónico.'),
),
);
Navigator.pop(context);
} catch (e) {
ScaffoldMessenger.of(context)
.showSnackBar(const SnackBar(
content: Text(
'Hubo un error al enviar el enlace de restablecimiento de contraseña.'),
));
}
},
label: 'Enviar'),
],
),
),
),
)),
);
}
}
-182
View File
@@ -1,182 +0,0 @@
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_storage/firebase_storage.dart';
import 'package:flutter/material.dart';
import 'package:flutter_rating_bar/flutter_rating_bar.dart';
import 'package:intl/intl.dart';
import 'package:prosappco/src/components/photo_view.dart';
import 'package:prosappco/src/components/pop_appbar.dart';
import 'package:prosappco/src/models/event_model.dart';
import 'package:prosappco/src/models/user_model.dart';
class ScoreScreen extends StatefulWidget {
final Event evento;
final bool pro;
const ScoreScreen({super.key, required this.evento, required this.pro});
@override
State<ScoreScreen> createState() => ScoreScreenState();
}
class ScoreScreenState extends State<ScoreScreen> {
TextEditingController commentController = TextEditingController();
Reference? ref_photo;
String nombre = '';
double _rating = 1.0;
@override
Widget build(BuildContext context) {
if (nombre == '') {
if (uid != widget.evento.userId) {
UserModel.getUser(widget.evento.userId).then((value) {
UserModel.getUser(uid.toString()).then((me) {
setState(() {
nombre = value.name;
ref_photo = value.photo;
});
});
});
} else {
UserModel.getUser(widget.evento.professionalId).then((value) {
setState(() {
nombre = value.name;
ref_photo = value.photo;
});
});
}
}
return Scaffold(
appBar: PopAppbar(
onPressed: () {
Navigator.pop(context);
},
label: 'Puntuación'),
body: Column(
children: [
Padding(
padding: const EdgeInsets.symmetric(horizontal: 15),
child: ListTile(
leading: Padding(
padding: const EdgeInsets.symmetric(horizontal: 5),
child: ReferencePhoto(
ref: ref_photo,
size: 50,
sizeCircle: 50,
sizeIcon: 35,
),
),
title: Text(
nombre,
style: const TextStyle(
color: Colors.black,
fontWeight: FontWeight.w600,
fontSize: 16),
),
subtitle: Text(
'${DateFormat('dd MMM', 'es').format(DateTime.parse(widget.evento.day))} ${TimeOfDay.fromDateTime(DateTime.parse(widget.evento.range1Hour1)).format(context)}'),
),
),
RatingBar.builder(
initialRating: _rating,
minRating: 1,
direction: Axis.horizontal,
allowHalfRating: true,
itemCount: 5,
itemSize: 40,
glow: false,
maxRating: 5,
itemPadding: const EdgeInsets.symmetric(horizontal: 5),
itemBuilder: (context, _) => const Icon(
Icons.star,
color: Color(0xFF2BA4EC),
),
onRatingUpdate: (rating) {
setState(() {
_rating = rating;
});
},
ignoreGestures: false,
),
Expanded(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 40, vertical: 40),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'Comentario',
style: TextStyle(fontSize: 18),
),
TextFormField(
maxLines: null,
maxLength: 250,
keyboardType: TextInputType.multiline,
controller: commentController,
),
],
),
),
),
Padding(
padding: const EdgeInsets.only(bottom: 30),
child: Column(
children: [
ElevatedButton(
onPressed: () {
if (widget.pro) {
FirebaseFirestore.instance
.collection("services")
.doc(widget.evento.id)
.update({'professional_scored': true}).then((value) {
FirebaseFirestore.instance.collection("scores").add({
"comment": commentController.text,
"from_user": widget.evento.professionalId,
"is_from_professional": false,
"score": _rating,
"to_user": widget.evento.userId,
}).then((value) {
Navigator.pop(context);
});
});
} else {
FirebaseFirestore.instance
.collection("services")
.doc(widget.evento.id)
.update({'user_scored': true}).then((value) {
FirebaseFirestore.instance.collection("scores").add({
"comment": commentController.text,
"from_user": widget.evento.userId,
"is_from_professional": true,
"score": _rating,
"to_user": widget.evento.professionalId,
}).then((value) {
Navigator.pop(context);
});
});
}
},
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFF2BA4EC),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(50),
),
elevation: 0,
minimumSize: const Size(230, 60),
),
child: const Text(
'Enviar',
style: TextStyle(
color: Colors.white,
fontWeight: FontWeight.bold,
fontSize: 18,
),
),
),
],
),
),
],
),
);
}
}
@@ -1,230 +0,0 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_rating_bar/flutter_rating_bar.dart';
import 'package:intl/intl.dart';
import 'package:prosappco/src/components/photo_view.dart';
import 'package:prosappco/src/components/pop_appbar.dart';
import 'package:prosappco/src/models/event_model.dart';
import 'package:prosappco/src/models/scores_model.dart';
import 'package:prosappco/src/models/setting_model.dart';
import 'package:prosappco/src/models/user_model.dart';
import 'package:prosappco/src/presentation/screens/map/service.dart';
import 'package:prosappco/src/presentation/screens/service_web.dart';
class ServiceAfterScreen extends StatefulWidget {
var eventoId;
ServiceAfterScreen({super.key, this.eventoId});
@override
State<ServiceAfterScreen> createState() => _ServiceAfterScreenState();
}
class _ServiceAfterScreenState extends State<ServiceAfterScreen> {
Event? evento;
UserModel? user;
ScoresModel? scoresModel;
SettingModel? settings;
@override
void initState() {
super.initState();
if (settings == null) {
SettingModel.getSettings().then(
(SettingModel value) => setState(() {
settings = value;
}),
);
}
Event.getEventById(widget.eventoId).then((value) {
setState(() {
evento = value;
});
if (scoresModel == null) {
ScoresModel.scoreTo(value.professionalId, true, false).then(
(ScoresModel s) => setState(() {
scoresModel = s;
}),
);
}
UserModel.getUser(value.professionalId).then((s) {
setState(
() => user = s,
);
});
});
}
String formatCurrency(int number) {
final formatter =
NumberFormat.currency(locale: 'es_CO', decimalDigits: 0, symbol: '');
return '\$${formatter.format(number)}';
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: PopAppbar(
onPressed: () {
kIsWeb
? Navigator.pushReplacement(
context,
CupertinoPageRoute(
builder: (BuildContext context) {
return const ServiceWebScreen();
},
),
)
: Navigator.pushReplacement(
context,
CupertinoPageRoute(
builder: (BuildContext context) {
return const ServiceScreen();
},
),
);
},
label: 'Servicio'),
body: Column(
children: [
ListTile(
leading: ReferencePhoto(
ref: user?.photo,
size: 55,
sizeCircle: 60,
),
title: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'${user?.name}',
style: const TextStyle(
color: Colors.black,
fontWeight: FontWeight.bold,
fontSize: 16,
),
),
Text(
'${DateFormat('dd MMMM', 'es').format(DateTime.parse(evento?.day ?? '2023-01-01 00:00:00.000Z'))} ${evento?.range1Hour1 != null ? DateFormat('h:mm a').format(DateTime.parse(evento!.range1Hour1)) : ''}',
style: TextStyle(
color: Colors.grey[600],
fontSize: 16,
),
),
],
),
subtitle: Column(
children: [
Row(
children: [
RatingBar.builder(
initialRating: scoresModel?.average ?? 0,
minRating: 1,
direction: Axis.horizontal,
allowHalfRating: true,
itemCount: 5,
itemSize: 25,
maxRating: 5,
itemPadding: const EdgeInsets.symmetric(horizontal: 0),
itemBuilder: (context, _) => const Icon(
Icons.star,
color: Color(0xFF2BA4EC),
),
onRatingUpdate: (rating) {},
ignoreGestures: true,
),
const SizedBox(width: 5),
Text(
'(${scoresModel?.total.toString()}) ${scoresModel?.average.toStringAsFixed(1)}'),
],
),
],
),
),
Container(
margin:
const EdgeInsets.only(left: 40, right: 40, top: 20, bottom: 20),
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 15),
decoration: BoxDecoration(
color: const Color(0xFFD6F4FF),
borderRadius: BorderRadius.circular(20),
boxShadow: [
BoxShadow(
color: Colors.grey.withOpacity(0.5),
spreadRadius: 1,
blurRadius: 5,
offset: const Offset(1, 3),
),
],
),
child: Row(
children: [
const Icon(
Icons.error_outline,
size: 27,
color: Colors.black54,
),
const SizedBox(width: 15),
evento?.ubicacion != 'sitio'
? const Text(
'Servicio a su domicilio.',
style: TextStyle(color: Colors.black, fontSize: 14),
)
: const Text(
'Servicio en sitio / consultorio',
style: TextStyle(color: Colors.black, fontSize: 14),
),
],
),
),
settings?.tarifas == true && evento?.tarifa != 0
? Column(children: [
Text(
formatCurrency(evento?.tarifa ?? 0),
style: const TextStyle(
fontWeight: FontWeight.w600, fontSize: 25),
),
const Text('Tarifa consulta'),
const SizedBox(height: 10)
])
: const SizedBox(),
ListTile(
leading: const Icon(Icons.near_me),
title: Text(
'${evento?.address}',
style: TextStyle(fontSize: 15, color: Colors.grey[600]),
),
),
Text(
'"${evento?.description}"',
style:
TextStyle(color: Colors.grey[600], fontStyle: FontStyle.italic),
),
const Center(
child: Column(
children: [
Padding(
padding: EdgeInsets.symmetric(vertical: 20),
child: Icon(
Icons.check_circle_outline_rounded,
color: Color(0xFF35A8ED),
size: 70,
),
),
Text(
'Servicio solicitado exitosamente',
style: TextStyle(
color: Color(0xFF35A8ED),
fontSize: 17,
fontWeight: FontWeight.w600),
),
],
),
),
],
),
);
}
}
@@ -1,99 +0,0 @@
import 'package:diacritic/diacritic.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:prosappco/src/components/pop_appbar.dart';
import 'package:prosappco/src/presentation/screens/profession.dart';
class ServiceTypeScreen extends StatefulWidget {
const ServiceTypeScreen({super.key});
@override
State<ServiceTypeScreen> createState() => _ServiceTypeScreenState();
}
class _ServiceTypeScreenState extends State<ServiceTypeScreen> {
List<String>? filteredProfessions;
TextEditingController searchController = TextEditingController();
List<String>? _professions;
@override
void initState() {
super.initState();
searchController.addListener(() {
setState(() {
if (_professions != null) {
if (searchController.text.isEmpty) {
filteredProfessions = _professions!;
} else {
filteredProfessions = _professions!
.where((profession) => removeDiacritics(profession)
.toLowerCase()
.contains(
removeDiacritics(searchController.text.toLowerCase())))
.toList();
}
}
});
});
if (_professions == null) {
getProfessions().then((List<String> element) => setState(() {
_professions = element;
filteredProfessions = element;
}));
}
}
@override
Widget build(BuildContext context) {
if (filteredProfessions == null) {
return const Center(
child: CircularProgressIndicator(
valueColor: AlwaysStoppedAnimation<Color>(Color(0xFF2BA4EC)),
),
);
}
List<String> professions = filteredProfessions!;
return Scaffold(
appBar: PopAppbar(
onPressed: () {
Navigator.pop(context);
},
label: 'Tipo de servicio'),
body: Column(
children: [
Padding(
padding: const EdgeInsets.all(10),
child: TextField(
controller: searchController,
decoration: const InputDecoration(
hintText: 'Escribe el tipo de servicio',
prefixIcon: Icon(Icons.assignment_ind_rounded),
),
),
),
Expanded(
child: ListView.builder(
itemCount: professions.length,
itemBuilder: (BuildContext context, int index) {
return ListTile(
title: Text(
professions[index],
style: const TextStyle(
fontSize: 18.0,
color: Colors.black,
),
),
onTap: () {
Navigator.pop(context, professions[index]);
},
);
},
),
),
],
),
);
}
}
@@ -1,653 +0,0 @@
import 'dart:convert';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:firebase_storage/firebase_storage.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:geocoding/geocoding.dart';
import 'package:geolocator/geolocator.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart';
import 'package:prosappco/src/authentication/authentication_repository.dart';
import 'package:prosappco/src/models/professional_model.dart';
import 'package:prosappco/src/presentation/screens/profile/profile.dart';
import 'package:prosappco/src/presentation/widgets/shared/drawer_menu.dart';
import 'package:prosappco/src/models/event_model.dart';
import 'package:prosappco/src/models/user_model.dart';
import 'package:prosappco/src/presentation/screens/professional.dart';
import 'package:intl/intl.dart';
import 'package:prosappco/src/presentation/screens/service_after.dart';
import 'package:prosappco/src/presentation/screens/ubicacion.dart';
import 'package:http/http.dart' as http;
import 'package:prosappco/src/components/network_utility.dart';
import 'package:prosappco/src/presentation/widgets/shared/warning_snackbar.dart';
import 'package:prosappco/src/providers/user_provider.dart';
import 'package:provider/provider.dart';
class ServiceWebScreen extends StatefulWidget {
const ServiceWebScreen({super.key});
@override
State<ServiceWebScreen> createState() => _ServiceOldScreenState();
}
class _ServiceOldScreenState extends State<ServiceWebScreen> {
void _saveToken() async {
FirebaseMessaging messaging = FirebaseMessaging.instance;
final token = await messaging.getToken();
try {
await FirebaseFirestore.instance
.collection('users')
.doc(uid)
.update({'token': token});
} catch (e) {
print(e);
}
}
Future<void> sendPushNotification(String pro) async {
try {
http.Response response = await http.post(
Uri.parse('https://fcm.googleapis.com/fcm/send'),
headers: <String, String>{
'Content-Type': 'application/json; charset=UTF-8',
'Authorization':
'key=AAAAORdR-xU:APA91bF_wblg86jHAC-uexrXPHavYRlk5wge1Gf46m56V4J2D2L37Cp_hf46JZUzpvsWPSpqc5ewHelKI9LTifUG_s2mciMI6e5VLKo7E1R8btbNo7iaM9do2ctoyHKUm1atlZBdKaN2',
},
body: jsonEncode(
<String, dynamic>{
'notification': <String, dynamic>{
'body': 'alguien a solicitado tus servicios',
'title': 'Nueva solicitud',
},
'priority': 'high',
'data': <String, dynamic>{
'click_action': 'FLUTTER_NOTIFICATION_CLICK',
'id': '1',
'status': 'done',
'screen': 'solicitud'
},
'to': pro
},
),
);
response;
} catch (e) {
print('error al enviar notificacion $e');
}
}
EventoService eventoService = EventoService();
String ubicacion = '';
final TextEditingController _locationController = TextEditingController();
final TextEditingController _ubicationController = TextEditingController();
final TextEditingController _profesionalController = TextEditingController();
final TextEditingController _serviceTypeController = TextEditingController();
final TextEditingController _observacionController = TextEditingController();
String professionalId = '';
String professionalToken = '';
int? professionalTarifa;
String professionalAddress = '';
String professionalUbicacion = '';
double? professionalLatitude;
double? professionalLongitude;
String _serviceType = 'Servicio';
final DateFormat formatter = DateFormat('dd/MM/yyyy');
final DateTime now = DateTime.now();
List<dynamic> _placesList = [];
String selectedPlace = '';
String _coordsOfCity = '0.0,0.0';
DateTime? _selectedDate;
TimeOfDay? _selectedTime;
GoogleMapController? googleMapController;
Set<Marker> markers = {};
Future<Position> _determinePosition() async {
bool serviceEnabled;
LocationPermission permission;
serviceEnabled = await Geolocator.isLocationServiceEnabled();
if (!serviceEnabled) {
return Future.error('Location services are disabled');
}
permission = await Geolocator.checkPermission();
if (permission == LocationPermission.denied) {
permission = await Geolocator.requestPermission();
if (permission == LocationPermission.denied) {
return Future.error('Location permission denied');
}
}
if (permission == LocationPermission.deniedForever) {
return Future.error('Location permissions are permanently denied');
}
Position position = await Geolocator.getCurrentPosition();
return position;
}
Future<void> _selectDate(BuildContext context) async {
final DateTime? picked = await showDatePicker(
context: context,
initialDate: now,
firstDate: now,
lastDate: DateTime(now.year + 1),
// builder: (context, child) {
// return Theme(data: ThemeData.dark(), child: child!);
// },
);
if (picked != null && picked != _selectedDate) {
if (mounted) {
setState(() {
_selectedDate = picked;
});
}
}
}
Future<void> _selectTime(BuildContext context) async {
final TimeOfDay? pickedTime = await showTimePicker(
context: context,
initialTime: TimeOfDay.now(),
);
if (pickedTime != null) {
if (mounted) {
setState(() {
_selectedTime = pickedTime;
});
}
}
}
Future<List<DocumentSnapshot<Map<String, dynamic>>>> getUsersWithActiveStatus(
String _serviceType) async {
var querySnapshot;
if (_serviceType != "Servicio") {
querySnapshot = await FirebaseFirestore.instance
.collection('users')
.where('estado', isEqualTo: 'activo')
.where('profesion', isEqualTo: _serviceType)
.get();
} else {
querySnapshot = await FirebaseFirestore.instance
.collection('users')
.where('estado', isEqualTo: 'activo')
.get();
}
return querySnapshot.docs;
}
final FirebaseStorage storage = FirebaseStorage.instance;
final uid = AuthenticationRepository.instance.getCurrentUserUid();
final fcmToken = FirebaseMessaging.instance.getToken();
BitmapDescriptor? _markerIcon;
String _ciudad = '...';
void _setInitialCameraPosition(String coordsOfCity) async {
List<String> coords = coordsOfCity.split(',');
double lat = double.parse(coords[0]);
double lng = double.parse(coords[1]);
googleMapController?.moveCamera(
CameraUpdate.newLatLngZoom(
LatLng(lat, lng),
14.4746,
),
);
try {
Position position = await _determinePosition();
googleMapController?.animateCamera(
CameraUpdate.newCameraPosition(
CameraPosition(
target: LatLng(
position.latitude,
position.longitude,
),
zoom: 17,
),
),
);
if (mounted) {
setState(() {});
}
} catch (e) {
print('Error: $e');
}
}
@override
void dispose() {
_locationController.dispose();
_ubicationController.dispose();
_profesionalController.dispose();
_serviceTypeController.dispose();
_observacionController.dispose();
super.dispose();
}
void updateMarkersForServiceType(String serviceType) {
getUsersWithActiveStatus(serviceType).then((value) {
markers.clear();
for (var doc in value) {
final element = doc.data()!;
if (element['latitude'] != null && element['longitude'] != null) {
markers.add(
Marker(
icon: _markerIcon!,
markerId: MarkerId(doc.id),
position: LatLng(
element['latitude'],
element['longitude'],
),
),
);
}
}
}).catchError((e) {
print('Error al actualizar los marcadores: $e');
});
}
String? settings;
@override
void initState() {
super.initState();
if (_ciudad == '...') {
AuthenticationRepository.instance
.getCity(uid.toString())
.then((String s) {
if (s.isEmpty) {
FirebaseFirestore.instance.collection('users').doc(uid).set({
'city': 'Cúcuta',
}).then((_) {
if (mounted) {
setState(() {
_ciudad = 'Cúcuta';
});
}
});
} else {
if (mounted) {
setState(() {
_ciudad = s;
});
}
}
});
}
if (_coordsOfCity == '0.0,0.0') {
AuthenticationRepository.instance
.getCoordsOfCity(uid.toString())
.then((String s) {
if (mounted) {
setState(() {
_coordsOfCity = s;
_setInitialCameraPosition(_coordsOfCity);
});
}
});
}
_saveToken();
BitmapDescriptor.fromAssetImage(
const ImageConfiguration(size: Size(6, 6)), 'images/pro_marke.png')
.then((icon) {
if (mounted) {
setState(() {
_markerIcon = icon;
});
}
});
updateMarkersForServiceType(_serviceType);
}
static CameraPosition initialCameraPosition = const CameraPosition(
target: LatLng(7.8939100, -72.5078200),
zoom: 14.4746,
);
void setInitialCameraPosition(String coordsOfCity) {
if (coordsOfCity != '0.0,0.0') {
List<String> coords = coordsOfCity.split(',');
double lat = double.parse(coords[0]);
double lng = double.parse(coords[1]);
initialCameraPosition = CameraPosition(
target: LatLng(lat, lng),
zoom: 14.4746,
);
}
}
late String lat;
late String long;
double latUser = 0.0;
double lngUser = 0.0;
var coordinates;
Future<String> getLocationName(double latitude, double longitude) async {
String address;
List<Placemark> placemarks =
await placemarkFromCoordinates(latitude, longitude);
Placemark place = placemarks[0];
if (place.thoroughfare != '' || place.subThoroughfare != '') {
address =
"${place.thoroughfare} ${place.subThoroughfare} ${place.subLocality}, ${place.locality}, ${place.administrativeArea}";
} else {
address = '';
}
return address;
}
void placeAutoComplete(String query) async {
Uri uri = Uri.https("admin.prosapp.co", "/autocomplete", {
"input": query,
"location": _coordsOfCity,
});
String? response = await NetworkUtility.fetchUrl(uri);
if (response != null) {
if (mounted) {
setState(() {
_placesList = jsonDecode(response.toString())['results'];
});
}
}
}
@override
Widget build(BuildContext context) {
final userProvider = Provider.of<UserProvider>(context);
UserModel? user = userProvider.user;
return Scaffold(
backgroundColor: const Color(0xFFD6F4FF),
drawer: DrawerMenu(),
appBar: AppBar(
elevation: 0,
title: const Text(
'Prosapp',
style: TextStyle(
color: Colors.white,
fontSize: 20,
),
)),
body: Center(
child: SizedBox(
width: 300,
height: 470,
child: Card(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10),
),
color: Colors.white,
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 20.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: [
TextFormField(
controller: _profesionalController,
readOnly: true,
onTap: () async {
final dynamic datos = await Navigator.push(
context,
CupertinoPageRoute(
builder: (BuildContext context) {
return ProfessionalScreen(
profession: _serviceTypeController.text,
);
},
),
);
if (datos != null) {
Professional? profesional = datos[0];
ubicacion = datos[1];
if (profesional != null) {
if (mounted) {
setState(() {
_profesionalController.text =
profesional.name.toString();
professionalId = profesional.id;
professionalTarifa = profesional.tarifa ?? 0;
professionalUbicacion = profesional.ubicacion;
professionalAddress = _ubicationController.text;
if (profesional.token != '') {
professionalToken = profesional.token!;
print(professionalToken);
}
if (ubicacion == 'sitio') {
professionalAddress = profesional.realAddress;
_ubicationController.text =
profesional.realAddress;
latUser = profesional.latitude;
lngUser = profesional.longitude;
}
});
}
}
}
},
decoration: const InputDecoration(
prefixIcon: Icon(Icons.assignment_ind_rounded),
suffixIcon: Icon(Icons.arrow_drop_down),
hintText: 'Seleccionar profesional'),
),
const SizedBox(height: 20.0),
TextFormField(
controller: _ubicationController,
readOnly: true,
onTap: () async {
final List<dynamic> datos = await Navigator.push(
context,
CupertinoPageRoute(
builder: (BuildContext context) {
return const UbicacionScreen();
},
),
);
if (datos.length == 3) {
final formattedAddress = datos[0];
final lat = datos[1];
final lng = datos[2];
if (mounted) {
setState(() {
_ubicationController.text = formattedAddress;
latUser = lat;
lngUser = lng;
});
}
}
},
decoration: const InputDecoration(
hintText: 'Escribe tu ubicación',
prefixIcon: Icon(Icons.location_on),
),
),
const SizedBox(height: 20.0),
TextFormField(
onTap: () {
if (_profesionalController.text.isNotEmpty) {
_selectDate(context);
} else {
WarningSnackbar.show(
title: 'Selecciona un profesional',
message:
'Selecciona un profesional antes de elegir la fecha y la hora de la cita.',
);
}
},
readOnly: true,
decoration: InputDecoration(
prefixIcon: const Icon(Icons.calendar_month),
suffixIcon: _selectedDate == null
? const Icon(Icons.arrow_drop_down)
: null,
hintText: 'Fecha',
),
controller: TextEditingController(
text: _selectedDate == null
? ''
: formatter.format(_selectedDate!)),
),
const SizedBox(height: 20.0),
TextFormField(
onTap: () {
if (_profesionalController.text.isNotEmpty) {
_selectTime(context);
} else {
WarningSnackbar.show(
title: 'Selecciona un profesional',
message:
'Selecciona un profesional antes de elegir la fecha y la hora de la cita.',
);
}
},
readOnly: true,
decoration: const InputDecoration(
prefixIcon: Icon(Icons.access_time),
suffixIcon: Icon(Icons.arrow_drop_down),
hintText: 'Hora',
),
controller: TextEditingController(
text: _selectedTime == null
? ''
: ' ${_selectedTime!.format(context)}'),
),
const SizedBox(height: 20.0),
TextFormField(
controller: _observacionController,
decoration: const InputDecoration(
prefixIcon: Icon(Icons.message_outlined),
hintText: 'Observaciones'),
),
const SizedBox(height: 40.0),
ElevatedButton(
onPressed: () {
if (user?.name == null ||
user?.name == '' ||
user?.phoneNumber == null ||
user?.phoneNumber == '') {
Navigator.push(
context,
CupertinoPageRoute(
builder: (BuildContext context) {
return const ProfileScreen();
},
),
);
} else {
try {
final DateTime combinedDate = DateTime(
_selectedDate!.year,
_selectedDate!.month,
_selectedDate!.day,
_selectedTime!.hour,
_selectedTime!.minute,
);
DateTime time2 =
combinedDate.add(const Duration(hours: 2));
UserModel.getUser(uid.toString()).then((value) {
eventoService
.createEvent(
value.name,
_observacionController.text,
'$_selectedDate',
'$combinedDate',
'$time2',
professionalId,
ubicacion,
professionalAddress,
latUser,
lngUser,
'pendiente',
professionalTarifa == 0 ? 0 : professionalTarifa,
false,
false,
)
.then((value) {
if (professionalToken != '') {
sendPushNotification(professionalToken);
}
Navigator.pushReplacement(
context,
CupertinoPageRoute(
builder: (BuildContext context) {
return ServiceAfterScreen(
eventoId: value,
);
},
),
);
});
});
} catch (e) {
WarningSnackbar.show(
title: 'Llena todos los campos',
message: 'Asegurate de llenar todos los campos.',
);
}
}
},
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFF2BA4EC),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(50),
),
elevation: 0,
minimumSize: const Size(230, 50),
),
child: const Text(
'Solicitar servicio',
style: TextStyle(
color: Colors.white,
fontWeight: FontWeight.bold,
fontSize: 18,
),
),
),
],
),
),
),
),
),
);
}
}
@@ -1,194 +0,0 @@
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_rating_bar/flutter_rating_bar.dart';
import 'package:intl/intl.dart';
import 'package:prosappco/src/components/drawer_professional.dart';
import 'package:prosappco/src/models/event_model.dart';
import 'package:prosappco/src/models/scores_model.dart';
import 'package:prosappco/src/presentation/screens/cita.dart';
class SolicitudScreen extends StatelessWidget {
SolicitudScreen({super.key});
DateTime today = DateTime.now();
@override
Widget build(BuildContext context) {
return SafeArea(
child: Scaffold(
appBar: AppBar(
backgroundColor: Colors.white,
iconTheme: const IconThemeData(
color: Colors.black,
),
title: const Text(
'Solicitudes',
style: TextStyle(
color: Colors.black,
),
),
),
drawer: DrawerProfessional(),
body: SingleChildScrollView(
child: Column(
children: [
_eventList(),
],
),
),
),
);
}
Widget _eventList() {
return StreamBuilder<List<Event>>(
stream: FirebaseFirestore.instance
.collection('services')
.where('professional_id', isEqualTo: uid)
.where('status', whereIn: ['pendiente', ''])
.snapshots()
.asyncMap((snapshot) async {
try {
List<Event> eventos = [];
for (var element in snapshot.docs) {
final event = Event.fromJson(element.data());
event.scoresModel =
await ScoresModel.scoreTo(event.userId, false, false);
event.id = element.id;
eventos.add(event);
}
return eventos;
} catch (e) {
print('Error getByProId $e');
return [];
}
}),
builder: (BuildContext context, AsyncSnapshot<List<Event>> snapshot) {
if (!snapshot.hasData) {
return const Center(
child: CircularProgressIndicator(),
);
}
List<Event> eventos = [];
try {
eventos.addAll(snapshot.data!);
eventos.sort((a, b) => a.timeStamp!.compareTo(b.timeStamp!));
} catch (e) {
print("Error" + e.toString());
}
if (eventos.isEmpty) {
return const Padding(
padding: EdgeInsets.symmetric(vertical: 50),
child: Center(child: Text('No tienes citas')),
);
}
return Column(
children: [
...eventos.map(
(event) => ListTile(
onTap: () {
Navigator.push(
context,
CupertinoPageRoute(
builder: (BuildContext context) {
return CitaScreen(evento: event);
},
),
);
},
leading: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(DateFormat('h:mm a')
.format(DateTime.parse(event.range1Hour1))),
],
),
title: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
event.title,
style: const TextStyle(
color: Colors.black,
fontWeight: FontWeight.bold,
fontSize: 16,
),
),
Text(
'${DateFormat('dd MMMM', 'es').format(DateTime.parse(event.day))} - ${DateFormat('h:mm a').format(DateTime.parse(event.range1Hour1))}',
style: const TextStyle(
color: Colors.grey,
fontSize: 16,
),
)
],
),
// RichText(
// text: TextSpan(
// children: [
// TextSpan(
// text: '${event.title}, ',
// style: const TextStyle(
// color: Colors.black,
// fontWeight: FontWeight.bold,
// fontSize: 16,
// ),
// ),
// TextSpan(
// text: DateFormat('dd MMM', 'es')
// .format(DateTime.parse(event.day)),
// style: const TextStyle(
// color: Colors.grey,
// fontSize: 16,
// ),
// ),
// ],
// ),
// ),
subtitle: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
RatingBar.builder(
initialRating: event.scoresModel?.average ?? 0,
minRating: 1,
direction: Axis.horizontal,
allowHalfRating: true,
itemCount: 5,
itemSize: 25,
maxRating: 5,
itemPadding:
const EdgeInsets.symmetric(horizontal: 0),
itemBuilder: (context, _) => const Icon(
Icons.star,
color: Color(0xFF2BA4EC),
),
onRatingUpdate: (rating) {},
ignoreGestures: true,
),
const SizedBox(width: 5),
Text(
'(${event.scoresModel?.total.toString()}) ${event.scoresModel?.average.toStringAsFixed(1)}'),
],
),
Text(
'"${event.description}"',
style: const TextStyle(fontStyle: FontStyle.italic),
),
],
),
trailing: const Icon(Icons.keyboard_arrow_right),
),
),
],
);
},
);
}
}
-157
View File
@@ -1,157 +0,0 @@
import 'package:community_material_icon/community_material_icon.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_email_sender/flutter_email_sender.dart';
import 'package:prosappco/src/components/pop_appbar.dart';
import 'package:prosappco/src/models/setting_model.dart';
import 'package:url_launcher/url_launcher.dart';
class SupportScreen extends StatefulWidget {
const SupportScreen({super.key});
@override
State<SupportScreen> createState() => SsupportStateScreen();
}
class SsupportStateScreen extends State<SupportScreen> {
SettingModel? settings;
final String subject = 'Soporte Prosapp';
final String body = '';
Future<void> _sendWhatsapp(String? number) async {
final _whatsappUrl = 'https://api.whatsapp.com/send?phone=$number&text=Hola%21+soy+usuario+de+Prosapp+y+quisiera+conocer+mas+sobre+esta+app+%F0%9F%98%81';
if (!await launch(_whatsappUrl)) {
throw Exception('Could not launch $_whatsappUrl');
}
}
Future<void> _sendEmail(String recipients) async {
final Email email = Email(
body: body,
subject: subject,
recipients: [recipients],
isHTML: false,
);
await FlutterEmailSender.send(email);
}
void _sendEmailWeb(String recipients) async {
final email = 'mailto:$recipients?subject=${Uri.encodeComponent(recipients)}&body=${Uri.encodeComponent(body)}';
if (await canLaunch(email)) {
await launch(email);
} else {}
}
@override
void initState() {
super.initState();
if (settings == null) {
SettingModel.getSettings().then(
(SettingModel value) => setState(() {
settings = value;
}),
);
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: PopAppbar(
onPressed: () {
Navigator.pop(context);
},
label: 'Soporte'),
body: Column(
children: [
Padding(
padding: const EdgeInsets.only(top: 30, left: 35, right: 35),
child: Text(
'${settings?.titulo}',
style: const TextStyle(fontWeight: FontWeight.w600),
),
),
Padding(
padding: const EdgeInsets.symmetric(vertical: 30, horizontal: 35),
child: Text('${settings?.parrafo}'),
),
Row(
children: [
const Expanded(child: SizedBox()),
ElevatedButton(
onPressed: () {
_sendWhatsapp(settings?.numero);
},
style: ElevatedButton.styleFrom(
foregroundColor: Colors.white,
backgroundColor: const Color(0xFF2BA4EC),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(50),
side: const BorderSide(
color: Color(0xFF2BA4EC),
width: 2,
),
),
),
child: const Padding(
padding: EdgeInsets.symmetric(vertical: 18, horizontal: 0),
child: Icon(
CommunityMaterialIcons.whatsapp,
size: 30,
color: Colors.white,
),
),
),
const SizedBox(width: 20),
ElevatedButton(
onPressed: () {
if (kIsWeb) {
_sendEmailWeb(settings?.email ?? '');
} else {
_sendEmail(settings?.email ?? '');
}
},
style: ElevatedButton.styleFrom(
foregroundColor: Colors.white,
backgroundColor: const Color(0xFF2BA4EC),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(50),
side: const BorderSide(
color: Color(0xFF2BA4EC),
width: 2,
),
),
),
child: const Padding(
padding: EdgeInsets.symmetric(vertical: 18, horizontal: 0),
child: Icon(
Icons.email_outlined,
size: 30,
color: Colors.white,
),
),
),
const Expanded(child: SizedBox()),
],
),
Padding(
padding: const EdgeInsets.symmetric(vertical: 50),
child: Column(
children: [
const Text(
'Horario de atención:',
style: TextStyle(fontWeight: FontWeight.w600),
),
const SizedBox(height: 20),
Text('${settings?.dias}'),
const SizedBox(height: 5),
Text('${settings?.horas}'),
],
),
),
],
),
);
}
}
@@ -1,98 +0,0 @@
import 'package:flutter/material.dart';
import 'dart:convert';
import 'package:prosappco/src/components/network_utility.dart';
import 'package:prosappco/src/components/pop_appbar.dart';
import '../../authentication/authentication_repository.dart';
class UbicacionScreen extends StatefulWidget {
const UbicacionScreen({super.key});
@override
State<UbicacionScreen> createState() => _UbicacionScreenState();
}
class _UbicacionScreenState extends State<UbicacionScreen> {
List<dynamic> _placesList = [];
String selectedPlace = '';
String _coordsOfCity = '0.0,0.0';
@override
void initState() {
super.initState();
final uid = AuthenticationRepository.instance.getCurrentUserUid();
if (_coordsOfCity == '0.0,0.0') {
AuthenticationRepository.instance
.getCoordsOfCity(uid.toString())
.then((String s) => setState(() {
_coordsOfCity = s;
}));
}
}
void placeAutoComplete(String query) async {
Uri uri = Uri.https("admin.prosapp.co", "/autocomplete", {
"input": query,
"location": _coordsOfCity,
});
String? response = await NetworkUtility.fetchUrl(uri);
if (response != null) {
setState(() {
_placesList = jsonDecode(response.toString())['results'];
});
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: PopAppbar(
onPressed: () {
Navigator.pop(context);
},
label: 'Tu ubicación'),
body: Center(
child: SizedBox(
width: 300,
child: Column(
children: [
const SizedBox(height: 20.0),
TextFormField(
decoration: const InputDecoration(
hintText: 'Escribe tu ubicación',
prefixIcon: Icon(Icons.location_on),
),
onChanged: (value) {
String modifiedValue = value.replaceAll(' ', '_');
placeAutoComplete(modifiedValue);
},
),
const SizedBox(height: 20.0),
Expanded(
child: ListView.builder(
itemCount: _placesList.length,
itemBuilder: (context, index) {
return ListTile(
onTap: () {
Navigator.pop(context, [
_placesList[index]['formatted_address'],
_placesList[index]['geometry']['location']['lat'],
_placesList[index]['geometry']['location']['lng']
]);
},
title: Text(_placesList[index]['formatted_address']),
);
},
),
),
],
),
),
),
);
}
}
@@ -1,410 +0,0 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_rating_bar/flutter_rating_bar.dart';
import 'package:prosappco/src/authentication/authentication_repository.dart';
import 'package:prosappco/src/components/photo_view.dart';
import 'package:prosappco/src/models/scores_model.dart';
import 'package:prosappco/src/models/user_model.dart';
import 'package:prosappco/src/presentation/widgets/shared/warning_snackbar.dart';
import 'package:prosappco/src/providers/user_provider.dart';
import 'package:prosappco/src/presentation/screens/configuracion.dart';
import 'package:prosappco/src/presentation/screens/professional_profile_web.dart';
import 'package:prosappco/src/presentation/screens/profile/profile.dart';
import 'package:prosappco/src/presentation/screens/reputation.dart';
import 'package:prosappco/src/presentation/screens/support.dart';
import 'package:prosappco/src/presentation/screens/web_view.dart';
import 'package:url_launcher/url_launcher.dart';
import 'package:get/get.dart';
import 'package:provider/provider.dart';
class DrawerMenu extends StatelessWidget {
final uid = AuthenticationRepository.instance.getCurrentUserUid();
Future<void> _irSugerencias() async {
const url = 'https://admin.prosapp.co/sugerencias';
final Uri _url = Uri.parse(url);
if (await canLaunchUrl(_url)) {
await launchUrl(_url);
} else {
throw 'No se pudo abrir la URL $url';
}
}
@override
Widget build(BuildContext context) {
final userProvider = Provider.of<UserProvider>(context);
UserModel? user = userProvider.user;
ScoresModel? score = userProvider.score;
return Drawer(
child: Column(
children: [
Container(
color: Colors.white,
child: Column(
children: [
infoUser(context, user),
], //
),
),
Container(
decoration: BoxDecoration(
boxShadow: [
BoxShadow(
color: Colors.grey.withOpacity(0.3),
spreadRadius: 1,
blurRadius: 3,
offset: const Offset(0, 0), // changes position of shadow
),
],
),
child: Divider(
height: 0,
color: Colors.grey[300],
),
),
Expanded(
child: Column(
children: [
ListTile(
onTap: () {
if (ModalRoute.of(context)?.settings.name !=
'/misservicios') {
Navigator.pushNamed(context, '/misservicios');
} else {
Scaffold.of(context).openEndDrawer();
}
},
leading: const Icon(
Icons.history,
color: Colors.black,
),
title: const Text(
'Mis servicios',
style: TextStyle(fontSize: 15),
),
),
ListTile(
onTap: () {
Navigator.push(
context,
CupertinoPageRoute(
builder: (BuildContext context) {
return const ProfileScreen();
},
),
);
},
leading: const Icon(
Icons.person_outline,
color: Colors.black,
),
title: const Text(
'Mi perfil',
style: TextStyle(fontSize: 15),
),
),
ListTile(
onTap: () {
Navigator.push(
context,
CupertinoPageRoute(
builder: (BuildContext context) {
return const ConfiguracionScreen();
},
),
);
},
leading: const Icon(
Icons.construction_outlined,
color: Colors.black,
),
title: const Text(
'Configuración',
style: TextStyle(fontSize: 15),
),
),
ListTile(
onTap: () {
Navigator.push(
context,
CupertinoPageRoute(
builder: (BuildContext context) {
return const SupportScreen();
},
),
);
},
leading: const Icon(
Icons.question_mark_rounded,
color: Colors.black,
),
title: const Text(
'Soporte',
style: TextStyle(fontSize: 15),
),
),
ListTile(
onTap: () {
if (kIsWeb) {
_irSugerencias();
} else {
Navigator.push(
context,
CupertinoPageRoute(
builder: (BuildContext context) {
return WebViewScreen(
label: 'Sugerencias',
link: 'https://admin.prosapp.co/sugerencias',
);
},
),
);
}
},
leading: const Icon(
Icons.campaign_outlined,
color: Colors.black,
),
title: const Text(
'Sugerencias',
style: TextStyle(fontSize: 15),
),
),
Builder(builder: (BuildContext context) {
return ListTile(
onTap: () {
if (ModalRoute.of(context)?.settings.name !=
'/servicio') {
Navigator.pushNamed(context, '/servicio');
} else {
Scaffold.of(context).openEndDrawer();
}
},
trailing: const Icon(
Icons.keyboard_arrow_right,
color: Colors.white,
),
title: const Text(
'Solicitar servicio',
style: TextStyle(
color: Colors.white,
fontSize: 17,
fontWeight: FontWeight.bold),
),
tileColor: const Color(0xFF2BA4EC),
contentPadding:
const EdgeInsets.symmetric(vertical: 5, horizontal: 16),
);
}),
Container(
decoration: BoxDecoration(
boxShadow: [
BoxShadow(
color: Colors.grey.withOpacity(0.5),
spreadRadius: 2,
blurRadius: 3,
offset:
const Offset(0, 2), // changes position of shadow
),
],
),
child: Container(
color: const Color(0xFFD6F4FF),
child: ListTile(
tileColor: const Color(0xFFD6F4FF),
onTap: () {
Navigator.push(
context,
CupertinoPageRoute(
builder: (BuildContext context) {
return const ReputationScreen();
},
),
);
},
trailing: const Icon(Icons.keyboard_arrow_right,
color: Colors.black),
title: const Text(
'Reputación',
style: TextStyle(color: Colors.black),
),
subtitle: Row(
children: [
RatingBar.builder(
initialRating: score?.average ?? 0,
minRating: 1,
direction: Axis.horizontal,
allowHalfRating: true,
itemCount: 5,
itemSize: 25,
maxRating: 5,
itemPadding:
const EdgeInsets.symmetric(horizontal: 0),
itemBuilder: (context, _) => const Icon(
Icons.star,
color: Color(0xFF2BA4EC),
),
onRatingUpdate: (rating) {},
ignoreGestures: true,
),
const SizedBox(width: 5),
Text(
'(${score?.total.toString()}) ${score?.average.toStringAsFixed(1)}'),
],
),
),
),
),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
'Prosapp',
style: TextStyle(fontSize: 10, color: Colors.grey[700]),
),
Padding(
padding: const EdgeInsets.only(top: 7, left: 3, right: 3),
child: Text(
'®',
style: TextStyle(fontSize: 25, color: Colors.grey[700]),
),
),
Text(
'todos los derechos reservados',
style: TextStyle(fontSize: 10, color: Colors.grey[700]),
),
],
),
],
),
),
Column(
children: [
ElevatedButton(
onPressed: () {
UserModel? user = userProvider.user;
if (user?.name == '' ||
user?.city == '' ||
user?.phoneNumber == '' ||
user?.phoneNumber == null) {
WarningSnackbar.show(
title: 'Completa tu perfil',
message:
'Para ser un profesional registrado, asegúrate de llenar todos los campos necesarios y no olvides guardar tus cambios para que surtan efecto.',
);
} else {
if (user?.phoneNumber != user?.phoneNumber) {
Get.snackbar(
'Tu número de teléfono sigue sin cambios.',
'Para guardar esta información, dirígete a tu perfil y selecciona la opción Guardar',
snackPosition: SnackPosition.BOTTOM,
);
} else {
if (user?.state == 'pendiente' || user?.state == null) {
Navigator.pop(context);
if (kIsWeb) {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) =>
const ProfessionalProfileWebScreen(),
),
);
} else {
Navigator.pushNamed(context, '/profesionalProfile');
}
} else if (user?.state == 'revision') {
Navigator.pushNamed(context, '/profesionalRevision');
} else if (user?.state == 'activo') {
Navigator.pushNamed(context, '/solicitud');
}
}
}
},
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFF2BA4EC),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(50),
),
elevation: 0,
minimumSize: const Size(230, 45),
),
child: const Text(
'Modo profesional',
style: TextStyle(
color: Colors.white,
fontSize: 15,
),
),
),
const SizedBox(height: 5),
ElevatedButton(
onPressed: () async {
await AuthenticationRepository.instance.logout(uid!);
userProvider.setNullUser();
},
style: ElevatedButton.styleFrom(
backgroundColor: Colors.red,
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(20)),
),
minimumSize: const Size(230, 40),
),
child: const Text(
'Cerrar Sesión',
style: TextStyle(
color: Colors.white,
fontSize: 15,
),
),
),
],
),
const SizedBox(height: 10),
],
),
);
}
ListTile infoUser(BuildContext context, UserModel? user) {
return ListTile(
onTap: () {
Navigator.push(
context,
CupertinoPageRoute(
builder: (BuildContext context) {
return const ProfileScreen();
},
),
);
},
title: Text(user?.name ?? '',
style: const TextStyle(fontWeight: FontWeight.bold)),
subtitle: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
user?.phoneNumber ?? '',
style: const TextStyle(fontSize: 12),
),
Text(
user?.city ?? '',
style: const TextStyle(fontSize: 12),
),
],
),
leading: ReferencePhoto(
ref: user?.photo,
size: 50,
sizeCircle: 50,
),
trailing: const Icon(Icons.keyboard_arrow_right, color: Colors.black),
contentPadding: const EdgeInsets.symmetric(vertical: 20, horizontal: 16),
);
}
}
@@ -1,50 +0,0 @@
import 'package:flutter/material.dart';
import 'package:shimmer/shimmer.dart';
class LoadingItemList extends StatelessWidget {
final bool useCircleAvatar;
const LoadingItemList({super.key, required this.useCircleAvatar});
@override
Widget build(BuildContext context) {
return Shimmer.fromColors(
baseColor: Colors.grey[300]!,
highlightColor: Colors.grey[100]!,
child: ListTile(
leading: useCircleAvatar
? const CircleAvatar(
radius: 25,
backgroundColor: Colors.white,
)
: Container(
width: 45,
height: 20,
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(8),
),
),
title: Container(
height: 20,
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(8),
),
),
subtitle: Container(
height: 15,
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(8),
),
),
trailing: const Icon(
Icons.chevron_right,
color: Colors.white,
size: 30,
),
),
);
}
}
@@ -1,51 +0,0 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
class NotificationSnackbar {
static void show({
required String title,
required String message,
SnackPosition position = SnackPosition.TOP,
Duration duration = const Duration(seconds: 4),
Color backgroundColor = Colors.red,
Color textColor = Colors.white,
double borderRadius = 10.0,
EdgeInsets margin = const EdgeInsets.all(10.0),
SnackStyle snackStyle = SnackStyle.FLOATING,
Duration animationDuration = const Duration(milliseconds: 800),
bool isDismissible = true,
DismissDirection dismissDirection = DismissDirection.horizontal,
Curve forwardAnimationCurve = Curves.easeOutBack,
Curve reverseAnimationCurve = Curves.easeInBack,
Icon icon = const Icon(Icons.warning, color: Colors.white),
bool shouldIconPulse = true,
}) {
Get.snackbar(
title,
message,
snackPosition: position,
duration: duration,
backgroundColor: backgroundColor,
colorText: textColor,
borderRadius: borderRadius,
margin: margin,
snackStyle: snackStyle,
animationDuration: animationDuration,
isDismissible: isDismissible,
dismissDirection: dismissDirection,
forwardAnimationCurve: forwardAnimationCurve,
reverseAnimationCurve: reverseAnimationCurve,
icon: icon,
shouldIconPulse: shouldIconPulse,
titleText: Text(
title,
style: const TextStyle(
fontSize: 18.0, fontWeight: FontWeight.bold, color: Colors.white),
),
messageText: Text(
message,
style: const TextStyle(fontSize: 16.0, color: Colors.white),
),
);
}
}
@@ -1,41 +0,0 @@
import 'package:flutter/material.dart';
class PrimaryButton extends StatelessWidget {
final VoidCallback onPressed;
final String text;
final double? minWidth;
final double? minHeight;
final bool isEnabled;
const PrimaryButton({
super.key,
required this.onPressed,
required this.text,
this.minWidth = 200,
this.minHeight = 50,
this.isEnabled = true,
});
@override
Widget build(BuildContext context) {
return ElevatedButton(
onPressed: isEnabled ? onPressed : null,
style: ElevatedButton.styleFrom(
backgroundColor: isEnabled ? const Color(0xFF2BA4EC) : Colors.grey,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(50),
),
elevation: 0,
minimumSize: Size(minWidth!, minHeight!),
),
child: Text(
text,
style: TextStyle(
color: isEnabled ? Colors.white : Colors.black,
fontWeight: FontWeight.bold,
fontSize: 17,
),
),
);
}
}
@@ -1,28 +0,0 @@
import 'package:flutter/material.dart';
class PrimaryCheckbox extends StatelessWidget {
final String text;
final bool initialValue;
final Function(bool) onChanged;
const PrimaryCheckbox({
Key? key,
required this.text,
required this.initialValue,
required this.onChanged,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return CheckboxListTile(
title: Text(
text,
style: const TextStyle(fontSize: 15),
),
value: initialValue,
onChanged: (value) {
onChanged(value!);
},
);
}
}
@@ -1,51 +0,0 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
class WarningSnackbar {
static void show({
required String title,
required String message,
SnackPosition position = SnackPosition.TOP,
Duration duration = const Duration(seconds: 4),
Color backgroundColor = Colors.red,
Color textColor = Colors.white,
double borderRadius = 10.0,
EdgeInsets margin = const EdgeInsets.all(10.0),
SnackStyle snackStyle = SnackStyle.FLOATING,
Duration animationDuration = const Duration(milliseconds: 800),
bool isDismissible = true,
DismissDirection dismissDirection = DismissDirection.horizontal,
Curve forwardAnimationCurve = Curves.easeOutBack,
Curve reverseAnimationCurve = Curves.easeInBack,
Icon icon = const Icon(Icons.warning, color: Colors.white),
bool shouldIconPulse = true,
}) {
Get.snackbar(
title,
message,
snackPosition: position,
duration: duration,
backgroundColor: backgroundColor,
colorText: textColor,
borderRadius: borderRadius,
margin: margin,
snackStyle: snackStyle,
animationDuration: animationDuration,
isDismissible: isDismissible,
dismissDirection: dismissDirection,
forwardAnimationCurve: forwardAnimationCurve,
reverseAnimationCurve: reverseAnimationCurve,
icon: icon,
shouldIconPulse: shouldIconPulse,
titleText: Text(
title,
style: const TextStyle(
fontSize: 18.0, fontWeight: FontWeight.bold, color: Colors.white),
),
messageText: Text(
message,
style: const TextStyle(fontSize: 16.0, color: Colors.white),
),
);
}
}
-54
View File
@@ -1,54 +0,0 @@
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:flutter/material.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:prosappco/src/models/scores_model.dart';
import 'package:prosappco/src/models/user_model.dart';
class UserProvider extends ChangeNotifier {
final FirebaseFirestore _firestore = FirebaseFirestore.instance;
UserModel? _user;
ScoresModel? _score;
Stream<DocumentSnapshot<Map<String, dynamic>>>? _stream;
UserProvider() {
initUserProvider();
}
void initUserProvider() {
var uid = FirebaseAuth.instance.currentUser?.uid;
if (uid == null) return;
_stream = _firestore.collection('users').doc(uid).snapshots();
_stream?.listen((documentSnapshot) async {
if (documentSnapshot.exists) {
final data = documentSnapshot.data() as Map<String, dynamic>;
_user = UserModel.fromFirestore(data);
_score = await ScoresModel.scoreTo(uid, false, false);
notifyListeners();
}
}, onDone: () {}, onError: (error) {});
}
void setNullUser() {
_user = null;
_score = null;
_stream = null;
notifyListeners();
}
Future<void> updateUserDataAndScores() async {
var uid = FirebaseAuth.instance.currentUser?.uid;
if (uid == null) return;
var documentSnapshot = await _firestore.collection('users').doc(uid).get();
if (documentSnapshot.exists) {
final data = documentSnapshot.data() as Map<String, dynamic>;
_user = UserModel.fromFirestore(data);
_score = await ScoresModel.scoreTo(uid, false, false);
notifyListeners();
}
}
UserModel? get user => _user;
ScoresModel? get score => _score;
}
-56
View File
@@ -1,56 +0,0 @@
import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:get/get.dart';
class FirebaseMessagingService {
FirebaseMessaging _firebaseMessaging = FirebaseMessaging.instance;
Future<void> initializeFirebaseMessaging() async {
// Solicitar permisos de notificación si es necesario (opcional)
NotificationSettings settings = await _firebaseMessaging.requestPermission(
alert: true,
badge: true,
sound: true,
);
// Verificar si los permisos de notificación están habilitados
if (settings.authorizationStatus == AuthorizationStatus.authorized ||
settings.authorizationStatus == AuthorizationStatus.provisional) {
// Obtener el token de registro para la instancia de la aplicación
String? token = await _firebaseMessaging.getToken();
print('Token FCM: $token');
FirebaseMessaging.onMessage.listen((RemoteMessage message) {
print(
'Mensaje FCM recibido: ${message.notification?.title} - ${message.notification?.body}');
// Obtener el valor de la clave "screen" de los datos de la notificación
String? notificationScreen = message.data['screen'];
// Navegar a la pantalla correspondiente según el valor de la clave "screen"
if (notificationScreen == "misservicios") {
// Navegar a MyServicesScreen
Get.offNamed('/misservicios');
} else if (notificationScreen == "solicitud") {
// Navegar a SolicitudScreen
Get.offNamed('/solicitud');
}
});
// Manejar la notificación cuando se toca y la aplicación está en primer plano (opcional)
FirebaseMessaging.onMessageOpenedApp.listen((RemoteMessage message) {
print(
'Mensaje FCM abierto desde la aplicación en primer plano: ${message.notification?.title} - ${message.notification?.body}');
// Aquí puedes redirigir al usuario a una pantalla específica o realizar acciones según los datos recibidos
});
// Manejar la notificación cuando se toca y la aplicación está cerrada (opcional)
RemoteMessage? initialMessage =
await FirebaseMessaging.instance.getInitialMessage();
if (initialMessage != null) {
print(
'Mensaje FCM abierto desde la aplicación cerrada: ${initialMessage.notification?.title} - ${initialMessage.notification?.body}');
// Aquí puedes redirigir al usuario a una pantalla específica o realizar acciones según los datos recibidos
}
}
}
}
-47
View File
@@ -1,47 +0,0 @@
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
import 'package:prosappco/src/services/firebase_messaging.dart';
final FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin =
FlutterLocalNotificationsPlugin();
Future<void> initNotifications() async {
const AndroidInitializationSettings initializationSettingsAndroid =
AndroidInitializationSettings('@mipmap/ic_launcher');
const DarwinInitializationSettings initializationSettingsIOS =
DarwinInitializationSettings(
requestAlertPermission: true,
requestBadgePermission: true,
requestSoundPermission: true,
);
const InitializationSettings initializationSettings = InitializationSettings(
android: initializationSettingsAndroid,
iOS: initializationSettingsIOS,
);
await flutterLocalNotificationsPlugin.initialize(initializationSettings);
// Inicializar Firebase Messaging
FirebaseMessagingService firebaseMessagingService =
FirebaseMessagingService();
await firebaseMessagingService.initializeFirebaseMessaging();
}
Future<void> showNotification(String title, String body) async {
const AndroidNotificationDetails androidNotificationDetails =
AndroidNotificationDetails('solicitud_servicio', 'Solicitud de Servicio',
importance: Importance.max, priority: Priority.high);
// IOSNotificationDetails --> DarwinNotificationDetails
const DarwinNotificationDetails iOSNotificationDetails =
DarwinNotificationDetails(
presentAlert: true,
presentBadge: true,
presentSound: true,
);
const NotificationDetails notificationDetails = NotificationDetails(
android: androidNotificationDetails, iOS: iOSNotificationDetails);
await flutterLocalNotificationsPlugin.show(
1, title, body, notificationDetails);
}
@@ -1,16 +0,0 @@
import 'package:image_picker/image_picker.dart';
Future<List<XFile?>> getImage(opc) async {
final ImagePicker picker = ImagePicker();
if (opc == 1) {
XFile? image = await picker.pickImage(source: ImageSource.camera);
return [image];
} else if (opc == 2) {
XFile? image = await picker.pickImage(source: ImageSource.gallery);
return [image];
} else {
final List<XFile> images = await picker.pickMultiImage();
return images;
}
}
-19
View File
@@ -1,19 +0,0 @@
import 'package:flutter/material.dart';
extension TimeOfDayExtension on TimeOfDay {
TimeOfDay add({int hour = 0, int minute = 0}) {
return replacing(hour: this.hour + hour, minute: this.minute + minute);
}
int compareTo(TimeOfDay other) {
if (hour < other.hour) return -1;
if (hour > other.hour) return 1;
if (minute < other.minute) return -1;
if (minute > other.minute) return 1;
return 0;
}
bool isBefore(TimeOfDay other) {
return compareTo(other) == -1;
}
}
-15
View File
@@ -1,15 +0,0 @@
import 'package:flutter/material.dart';
import 'package:prosappco/src/utils/time_of_day_extension.dart';
class TimeOfDayUtils {
static List<TimeOfDay> genRanges(TimeOfDay timeStart, TimeOfDay timeEnd) {
List<TimeOfDay> ranges = [];
TimeOfDay current = timeStart;
while (current.isBefore(timeEnd)) {
ranges.add(current);
// Sumar 2 horas al objeto DateTime
current = current.add(hour: 2);
}
return ranges;
}
}
@@ -1,5 +1,4 @@
import 'package:city_repository/city_repository.dart';
import 'package:city_repository/src/models/city_ui.dart';
abstract class CityRepository {
Future<List<CityUi>> getCities();
@@ -0,0 +1,6 @@
library profession_repository;
export 'src/models/models.dart';
export 'src/entities/entities.dart';
export 'src/repositories/profession_repo.dart';
export 'src/repositories/firebase_profession_repository.dart';
@@ -0,0 +1 @@
export '/src/entities/profession_entity.dart';
@@ -0,0 +1,29 @@
import 'package:equatable/equatable.dart';
class ProfessionEntity extends Equatable {
final String name;
const ProfessionEntity({required this.name});
Map<String, Object?> toDocument() {
return {
'name': name,
};
}
static ProfessionEntity fromDocument(Map<String, dynamic> doc) {
return ProfessionEntity(
name: doc['name'] as String,
);
}
@override
List<Object?> get props => [name];
@override
String toString() {
return '''ProfessionEntity {
name: $name
}''';
}
}
@@ -0,0 +1 @@
export 'profession_ui.dart';
@@ -0,0 +1,12 @@
import 'package:equatable/equatable.dart';
class ProfessionUi extends Equatable {
final String name;
const ProfessionUi({
required this.name,
});
@override
List<Object?> get props => [name];
}
@@ -0,0 +1,30 @@
import 'dart:developer';
import 'package:profession_repository/profession_repository.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
class FirebaseProfessionRepository implements ProfessionRepository {
final professionsCollection =
FirebaseFirestore.instance.collection('professions');
@override
Future<Professions> getProfessions() async {
try {
final doc = await professionsCollection.doc("professions").get();
return Professions.fromDocument(doc.data() as Map<String, dynamic>);
} catch (e) {
log('Error getting documents: $e');
rethrow;
}
}
}
class Professions {
final List<String> professions;
Professions(this.professions);
factory Professions.fromDocument(Map<String, dynamic> json) {
return Professions(List<String>.from(json['professions']));
}
}

Some files were not shown because too many files have changed in this diff Show More