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:injector/injector.dart';
|
||||||
import 'package:prosappco/blocs/authentication_bloc/authentication_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/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/profile_bloc/profile_bloc.dart';
|
||||||
|
|
||||||
import 'app_view.dart';
|
import 'app_view.dart';
|
||||||
@@ -23,6 +24,9 @@ class MainApp extends StatelessWidget {
|
|||||||
BlocProvider<ProfileBloc>(
|
BlocProvider<ProfileBloc>(
|
||||||
create: (context) => Injector.appInstance.get<ProfileBloc>(),
|
create: (context) => Injector.appInstance.get<ProfileBloc>(),
|
||||||
),
|
),
|
||||||
|
BlocProvider<ProfessionalBloc>(
|
||||||
|
create: (context) => Injector.appInstance.get<ProfessionalBloc>(),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
child: BlocBuilder<MyUserBloc, MyUserState>(
|
child: BlocBuilder<MyUserBloc, MyUserState>(
|
||||||
builder: (context, state) {
|
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/cupertino.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/material.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_header.dart';
|
||||||
import 'package:prosappco/components/general_drawer_item.dart';
|
import 'package:prosappco/components/general_drawer_item.dart';
|
||||||
import 'package:prosappco/screens/configuration/configuration_screen.dart';
|
import 'package:prosappco/screens/configuration/configuration_screen.dart';
|
||||||
import 'package:prosappco/screens/configuration/configuration_support_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:prosappco/screens/web/web_view_screen.dart';
|
||||||
import 'package:url_launcher/url_launcher.dart';
|
import 'package:url_launcher/url_launcher.dart';
|
||||||
|
import 'package:user_repository/user_repository.dart';
|
||||||
|
|
||||||
class GeneralDrawer extends StatelessWidget {
|
class GeneralDrawer extends StatelessWidget {
|
||||||
const GeneralDrawer({super.key});
|
const GeneralDrawer({super.key});
|
||||||
@@ -25,6 +32,10 @@ class GeneralDrawer extends StatelessWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
return BlocBuilder<MyUserBloc, MyUserState>(
|
||||||
|
builder: (context, userState) {
|
||||||
|
return BlocBuilder<ProfessionalBloc, ProfessionalState>(
|
||||||
|
builder: (context, professionalState) {
|
||||||
return Drawer(
|
return Drawer(
|
||||||
backgroundColor: Theme.of(context).colorScheme.background,
|
backgroundColor: Theme.of(context).colorScheme.background,
|
||||||
child: Column(
|
child: Column(
|
||||||
@@ -32,7 +43,10 @@ class GeneralDrawer extends StatelessWidget {
|
|||||||
children: [
|
children: [
|
||||||
const GeneralDrawerHeader(),
|
const GeneralDrawerHeader(),
|
||||||
Divider(
|
Divider(
|
||||||
color: Theme.of(context).colorScheme.onSurface.withOpacity(0.1),
|
color: Theme.of(context)
|
||||||
|
.colorScheme
|
||||||
|
.onSurface
|
||||||
|
.withOpacity(0.1),
|
||||||
thickness: 0.5,
|
thickness: 0.5,
|
||||||
height: 1,
|
height: 1,
|
||||||
),
|
),
|
||||||
@@ -51,11 +65,11 @@ class GeneralDrawer extends StatelessWidget {
|
|||||||
leading: Icons.settings_outlined,
|
leading: Icons.settings_outlined,
|
||||||
label: 'Configuración',
|
label: 'Configuración',
|
||||||
onTap: () {
|
onTap: () {
|
||||||
Navigator.pop(context);
|
|
||||||
Navigator.push(
|
Navigator.push(
|
||||||
context,
|
context,
|
||||||
CupertinoPageRoute(
|
CupertinoPageRoute(
|
||||||
builder: (context) => const ConfigurationScreen(),
|
builder: (context) =>
|
||||||
|
const ConfigurationScreen(),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
@@ -64,7 +78,6 @@ class GeneralDrawer extends StatelessWidget {
|
|||||||
leading: Icons.help_outline,
|
leading: Icons.help_outline,
|
||||||
label: 'Soporte',
|
label: 'Soporte',
|
||||||
onTap: () {
|
onTap: () {
|
||||||
Navigator.pop(context);
|
|
||||||
Navigator.push(
|
Navigator.push(
|
||||||
context,
|
context,
|
||||||
CupertinoPageRoute(
|
CupertinoPageRoute(
|
||||||
@@ -87,7 +100,8 @@ class GeneralDrawer extends StatelessWidget {
|
|||||||
builder: (BuildContext context) {
|
builder: (BuildContext context) {
|
||||||
return WebViewScreen(
|
return WebViewScreen(
|
||||||
label: 'Sugerencias',
|
label: 'Sugerencias',
|
||||||
link: 'https://admin.prosapp.co/sugerencias');
|
link:
|
||||||
|
'https://admin.prosapp.co/sugerencias');
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@@ -99,30 +113,83 @@ class GeneralDrawer extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
Divider(
|
Divider(
|
||||||
color: Theme.of(context).colorScheme.onSurface.withOpacity(0.1),
|
color: Theme.of(context)
|
||||||
|
.colorScheme
|
||||||
|
.onSurface
|
||||||
|
.withOpacity(0.1),
|
||||||
thickness: 0.5,
|
thickness: 0.5,
|
||||||
height: 1,
|
height: 1,
|
||||||
),
|
),
|
||||||
const SizedBox(height: 15),
|
const SizedBox(height: 15),
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 20),
|
padding: const EdgeInsets.symmetric(horizontal: 20),
|
||||||
child: ElevatedButton(
|
child: buttonOfState(context, professionalState),
|
||||||
onPressed: () {},
|
),
|
||||||
|
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(
|
style: ElevatedButton.styleFrom(
|
||||||
backgroundColor: Theme.of(context).colorScheme.primary,
|
backgroundColor: Theme.of(context).colorScheme.primary,
|
||||||
padding: const EdgeInsets.symmetric(vertical: 15),
|
padding: const EdgeInsets.symmetric(vertical: 15),
|
||||||
shape: RoundedRectangleBorder(
|
shape: RoundedRectangleBorder(
|
||||||
borderRadius: BorderRadius.circular(10),
|
borderRadius: BorderRadius.circular(10),
|
||||||
)),
|
)),
|
||||||
child: const Text(
|
child: (state is LoadedModeProState)
|
||||||
'Modo Profesional',
|
? Text(
|
||||||
style: TextStyle(color: Colors.white, fontSize: 18),
|
state.isProModeActive ? 'Modo cliente' : 'Modo profesional',
|
||||||
),
|
style: const TextStyle(color: Colors.white, fontSize: 18),
|
||||||
),
|
)
|
||||||
),
|
: const CircularProgressIndicator(
|
||||||
const SizedBox(height: 15),
|
color: Colors.white,
|
||||||
],
|
));
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import 'package:flutter/cupertino.dart';
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:prosappco/blocs/my_user_bloc/my_user_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';
|
import 'package:prosappco/screens/profile/profile_screen.dart';
|
||||||
|
|
||||||
class GeneralDrawerHeader extends StatelessWidget {
|
class GeneralDrawerHeader extends StatelessWidget {
|
||||||
|
|||||||
@@ -1,8 +1,11 @@
|
|||||||
import 'package:firebase_auth/firebase_auth.dart';
|
import 'package:firebase_auth/firebase_auth.dart';
|
||||||
import 'package:injector/injector.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/auth_bloc/auth_bloc.dart';
|
||||||
import 'package:prosappco/blocs/authentication_bloc/authentication_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/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/profile_bloc/profile_bloc.dart';
|
||||||
import 'package:prosappco/blocs/setting_bloc/setting_bloc.dart';
|
import 'package:prosappco/blocs/setting_bloc/setting_bloc.dart';
|
||||||
import 'package:prosappco/blocs/sign_up_bloc/sign_up_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<CityRepository>(() => FirebaseCityRepository());
|
||||||
|
|
||||||
|
injector.registerSingleton<ProfessionRepository>(
|
||||||
|
() => FirebaseProfessionRepository());
|
||||||
|
|
||||||
injector.registerSingleton<SettingRepository>(
|
injector.registerSingleton<SettingRepository>(
|
||||||
() => FirebaseSettingRepository());
|
() => FirebaseSettingRepository());
|
||||||
|
|
||||||
|
injector.registerSingleton(
|
||||||
|
() => FirebaseProfessionalRepository(),
|
||||||
|
);
|
||||||
|
|
||||||
injector.registerSingleton<AuthenticationBloc>((() =>
|
injector.registerSingleton<AuthenticationBloc>((() =>
|
||||||
AuthenticationBloc(myUserRepository: injector.get<UserRepository>())));
|
AuthenticationBloc(myUserRepository: injector.get<UserRepository>())));
|
||||||
|
|
||||||
@@ -32,6 +42,12 @@ class AppDI {
|
|||||||
injector.registerSingleton<ProfileBloc>(
|
injector.registerSingleton<ProfileBloc>(
|
||||||
(() => ProfileBloc(userRepository: injector.get<UserRepository>())));
|
(() => ProfileBloc(userRepository: injector.get<UserRepository>())));
|
||||||
|
|
||||||
|
injector.registerSingleton(
|
||||||
|
() => ProfessionalBloc(
|
||||||
|
professionalRepository: injector.get(),
|
||||||
|
userRepository: injector.get<UserRepository>()),
|
||||||
|
);
|
||||||
|
|
||||||
injector.registerDependency<SignInBloc>(
|
injector.registerDependency<SignInBloc>(
|
||||||
(() => SignInBloc(userRepository: injector.get<UserRepository>())));
|
(() => SignInBloc(userRepository: injector.get<UserRepository>())));
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
import 'package:city_repository/city_repository.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:city_repository/city_repository.dart';
|
||||||
import 'package:injector/injector.dart';
|
import 'package:injector/injector.dart';
|
||||||
import 'package:intl_phone_field/helpers.dart';
|
import 'package:intl_phone_field/helpers.dart';
|
||||||
import 'package:prosappco/src/components/pop_appbar.dart';
|
|
||||||
import 'package:shimmer/shimmer.dart';
|
import 'package:shimmer/shimmer.dart';
|
||||||
|
|
||||||
class CityScreen extends StatefulWidget {
|
class CityScreen extends StatefulWidget {
|
||||||
@@ -17,7 +16,6 @@ class _CityScreenState extends State<CityScreen> {
|
|||||||
final cityRepository = Injector.appInstance.get<CityRepository>();
|
final cityRepository = Injector.appInstance.get<CityRepository>();
|
||||||
List<CityUi>? _cities;
|
List<CityUi>? _cities;
|
||||||
List<CityUi>? _filteredCities;
|
List<CityUi>? _filteredCities;
|
||||||
|
|
||||||
bool _isLoading = true;
|
bool _isLoading = true;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -38,13 +36,9 @@ class _CityScreenState extends State<CityScreen> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return SafeArea(
|
return Scaffold(
|
||||||
child: Scaffold(
|
appBar: AppBar(
|
||||||
appBar: PopAppbar(
|
title: const Text('Ciudad'),
|
||||||
onPressed: () {
|
|
||||||
Navigator.pop(context);
|
|
||||||
},
|
|
||||||
label: 'Ciudad',
|
|
||||||
),
|
),
|
||||||
body: Column(
|
body: Column(
|
||||||
children: [
|
children: [
|
||||||
@@ -67,8 +61,7 @@ class _CityScreenState extends State<CityScreen> {
|
|||||||
_isLoading
|
_isLoading
|
||||||
? _buildShimmerEffect()
|
? _buildShimmerEffect()
|
||||||
: Expanded(
|
: Expanded(
|
||||||
child: _filteredCities != null &&
|
child: _filteredCities != null && _filteredCities!.isNotEmpty
|
||||||
_filteredCities!.isNotEmpty
|
|
||||||
? ListView.builder(
|
? ListView.builder(
|
||||||
itemCount: _filteredCities!.length,
|
itemCount: _filteredCities!.length,
|
||||||
itemBuilder: (BuildContext context, int index) {
|
itemBuilder: (BuildContext context, int index) {
|
||||||
@@ -89,8 +82,7 @@ class _CityScreenState extends State<CityScreen> {
|
|||||||
),
|
),
|
||||||
subtitle: Text(
|
subtitle: Text(
|
||||||
"${_filteredCities![index].stateOfCity}, ${_filteredCities![index].countryOfCity}",
|
"${_filteredCities![index].stateOfCity}, ${_filteredCities![index].countryOfCity}",
|
||||||
style:
|
style: const TextStyle(color: Colors.black54),
|
||||||
const TextStyle(color: Colors.black54),
|
|
||||||
),
|
),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
Navigator.pop(context,
|
Navigator.pop(context,
|
||||||
@@ -101,12 +93,11 @@ class _CityScreenState extends State<CityScreen> {
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
: const Center(
|
: const Center(
|
||||||
child: Text('No se encontraron ciudades.'),
|
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/blocs/my_user_bloc/my_user_bloc.dart';
|
||||||
import 'package:prosappco/components/general_input_decoration.dart';
|
import 'package:prosappco/components/general_input_decoration.dart';
|
||||||
import 'package:prosappco/components/general_primary_button.dart';
|
import 'package:prosappco/components/general_primary_button.dart';
|
||||||
import 'package:prosappco/screens/authentication/otp_auth_screen.dart';
|
|
||||||
|
|
||||||
class ProfileRegisterPhoneScreen extends StatefulWidget {
|
class ProfileRegisterPhoneScreen extends StatefulWidget {
|
||||||
const ProfileRegisterPhoneScreen({super.key});
|
const ProfileRegisterPhoneScreen({super.key});
|
||||||
@@ -22,8 +21,6 @@ class ProfileRegisterPhoneScreen extends StatefulWidget {
|
|||||||
|
|
||||||
class _ProfileRegisterPhoneScreenState
|
class _ProfileRegisterPhoneScreenState
|
||||||
extends State<ProfileRegisterPhoneScreen> {
|
extends State<ProfileRegisterPhoneScreen> {
|
||||||
final TextEditingController _actualPasswordController =
|
|
||||||
TextEditingController();
|
|
||||||
late final AuthBloc authBloc;
|
late final AuthBloc authBloc;
|
||||||
late String verificationCode;
|
late String verificationCode;
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:prosappco/src/components/pop_appbar.dart';
|
|
||||||
import 'package:webview_flutter/webview_flutter.dart';
|
import 'package:webview_flutter/webview_flutter.dart';
|
||||||
|
|
||||||
class WebViewScreen extends StatefulWidget {
|
class WebViewScreen extends StatefulWidget {
|
||||||
@@ -56,11 +55,8 @@ class _WebViewScreenState extends State<WebViewScreen> {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: PopAppbar(
|
appBar: AppBar(
|
||||||
onPressed: () {
|
title: Text(widget.label),
|
||||||
Navigator.pop(context);
|
|
||||||
},
|
|
||||||
label: widget.label,
|
|
||||||
),
|
),
|
||||||
body: Stack(
|
body: Stack(
|
||||||
children: [
|
children: [
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import 'dart:developer';
|
import 'dart:developer';
|
||||||
|
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
|
||||||
class SimpleBlocObserver extends BlocObserver {
|
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/city_repository.dart';
|
||||||
import 'package:city_repository/src/models/city_ui.dart';
|
|
||||||
|
|
||||||
abstract class CityRepository {
|
abstract class CityRepository {
|
||||||
Future<List<CityUi>> getCities();
|
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