import 'dart:async'; import 'dart:convert'; import 'dart:io'; import 'dart:ui' as ui; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:michele_s_application8/widgets/custom_button.dart'; import 'package:michele_s_application8/widgets/custom_text_form_field.dart'; import 'package:http/http.dart' as http; import 'package:michele_s_application8/presentation/camera_page/camera_page.dart'; import 'package:flutter_dotenv/flutter_dotenv.dart'; import 'package:shared_preferences/shared_preferences.dart'; class LoginScreen extends StatefulWidget { @override _LoginScreenState createState() => _LoginScreenState(); } class _LoginScreenState extends State { GlobalKey _formKey = GlobalKey(); final TextEditingController _timeController1 = TextEditingController(); final TextEditingController _titleController = TextEditingController(); final TextEditingController _presenterNameController = TextEditingController(); final TextEditingController _camNumberController = TextEditingController(text: '1'); late TextEditingController _languageController; bool isRecording = false; bool isLoading = false; bool isError = false; bool showStartRecordingButton = true; Timer? _recordingTimer; List _defaultTimes = []; String mainTitle = ''; String selectDuration = ''; String enterCustomDuration = ''; String insertTitle = ''; String insertPresenterName = ''; String startRecording = ''; String recordingDetails = ''; String goToCamera = ''; @override void initState() { super.initState(); String deviceLanguage = ui.window.locale.languageCode; String language = (deviceLanguage == 'en') ? 'EN' : (deviceLanguage == 'it') ? 'IT' : 'IT'; _languageController = TextEditingController(text: language); fetchStringsFromServer(); } Future fetchStringsFromServer() async { try { String token = await loginAndGetToken(); // Get the token first HttpClient httpClient = new HttpClient() ..badCertificateCallback = ((X509Certificate cert, String host, int port) => true); var request = await httpClient.getUrl(Uri.parse(dotenv.env['SERVER_URL']! + '/get-strings')) ..headers.add('Authorization', 'Bearer $token'); var response = await request.close(); var responseBody = await response.transform(utf8.decoder).join(); if (response.statusCode == 200) { final data = jsonDecode(responseBody); setState(() { _defaultTimes = List.from(data['durations']); selectDuration = data['select_duration']; enterCustomDuration = data['enter_custom_duration']; insertTitle = data['insert_title']; insertPresenterName = data['insert_presenter_name']; startRecording = data['start_recording']; recordingDetails = data['recording_details']; goToCamera = data['go_to_camera']; mainTitle = data['main_title']; }); } else { print('Failed to load strings with status code: ${response.statusCode}'); throw Exception('Failed to load strings'); } } catch (e) { print('Error fetching strings: $e'); // Debug: Print error message setState(() { isError = true; _defaultTimes = ["00:05:00", "01:00:00", "01:30:00", "02:00:00"]; mainTitle = 'CookingLab'; selectDuration = 'seleziona Durata'; enterCustomDuration = 'Oppure inserisci una durata personalizzata'; insertTitle = 'Inserisci il titolo'; insertPresenterName = 'Inserisci il nome del relatore'; startRecording = 'Inizia a Registrare'; recordingDetails = 'Dettagli di registrazione'; goToCamera = 'Vai a Fotocamera'; }); } } @override void dispose() { _recordingTimer?.cancel(); super.dispose(); } @override Widget build(BuildContext context) { return SafeArea( child: Scaffold( resizeToAvoidBottomInset: false, body: Container( decoration: BoxDecoration( gradient: LinearGradient( begin: Alignment.topLeft, end: Alignment.bottomRight, colors: [Colors.white, Colors.purple], ), ), child: Stack( children: [ Center( // Use Center to vertically and horizontally center the form child: Column( mainAxisSize: MainAxisSize.min, // Minimize the column size to its content children: [ if (!isRecording || showStartRecordingButton) Text( mainTitle, style: TextStyle(fontSize: 32, fontWeight: FontWeight.bold), ), if (!isRecording || showStartRecordingButton) Form( key: _formKey, child: Container( width: double.maxFinite, padding: EdgeInsets.symmetric(horizontal: 23), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ DropdownButtonFormField( value: null, hint: Text(selectDuration), icon: Icon(Icons.timer), decoration: InputDecoration( border: OutlineInputBorder(), ), items: _defaultTimes.map((String value) { return DropdownMenuItem( value: value, child: Text(value), ); }).toList(), onChanged: (newValue) { setState(() { _timeController1.text = newValue!; }); }, validator: (value) { if (_timeController1.text.isEmpty || !isTime(_timeController1.text)) { return "Inserisci un'ora valida (HH:MM:SS)"; } return null; }, ), SizedBox(height: 12), GestureDetector( onTap: () async { await showDialog( context: context, builder: (BuildContext context) { return Dialog( child: Container( height: MediaQuery.of(context).copyWith().size.height / 3, child: CupertinoTimerPicker( mode: CupertinoTimerPickerMode.hms, onTimerDurationChanged: (Duration duration) { _timeController1.text = "${duration.inHours.toString().padLeft(2, '0')}:${duration.inMinutes.remainder(60).toString().padLeft(2, '0')}:${duration.inSeconds.remainder(60).toString().padLeft(2, '0')}"; }, initialTimerDuration: Duration(hours: 0, minutes: 0, seconds: 0), ), ), ); }, ); }, child: AbsorbPointer( child: CustomTextFormField( controller: _timeController1, prefixIcon: Icon(Icons.timer), hintText: enterCustomDuration, decoration: InputDecoration( border: OutlineInputBorder(), ), margin: EdgeInsets.fromLTRB(5, 12, 10, 0), validator: (value) { if (value != null && !isTime(value)) { return "Inserisci un'ora valida (HH:MM:SS)"; } return null; }, ), ), ), CustomTextFormField( controller: _titleController, prefixIcon: Icon(Icons.title), hintText: insertTitle, margin: EdgeInsets.fromLTRB(5, 12, 10, 0), validator: (value) { if (value != null && !isText(value)) { return "Inserisci un testo valido"''; } return null; }, ), CustomTextFormField( controller: _presenterNameController, prefixIcon: Icon(Icons.person), hintText: insertPresenterName, margin: EdgeInsets.fromLTRB(5, 12, 10, 0), validator: (value) { if (value != null && !isText(value)) { return "Inserisci un testo valido"; } return null; }, ), ], ), ), ), if (!isRecording || showStartRecordingButton) CustomButton( text: startRecording, margin: EdgeInsets.fromLTRB(50, 30, 50, 10), variant: ButtonVariant.OutlineBlack9003f, padding: ButtonPadding.PaddingAll9, onTap: () async { if (_formKey.currentState!.validate()) { setState(() { isLoading = true; }); // Remove focus from any focused text field FocusScope.of(context).unfocus(); String token = await loginAndGetToken(); String time = _timeController1.text; String camNumber = _camNumberController.text; Map data = { "title": _titleController.text, "presenter_name": _presenterNameController.text, "language": _languageController.text, }; try { await sendPostRequestStart(token, data, time, camNumber); setState(() { isRecording = true; showStartRecordingButton = false; }); // Start the timer to update the UI after the duration ends List timeParts = time.split(':'); int durationInSeconds = int.parse(timeParts[0]) * 3600 + int.parse(timeParts[1]) * 60 + int.parse(timeParts[2]); _recordingTimer = Timer(Duration(seconds: durationInSeconds), () { setState(() { showStartRecordingButton = true; _resetFormFields(); }); }); } catch (error) { setState(() { isError = true; }); } finally { setState(() { isLoading = false; }); } } else { Get.snackbar( "Errore", "Si prega di compilare tutti i campi richiesti", backgroundColor: Colors.red, colorText: Colors.white, ); } }, ), if (isRecording && !showStartRecordingButton && !isError) Expanded( child: Center( child: Column( mainAxisSize: MainAxisSize.min, // Minimize the column size to its content children: [ Container( padding: EdgeInsets.all(40), // Increase the padding to increase the size of the Container decoration: BoxDecoration( color: Colors.white, // Change this to your preferred color borderRadius: BorderRadius.circular(15), boxShadow: [ BoxShadow( color: Colors.grey.withOpacity(0.5), spreadRadius: 5, blurRadius: 7, offset: Offset(0, 3), // changes position of shadow ), ], ), child: Column( children: [ Text( recordingDetails, style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold, color: Colors.black), ), SizedBox(height: 10), Text( 'Durata: ${_timeController1.text}', style: TextStyle(fontSize: 20, color: Colors.black), ), SizedBox(height: 10), Text( 'Titolo: ${_titleController.text}', style: TextStyle(fontSize: 20, color: Colors.black), ), SizedBox(height: 10), Text( 'Nome del relatore: ${_presenterNameController.text}', style: TextStyle(fontSize: 20, color: Colors.black), ), ], ), ), CustomButton( text: "Interrompi la registrazione", margin: EdgeInsets.fromLTRB(80, 10, 80, 10), variant: ButtonVariant.OutlineBlack9003f, padding: ButtonPadding.PaddingAll15, onTap: () async { String token = await loginAndGetToken(); String camNumber = _camNumberController.text; await sendPostRequestStop(token, camNumber); setState(() { isRecording = false; showStartRecordingButton = true; _resetFormFields(); }); }, ), ], ), ), ), ], ), ), if (isLoading) Positioned.fill( child: Container( color: Colors.black54, child: Center( child: CircularProgressIndicator(), ), ), ), Positioned( bottom: 20, left: 0, right: 0, child: isLoading ? SizedBox() // Hide the button when isLoading is true : CustomButton( text: goToCamera, // prefixIcon: Icon(Icons.camera_alt), margin: EdgeInsets.symmetric(horizontal: 50, vertical: 30), variant: ButtonVariant.OutlineBlack9003f, padding: ButtonPadding.PaddingAll9, onTap: () { Navigator.push( context, MaterialPageRoute(builder: (context) => CameraPage()), ); }, ), ), ], ), ), )); } void _resetFormFields() { _timeController1.clear(); _titleController.clear(); _presenterNameController.clear(); } Future loginAndGetToken() async { var url = Uri.parse(dotenv.env['SERVER_URL']! + '/auth/login'); var data = { 'username': dotenv.env['USERNAME']!, 'password': dotenv.env['PASSWORD']!, }; var body = data.keys.map((key) => "${Uri.encodeComponent(key)}=${Uri.encodeComponent(data[key] ?? '')}").join("&"); HttpClient httpClient = new HttpClient() ..badCertificateCallback = ((X509Certificate cert, String host, int port) => true); var request = await httpClient.postUrl(url) ..headers.contentType = ContentType("application", "x-www-form-urlencoded") ..write(body); var response = await request.close(); var responseBody = await utf8.decodeStream(response); var parsedJson = jsonDecode(responseBody); if (parsedJson['access_token'] != null) { return parsedJson['access_token']; } else { throw Exception('Token not found in response'); } } Future sendPostRequestStart(String token, Map data, String time, String camNumber) async { var url = Uri.parse(dotenv.env['SERVER_URL']! + '/start-recording'); List timeParts = time.split(':'); int durationInSeconds = int.parse(timeParts[0]) * 3600 + int.parse(timeParts[1]) * 60 + int.parse(timeParts[2]); durationInSeconds += 10; var requestData = { "camera_name": int.parse(camNumber), "duration": durationInSeconds, "title": data['title'], "presenter_name": data['presenter_name'], "language": data['language'] }; var jsonData = jsonEncode(requestData); HttpClient httpClient = new HttpClient() ..badCertificateCallback = ((X509Certificate cert, String host, int port) => true); var request = await httpClient.postUrl(url) ..headers.contentType = ContentType.json ..headers.add('Authorization', 'Bearer $token') ..write(jsonData); var response = await request.close(); var responseBody = await utf8.decodeStream(response); var responseJson = jsonDecode(responseBody); var message = responseJson['message']; bool isError = message.startsWith('Error:') || message.startsWith('Errore:'); Get.snackbar( isError ? "Error" : "Message", message, backgroundColor: isError ? Colors.red : Colors.green, colorText: Colors.white, ); setState(() { this.isError = isError; if (isError) { isRecording = false; throw Exception('Failed to start recording'); } }); } Future sendPostRequestStop(String token, String camNumber) async { var url = Uri.parse(dotenv.env['SERVER_URL']! + '/stop-recording'); var requestData = { "camera_name": int.parse(camNumber), }; var jsonData = jsonEncode(requestData); HttpClient httpClient = new HttpClient() ..badCertificateCallback = ((X509Certificate cert, String host, int port) => true); var request = await httpClient.postUrl(url) ..headers.contentType = ContentType.json ..headers.add('Authorization', 'Bearer $token') ..write(jsonData); var response = await request.close(); var responseBody = await utf8.decodeStream(response); var responseJson = jsonDecode(responseBody); var message = responseJson['message']; bool isError = message.startsWith('Error:') || message.startsWith('Errore:'); Get.snackbar( isError ? "Error" : "Message", message, backgroundColor: isError ? Colors.red : Colors.green, colorText: Colors.white, ); setState(() { this.isError = isError; if (!isError) { isRecording = false; _timeController1.clear(); _titleController.clear(); _presenterNameController.clear(); } }); } bool isTime(String time) { var timeParts = time.split(':'); if (timeParts.length != 3) { return false; } var hours = int.tryParse(timeParts[0]); var minutes = int.tryParse(timeParts[1]); var seconds = int.tryParse(timeParts[2]); return hours != null && hours >= 0 && hours < 24 && minutes != null && minutes >= 0 && minutes < 60 && seconds != null && seconds >= 0 && seconds < 60; } bool isText(String? value) { return value != null && value.isNotEmpty; } }