Files
prosappco/lib/screens/configuration/configuration_support_screen.dart
T
Lizandro GuarnizoandClaude Opus 5 8631e6f729 fix: port 7 web features and repair the endless-loading screens
Root cause behind most "stuck loading" reports: the backend changed shape
(schedules became an array, location_preferences a string) while the mobile
parser still hard-cast to Map/int. The TypeError was swallowed by a silent
catch that returned null, and screens only handled the success state, so a
parse failure rendered as a permanent spinner. Same class of bug appeared
across service lists via non-null map lookups and a total absence of
request timeouts.

Ported from prosappweb:
- in-app suggestions (POST /suggestions)
- policies/terms from GET /settings/policies
- configurable appointment length (slot_duration_minutes)
- block/unblock calendar slots (POST /services/block)
- GPS city detection on the profile (Nominatim)
- server-side professional search with haversine distance
- retry cooldown after a rejected professional application

Reliability:
- parse schedules array (day_of_week 0=Mon) and string location_preferences
- read times as wall clock, so 08:00 stays 08:00 across timezones
- carry minutes into hours in TimeOfDay.add; a minute-based step used to
  loop forever and freeze the calendar (covered by test/time_slots_test.dart)
- semver update check instead of string equality, which blocked every build
  that did not exactly match the configured version
- request timeouts across all repositories
- surface HTTP >= 400 instead of reporting failed writes as success
- error states with retry instead of an indefinite shimmer

Includes pre-existing uncommitted work from the UI redesign.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-24 16:45:42 -05:00

216 lines
7.2 KiB
Dart

import 'package:community_material_icon/community_material_icon.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:injector/injector.dart';
import 'package:setting_repository/setting_repository.dart';
import 'package:url_launcher/url_launcher.dart';
import 'package:flutter_email_sender/flutter_email_sender.dart';
class ConfigurationSupportScreen extends StatefulWidget {
const ConfigurationSupportScreen({super.key});
@override
State<ConfigurationSupportScreen> createState() =>
_ConfigurationSupportScreenState();
}
class _ConfigurationSupportScreenState
extends State<ConfigurationSupportScreen> {
final settingRepository = Injector.appInstance.get<SettingRepository>();
SettingEntity? settings;
final String subject = 'Soporte Prosapp';
final String body = '';
@override
void initState() {
super.initState();
_loadSettings();
}
void _loadSettings() {
settingRepository.getSettings().then(
(value) => setState(() {
settings = value;
}),
);
}
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)) {
ScaffoldMessenger.of(context).clearSnackBars();
ScaffoldMessenger.of(context).showSnackBar(const SnackBar(
content: Text('Error al enviar el mensaje de WhatsApp'),
));
}
}
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 {
ScaffoldMessenger.of(context).clearSnackBars();
ScaffoldMessenger.of(context).showSnackBar(const SnackBar(
content: Text('Error al enviar el correo'),
));
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Soporte'),
),
body: Column(
children: [
Padding(
padding: const EdgeInsets.only(top: 30, left: 35, right: 35),
child: Text(
settings == null
? 'Cargando...'
: (settings?.tituloSoporte ?? 'Soporte'),
style: const TextStyle(fontWeight: FontWeight.w600),
),
),
Padding(
padding: const EdgeInsets.symmetric(vertical: 30, horizontal: 35),
child: Text(
settings == null ? 'Cargando...' : (settings?.parrafoSoporte ?? ''),
),
),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Material(
color: Colors.blue, // Color de fondo azul
shape: const CircleBorder(), // Forma circular
child: InkWell(
borderRadius: BorderRadius.circular(
30), // Radio del área de interacción
onTap: () {
_sendWhatsapp(settings?.numeroSoporte);
},
child: const Padding(
padding: EdgeInsets.all(15),
child: Icon(
CommunityMaterialIcons.whatsapp,
color: Colors.white,
size: 30,
),
),
),
),
const SizedBox(width: 20),
Material(
color: Colors.blue,
shape: const CircleBorder(),
child: InkWell(
borderRadius: BorderRadius.circular(30),
onTap: () {
if (kIsWeb) {
_sendEmailWeb(settings?.emailSoporte ?? '');
} else {
_sendEmail(settings?.emailSoporte ?? '');
}
},
child: const Padding(
padding: EdgeInsets.all(15),
child: Icon(
Icons.email_outlined,
color: Colors.white,
size: 30,
),
),
),
),
// ElevatedButton(
// onPressed: () {
// // _sendWhatsapp(settings?.numeroSoporte);
// },
// 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?.emailSoporte ?? '');
// } else {
// // _sendEmail(settings?.emailSoporte ?? '');
// }
// },
// 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,
// ),
// ),
// ),
],
),
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?.diasSoporte}'),
const SizedBox(height: 5),
Text('${settings?.horasSoporte}'),
],
),
),
],
),
);
}
}