mirror of
https://github.com/twonlyapp/twonly-app.git
synced 2026-01-15 11:18:41 +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
|
||||
- connectivity_plus (0.0.1):
|
||||
- Flutter
|
||||
- device_info_plus (0.0.1):
|
||||
- Flutter
|
||||
- Firebase (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`)
|
||||
- camera_avfoundation (from `.symlinks/plugins/camera_avfoundation/ios`)
|
||||
- connectivity_plus (from `.symlinks/plugins/connectivity_plus/ios`)
|
||||
- device_info_plus (from `.symlinks/plugins/device_info_plus/ios`)
|
||||
- Firebase
|
||||
- firebase_core (from `.symlinks/plugins/firebase_core/ios`)
|
||||
- firebase_messaging (from `.symlinks/plugins/firebase_messaging/ios`)
|
||||
|
|
@ -291,6 +294,8 @@ EXTERNAL SOURCES:
|
|||
:path: ".symlinks/plugins/camera_avfoundation/ios"
|
||||
connectivity_plus:
|
||||
:path: ".symlinks/plugins/connectivity_plus/ios"
|
||||
device_info_plus:
|
||||
:path: ".symlinks/plugins/device_info_plus/ios"
|
||||
firebase_core:
|
||||
:path: ".symlinks/plugins/firebase_core/ios"
|
||||
firebase_messaging:
|
||||
|
|
@ -344,6 +349,7 @@ SPEC CHECKSUMS:
|
|||
background_downloader: 50e91d979067b82081aba359d7d916b3ba5fadad
|
||||
camera_avfoundation: be3be85408cd4126f250386828e9b1dfa40ab436
|
||||
connectivity_plus: cb623214f4e1f6ef8fe7403d580fdad517d2f7dd
|
||||
device_info_plus: 21fcca2080fbcd348be798aa36c3e5ed849eefbe
|
||||
Firebase: 1fe1c0a7d9aaea32efe01fbea5f0ebd8d70e53a2
|
||||
firebase_core: ba71b44041571da878cb624ce0d80250bcbe58ad
|
||||
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.",
|
||||
"settingsHelp": "Hilfe",
|
||||
"settingsHelpFAQ": "FAQ",
|
||||
"feedbackTooltip": "Feedback zur Verbesserung von twonly geben.",
|
||||
"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",
|
||||
"settingsHelpVersion": "Version",
|
||||
"settingsHelpLicenses": "Lizenzen (Source-Code)",
|
||||
|
|
@ -153,6 +171,7 @@
|
|||
"undo": "Rückgängig",
|
||||
"redo": "Wiederholen",
|
||||
"next": "Weiter",
|
||||
"submit": "Abschicken",
|
||||
"close": "Schließen",
|
||||
"cancel": "Abbrechen",
|
||||
"ok": "Ok",
|
||||
|
|
|
|||
|
|
@ -213,6 +213,7 @@
|
|||
"@settingsHelpDiagnostics": {},
|
||||
"settingsHelpFAQ": "FAQ",
|
||||
"@settingsHelpFAQ": {},
|
||||
"feedbackTooltip": "Give Feedback to improve twonly.",
|
||||
"settingsHelpContactUs": "Contact us",
|
||||
"@settingsHelpContactUs": {},
|
||||
"settingsHelpVersion": "Version",
|
||||
|
|
@ -222,6 +223,23 @@
|
|||
"settingsHelpCredits": "Licenses (Images)",
|
||||
"@settingsHelpCredits": {},
|
||||
"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",
|
||||
"settingsAppearanceTheme": "Theme",
|
||||
"@settingsAppearanceTheme": {},
|
||||
|
|
@ -276,6 +294,7 @@
|
|||
"undo": "Undo",
|
||||
"redo": "Redo",
|
||||
"next": "Next",
|
||||
"submit": "Submit",
|
||||
"close": "Close",
|
||||
"disable": "Disable",
|
||||
"enable": "Enable",
|
||||
|
|
|
|||
|
|
@ -734,6 +734,12 @@ abstract class AppLocalizations {
|
|||
/// **'FAQ'**
|
||||
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.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
|
|
@ -764,6 +770,108 @@ abstract class AppLocalizations {
|
|||
/// **'Imprint & Privacy Policy'**
|
||||
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.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
|
|
@ -920,6 +1028,12 @@ abstract class AppLocalizations {
|
|||
/// **'Next'**
|
||||
String get next;
|
||||
|
||||
/// No description provided for @submit.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Submit'**
|
||||
String get submit;
|
||||
|
||||
/// No description provided for @close.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
|
|
|
|||
|
|
@ -357,6 +357,9 @@ class AppLocalizationsDe extends AppLocalizations {
|
|||
@override
|
||||
String get settingsHelpFAQ => 'FAQ';
|
||||
|
||||
@override
|
||||
String get feedbackTooltip => 'Feedback zur Verbesserung von twonly geben.';
|
||||
|
||||
@override
|
||||
String get settingsHelpContactUs => 'Kontaktiere uns';
|
||||
|
||||
|
|
@ -372,6 +375,59 @@ class AppLocalizationsDe extends AppLocalizations {
|
|||
@override
|
||||
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
|
||||
String get settingsHelpTerms => 'Nutzungsbedingungen';
|
||||
|
||||
|
|
@ -465,6 +521,9 @@ class AppLocalizationsDe extends AppLocalizations {
|
|||
@override
|
||||
String get next => 'Weiter';
|
||||
|
||||
@override
|
||||
String get submit => 'Abschicken';
|
||||
|
||||
@override
|
||||
String get close => 'Schließen';
|
||||
|
||||
|
|
|
|||
|
|
@ -352,6 +352,9 @@ class AppLocalizationsEn extends AppLocalizations {
|
|||
@override
|
||||
String get settingsHelpFAQ => 'FAQ';
|
||||
|
||||
@override
|
||||
String get feedbackTooltip => 'Give Feedback to improve twonly.';
|
||||
|
||||
@override
|
||||
String get settingsHelpContactUs => 'Contact us';
|
||||
|
||||
|
|
@ -367,6 +370,59 @@ class AppLocalizationsEn extends AppLocalizations {
|
|||
@override
|
||||
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
|
||||
String get settingsHelpTerms => 'Terms of Service';
|
||||
|
||||
|
|
@ -460,6 +516,9 @@ class AppLocalizationsEn extends AppLocalizations {
|
|||
@override
|
||||
String get next => 'Next';
|
||||
|
||||
@override
|
||||
String get submit => 'Submit';
|
||||
|
||||
@override
|
||||
String get close => 'Close';
|
||||
|
||||
|
|
|
|||
|
|
@ -44,6 +44,9 @@ class UserData {
|
|||
@JsonKey(defaultValue: true)
|
||||
bool useHighQuality = true;
|
||||
|
||||
@JsonKey(defaultValue: true)
|
||||
bool showFeedbackShortcut = true;
|
||||
|
||||
List<String>? preSelectedEmojies;
|
||||
|
||||
Map<String, List<String>>? autoDownloadOptions;
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ UserData _$UserDataFromJson(Map<String, dynamic> json) => UserData(
|
|||
ThemeMode.system
|
||||
..defaultShowTime = (json['defaultShowTime'] as num?)?.toInt()
|
||||
..useHighQuality = json['useHighQuality'] as bool? ?? true
|
||||
..showFeedbackShortcut = json['showFeedbackShortcut'] as bool? ?? true
|
||||
..preSelectedEmojies = (json['preSelectedEmojies'] as List<dynamic>?)
|
||||
?.map((e) => e as String)
|
||||
.toList()
|
||||
|
|
@ -77,6 +78,7 @@ Map<String, dynamic> _$UserDataToJson(UserData instance) => <String, dynamic>{
|
|||
'themeMode': _$ThemeModeEnumMap[instance.themeMode]!,
|
||||
'defaultShowTime': instance.defaultShowTime,
|
||||
'useHighQuality': instance.useHighQuality,
|
||||
'showFeedbackShortcut': instance.showFeedbackShortcut,
|
||||
'preSelectedEmojies': instance.preSelectedEmojies,
|
||||
'autoDownloadOptions': instance.autoDownloadOptions,
|
||||
'storeMediaFilesInGallery': instance.storeMediaFilesInGallery,
|
||||
|
|
|
|||
|
|
@ -32,6 +32,17 @@ class Log {
|
|||
|
||||
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 {
|
||||
final directory = await getApplicationSupportDirectory();
|
||||
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:twonly/globals.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/connection_info.comp.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/media_viewer.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/chats/add_new_user.view.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
|
@ -40,6 +42,7 @@ class _ChatListViewState extends State<ChatListView> {
|
|||
GlobalKey firstUserListItemKey = GlobalKey();
|
||||
GlobalKey searchForOtherUsers = GlobalKey();
|
||||
Timer? tutorial;
|
||||
bool showFeedbackShortcut = false;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
|
|
@ -65,6 +68,13 @@ class _ChatListViewState extends State<ChatListView> {
|
|||
await showChatListTutorialContextMenu(context, firstUserListItemKey);
|
||||
}
|
||||
});
|
||||
|
||||
final user = await getUser();
|
||||
if (user != null) {
|
||||
setState(() {
|
||||
showFeedbackShortcut = user.showFeedbackShortcut;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
|
|
@ -105,6 +115,20 @@ class _ChatListViewState extends State<ChatListView> {
|
|||
),
|
||||
]),
|
||||
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(
|
||||
stream: twonlyDB.contactsDao.watchContactsRequested(),
|
||||
builder: (context, snapshot) {
|
||||
|
|
|
|||
|
|
@ -1,12 +1,34 @@
|
|||
import 'package:flutter/material.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/providers/settings.provider.dart';
|
||||
import 'package:twonly/src/utils/misc.dart';
|
||||
|
||||
class AppearanceView extends StatelessWidget {
|
||||
class AppearanceView extends StatefulWidget {
|
||||
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 {
|
||||
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
|
||||
Widget build(BuildContext context) {
|
||||
ThemeMode? selectedTheme =
|
||||
|
|
@ -73,6 +103,14 @@ class AppearanceView extends StatelessWidget {
|
|||
_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_secure_storage/flutter_secure_storage.dart';
|
||||
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/storage.dart';
|
||||
import 'package:twonly/src/views/settings/help/contact_us/submit_message.view.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
|
||||
class ContactUsView extends StatefulWidget {
|
||||
|
|
@ -14,94 +26,235 @@ class ContactUsView extends StatefulWidget {
|
|||
class _ContactUsState extends State<ContactUsView> {
|
||||
final TextEditingController _controller = TextEditingController();
|
||||
bool isLoading = false;
|
||||
bool includeDebugLog = true;
|
||||
int? _selectedFeedback;
|
||||
String? _selectedReason;
|
||||
String? debugLogDownloadToken;
|
||||
|
||||
Future<void> _submitFeedback() async {
|
||||
final String feedback = _controller.text;
|
||||
Future<String?> uploadDebugLog() async {
|
||||
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(() {
|
||||
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) {
|
||||
// Show a message if the text field is empty
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text('Please enter your message.')),
|
||||
);
|
||||
return;
|
||||
if (!mounted) return "";
|
||||
|
||||
// Get device information
|
||||
if (Theme.of(context).platform == TargetPlatform.android) {
|
||||
AndroidDeviceInfo androidInfo = await deviceInfo.androidInfo;
|
||||
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 (user == null) return;
|
||||
|
||||
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 (includeDebugLog) {
|
||||
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!')),
|
||||
);
|
||||
if (!mounted) return;
|
||||
}
|
||||
}
|
||||
|
||||
setState(() {
|
||||
isLoading = false;
|
||||
});
|
||||
|
||||
if (response.statusCode == 200) {
|
||||
// Handle successful response
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text('Feedback submitted successfully!')),
|
||||
);
|
||||
Navigator.pop(context);
|
||||
} else {
|
||||
// Handle error response
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text('Failed to submit feedback.')),
|
||||
);
|
||||
}
|
||||
return """
|
||||
$feedback
|
||||
|
||||
----------
|
||||
Reason: ${_selectedReason ?? "Other"}
|
||||
Locale: $locale
|
||||
Emoji: ${FeedbackEmojiRow.getEmoji(_selectedFeedback)}
|
||||
Device info: $phoneModel
|
||||
twonly Version: $appVersion
|
||||
twonly Package: $packageName
|
||||
$osVersion
|
||||
$debugLogToken
|
||||
""";
|
||||
}
|
||||
|
||||
@override
|
||||
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(
|
||||
appBar: AppBar(
|
||||
title: Text(context.lang.settingsHelpContactUs),
|
||||
),
|
||||
body: Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Column(
|
||||
child: ListView(
|
||||
children: [
|
||||
Text(context.lang.contactUsMessageTitle),
|
||||
SizedBox(height: 5),
|
||||
TextField(
|
||||
controller: _controller,
|
||||
decoration: InputDecoration(
|
||||
hintText: 'Your Feedback.',
|
||||
hintText: context.lang.contactUsYourMessage,
|
||||
border: OutlineInputBorder(),
|
||||
),
|
||||
minLines: 5,
|
||||
maxLines: 10,
|
||||
),
|
||||
Padding(
|
||||
padding: EdgeInsets.all(20),
|
||||
SizedBox(height: 5),
|
||||
Text(
|
||||
context.lang.contactUsMessage,
|
||||
textAlign: TextAlign.left,
|
||||
style: TextStyle(
|
||||
fontSize: 10,
|
||||
),
|
||||
),
|
||||
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/support"));
|
||||
launchUrl(Uri.parse("https://twonly.eu/en/faq/"));
|
||||
},
|
||||
child: Text(
|
||||
'Have you read our FAQ yet?',
|
||||
context.lang.contactUsFaq,
|
||||
style: TextStyle(
|
||||
color: Colors.blue,
|
||||
),
|
||||
),
|
||||
),
|
||||
ElevatedButton(
|
||||
onPressed: (isLoading) ? null : _submitFeedback,
|
||||
child: Text('Submit'),
|
||||
),
|
||||
],
|
||||
),
|
||||
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),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
|
@ -109,3 +262,128 @@ class _ContactUsState extends State<ContactUsView> {
|
|||
);
|
||||
}
|
||||
}
|
||||
|
||||
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 'dart:io';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:share_plus/share_plus.dart';
|
||||
import 'package:twonly/src/utils/log.dart';
|
||||
import 'package:twonly/src/utils/misc.dart';
|
||||
|
||||
class DiagnosticsView extends StatefulWidget {
|
||||
|
|
@ -29,7 +29,7 @@ class _DiagnosticsViewState extends State<DiagnosticsView> {
|
|||
return Scaffold(
|
||||
appBar: AppBar(title: const Text('Diagnostics')),
|
||||
body: FutureBuilder<String>(
|
||||
future: _loadLogFile(),
|
||||
future: loadLogFile(),
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.connectionState == ConnectionState.waiting) {
|
||||
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"
|
||||
source: hosted
|
||||
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:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
@ -1879,6 +1895,14 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
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:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
|
|||
|
|
@ -70,6 +70,7 @@ dependencies:
|
|||
background_downloader: ^9.2.2
|
||||
hashlib: ^2.0.0
|
||||
video_thumbnail: ^0.5.6
|
||||
device_info_plus: ^11.5.0
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
|
|
|
|||
Loading…
Reference in a new issue