Skip to content
Draft
57 changes: 55 additions & 2 deletions lib/core/managers/quick_link_repository.dart
Original file line number Diff line number Diff line change
@@ -1,18 +1,29 @@
// Dart imports:
import 'dart:convert';
import 'package:cached_network_image/cached_network_image.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:notredame/ui/utils/app_theme.dart';
import 'package:font_awesome_flutter/name_icon_mapping.dart';

// Package imports:
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';

// Project imports:
import 'package:notredame/core/constants/quick_links.dart';
import 'package:notredame/core/managers/cache_manager.dart';
import 'package:notredame/core/models/quick_link.dart';
import 'package:notredame/core/models/quick_link_data.dart';
import 'package:notredame/core/services/firebase_storage_service.dart';
import 'package:notredame/locator.dart';
import 'package:notredame/core/services/remote_config_service.dart';

class QuickLinkRepository {
final CacheManager _cacheManager = locator<CacheManager>();
final RemoteConfigService _remoteConfigService =
locator<RemoteConfigService>();
final FirebaseStorageService _storageService =
locator<FirebaseStorageService>();

static const String quickLinksCacheKey = "quickLinksCache";

Expand All @@ -36,7 +47,49 @@ class QuickLinkRepository {
quickLinksCacheKey, jsonEncode(quickLinkDataList));
}

List<QuickLink> getDefaultQuickLinks(AppIntl intl) {
return quickLinks(intl);
Future<List<QuickLink>> getDefaultQuickLinks(AppIntl intl) async {
final values = await _remoteConfigService.quicklinksValues;

if (values == null || values as List == null || (values as List).isEmpty) {
return quickLinks(intl);
}
final listValues = values as List<dynamic>;
final List<QuickLink> listQuicklink = [];

for (var i = 0; i < listValues.length; i++) {
Widget imageWidget;
final map = listValues[i] as Map<String, dynamic>;
if (map['icon'] != null) {
final String iconName = map['icon'] as String;
imageWidget = FaIcon(
faIconNameMapping['solid $iconName'],
color: AppTheme.etsLightRed,
size: 64,
);
} else if (map['remotePath'] != null) {
// from cache
final String remotePathKey = map['remotePath'] as String;
String imageUrl;
try {
imageUrl = await _cacheManager.get(remotePathKey);
} on Exception catch (_) {
imageUrl = await _storageService.getImageUrl(remotePathKey);
_cacheManager.update(remotePathKey, imageUrl);
}

imageWidget = CachedNetworkImage(
imageUrl: imageUrl,
color: AppTheme.etsLightRed,
);
}
final QuickLink quickLink =
QuickLink.fromJson(listValues[i] as Map<String, dynamic>);
quickLink.name =
intl.localeName == "fr" ? quickLink.nameFr : quickLink.nameEn;
quickLink.image = imageWidget;
listQuicklink.add(quickLink);
}

return listQuicklink;
}
}
22 changes: 18 additions & 4 deletions lib/core/models/quick_link.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,27 @@ import 'package:flutter/material.dart';

class QuickLink {
final int id;
final Widget image;
final String name;
Widget image;
final String link;
final String nameFr;
final String nameEn;
String name;

// If name is provided it will be used instead of nameFr and nameEn
QuickLink(
{required this.id,
required this.image,
required this.name,
this.image,
this.name,
this.nameFr,
this.nameEn,
required this.link});

factory QuickLink.fromJson(Map<String, dynamic> json) {
return QuickLink(
id: int.parse(json['id'] as String),
nameFr: json['nameFr'] as String,
nameEn: json['nameEn'] as String,
link: json['link'] as String,
);
}
}
57 changes: 57 additions & 0 deletions lib/core/services/course_grade_cache_service.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
// Package imports:
import 'package:firebase_analytics/firebase_analytics.dart';

/// Manage the analytics of the application
class CourseGradeCacheService {
final FirebaseAnalytics _analytics = FirebaseAnalytics.instance;

CourseGradeCacheService();

void setCourseGradeCache(String semester, String courseCode,
String courseName, double courseGrade) {
_analytics.logEvent(name: "setCourseGradeCache");
}
}

