diff --git a/assets/app_logo_circle_broken.png b/assets/app_logo_circle_broken.png new file mode 100644 index 0000000..52d12fa Binary files /dev/null and b/assets/app_logo_circle_broken.png differ diff --git a/assets/images/drinking.png b/assets/images/drinking.png new file mode 100644 index 0000000..7e34aee Binary files /dev/null and b/assets/images/drinking.png differ diff --git a/assets/images/eating.png b/assets/images/eating.png new file mode 100644 index 0000000..fceee18 Binary files /dev/null and b/assets/images/eating.png differ diff --git a/assets/images/reading.png b/assets/images/reading.png new file mode 100644 index 0000000..d21bec4 Binary files /dev/null and b/assets/images/reading.png differ diff --git a/assets/images/sleeping.png b/assets/images/sleeping.png new file mode 100644 index 0000000..09d0d07 Binary files /dev/null and b/assets/images/sleeping.png differ diff --git a/assets/images/sport.png b/assets/images/sport.png new file mode 100644 index 0000000..cd1e84e Binary files /dev/null and b/assets/images/sport.png differ diff --git a/assets/images/walking.png b/assets/images/walking.png new file mode 100644 index 0000000..0b1775d Binary files /dev/null and b/assets/images/walking.png differ diff --git a/assets/images/working.png b/assets/images/working.png new file mode 100644 index 0000000..2967fae Binary files /dev/null and b/assets/images/working.png differ diff --git a/assets/sounds/finish_bigdsc.mp3 b/assets/sounds/finish_bigdsc.mp3 new file mode 100644 index 0000000..fb235e1 Binary files /dev/null and b/assets/sounds/finish_bigdsc.mp3 differ diff --git a/assets/sounds/light_pop.mp3 b/assets/sounds/light_pop.mp3 deleted file mode 100644 index e2b9342..0000000 Binary files a/assets/sounds/light_pop.mp3 and /dev/null differ diff --git a/assets/sounds/light_pop_bigdsc.mp3 b/assets/sounds/light_pop_bigdsc.mp3 new file mode 100644 index 0000000..d4437db Binary files /dev/null and b/assets/sounds/light_pop_bigdsc.mp3 differ diff --git a/assets/translations/en.json b/assets/translations/en.json index 74ecdfe..41021e4 100644 --- a/assets/translations/en.json +++ b/assets/translations/en.json @@ -18,7 +18,7 @@ "open_source_txt": "Open Source", "about_balance_txt": "About Balance", "version_txt": "Version", - "build_txt": "alpha.", + "build_txt": "beta.", "made_with_heart_txt": "Credits", "easter_egg_txt": "Keep clicking! The new release will come out faster!", "empty_txt": "Nothing to show here!", @@ -74,7 +74,7 @@ "leave_dialog_title": "Are you sure you want to leave?", "leave_dialog_msg": "If you leave the test will fail", "never_show_again": "Never show again", - "tutorial_msg": "Before performing a test select eyes open or eyes closed.\nPlease maintain a straight posture and hold the phone with two hands in a vertical position at the navel level.\nThe test will last about 30 seconds, at the end of which you will feel a vibration.", + "tutorial_msg": "While performing a test, keep a straight posture and hold the phone with two hands in a vertical position at the navel level.\nThe correct positioning of the smartphone will be suggested by a beep! The analysis will start with a vibration and will last approximately 30 seconds, after which you will hear an additional beep. Remember to keep your eyes open or closed as you stated throughout the measurement!", "your_personal_info_txt": "My personal data", "general_title": "GENERAL", "health_title": "HEALTH", @@ -148,6 +148,38 @@ "about_balance_title": "About Balance", "about_balance_msg": "Balance is an effort of Computer Science researchers at the University of Urbino with the aim of providing a tool to measure your posture in a few seconds, with your smartphone.", "cool_btn": "Cool!", + "back_home_btn": "Back to Home Screen", + "report_title": "Support", + "report_issue_title": "Report an Issue", + "report_title_txt": "Report an Issue", + "report_description_txt": "Please describe...", + "report_snack_true_txt": "Report sent successfully!", + "report_snack_false_txt": "You should write something first!", + "report_send_txt": "Send Report", + "credits_description_txt": "Balance is a smartphone application developed by the researchers of the University of Urbino to measure your stability. It is designed to be easy to use and quick enough to give stabilometric indices in a few seconds. It is part of the iPhD funded by Regione Marche and led by Professor E. Lattanzi. The PhD is a joint initiative of the University of Urbino, Politecnica delle Marche, TU Wien and the company Digit Srl. All trademarks mentioned belong to their respective owners.", + "credits_authors_txt": "Authors", + "credits_authors_bigini_txt": "iPh.D Student @ University of Urbino", + "credits_authors_freschi_txt": "Researcher @ University of Urbino", + "credits_authors_lattanzi_txt": "Associate Professor @ University of Urbino", + "credits_developers_txt": "Developers", + "credits_developers_bigini_txt": "Software Developer @ University of Urbino", + "credits_developers_difrancesco_txt": "Development Support @ DIGIT Srl", + "credits_developers_calisti_txt": "Original Design & Code @ University of Urbino", + "credits_collaborators_txt": "Collaborators", + "credits_collaborators_klopfenstein_txt": "Infrastructure Provider @ DIGIT Srl", + "credits_collaborators_delpriori_txt": "Infrastructure Provider @ DIGIT Srl", + "credits_collaborators_bogliolo_txt": "Data Processor @ DIGIT Srl", + "credits_foundation_title": "Foundations", + "credits_sponsors_partners_txt": "Partners and Sponsors", + "test_measurement_ok_title": "Measurement performed correctly", + "test_measurement_ok_txt": "Congratulations! When you take a measurement we check your parameters. Some of these provide an indication of correct execution and it seems that everything went well!", + "test_measurement_wrong_title": "Suspicious Measurement", + "test_measurement_wrong_txt": "This measurement shows erratic results. If you did not execute the test correctly, try it again. If the problem persists, contact the support.", + "test_backend_ok_title": "Measurement successfully sent to the server", + "test_backend_ok_txt": "Your measurement has been successfully recorded. You are actively contributing to Balance's research and development!", + "test_backend_wrong_title": "Your measurement was not saved", + "test_backend_wrong_txt": "What a pity! The measurement did not reach our servers (maybe they were resting, never sleep a wink). By submitting the results you will help our researchers better understand stability. Please connect to the internet next time.", + "intro_backend_snack_txt": "Unable to retrieve the unique Token. Please activate your Internet connection and try again. If the problem persists, try again in a few hours.", "privacy_title": "Balance Terms and Conditions and Privacy Policy", "privacy_finality_title": "Purposes and Methods of Processing", "privacy_finality_txt": "The processing of your personal data through the Balance application is carried out for the realization of the scientific purposes of the \"Smartphone-based Postural Stability Monitoring System for Falls Prevention in the Elderly\" project. The Project was drawn up in accordance with the methodological standards of the disciplinary sector concerned and is filed with the Department of Pure and Applied Sciences of the University of Urbino Carlo Bo, where it will be kept for five years from the scheduled conclusion of the research itself. Your personal data will be processed only to the extent that they are indispensable in relation to the objective of the project, in compliance with the provisions of current legislation on the protection of personal data and in accordance with the provisions of the general authorizations of the Guarantor Authority for the protection of personal data. Your data will be processed exclusively by the Data Controller, by the Scientific Manager and/or by authorized subjects in the context of the realization of the Project, with automated and non-automated tools, exclusively to allow the carrying out of the research in question and all the related operations and related activities, including administrative ones, conducted by the University of Urbino in collaboration with Digit srl. The data will be processed electronically by adopting pseudonymisation: the user's identity will never be requested but a unique identifier will be associated with each installation that will allow the data provided by the same user to be correlated over time. Furthermore, although the experimentation does not fall into medical devices, the use of Balance will constitute a service for users through personal monitoring of their progress.", diff --git a/assets/translations/it.json b/assets/translations/it.json index 325423a..1005dc4 100644 --- a/assets/translations/it.json +++ b/assets/translations/it.json @@ -18,7 +18,7 @@ "open_source_txt": "Open Source", "about_balance_txt": "Riguardo a Balance", "version_txt": "Versione", - "build_txt": "alpha.", + "build_txt": "beta.", "made_with_heart_txt": "Crediti", "easter_egg_txt": "Continua a cliccare! La nuova release uscirà più velocemente!", "empty_txt": "Non c'è nulla da vedere qui!", @@ -74,7 +74,7 @@ "leave_dialog_title": "Sei sicuro di voler uscire?", "leave_dialog_msg": "Se esci il test fallirà", "never_show_again": "Non mostrare più", - "tutorial_msg": "Prima di eseguire un test seleziona occhi aperti oppure occhi chiusi. \nPer favore mantieni una postura dritta e tieni il telefono con due mani in posizione verticale all'altezza dell'ombelico. \nIl test durerà circa 30 secondi, al termine dei quali sentirai una vibrazione.", + "tutorial_msg": "Durante l'esecuzione mantieni una postura dritta e tieni il telefono con due mani in posizione verticale all'altezza dell'ombelico.\nIl corretto posizionamento del telefono ti verrà suggerito da un segnale acustico! L'analisi inizierà con una vibrazione e durerà circa 30 secondi, al termine dei quali sentirai un'ulteriore segnale acustico. Ricorda di tenere gli occhi aperti o chiusi come hai dichiarato per tutto il tempo della misurazione!", "your_personal_info_txt": "Le mie informazioni personali", "general_title": "GENERALE", "health_title": "SALUTE", @@ -148,6 +148,38 @@ "about_balance_title": "Riguardo a Balance", "about_balance_msg": "Balance nasce dallo sforzo dei ricercatori di Computer Science dell'Università di Urbino con l'obbiettivo di fornire uno strumento per misurare la propria postura in pochi secondi, con il proprio smartphone.", "cool_btn": "Figo!", + "back_home_btn": "Torna alla Schermata Principale", + "report_title": "Supporto", + "report_issue_title": "Segnala un Problema", + "report_title_txt": "Segnala Un Malfunzionamento", + "report_description_txt": "Descrivici il problema...", + "report_snack_true_txt": "Segnalazione inviata con successo!", + "report_snack_false_txt": "Prima dovresti scriverci qualcosa!", + "report_send_txt": "Invia Segnalazione", + "credits_description_txt": "Balance è un'applicazione per smartphone sviluppata dai ricercatori dell'Università degli Studi di Urbino per misurare la tua stabilità. È progettato per essere facile da usare e abbastanza veloce da fornire indici stabilometrici in pochi secondi. Fa parte del percorso di Dottorato Innovativo finanziato dalla Regione Marche e guidato dal Professor E. Lattanzi. Il Dottorato di Ricerca è un'iniziativa congiunta dell'Università degli Studi di Urbino, Politecnica delle Marche, Università Tecnica di Vienna e la società Digit Srl. Tutti i marchi citati appartengono ai rispettivi proprietari.", + "credits_authors_txt": "Autori", + "credits_authors_bigini_txt": "Studente Dottorato Innovativo @ Università degli Studi di Urbino", + "credits_authors_freschi_txt": "Ricercatore @ Università degli Studi di Urbino", + "credits_authors_lattanzi_txt": "Professore Associato @ Università degli Studi di Urbino", + "credits_developers_txt": "Sviluppatori", + "credits_developers_bigini_txt": "Sviluppatore Software @ Università degli Studi di Urbino", + "credits_developers_difrancesco_txt": "Supporto allo Sviluppo @ DIGIT Srl", + "credits_developers_calisti_txt": "Design & Codice Originale @ Università degli Studi di Urbino", + "credits_collaborators_txt": "Collaboratori", + "credits_collaborators_klopfenstein_txt": "Fornitore Infrastrutture @ DIGIT Srl", + "credits_collaborators_delpriori_txt": "Fornitore Infrastrutture @ DIGIT Srl", + "credits_collaborators_bogliolo_txt": "Titolare del Trattamento Dati @ DIGIT Srl", + "credits_foundation_title": "Fondamenti", + "credits_sponsors_partners_txt": "Sponsor e Partner", + "test_measurement_ok_title": "Misurazione eseguita correttamente", + "test_measurement_ok_txt": "Congratulazioni! Quando esegui una misurazione verifichiamo i tuoi parametri. Alcuni di questi forniscono un'indicazione riguardante la corretta esecuzione e sembra sia andato tutto bene!", + "test_measurement_wrong_title": "Misurazione Sospetta", + "test_measurement_wrong_txt": "Questa misurazione presenta dei risultati irregolari. Se non hai eseguito il test correttamente, effettualo nuovamente. Se il problema persiste, contatta i ricercatori.", + "test_backend_ok_title": "Misurazione inviata correttamente al server", + "test_backend_ok_txt": "La tua misurazione é stata registrata correttamente. Stai contribuendo attivamente alla ricerca e sviluppo di Balance!", + "test_backend_wrong_title": "La tua misurazione non é stata salvata", + "test_backend_wrong_txt": "Che peccato! La misurazione non ha raggiunto i nostri server (forse si stavano riposando, non chiudono mai occhio). Inviando i risultati aiuterai i nostri ricercatori a comprendere meglio la stabilità. Per favore, attiva la connessione alla rete internet la prossima volta.", + "intro_backend_snack_txt": "Impossibile recuperare il Token univoco. Attivare la connessione Internet e riprovare. Se il problema persiste, riprovare fra qualche ora.", "privacy_title": "Termini e Condizioni e Informativa sulla Privacy di Balance", "privacy_finality_title": "Finalità e Modalità del Trattamento", "privacy_finality_txt": "Il trattamento dei Suoi dati personali attraverso l'applicazione Balance è effettuato per la realizzazione delle finalità scientifiche del Progetto \"Sistema di Monitoraggio della Stabilità Posturale basato su Smartphone\". Il Progetto è stato redatto conformemente agli standard metodologici del settore disciplinare interessato ed è depositato presso il Dipartimento di Scienze Pure e Applicate dell’Università degli Studi di Urbino Carlo Bo, ove verrà conservato per cinque anni dalla conclusione programmata della ricerca stessa. I suoi dati personali saranno trattati soltanto nella misura in cui siano indispensabili in relazione all'obiettivo del progetto, nel rispetto di quanto previsto dalla normativa vigente in materia di protezione dei dati personali e conformemente alle disposizioni di cui alle autorizzazioni generali dell’Autorità Garante per la protezione dei dati personali.I Suoi dati saranno trattati esclusivamente dal Titolare, dal Responsabile scientifico e/o da soggetti autorizzati nell’ambito della realizzazione del Progetto, con strumenti automatizzati e non, esclusivamente per consentire lo svolgimento della ricerca in parola e di tutte le relative operazioni ed attività connesse, comprese quelle amministrative, condotte dal’Università di Urbino in collaborazione con Digit srl. I dati saranno trattati mediante strumenti elettronici adottando la pseudonimizzazione: l’identità dell’utente non verrà mai richiesta ma ad ogni installazione verrà associato un identificativo univoco che consentirà di correlare nel tempo i dati forniti dallo stesso utente. Inoltre, sebbene la sperimentazione non ricada nei dispositivi di tipo medico, l’utilizzo di Balance costituirà un servizio per i soggetti utilizzatori attraverso un monitoraggio personale dei propri progressi.", diff --git a/ios/Flutter/.last_build_id b/ios/Flutter/.last_build_id index aff6e57..4efc140 100644 --- a/ios/Flutter/.last_build_id +++ b/ios/Flutter/.last_build_id @@ -1 +1 @@ -bdd717abda10237c2fa465b7a097dac7 \ No newline at end of file +f4a445f069a0b2de2c728ed6b4490eda \ No newline at end of file diff --git a/ios/Podfile.lock b/ios/Podfile.lock index eba08ca..b89e4d2 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -22,7 +22,7 @@ PODS: - sqflite (0.0.2): - Flutter - FMDB (>= 2.7.5) - - vibration (1.7.1): + - vibration (1.7.3): - Flutter - wakelock (0.0.1): - Flutter @@ -83,7 +83,7 @@ SPEC CHECKSUMS: sensors: 84eb7a30e47a649e4172b71d6e81be614c280336 shared_preferences: af6bfa751691cdc24be3045c43ec037377ada40d sqflite: 6d358c025f5b867b29ed92fc697fd34924e11904 - vibration: 4f4ded27b9d0e21e4110ca43978f057412d28c44 + vibration: b5a33e764c3f609a975b9dca73dce20fdde627dc wakelock: bfc7955c418d0db797614075aabbc58a39ab5107 PODFILE CHECKSUM: aafe91acc616949ddb318b77800a7f51bffa2a4c diff --git a/lib/bloc/main/home/countdown_bloc_impl.dart b/lib/bloc/main/home/countdown_bloc_impl.dart index 493b1fb..f12abd8 100644 --- a/lib/bloc/main/home/countdown_bloc_impl.dart +++ b/lib/bloc/main/home/countdown_bloc_impl.dart @@ -46,7 +46,7 @@ class CountdownBloc extends Bloc { print("CountdownBloc.mapEventToState: startPreMeasure"); _isCountdownCancelled = false; _countdownTimer = CountdownTimer( - Duration(milliseconds: 6000), + Duration(milliseconds: 2000), Duration(milliseconds: 1000) )..listen((event) { /*No-Op*/ }, onDone: () { diff --git a/lib/floor/test_database_view.dart b/lib/floor/test_database_view.dart index 97a8490..1a05666 100644 --- a/lib/floor/test_database_view.dart +++ b/lib/floor/test_database_view.dart @@ -14,7 +14,7 @@ import 'package:floor/floor.dart'; /// /// See also: /// * [Measurement] -@DatabaseView("SELECT id, creation_date, eyes_open, invalid FROM measurements", viewName: "tests") +@DatabaseView("SELECT id, creation_date, eyes_open, invalid, sent FROM measurements", viewName: "tests") class Test { @ColumnInfo(name: "id") final int id; @@ -24,15 +24,18 @@ class Test { final bool eyesOpen; @ColumnInfo(name: "invalid") final bool invalid; + @ColumnInfo(name: "sent") + final bool sent; - Test({this.id, this.creationDate, this.eyesOpen, this.invalid}); + Test({this.id, this.creationDate, this.eyesOpen, this.invalid, this.sent}); @override bool operator ==(other) => other is Test && other.id == id && other.creationDate == creationDate && other.eyesOpen == eyesOpen && - other.invalid == invalid; + other.invalid == invalid && + other.sent == sent; @override int get hashCode { @@ -42,9 +45,10 @@ class Test { result = prime * result + creationDate.hashCode; result = prime * result + eyesOpen.hashCode; result = prime * result + invalid.hashCode; + result = prime * result + sent.hashCode; return result; } @override - String toString() => "Test(id=$id, creationDate=$creationDate, eyesOpen=$eyesOpen, invalid=$invalid)"; + String toString() => "Test(id=$id, creationDate=$creationDate, eyesOpen=$eyesOpen, invalid=$invalid, sent=$sent)"; } \ No newline at end of file diff --git a/lib/main.dart b/lib/main.dart index c5c9e25..87bfa8e 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -3,6 +3,7 @@ import 'dart:io'; import 'package:balance_app/bloc/intro_state/on_boarding_data_bloc.dart'; import 'package:balance_app/bloc/main/home/countdown_bloc_impl.dart'; import 'package:balance_app/screens/calibration/quick_calibration_screen.dart'; +import 'package:balance_app/screens/issues/issues_screen.dart'; import 'package:balance_app/screens/res/colors.dart'; import 'package:balance_app/screens/res/theme.dart'; import 'package:device_info/device_info.dart'; @@ -41,7 +42,7 @@ Future main() async { var sdkInt = androidInfo.version.sdkInt; var manufacturer = androidInfo.manufacturer; var model = androidInfo.model; - PreferenceManager.updateSystemInfo(producer: manufacturer, model: model, appVersion: "alpha.5", osVersion: "Android "+release+" SDK "+sdkInt.toString()); + PreferenceManager.updateSystemInfo(producer: manufacturer, model: model, appVersion: "beta.5", osVersion: "Android "+release+" SDK "+sdkInt.toString()); } if (Platform.isIOS) { @@ -50,7 +51,7 @@ Future main() async { var version = iosInfo.systemVersion; var name = iosInfo.name; //var model = iosInfo.model; - PreferenceManager.updateSystemInfo(producer: "Apple", model: name, appVersion: "alpha.5", osVersion: systemName+" "+version); + PreferenceManager.updateSystemInfo(producer: "Apple", model: name, appVersion: "beta.5", osVersion: systemName+" "+version); } runApp( @@ -95,6 +96,7 @@ class BalanceApp extends StatelessWidget { Routes.info: (_) => UserInfoRecapScreen(), Routes.slider: (_) => SliderScreen(), Routes.credits: (_) => CreditsScreen(), + Routes.issues: (_) => IssuesScreen(), Routes.result: (_) => ResultScreen(), Routes.open_source: (_) => OpenSourceScreen(), }, diff --git a/lib/manager/preference_manager.dart b/lib/manager/preference_manager.dart index 77adc05..b44553a 100644 --- a/lib/manager/preference_manager.dart +++ b/lib/manager/preference_manager.dart @@ -11,6 +11,8 @@ class PreferenceManager { static const _showTutorial = "ShowTutorial"; // Calibration static const _isDeviceCalibrated = "IsDeviceCalibrated"; + // Initial Condition + static const _initialCondition = "initialCondition"; // Accelerometer static const _accelerometerBiasX = "AccelerometerBiasX"; static const _accelerometerBiasY = "AccelerometerBiasY"; @@ -74,6 +76,16 @@ class PreferenceManager { return pref.getBool(_isDeviceCalibrated) ?? false; } + /// Update the all sensors biases with fresh values + /// + /// Given an accelerometer [SensorBias] and a gyroscope + /// [SensorBias] update their values into the [SharedPreferences] + /// and set [_isDeviceCalibrated] to true. + static Future updateInitialCondition(int condition) async { + var pref = await SharedPreferences.getInstance(); + pref.setInt(_initialCondition, condition); + } + /// Update the all sensors biases with fresh values /// /// Given an accelerometer [SensorBias] and a gyroscope @@ -109,6 +121,16 @@ class PreferenceManager { pref.setString(_osVersion, osVersion); } + /// Return a [Future] with accelerometer [SensorBias] + /// + /// This method will return the accelerometer + /// [SensorBias] stored in [SharedPreferences]; + /// if the values are null [0.0] will be passed instead. + static Future get initialCondition async { + var pref = await SharedPreferences.getInstance(); + return pref.getInt(_initialCondition) ?? 0; + } + /// Return a [Future] with accelerometer [SensorBias] /// /// This method will return the accelerometer diff --git a/lib/manager/vibration_manager.dart b/lib/manager/vibration_manager.dart index d5967bc..320fc64 100644 --- a/lib/manager/vibration_manager.dart +++ b/lib/manager/vibration_manager.dart @@ -4,7 +4,6 @@ import 'package:audioplayers/audioplayers.dart'; import 'package:vibration/vibration.dart'; class VibrationManager { - static const _vibrationPattern = [0, 300, 700, 300, 700, 300, 700, 300, 700, 300, 700, 300, 700]; static const _longVibrationTime = 800; final AudioCache _audioCache; AudioPlayer _playing; @@ -14,12 +13,17 @@ class VibrationManager { prefix: "sounds/", respectSilence: true ) { - _audioCache.load("light_pop.mp3"); + _audioCache.load("light_pop_bigdsc.mp3"); + _audioCache.load("finish_bigdsc.mp3"); } - Future playPattern() async{ - _playing = await _audioCache.play("light_pop.mp3"); - Vibration.vibrate(pattern: _vibrationPattern); + Future measuring() async{ + _playing = await _audioCache.play("light_pop_bigdsc.mp3"); + return null; + } + + Future finish() async{ + _playing = await _audioCache.play("finish_bigdsc.mp3"); return null; } diff --git a/lib/model/measurement.dart b/lib/model/measurement.dart index 7ea31ec..9fa4994 100644 --- a/lib/model/measurement.dart +++ b/lib/model/measurement.dart @@ -15,6 +15,12 @@ class Measurement { // Flag the measurement if invalid @ColumnInfo(name: "invalid") final bool invalid; + // Flag the measurement if invalid + @ColumnInfo(name: "initCondition") + final int initCondition; + // Flag the measurement if invalid + @ColumnInfo(name: "sent") + final bool sent; // General info about the measurement @ColumnInfo(name: "eyes_open", nullable: false) final bool eyesOpen; @@ -80,6 +86,8 @@ class Measurement { this.id, this.token, this.invalid = false, + this.initCondition = 0, + this.sent = true, this.creationDate, this.eyesOpen, this.hasFeatures = false, @@ -107,11 +115,13 @@ class Measurement { /// This method will create a new [Measurement] form an existing one and /// a [Statokinesigram]. /// The new [Measurement] will retain the id of the given one. - factory Measurement.from(Measurement m, String token, Statokinesigram s) => + factory Measurement.from(Measurement m, String token, int initCondition, Statokinesigram s, bool delivered) => Measurement( id: m.id, token: token, + initCondition: initCondition, invalid: s.outOfRange == 1.0 ? true : false, + sent: delivered ?? true, creationDate: m.creationDate, eyesOpen: m.eyesOpen, hasFeatures: true, @@ -150,10 +160,59 @@ class Measurement { grZ: s.grZ, ); + /// Factory method to update the sending state of a [Measurement] + /// This method will create a new [Measurement] form an existing one. + /// The new [Measurement] will have the right bool for sent. + factory Measurement.sent(Measurement m, bool sent) => + Measurement( + id: m.id, + token: m.token, + initCondition: m.initCondition, + invalid: m.invalid, + sent: sent, + creationDate: m.creationDate, + eyesOpen: m.eyesOpen, + hasFeatures: true, + swayPath: m.swayPath, + meanDisplacement: m.meanDisplacement, + stdDisplacement: m.stdDisplacement, + minDist: m.minDist, + maxDist: m.maxDist, + frequencyPeakAP: m.frequencyPeakAP, + frequencyPeakML: m.frequencyPeakML, + meanFrequencyML: m.meanFrequencyML, + meanFrequencyAP: m.meanFrequencyAP, + f80ML: m.f80ML, + f80AP: m.f80AP, + numMax: m.numMax, + meanTime: m.meanTime, + stdTime: m.stdTime, + meanDistance: m.meanDistance, + stdDistance: m.stdDistance, + meanPeaks: m.meanPeaks, + stdPeaks: m.stdPeaks, + gsX: m.gsX, + gsY: m.gsY, + gsZ: m.gsZ, + gkX: m.gkX, + gkY: m.gkY, + gkZ: m.gkZ, + gmX: m.gmX, + gmY: m.gmY, + gmZ: m.gmZ, + gvX: m.gvX, + gvY: m.gvY, + gvZ: m.gvZ, + grX: m.grX, + grY: m.grY, + grZ: m.grZ, + ); + Map toJson() => { 'id': this.id, 'token': this.token, 'invalid': this.invalid, + 'initCondition': this.initCondition, 'creationDate': this.creationDate, 'eyesOpen': this.eyesOpen, 'hasFeatures': this.hasFeatures, @@ -191,6 +250,8 @@ class Measurement { runtimeType == other.runtimeType && id == other.id && invalid == other.invalid && + initCondition == other.initCondition && + sent == other.sent && creationDate == other.creationDate && eyesOpen == other.eyesOpen && hasFeatures == other.hasFeatures && @@ -232,6 +293,8 @@ class Measurement { int get hashCode => id.hashCode ^ invalid.hashCode ^ + initCondition.hashCode ^ + sent.hashCode ^ creationDate.hashCode ^ eyesOpen.hashCode ^ hasFeatures.hashCode ^ @@ -272,7 +335,7 @@ class Measurement { @override String toString() { return 'Measurement(' - 'id: $id, invalid: $invalid, creationDate: $creationDate, eyesOpen: $eyesOpen, hasFeatures: $hasFeatures, ' + 'id: $id, invalid: $invalid, condition: $initCondition, creationDate: $creationDate, eyesOpen: $eyesOpen, hasFeatures: $hasFeatures, ' 'swayPath: $swayPath, meanDisplacement: $meanDisplacement, ' 'stdDisplacement: $stdDisplacement, minDist: $minDist, maxDist: $maxDist, ' 'meanFrequencyAP: $meanFrequencyAP, meanFrequencyML: $meanFrequencyML, ' @@ -284,11 +347,11 @@ class Measurement { } String toCSV() { - return 'id;invalid;creationDate;eyesOpen;hasFeatures;swayPath;meanDisplacement;stdDisplacement;' + return 'id;invalid;condition;creationDate;eyesOpen;hasFeatures;swayPath;meanDisplacement;stdDisplacement;' 'minDist;maxDist;meanFrequencyAP;meanFrequencyML;frequencyPeakAP;frequencyPeakML;' 'f80AP;f80ML;np;meanTime;stdTime;meanDistance;stdDistance;meanPeaks;stdPeaks;' 'grX;grY;grZ;gmX;gmY;gmZ;gvX;gvY;gvZ;gkX;gkY;gkZ;gsX;gsY;gsZ\n' - '$id;$invalid;$creationDate;$eyesOpen;$hasFeatures;$swayPath;$meanDisplacement;' + '$id;$invalid;$initCondition;$creationDate;$eyesOpen;$hasFeatures;$swayPath;$meanDisplacement;' '$stdDisplacement;$minDist;$maxDist;$meanFrequencyAP;$meanFrequencyML;' '$frequencyPeakAP;$frequencyPeakML;$f80AP;$f80ML;$numMax;$meanTime;' '$stdTime;$meanDistance;$stdDistance;$meanPeaks;$stdPeaks;$grX;$grY;' diff --git a/lib/repository/measure_countdown_repository.dart b/lib/repository/measure_countdown_repository.dart index 543e16a..1e14fbe 100644 --- a/lib/repository/measure_countdown_repository.dart +++ b/lib/repository/measure_countdown_repository.dart @@ -1,4 +1,6 @@ +import 'dart:async'; import 'dart:convert'; +import 'dart:io'; import 'package:balance_app/floor/measurement_database.dart'; @@ -29,13 +31,12 @@ class MeasureCountdownRepository { ), ); + // Generate data as RawMeasurementData + var rawData = _generateRawData(rawSensorData, newMeasId).toList(); // Store the SensorData in database as RawMeasurementData - await rawMeasDataDao.insertRawMeasurements( - await _generateRawData(rawSensorData, newMeasId).toList() - ); - - // send data to server - _makePostRequest(await _generateRawData(rawSensorData, newMeasId).toList()); + await rawMeasDataDao.insertRawMeasurements(await rawData); + // Send data to server + _makePostRequest(await rawData); // return the newly added Test return await measurementDao.findTestById(newMeasId); @@ -45,21 +46,28 @@ class MeasureCountdownRepository { } } - _makePostRequest(var data) async { - // TODO: This stuff here is hardcode. Need changes + Future _makePostRequest(var data) async { // set up POST request arguments String url = 'https://www.balancemobile.it/api/v1/db/sway'; //String url = 'http://192.168.1.206:8000/api/v1/db/sway'; Map headers = {"Content-type": "application/json"}; - String json = jsonEncode(data); - // make POST request - Response response = await post(url, headers: headers, body: json); + try { + Response response = await post(url, headers: headers, body: jsonEncode(data)).timeout(Duration(seconds: 30)); - // response - //int statusCode = response.statusCode; - //String body = response.body; - print("Response from backend: "+response.toString()); + if (response.statusCode == 200) { + return true; + } else { + print("_SendingData.RawMeasurement: The server answered with: "+response.statusCode.toString()); + return false; + } + } on TimeoutException catch (_) { + print("_SendingData.RawMeasurement: The connection dropped, maybe the server is congested"); + return false; + } on SocketException catch (_) { + print("_SendingData.RawMeasurement: Communication failed. The server was not reachable"); + return false; + } } /// Asynchronously generate the [RawMeasurementData] from the [SensorData] diff --git a/lib/repository/result_repository.dart b/lib/repository/result_repository.dart index 156057f..82aad4d 100644 --- a/lib/repository/result_repository.dart +++ b/lib/repository/result_repository.dart @@ -1,4 +1,5 @@ +import 'dart:async'; import 'dart:convert'; import 'dart:io'; @@ -29,6 +30,7 @@ class ResultRepository { final measurement = await database.measurementDao.findMeasurementById(measurementId); final cogv = await database.cogvDataDao.findAllCogvDataForId(measurementId); final token = (await PreferenceManager.userInfo).token; + final condition = await PreferenceManager.initialCondition; // 2. Check if the features and the cogv data are present and compute them if not if (!measurement.hasFeatures && cogv.isEmpty) { print("ResultRepository.getResult: Computing Features..."); @@ -39,8 +41,15 @@ class ResultRepository { final computed = await PostureProcessor.computeFromData(measurementId, rawMeasurementData); // Update the measurement with the computed features - database.measurementDao.updateMeasurement(Measurement.from(measurement, token, computed)); - _makePostRequest(Measurement.from(measurement, token, computed)); + var created_measurement = Measurement.from(measurement, token, condition, computed, true); + print(jsonEncode(created_measurement)); + bool result = await _makePostRequest(created_measurement); + if (result) + database.measurementDao.updateMeasurement(created_measurement); + else { + database.measurementDao.updateMeasurement(Measurement.from(measurement, token, condition, computed, false)); + print("_SendingData.Measurement: BAD DELIVERY STATUS for test $measurementId"); + } // Store the computed CogvData database.cogvDataDao.insertCogvData(computed.cogv); @@ -50,21 +59,28 @@ class ResultRepository { return Statokinesigram.from(measurement, cogv); } - _makePostRequest(var data) async { - // TODO: This stuff here is hardcode. Need changes - // set up POST request arguments + Future _makePostRequest(var data) async { + // set up POST request argumentsq String url = 'https://www.balancemobile.it/api/v1/db/measurement'; //String url = 'http://192.168.1.206:8000/api/v1/db/measurement'; Map headers = {"Content-type": "application/json"}; - String json = jsonEncode(data.toJson()); - // make POST request - Response response = await post(url, headers: headers, body: json); - - // response - //int statusCode = response.statusCode; - //String body = response.body; - print("Response from backend: "+response.toString()); + try { + Response response = await post(url, headers: headers, body: jsonEncode(data)).timeout(Duration(seconds: 5)); + + if (response.statusCode == 200) { + return true; + } else { + print("_SendingData.Measurement: The server answered with: "+response.statusCode.toString()); + return false; + } + } on TimeoutException catch (_) { + print("_SendingData.Measurement: The connection dropped, maybe the server is congested"); + return false; + } on SocketException catch (_) { + print("_SendingData.Measurement: Communication failed. The server was not reachable"); + return false; + } } /// Save all the measurement in a .json file diff --git a/lib/routes.dart b/lib/routes.dart index 4d8f16d..b4a4778 100644 --- a/lib/routes.dart +++ b/lib/routes.dart @@ -12,4 +12,5 @@ class Routes { static const String result = "/result_route"; static const String open_source = "/open_source"; static const String credits = "/credits"; + static const String issues = "/issues"; } \ No newline at end of file diff --git a/lib/screens/calibration/quick_calibration_screen.dart b/lib/screens/calibration/quick_calibration_screen.dart index fd2bea6..49411dc 100644 --- a/lib/screens/calibration/quick_calibration_screen.dart +++ b/lib/screens/calibration/quick_calibration_screen.dart @@ -46,18 +46,10 @@ class QuickCalibrationScreen extends StatelessWidget { Align( alignment: Alignment.center, child: RaisedButton( - onPressed: () { - if (state == SensorController.listening) - null; - else if (state == SensorController.complete) { - Navigator.pop(context); - } - else - controller.listen(); - }, + onPressed: state == SensorController.listening ? null : (state == SensorController.complete ? () => Navigator.pop(context) : () => controller.listen()), child: Text( state == SensorController.complete - ? 'Torna alla Schermata principale' + ? 'back_home_btn'.tr() : 'start_calibration_btn'.tr() ), ), diff --git a/lib/screens/credits/credits.dart b/lib/screens/credits/credits.dart index d535228..e5f5f93 100644 --- a/lib/screens/credits/credits.dart +++ b/lib/screens/credits/credits.dart @@ -1,11 +1,26 @@ import 'package:flutter/material.dart'; import 'package:easy_localization/easy_localization.dart'; +import 'package:package_info/package_info.dart'; + +class CreditsScreen extends StatefulWidget { + @override + _CreditsScreenState createState() => _CreditsScreenState(); +} /// Widget for displaying informations about open source dependencies -class CreditsScreen extends StatelessWidget { +class _CreditsScreenState extends State { + PackageInfo packageInfo; + + @override + void initState() { + super.initState(); + PackageInfo.fromPlatform().then((value) => setState(() => packageInfo = value)); + } + @override Widget build(BuildContext context) { + PackageInfo.fromPlatform().then((value) => packageInfo = value); return Scaffold( appBar: AppBar( title: Text('about_title'.tr()), @@ -24,12 +39,27 @@ class CreditsScreen extends StatelessWidget { ), ), Padding( - padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 16.0), + padding: const EdgeInsets.fromLTRB(0.0,16.0,0.0,0.0), child: Text( 'Balance Mobile ©', style: Theme.of(context).textTheme.headline4, ), ), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 8.0, vertical: 8.0), + child: Text( + "${'version_txt'.tr()} ${packageInfo?.version} (${'build_txt'.tr()}${packageInfo?.buildNumber})", + style: Theme.of(context).textTheme.bodyText1, + ), + ), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 8.0, vertical: 8.0), + child: Text( + "credits_description_txt".tr(), + style: Theme.of(context).textTheme.bodyText1, + textAlign: TextAlign.justify, + ), + ), Card( margin: const EdgeInsets.symmetric(horizontal: 8.0, vertical: 4.0), child: Padding( @@ -38,14 +68,45 @@ class CreditsScreen extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - 'Authors', + 'credits_authors_txt'.tr(), style: Theme.of(context).textTheme.headline5, ), Divider(), - SizedBox(height: 4.0), + SizedBox(height: 8.0), + RichText( + overflow: TextOverflow.clip, + text: TextSpan( + text: 'Gioele Bigini', + style: Theme.of(context).textTheme.headline6.copyWith(fontSize: 16), + ), + ), Text( - 'Emanuele Lattanzi, Valerio Freschi, Gioele Bigini', - style: Theme.of(context).textTheme.bodyText2, + 'credits_authors_bigini_txt'.tr(), + style: Theme.of(context).textTheme.bodyText1, + ), + SizedBox(height: 8.0), + RichText( + overflow: TextOverflow.clip, + text: TextSpan( + text: 'Valerio Freschi', + style: Theme.of(context).textTheme.headline6.copyWith(fontSize: 16), + ), + ), + Text( + 'credits_authors_freschi_txt'.tr(), + style: Theme.of(context).textTheme.bodyText1, + ), + SizedBox(height: 8.0), + RichText( + overflow: TextOverflow.clip, + text: TextSpan( + text: 'Emanuele Lattanzi', + style: Theme.of(context).textTheme.headline6.copyWith(fontSize: 16), + ), + ), + Text( + 'credits_authors_lattanzi_txt'.tr(), + style: Theme.of(context).textTheme.bodyText1, ), ], ), @@ -59,14 +120,45 @@ class CreditsScreen extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - 'Developers', + 'credits_developers_txt'.tr(), style: Theme.of(context).textTheme.headline5, ), Divider(), - SizedBox(height: 4.0), + SizedBox(height: 8.0), + RichText( + overflow: TextOverflow.clip, + text: TextSpan( + text: 'Gioele Bigini', + style: Theme.of(context).textTheme.headline6.copyWith(fontSize: 16), + ), + ), + Text( + 'credits_developers_bigini_txt'.tr(), + style: Theme.of(context).textTheme.bodyText1, + ), + SizedBox(height: 8.0), + RichText( + overflow: TextOverflow.clip, + text: TextSpan( + text: 'Gian Marco di Francesco', + style: Theme.of(context).textTheme.headline6.copyWith(fontSize: 16), + ), + ), Text( - 'Gioele Bigini, Gianmarco di Francesco, Lorenzo Calisti', - style: Theme.of(context).textTheme.bodyText2, + 'credits_developers_difrancesco_txt'.tr(), + style: Theme.of(context).textTheme.bodyText1, + ), + SizedBox(height: 8.0), + RichText( + overflow: TextOverflow.clip, + text: TextSpan( + text: 'Lorenzo Calisti', + style: Theme.of(context).textTheme.headline6.copyWith(fontSize: 16), + ), + ), + Text( + 'credits_developers_calisti_txt'.tr(), + style: Theme.of(context).textTheme.bodyText1, ), ], ), @@ -80,14 +172,45 @@ class CreditsScreen extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - 'Collaboratori', + 'credits_collaborators_txt'.tr(), style: Theme.of(context).textTheme.headline5, ), Divider(), - SizedBox(height: 4.0), + SizedBox(height: 8.0), + RichText( + overflow: TextOverflow.clip, + text: TextSpan( + text: 'Lorenz Cuno Klopfenstein', + style: Theme.of(context).textTheme.headline6.copyWith(fontSize: 16), + ), + ), Text( - 'Lorenz Cuno Klopfestein, Saverio Delpriori', - style: Theme.of(context).textTheme.bodyText2, + 'credits_collaborators_klopfenstein_txt'.tr(), + style: Theme.of(context).textTheme.bodyText1, + ), + SizedBox(height: 8.0), + RichText( + overflow: TextOverflow.clip, + text: TextSpan( + text: 'Saverio Delpriori', + style: Theme.of(context).textTheme.headline6.copyWith(fontSize: 16), + ), + ), + Text( + 'credits_collaborators_delpriori_txt'.tr(), + style: Theme.of(context).textTheme.bodyText1, + ), + SizedBox(height: 8.0), + RichText( + overflow: TextOverflow.clip, + text: TextSpan( + text: 'Alessandro Bogliolo', + style: Theme.of(context).textTheme.headline6.copyWith(fontSize: 16), + ), + ), + Text( + 'credits_collaborators_bogliolo_txt'.tr(), + style: Theme.of(context).textTheme.bodyText1, ), ], ), @@ -101,15 +224,35 @@ class CreditsScreen extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - 'Responsabile del Trattamento Dati', + 'credits_foundation_title'.tr(), style: Theme.of(context).textTheme.headline5, ), Divider(), - SizedBox(height: 4.0), + SizedBox(height: 8.0), + RichText( + overflow: TextOverflow.clip, + text: TextSpan( + text: 'Standing Balance Assessment by Measurement of Body Center of Gravity Using Smartphones', + style: Theme.of(context).textTheme.headline6.copyWith(fontSize: 14), + ), + ), + Text( + 'E. Lattanzi et al., IEEE Access, 2019', + style: Theme.of(context).textTheme.bodyText1, + ), + SizedBox(height: 8.0), + RichText( + overflow: TextOverflow.clip, + text: TextSpan( + text: 'A Review on Blockchain for the Internet of Medical Things', + style: Theme.of(context).textTheme.headline6.copyWith(fontSize: 14), + ), + ), Text( - 'Alessandro Bogliolo', - style: Theme.of(context).textTheme.bodyText2, + 'G. Bigini et al., Future Internet, 2020', + style: Theme.of(context).textTheme.bodyText1, ), + SizedBox(height: 8.0), ], ), ), @@ -122,7 +265,7 @@ class CreditsScreen extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - 'Sponsor e Partner', + 'credits_sponsors_partners_txt'.tr(), style: Theme.of(context).textTheme.headline5, ), Divider(), diff --git a/lib/screens/intro/intro_screen.dart b/lib/screens/intro/intro_screen.dart index e4d881c..6e7df32 100644 --- a/lib/screens/intro/intro_screen.dart +++ b/lib/screens/intro/intro_screen.dart @@ -26,6 +26,8 @@ import 'slider/posture.dart'; import 'slider/habits.dart'; import 'slider/sight.dart'; +import 'package:easy_localization/easy_localization.dart'; + /// This class show an introduction when the app is first open. class IntroScreen extends StatefulWidget { @override @@ -121,10 +123,12 @@ class _IntroScreenState extends State { bool result = await _makePostRequest(jsonEncode(await PreferenceManager.userInfo), true); setState(() { - if (result) { - _isLoggedIn = true; - } else { - _isLoggedIn = false; + if (!result) { + Scaffold.of(context).showSnackBar(SnackBar( + duration: const Duration(seconds: 10), + behavior: SnackBarBehavior.floating, + content: Text('intro_backend_snack_txt'.tr()) + )); } _isInAsyncCall = false; }); @@ -135,9 +139,9 @@ class _IntroScreenState extends State { } } else { /* - * All the required data are stored... mark the - * first launch as done so we don't ask this data anymore - */ + * All the required data are stored... mark the + * first launch as done so we don't ask this data anymore + */ print(await PreferenceManager.userInfo); // Move to next page _pageController.nextPage( diff --git a/lib/screens/intro/slider/widgets/turn_internet_on_dialog.dart b/lib/screens/intro/slider/widgets/turn_internet_on_dialog.dart new file mode 100644 index 0000000..628c598 --- /dev/null +++ b/lib/screens/intro/slider/widgets/turn_internet_on_dialog.dart @@ -0,0 +1,34 @@ + +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; + +void showTurnInternetOnDialog(BuildContext context) { + showDialog( + context: context, + builder: (context) => AlertDialog( + backgroundColor: Colors.black45, + title: + Text('other_trauma_title'.tr(), + style: Theme.of(context).textTheme.subtitle2.copyWith( + fontSize: 16, + color: Colors.white, + ),), + content: + Text('trauma_explained_txt'.tr(), + style: Theme.of(context).textTheme.subtitle2.copyWith( + fontSize: 12, + color: Colors.white, + ),), + actions: [ + FlatButton( + child: Text('close'.tr(), + style: Theme.of(context).textTheme.subtitle2.copyWith( + fontSize: 10, + color: Colors.white, + ),), + onPressed: () => Navigator.pop(context), + ) + ], + ), + ); +} \ No newline at end of file diff --git a/lib/screens/issues/issues_screen.dart b/lib/screens/issues/issues_screen.dart new file mode 100644 index 0000000..777f99e --- /dev/null +++ b/lib/screens/issues/issues_screen.dart @@ -0,0 +1,153 @@ + +import 'dart:async'; +import 'dart:convert'; +import 'dart:io'; + +import 'package:balance_app/manager/preference_manager.dart'; +import 'package:balance_app/screens/issues/widgets/custom_form_field.dart'; +import 'package:flutter/material.dart'; +import 'package:easy_localization/easy_localization.dart'; +import 'package:http/http.dart'; +import 'package:package_info/package_info.dart'; + +class IssuesScreen extends StatefulWidget { + @override + _IssuesScreenState createState() => _IssuesScreenState(); +} + +/// Widget for displaying informations about open source dependencies +class _IssuesScreenState extends State { + PackageInfo packageInfo; + String token; + String _description; + + @override + void initState() { + super.initState(); + PackageInfo.fromPlatform().then((value) => setState(() => packageInfo = value)); + _description = ''; + } + + @override + Widget build(BuildContext context) { + PackageInfo.fromPlatform().then((value) => packageInfo = value); + return Scaffold( + appBar: AppBar( + title: Text('report_title'.tr()), + ), + body: Builder( + // Create an inner BuildContext so that the onPressed methods + // can refer to the Scaffold with Scaffold.of(). + builder: (BuildContext context) { + return SingleChildScrollView( + padding: const EdgeInsets.only( + left: 8.0, top: 16.0, right: 8.0, bottom: 16.0), + child: Column( + children: [ + Container( + padding: const EdgeInsets.fromLTRB(0.0, 16.0, 0.0, 16.0), + width: 150, + height: 150, + child: Center( + child: Image.asset("assets/app_logo_circle_broken.png"), + ), + ), + Padding( + padding: const EdgeInsets.fromLTRB(0.0, 16.0, 0.0, 0.0), + child: Text( + 'report_title_txt'.tr(), + style: Theme + .of(context) + .textTheme + .headline5, + ), + ), + Padding( + padding: const EdgeInsets.symmetric( + horizontal: 8.0, vertical: 8.0), + child: Text( + "${'version_txt'.tr()} ${packageInfo + ?.version} (${'build_txt'.tr()}${packageInfo + ?.buildNumber})", + style: Theme + .of(context) + .textTheme + .bodyText1, + ), + ), + Padding( + padding: const EdgeInsets.symmetric( + horizontal: 16.0, vertical: 16.0), + child: CustomFormField( + labelText: 'report_description_txt'.tr(), + initialValue: '', + onChanged: (value) { + setState(() { + _description = value; + }); + }, + validator: (value) { + if (value.isEmpty) + return 'too_short_error_txt'.tr(); + }, + ), + ), + RaisedButton( + onPressed: () async { + if (_description.length > 0) { + String token = (await PreferenceManager.userInfo).token; + String version = "${'version_txt'.tr()} ${packageInfo + ?.version} (${'build_txt'.tr()}${packageInfo + ?.buildNumber})"; + String json = '{"token":"$token","version":"$version","description":"$_description"}'; + _makePostRequest(json); + FocusScope.of(context).unfocus(); + Scaffold.of(context).showSnackBar(SnackBar( + behavior: SnackBarBehavior.floating, + content: Text('report_snack_true_txt'.tr()), + duration: Duration(seconds: 2), + )); + } else { + FocusScope.of(context).unfocus(); + Scaffold.of(context).showSnackBar(SnackBar( + behavior: SnackBarBehavior.floating, + content: Text('report_snack_false_txt'.tr()), + duration: Duration(seconds: 2), + )); + } + }, + child: Text('report_send_txt'.tr()) + ), + SizedBox(height: 16), + ], + ), + ); + }, + ), + ); + } +} + +Future _makePostRequest(var data) async { + // set up POST request arguments + String url = 'https://www.balancemobile.it/api/v1/db/reporting'; + //String url = 'http://192.168.1.206:8000/api/v1/db/reporting'; + Map headers = {"Content-type": "application/json"}; + + try { + Response response = await post(url, headers: headers, body: data).timeout(Duration(seconds: 30)); + + if (response.statusCode == 200) { + return true; + } else { + print("_SendingData.Issues: The server answered with: "+response.statusCode.toString()); + return false; + } + } on TimeoutException catch (_) { + print("_SendingData.Issues: The connection dropped, maybe the server is congested"); + return false; + } on SocketException catch (_) { + print("_SendingData.Issues: Communication failed. The server was not reachable"); + return false; + } +} \ No newline at end of file diff --git a/lib/screens/issues/widgets/custom_form_field.dart b/lib/screens/issues/widgets/custom_form_field.dart new file mode 100644 index 0000000..0cc9538 --- /dev/null +++ b/lib/screens/issues/widgets/custom_form_field.dart @@ -0,0 +1,74 @@ + +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; + +/// StatelessWidget that implements the custom +/// style for a [TextFormField] +class CustomFormField extends StatelessWidget { + final String initialValue; + final String labelText; + final String suffix; + final bool decimal; + final ValueChanged onChanged; + final FormFieldValidator validator; + final FormFieldSetter onSaved; + + CustomFormField({ + this.labelText, + this.suffix, + this.decimal = false, + this.initialValue, + this.onSaved, + this.validator, + this.onChanged, + }); + + @override + Widget build(BuildContext context) { + return GestureDetector( + onTap: () { + FocusScopeNode currentFocus = FocusScope.of(context); + }, + child: Material( + elevation: 4, + color: Colors.white, + borderRadius: BorderRadius.circular(9), + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 4), + child: TextFormField( + inputFormatters: [ + new LengthLimitingTextInputFormatter(256), + ], + keyboardType: TextInputType.multiline, + maxLines: null, + decoration: InputDecoration( + border: InputBorder.none, + hintText: labelText, + suffixText: suffix, + hintStyle: TextStyle( + color: Color(0xFFBFBFBF), + fontSize: 14, + fontWeight: FontWeight.w500, + ), + suffixStyle: TextStyle( + color: Color(0xFFBFBFBF), + fontSize: 14, + fontWeight: FontWeight.w500, + ), + ), + style: TextStyle( + color: Colors.black45, + fontSize: 14, + fontWeight: FontWeight.w500, + ), + autocorrect: false, + initialValue: initialValue, + onChanged: (newValue) => onChanged?.call(newValue), + validator: validator, + onSaved: onSaved, + ), + ), + ), + ); + } +} \ No newline at end of file diff --git a/lib/screens/main/home/widgets/circular_countdown.dart b/lib/screens/main/home/widgets/circular_countdown.dart index 7e66020..17ff4be 100644 --- a/lib/screens/main/home/widgets/circular_countdown.dart +++ b/lib/screens/main/home/widgets/circular_countdown.dart @@ -64,7 +64,7 @@ class _CircularCounterState extends State with SingleTickerProv Duration get _duration => Duration( milliseconds: widget.state is CountdownMeasureState ? 30000 - : 6000 + : 2000 ); String get _timeString { diff --git a/lib/screens/main/home/widgets/measure_countdown.dart b/lib/screens/main/home/widgets/measure_countdown.dart index 7482429..7d80f89 100644 --- a/lib/screens/main/home/widgets/measure_countdown.dart +++ b/lib/screens/main/home/widgets/measure_countdown.dart @@ -5,6 +5,7 @@ import 'dart:io'; import 'package:balance_app/manager/vibration_manager.dart'; import 'package:balance_app/routes.dart'; import 'package:balance_app/screens/main/home/widgets/device_not_ready_dialog.dart'; +import 'package:balance_app/screens/main/home/widgets/measuring_condition_dialog.dart'; import 'package:balance_app/screens/main/home/widgets/targeting_game.dart'; import 'package:battery/battery.dart'; import 'package:flutter/material.dart'; @@ -93,11 +94,15 @@ class _MeasureCountdownState extends State with WidgetsBinding // Start/Stop the vibration and sounds if (state is CountdownPreMeasureState) { Wakelock.enable(); - vibrationManager.playPattern(); + vibrationManager.playSingle(); } - else if (state is CountdownMeasureState || state is CountdownCompleteState) { + else if (state is CountdownMeasureState) { Wakelock.enable(); - vibrationManager.playSingle(); + vibrationManager.measuring(); + } + else if (state is CountdownCompleteState) { + Wakelock.enable(); + vibrationManager.finish(); } else { Wakelock.disable(); @@ -121,55 +126,61 @@ class _MeasureCountdownState extends State with WidgetsBinding SizedBox(height: 0), RaisedButton( onPressed: () async{ - int batteryLevel = await _battery.batteryLevel; - if (batteryLevel >= 30) { - if (state is CountdownIdleState) { - /* - * Every time the user presses the start button we need to check - * two conditions: - * - the device is calibrated? if not ask the user to do it - * - we need to show the tutorial? - */ - bool isDeviceCalibrated = await PreferenceManager - .isDeviceCalibrated; - bool showTutorial = await PreferenceManager - .showMeasuringTutorial; + try { + if (await _battery.batteryLevel < 30) + return showDeviceNotReady(context); + } on Exception { + print("Cannot take battery level from smartphone"); + } - if (!isDeviceCalibrated) - showCalibrateDeviceDialog(context); - else if (showTutorial) - showTutorialDialog( - context, - () => - context.bloc().add( - CountdownEvents.startTargeting) - ); - else - context.bloc().add( - CountdownEvents.startTargeting); - } - else if (state is CountdownPreMeasureState) { - // Stop the pre measure countdown - vibrationManager.cancel(); - context.bloc().add( - CountdownEvents.stopPreMeasure); - } - else if (state is CountdownMeasureState) { - // Stop the measurement - vibrationManager.cancel(); - context.bloc().add( - CountdownEvents.stopMeasure); + if (state is CountdownIdleState) { + /* + * Every time the user presses the start button we need to check + * two conditions: + * - the device is calibrated? if not ask the user to do it + * - we need to show the tutorial? + */ + bool isDeviceCalibrated = await PreferenceManager + .isDeviceCalibrated; + bool showTutorial = await PreferenceManager + .showMeasuringTutorial; + + if (!isDeviceCalibrated) + showCalibrateDeviceDialog(context); + else if (showTutorial) { + showMeasuringConditionDialog( + context, + () => context.bloc().add(CountdownEvents.startTargeting) + ); + showTutorialDialog( + context, + () => null + ); } - else if (state is CountdownTargetingState) { - // Stop the measurement - vibrationManager.cancel(); - context.bloc().add( - CountdownEvents.stopTargeting); + else { + showMeasuringConditionDialog(context, + () => context.bloc().add(CountdownEvents.startTargeting) + ); } } - else { - showDeviceNotReady(context); - }; + else if (state is CountdownPreMeasureState) { + // Stop the pre measure countdown + vibrationManager.cancel(); + context.bloc().add( + CountdownEvents.stopPreMeasure); + } + else if (state is CountdownMeasureState) { + // Stop the measurement + vibrationManager.cancel(); + context.bloc().add( + CountdownEvents.stopMeasure); + } + else if (state is CountdownTargetingState) { + // Stop the measurement + vibrationManager.cancel(); + context.bloc().add( + CountdownEvents.stopTargeting); + } }, color: BColors.colorAccent, child: Text( diff --git a/lib/screens/main/home/widgets/measuring_condition_dialog.dart b/lib/screens/main/home/widgets/measuring_condition_dialog.dart new file mode 100644 index 0000000..9bf8231 --- /dev/null +++ b/lib/screens/main/home/widgets/measuring_condition_dialog.dart @@ -0,0 +1,195 @@ + +import 'package:balance_app/manager/preference_manager.dart'; +import 'package:balance_app/screens/res/colors.dart'; +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; + +/// Show the [TutorialDialog] +/// +/// The callback [onDone] is called every time the action button +/// is pressed and it lets the parent Widget start the measuring +void showMeasuringConditionDialog(BuildContext context, VoidCallback onDone) { + showDialog( + barrierDismissible: false, + context: context, + builder: (context) => MeasuringConditionDialog(onDone), + ); +} + +/// Widget that implements a tutorial dialog +/// +/// This dialog has the purpose of teaching the user +/// how to correctly perform a measurement. +class MeasuringConditionDialog extends StatefulWidget { + final VoidCallback callback; + + MeasuringConditionDialog(this.callback); + + @override + _MeasuringConditionDialogState createState() => _MeasuringConditionDialogState(); +} + +class _MeasuringConditionDialogState extends State { + int _value; + + @override + Widget build(BuildContext context) { + return AlertDialog( + title: Text('Le tue condizioni'), + contentPadding: const EdgeInsets.all(0.0), + content: Container( + child: SingleChildScrollView( + child: Column( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: const EdgeInsets.all(24.0), + child: Text('Conoscere la tua condizione degli ultimi 15 minuti é estremamente importante. Seleziona le icone che meglio descrivono quello che stavi facendo!'), + ), + SizedBox(width: 4), + Padding( + padding: const EdgeInsets.fromLTRB(24, 0, 24, 16), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + GestureDetector( + onTap: () => setState(() => _value = 1), + child: ClipRRect( + borderRadius: BorderRadius.circular(15.0), + child: Container( + height: 56, + width: 56, + color: _value == 1 ? BColors.colorPrimary : Colors.transparent, + child: Center( + child: Image.asset("assets/images/sleeping.png"), + ), + ), + ), + ), + SizedBox(width: 4), + GestureDetector( + onTap: () => setState(() => _value = 2), + child: ClipRRect( + borderRadius: BorderRadius.circular(15.0), + child: Container( + height: 56, + width: 56, + color: _value == 2 ? BColors.colorPrimary : Colors.transparent, + child: Center( + child: Image.asset("assets/images/working.png"), + ), + ), + ), + ), + SizedBox(width: 4), + GestureDetector( + onTap: () => setState(() => _value = 3), + child: ClipRRect( + borderRadius: BorderRadius.circular(15.0), + child: Container( + height: 56, + width: 56, + color: _value == 3 ? BColors.colorPrimary : Colors.transparent, + child: Center( + child: Image.asset("assets/images/walking.png"), + ), + ), + ), + ), + ], + ), + ), + Padding( + padding: const EdgeInsets.fromLTRB(24, 0, 24, 16), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + GestureDetector( + onTap: () => setState(() => _value = 4), + child: ClipRRect( + borderRadius: BorderRadius.circular(15.0), + child: Container( + height: 56, + width: 56, + color: _value == 4 ? BColors.colorPrimary : Colors.transparent, + child: Center( + child: Image.asset("assets/images/reading.png"), + ), + ), + ), + ), + SizedBox(width: 4), + GestureDetector( + onTap: () => setState(() => _value = 5), + child: ClipRRect( + borderRadius: BorderRadius.circular(15.0), + child: Container( + height: 56, + width: 56, + color: _value == 5 ? BColors.colorPrimary : Colors.transparent, + child: Center( + child: Image.asset("assets/images/eating.png"), + ), + ), + ), + ), + SizedBox(width: 4), + GestureDetector( + onTap: () => setState(() => _value = 6), + child: ClipRRect( + borderRadius: BorderRadius.circular(15.0), + child: Container( + height: 56, + width: 56, + color: _value == 6 ? BColors.colorPrimary : Colors.transparent, + child: Center( + child: Image.asset("assets/images/drinking.png"), + ), + ), + ), + ), + ], + ), + ), + Padding( + padding: const EdgeInsets.fromLTRB(24, 0, 24, 16), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + GestureDetector( + onTap: () => setState(() => _value = 7), + child: ClipRRect( + borderRadius: BorderRadius.circular(15.0), + child: Container( + height: 56, + width: 56, + color: _value == 7 ? BColors.colorPrimary : Colors.transparent, + child: Center( + child: Image.asset("assets/images/sport.png"), + ), + ), + ), + ), + ], + ), + ), + ], + ), + ), + ), + actions: [ + FlatButton( + onPressed: () { + widget.callback(); + PreferenceManager.updateInitialCondition(_value ?? 0); + Navigator.pop(context); + }, + child: Text('ok'.tr()) + ), + ], + ); + } +} diff --git a/lib/screens/main/home/widgets/measuring_tutorial_dialog.dart b/lib/screens/main/home/widgets/measuring_tutorial_dialog.dart index 02b83e3..d73583f 100644 --- a/lib/screens/main/home/widgets/measuring_tutorial_dialog.dart +++ b/lib/screens/main/home/widgets/measuring_tutorial_dialog.dart @@ -11,6 +11,7 @@ import 'package:flutter/rendering.dart'; /// is pressed and it lets the parent Widget start the measuring void showTutorialDialog(BuildContext context, VoidCallback onDone) { showDialog( + barrierDismissible: false, context: context, builder: (context) => TutorialDialog(onDone), ); @@ -36,55 +37,59 @@ class _TutorialDialogState extends State { Widget build(BuildContext context) { return AlertDialog( contentPadding: const EdgeInsets.all(0.0), - content: Column( - mainAxisSize: MainAxisSize.min, - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Container( - width: double.maxFinite, - height: 250, - child: ClipRRect( - borderRadius: BorderRadius.vertical(top: Radius.circular(9.0)), - child: Image.asset( - "assets/images/tutorial.png", - fit: BoxFit.fitHeight, - ), - ), - ), - Padding( - padding: const EdgeInsets.all(24.0), - child: Text( - 'tutorial_msg'.tr(), - textScaleFactor: 0.9, - ), - ), - Padding( - padding: const EdgeInsets.symmetric(horizontal: 12.0), - child: GestureDetector( - onTap: () { - setState(() => _neverShowAgainCheck = !_neverShowAgainCheck); - }, - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - CircularCheckBox( - value: _neverShowAgainCheck, - onChanged: (value) { - setState(() => _neverShowAgainCheck = value); - }, - activeColor: Colors.blue, - ), - SizedBox(width: 8), - Text( - 'never_show_again'.tr(), - textScaleFactor: 0.9, + content: Container( + child: SingleChildScrollView( + child: Column( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + width: double.maxFinite, + height: 190, + child: ClipRRect( + borderRadius: BorderRadius.vertical(top: Radius.circular(9.0)), + child: Image.asset( + "assets/images/tutorial.png", + fit: BoxFit.fitHeight, ), - ], + ), ), - ), - ) - ], + Padding( + padding: const EdgeInsets.all(24.0), + child: Text( + 'tutorial_msg'.tr(), + textScaleFactor: 0.9, + ), + ), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 12.0), + child: GestureDetector( + onTap: () { + setState(() => _neverShowAgainCheck = !_neverShowAgainCheck); + }, + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + CircularCheckBox( + value: _neverShowAgainCheck, + onChanged: (value) { + setState(() => _neverShowAgainCheck = value); + }, + activeColor: Colors.blue, + ), + SizedBox(width: 8), + Text( + 'never_show_again'.tr(), + textScaleFactor: 0.9, + ), + ], + ), + ), + ) + ], + ), + ), ), actions: [ FlatButton( diff --git a/lib/screens/main/home/widgets/targeting_game.dart b/lib/screens/main/home/widgets/targeting_game.dart index 93b85d3..1c449c3 100644 --- a/lib/screens/main/home/widgets/targeting_game.dart +++ b/lib/screens/main/home/widgets/targeting_game.dart @@ -104,7 +104,7 @@ class _TargetingGameState extends State { timer2 = Timer.periodic(Duration(milliseconds: 1000), (_) { if (count == 0) { - FlutterBeep.beep(); + FlutterBeep.beep(false); } }); diff --git a/lib/screens/main/measurements/measurements_screen.dart b/lib/screens/main/measurements/measurements_screen.dart index 02b997a..2fec1b0 100644 --- a/lib/screens/main/measurements/measurements_screen.dart +++ b/lib/screens/main/measurements/measurements_screen.dart @@ -13,7 +13,14 @@ import 'package:provider/provider.dart'; import 'package:balance_app/routes.dart'; import 'package:easy_localization/easy_localization.dart'; -class MeasurementsScreen extends StatelessWidget { +class MeasurementsScreen extends StatefulWidget { + MeasurementsScreen(); + + @override + _MeasurementsScreenState createState() => _MeasurementsScreenState(); +} + +class _MeasurementsScreenState extends State { @override Widget build(BuildContext context) { return BlocProvider( @@ -29,7 +36,7 @@ class MeasurementsScreen extends StatelessWidget { return ListView.builder( padding: EdgeInsets.symmetric(vertical: 8), itemBuilder: (context, index) => - _measurementItemTemplate(context, state.tests[index]), + _measurementItemTemplate(context,state.tests[index]), itemCount: state.tests.length, ); } @@ -160,22 +167,25 @@ class MeasurementsScreen extends StatelessWidget { FlatButton( minWidth: 30.0, height: 30.0, - color: (test?.invalid ?? false) ? Colors.red : Colors.green, + color: test.invalid ? Colors.red : Colors.green, splashColor: Colors.grey, onPressed: () { - showMeasurementDialog(context, test?.invalid ?? false); + showMeasurementDialog(context, test.invalid ?? false); }, - child: (test?.invalid ?? false) ? Icon(Icons.priority_high) : Icon(Icons.check), + child: test.invalid ? Icon(Icons.priority_high) : Icon(Icons.check), ), FlatButton( minWidth: 30.0, height: 30.0, - color: (test?.invalid ?? false) ? Colors.red : Colors.green, + color: test.sent ? Colors.green : Colors.red , splashColor: Colors.grey, - onPressed: () { - showBackendStatusDialog(context, test?.invalid ?? false); + onPressed: () async { + bool result = await showBackendStatusDialog(context, test.sent, test.id); + if (result) + BlocProvider.of(context).add( + MeasurementsEvents.fetch); }, - child: (test?.invalid ?? false) ? Icon(Icons.signal_wifi_off) : Icon(Icons.wifi), + child: test.sent ? Icon(Icons.wifi) : Icon(Icons.signal_wifi_off), ), ] ), diff --git a/lib/screens/main/measurements/widgets/backend_status_dialog.dart b/lib/screens/main/measurements/widgets/backend_status_dialog.dart index 3bcce45..7e0395b 100644 --- a/lib/screens/main/measurements/widgets/backend_status_dialog.dart +++ b/lib/screens/main/measurements/widgets/backend_status_dialog.dart @@ -1,18 +1,27 @@ +import 'dart:async'; +import 'dart:convert'; +import 'dart:io'; + +import 'package:balance_app/floor/measurement_database.dart'; +import 'package:balance_app/manager/preference_manager.dart'; +import 'package:balance_app/model/measurement.dart'; +import 'package:balance_app/model/raw_measurement_data.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; +import 'package:http/http.dart'; /// Show a dialog to ask the user if he wants to close the app during the test. /// /// This method return a [Future] of [bool] used by the method [didPopRoute] /// to know if the app should be closed or not. -Future showBackendStatusDialog(BuildContext context, bool valid) { - if (!valid) +Future showBackendStatusDialog(BuildContext context, bool valid, int id) { + if (valid) return showDialog( context: context, builder: (context) => AlertDialog( - title: Text('Misurazione inviata correttamente al server'), - content: Text('La tua misurazione é stata registrata correttamente nel server. Stai contribuendo attivamente alla ricerca!'), + title: Text('test_backend_ok_title'.tr()), + content: Text('test_backend_ok_txt'.tr()), actions: [ // Stop the test and close the app FlatButton( @@ -29,18 +38,51 @@ Future showBackendStatusDialog(BuildContext context, bool valid) { return showDialog( context: context, builder: (context) => AlertDialog( - title: Text('La misurazione non ha raggiunto il backend'), - content: Text('La misurazione non ha raggiunto il backend. Per farlo devi attivare la connessione alla rete internet. Se non invii i risultati della tua misurazione non contribuirai alla ricerca ma solo a te stesso.'), + title: Text('test_backend_wrong_title'.tr()), + content: Text('test_backend_wrong_txt'.tr()), actions: [ - // Stop the test and close the app - FlatButton( - onPressed: () { - // Close the dialog and the app - Navigator.pop(context, false); - }, - child: Text('close'.tr()), + Row( + children: [ + // Try sending measurement + FlatButton( + onPressed: () async { + // Close the dialog and the app + Navigator.pop(context, await _makePostRequest(id)); + }, + child: Text('Ritenta invio'), + ), + ] ), ], ) ); +} + +Future _makePostRequest(measurementId) async { + // set up POST request arguments + String url_measurement = 'https://www.balancemobile.it/api/v1/db/measurement'; + //String url_measurement = 'http://192.168.1.206:8000/api/v1/db/sway'; + Map headers = {"Content-type": "application/json"}; + + final database = await MeasurementDatabase.getDatabase(); + var measurement = await database.measurementDao.findMeasurementById(measurementId); + + try { + Measurement newMeasurement = Measurement.sent(measurement, true); + Response response = await post(url_measurement, headers: headers, body: jsonEncode(newMeasurement)).timeout(Duration(seconds: 5)); + + if (response.statusCode == 200) { + await database.measurementDao.updateMeasurement(newMeasurement); + return true; + } else { + print("_SendingData.RawMeasurement: The server answered with: "+response.statusCode.toString()); + return false; + } + } on TimeoutException catch (_) { + print("_SendingData.RawMeasurement: The connection dropped, maybe the server is congested"); + return false; + } on SocketException catch (_) { + print("_SendingData.RawMeasurement: Communication failed. The server was not reachable"); + return false; + } } \ No newline at end of file diff --git a/lib/screens/main/measurements/widgets/wrong_measurement_dialog.dart b/lib/screens/main/measurements/widgets/wrong_measurement_dialog.dart index 09cb3e3..ec96bc6 100644 --- a/lib/screens/main/measurements/widgets/wrong_measurement_dialog.dart +++ b/lib/screens/main/measurements/widgets/wrong_measurement_dialog.dart @@ -11,8 +11,8 @@ Future showMeasurementDialog(BuildContext context, bool valid) { return showDialog( context: context, builder: (context) => AlertDialog( - title: Text('Misurazione eseguita correttamente'), - content: Text('Congratulazioni! Quando esegui una misurazione verifichiamo i tuoi parametri. Alcuni di questi forniscono un\'indicazione riguardante la corretta esecuzione e sembra sia andato tutto bene!'), + title: Text('test_measurement_ok_title'.tr()), + content: Text('test_measurement_ok_txt'.tr()), actions: [ // Stop the test and close the app FlatButton( @@ -29,8 +29,8 @@ Future showMeasurementDialog(BuildContext context, bool valid) { return showDialog( context: context, builder: (context) => AlertDialog( - title: Text('Misurazione Sospetta'), - content: Text('Questa misurazione presenta dei risultati irregolari. Se non hai eseguito il test correttamente, effettualo nuovamente. Se il problema persiste, contatta i ricercatori.'), + title: Text('test_measurement_wrong_title'.tr()), + content: Text('test_measurement_wrong_txt'.tr()), actions: [ // Stop the test and close the app FlatButton( diff --git a/lib/screens/main/settings/settings_screen.dart b/lib/screens/main/settings/settings_screen.dart index a440d48..aae6156 100644 --- a/lib/screens/main/settings/settings_screen.dart +++ b/lib/screens/main/settings/settings_screen.dart @@ -66,24 +66,35 @@ class _SettingsScreenState extends State { ] ), SettingsGroup( - title: 'version_txt'.tr(), - children: [ - SettingsElement( - icon: Icon(BIcons.version), - text: "${'version_txt'.tr()} ${packageInfo?.version} (${'build_txt'.tr()}${packageInfo?.buildNumber})", - onLongPress: () { - Scaffold.of(context) - .showSnackBar( - SnackBar( - behavior: SnackBarBehavior.floating, - content: Text('easter_egg_txt'.tr()), - duration: Duration(seconds: 2), - ) - ); - } - ), - ] + title: 'report_title'.tr(), + children: [ + SettingsElement( + icon: Icon(BIcons.version), + text: "report_issue_title".tr(), + //text: "${'version_txt'.tr()} ${packageInfo?.version} (${'build_txt'.tr()}${packageInfo?.buildNumber})", + onTap: () => Navigator.of(context).pushNamed(Routes.issues), + ), + ] ), + //SettingsGroup( + // title: 'version_txt'.tr(), + // children: [ + // SettingsElement( + // icon: Icon(BIcons.version), + // text: "${'version_txt'.tr()} ${packageInfo?.version} (${'build_txt'.tr()}${packageInfo?.buildNumber})", + // onLongPress: () { + // Scaffold.of(context) + // .showSnackBar( + // SnackBar( + // behavior: SnackBarBehavior.floating, + // content: Text('easter_egg_txt'.tr()), + // duration: Duration(seconds: 2), + // ) + // ); + // } + // ), + // ] + //), ] ); } diff --git a/pubspec.yaml b/pubspec.yaml index 9fb0776..62fe137 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,7 +1,7 @@ # Flutter app name: balance_app description: Flutter application to measure you posture! -version: 1.0.0+5 +version: 1.0.0+1 environment: sdk: ">=2.6.0 <3.0.0" @@ -89,6 +89,7 @@ flutter: assets: # App Logo - assets/app_logo_circle.png + - assets/app_logo_circle_broken.png - assets/app_logo_circle_grey.png - assets/app_logo.png # Partners @@ -105,8 +106,16 @@ flutter: - assets/images/tutorial.png - assets/images/appstore_logo.png - assets/images/open_source.png + - assets/images/reading.png + - assets/images/walking.png + - assets/images/working.png + - assets/images/drinking.png + - assets/images/eating.png + - assets/images/sleeping.png + - assets/images/sport.png # Sounds - - assets/sounds/light_pop.mp3 + - assets/sounds/light_pop_bigdsc.mp3 + - assets/sounds/finish_bigdsc.mp3 # Translations - assets/translations/en.json - assets/translations/it.json