Skip to content

Commit

Permalink
Bugfix/quicklinks (#106)
Browse files Browse the repository at this point in the history
* Fix quicklinks

* Move import

* Open link in app

* Add link web view test and make webView work on IOS

* Surround launchInBrowser in try catch

* Move logic to a viewmodel and add/fix tests

* Pass quicklink instead of only the link and add await on github_api refactor

* Fix tests and reorder imports

* Refactor to use ViewModelBuilder

* Add progressbar while the page is loading

* Fix test

* Use buildLoading and add verify no more interfactions

* Use baseScaffold

Co-authored-by: Camille Brulotte <[email protected]>
Co-authored-by: Camille Brulotte <>
  • Loading branch information
camillebrulotte and Camille Brulotte authored May 8, 2021
1 parent 80cb15d commit a773a3f
Show file tree
Hide file tree
Showing 15 changed files with 343 additions and 72 deletions.
3 changes: 2 additions & 1 deletion android/app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@
<application
android:name="io.flutter.app.FlutterApplication"
android:label="ÉTS Mobile"
android:icon="@mipmap/launcher_icon">
android:icon="@mipmap/launcher_icon"
android:usesCleartextTraffic="true">
<activity
android:name=".MainActivity"
android:launchMode="singleTop"
Expand Down
7 changes: 7 additions & 0 deletions ios/Runner/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,13 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
<key>NSAllowsArbitraryLoadsInWebContent</key>
<true/>
</dict>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleExecutable</key>
Expand Down
1 change: 1 addition & 0 deletions lib/core/constants/router_paths.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ class RouterPaths {
static const String student = "/student";
static const String gradeDetails = "/student/grade/details";
static const String ets = "/ets";
static const String webView = "/ets/web-view";
static const String security = "/ets/security";
static const String more = "/more";
static const String settings = "/more/settings";
Expand Down
15 changes: 10 additions & 5 deletions lib/core/services/github_api.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,13 @@ import 'package:package_info/package_info.dart';
import 'package:path_provider/path_provider.dart';
import 'package:flutter_config/flutter_config.dart';

// SERVICES
import 'package:notredame/core/services/internal_info_service.dart';

// OTHER
import 'package:notredame/locator.dart';


class GithubApi {
static const String tag = "GithubApi";
static const String tagError = "$tag - Error";
Expand All @@ -18,6 +25,8 @@ class GithubApi {

GitHub _github;

final InternalInfoService _internalInfoService = locator<InternalInfoService>();

GithubApi() {
String githubApiToken;
if (kDebugMode) {
Expand Down Expand Up @@ -55,11 +64,7 @@ class GithubApi {
"```$feedbackText```\n\n"
"**Screenshot** \n"
"![screenshot](https://github.com/$_repositoryReportSlug/blob/main/$fileName?raw=true)\n\n"
"**Device Infos** \n"
"- **Version:** ${packageInfo.version} \n"
"- **Build number:** ${packageInfo.buildNumber} \n"
"- **Platform operating system:** ${Platform.operatingSystem} \n"
"- **Platform operating system version:** ${Platform.operatingSystemVersion} \n",
"${await _internalInfoService.getDeviceInfoForErrorReporting()}",
labels: ['bug', 'platform: ${Platform.operatingSystem}']));
}

Expand Down
17 changes: 17 additions & 0 deletions lib/core/services/internal_info_service.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// FLUTTER / DART / THIRD-PARTIES
import 'dart:io';
import 'package:package_info/package_info.dart';

class InternalInfoService {

// Build the error message with the current device informations
Future<String> getDeviceInfoForErrorReporting() async {
final PackageInfo packageInfo = await PackageInfo.fromPlatform();

return "**Device Infos** \n"
"- **Version:** ${packageInfo.version} \n"
"- **Build number:** ${packageInfo.buildNumber} \n"
"- **Platform operating system:** ${Platform.operatingSystem} \n"
"- **Platform operating system version:** ${Platform.operatingSystemVersion} \n";
}
}
65 changes: 65 additions & 0 deletions lib/core/viewmodels/web_link_card_viewmodel.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
// FLUTTER / DART / THIRD-PARTIES
import 'package:notredame/core/models/quick_link.dart';
import 'package:stacked/stacked.dart';

// MODELS
import 'package:flutter_inappwebview/flutter_inappwebview.dart';

// CONSTANTS
import 'package:notredame/core/constants/router_paths.dart';

// SERVICES
import 'package:notredame/core/services/analytics_service.dart';
import 'package:notredame/core/services/navigation_service.dart';
import 'package:notredame/core/services/internal_info_service.dart';

// UTILS
import 'package:notredame/ui/utils/app_theme.dart';

// OTHER
import 'package:notredame/locator.dart';

class WebLinkCardViewModel extends BaseViewModel {

/// used to redirect on the security.
final NavigationService _navigationService = locator<NavigationService>();

final AnalyticsService _analyticsService = locator<AnalyticsService>();

final InternalInfoService _internalInfoService = locator<InternalInfoService>();

/// used to open a website or the security view
Future<void> onLinkClicked(QuickLink link) async {
if (link.link == 'security') {
_navigationService.pushNamed(RouterPaths.security);
} else {
try {
await launchInBrowser(link.link);
} catch (error) {
await launchWebView(error.toString(), link);
}
}
}

/// used to open a website inside AndroidChromeCustomTabs or SFSafariViewController
Future<void> launchInBrowser(String url) async {
final ChromeSafariBrowser browser = ChromeSafariBrowser();
await browser.open(
url: Uri.parse(url),
options: ChromeSafariBrowserClassOptions(
android: AndroidChromeCustomTabsOptions(
addDefaultShareMenuItem: false,
enableUrlBarHiding: true,
toolbarBackgroundColor: AppTheme.etsLightRed),
ios: IOSSafariOptions(
barCollapsingEnabled: true,
preferredBarTintColor: AppTheme.etsLightRed)));
}

Future<void> launchWebView(String error, QuickLink link) async {
final String errorMessage = await _internalInfoService.getDeviceInfoForErrorReporting();

_analyticsService.logError("web_link_card", "**Error message : $error\n$errorMessage");
_navigationService.pushNamed(RouterPaths.webView, arguments: link);
}
}
4 changes: 3 additions & 1 deletion lib/locator.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:get_it/get_it.dart';
import 'package:http/http.dart' as http;
import 'package:logger/logger.dart';
import 'package:notredame/core/services/github_api.dart';

// SERVICES
import 'package:notredame/core/services/navigation_service.dart';
Expand All @@ -12,6 +11,8 @@ import 'package:notredame/core/services/mon_ets_api.dart';
import 'package:notredame/core/services/preferences_service.dart';
import 'package:notredame/core/services/signets_api.dart';
import 'package:notredame/core/services/rive_animation_service.dart';
import 'package:notredame/core/services/github_api.dart';
import 'package:notredame/core/services/internal_info_service.dart';

// MANAGERS
import 'package:notredame/core/managers/user_repository.dart';
Expand All @@ -27,6 +28,7 @@ void setupLocator() {
locator.registerLazySingleton(() => AnalyticsService());
locator.registerLazySingleton(() => RiveAnimationService());
locator.registerLazySingleton(() => MonETSApi(http.Client()));
locator.registerLazySingleton(() => InternalInfoService());
locator.registerLazySingleton(() => GithubApi());
locator.registerLazySingleton(() => SignetsApi());
locator.registerLazySingleton(() => const FlutterSecureStorage());
Expand Down
9 changes: 9 additions & 0 deletions lib/ui/router.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
// FLUTTER / DART / THIRD-PARTIES
import 'package:flutter/material.dart';

// MODELS
import 'package:notredame/core/models/quick_link.dart';

// ROUTES
import 'package:notredame/core/constants/router_paths.dart';

Expand All @@ -21,6 +24,9 @@ import 'package:notredame/ui/views/contributors_view.dart';
import 'package:notredame/ui/views/choose_language_view.dart';
import 'package:notredame/ui/views/grade_details_view.dart';

// WIDGETS
import 'package:notredame/ui/widgets/link_web_view.dart';

class AppRouter {
// ignore: missing_return
static Route<dynamic> generateRoute(RouteSettings routeSettings) {
Expand All @@ -45,6 +51,9 @@ class AppRouter {
return PageRouteBuilder(
settings: RouteSettings(name: routeSettings.name),
pageBuilder: (_, __, ___) => QuickLinksView());
case RouterPaths.webView:
return PageRouteBuilder(
pageBuilder: (_, __, ___) => LinkWebView(routeSettings.arguments as QuickLink));
case RouterPaths.security:
return PageRouteBuilder(
settings: RouteSettings(name: routeSettings.name),
Expand Down
46 changes: 46 additions & 0 deletions lib/ui/widgets/link_web_view.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
// FLUTTER / DART / THIRD-PARTIES
import 'package:flutter/material.dart';
import 'package:webview_flutter/webview_flutter.dart';

// MODELS
import 'package:notredame/core/models/quick_link.dart';

// WIDGETS
import 'package:notredame/ui/widgets/base_scaffold.dart';

class LinkWebView extends StatefulWidget {
final QuickLink _links;

const LinkWebView(this._links);

@override
_LinkWebViewState createState() => _LinkWebViewState();
}

class _LinkWebViewState extends State<LinkWebView> {
bool isLoading = true;

@override
Widget build(BuildContext context) {
return BaseScaffold(
isLoading: isLoading,
showBottomBar: false,
appBar: AppBar(
title: Text(widget._links.name),
),
body: Stack(
children: <Widget>[
WebView(
initialUrl: widget._links.link,
javascriptMode: JavascriptMode.unrestricted,
onPageFinished: (finish) {
setState(() {
isLoading = false;
});
},
),
],
),
);
}
}
100 changes: 37 additions & 63 deletions lib/ui/widgets/web_link_card.dart
Original file line number Diff line number Diff line change
@@ -1,82 +1,56 @@
// FLUTTER / DART / THIRD-PARTIES
import 'package:flutter/material.dart';
import 'package:flutter_inappwebview/flutter_inappwebview.dart';
import 'package:stacked/stacked.dart';

// CONSTANT
import 'package:notredame/core/constants/router_paths.dart';
// VIEWMODEL
import 'package:notredame/core/viewmodels/web_link_card_viewmodel.dart';

// MODEL
import 'package:notredame/core/models/quick_link.dart';

// SERVICE
import 'package:notredame/core/services/navigation_service.dart';

// OTHER
import 'package:notredame/locator.dart';
// UTILS
import 'package:notredame/ui/utils/app_theme.dart';

class WebLinkCard extends StatelessWidget {
final QuickLink _links;

/// used to redirect on the security.
final NavigationService _navigationService = locator<NavigationService>();

WebLinkCard(this._links);
const WebLinkCard(this._links);

@override
Widget build(BuildContext context) {
return SizedBox(
width: MediaQuery.of(context).size.width / 3.1,
height: 130,
child: Card(
elevation: 4.0,
child: InkWell(
onTap: () => _onLinkClicked(_links.link),
splashColor: AppTheme.etsLightRed.withAlpha(50),
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
children: [
Expanded(
flex: 40,
child: Image.asset(_links.image, color: AppTheme.etsLightRed),
),
FittedBox(
fit: BoxFit.fitWidth,
child: Text(
_links.name,
style: const TextStyle(color: Colors.red, fontSize: 18.0),
),
Widget build(BuildContext context) =>
ViewModelBuilder<WebLinkCardViewModel>.reactive(
viewModelBuilder: () => WebLinkCardViewModel(),
builder: (context, model, child) {
return SizedBox(
width: MediaQuery.of(context).size.width / 3.1,
height: 130,
child: Card(
elevation: 4.0,
child: InkWell(
onTap: () => model.onLinkClicked(_links),
splashColor: AppTheme.etsLightRed.withAlpha(50),
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
children: [
Expanded(
flex: 40,
child: Image.asset(_links.image, color: AppTheme.etsLightRed),
),
FittedBox(
fit: BoxFit.fitWidth,
child: Text(
_links.name,
style: const TextStyle(color: Colors.red, fontSize: 18.0),
),
),
],
),
],
),
),
),
),
),
);
}
);
}

/// used to open a website or the security view
void _onLinkClicked(String link) {
if (link == 'security') {
_navigationService.pushNamed(RouterPaths.security);
} else {
_launchInBrowser(link);
}
}

/// used to open a website inside AndroidChromeCustomTabs or SFSafariViewController
Future<void> _launchInBrowser(String url) async {
final ChromeSafariBrowser browser = ChromeSafariBrowser();
await browser.open(
url: Uri.parse(url),
options: ChromeSafariBrowserClassOptions(
android: AndroidChromeCustomTabsOptions(
addDefaultShareMenuItem: false,
enableUrlBarHiding: true,
toolbarBackgroundColor: Colors.red),
ios: IOSSafariOptions(
barCollapsingEnabled: true,
preferredBarTintColor: Colors.red)));
}
}

Loading

0 comments on commit a773a3f

Please sign in to comment.