Skip to content

Commit

Permalink
Merge pull request #2541 from instructure/release/student-7.5.3-266
Browse files Browse the repository at this point in the history
Release Student 7.5.3 (266)
  • Loading branch information
hermannakos authored Sep 5, 2024
2 parents cb6bb73 + 4df9836 commit 8fde046
Show file tree
Hide file tree
Showing 195 changed files with 5,600 additions and 902 deletions.
2 changes: 1 addition & 1 deletion android-vault
10 changes: 10 additions & 0 deletions apps/flutter_parent/lib/l10n/app_localizations.dart
Original file line number Diff line number Diff line change
Expand Up @@ -1715,4 +1715,14 @@ class AppLocalizations {

String get needToEnablePermission =>
Intl.message('You need to enable exact alarm permission for this action', desc: 'Error message when the user tries to set a reminder without the permission');

String get submissionAndRubric => Intl.message(
'Submission & Rubric',
desc: 'Button text for Submission and Rubric on Assignment Details Screen'
);

String get submission => Intl.message(
'Submission',
desc: 'Title for WebView screen when opening submission'
);
}
3 changes: 2 additions & 1 deletion apps/flutter_parent/lib/network/utils/analytics.dart
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@
import 'package:firebase_crashlytics/firebase_crashlytics.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter_parent/network/api/heap_api.dart';
import 'package:flutter_parent/utils/features_utils.dart';
import 'package:flutter_parent/utils/debug_flags.dart';
import 'package:flutter_parent/utils/features_utils.dart';

/// Event names
/// The naming scheme for the majority of these is found in a google doc so that we can be consistent
Expand Down Expand Up @@ -54,6 +54,7 @@ class AnalyticsEventConstants {
static const USER_PROPERTY_BUILD_TYPE = 'build_type';
static const USER_PROPERTY_OS_VERSION = 'os_version';
static const VIEWED_OLD_REMINDER_MESSAGE = 'viewed_old_reminder_message';
static const SUBMISSION_AND_RUBRIC_INTERACTION = 'submission_and_rubric_interaction';
}

/// (Copied from canvas-api-2, make sure to stay in sync)
Expand Down
23 changes: 18 additions & 5 deletions apps/flutter_parent/lib/router/panda_router.dart
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

import 'dart:convert';
import 'dart:core';

