form pro - sin imagenes
This commit is contained in:
@@ -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];
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
@@ -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>())));
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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,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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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)),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
@@ -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),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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});
|
||||
}
|
||||
@@ -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 [];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 }';
|
||||
}
|
||||
}
|
||||
@@ -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, '', '');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 ?? ''),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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(),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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
@@ -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'),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
)),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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];
|
||||
}
|
||||
+30
@@ -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
Reference in New Issue
Block a user