Skip to content

Commit

Permalink
Add user ID to the subject when pressing email support button (#1777)
Browse files Browse the repository at this point in the history
In case the `userId` is available, it will be appended to the subject.

Part of #1069
  • Loading branch information
nilsreichardt authored Oct 18, 2024
1 parent 8be6c56 commit 1e81b01
Show file tree
Hide file tree
Showing 8 changed files with 265 additions and 19 deletions.
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

0 comments on commit 1e81b01

Please sign in to comment.