import 'package:fluro/fluro.dart';
Expand All @@ -34,13 +35,13 @@ import 'package:flutter_parent/screens/dashboard/dashboard_screen.dart';
import 'package:flutter_parent/screens/domain_search/domain_search_screen.dart';
import 'package:flutter_parent/screens/events/event_details_screen.dart';
import 'package:flutter_parent/screens/help/help_screen.dart';
import 'package:flutter_parent/screens/settings/legal_screen.dart';
import 'package:flutter_parent/screens/help/terms_of_use_screen.dart';
import 'package:flutter_parent/screens/inbox/conversation_list/conversation_list_screen.dart';
import 'package:flutter_parent/screens/login_landing_screen.dart';
import 'package:flutter_parent/screens/not_a_parent_screen.dart';
import 'package:flutter_parent/screens/pairing/qr_pairing_screen.dart';
import 'package:flutter_parent/screens/qr_login/qr_login_tutorial_screen.dart';
import 'package:flutter_parent/screens/settings/legal_screen.dart';
import 'package:flutter_parent/screens/settings/settings_screen.dart';
import 'package:flutter_parent/screens/splash/splash_screen.dart';
import 'package:flutter_parent/screens/web_login/web_login_screen.dart';
Expand Down Expand Up @@ -152,8 +153,11 @@ class PandaRouter {

static final String _simpleWebView = '/internal';

static String simpleWebViewRoute(String url, String infoText) =>
'/internal?${_RouterKeys.url}=${Uri.encodeQueryComponent(url)}&${_RouterKeys.infoText}=${Uri.encodeQueryComponent(infoText)}';
static String simpleWebViewRoute(String url, String infoText, bool limitWebAccess) =>
'/internal?${_RouterKeys.url}=${Uri.encodeQueryComponent(url)}&${_RouterKeys.infoText}=${Uri.encodeQueryComponent(infoText)}&${_RouterKeys.limitWebAccess}=${limitWebAccess}';

static String submissionWebViewRoute(String url, String title, Map<String, String> cookies, bool limitWebAccess) =>
'/internal?${_RouterKeys.url}=${Uri.encodeQueryComponent(url)}&${_RouterKeys.title}=${Uri.encodeQueryComponent(title)}&${_RouterKeys.cookies}=${jsonEncode(cookies)}&${_RouterKeys.limitWebAccess}=${limitWebAccess}';

static String settings() => '/settings';

Expand Down Expand Up @@ -376,7 +380,13 @@ class PandaRouter {
static Handler _simpleWebViewHandler = Handler(handlerFunc: (BuildContext? context, Map<String, List<String>> params) {
final url = params[_RouterKeys.url]![0];
final infoText = params[_RouterKeys.infoText]?.elementAt(0);
return SimpleWebViewScreen(url, url, infoText: infoText == null || infoText == 'null' ? null : infoText);
final titleParam = params[_RouterKeys.title]?.firstOrNull;
final title = (titleParam == null || titleParam.isEmpty) ? url : titleParam;
final cookiesParam = params[_RouterKeys.cookies]?.firstOrNull;
final cookies = (cookiesParam == null || cookiesParam.isEmpty) ? {} : jsonDecode(cookiesParam);
final limitWebAccess = params[_RouterKeys.limitWebAccess]?.firstOrNull == 'true';
return SimpleWebViewScreen(url, title, limitWebAccess,
infoText: infoText == null || infoText == 'null' ? null : infoText, initialCookies: cookies);
});

static Handler _syllabusHandler = Handler(handlerFunc: (BuildContext? context, Map<String, List<String>> params) {
Expand Down Expand Up @@ -461,7 +471,7 @@ class PandaRouter {
final url = await _interactor.getAuthUrl(link);
if (limitWebAccess) {
// Special case for limit webview access flag (We don't want them to be able to navigate within the webview)
locator<QuickNav>().pushRoute(context, simpleWebViewRoute(url, L10n(context).webAccessLimitedMessage));
locator<QuickNav>().pushRoute(context, simpleWebViewRoute(url, L10n(context).webAccessLimitedMessage, true));
} else if (await locator<UrlLauncher>().canLaunch(link) ?? false) {
// No native route found, let's launch the url if possible, or show an error toast
locator<UrlLauncher>().launch(url);
Expand Down Expand Up @@ -519,6 +529,9 @@ class _RouterKeys {
static final accountName = 'accountName';
static final eventId = 'eventId';
static final infoText = 'infoText';
static final title = 'title';
static final cookies = 'cookies';
static final limitWebAccess = 'limitWebAccess';
static final isCreatingAccount = 'isCreatingAccount';
static final loginFlow = 'loginFlow';
static final qrLoginUrl = 'qrLoginUrl';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import 'package:flutter_parent/models/assignment.dart';
import 'package:flutter_parent/models/reminder.dart';
import 'package:flutter_parent/models/user.dart';
import 'package:flutter_parent/network/utils/api_prefs.dart';
import 'package:flutter_parent/router/panda_router.dart';
import 'package:flutter_parent/screens/assignments/assignment_details_interactor.dart';
import 'package:flutter_parent/screens/assignments/grade_cell.dart';
import 'package:flutter_parent/screens/inbox/create_conversation/create_conversation_screen.dart';
Expand All @@ -33,6 +34,7 @@ import 'package:flutter_parent/utils/service_locator.dart';
import 'package:flutter_svg/svg.dart';
import 'package:permission_handler/permission_handler.dart';

import '../../network/utils/analytics.dart';
import '../../utils/veneers/flutter_snackbar_veneer.dart';

class AssignmentDetailsScreen extends StatefulWidget {
Expand Down Expand Up @@ -183,6 +185,30 @@ class _AssignmentDetailsScreenState extends State<AssignmentDetailsScreen> {
],
),
),
Divider(),
Padding(
padding: const EdgeInsets.only(top: 16.0, bottom: 16.0),
child: OutlinedButton(
onPressed: () {
_onSubmissionAndRubricClicked(assignment.htmlUrl, l10n.submission);
},
child: Align(
alignment: Alignment.center,
child: Row(mainAxisSize: MainAxisSize.min, children: [
Text(
l10n.submissionAndRubric,
style: textTheme.titleMedium?.copyWith(color: ParentTheme.of(context)?.studentColor),
),
SizedBox(width: 6),
Icon(CanvasIconsSolid.arrow_open_right, color: ParentTheme.of(context)?.studentColor, size: 14)
])),
style: OutlinedButton.styleFrom(
minimumSize: Size(double.infinity, 48),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(6.0),
),
side: BorderSide(width: 0.5, color: ParentTheme.of(context)?.onSurfaceColor ?? Colors.grey),
))),
if (!fullyLocked) ...[
Divider(),
..._rowTile(
Expand Down Expand Up @@ -372,4 +398,17 @@ class _AssignmentDetailsScreenState extends State<AssignmentDetailsScreen> {
locator.get<QuickNav>().push(context, screen);
}
}

_onSubmissionAndRubricClicked(String? assignmentUrl, String title) {
if (assignmentUrl == null) return;
final parentId = ApiPrefs.getUser()?.id ?? 0;
final currentStudentId = _currentStudent?.id ?? 0;
locator<QuickNav>().pushRoute(context, PandaRouter.submissionWebViewRoute(
assignmentUrl,
title,
{"k5_observed_user_for_$parentId": "$currentStudentId"},
false
));
locator<Analytics>().logEvent(AnalyticsEventConstants.SUBMISSION_AND_RUBRIC_INTERACTION);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,24 @@ import 'package:flutter_parent/utils/design/parent_theme.dart';
import 'package:flutter_parent/utils/web_view_utils.dart';
import 'package:webview_flutter/webview_flutter.dart';

import '../../service_locator.dart';
import '../../url_launcher.dart';

class SimpleWebViewScreen extends StatefulWidget {
final String _url;
final String _title;
final String? _infoText;
final String url;
final String title;
final String? infoText;
final Map<dynamic, dynamic>? initialCookies;
final bool limitWebAccess;

SimpleWebViewScreen(this._url, this._title, {String? infoText}) : _infoText = infoText;
SimpleWebViewScreen(
this.url,
this.title,
this.limitWebAccess, {
String? infoText,
Map<dynamic, dynamic>? initialCookies,
}) : this.infoText = infoText,
this.initialCookies = initialCookies;

@override
State<StatefulWidget> createState() => _SimpleWebViewScreenState();
Expand All @@ -43,7 +55,7 @@ class _SimpleWebViewScreenState extends State<SimpleWebViewScreen> {
backgroundColor: Colors.transparent,
iconTheme: Theme.of(context).iconTheme,
bottom: ParentTheme.of(context)?.appBarDivider(shadowInLightMode: false),
title: Text(widget._title, style: Theme.of(context).textTheme.titleLarge),
title: Text(widget.title, style: Theme.of(context).textTheme.titleLarge),
),
body: WebView(
javascriptMode: JavascriptMode.unrestricted,
Expand All @@ -52,34 +64,51 @@ class _SimpleWebViewScreenState extends State<SimpleWebViewScreen> {
navigationDelegate: _handleNavigation,
onWebViewCreated: (controller) {
_controller = controller;
controller.loadUrl(widget._url);
controller.loadUrl(widget.url);
},
onPageFinished: _handlePageLoaded,
initialCookies: _getCookies(),
),
),
);
}

NavigationDecision _handleNavigation(NavigationRequest request) {
if (!request.isForMainFrame || widget._url.startsWith(request.url)) return NavigationDecision.navigate;
if (request.url.contains('/download?download_frd=')) {
locator<UrlLauncher>().launch(request.url);
return NavigationDecision.prevent;
}
if (!request.isForMainFrame || widget.url.startsWith(request.url) || !widget.limitWebAccess) return NavigationDecision.navigate;
return NavigationDecision.prevent;
}

void _handlePageLoaded(String url) async {
// If there's no info to show, just return
if (widget._infoText == null || widget._infoText!.isEmpty) return;
if (widget.infoText == null || widget.infoText!.isEmpty) return;

// Run javascript to show the info alert
await _controller?.evaluateJavascript(_showAlertJavascript);
}

String _getDomain() {
final uri = Uri.parse(widget.url);
return uri.host;
}

List<WebViewCookie> _getCookies() {
return widget.initialCookies?.entries
.map((entry) => WebViewCookie(name: entry.key.toString(), value: entry.value.toString(), domain: _getDomain()))
.toList() ??
[];
}

String get _showAlertJavascript => """
const floatNode = `<div id="flash_message_holder_mobile" style="z-index: 10000; position: fixed; bottom: 0; left: 0; right: 0; margin: 16px; width: auto;">
<div class="ic-flash-info" aria-hidden="true" style="width: unset; max-width: 475px">
<div class="ic-flash__icon">
<i class="icon-info"></i>
</div>
${widget._infoText}
${widget.infoText}
<button type="button" class="Button Button--icon-action close_link">
<i class="icon-x"></i>
</button>
Expand Down
19 changes: 15 additions & 4 deletions apps/flutter_parent/test/router/panda_router_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -25,20 +25,19 @@ import 'package:flutter_parent/screens/announcements/announcement_details_screen
import 'package:flutter_parent/screens/assignments/assignment_details_screen.dart';
import 'package:flutter_parent/screens/calendar/calendar_screen.dart';
import 'package:flutter_parent/screens/calendar/calendar_widget/calendar_widget.dart';
import 'package:flutter_parent/screens/courses/details/course_details_interactor.dart';
import 'package:flutter_parent/screens/courses/details/course_details_screen.dart';
import 'package:flutter_parent/screens/courses/routing_shell/course_routing_shell_screen.dart';
import 'package:flutter_parent/screens/dashboard/dashboard_screen.dart';
import 'package:flutter_parent/screens/domain_search/domain_search_screen.dart';
import 'package:flutter_parent/screens/events/event_details_screen.dart';
import 'package:flutter_parent/screens/help/help_screen.dart';
import 'package:flutter_parent/screens/settings/legal_screen.dart';
import 'package:flutter_parent/screens/help/terms_of_use_screen.dart';
import 'package:flutter_parent/screens/inbox/conversation_list/conversation_list_screen.dart';
import 'package:flutter_parent/screens/login_landing_screen.dart';
import 'package:flutter_parent/screens/not_a_parent_screen.dart';
import 'package:flutter_parent/screens/pairing/qr_pairing_screen.dart';
import 'package:flutter_parent/screens/qr_login/qr_login_tutorial_screen.dart';
import 'package:flutter_parent/screens/settings/legal_screen.dart';
import 'package:flutter_parent/screens/settings/settings_screen.dart';
import 'package:flutter_parent/screens/splash/splash_screen.dart';
import 'package:flutter_parent/screens/web_login/web_login_screen.dart';
Expand All @@ -55,7 +54,6 @@ import '../utils/accessibility_utils.dart';
import '../utils/canvas_model_utils.dart';
import '../utils/platform_config.dart';
import '../utils/test_app.dart';
import '../utils/test_helpers/mock_helpers.dart';
import '../utils/test_helpers/mock_helpers.mocks.dart';

final _analytics = MockAnalytics();
Expand Down Expand Up @@ -369,6 +367,19 @@ void main() {
expect((widget as CourseRoutingShellScreen).courseId, courseId);
expect((widget).type, CourseShellType.frontPage);
});

test('submissionWebViewRoute returns SimpleWebViewScreen', () {
final url = 'https://test.instructure.com';
final title = 'Title';
final cookies = {'key': 'value'};
final widget = _getWidgetFromRoute(PandaRouter.submissionWebViewRoute(url, title, cookies, false)) as SimpleWebViewScreen;

expect(widget, isA<SimpleWebViewScreen>());
expect(widget.url, url);
expect(widget.title, title);
expect(widget.initialCookies, cookies);
expect(widget.limitWebAccess, false);
});
});

group('external url handler', () {
Expand Down Expand Up @@ -584,7 +595,7 @@ void main() {
);

verify(_analytics.logMessage('Attempting to route INTERNAL url: $url')).called(1);
verify(_mockNav.pushRoute(any, PandaRouter.simpleWebViewRoute(url, AppLocalizations().webAccessLimitedMessage)));
verify(_mockNav.pushRoute(any, PandaRouter.simpleWebViewRoute(url, AppLocalizations().webAccessLimitedMessage, true)));
});
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import 'package:flutter_parent/screens/inbox/create_conversation/create_conversa
import 'package:flutter_parent/utils/common_widgets/error_panda_widget.dart';
import 'package:flutter_parent/utils/common_widgets/loading_indicator.dart';
import 'package:flutter_parent/utils/common_widgets/web_view/html_description_tile.dart';
import 'package:flutter_parent/utils/common_widgets/web_view/simple_web_view_screen.dart';
import 'package:flutter_parent/utils/common_widgets/web_view/web_content_interactor.dart';
import 'package:flutter_parent/utils/core_extensions/date_time_extensions.dart';
import 'package:flutter_parent/utils/design/canvas_icons_solid.dart';
Expand All @@ -48,7 +49,6 @@ import 'package:permission_handler/permission_handler.dart';
import '../../utils/accessibility_utils.dart';
import '../../utils/platform_config.dart';
import '../../utils/test_app.dart';
import '../../utils/test_helpers/mock_helpers.dart';
import '../../utils/test_helpers/mock_helpers.mocks.dart';

void main() {
Expand Down Expand Up @@ -621,4 +621,45 @@ void main() {
expect(find.text(AppLocalizations().assignmentRemindMeDescription), findsOneWidget);
expect((tester.widget(find.byType(Switch)) as Switch).value, false);
});

testWidgetsWithAccessibilityChecks('shows Submission & Rubric button', (tester) async {
when(interactor.loadAssignmentDetails(any, courseId, assignmentId, studentId)).thenAnswer((_) async => AssignmentDetails(assignment: assignment));

await tester.pumpWidget(TestApp(
AssignmentDetailsScreen(
courseId: courseId,
assignmentId: assignmentId,
),
platformConfig: PlatformConfig(mockApiPrefs: {ApiPrefs.KEY_CURRENT_STUDENT: json.encode(serialize(student))}),
));

// Pump for a duration since we're delaying webview load for the animation
await tester.pumpAndSettle(Duration(seconds: 1));

expect(find.text(AppLocalizations().submissionAndRubric), findsOneWidget);
});

testWidgetsWithAccessibilityChecks('Submission & Rubric button opens SimpleWebViewScreen', (tester) async {
when(interactor.loadAssignmentDetails(any, courseId, assignmentId, studentId)).thenAnswer((_) async => AssignmentDetails(assignment: assignment));

await tester.pumpWidget(TestApp(
AssignmentDetailsScreen(
courseId: courseId,
assignmentId: assignmentId,
),
platformConfig: PlatformConfig(mockApiPrefs: {ApiPrefs.KEY_CURRENT_STUDENT: json.encode(serialize(student))}, initWebview: true),
));

// Pump for a duration since we're delaying webview load for the animation
await tester.pumpAndSettle(Duration(seconds: 1));

await tester.tap(find.text(AppLocalizations().submissionAndRubric));
await tester.pumpAndSettle();

// Check to make sure we're on the SimpleWebViewScreen screen
expect(find.byType(SimpleWebViewScreen), findsOneWidget);

// Check that we have the correct title
expect(find.text(AppLocalizations().submission), findsOneWidget);
});
}
Loading

0 comments on commit 8fde046

Please sign in to comment.