mirror of
https://github.com/twonlyapp/twonly-app.git
synced 2026-01-15 16:28:40 +00:00
fix #228
This commit is contained in:
parent
94017615c5
commit
9a5114eb5d
16 changed files with 824 additions and 72 deletions
|
|
@ -5,6 +5,8 @@ PODS:
|
||||||
- Flutter
|
- Flutter
|
||||||
- connectivity_plus (0.0.1):
|
- connectivity_plus (0.0.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
|
- device_info_plus (0.0.1):
|
||||||
|
- Flutter
|
||||||
- Firebase (11.10.0):
|
- Firebase (11.10.0):
|
||||||
- Firebase/Core (= 11.10.0)
|
- Firebase/Core (= 11.10.0)
|
||||||
- Firebase/Core (11.10.0):
|
- Firebase/Core (11.10.0):
|
||||||
|
|
@ -232,6 +234,7 @@ DEPENDENCIES:
|
||||||
- background_downloader (from `.symlinks/plugins/background_downloader/ios`)
|
- background_downloader (from `.symlinks/plugins/background_downloader/ios`)
|
||||||
- camera_avfoundation (from `.symlinks/plugins/camera_avfoundation/ios`)
|
- camera_avfoundation (from `.symlinks/plugins/camera_avfoundation/ios`)
|
||||||
- connectivity_plus (from `.symlinks/plugins/connectivity_plus/ios`)
|
- connectivity_plus (from `.symlinks/plugins/connectivity_plus/ios`)
|
||||||
|
- device_info_plus (from `.symlinks/plugins/device_info_plus/ios`)
|
||||||
- Firebase
|
- Firebase
|
||||||
- firebase_core (from `.symlinks/plugins/firebase_core/ios`)
|
- firebase_core (from `.symlinks/plugins/firebase_core/ios`)
|
||||||
- firebase_messaging (from `.symlinks/plugins/firebase_messaging/ios`)
|
- firebase_messaging (from `.symlinks/plugins/firebase_messaging/ios`)
|
||||||
|
|
@ -291,6 +294,8 @@ EXTERNAL SOURCES:
|
||||||
:path: ".symlinks/plugins/camera_avfoundation/ios"
|
:path: ".symlinks/plugins/camera_avfoundation/ios"
|
||||||
connectivity_plus:
|
connectivity_plus:
|
||||||
:path: ".symlinks/plugins/connectivity_plus/ios"
|
:path: ".symlinks/plugins/connectivity_plus/ios"
|
||||||
|
device_info_plus:
|
||||||
|
:path: ".symlinks/plugins/device_info_plus/ios"
|
||||||
firebase_core:
|
firebase_core:
|
||||||
:path: ".symlinks/plugins/firebase_core/ios"
|
:path: ".symlinks/plugins/firebase_core/ios"
|
||||||
firebase_messaging:
|
firebase_messaging:
|
||||||
|
|
@ -344,6 +349,7 @@ SPEC CHECKSUMS:
|
||||||
background_downloader: 50e91d979067b82081aba359d7d916b3ba5fadad
|
background_downloader: 50e91d979067b82081aba359d7d916b3ba5fadad
|
||||||
camera_avfoundation: be3be85408cd4126f250386828e9b1dfa40ab436
|
camera_avfoundation: be3be85408cd4126f250386828e9b1dfa40ab436
|
||||||
connectivity_plus: cb623214f4e1f6ef8fe7403d580fdad517d2f7dd
|
connectivity_plus: cb623214f4e1f6ef8fe7403d580fdad517d2f7dd
|
||||||
|
device_info_plus: 21fcca2080fbcd348be798aa36c3e5ed849eefbe
|
||||||
Firebase: 1fe1c0a7d9aaea32efe01fbea5f0ebd8d70e53a2
|
Firebase: 1fe1c0a7d9aaea32efe01fbea5f0ebd8d70e53a2
|
||||||
firebase_core: ba71b44041571da878cb624ce0d80250bcbe58ad
|
firebase_core: ba71b44041571da878cb624ce0d80250bcbe58ad
|
||||||
firebase_messaging: 13129fe2ca166d1ed2d095062d76cee88943d067
|
firebase_messaging: 13129fe2ca166d1ed2d095062d76cee88943d067
|
||||||
|
|
|
||||||
|
|
@ -121,7 +121,25 @@
|
||||||
"settingsNotifyTroubleshootingNoProblemDesc": "Klicke auf OK, um eine Testbenachrichtigung zu erhalten. Wenn du auch nach 10 Minuten warten keine Nachricht erhältst, sende uns bitte dein Diagnoseprotokoll unter Einstellungen > Hilfe > Diagnoseprotokoll, damit wir uns das Problem ansehen können.",
|
"settingsNotifyTroubleshootingNoProblemDesc": "Klicke auf OK, um eine Testbenachrichtigung zu erhalten. Wenn du auch nach 10 Minuten warten keine Nachricht erhältst, sende uns bitte dein Diagnoseprotokoll unter Einstellungen > Hilfe > Diagnoseprotokoll, damit wir uns das Problem ansehen können.",
|
||||||
"settingsHelp": "Hilfe",
|
"settingsHelp": "Hilfe",
|
||||||
"settingsHelpFAQ": "FAQ",
|
"settingsHelpFAQ": "FAQ",
|
||||||
|
"feedbackTooltip": "Feedback zur Verbesserung von twonly geben.",
|
||||||
"settingsHelpContactUs": "Kontaktiere uns",
|
"settingsHelpContactUs": "Kontaktiere uns",
|
||||||
|
"contactUsFaq": "FAQ schon gelesen?",
|
||||||
|
"contactUsEmojis": "Wie fühlst du dich? (optional)",
|
||||||
|
"contactUsSelectOption": "Bitte wähle eine Option",
|
||||||
|
"contactUsReason": "Sag uns, warum du uns kontaktierst",
|
||||||
|
"contactUsMessage": "Wenn du eine Antwort erhalten möchtest, füge bitte deine E-Mail-Adresse hinzu, damit wir dich kontaktieren können.",
|
||||||
|
"contactUsYourMessage": "Deine Nachricht",
|
||||||
|
"contactUsMessageTitle": "Erzähl uns, was los ist",
|
||||||
|
"contactUsReasonNotWorking": "Etwas funktioniert nicht",
|
||||||
|
"contactUsReasonFeatureRequest": "Funktionsanfrage",
|
||||||
|
"contactUsReasonQuestion": "Frage",
|
||||||
|
"contactUsReasonFeedback": "Feedback",
|
||||||
|
"contactUsReasonOther": "Sonstiges",
|
||||||
|
"contactUsIncludeLog": "Debug-Protokoll anhängen.",
|
||||||
|
"contactUsWhatsThat": "Was ist das?",
|
||||||
|
"contactUsLastWarning": "Dies sind die Informationen, die an uns gesendet werden. Bitte prüfen Sie sie und klicke dann auf „Abschicken“.",
|
||||||
|
"contactUsSuccess": "Feedback erfolgreich übermittelt!",
|
||||||
|
"contactUsShortcut": "Feedback-Symbol ausblenden",
|
||||||
"settingsHelpDiagnostics": "Diagnoseprotokoll",
|
"settingsHelpDiagnostics": "Diagnoseprotokoll",
|
||||||
"settingsHelpVersion": "Version",
|
"settingsHelpVersion": "Version",
|
||||||
"settingsHelpLicenses": "Lizenzen (Source-Code)",
|
"settingsHelpLicenses": "Lizenzen (Source-Code)",
|
||||||
|
|
@ -153,6 +171,7 @@
|
||||||
"undo": "Rückgängig",
|
"undo": "Rückgängig",
|
||||||
"redo": "Wiederholen",
|
"redo": "Wiederholen",
|
||||||
"next": "Weiter",
|
"next": "Weiter",
|
||||||
|
"submit": "Abschicken",
|
||||||
"close": "Schließen",
|
"close": "Schließen",
|
||||||
"cancel": "Abbrechen",
|
"cancel": "Abbrechen",
|
||||||
"ok": "Ok",
|
"ok": "Ok",
|
||||||
|
|
|
||||||
|
|
@ -213,6 +213,7 @@
|
||||||
"@settingsHelpDiagnostics": {},
|
"@settingsHelpDiagnostics": {},
|
||||||
"settingsHelpFAQ": "FAQ",
|
"settingsHelpFAQ": "FAQ",
|
||||||
"@settingsHelpFAQ": {},
|
"@settingsHelpFAQ": {},
|
||||||
|
"feedbackTooltip": "Give Feedback to improve twonly.",
|
||||||
"settingsHelpContactUs": "Contact us",
|
"settingsHelpContactUs": "Contact us",
|
||||||
"@settingsHelpContactUs": {},
|
"@settingsHelpContactUs": {},
|
||||||
"settingsHelpVersion": "Version",
|
"settingsHelpVersion": "Version",
|
||||||
|
|
@ -222,6 +223,23 @@
|
||||||
"settingsHelpCredits": "Licenses (Images)",
|
"settingsHelpCredits": "Licenses (Images)",
|
||||||
"@settingsHelpCredits": {},
|
"@settingsHelpCredits": {},
|
||||||
"settingsHelpImprint": "Imprint & Privacy Policy",
|
"settingsHelpImprint": "Imprint & Privacy Policy",
|
||||||
|
"contactUsFaq": "Have you read our FAQ yet?",
|
||||||
|
"contactUsEmojis": "How do you feel? (optional)",
|
||||||
|
"contactUsSelectOption": "Please select an option",
|
||||||
|
"contactUsReason": "Tell us why you're reaching out",
|
||||||
|
"contactUsMessage": "If you want to receive an answer, please add your e-mail address so we can contact you.",
|
||||||
|
"contactUsYourMessage": "Your message",
|
||||||
|
"contactUsMessageTitle": "Tell us what's going on",
|
||||||
|
"contactUsReasonNotWorking": "Something's not working",
|
||||||
|
"contactUsReasonFeatureRequest": "Feature request",
|
||||||
|
"contactUsReasonQuestion": "Question",
|
||||||
|
"contactUsReasonFeedback": "Feedback",
|
||||||
|
"contactUsReasonOther": "Other",
|
||||||
|
"contactUsIncludeLog": "Include debug log",
|
||||||
|
"contactUsWhatsThat": "What's that?",
|
||||||
|
"contactUsLastWarning": "This are the information's which will be send to us. Please verify them and then press submit.",
|
||||||
|
"contactUsSuccess": "Feedback submitted successfully!",
|
||||||
|
"contactUsShortcut": "Hide Feedback Icon",
|
||||||
"settingsHelpTerms": "Terms of Service",
|
"settingsHelpTerms": "Terms of Service",
|
||||||
"settingsAppearanceTheme": "Theme",
|
"settingsAppearanceTheme": "Theme",
|
||||||
"@settingsAppearanceTheme": {},
|
"@settingsAppearanceTheme": {},
|
||||||
|
|
@ -276,6 +294,7 @@
|
||||||
"undo": "Undo",
|
"undo": "Undo",
|
||||||
"redo": "Redo",
|
"redo": "Redo",
|
||||||
"next": "Next",
|
"next": "Next",
|
||||||
|
"submit": "Submit",
|
||||||
"close": "Close",
|
"close": "Close",
|
||||||
"disable": "Disable",
|
"disable": "Disable",
|
||||||
"enable": "Enable",
|
"enable": "Enable",
|
||||||
|
|
|
||||||
|
|
@ -734,6 +734,12 @@ abstract class AppLocalizations {
|
||||||
/// **'FAQ'**
|
/// **'FAQ'**
|
||||||
String get settingsHelpFAQ;
|
String get settingsHelpFAQ;
|
||||||
|
|
||||||
|
/// No description provided for @feedbackTooltip.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Give Feedback to improve twonly.'**
|
||||||
|
String get feedbackTooltip;
|
||||||
|
|
||||||
/// No description provided for @settingsHelpContactUs.
|
/// No description provided for @settingsHelpContactUs.
|
||||||
///
|
///
|
||||||
/// In en, this message translates to:
|
/// In en, this message translates to:
|
||||||
|
|
@ -764,6 +770,108 @@ abstract class AppLocalizations {
|
||||||
/// **'Imprint & Privacy Policy'**
|
/// **'Imprint & Privacy Policy'**
|
||||||
String get settingsHelpImprint;
|
String get settingsHelpImprint;
|
||||||
|
|
||||||
|
/// No description provided for @contactUsFaq.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Have you read our FAQ yet?'**
|
||||||
|
String get contactUsFaq;
|
||||||
|
|
||||||
|
/// No description provided for @contactUsEmojis.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'How do you feel? (optional)'**
|
||||||
|
String get contactUsEmojis;
|
||||||
|
|
||||||
|
/// No description provided for @contactUsSelectOption.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Please select an option'**
|
||||||
|
String get contactUsSelectOption;
|
||||||
|
|
||||||
|
/// No description provided for @contactUsReason.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Tell us why you\'re reaching out'**
|
||||||
|
String get contactUsReason;
|
||||||
|
|
||||||
|
/// No description provided for @contactUsMessage.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'If you want to receive an answer, please add your e-mail address so we can contact you.'**
|
||||||
|
String get contactUsMessage;
|
||||||
|
|
||||||
|
/// No description provided for @contactUsYourMessage.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Your message'**
|
||||||
|
String get contactUsYourMessage;
|
||||||
|
|
||||||
|
/// No description provided for @contactUsMessageTitle.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Tell us what\'s going on'**
|
||||||
|
String get contactUsMessageTitle;
|
||||||
|
|
||||||
|
/// No description provided for @contactUsReasonNotWorking.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Something\'s not working'**
|
||||||
|
String get contactUsReasonNotWorking;
|
||||||
|
|
||||||
|
/// No description provided for @contactUsReasonFeatureRequest.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Feature request'**
|
||||||
|
String get contactUsReasonFeatureRequest;
|
||||||
|
|
||||||
|
/// No description provided for @contactUsReasonQuestion.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Question'**
|
||||||
|
String get contactUsReasonQuestion;
|
||||||
|
|
||||||
|
/// No description provided for @contactUsReasonFeedback.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Feedback'**
|
||||||
|
String get contactUsReasonFeedback;
|
||||||
|
|
||||||
|
/// No description provided for @contactUsReasonOther.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Other'**
|
||||||
|
String get contactUsReasonOther;
|
||||||
|
|
||||||
|
/// No description provided for @contactUsIncludeLog.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Include debug log'**
|
||||||
|
String get contactUsIncludeLog;
|
||||||
|
|
||||||
|
/// No description provided for @contactUsWhatsThat.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'What\'s that?'**
|
||||||
|
String get contactUsWhatsThat;
|
||||||
|
|
||||||
|
/// No description provided for @contactUsLastWarning.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'This are the information\'s which will be send to us. Please verify them and then press submit.'**
|
||||||
|
String get contactUsLastWarning;
|
||||||
|
|
||||||
|
/// No description provided for @contactUsSuccess.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Feedback submitted successfully!'**
|
||||||
|
String get contactUsSuccess;
|
||||||
|
|
||||||
|
/// No description provided for @contactUsShortcut.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Hide Feedback Icon'**
|
||||||
|
String get contactUsShortcut;
|
||||||
|
|
||||||
/// No description provided for @settingsHelpTerms.
|
/// No description provided for @settingsHelpTerms.
|
||||||
///
|
///
|
||||||
/// In en, this message translates to:
|
/// In en, this message translates to:
|
||||||
|
|
@ -920,6 +1028,12 @@ abstract class AppLocalizations {
|
||||||
/// **'Next'**
|
/// **'Next'**
|
||||||
String get next;
|
String get next;
|
||||||
|
|
||||||
|
/// No description provided for @submit.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Submit'**
|
||||||
|
String get submit;
|
||||||
|
|
||||||
/// No description provided for @close.
|
/// No description provided for @close.
|
||||||
///
|
///
|
||||||
/// In en, this message translates to:
|
/// In en, this message translates to:
|
||||||
|
|
|
||||||
|
|
@ -357,6 +357,9 @@ class AppLocalizationsDe extends AppLocalizations {
|
||||||
@override
|
@override
|
||||||
String get settingsHelpFAQ => 'FAQ';
|
String get settingsHelpFAQ => 'FAQ';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get feedbackTooltip => 'Feedback zur Verbesserung von twonly geben.';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get settingsHelpContactUs => 'Kontaktiere uns';
|
String get settingsHelpContactUs => 'Kontaktiere uns';
|
||||||
|
|
||||||
|
|
@ -372,6 +375,59 @@ class AppLocalizationsDe extends AppLocalizations {
|
||||||
@override
|
@override
|
||||||
String get settingsHelpImprint => 'Impressum & Datenschutzrichtlinie';
|
String get settingsHelpImprint => 'Impressum & Datenschutzrichtlinie';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get contactUsFaq => 'FAQ schon gelesen?';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get contactUsEmojis => 'Wie fühlst du dich? (optional)';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get contactUsSelectOption => 'Bitte wähle eine Option';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get contactUsReason => 'Sag uns, warum du uns kontaktierst';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get contactUsMessage =>
|
||||||
|
'Wenn du eine Antwort erhalten möchtest, füge bitte deine E-Mail-Adresse hinzu, damit wir dich kontaktieren können.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get contactUsYourMessage => 'Deine Nachricht';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get contactUsMessageTitle => 'Erzähl uns, was los ist';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get contactUsReasonNotWorking => 'Etwas funktioniert nicht';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get contactUsReasonFeatureRequest => 'Funktionsanfrage';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get contactUsReasonQuestion => 'Frage';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get contactUsReasonFeedback => 'Feedback';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get contactUsReasonOther => 'Sonstiges';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get contactUsIncludeLog => 'Debug-Protokoll anhängen.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get contactUsWhatsThat => 'Was ist das?';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get contactUsLastWarning =>
|
||||||
|
'Dies sind die Informationen, die an uns gesendet werden. Bitte prüfen Sie sie und klicke dann auf „Abschicken“.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get contactUsSuccess => 'Feedback erfolgreich übermittelt!';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get contactUsShortcut => 'Feedback-Symbol ausblenden';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get settingsHelpTerms => 'Nutzungsbedingungen';
|
String get settingsHelpTerms => 'Nutzungsbedingungen';
|
||||||
|
|
||||||
|
|
@ -465,6 +521,9 @@ class AppLocalizationsDe extends AppLocalizations {
|
||||||
@override
|
@override
|
||||||
String get next => 'Weiter';
|
String get next => 'Weiter';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get submit => 'Abschicken';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get close => 'Schließen';
|
String get close => 'Schließen';
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -352,6 +352,9 @@ class AppLocalizationsEn extends AppLocalizations {
|
||||||
@override
|
@override
|
||||||
String get settingsHelpFAQ => 'FAQ';
|
String get settingsHelpFAQ => 'FAQ';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get feedbackTooltip => 'Give Feedback to improve twonly.';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get settingsHelpContactUs => 'Contact us';
|
String get settingsHelpContactUs => 'Contact us';
|
||||||
|
|
||||||
|
|
@ -367,6 +370,59 @@ class AppLocalizationsEn extends AppLocalizations {
|
||||||
@override
|
@override
|
||||||
String get settingsHelpImprint => 'Imprint & Privacy Policy';
|
String get settingsHelpImprint => 'Imprint & Privacy Policy';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get contactUsFaq => 'Have you read our FAQ yet?';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get contactUsEmojis => 'How do you feel? (optional)';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get contactUsSelectOption => 'Please select an option';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get contactUsReason => 'Tell us why you\'re reaching out';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get contactUsMessage =>
|
||||||
|
'If you want to receive an answer, please add your e-mail address so we can contact you.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get contactUsYourMessage => 'Your message';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get contactUsMessageTitle => 'Tell us what\'s going on';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get contactUsReasonNotWorking => 'Something\'s not working';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get contactUsReasonFeatureRequest => 'Feature request';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get contactUsReasonQuestion => 'Question';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get contactUsReasonFeedback => 'Feedback';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get contactUsReasonOther => 'Other';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get contactUsIncludeLog => 'Include debug log';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get contactUsWhatsThat => 'What\'s that?';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get contactUsLastWarning =>
|
||||||
|
'This are the information\'s which will be send to us. Please verify them and then press submit.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get contactUsSuccess => 'Feedback submitted successfully!';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get contactUsShortcut => 'Hide Feedback Icon';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get settingsHelpTerms => 'Terms of Service';
|
String get settingsHelpTerms => 'Terms of Service';
|
||||||
|
|
||||||
|
|
@ -460,6 +516,9 @@ class AppLocalizationsEn extends AppLocalizations {
|
||||||
@override
|
@override
|
||||||
String get next => 'Next';
|
String get next => 'Next';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get submit => 'Submit';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get close => 'Close';
|
String get close => 'Close';
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -44,6 +44,9 @@ class UserData {
|
||||||
@JsonKey(defaultValue: true)
|
@JsonKey(defaultValue: true)
|
||||||
bool useHighQuality = true;
|
bool useHighQuality = true;
|
||||||
|
|
||||||
|
@JsonKey(defaultValue: true)
|
||||||
|
bool showFeedbackShortcut = true;
|
||||||
|
|
||||||
List<String>? preSelectedEmojies;
|
List<String>? preSelectedEmojies;
|
||||||
|
|
||||||
Map<String, List<String>>? autoDownloadOptions;
|
Map<String, List<String>>? autoDownloadOptions;
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,7 @@ UserData _$UserDataFromJson(Map<String, dynamic> json) => UserData(
|
||||||
ThemeMode.system
|
ThemeMode.system
|
||||||
..defaultShowTime = (json['defaultShowTime'] as num?)?.toInt()
|
..defaultShowTime = (json['defaultShowTime'] as num?)?.toInt()
|
||||||
..useHighQuality = json['useHighQuality'] as bool? ?? true
|
..useHighQuality = json['useHighQuality'] as bool? ?? true
|
||||||
|
..showFeedbackShortcut = json['showFeedbackShortcut'] as bool? ?? true
|
||||||
..preSelectedEmojies = (json['preSelectedEmojies'] as List<dynamic>?)
|
..preSelectedEmojies = (json['preSelectedEmojies'] as List<dynamic>?)
|
||||||
?.map((e) => e as String)
|
?.map((e) => e as String)
|
||||||
.toList()
|
.toList()
|
||||||
|
|
@ -77,6 +78,7 @@ Map<String, dynamic> _$UserDataToJson(UserData instance) => <String, dynamic>{
|
||||||
'themeMode': _$ThemeModeEnumMap[instance.themeMode]!,
|
'themeMode': _$ThemeModeEnumMap[instance.themeMode]!,
|
||||||
'defaultShowTime': instance.defaultShowTime,
|
'defaultShowTime': instance.defaultShowTime,
|
||||||
'useHighQuality': instance.useHighQuality,
|
'useHighQuality': instance.useHighQuality,
|
||||||
|
'showFeedbackShortcut': instance.showFeedbackShortcut,
|
||||||
'preSelectedEmojies': instance.preSelectedEmojies,
|
'preSelectedEmojies': instance.preSelectedEmojies,
|
||||||
'autoDownloadOptions': instance.autoDownloadOptions,
|
'autoDownloadOptions': instance.autoDownloadOptions,
|
||||||
'storeMediaFilesInGallery': instance.storeMediaFilesInGallery,
|
'storeMediaFilesInGallery': instance.storeMediaFilesInGallery,
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,17 @@ class Log {
|
||||||
|
|
||||||
Mutex writeToLogGuard = Mutex();
|
Mutex writeToLogGuard = Mutex();
|
||||||
|
|
||||||
|
Future<String> loadLogFile() async {
|
||||||
|
final directory = await getApplicationSupportDirectory();
|
||||||
|
final logFile = File('${directory.path}/app.log');
|
||||||
|
|
||||||
|
if (await logFile.exists()) {
|
||||||
|
return await logFile.readAsString();
|
||||||
|
} else {
|
||||||
|
return 'Log file does not exist.';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> _writeLogToFile(LogRecord record) async {
|
Future<void> _writeLogToFile(LogRecord record) async {
|
||||||
final directory = await getApplicationSupportDirectory();
|
final directory = await getApplicationSupportDirectory();
|
||||||
final logFile = File('${directory.path}/app.log');
|
final logFile = File('${directory.path}/app.log');
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'package:twonly/globals.dart';
|
import 'package:twonly/globals.dart';
|
||||||
import 'package:twonly/src/services/api/media_download.dart';
|
import 'package:twonly/src/services/api/media_download.dart';
|
||||||
|
import 'package:twonly/src/utils/storage.dart';
|
||||||
import 'package:twonly/src/views/chats/chat_list_components/backup_notice.card.dart';
|
import 'package:twonly/src/views/chats/chat_list_components/backup_notice.card.dart';
|
||||||
import 'package:twonly/src/views/chats/chat_list_components/connection_info.comp.dart';
|
import 'package:twonly/src/views/chats/chat_list_components/connection_info.comp.dart';
|
||||||
import 'package:twonly/src/views/chats/chat_list_components/demo_user.card.dart';
|
import 'package:twonly/src/views/chats/chat_list_components/demo_user.card.dart';
|
||||||
|
|
@ -20,6 +21,7 @@ import 'package:twonly/src/views/camera/camera_send_to_view.dart';
|
||||||
import 'package:twonly/src/views/chats/chat_messages.view.dart';
|
import 'package:twonly/src/views/chats/chat_messages.view.dart';
|
||||||
import 'package:twonly/src/views/chats/media_viewer.view.dart';
|
import 'package:twonly/src/views/chats/media_viewer.view.dart';
|
||||||
import 'package:twonly/src/views/chats/start_new_chat.view.dart';
|
import 'package:twonly/src/views/chats/start_new_chat.view.dart';
|
||||||
|
import 'package:twonly/src/views/settings/help/contact_us.view.dart';
|
||||||
import 'package:twonly/src/views/settings/settings_main.view.dart';
|
import 'package:twonly/src/views/settings/settings_main.view.dart';
|
||||||
import 'package:twonly/src/views/chats/add_new_user.view.dart';
|
import 'package:twonly/src/views/chats/add_new_user.view.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
@ -40,6 +42,7 @@ class _ChatListViewState extends State<ChatListView> {
|
||||||
GlobalKey firstUserListItemKey = GlobalKey();
|
GlobalKey firstUserListItemKey = GlobalKey();
|
||||||
GlobalKey searchForOtherUsers = GlobalKey();
|
GlobalKey searchForOtherUsers = GlobalKey();
|
||||||
Timer? tutorial;
|
Timer? tutorial;
|
||||||
|
bool showFeedbackShortcut = false;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
|
|
@ -65,6 +68,13 @@ class _ChatListViewState extends State<ChatListView> {
|
||||||
await showChatListTutorialContextMenu(context, firstUserListItemKey);
|
await showChatListTutorialContextMenu(context, firstUserListItemKey);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
final user = await getUser();
|
||||||
|
if (user != null) {
|
||||||
|
setState(() {
|
||||||
|
showFeedbackShortcut = user.showFeedbackShortcut;
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|
@ -105,6 +115,20 @@ class _ChatListViewState extends State<ChatListView> {
|
||||||
),
|
),
|
||||||
]),
|
]),
|
||||||
actions: [
|
actions: [
|
||||||
|
if (showFeedbackShortcut)
|
||||||
|
IconButton(
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.push(
|
||||||
|
context,
|
||||||
|
MaterialPageRoute(
|
||||||
|
builder: (context) => ContactUsView(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
color: Colors.grey,
|
||||||
|
tooltip: context.lang.feedbackTooltip,
|
||||||
|
icon: FaIcon(FontAwesomeIcons.commentDots, size: 19),
|
||||||
|
),
|
||||||
StreamBuilder(
|
StreamBuilder(
|
||||||
stream: twonlyDB.contactsDao.watchContactsRequested(),
|
stream: twonlyDB.contactsDao.watchContactsRequested(),
|
||||||
builder: (context, snapshot) {
|
builder: (context, snapshot) {
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,34 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
import 'package:twonly/src/utils/storage.dart';
|
||||||
import 'package:twonly/src/views/components/radio_button.dart';
|
import 'package:twonly/src/views/components/radio_button.dart';
|
||||||
import 'package:twonly/src/providers/settings.provider.dart';
|
import 'package:twonly/src/providers/settings.provider.dart';
|
||||||
import 'package:twonly/src/utils/misc.dart';
|
import 'package:twonly/src/utils/misc.dart';
|
||||||
|
|
||||||
class AppearanceView extends StatelessWidget {
|
class AppearanceView extends StatefulWidget {
|
||||||
const AppearanceView({super.key});
|
const AppearanceView({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<AppearanceView> createState() => _AppearanceViewState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _AppearanceViewState extends State<AppearanceView> {
|
||||||
|
bool showFeedbackShortcut = false;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
initAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future initAsync() async {
|
||||||
|
final user = await getUser();
|
||||||
|
if (user == null) return;
|
||||||
|
setState(() {
|
||||||
|
showFeedbackShortcut = user.showFeedbackShortcut;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
void _showSelectThemeMode(BuildContext context) async {
|
void _showSelectThemeMode(BuildContext context) async {
|
||||||
ThemeMode? selectedValue = context.read<SettingsChangeProvider>().themeMode;
|
ThemeMode? selectedValue = context.read<SettingsChangeProvider>().themeMode;
|
||||||
|
|
||||||
|
|
@ -55,6 +77,14 @@ class AppearanceView extends StatelessWidget {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void toggleShowFeedbackIcon() async {
|
||||||
|
await updateUserdata((u) {
|
||||||
|
u.showFeedbackShortcut = !u.showFeedbackShortcut;
|
||||||
|
return u;
|
||||||
|
});
|
||||||
|
initAsync();
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
ThemeMode? selectedTheme =
|
ThemeMode? selectedTheme =
|
||||||
|
|
@ -73,6 +103,14 @@ class AppearanceView extends StatelessWidget {
|
||||||
_showSelectThemeMode(context);
|
_showSelectThemeMode(context);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
ListTile(
|
||||||
|
title: Text(context.lang.contactUsShortcut),
|
||||||
|
onTap: toggleShowFeedbackIcon,
|
||||||
|
trailing: Checkbox(
|
||||||
|
value: !showFeedbackShortcut,
|
||||||
|
onChanged: (a) => toggleShowFeedbackIcon(),
|
||||||
|
),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,19 @@
|
||||||
|
import 'dart:convert';
|
||||||
|
import 'dart:typed_data';
|
||||||
|
import 'package:device_info_plus/device_info_plus.dart';
|
||||||
|
import 'package:fixnum/fixnum.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
||||||
import 'package:http/http.dart' as http;
|
import 'package:http/http.dart' as http;
|
||||||
|
import 'package:package_info_plus/package_info_plus.dart';
|
||||||
|
import 'package:twonly/globals.dart';
|
||||||
|
import 'package:twonly/src/constants/secure_storage_keys.dart';
|
||||||
|
import 'package:twonly/src/model/protobuf/api/http/http_requests.pb.dart';
|
||||||
|
import 'package:twonly/src/services/api/media_upload.dart'
|
||||||
|
show createDownloadTokens, uint8ListToHex;
|
||||||
|
import 'package:twonly/src/utils/log.dart';
|
||||||
import 'package:twonly/src/utils/misc.dart';
|
import 'package:twonly/src/utils/misc.dart';
|
||||||
import 'package:twonly/src/utils/storage.dart';
|
import 'package:twonly/src/views/settings/help/contact_us/submit_message.view.dart';
|
||||||
import 'package:url_launcher/url_launcher.dart';
|
import 'package:url_launcher/url_launcher.dart';
|
||||||
|
|
||||||
class ContactUsView extends StatefulWidget {
|
class ContactUsView extends StatefulWidget {
|
||||||
|
|
@ -14,98 +26,364 @@ class ContactUsView extends StatefulWidget {
|
||||||
class _ContactUsState extends State<ContactUsView> {
|
class _ContactUsState extends State<ContactUsView> {
|
||||||
final TextEditingController _controller = TextEditingController();
|
final TextEditingController _controller = TextEditingController();
|
||||||
bool isLoading = false;
|
bool isLoading = false;
|
||||||
|
bool includeDebugLog = true;
|
||||||
|
int? _selectedFeedback;
|
||||||
|
String? _selectedReason;
|
||||||
|
String? debugLogDownloadToken;
|
||||||
|
|
||||||
Future<void> _submitFeedback() async {
|
Future<String?> uploadDebugLog() async {
|
||||||
final String feedback = _controller.text;
|
if (debugLogDownloadToken != null) return debugLogDownloadToken;
|
||||||
|
Uint8List downloadToken = createDownloadTokens(1)[0];
|
||||||
|
|
||||||
|
String debugLog = await loadLogFile();
|
||||||
|
|
||||||
|
var messageOnSuccess = TextMessage()
|
||||||
|
..body = []
|
||||||
|
..userId = Int64(0);
|
||||||
|
|
||||||
|
final uploadRequest = UploadRequest(
|
||||||
|
messagesOnSuccess: [messageOnSuccess],
|
||||||
|
downloadTokens: [downloadToken],
|
||||||
|
encryptedData: debugLog.codeUnits,
|
||||||
|
);
|
||||||
|
|
||||||
|
final uploadRequestBytes = uploadRequest.writeToBuffer();
|
||||||
|
|
||||||
|
String? apiAuthTokenRaw =
|
||||||
|
await FlutterSecureStorage().read(key: SecureStorageKeys.apiAuthToken);
|
||||||
|
if (apiAuthTokenRaw == null) {
|
||||||
|
Log.error("api auth token not defined.");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
String apiAuthToken = uint8ListToHex(base64Decode(apiAuthTokenRaw));
|
||||||
|
|
||||||
|
String apiUrl =
|
||||||
|
"http${apiService.apiSecure}://${apiService.apiHost}/api/upload";
|
||||||
|
|
||||||
|
var requestMultipart = http.MultipartRequest(
|
||||||
|
"POST",
|
||||||
|
Uri.parse(apiUrl),
|
||||||
|
);
|
||||||
|
requestMultipart.headers['x-twonly-auth-token'] = apiAuthToken;
|
||||||
|
|
||||||
|
requestMultipart.files.add(http.MultipartFile.fromBytes(
|
||||||
|
"file",
|
||||||
|
uploadRequestBytes,
|
||||||
|
filename: "upload",
|
||||||
|
));
|
||||||
|
|
||||||
|
final response = await requestMultipart.send();
|
||||||
|
if (response.statusCode == 200) {
|
||||||
|
setState(() {
|
||||||
|
debugLogDownloadToken = uint8ListToHex(downloadToken);
|
||||||
|
});
|
||||||
|
return debugLogDownloadToken;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<String> _getFeedbackText() async {
|
||||||
setState(() {
|
setState(() {
|
||||||
isLoading = true;
|
isLoading = true;
|
||||||
});
|
});
|
||||||
|
String osVersion = '';
|
||||||
|
String locale = context.lang.localeName;
|
||||||
|
DeviceInfoPlugin deviceInfo = DeviceInfoPlugin();
|
||||||
|
String phoneModel = "";
|
||||||
|
PackageInfo packageInfo = await PackageInfo.fromPlatform();
|
||||||
|
String appVersion = packageInfo.version;
|
||||||
|
String packageName = packageInfo.packageName;
|
||||||
|
final String feedback = _controller.text;
|
||||||
|
String debugLogToken = "";
|
||||||
|
|
||||||
if (feedback.isEmpty) {
|
if (!mounted) return "";
|
||||||
// Show a message if the text field is empty
|
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
// Get device information
|
||||||
SnackBar(content: Text('Please enter your message.')),
|
if (Theme.of(context).platform == TargetPlatform.android) {
|
||||||
);
|
AndroidDeviceInfo androidInfo = await deviceInfo.androidInfo;
|
||||||
return;
|
osVersion = "Android version: ${androidInfo.version.release}";
|
||||||
|
phoneModel = " ${androidInfo.model} (${androidInfo.brand})";
|
||||||
|
} else if (Theme.of(context).platform == TargetPlatform.iOS) {
|
||||||
|
IosDeviceInfo iosInfo = await deviceInfo.iosInfo;
|
||||||
|
osVersion = "iOS version: ${iosInfo.utsname.release}";
|
||||||
|
phoneModel = " ${iosInfo.name}";
|
||||||
}
|
}
|
||||||
|
|
||||||
final user = await getUser();
|
if (includeDebugLog) {
|
||||||
if (user == null) return;
|
try {
|
||||||
|
final token = await uploadDebugLog();
|
||||||
|
if (token != null) {
|
||||||
|
debugLogToken =
|
||||||
|
"Debug Log: https://api.twonly.eu/api/download/$token";
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
SnackBar(content: Text('Could not upload the debug log!')),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
final feedbackFull = "${user.username}\n\n$feedback";
|
|
||||||
|
|
||||||
final response = await http.post(
|
|
||||||
Uri.parse('https://twonly.theconnectapp.de/subscribe.twonly.php'),
|
|
||||||
headers: <String, String>{
|
|
||||||
'Content-Type': 'application/x-www-form-urlencoded',
|
|
||||||
},
|
|
||||||
body: {
|
|
||||||
'feedback': feedbackFull,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
if (!mounted) return;
|
|
||||||
setState(() {
|
setState(() {
|
||||||
isLoading = false;
|
isLoading = false;
|
||||||
});
|
});
|
||||||
|
|
||||||
if (response.statusCode == 200) {
|
return """
|
||||||
// Handle successful response
|
$feedback
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
|
||||||
SnackBar(content: Text('Feedback submitted successfully!')),
|
----------
|
||||||
);
|
Reason: ${_selectedReason ?? "Other"}
|
||||||
Navigator.pop(context);
|
Locale: $locale
|
||||||
} else {
|
Emoji: ${FeedbackEmojiRow.getEmoji(_selectedFeedback)}
|
||||||
// Handle error response
|
Device info: $phoneModel
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
twonly Version: $appVersion
|
||||||
SnackBar(content: Text('Failed to submit feedback.')),
|
twonly Package: $packageName
|
||||||
);
|
$osVersion
|
||||||
}
|
$debugLogToken
|
||||||
|
""";
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
final List<String> _reasons = [
|
||||||
|
context.lang.contactUsReasonNotWorking,
|
||||||
|
context.lang.contactUsReasonFeatureRequest,
|
||||||
|
context.lang.contactUsReasonQuestion,
|
||||||
|
context.lang.contactUsReasonFeedback,
|
||||||
|
context.lang.contactUsReasonOther,
|
||||||
|
];
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
title: Text(context.lang.settingsHelpContactUs),
|
title: Text(context.lang.settingsHelpContactUs),
|
||||||
),
|
),
|
||||||
body: Padding(
|
body: Padding(
|
||||||
padding: const EdgeInsets.all(16.0),
|
padding: const EdgeInsets.all(16.0),
|
||||||
child: Column(
|
child: ListView(
|
||||||
children: [
|
children: [
|
||||||
|
Text(context.lang.contactUsMessageTitle),
|
||||||
|
SizedBox(height: 5),
|
||||||
TextField(
|
TextField(
|
||||||
controller: _controller,
|
controller: _controller,
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
hintText: 'Your Feedback.',
|
hintText: context.lang.contactUsYourMessage,
|
||||||
border: OutlineInputBorder(),
|
border: OutlineInputBorder(),
|
||||||
),
|
),
|
||||||
|
minLines: 5,
|
||||||
maxLines: 10,
|
maxLines: 10,
|
||||||
),
|
),
|
||||||
Padding(
|
SizedBox(height: 5),
|
||||||
padding: EdgeInsets.all(20),
|
Text(
|
||||||
child: Row(
|
context.lang.contactUsMessage,
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
textAlign: TextAlign.left,
|
||||||
children: [
|
style: TextStyle(
|
||||||
GestureDetector(
|
fontSize: 10,
|
||||||
onTap: () {
|
|
||||||
launchUrl(Uri.parse("https://twonly.eu/support"));
|
|
||||||
},
|
|
||||||
child: Text(
|
|
||||||
'Have you read our FAQ yet?',
|
|
||||||
style: TextStyle(
|
|
||||||
color: Colors.blue,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
ElevatedButton(
|
|
||||||
onPressed: (isLoading) ? null : _submitFeedback,
|
|
||||||
child: Text('Submit'),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
SizedBox(height: 20),
|
||||||
|
Text(context.lang.contactUsReason),
|
||||||
|
SizedBox(height: 5),
|
||||||
|
DropdownButton<String>(
|
||||||
|
hint: Text(context.lang.contactUsSelectOption),
|
||||||
|
underline: SizedBox.shrink(),
|
||||||
|
value: _selectedReason,
|
||||||
|
onChanged: (String? newValue) {
|
||||||
|
setState(() {
|
||||||
|
_selectedReason = newValue;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
items: _reasons.map<DropdownMenuItem<String>>((String reason) {
|
||||||
|
return DropdownMenuItem<String>(
|
||||||
|
value: reason,
|
||||||
|
child: Text(reason),
|
||||||
|
);
|
||||||
|
}).toList(),
|
||||||
|
),
|
||||||
|
SizedBox(height: 20),
|
||||||
|
Text(context.lang.contactUsEmojis),
|
||||||
|
SizedBox(height: 5),
|
||||||
|
FeedbackEmojiRow(
|
||||||
|
selectedFeedback: _selectedFeedback,
|
||||||
|
onFeedbackChanged: (int? newValue) {
|
||||||
|
setState(() {
|
||||||
|
_selectedFeedback = newValue;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
),
|
||||||
|
SizedBox(height: 20),
|
||||||
|
IncludeDebugLog(
|
||||||
|
isChecked: includeDebugLog,
|
||||||
|
onChanged: (value) {
|
||||||
|
setState(() {
|
||||||
|
includeDebugLog = value;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
bottomNavigationBar: Padding(
|
||||||
|
padding: EdgeInsets.symmetric(vertical: 40, horizontal: 40),
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
GestureDetector(
|
||||||
|
onTap: () {
|
||||||
|
launchUrl(Uri.parse("https://twonly.eu/en/faq/"));
|
||||||
|
},
|
||||||
|
child: Text(
|
||||||
|
context.lang.contactUsFaq,
|
||||||
|
style: TextStyle(
|
||||||
|
color: Colors.blue,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
ElevatedButton(
|
||||||
|
onPressed: (isLoading)
|
||||||
|
? null
|
||||||
|
: () async {
|
||||||
|
final fullMessage = await _getFeedbackText();
|
||||||
|
if (!context.mounted) return;
|
||||||
|
|
||||||
|
bool? feedbackSend = await Navigator.push(context,
|
||||||
|
MaterialPageRoute(builder: (context) {
|
||||||
|
return SubmitMessage(
|
||||||
|
fullMessage: fullMessage,
|
||||||
|
);
|
||||||
|
}));
|
||||||
|
|
||||||
|
if (feedbackSend == true && context.mounted) {
|
||||||
|
Navigator.pop(context);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
child: Text(context.lang.next),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class IncludeDebugLog extends StatefulWidget {
|
||||||
|
final bool isChecked;
|
||||||
|
final Function(bool) onChanged;
|
||||||
|
|
||||||
|
const IncludeDebugLog({
|
||||||
|
super.key,
|
||||||
|
required this.isChecked,
|
||||||
|
required this.onChanged,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<IncludeDebugLog> createState() => _IncludeDebugLogState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _IncludeDebugLogState extends State<IncludeDebugLog> {
|
||||||
|
void _launchURL() async {
|
||||||
|
const url = 'https://twonly.eu/en/faq/troubleshooting/debug-log.html';
|
||||||
|
if (await launchUrl(Uri.parse(url))) {
|
||||||
|
} else {
|
||||||
|
throw 'Could not launch $url';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Checkbox(
|
||||||
|
value: widget.isChecked,
|
||||||
|
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
|
||||||
|
onChanged: (bool? value) {
|
||||||
|
if (value != null) {
|
||||||
|
widget.onChanged(value);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
Text(context.lang.contactUsIncludeLog),
|
||||||
|
SizedBox(width: 20),
|
||||||
|
GestureDetector(
|
||||||
|
onTap: _launchURL,
|
||||||
|
child: Text(
|
||||||
|
context.lang.contactUsWhatsThat,
|
||||||
|
style: TextStyle(
|
||||||
|
color: Colors.blue,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class FeedbackEmojiRow extends StatelessWidget {
|
||||||
|
final int? selectedFeedback;
|
||||||
|
final ValueChanged<int?> onFeedbackChanged;
|
||||||
|
|
||||||
|
const FeedbackEmojiRow({
|
||||||
|
super.key,
|
||||||
|
required this.selectedFeedback,
|
||||||
|
required this.onFeedbackChanged,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
_buildEmojiButton(5, Icons.sentiment_very_satisfied),
|
||||||
|
_buildEmojiButton(4, Icons.sentiment_satisfied),
|
||||||
|
_buildEmojiButton(3, Icons.sentiment_neutral),
|
||||||
|
_buildEmojiButton(2, Icons.sentiment_dissatisfied),
|
||||||
|
_buildEmojiButton(1, Icons.sentiment_very_dissatisfied),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static String getEmoji(int? value) {
|
||||||
|
if (value == null) return "";
|
||||||
|
switch (value) {
|
||||||
|
case 5:
|
||||||
|
return '😄';
|
||||||
|
case 4:
|
||||||
|
return '😊';
|
||||||
|
case 3:
|
||||||
|
return '😐';
|
||||||
|
case 2:
|
||||||
|
return '😕';
|
||||||
|
case 1:
|
||||||
|
return '😞';
|
||||||
|
default:
|
||||||
|
return '❓';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildEmojiButton(int value, IconData icon) {
|
||||||
|
bool isSelected = selectedFeedback == value;
|
||||||
|
|
||||||
|
return GestureDetector(
|
||||||
|
onTap: () {
|
||||||
|
onFeedbackChanged(value);
|
||||||
|
},
|
||||||
|
child: Icon(
|
||||||
|
icon,
|
||||||
|
size: 40,
|
||||||
|
color: _getColorForValue(value, isSelected),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Color _getColorForValue(int value, bool isSelected) {
|
||||||
|
if (isSelected) {
|
||||||
|
if (value == 5) {
|
||||||
|
return Colors.greenAccent;
|
||||||
|
} else if (value > 1) {
|
||||||
|
return Colors.yellow;
|
||||||
|
} else {
|
||||||
|
return Colors.red;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return const Color.fromARGB(155, 134, 134, 134);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
106
lib/src/views/settings/help/contact_us/submit_message.view.dart
Normal file
106
lib/src/views/settings/help/contact_us/submit_message.view.dart
Normal file
|
|
@ -0,0 +1,106 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:http/http.dart' as http;
|
||||||
|
import 'package:twonly/src/utils/misc.dart';
|
||||||
|
|
||||||
|
class SubmitMessage extends StatefulWidget {
|
||||||
|
const SubmitMessage({super.key, required this.fullMessage});
|
||||||
|
|
||||||
|
final String fullMessage;
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<SubmitMessage> createState() => _ContactUsState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ContactUsState extends State<SubmitMessage> {
|
||||||
|
final TextEditingController _controller = TextEditingController();
|
||||||
|
bool isLoading = false;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_controller.text = widget.fullMessage;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _submitFeedback() async {
|
||||||
|
final String feedback = _controller.text;
|
||||||
|
setState(() {
|
||||||
|
isLoading = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (feedback.isEmpty) {
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
SnackBar(content: Text('Please enter your message.')),
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final response = await http.post(
|
||||||
|
Uri.parse('https://twonly.theconnectapp.de/subscribe.twonly.php'),
|
||||||
|
headers: <String, String>{
|
||||||
|
'Content-Type': 'application/x-www-form-urlencoded',
|
||||||
|
},
|
||||||
|
body: {
|
||||||
|
'feedback': feedback,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
if (!mounted) return;
|
||||||
|
setState(() {
|
||||||
|
isLoading = false;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.statusCode == 200) {
|
||||||
|
// Handle successful response
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
SnackBar(content: Text(context.lang.contactUsSuccess)),
|
||||||
|
);
|
||||||
|
Navigator.pop(context, true);
|
||||||
|
} else {
|
||||||
|
// Handle error response
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
SnackBar(content: Text('Failed to submit feedback.')),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Scaffold(
|
||||||
|
appBar: AppBar(
|
||||||
|
title: Text(context.lang.settingsHelpContactUs),
|
||||||
|
),
|
||||||
|
body: Padding(
|
||||||
|
padding: const EdgeInsets.all(16.0),
|
||||||
|
child: ListView(
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
context.lang.contactUsLastWarning,
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
),
|
||||||
|
SizedBox(height: 10),
|
||||||
|
TextField(
|
||||||
|
controller: _controller,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
hintText: context.lang.contactUsYourMessage,
|
||||||
|
border: OutlineInputBorder(),
|
||||||
|
),
|
||||||
|
minLines: 5,
|
||||||
|
maxLines: 20,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
bottomNavigationBar: Padding(
|
||||||
|
padding: EdgeInsets.symmetric(vertical: 40, horizontal: 40),
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
ElevatedButton(
|
||||||
|
onPressed: (isLoading) ? null : _submitFeedback,
|
||||||
|
child: Text(context.lang.submit),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'dart:io';
|
|
||||||
import 'package:path_provider/path_provider.dart';
|
import 'package:path_provider/path_provider.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:share_plus/share_plus.dart';
|
import 'package:share_plus/share_plus.dart';
|
||||||
|
import 'package:twonly/src/utils/log.dart';
|
||||||
import 'package:twonly/src/utils/misc.dart';
|
import 'package:twonly/src/utils/misc.dart';
|
||||||
|
|
||||||
class DiagnosticsView extends StatefulWidget {
|
class DiagnosticsView extends StatefulWidget {
|
||||||
|
|
@ -29,7 +29,7 @@ class _DiagnosticsViewState extends State<DiagnosticsView> {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(title: const Text('Diagnostics')),
|
appBar: AppBar(title: const Text('Diagnostics')),
|
||||||
body: FutureBuilder<String>(
|
body: FutureBuilder<String>(
|
||||||
future: _loadLogFile(),
|
future: loadLogFile(),
|
||||||
builder: (context, snapshot) {
|
builder: (context, snapshot) {
|
||||||
if (snapshot.connectionState == ConnectionState.waiting) {
|
if (snapshot.connectionState == ConnectionState.waiting) {
|
||||||
return const Center(child: CircularProgressIndicator());
|
return const Center(child: CircularProgressIndicator());
|
||||||
|
|
@ -109,15 +109,4 @@ class _DiagnosticsViewState extends State<DiagnosticsView> {
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<String> _loadLogFile() async {
|
|
||||||
final directory = await getApplicationSupportDirectory();
|
|
||||||
final logFile = File('${directory.path}/app.log');
|
|
||||||
|
|
||||||
if (await logFile.exists()) {
|
|
||||||
return await logFile.readAsString();
|
|
||||||
} else {
|
|
||||||
return 'Log file does not exist.';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
24
pubspec.lock
24
pubspec.lock
|
|
@ -337,6 +337,22 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.7.11"
|
version: "0.7.11"
|
||||||
|
device_info_plus:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: device_info_plus
|
||||||
|
sha256: "98f28b42168cc509abc92f88518882fd58061ea372d7999aecc424345c7bff6a"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "11.5.0"
|
||||||
|
device_info_plus_platform_interface:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: device_info_plus_platform_interface
|
||||||
|
sha256: e1ea89119e34903dca74b883d0dd78eb762814f97fb6c76f35e9ff74d261a18f
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "7.0.3"
|
||||||
dots_indicator:
|
dots_indicator:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
@ -1879,6 +1895,14 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "5.13.0"
|
version: "5.13.0"
|
||||||
|
win32_registry:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: win32_registry
|
||||||
|
sha256: "6f1b564492d0147b330dd794fee8f512cec4977957f310f9951b5f9d83618dae"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.1.0"
|
||||||
x25519:
|
x25519:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
|
||||||
|
|
@ -70,6 +70,7 @@ dependencies:
|
||||||
background_downloader: ^9.2.2
|
background_downloader: ^9.2.2
|
||||||
hashlib: ^2.0.0
|
hashlib: ^2.0.0
|
||||||
video_thumbnail: ^0.5.6
|
video_thumbnail: ^0.5.6
|
||||||
|
device_info_plus: ^11.5.0
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue