mirror of
https://github.com/twonlyapp/twonly-app.git
synced 2026-09-01 07:24:07 +00:00
remove user study
Some checks are pending
Flutter analyze & test / flutter_analyze_and_test (push) Waiting to run
Some checks are pending
Flutter analyze & test / flutter_analyze_and_test (push) Waiting to run
This commit is contained in:
parent
195f6091f2
commit
1fb6b3c94a
9 changed files with 9 additions and 573 deletions
|
|
@ -50,7 +50,6 @@ class Routes {
|
|||
static const String settingsHelpContactUs = '/settings/help/contact_us';
|
||||
static const String settingsHelpNews = '/settings/help/news';
|
||||
static const String settingsHelpDiagnostics = '/settings/help/diagnostics';
|
||||
static const String settingsHelpUserStudy = '/settings/help/user_study';
|
||||
static const String settingsHelpUserStudyQuestionnaire =
|
||||
'/settings/help/user_study/questionnaire';
|
||||
static const String settingsHelpCredits = '/settings/help/credits';
|
||||
|
|
|
|||
|
|
@ -174,21 +174,6 @@ class UserData {
|
|||
// Used for push notifcation via FCM.
|
||||
String? fcmToken;
|
||||
|
||||
// For my master thesis I want to create a anonymous user study:
|
||||
// - users in the "Tester" Plan can, if they want, take part of the user study
|
||||
|
||||
@JsonKey(defaultValue: false)
|
||||
bool askedForUserStudyPermission = false;
|
||||
|
||||
// So update data can be assigned. If set the user choose to participate.
|
||||
String? userStudyParticipantsToken;
|
||||
|
||||
@JsonKey(defaultValue: 0)
|
||||
int userStudyCountNewFriendsViaSuggestion = 0;
|
||||
|
||||
// Once a day the anonymous data is collected and send to the server
|
||||
DateTime? lastUserStudyDataUpload;
|
||||
|
||||
String? currentSetupPage;
|
||||
|
||||
@JsonKey(defaultValue: false)
|
||||
|
|
|
|||
|
|
@ -47,8 +47,6 @@ import 'package:twonly/src/visual/views/settings/profile/profile.view.dart';
|
|||
import 'package:twonly/src/visual/views/settings/settings_main.view.dart';
|
||||
import 'package:twonly/src/visual/views/settings/share_with_friends.view.dart';
|
||||
import 'package:twonly/src/visual/views/settings/subscription/subscription.view.dart';
|
||||
import 'package:twonly/src/visual/views/user_study/user_study_questionnaire.view.dart';
|
||||
import 'package:twonly/src/visual/views/user_study/user_study_welcome.view.dart';
|
||||
|
||||
final GlobalKey<NavigatorState> rootNavigatorKey = GlobalKey<NavigatorState>();
|
||||
|
||||
|
|
@ -268,19 +266,6 @@ final routerProvider = GoRouter(
|
|||
path: 'diagnostics',
|
||||
builder: (context, state) => const DiagnosticsView(),
|
||||
),
|
||||
GoRoute(
|
||||
path: 'user_study',
|
||||
builder: (context, state) => UserStudyWelcomeView(
|
||||
wasOpenedAutomatic: state.extra as bool? ?? false,
|
||||
),
|
||||
routes: [
|
||||
GoRoute(
|
||||
path: 'questionnaire',
|
||||
builder: (context, state) =>
|
||||
const UserStudyQuestionnaireView(),
|
||||
),
|
||||
],
|
||||
),
|
||||
GoRoute(
|
||||
path: 'credits',
|
||||
builder: (context, state) => const CreditsView(),
|
||||
|
|
|
|||
|
|
@ -44,7 +44,6 @@ import 'package:twonly/src/services/signal/utils.signal.dart';
|
|||
import 'package:twonly/src/services/subscription.service.dart';
|
||||
import 'package:twonly/src/services/user.service.dart';
|
||||
import 'package:twonly/src/services/user_discovery.service.dart';
|
||||
import 'package:twonly/src/services/user_study.service.dart';
|
||||
import 'package:twonly/src/utils/keyvalue.dart';
|
||||
import 'package:twonly/src/utils/log.dart';
|
||||
import 'package:twonly/src/utils/misc.dart';
|
||||
|
|
@ -147,11 +146,6 @@ class ApiService {
|
|||
|
||||
unawaited(UserDiscoveryService.checkForNewAnnouncedUsers());
|
||||
memoriesCloudService.init();
|
||||
|
||||
if (userService.currentUser.userStudyParticipantsToken != null) {
|
||||
// In case the user participates in the user study, call the handler after authenticated, to be sure there is a internet connection
|
||||
unawaited(handleUserStudyUpload());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,141 +0,0 @@
|
|||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:twonly/locator.dart';
|
||||
import 'package:twonly/src/database/tables/contacts.table.dart';
|
||||
import 'package:twonly/src/services/user.service.dart';
|
||||
import 'package:twonly/src/utils/keyvalue.dart';
|
||||
import 'package:twonly/src/utils/log.dart';
|
||||
import 'package:twonly/src/utils/misc.dart';
|
||||
|
||||
const userStudySurveyKey = 'user_study_survey';
|
||||
|
||||
// PLEASE DO NOT SPAM OR TRY SENDING DIRECTLY TO THIS URL!
|
||||
// You're just making my master's thesis more difficult and destroy scientific data. :/
|
||||
const surveyUrlBase = 'https://survey.twonly.org/upload.php';
|
||||
|
||||
Future<void> handleUserStudyUpload() async {
|
||||
try {
|
||||
final token = userService.currentUser.userStudyParticipantsToken;
|
||||
if (token == null) return;
|
||||
|
||||
// in case the survey was taken offline try again
|
||||
final userStudySurvey = await KeyValueStore.get(userStudySurveyKey);
|
||||
if (userStudySurvey != null) {
|
||||
final response = await http.post(
|
||||
Uri.parse('$surveyUrlBase/create/$token'),
|
||||
body: jsonEncode(userStudySurvey),
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
);
|
||||
if (response.statusCode != 200) {
|
||||
Log.warn(
|
||||
'Got different status code for survey upload: ${response.statusCode}',
|
||||
);
|
||||
return;
|
||||
}
|
||||
await KeyValueStore.delete(userStudySurveyKey);
|
||||
}
|
||||
|
||||
if (userService.currentUser.lastUserStudyDataUpload != null &&
|
||||
isToday(userService.currentUser.lastUserStudyDataUpload!)) {
|
||||
// Only send updates once a day.
|
||||
// This enables to see if improvements to actually work.
|
||||
return;
|
||||
}
|
||||
|
||||
final contacts = await twonlyDB.contactsDao.getAllContacts();
|
||||
final verifications = await twonlyDB.keyVerificationDao
|
||||
.getFirstVerificationTypeByContacts();
|
||||
final udVerifiedByContactsCount = await twonlyDB.keyVerificationDao
|
||||
.getTransferredTrustVerificationsCount();
|
||||
|
||||
final totalContactsWithVerificationBadge = await twonlyDB.keyVerificationDao
|
||||
.getCountOfContactsWithVerificationBadge();
|
||||
|
||||
final udFriendsShared = await twonlyDB.contactsDao
|
||||
.getContactsAnnouncedViaUserDiscovery();
|
||||
|
||||
final udAllAnnouncedUsers = await twonlyDB.userDiscoveryDao
|
||||
.getAllAnnouncedUsersWithRelations();
|
||||
|
||||
var udUnknownAnnouncedUsers = 0;
|
||||
|
||||
for (final udUser in udAllAnnouncedUsers.keys) {
|
||||
if (!contacts.any((c) => c.userId == udUser.announcedUserId)) {
|
||||
udUnknownAnnouncedUsers += 1;
|
||||
}
|
||||
}
|
||||
|
||||
final dataCollection = {
|
||||
'total_contacts': contacts.length,
|
||||
|
||||
'user_discovery_enabled': userService.currentUser.isUserDiscoveryEnabled,
|
||||
'user_discovery_required_send_images':
|
||||
userService.currentUser.requiredSendImages,
|
||||
'user_discovery_threshold':
|
||||
userService.currentUser.userDiscoveryThreshold,
|
||||
'user_discovery_requires_manual_approval':
|
||||
userService.currentUser.userDiscoveryRequiresManualApproval,
|
||||
'user_discovery_share_promotion':
|
||||
userService.currentUser.userDiscoverySharePromotion,
|
||||
|
||||
'user_discovery_count_friends_shared': udFriendsShared.length,
|
||||
|
||||
'user_discovery_count_announced_users': udAllAnnouncedUsers.length,
|
||||
'user_discovery_count_unknown_announced_users': udUnknownAnnouncedUsers,
|
||||
|
||||
'user_study_count_new_friends_via_suggestion':
|
||||
userService.currentUser.userStudyCountNewFriendsViaSuggestion,
|
||||
'user_discovery_count_verified_by_contacts': udVerifiedByContactsCount,
|
||||
|
||||
'accepted_contacts': contacts.where((c) => c.accepted).length,
|
||||
'verified_contacts': verifications.length,
|
||||
'total_contacts_with_verification_badge':
|
||||
totalContactsWithVerificationBadge,
|
||||
'verified_contacts_via_migrated_from_old_version': verifications.values
|
||||
.where((c) => c == VerificationType.migratedFromOldVersion)
|
||||
.length,
|
||||
'verified_contacts_via_qr_scanned': verifications.values
|
||||
.where((c) => c == VerificationType.qrScanned)
|
||||
.length,
|
||||
'verified_contacts_via_link': verifications.values
|
||||
.where((c) => c == VerificationType.link)
|
||||
.length,
|
||||
'verified_contacts_via_secret_qr_token': verifications.values
|
||||
.where((c) => c == VerificationType.secretQrToken)
|
||||
.length,
|
||||
'verified_contacts_via_contact_shared_by_verified': verifications.values
|
||||
.where((c) => c == VerificationType.contactSharedByVerified)
|
||||
.length,
|
||||
};
|
||||
|
||||
final response = await http.post(
|
||||
Uri.parse('$surveyUrlBase/push/$token'),
|
||||
body: jsonEncode(dataCollection),
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
);
|
||||
if (response.statusCode == 200) {
|
||||
await UserService.update((u) {
|
||||
u.lastUserStudyDataUpload = DateTime.now();
|
||||
});
|
||||
}
|
||||
if (response.statusCode == 404) {
|
||||
// Token is unknown to the server...
|
||||
await UserService.update((u) {
|
||||
u
|
||||
..lastUserStudyDataUpload = null
|
||||
..userStudyParticipantsToken = null;
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
if (e is http.ClientException ||
|
||||
e is SocketException ||
|
||||
e is TimeoutException) {
|
||||
Log.warn('Error uploading user study data: $e');
|
||||
} else {
|
||||
Log.error(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -7,7 +7,6 @@ import 'package:twonly/src/database/daos/user_discovery.dao.dart';
|
|||
import 'package:twonly/src/database/twonly.db.dart';
|
||||
import 'package:twonly/src/services/api/messages.api.dart';
|
||||
import 'package:twonly/src/services/api/utils.api.dart';
|
||||
import 'package:twonly/src/services/user.service.dart';
|
||||
import 'package:twonly/src/utils/log.dart';
|
||||
import 'package:twonly/src/utils/misc.dart';
|
||||
import 'package:twonly/src/visual/components/avatar_icon.comp.dart';
|
||||
|
|
@ -68,10 +67,6 @@ class FriendSuggestionsComp extends StatelessWidget {
|
|||
);
|
||||
|
||||
if (added > 0) await importSignalContactAndCreateRequest(userdata);
|
||||
|
||||
await UserService.update(
|
||||
(u) => u.userStudyCountNewFriendsViaSuggestion += 1,
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _hideAnnouncedUser(int userId) async {
|
||||
|
|
@ -110,11 +105,14 @@ class FriendSuggestionsComp extends StatelessWidget {
|
|||
mainAxisSize: MainAxisSize.min,
|
||||
children: friends.map((f) {
|
||||
final contact = f.$1;
|
||||
final isSelected =
|
||||
selectedFriends.contains(contact.userId);
|
||||
final isSelected = selectedFriends.contains(
|
||||
contact.userId,
|
||||
);
|
||||
return CheckboxListTile.adaptive(
|
||||
contentPadding: EdgeInsets.zero,
|
||||
title: Text(contact.displayName ?? contact.username),
|
||||
title: Text(
|
||||
contact.displayName ?? contact.username,
|
||||
),
|
||||
value: isSelected,
|
||||
onChanged: (val) {
|
||||
setState(() {
|
||||
|
|
@ -154,7 +152,7 @@ class FriendSuggestionsComp extends StatelessWidget {
|
|||
for (final contactId in selectedFriends) {
|
||||
await insertAndSendAskAboutUserMessage(contactId, user.announcedUserId);
|
||||
}
|
||||
|
||||
|
||||
await twonlyDB.userDiscoveryDao.updateAnnouncedUser(
|
||||
user.announcedUserId,
|
||||
const UserDiscoveryAnnouncedUsersCompanion(
|
||||
|
|
@ -255,7 +253,8 @@ class FriendSuggestionsComp extends StatelessWidget {
|
|||
left: 4,
|
||||
),
|
||||
).merge(secondaryGreyButtonStyle(context)),
|
||||
onPressed: () => _askFriends(context, user, friends),
|
||||
onPressed: () =>
|
||||
_askFriends(context, user, friends),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
|
|
@ -61,12 +60,6 @@ class _HelpViewState extends State<HelpView> {
|
|||
onTap: () => context.push(Routes.settingsHelpDiagnostics),
|
||||
),
|
||||
const Divider(),
|
||||
if (userService.currentUser.userStudyParticipantsToken == null ||
|
||||
kDebugMode)
|
||||
ListTile(
|
||||
title: const Text('Teilnahme an Nutzerstudie'),
|
||||
onTap: () => context.push(Routes.settingsHelpUserStudy),
|
||||
),
|
||||
FutureBuilder(
|
||||
future: PackageInfo.fromPlatform(),
|
||||
builder: (context, snap) {
|
||||
|
|
|
|||
|
|
@ -1,267 +0,0 @@
|
|||
// ignore_for_file: avoid_dynamic_calls
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:twonly/src/services/user.service.dart';
|
||||
import 'package:twonly/src/services/user_study.service.dart';
|
||||
import 'package:twonly/src/utils/keyvalue.dart';
|
||||
import 'package:twonly/src/utils/misc.dart';
|
||||
import 'package:twonly/src/visual/components/snackbar.dart';
|
||||
|
||||
class UserStudyQuestionnaireView extends StatefulWidget {
|
||||
const UserStudyQuestionnaireView({super.key});
|
||||
|
||||
@override
|
||||
State<UserStudyQuestionnaireView> createState() =>
|
||||
_UserStudyQuestionnaireViewState();
|
||||
}
|
||||
|
||||
class _UserStudyQuestionnaireViewState
|
||||
extends State<UserStudyQuestionnaireView> {
|
||||
final Map<String, dynamic> _responses = {
|
||||
'age': null,
|
||||
'education': null,
|
||||
'education_free': '',
|
||||
'vocational': null,
|
||||
'vocational_free': '',
|
||||
'enrolled': null,
|
||||
'study_program': '',
|
||||
'working': null,
|
||||
'work_field': '',
|
||||
'smartphone_2years': null,
|
||||
'comp_knowledge': null,
|
||||
'security_knowledge': null,
|
||||
'messengers': [],
|
||||
'is_release_mode': kReleaseMode,
|
||||
};
|
||||
|
||||
final List<String> _messengerOptions = [
|
||||
'WhatsApp',
|
||||
'Signal',
|
||||
'Telegram',
|
||||
'Facebook Messenger',
|
||||
'iMessage',
|
||||
'Teams',
|
||||
'Viber',
|
||||
'Element',
|
||||
'Andere',
|
||||
];
|
||||
|
||||
Future<void> _submitData() async {
|
||||
await KeyValueStore.put(userStudySurveyKey, _responses);
|
||||
|
||||
await UserService.update((u) {
|
||||
// generate a random participants id to identify data send later while keeping the user anonym
|
||||
u
|
||||
..userStudyParticipantsToken = getRandomString(25)
|
||||
..askedForUserStudyPermission = true;
|
||||
});
|
||||
|
||||
await handleUserStudyUpload();
|
||||
|
||||
if (!mounted) return;
|
||||
showSnackbar(
|
||||
context,
|
||||
'Vielen Dank für deine Teilnahme!',
|
||||
level: SnackbarLevel.success,
|
||||
);
|
||||
context.pop();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(title: const Text('Befragung')),
|
||||
body: SingleChildScrollView(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
_sectionTitle('Demografische Daten'),
|
||||
_questionText('Wie alt bist du?'),
|
||||
_buildRadioList(
|
||||
[
|
||||
'18-22',
|
||||
'23-27',
|
||||
'28-32',
|
||||
'33-37',
|
||||
'38-42',
|
||||
'43-47',
|
||||
'48-52',
|
||||
'53-57',
|
||||
'58-62',
|
||||
'63-67',
|
||||
'68-72',
|
||||
'73-77',
|
||||
'77 oder älter',
|
||||
'Keine Angabe',
|
||||
],
|
||||
'age',
|
||||
),
|
||||
_questionText('Was ist dein höchster Schulabschluss?'),
|
||||
_buildRadioList(
|
||||
[
|
||||
'Noch in der Schule',
|
||||
'Hauptschulabschluss',
|
||||
'POS (Polytechnische Oberschule)',
|
||||
'Realschulabschluss',
|
||||
'Abitur / Hochschulreife',
|
||||
'Kein Abschluss',
|
||||
'Keine Angabe',
|
||||
],
|
||||
'education',
|
||||
),
|
||||
_buildTextField(
|
||||
'Freitext (optional)',
|
||||
(val) => _responses['education_free'] = val,
|
||||
),
|
||||
_questionText('Was ist dein höchster beruflicher Abschluss?'),
|
||||
_buildRadioList(
|
||||
[
|
||||
'Berufsausbildung / Duales System',
|
||||
'Fachschulabschluss',
|
||||
'Fachschulabschluss (ehem. DDR)',
|
||||
'Bachelor',
|
||||
'Master',
|
||||
'Diplom',
|
||||
'Promotion (PhD)',
|
||||
'Kein beruflicher Abschluss',
|
||||
'Keine Angabe',
|
||||
],
|
||||
'vocational',
|
||||
),
|
||||
_buildTextField(
|
||||
'Freitext (optional)',
|
||||
(val) => _responses['vocational_free'] = val,
|
||||
),
|
||||
_questionText(
|
||||
'Bist du derzeit in einem Studiengang eingeschrieben? (Bachelor, Master, Diplom, Staatsexamen, Magister)',
|
||||
),
|
||||
_buildRadioList(['Ja', 'Nein', 'Keine Angabe'], 'enrolled'),
|
||||
_questionText('Wenn ja, welcher Studiengang?'),
|
||||
_buildTextField(
|
||||
'Studiengang eingeben',
|
||||
(val) => _responses['study_program'] = val,
|
||||
),
|
||||
_questionText('Bist du derzeit berufstätig?'),
|
||||
_buildRadioList(['Ja', 'Nein', 'Keine Angabe'], 'working'),
|
||||
_questionText('Wenn ja, in welchem Bereich arbeiten Sie?'),
|
||||
_buildTextField(
|
||||
'Arbeitsbereich eingeben',
|
||||
(val) => _responses['work_field'] = val,
|
||||
),
|
||||
const SizedBox(height: 30),
|
||||
// const Divider(),
|
||||
_sectionTitle('Technisches Wissen'),
|
||||
_questionText(
|
||||
'Nutzt du seit mehr als zwei Jahren ein Smartphone?',
|
||||
),
|
||||
_buildRadioList(['Ja', 'Nein'], 'smartphone_2years'),
|
||||
_questionText(
|
||||
'Wie schätzt du deine allgemeinen Computerkenntnisse ein?',
|
||||
),
|
||||
_buildRadioList(
|
||||
['Anfänger', 'Mittel', 'Fortgeschritten'],
|
||||
'comp_knowledge',
|
||||
),
|
||||
_questionText(
|
||||
'Wie schätzt du dein Wissen im Bereich IT-Sicherheit ein?',
|
||||
),
|
||||
_buildRadioList(
|
||||
['Anfänger', 'Mittel', 'Fortgeschritten'],
|
||||
'security_knowledge',
|
||||
),
|
||||
_questionText(
|
||||
'Welche der folgenden Messenger hast du schon einmal benutzt?',
|
||||
),
|
||||
..._messengerOptions.map(
|
||||
(m) => CheckboxListTile.adaptive(
|
||||
title: Text(m),
|
||||
visualDensity: const VisualDensity(vertical: -4),
|
||||
value: (_responses['messengers'] as List<dynamic>).contains(m),
|
||||
onChanged: (value) {
|
||||
setState(() {
|
||||
value!
|
||||
? _responses['messengers'].add(m)
|
||||
: _responses['messengers'].remove(m);
|
||||
});
|
||||
},
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 30),
|
||||
Center(
|
||||
child: FilledButton(
|
||||
onPressed: _submitData,
|
||||
child: const Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 40, vertical: 15),
|
||||
child: Text(
|
||||
'Jetzt teilnehmen',
|
||||
style: TextStyle(fontSize: 18),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 50),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// Hilfsmethoden für das UI
|
||||
Widget _sectionTitle(String title) => Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 10),
|
||||
child: Text(
|
||||
title,
|
||||
style: const TextStyle(
|
||||
fontSize: 24,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
Widget _questionText(String text) => Padding(
|
||||
padding: const EdgeInsets.only(top: 20, bottom: 5),
|
||||
child: Text(
|
||||
text,
|
||||
style: const TextStyle(fontSize: 16, fontWeight: FontWeight.w600),
|
||||
),
|
||||
);
|
||||
|
||||
Widget _buildRadioList(List<String> options, String key) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 8),
|
||||
child: DropdownButtonFormField<String>(
|
||||
decoration: const InputDecoration(
|
||||
border: OutlineInputBorder(),
|
||||
contentPadding: EdgeInsets.symmetric(horizontal: 12, vertical: 8),
|
||||
labelText: 'Bitte wählen...',
|
||||
),
|
||||
initialValue: _responses[key] as String?,
|
||||
items: options.map((value) {
|
||||
return DropdownMenuItem<String>(
|
||||
value: value,
|
||||
child: Text(value),
|
||||
);
|
||||
}).toList(),
|
||||
onChanged: (val) {
|
||||
setState(() {
|
||||
_responses[key] = val;
|
||||
});
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildTextField(String hint, void Function(String) onChanged) {
|
||||
return TextField(
|
||||
textCapitalization: TextCapitalization.sentences,
|
||||
decoration: InputDecoration(
|
||||
hintText: hint,
|
||||
border: const OutlineInputBorder(),
|
||||
),
|
||||
onChanged: onChanged,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,111 +0,0 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:twonly/src/constants/routes.keys.dart';
|
||||
import 'package:twonly/src/services/user.service.dart';
|
||||
|
||||
class UserStudyWelcomeView extends StatefulWidget {
|
||||
const UserStudyWelcomeView({super.key, this.wasOpenedAutomatic = false});
|
||||
|
||||
final bool wasOpenedAutomatic;
|
||||
|
||||
@override
|
||||
State<UserStudyWelcomeView> createState() => _UserStudyWelcomeViewState();
|
||||
}
|
||||
|
||||
class _UserStudyWelcomeViewState extends State<UserStudyWelcomeView> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text('Teilnahme an Nutzerstudie'),
|
||||
),
|
||||
body: Padding(
|
||||
padding: const EdgeInsets.all(12),
|
||||
child: ListView(
|
||||
children: [
|
||||
const SizedBox(height: 30),
|
||||
const Text(
|
||||
'Es dauert nur ein paar Minuten.',
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 20,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
const Text(
|
||||
'Im Rahmen meiner Masterarbeit möchte ich die Benutzerfreundlichkeit von anonymen und dezentralen Messenger-Diensten verbessern.',
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
const Text(
|
||||
'Zu diesem Zweck werden in den nächsten Monaten verschiedene Änderungen an der App vorgenommen. Um die Wirksamkeit der Änderungen zu messen, möchte ich einige Daten über deine Nutzung der App sammeln sowie eine kurze Befragung durchführen.',
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
const Text(
|
||||
'Die Daten bestehen ausschließlich aus Zahlen, z. B. Anzahl deiner Kontakte. Alle Daten werden anonym übermittelt und können nicht mit deinem Benutzerkonto verknüpft werden.',
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
const Text(
|
||||
'Die Masterarbeit und damit die Nutzerstudie wird bis September durchgeführt. Nach Abschluss erhältst du eine Benachrichtigung und wirst über die Ergebnisse informiert.',
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
const SizedBox(height: 40),
|
||||
Center(
|
||||
child: FilledButton(
|
||||
onPressed: () => context.pushReplacement(
|
||||
Routes.settingsHelpUserStudyQuestionnaire,
|
||||
),
|
||||
child: const Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 30, vertical: 15),
|
||||
child: Text(
|
||||
'Weiter zur Befragung',
|
||||
style: TextStyle(fontSize: 18),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
if (widget.wasOpenedAutomatic)
|
||||
Center(
|
||||
child: OutlinedButton(
|
||||
onPressed: () => context.pop(),
|
||||
child: const Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 20, vertical: 10),
|
||||
child: Text(
|
||||
'Frag mich später noch mal',
|
||||
style: TextStyle(fontSize: 18),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
if (widget.wasOpenedAutomatic)
|
||||
Center(
|
||||
child: GestureDetector(
|
||||
onTap: () async {
|
||||
await UserService.update(
|
||||
(u) => u.askedForUserStudyPermission = true,
|
||||
);
|
||||
if (context.mounted) context.pop();
|
||||
},
|
||||
child: const Text(
|
||||
'Nicht mehr anzeigen',
|
||||
style: TextStyle(fontSize: 12),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
const Text(
|
||||
'PS: twonly ist Open Source, wenn du also genau wissen willst, welche Daten übertragen werden, schau dir einfach die Datei "lib/src/views/user_study/user_study_data_collection.dart" im Repository an :).',
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(fontSize: 10),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue