Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add user ID to the subject when pressing email support button #1777

Merged
merged 3 commits into from
Oct 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions app/lib/main/auth_app.dart
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import 'package:sharezone/support/support_page.dart';
import 'package:sharezone/legal/privacy_policy/privacy_policy_page.dart';
import 'package:sharezone/support/support_page_controller.dart';
import 'package:sharezone/util/cache/streaming_key_value_store.dart';
import 'package:url_launcher_extended/url_launcher_extended.dart';

class AuthApp extends StatefulWidget {
final Analytics analytics;
Expand Down Expand Up @@ -69,6 +70,7 @@ class _AuthAppState extends State<AuthApp> {
hasPlusSupportUnlockedStream: Stream.value(false),
isUserInGroupOnboardingStream: Stream.value(false),
typeOfUserStream: Stream.value(null),
urlLauncher: UrlLauncherExtended(),
),
),
],
Expand Down
2 changes: 2 additions & 0 deletions app/lib/main/sharezone_bloc_providers.dart
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,7 @@ import 'package:sharezone/util/platform_information_manager/flutter_platform_inf
import 'package:sharezone/util/platform_information_manager/get_platform_information_retreiver.dart';
import 'package:sharezone_common/references.dart';
import 'package:stripe_checkout_session/stripe_checkout_session.dart';
import 'package:url_launcher_extended/url_launcher_extended.dart';
import 'package:user/user.dart';

