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 broadcast card in grades page #909

Closed
wants to merge 5 commits into from
Closed
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
6 changes: 6 additions & 0 deletions lib/core/services/remote_config_service.dart
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ class RemoteConfigService {
static const _privacyPolicyURL = "privacy_policy_url";

// dashboard message remote config keys
static const _gradesMsgToggle = "grades_message_toggle";
static const _dashboardMsgToggle = "dashboard_message_toggle";
static const _dashboardMsgFr = "dashboard_message_fr";
static const _dashboardMsgEn = "dashboard_message_en";
Expand Down Expand Up @@ -58,6 +59,11 @@ class RemoteConfigService {
return _remoteConfig.getBool(_dashboardMsgToggle);
}

bool get gradesMessageActive {
fetch();
return _remoteConfig.getBool(_gradesMsgToggle);
}

bool get scheduleListViewDefault {
fetch();
return _remoteConfig.getBool(_scheduleListViewDefault);
Expand Down
37 changes: 37 additions & 0 deletions lib/core/viewmodels/grades_viewmodel.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import 'package:ets_api_clients/models.dart';
import 'package:feature_discovery/feature_discovery.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:fluttertoast/fluttertoast.dart';
import 'package:notredame/core/services/remote_config_service.dart';
import 'package:stacked/stacked.dart';

// Project imports:
Expand All @@ -19,6 +20,8 @@ import 'package:notredame/ui/utils/discovery_components.dart';
class GradesViewModel extends FutureViewModel<Map<String, List<Course>>> {
/// Used to get the courses of the student
final CourseRepository _courseRepository = locator<CourseRepository>();
final RemoteConfigService remoteConfigService =
locator<RemoteConfigService>();

/// Localization class of the application.
final AppIntl _appIntl;
Expand All @@ -30,11 +33,20 @@ class GradesViewModel extends FutureViewModel<Map<String, List<Course>>> {
/// session.
final List<String> sessionOrder = [];

/// Message to display in case of urgent/important broadcast need (Firebase
/// remote config), and the associated card title
String broadcastMessage = "";
String broadcastTitle = "";
String broadcastColor = "";
String broadcastUrl = "";
String broadcastType = "";

GradesViewModel({@required AppIntl intl}) : _appIntl = intl;

@override
Future<Map<String, List<Course>>> futureToRun() async =>
_courseRepository.getCourses(fromCacheOnly: true).then((coursesCached) {
futureToRunBroadcast();
setBusy(true);
_buildCoursesBySession(coursesCached);
// ignore: return_type_invalid_for_catch_error
Expand Down Expand Up @@ -124,4 +136,29 @@ class GradesViewModel extends FutureViewModel<Map<String, List<Course>>> {
settingsManager.setBool(PreferencesFlag.discoveryStudentGrade, true);
}
}

Future<void> futureToRunBroadcast() async {
setBusyForObject(broadcastMessage, true);
setBusyForObject(broadcastTitle, true);
setBusyForObject(broadcastColor, true);
setBusyForObject(broadcastUrl, true);
setBusyForObject(broadcastType, true);

if (_appIntl.localeName == "fr") {
broadcastMessage = remoteConfigService.dashboardMessageFr;
broadcastTitle = remoteConfigService.dashboardMessageTitleFr;
} else {
broadcastMessage = remoteConfigService.dashboardMessageEn;
broadcastTitle = remoteConfigService.dashboardMessageTitleEn;
}
broadcastColor = remoteConfigService.dashboardMsgColor;
broadcastUrl = remoteConfigService.dashboardMsgUrl;
broadcastType = remoteConfigService.dashboardMsgType;

setBusyForObject(broadcastMessage, false);
setBusyForObject(broadcastTitle, false);
setBusyForObject(broadcastColor, false);
setBusyForObject(broadcastUrl, false);
setBusyForObject(broadcastType, false);
}
}
79 changes: 79 additions & 0 deletions lib/ui/views/grades_view.dart
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
// Flutter imports:
import 'package:auto_size_text/auto_size_text.dart';
import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart';

// Package imports:
import 'package:ets_api_clients/models.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:flutter_staggered_animations/flutter_staggered_animations.dart';
import 'package:notredame/core/viewmodels/dashboard_viewmodel.dart';
import 'package:notredame/ui/widgets/dismissible_card.dart';
import 'package:stacked/stacked.dart';

// Project imports:
Expand Down Expand Up @@ -97,6 +100,8 @@ class _GradesViewState extends State<GradesView> {
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
if (model.remoteConfigService.gradesMessageActive && index == 0)
_buildMessageBroadcastCard(model),
Text(sessionName,
style: const TextStyle(
fontSize: 25,
Expand All @@ -114,6 +119,80 @@ class _GradesViewState extends State<GradesView> {
),
);

Widget _buildMessageBroadcastCard(GradesViewModel model) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Il y a exactement la même méthode dans broadcast_view si je ne me trompes pas. Peut-etre ca serait worth de les extraire et faire un component reutilisable avec ?

final broadcastMsgColor = Color(int.parse(model.broadcastColor));
final broadcastMsgType = model.broadcastType;
final broadcastMsgUrl = model.broadcastUrl;
return Padding(
padding: const EdgeInsets.only(bottom: 8.0),
child: DismissibleCard(
key: UniqueKey(),
onDismissed: (DismissDirection direction) {},
isBusy: model.busy(model.broadcastMessage),
cardColor: broadcastMsgColor,
child: Padding(
padding: const EdgeInsets.fromLTRB(17, 10, 15, 20),
child: Column(mainAxisSize: MainAxisSize.min, children: [
// title row
Row(
children: [
Expanded(
child: Align(
alignment: Alignment.centerLeft,
child: Text(model.broadcastTitle,
style: Theme.of(context).primaryTextTheme.headline6),
),
),
Align(
alignment: Alignment.centerRight,
child: InkWell(
child:
getBroadcastIcon(broadcastMsgType, broadcastMsgUrl),
),
),
],
),
// main text
AutoSizeText(model.broadcastMessage ?? "",
style: Theme.of(context).primaryTextTheme.bodyText2)
]),
)),
);
}

Widget getBroadcastIcon(String type, String url) {
switch (type) {
case "warning":
return const Icon(
Icons.warning_rounded,
color: AppTheme.lightThemeBackground,
size: 36.0,
);
case "alert":
return const Icon(
Icons.error,
color: AppTheme.lightThemeBackground,
size: 36.0,
);
case "link":
return IconButton(
onPressed: () {
DashboardViewModel.launchBroadcastUrl(url);
},
icon: const Icon(
Icons.open_in_new,
color: AppTheme.lightThemeBackground,
size: 30.0,
),
);
}
return const Icon(
Icons.campaign,
color: AppTheme.lightThemeBackground,
size: 36.0,
);
}

/// Build the complete name of the session for the user local.
String _sessionName(String shortName, AppIntl intl) {
switch (shortName[0]) {
Expand Down
2 changes: 1 addition & 1 deletion pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ description: The 4th generation of ÉTSMobile, the main gateway between the Éco
# pub.dev using `pub publish`. This is preferred for private packages.
publish_to: 'none' # Remove this line if you wish to publish to pub.dev

version: 4.33.1+1
version: 4.34.0+1

environment:
sdk: ">=2.10.0 <3.0.0"
Expand Down
5 changes: 5 additions & 0 deletions test/mock/services/remote_config_service_mock.dart
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,11 @@ class RemoteConfigServiceMock extends Mock implements RemoteConfigService {
when(mock.scheduleListViewDefault).thenReturn(toReturn);
}

static void stubGetGradesEnabled(RemoteConfigServiceMock mock,
{bool toReturn = true}) {
when(mock.gradesMessageActive).thenReturn(toReturn);
}

static void stubGetBroadcastEnabled(RemoteConfigServiceMock mock,
{bool toReturn = true}) {
when(mock.dashboardMessageActive).thenReturn(toReturn);
Expand Down
13 changes: 13 additions & 0 deletions test/ui/views/grades_view_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,16 @@ import 'package:flutter_test/flutter_test.dart';
import 'package:notredame/core/managers/course_repository.dart';
import 'package:notredame/core/managers/settings_manager.dart';
import 'package:notredame/core/services/networking_service.dart';
import 'package:notredame/core/services/remote_config_service.dart';
import 'package:notredame/ui/views/grades_view.dart';
import 'package:notredame/ui/widgets/grade_button.dart';
import '../../helpers.dart';
import '../../mock/managers/course_repository_mock.dart';
import '../../mock/services/remote_config_service_mock.dart';

void main() {
CourseRepository courseRepository;
RemoteConfigService remoteConfigService;
AppIntl intl;

final Course courseSummer = Course(
Expand Down Expand Up @@ -66,6 +69,7 @@ void main() {
intl = await setupAppIntl();
setupNavigationServiceMock();
courseRepository = setupCourseRepositoryMock();
remoteConfigService = setupRemoteConfigServiceMock();
setupSettingsManagerMock();
setupAnalyticsServiceMock();
});
Expand Down Expand Up @@ -111,6 +115,9 @@ void main() {
courseRepository as CourseRepositoryMock,
toReturn: courses,
fromCacheOnly: true);
RemoteConfigServiceMock.stubGetGradesEnabled(
remoteConfigService as RemoteConfigServiceMock,
toReturn: false);

tester.binding.window.physicalSizeTestValue = const Size(800, 1410);

Expand All @@ -136,6 +143,9 @@ void main() {
CourseRepositoryMock.stubGetCourses(
courseRepository as CourseRepositoryMock,
fromCacheOnly: true);
RemoteConfigServiceMock.stubGetGradesEnabled(
remoteConfigService as RemoteConfigServiceMock,
toReturn: false);

tester.binding.window.physicalSizeTestValue = const Size(800, 1410);

Expand All @@ -160,6 +170,9 @@ void main() {
courseRepository as CourseRepositoryMock,
toReturn: courses,
fromCacheOnly: true);
RemoteConfigServiceMock.stubGetGradesEnabled(
remoteConfigService as RemoteConfigServiceMock,
toReturn: false);

tester.binding.window.physicalSizeTestValue = const Size(800, 1410);

Expand Down
21 changes: 21 additions & 0 deletions test/ui/views/student_view_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -12,20 +12,24 @@ import 'package:flutter_test/flutter_test.dart';
import 'package:notredame/core/managers/course_repository.dart';
import 'package:notredame/core/managers/settings_manager.dart';
import 'package:notredame/core/services/networking_service.dart';
import 'package:notredame/core/services/remote_config_service.dart';
import 'package:notredame/ui/views/student_view.dart';
import 'package:notredame/ui/widgets/base_scaffold.dart';
import '../../helpers.dart';
import '../../mock/managers/course_repository_mock.dart';
import '../../mock/services/analytics_service_mock.dart';
import '../../mock/services/remote_config_service_mock.dart';

void main() {
CourseRepository courseRepository;
RemoteConfigService remoteConfigService;

group('StudentView - ', () {
setUp(() async {
setupNavigationServiceMock();
setupNetworkingServiceMock();
courseRepository = setupCourseRepositoryMock();
remoteConfigService = setupRemoteConfigServiceMock();
setupSettingsManagerMock();
setupAnalyticsServiceMock();

Expand All @@ -37,6 +41,23 @@ void main() {
CourseRepositoryMock.stubGetCourses(
courseRepository as CourseRepositoryMock,
fromCacheOnly: true);

RemoteConfigServiceMock.stubGetGradesEnabled(
remoteConfigService as RemoteConfigServiceMock);
RemoteConfigServiceMock.stubGetBroadcastColor(
remoteConfigService as RemoteConfigServiceMock);
RemoteConfigServiceMock.stubGetBroadcastEn(
remoteConfigService as RemoteConfigServiceMock);
RemoteConfigServiceMock.stubGetBroadcastFr(
remoteConfigService as RemoteConfigServiceMock);
RemoteConfigServiceMock.stubGetBroadcastTitleEn(
remoteConfigService as RemoteConfigServiceMock);
RemoteConfigServiceMock.stubGetBroadcastTitleFr(
remoteConfigService as RemoteConfigServiceMock);
RemoteConfigServiceMock.stubGetBroadcastType(
remoteConfigService as RemoteConfigServiceMock);
RemoteConfigServiceMock.stubGetBroadcastUrl(
remoteConfigService as RemoteConfigServiceMock);
});

tearDown(() {
Expand Down
7 changes: 7 additions & 0 deletions test/viewmodels/grades_viewmodel_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,16 @@ import 'package:mockito/mockito.dart';
import 'package:notredame/core/managers/course_repository.dart';
import 'package:notredame/core/managers/settings_manager.dart';
import 'package:notredame/core/services/navigation_service.dart';
import 'package:notredame/core/services/remote_config_service.dart';
import 'package:notredame/core/viewmodels/grades_viewmodel.dart';
import '../helpers.dart';
import '../mock/managers/course_repository_mock.dart';
import '../mock/services/remote_config_service_mock.dart';

void main() {
TestWidgetsFlutterBinding.ensureInitialized();
CourseRepository courseRepository;
RemoteConfigService remoteConfigService;
AppIntl intl;
GradesViewModel viewModel;

Expand Down Expand Up @@ -84,10 +87,14 @@ void main() {
group('GradesViewModel -', () {
setUp(() async {
courseRepository = setupCourseRepositoryMock();
remoteConfigService = setupRemoteConfigServiceMock();
intl = await setupAppIntl();
setupSettingsManagerMock();
setupNavigationServiceMock();

RemoteConfigServiceMock.stubGetGradesEnabled(
remoteConfigService as RemoteConfigServiceMock);

viewModel = GradesViewModel(intl: intl);
});

Expand Down
Loading