Skip to content

Commit

Permalink
Add option to change the type of user (#1349)
Browse files Browse the repository at this point in the history
## Description

A user can change twice every two weeks their type of user in the app
setting without contacting the support.

## Demo

### Changing type of user


https://github.com/SharezoneApp/sharezone-app/assets/24459435/9300571b-5e09-4b96-b103-76b41b7cfec2

### When the user changed too often the type of user


https://github.com/SharezoneApp/sharezone-app/assets/24459435/cce72970-4b1b-4aba-926e-efca5760554d

## Related tickets

Closes #556
  • Loading branch information
nilsreichardt authored Mar 4, 2024
1 parent a8ba1c8 commit 18b3d0e
Show file tree
Hide file tree
Showing 25 changed files with 772 additions and 24 deletions.
3 changes: 3 additions & 0 deletions app/lib/main/sharezone_app.dart
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ import 'package:sharezone/settings/src/subpages/changelog_page.dart';
import 'package:sharezone/settings/src/subpages/my_profile/change_email.dart';
import 'package:sharezone/settings/src/subpages/my_profile/change_password.dart';
import 'package:sharezone/settings/src/subpages/my_profile/change_state.dart';
import 'package:sharezone/settings/src/subpages/my_profile/change_type_of_user/change_type_of_user_page.dart';
import 'package:sharezone/settings/src/subpages/my_profile/my_profile_page.dart';
import 'package:sharezone/settings/src/subpages/notification.dart';
import 'package:sharezone/settings/src/subpages/about/about_page.dart';
Expand Down Expand Up @@ -187,6 +188,8 @@ class _SharezoneAppState extends State<SharezoneApp>
ImprintPage.tag: (context) => const ImprintPage(),
PastCalendricalEventsPage.tag: (context) =>
const PastCalendricalEventsPage(),
ChangeTypeOfUserPage.tag: (context) =>
const ChangeTypeOfUserPage(),
},
navigatorKey: navigationService.navigatorKey,
),
Expand Down
13 changes: 13 additions & 0 deletions app/lib/main/sharezone_bloc_providers.dart
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,9 @@ import 'package:sharezone/settings/src/bloc/user_tips_bloc.dart';
import 'package:sharezone/settings/src/subpages/imprint/analytics/imprint_analytics.dart';
import 'package:sharezone/settings/src/subpages/imprint/bloc/imprint_bloc_factory.dart';
import 'package:sharezone/settings/src/subpages/imprint/gateway/imprint_gateway.dart';
import 'package:sharezone/settings/src/subpages/my_profile/change_type_of_user/change_type_of_user_analytics.dart';
import 'package:sharezone/settings/src/subpages/my_profile/change_type_of_user/change_type_of_user_controller.dart';
import 'package:sharezone/settings/src/subpages/my_profile/change_type_of_user/change_type_of_user_service.dart';
import 'package:sharezone/settings/src/subpages/timetable/bloc/timetable_settings_bloc_factory.dart';
import 'package:sharezone/settings/src/subpages/timetable/time_picker_settings_cache.dart';
import 'package:sharezone/sharezone_plus/page/sharezone_plus_page_controller.dart';
Expand Down Expand Up @@ -361,6 +364,16 @@ class _SharezoneBlocProvidersState extends State<SharezoneBlocProviders> {
value: typeOfUserStream,
initialData: null,
),
ChangeNotifierProvider(
create: (context) => ChangeTypeOfUserController(
service: ChangeTypeOfUserService(
functions: widget.blocDependencies.functions,
),
typeOfUserStream: typeOfUserStream,
analytics: ChangeTypeOfUserAnalytics(analytics),
userId: UserId(api.uID),
),
),
Provider(
create: (context) => PastCalendricalEventsPageControllerFactory(
clock: clock,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
// Copyright (c) 2023 Sharezone UG (haftungsbeschränkt)
// Licensed under the EUPL-1.2-or-later.
//
// You may obtain a copy of the Licence at:
// https://joinup.ec.europa.eu/software/page/eupl
//
// SPDX-License-Identifier: EUPL-1.2

import 'package:analytics/analytics.dart';
import 'package:user/user.dart';

class ChangeTypeOfUserAnalytics {
final Analytics analytics;

const ChangeTypeOfUserAnalytics(this.analytics);

void logChangedOrder({
required TypeOfUser? from,
required TypeOfUser to,
}) {
analytics.log(
ChangeOfTypeOfUserAnalyticsEvent(
data: {
'from': from?.name,
'to': to.name,
},
),
);
}
}

class ChangeOfTypeOfUserAnalyticsEvent extends AnalyticsEvent {
const ChangeOfTypeOfUserAnalyticsEvent({
Map<String, dynamic>? data,
}) : super('changed_type_of_user', data: data);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
// Copyright (c) 2024 Sharezone UG (haftungsbeschränkt)
// Licensed under the EUPL-1.2-or-later.
//
// You may obtain a copy of the Licence at:
// https://joinup.ec.europa.eu/software/page/eupl
//
// SPDX-License-Identifier: EUPL-1.2

import 'dart:async';
import 'dart:convert';

import 'package:cloud_functions/cloud_functions.dart';
import 'package:common_domain_models/common_domain_models.dart';
import 'package:flutter/material.dart';
import 'package:sharezone/settings/src/subpages/my_profile/change_type_of_user/change_type_of_user_analytics.dart';
import 'package:sharezone/settings/src/subpages/my_profile/change_type_of_user/change_type_of_user_service.dart';
import 'package:user/user.dart';

class ChangeTypeOfUserController extends ChangeNotifier {
final UserId userId;
final ChangeTypeOfUserAnalytics analytics;
final ChangeTypeOfUserService service;
StreamSubscription<TypeOfUser?>? _typeOfUserSubscription;

late ChangeTypeOfUserState state;

/// The initial type of user of the user before the change.
TypeOfUser? initialTypeOfUser;

/// The currently selected type of user.
TypeOfUser? selectedTypeOfUser;

ChangeTypeOfUserController({
required this.userId,
required this.analytics,
required this.service,
required Stream<TypeOfUser?> typeOfUserStream,
}) {
state = const ChangeTypeOfUserInitial();
_listenToTypeOfUserStream(typeOfUserStream);
}

void _listenToTypeOfUserStream(Stream<TypeOfUser?> typeOfUserStream) {
_typeOfUserSubscription = typeOfUserStream.listen((typeOfUser) {
initialTypeOfUser = typeOfUser;
notifyListeners();
});
}

Future<void> changeTypeOfUser() async {
final typeOfUser = selectedTypeOfUser;
if (typeOfUser == null) {
throw const NoTypeOfUserSelectedException();
}

if (typeOfUser == initialTypeOfUser) {
throw const TypeUserOfUserHasNotChangedException();
}

try {
state = const ChangeTypeOfUserLoading();
notifyListeners();

await service.changeTypeOfUser(typeOfUser);

analytics.logChangedOrder(
from: initialTypeOfUser,
to: typeOfUser,
);
initialTypeOfUser = typeOfUser;
} on FirebaseFunctionsException catch (e) {
_parseException(e);
} catch (e) {
throw ChangeTypeOfUserUnknownException(e);
} finally {
state = const ChangeTypeOfUserInitial();
notifyListeners();
}
}

void _parseException(FirebaseFunctionsException e) {
final unknownErrorMessage = '[${e.plugin}/${e.code}] ${e.message}';
if (e.code != 'failed-precondition') {
throw ChangeTypeOfUserUnknownException(unknownErrorMessage);
}

try {
final json = jsonDecode(e.message!);
if (json['errorId'] == 'typeofuser-change-limit-reached') {
final blockedUntil = DateTime.parse(json['blockedUntil']);
throw ChangedTypeOfUserTooOftenException(
blockedUntil: blockedUntil,
);
} else {
throw ChangeTypeOfUserUnknownException(unknownErrorMessage);
}
} catch (e) {
if (e is ChangedTypeOfUserTooOftenException) rethrow;
throw ChangeTypeOfUserUnknownException(unknownErrorMessage);
}
}

void setSelectedTypeOfUser(TypeOfUser typeOfUser) {
selectedTypeOfUser = typeOfUser;
notifyListeners();
}

@override
void dispose() {
_typeOfUserSubscription?.cancel();
super.dispose();
}
}

sealed class ChangeTypeOfUserState {
const ChangeTypeOfUserState();
}

class ChangeTypeOfUserInitial extends ChangeTypeOfUserState {
const ChangeTypeOfUserInitial();
}

class ChangeTypeOfUserLoading extends ChangeTypeOfUserState {
const ChangeTypeOfUserLoading();
}

sealed class ChangeTypeOfUserFailed {
const ChangeTypeOfUserFailed();
}

class NoTypeOfUserSelectedException extends ChangeTypeOfUserFailed {
const NoTypeOfUserSelectedException();
}

class TypeUserOfUserHasNotChangedException extends ChangeTypeOfUserFailed {
const TypeUserOfUserHasNotChangedException();
}

class ChangedTypeOfUserTooOftenException extends ChangeTypeOfUserFailed {
final DateTime blockedUntil;

const ChangedTypeOfUserTooOftenException({
required this.blockedUntil,
});
}

class ChangeTypeOfUserUnknownException extends ChangeTypeOfUserFailed {
final Object? error;

const ChangeTypeOfUserUnknownException(this.error);
}
Loading

0 comments on commit 18b3d0e

Please sign in to comment.