class SemesterCache {
final String semester;
final List<GradeCache> gradeCacheList;

SemesterCache({this.semester, this.gradeCacheList});

factory SemesterCache.fromJson(Map<String, dynamic> json) {
return SemesterCache(
semester: json['semester'] as String,
gradeCacheList: (json['gradeCacheList'] as List<dynamic>)
.map((e) => GradeCache.fromJson(e as Map<String, dynamic>))
.toList(),
);
}

Map<String, dynamic> toJson() => {
'semester': semester,
'gradeCacheList': gradeCacheList.map((e) => e.toJson()),
};
}

class GradeCache {
final String courseCode;
final String courseName;
final double courseGrade;

GradeCache({this.courseCode, this.courseName, this.courseGrade});

factory GradeCache.fromJson(Map<String, dynamic> json) {
return GradeCache(
courseCode: json['courseCode'] as String,
courseName: json['courseName'] as String,
courseGrade: json['courseGrade'] as double,
);
}

Map<String, dynamic> toJson() => {
'courseCode': courseCode,
'courseName': courseName,
'courseGrade': courseGrade,
};
}
43 changes: 43 additions & 0 deletions lib/core/services/firebase_storage_service.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
// Package imports:
import 'package:firebase_storage/firebase_storage.dart';
import 'package:flutter/cupertino.dart';

// Project imports:
import 'package:notredame/core/services/analytics_service.dart';
import 'package:notredame/locator.dart';

/// Manage the analytics of the application
class FirebaseStorageService {
static const String tag = "FirebaseStorageService";
static const String _defaultPath = "app-images";

FirebaseStorage firebaseStorage;

final AnalyticsService _analyticsService = locator<AnalyticsService>();

/// Reference to the app images folder in default cloud storage
Reference _appImageReferences;

@visibleForTesting
Reference get appImageReferences => _appImageReferences;

FirebaseStorageService({this.firebaseStorage}) {
firebaseStorage ??= FirebaseStorage.instance;
_appImageReferences = firebaseStorage.ref(_defaultPath);
}

/// Get the url of the image from the firebase storage
/// [fileName] is the fileName of the image in the firebase storage
Future<String> getImageUrl(String fileName) async {
try {
_appImageReferences ??= firebaseStorage.ref(_defaultPath);

final child = _appImageReferences.child(fileName);
final url = await child.getDownloadURL();
return url;
} on Exception catch (e) {
_analyticsService.logError(tag, "Error while getting the image url", e);
rethrow;
}
}
}
12 changes: 8 additions & 4 deletions lib/core/services/remote_config_service.dart
Original file line number Diff line number Diff line change
@@ -1,14 +1,12 @@
//SERVICE

// Package imports:
import 'dart:convert';

import 'package:firebase_remote_config/firebase_remote_config.dart';

// Project imports:
import 'package:notredame/core/services/analytics_service.dart';
import 'package:notredame/locator.dart';

//OTHERS