import '../holidays/holiday_bloc.dart';
Expand Down Expand Up @@ -378,6 +379,7 @@ class _SharezoneBlocProvidersState extends State<SharezoneBlocProviders> {
.hasFeatureUnlockedStream(SharezonePlusFeature.plusSupport),
isUserInGroupOnboardingStream: signUpBloc.signedUp,
typeOfUserStream: typeOfUserStream,
urlLauncher: UrlLauncherExtended(),
),
),
StreamProvider<TypeOfUser?>.value(
Expand Down
20 changes: 8 additions & 12 deletions app/lib/support/support_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,9 @@ import 'package:provider/provider.dart';
import 'package:sharezone/navigation/logic/navigation_bloc.dart';
import 'package:sharezone/navigation/models/navigation_item.dart';
import 'package:sharezone/support/support_page_controller.dart';
import 'package:sharezone_utils/launch_link.dart';
import 'package:sharezone/widgets/avatar_card.dart';
import 'package:sharezone_utils/launch_link.dart';
import 'package:sharezone_widgets/sharezone_widgets.dart';
import 'package:url_launcher/url_launcher.dart';

class SupportPage extends StatelessWidget {
static const String tag = 'support-page';
Expand Down Expand Up @@ -232,17 +231,16 @@ class _FreeEmailTile extends StatelessWidget {
semanticsLabel: 'E-Mail Icon',
),
title: 'E-Mail',
subtitle: '[email protected]',
subtitle: freeSupportEmail,
onPressed: () async {
final url = Uri.parse(Uri.encodeFull(
'mailto:[email protected]?subject=Ich brauche eure Hilfe! 😭'));
try {
await launchUrl(url);
final controller = context.read<SupportPageController>();
await controller.sendEmailToFreeSupport();
} on Exception catch (_) {
if (!context.mounted) return;
showSnackSec(
context: context,
text: 'E-Mail: [email protected]',
text: 'E-Mail: $freeSupportEmail',
);
}
},
Expand All @@ -256,7 +254,6 @@ class _PlusEmailTile extends StatelessWidget {

@override
Widget build(BuildContext context) {
const emailAddress = '[email protected]';
return _SupportCard(
icon: PlatformSvg.asset(
'assets/icons/email.svg',
Expand All @@ -266,15 +263,14 @@ class _PlusEmailTile extends StatelessWidget {
title: 'E-Mail',
subtitle: 'Erhalte eine Rückmeldung innerhalb von wenigen Stunden.',
onPressed: () async {
final url = Uri.parse(Uri.encodeFull(
'mailto:$emailAddress?subject=[💎 Sharezone Plus Support] Meine Anfrage'));
try {
await launchUrl(url);
final controller = context.read<SupportPageController>();
await controller.sendEmailToPlusSupport();
} on Exception catch (_) {
if (!context.mounted) return;
showSnackSec(
context: context,
text: 'E-Mail: $emailAddress',
text: 'E-Mail: $plusSupportEmail',
);
}
},
Expand Down
36 changes: 35 additions & 1 deletion app/lib/support/support_page_controller.dart
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,12 @@ import 'dart:async';

import 'package:common_domain_models/common_domain_models.dart';
import 'package:flutter/material.dart';
import 'package:url_launcher_extended/url_launcher_extended.dart';
import 'package:user/user.dart';

const freeSupportEmail = '[email protected]';
const plusSupportEmail = '[email protected]';

class SupportPageController extends ChangeNotifier {
bool get hasPlusSupportUnlocked =>
_hasSharezonePlus && _typeOfUser == TypeOfUser.student;
Expand All @@ -30,6 +34,7 @@ class SupportPageController extends ChangeNotifier {
late StreamSubscription<bool> _hasPlusSupportUnlockedSubscription;
late StreamSubscription<bool> _isUserInGroupOnboardingSubscription;
late StreamSubscription<TypeOfUser?> _typeOfUserSubscription;
final UrlLauncherExtended _urlLauncher;

SupportPageController({
required Stream<UserId?> userIdStream,
Expand All @@ -38,7 +43,8 @@ class SupportPageController extends ChangeNotifier {
required Stream<bool> hasPlusSupportUnlockedStream,
required Stream<bool> isUserInGroupOnboardingStream,
required Stream<TypeOfUser?> typeOfUserStream,
}) {
required UrlLauncherExtended urlLauncher,
}) : _urlLauncher = urlLauncher {
_userIdSubscription = userIdStream.listen((userId) {
this.userId = userId;
notifyListeners();
Expand Down Expand Up @@ -119,6 +125,34 @@ class SupportPageController extends ChangeNotifier {
return url;
}

Future<void> sendEmailToFreeSupport() async {
await _openEmailApp(
email: freeSupportEmail,
subject: 'Meine Anfrage',
);
}

Future<void> sendEmailToPlusSupport() async {
await _openEmailApp(
email: plusSupportEmail,
subject: '[💎 Plus Support] Meine Anfrage',
);
}

Future<void> _openEmailApp({
required String email,

/// The subject of the email.
///
/// The user ID is appended to the subject if it's not `null`.
required String subject,
}) async {
if (userId != null) {
subject += ' [User-ID: $userId]';
}
await _urlLauncher.tryLaunchMailOrThrow(email, subject: subject);
}

bool _isPrivateAppleEmail(String email) {
return email.endsWith('@privaterelay.appleid.com') || email == '-';
}
Expand Down
68 changes: 68 additions & 0 deletions app/test/settings/support/support_page_controller_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,23 @@

import 'package:common_domain_models/common_domain_models.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mockito/annotations.dart';
import 'package:mockito/mockito.dart';
import 'package:sharezone/support/support_page_controller.dart';
import 'package:url_launcher_extended/url_launcher_extended.dart';
import 'package:user/user.dart';

import 'support_page_controller_test.mocks.dart';

@GenerateNiceMocks([MockSpec<UrlLauncherExtended>()])
void main() {
group(SupportPageController, () {
late MockUrlLauncherExtended urlLauncher;

setUp(() {
urlLauncher = MockUrlLauncherExtended();
});

group('getVideoCallAppointmentsUnencodedUrlWithPrefills()', () {
test('throws $UserNotAuthenticatedException when user Id is null',
() async {
Expand All @@ -23,6 +35,7 @@ void main() {
hasPlusSupportUnlockedStream: Stream.value(false),
isUserInGroupOnboardingStream: Stream.value(false),
typeOfUserStream: Stream.value(null),
urlLauncher: urlLauncher,
);

// Workaround to wait for stream subscription in constructor.
Expand All @@ -42,6 +55,7 @@ void main() {
hasPlusSupportUnlockedStream: Stream.value(false),
isUserInGroupOnboardingStream: Stream.value(false),
typeOfUserStream: Stream.value(null),
urlLauncher: urlLauncher,
);

// Workaround to wait for stream subscription in constructor.
Expand All @@ -63,6 +77,7 @@ void main() {
hasPlusSupportUnlockedStream: Stream.value(true),
isUserInGroupOnboardingStream: Stream.value(false),
typeOfUserStream: Stream.value(TypeOfUser.parent),
urlLauncher: urlLauncher,
);

// Workaround to wait for stream subscription in constructor.
Expand All @@ -82,6 +97,7 @@ void main() {
hasPlusSupportUnlockedStream: Stream.value(true),
isUserInGroupOnboardingStream: Stream.value(false),
typeOfUserStream: Stream.value(TypeOfUser.teacher),
urlLauncher: urlLauncher,
);

// Workaround to wait for stream subscription in constructor.
Expand All @@ -101,6 +117,7 @@ void main() {
hasPlusSupportUnlockedStream: Stream.value(false),
isUserInGroupOnboardingStream: Stream.value(false),
typeOfUserStream: Stream.value(TypeOfUser.teacher),
urlLauncher: urlLauncher,
);

// Workaround to wait for stream subscription in constructor.
Expand All @@ -120,6 +137,7 @@ void main() {
hasPlusSupportUnlockedStream: Stream.value(true),
isUserInGroupOnboardingStream: Stream.value(false),
typeOfUserStream: Stream.value(TypeOfUser.student),
urlLauncher: urlLauncher,
);

// Workaround to wait for stream subscription in constructor.
Expand All @@ -131,5 +149,55 @@ void main() {
);
});
});

group('open email app', () {
test('free user', () async {
final controller = SupportPageController(
userIdStream: Stream.value(const UserId('userId123')),
userNameStream: Stream.value('My Cool Name'),
userEmailStream: Stream.value('[email protected]'),
hasPlusSupportUnlockedStream: Stream.value(false),
isUserInGroupOnboardingStream: Stream.value(false),
typeOfUserStream: Stream.value(TypeOfUser.student),
urlLauncher: urlLauncher,
);

// Workaround to wait for stream subscription in constructor.
await Future.delayed(Duration.zero);

await controller.sendEmailToFreeSupport();

verify(
urlLauncher.tryLaunchMailOrThrow(
freeSupportEmail,
subject: "Meine Anfrage [User-ID: userId123]",
),
);
});

test('plus user', () async {
final controller = SupportPageController(
userIdStream: Stream.value(const UserId('userId123')),
userNameStream: Stream.value('My Cool Name'),
userEmailStream: Stream.value('[email protected]'),
hasPlusSupportUnlockedStream: Stream.value(true),
isUserInGroupOnboardingStream: Stream.value(false),
typeOfUserStream: Stream.value(TypeOfUser.student),
urlLauncher: urlLauncher,
);

// Workaround to wait for stream subscription in constructor.
await Future.delayed(Duration.zero);

await controller.sendEmailToPlusSupport();

verify(
urlLauncher.tryLaunchMailOrThrow(
plusSupportEmail,
subject: "[💎 Plus Support] Meine Anfrage [User-ID: userId123]",
),
);
});
});
});
}
102 changes: 102 additions & 0 deletions app/test/settings/support/support_page_controller_test.mocks.dart

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading