Vous ne pouvez pas sélectionner plus de 25 sujets Les noms de sujets doivent commencer par une lettre ou un nombre, peuvent contenir des tirets ('-') et peuvent comporter jusqu'à 35 caractères.
 
 
 
 

533 lignes
22 KiB

  1. import 'dart:async';
  2. import 'dart:convert';
  3. import 'dart:io';
  4. import 'dart:ui' as ui;
  5. import 'package:flutter/cupertino.dart';
  6. import 'package:flutter/material.dart';
  7. import 'package:get/get.dart';
  8. import 'package:michele_s_application8/widgets/custom_button.dart';
  9. import 'package:michele_s_application8/widgets/custom_text_form_field.dart';
  10. import 'package:http/http.dart' as http;
  11. import 'package:michele_s_application8/presentation/camera_page/camera_page.dart';
  12. import 'package:flutter_dotenv/flutter_dotenv.dart';
  13. import 'package:shared_preferences/shared_preferences.dart';
  14. class LoginScreen extends StatefulWidget {
  15. @override
  16. _LoginScreenState createState() => _LoginScreenState();
  17. }
  18. class _LoginScreenState extends State<LoginScreen> {
  19. GlobalKey<FormState> _formKey = GlobalKey<FormState>();
  20. final TextEditingController _timeController1 = TextEditingController();
  21. final TextEditingController _titleController = TextEditingController();
  22. final TextEditingController _presenterNameController = TextEditingController();
  23. final TextEditingController _camNumberController = TextEditingController(text: '1');
  24. late TextEditingController _languageController;
  25. bool isRecording = false;
  26. bool isLoading = false;
  27. bool isError = false;
  28. bool showStartRecordingButton = true;
  29. Timer? _recordingTimer;
  30. List<String> _defaultTimes = [];
  31. String mainTitle = '';
  32. String selectDuration = '';
  33. String enterCustomDuration = '';
  34. String insertTitle = '';
  35. String insertPresenterName = '';
  36. String startRecording = '';
  37. String recordingDetails = '';
  38. String goToCamera = '';
  39. @override
  40. void initState() {
  41. super.initState();
  42. String deviceLanguage = ui.window.locale.languageCode;
  43. String language = (deviceLanguage == 'en') ? 'EN' : (deviceLanguage == 'it') ? 'IT' : 'IT';
  44. _languageController = TextEditingController(text: language);
  45. fetchStringsFromServer();
  46. }
  47. Future<void> fetchStringsFromServer() async {
  48. try {
  49. String token = await loginAndGetToken(); // Get the token first
  50. HttpClient httpClient = new HttpClient()
  51. ..badCertificateCallback =
  52. ((X509Certificate cert, String host, int port) => true);
  53. var request = await httpClient.getUrl(Uri.parse(dotenv.env['SERVER_URL']! + '/get-strings'))
  54. ..headers.add('Authorization', 'Bearer $token');
  55. var response = await request.close();
  56. var responseBody = await response.transform(utf8.decoder).join();
  57. if (response.statusCode == 200) {
  58. final data = jsonDecode(responseBody);
  59. setState(() {
  60. _defaultTimes = List<String>.from(data['durations']);
  61. selectDuration = data['select_duration'];
  62. enterCustomDuration = data['enter_custom_duration'];
  63. insertTitle = data['insert_title'];
  64. insertPresenterName = data['insert_presenter_name'];
  65. startRecording = data['start_recording'];
  66. recordingDetails = data['recording_details'];
  67. goToCamera = data['go_to_camera'];
  68. mainTitle = data['main_title'];
  69. });
  70. } else {
  71. print('Failed to load strings with status code: ${response.statusCode}');
  72. throw Exception('Failed to load strings');
  73. }
  74. } catch (e) {
  75. print('Error fetching strings: $e'); // Debug: Print error message
  76. setState(() {
  77. isError = true;
  78. _defaultTimes = ["00:05:00", "01:00:00", "01:30:00", "02:00:00"];
  79. mainTitle = 'CookingLab';
  80. selectDuration = 'seleziona Durata';
  81. enterCustomDuration = 'Oppure inserisci una durata personalizzata';
  82. insertTitle = 'Inserisci il titolo';
  83. insertPresenterName = 'Inserisci il nome del relatore';
  84. startRecording = 'Inizia a Registrare';
  85. recordingDetails = 'Dettagli di registrazione';
  86. goToCamera = 'Vai a Fotocamera';
  87. });
  88. }
  89. }
  90. @override
  91. void dispose() {
  92. _recordingTimer?.cancel();
  93. super.dispose();
  94. }
  95. @override
  96. Widget build(BuildContext context) {
  97. return SafeArea(
  98. child: Scaffold(
  99. resizeToAvoidBottomInset: false,
  100. body: Container(
  101. decoration: BoxDecoration(
  102. gradient: LinearGradient(
  103. begin: Alignment.topLeft,
  104. end: Alignment.bottomRight,
  105. colors: [Colors.white, Colors.purple],
  106. ),
  107. ),
  108. child: Stack(
  109. children: [
  110. Center( // Use Center to vertically and horizontally center the form
  111. child: Column(
  112. mainAxisSize: MainAxisSize.min, // Minimize the column size to its content
  113. children: [
  114. if (!isRecording || showStartRecordingButton)
  115. Text(
  116. mainTitle,
  117. style: TextStyle(fontSize: 32, fontWeight: FontWeight.bold),
  118. ),
  119. if (!isRecording || showStartRecordingButton)
  120. Form(
  121. key: _formKey,
  122. child: Container(
  123. width: double.maxFinite,
  124. padding: EdgeInsets.symmetric(horizontal: 23),
  125. child: Column(
  126. crossAxisAlignment: CrossAxisAlignment.start,
  127. children: [
  128. DropdownButtonFormField<String>(
  129. value: null,
  130. hint: Text(selectDuration),
  131. icon: Icon(Icons.timer),
  132. decoration: InputDecoration(
  133. border: OutlineInputBorder(),
  134. ),
  135. items: _defaultTimes.map((String value) {
  136. return DropdownMenuItem<String>(
  137. value: value,
  138. child: Text(value),
  139. );
  140. }).toList(),
  141. onChanged: (newValue) {
  142. setState(() {
  143. _timeController1.text = newValue!;
  144. });
  145. },
  146. validator: (value) {
  147. if (_timeController1.text.isEmpty || !isTime(_timeController1.text)) {
  148. return "Inserisci un'ora valida (HH:MM:SS)";
  149. }
  150. return null;
  151. },
  152. ),
  153. SizedBox(height: 12),
  154. GestureDetector(
  155. onTap: () async {
  156. await showDialog(
  157. context: context,
  158. builder: (BuildContext context) {
  159. return Dialog(
  160. child: Container(
  161. height: MediaQuery.of(context).copyWith().size.height / 3,
  162. child: CupertinoTimerPicker(
  163. mode: CupertinoTimerPickerMode.hms,
  164. onTimerDurationChanged: (Duration duration) {
  165. _timeController1.text = "${duration.inHours.toString().padLeft(2, '0')}:${duration.inMinutes.remainder(60).toString().padLeft(2, '0')}:${duration.inSeconds.remainder(60).toString().padLeft(2, '0')}";
  166. },
  167. initialTimerDuration: Duration(hours: 0, minutes: 0, seconds: 0),
  168. ),
  169. ),
  170. );
  171. },
  172. );
  173. },
  174. child: AbsorbPointer(
  175. child: CustomTextFormField(
  176. controller: _timeController1,
  177. prefixIcon: Icon(Icons.timer),
  178. hintText: enterCustomDuration,
  179. decoration: InputDecoration(
  180. border: OutlineInputBorder(),
  181. ),
  182. margin: EdgeInsets.fromLTRB(5, 12, 10, 0),
  183. validator: (value) {
  184. if (value != null && !isTime(value)) {
  185. return "Inserisci un'ora valida (HH:MM:SS)";
  186. }
  187. return null;
  188. },
  189. ),
  190. ),
  191. ),
  192. CustomTextFormField(
  193. controller: _titleController,
  194. prefixIcon: Icon(Icons.title),
  195. hintText: insertTitle,
  196. margin: EdgeInsets.fromLTRB(5, 12, 10, 0),
  197. validator: (value) {
  198. if (value != null && !isText(value)) {
  199. return "Inserisci un testo valido"'';
  200. }
  201. return null;
  202. },
  203. ),
  204. CustomTextFormField(
  205. controller: _presenterNameController,
  206. prefixIcon: Icon(Icons.person),
  207. hintText: insertPresenterName,
  208. margin: EdgeInsets.fromLTRB(5, 12, 10, 0),
  209. validator: (value) {
  210. if (value != null && !isText(value)) {
  211. return "Inserisci un testo valido";
  212. }
  213. return null;
  214. },
  215. ),
  216. ],
  217. ),
  218. ),
  219. ),
  220. if (!isRecording || showStartRecordingButton)
  221. CustomButton(
  222. text: startRecording,
  223. margin: EdgeInsets.fromLTRB(50, 30, 50, 10),
  224. variant: ButtonVariant.OutlineBlack9003f,
  225. padding: ButtonPadding.PaddingAll9,
  226. onTap: () async {
  227. if (_formKey.currentState!.validate()) {
  228. setState(() {
  229. isLoading = true;
  230. });
  231. // Remove focus from any focused text field
  232. FocusScope.of(context).unfocus();
  233. String token = await loginAndGetToken();
  234. String time = _timeController1.text;
  235. String camNumber = _camNumberController.text;
  236. Map<String, dynamic> data = {
  237. "title": _titleController.text,
  238. "presenter_name": _presenterNameController.text,
  239. "language": _languageController.text,
  240. };
  241. try {
  242. await sendPostRequestStart(token, data, time, camNumber);
  243. setState(() {
  244. isRecording = true;
  245. showStartRecordingButton = false;
  246. });
  247. // Start the timer to update the UI after the duration ends
  248. List<String> timeParts = time.split(':');
  249. int durationInSeconds = int.parse(timeParts[0]) * 3600 + int.parse(timeParts[1]) * 60 + int.parse(timeParts[2]);
  250. _recordingTimer = Timer(Duration(seconds: durationInSeconds), () {
  251. setState(() {
  252. showStartRecordingButton = true;
  253. _resetFormFields();
  254. });
  255. });
  256. } catch (error) {
  257. setState(() {
  258. isError = true;
  259. });
  260. } finally {
  261. setState(() {
  262. isLoading = false;
  263. });
  264. }
  265. } else {
  266. Get.snackbar(
  267. "Errore",
  268. "Si prega di compilare tutti i campi richiesti",
  269. backgroundColor: Colors.red,
  270. colorText: Colors.white,
  271. );
  272. }
  273. },
  274. ),
  275. if (isRecording && !showStartRecordingButton && !isError)
  276. Expanded(
  277. child: Center(
  278. child: Column(
  279. mainAxisSize: MainAxisSize.min, // Minimize the column size to its content
  280. children: [
  281. Container(
  282. padding: EdgeInsets.all(40), // Increase the padding to increase the size of the Container
  283. decoration: BoxDecoration(
  284. color: Colors.white, // Change this to your preferred color
  285. borderRadius: BorderRadius.circular(15),
  286. boxShadow: [
  287. BoxShadow(
  288. color: Colors.grey.withOpacity(0.5),
  289. spreadRadius: 5,
  290. blurRadius: 7,
  291. offset: Offset(0, 3), // changes position of shadow
  292. ),
  293. ],
  294. ),
  295. child: Column(
  296. children: [
  297. Text(
  298. recordingDetails,
  299. style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold, color: Colors.black),
  300. ),
  301. SizedBox(height: 10),
  302. Text(
  303. 'Durata: ${_timeController1.text}',
  304. style: TextStyle(fontSize: 20, color: Colors.black),
  305. ),
  306. SizedBox(height: 10),
  307. Text(
  308. 'Titolo: ${_titleController.text}',
  309. style: TextStyle(fontSize: 20, color: Colors.black),
  310. ),
  311. SizedBox(height: 10),
  312. Text(
  313. 'Nome del relatore: ${_presenterNameController.text}',
  314. style: TextStyle(fontSize: 20, color: Colors.black),
  315. ),
  316. ],
  317. ),
  318. ),
  319. CustomButton(
  320. text: "Interrompi la registrazione",
  321. margin: EdgeInsets.fromLTRB(80, 10, 80, 10),
  322. variant: ButtonVariant.OutlineBlack9003f,
  323. padding: ButtonPadding.PaddingAll15,
  324. onTap: () async {
  325. String token = await loginAndGetToken();
  326. String camNumber = _camNumberController.text;
  327. await sendPostRequestStop(token, camNumber);
  328. setState(() {
  329. isRecording = false;
  330. showStartRecordingButton = true;
  331. _resetFormFields();
  332. });
  333. },
  334. ),
  335. ],
  336. ),
  337. ),
  338. ),
  339. ],
  340. ),
  341. ),
  342. if (isLoading)
  343. Positioned.fill(
  344. child: Container(
  345. color: Colors.black54,
  346. child: Center(
  347. child: CircularProgressIndicator(),
  348. ),
  349. ),
  350. ),
  351. Positioned(
  352. bottom: 20,
  353. left: 0,
  354. right: 0,
  355. child: isLoading
  356. ? SizedBox() // Hide the button when isLoading is true
  357. : CustomButton(
  358. text: goToCamera,
  359. // prefixIcon: Icon(Icons.camera_alt),
  360. margin: EdgeInsets.symmetric(horizontal: 50, vertical: 30),
  361. variant: ButtonVariant.OutlineBlack9003f,
  362. padding: ButtonPadding.PaddingAll9,
  363. onTap: () {
  364. Navigator.push(
  365. context,
  366. MaterialPageRoute(builder: (context) => CameraPage()),
  367. );
  368. },
  369. ),
  370. ),
  371. ],
  372. ),
  373. ),
  374. ));
  375. }
  376. void _resetFormFields() {
  377. _timeController1.clear();
  378. _titleController.clear();
  379. _presenterNameController.clear();
  380. }
  381. Future<String> loginAndGetToken() async {
  382. var url = Uri.parse(dotenv.env['SERVER_URL']! + '/auth/login');
  383. var data = {
  384. 'username': dotenv.env['USERNAME']!,
  385. 'password': dotenv.env['PASSWORD']!,
  386. };
  387. var body = data.keys.map((key) => "${Uri.encodeComponent(key)}=${Uri.encodeComponent(data[key] ?? '')}").join("&");
  388. HttpClient httpClient = new HttpClient()
  389. ..badCertificateCallback =
  390. ((X509Certificate cert, String host, int port) => true);
  391. var request = await httpClient.postUrl(url)
  392. ..headers.contentType = ContentType("application", "x-www-form-urlencoded")
  393. ..write(body);
  394. var response = await request.close();
  395. var responseBody = await utf8.decodeStream(response);
  396. var parsedJson = jsonDecode(responseBody);
  397. if (parsedJson['access_token'] != null) {
  398. return parsedJson['access_token'];
  399. } else {
  400. throw Exception('Token not found in response');
  401. }
  402. }
  403. Future<void> sendPostRequestStart(String token, Map<String, dynamic> data, String time, String camNumber) async {
  404. var url = Uri.parse(dotenv.env['SERVER_URL']! + '/start-recording');
  405. List<String> timeParts = time.split(':');
  406. int durationInSeconds = int.parse(timeParts[0]) * 3600 + int.parse(timeParts[1]) * 60 + int.parse(timeParts[2]);
  407. durationInSeconds += 10;
  408. var requestData = {
  409. "camera_name": int.parse(camNumber),
  410. "duration": durationInSeconds,
  411. "title": data['title'],
  412. "presenter_name": data['presenter_name'],
  413. "language": data['language']
  414. };
  415. var jsonData = jsonEncode(requestData);
  416. HttpClient httpClient = new HttpClient()
  417. ..badCertificateCallback =
  418. ((X509Certificate cert, String host, int port) => true);
  419. var request = await httpClient.postUrl(url)
  420. ..headers.contentType = ContentType.json
  421. ..headers.add('Authorization', 'Bearer $token')
  422. ..write(jsonData);
  423. var response = await request.close();
  424. var responseBody = await utf8.decodeStream(response);
  425. var responseJson = jsonDecode(responseBody);
  426. var message = responseJson['message'];
  427. bool isError = message.startsWith('Error:') || message.startsWith('Errore:');
  428. Get.snackbar(
  429. isError ? "Error" : "Message",
  430. message,
  431. backgroundColor: isError ? Colors.red : Colors.green,
  432. colorText: Colors.white,
  433. );
  434. setState(() {
  435. this.isError = isError;
  436. if (isError) {
  437. isRecording = false;
  438. throw Exception('Failed to start recording');
  439. }
  440. });
  441. }
  442. Future<void> sendPostRequestStop(String token, String camNumber) async {
  443. var url = Uri.parse(dotenv.env['SERVER_URL']! + '/stop-recording');
  444. var requestData = {
  445. "camera_name": int.parse(camNumber),
  446. };
  447. var jsonData = jsonEncode(requestData);
  448. HttpClient httpClient = new HttpClient()
  449. ..badCertificateCallback =
  450. ((X509Certificate cert, String host, int port) => true);
  451. var request = await httpClient.postUrl(url)
  452. ..headers.contentType = ContentType.json
  453. ..headers.add('Authorization', 'Bearer $token')
  454. ..write(jsonData);
  455. var response = await request.close();
  456. var responseBody = await utf8.decodeStream(response);
  457. var responseJson = jsonDecode(responseBody);
  458. var message = responseJson['message'];
  459. bool isError = message.startsWith('Error:') || message.startsWith('Errore:');
  460. Get.snackbar(
  461. isError ? "Error" : "Message",
  462. message,
  463. backgroundColor: isError ? Colors.red : Colors.green,
  464. colorText: Colors.white,
  465. );
  466. setState(() {
  467. this.isError = isError;
  468. if (!isError) {
  469. isRecording = false;
  470. _timeController1.clear();
  471. _titleController.clear();
  472. _presenterNameController.clear();
  473. }
  474. });
  475. }
  476. bool isTime(String time) {
  477. var timeParts = time.split(':');
  478. if (timeParts.length != 3) {
  479. return false;
  480. }
  481. var hours = int.tryParse(timeParts[0]);
  482. var minutes = int.tryParse(timeParts[1]);
  483. var seconds = int.tryParse(timeParts[2]);
  484. return hours != null && hours >= 0 && hours < 24 &&
  485. minutes != null && minutes >= 0 && minutes < 60 &&
  486. seconds != null && seconds >= 0 && seconds < 60;
  487. }
  488. bool isText(String? value) {
  489. return value != null && value.isNotEmpty;
  490. }
  491. }