/// Manage the analytics of the application
class RemoteConfigService {
static const String tag = "RemoteConfigService";
Expand All @@ -26,6 +24,7 @@ class RemoteConfigService {
static const _dashboardMsgColor = "dashboard_message_color";
static const _dashboardMsgUrl = "dashboard_message_url";
static const _dashboardMsgType = "dashboard_message_type";
static const _quicklinksValues = "quicklinks_values";

static const _scheduleListViewDefault = "schedule_list_view_default";
final FirebaseRemoteConfig _remoteConfig = FirebaseRemoteConfig.instance;
Expand Down Expand Up @@ -108,6 +107,11 @@ class RemoteConfigService {
return _remoteConfig.getString(_dashboardMsgType);
}

Future<dynamic> get quicklinksValues async {
await fetch();
return jsonDecode(_remoteConfig.getString(_quicklinksValues));
}

Future<void> fetch() async {
final AnalyticsService analyticsService = locator<AnalyticsService>();
try {
Expand Down
6 changes: 3 additions & 3 deletions lib/core/viewmodels/quick_links_viewmodel.dart
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ class QuickLinksViewModel extends FutureViewModel<List<QuickLink>> {

// otherwise, return quickLinks according to the cache
final defaultQuickLinks =
_quickLinkRepository.getDefaultQuickLinks(_appIntl);
await _quickLinkRepository.getDefaultQuickLinks(_appIntl);
quickLinkDataList.sort((a, b) => a.index.compareTo(b.index));
return quickLinkDataList
.map((data) => defaultQuickLinks
Expand All @@ -47,8 +47,8 @@ class QuickLinksViewModel extends FutureViewModel<List<QuickLink>> {
final currentQuickLinkIds = quickLinkList.map((e) => e.id).toList();

// Return those not in current quick links but in default list
return _quickLinkRepository
.getDefaultQuickLinks(_appIntl)
final defaults = await _quickLinkRepository.getDefaultQuickLinks(_appIntl);
return defaults
.where((element) => !currentQuickLinkIds.contains(element.id))
.toList();
}
Expand Down
2 changes: 2 additions & 0 deletions lib/locator.dart
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import 'package:notredame/core/managers/settings_manager.dart';
import 'package:notredame/core/managers/user_repository.dart';
import 'package:notredame/core/services/analytics_service.dart';
import 'package:notredame/core/services/app_widget_service.dart';
import 'package:notredame/core/services/firebase_storage_service.dart';
import 'package:notredame/core/services/github_api.dart';
import 'package:notredame/core/services/in_app_review_service.dart';
import 'package:notredame/core/services/internal_info_service.dart';
Expand All @@ -35,6 +36,7 @@ void setupLocator() {
locator.registerLazySingleton(() => const FlutterSecureStorage());
locator.registerLazySingleton(() => PreferencesService());
locator.registerLazySingleton(() => NetworkingService());
locator.registerLazySingleton(() => FirebaseStorageService());
locator.registerLazySingleton(() => SirenFlutterService());
locator.registerLazySingleton(() => AppWidgetService());
locator.registerLazySingleton(() => InAppReviewService());
Expand Down
56 changes: 56 additions & 0 deletions pubspec.lock
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,27 @@ packages:
url: "https://pub.dev"
source: hosted
version: "8.9.2"
cached_network_image:
dependency: "direct main"
description:
name: cached_network_image
url: "https://pub.dartlang.org"
source: hosted
version: "3.2.3"
cached_network_image_platform_interface:
dependency: transitive
description:
name: cached_network_image_platform_interface
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.0"
cached_network_image_web:
dependency: transitive
description:
name: cached_network_image_web
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.2"
calendar_view:
dependency: "direct main"
description:
Expand Down Expand Up @@ -426,6 +447,27 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.4.25"
firebase_storage:
dependency: "direct main"
description:
name: firebase_storage
url: "https://pub.dartlang.org"
source: hosted
version: "11.2.8"
firebase_storage_platform_interface:
dependency: transitive
description:
name: firebase_storage_platform_interface
url: "https://pub.dartlang.org"
source: hosted
version: "4.4.7"
firebase_storage_web:
dependency: transitive
description:
name: firebase_storage_web
url: "https://pub.dartlang.org"
source: hosted
version: "3.6.8"
fixnum:
dependency: transitive
description:
Expand All @@ -439,6 +481,13 @@ packages:
description: flutter
source: sdk
version: "0.0.0"
flutter_blurhash:
dependency: transitive
description:
name: flutter_blurhash
url: "https://pub.dartlang.org"
source: hosted
version: "0.7.0"
flutter_cache_manager:
dependency: "direct main"
description:
Expand Down Expand Up @@ -934,6 +983,13 @@ packages:
url: "https://pub.dev"
source: hosted
version: "0.5.0"
octo_image:
dependency: transitive
description:
name: octo_image
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.2"
package_config:
dependency: transitive
description:
Expand Down
Loading