Skip to content
This repository has been archived by the owner on Oct 22, 2021. It is now read-only.

Migrate assignment module #301

Merged
merged 5 commits into from
Jun 26, 2020
Merged
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
2 changes: 1 addition & 1 deletion lib/app/app.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ export 'package:hive/hive.dart'
export 'package:hive_cache/hive_cache.dart';
export 'package:meta/meta.dart';
export 'package:pedantic/pedantic.dart';
export 'package:time_machine/time_machine.dart' show Instant;
export 'package:time_machine/time_machine.dart' show Instant, LocalDate, Period;

export 'app_config.dart';
export 'data.dart';
Expand Down
2 changes: 1 addition & 1 deletion lib/app/services/network.dart
Original file line number Diff line number Diff line change
Expand Up @@ -297,7 +297,7 @@ class NetworkService {
// ignore: parameter_assignments
headers = {
'Content-Type': 'application/json',
if (headers != null) ...headers,
...?headers,
};
for (final entry in headers.entries) {
request.headers[entry.key] = entry.value;
Expand Down
1 change: 0 additions & 1 deletion lib/assignment/assignment.dart
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
export 'data.dart';
export 'routes.dart';
export 'widgets/assignments_screen.dart';
export 'widgets/dashboard_card.dart';
124 changes: 124 additions & 0 deletions lib/assignment/pages/assignment_detail_page/page.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
import 'dart:math' as math;

import 'package:flutter/material.dart';
import 'package:schulcloud/app/app.dart';
import 'package:schulcloud/course/course.dart';

import '../../data.dart';
import 'tab_details.dart';
import 'tab_feedback.dart';
import 'tab_submission.dart';
import 'tab_submissions.dart';

class AssignmentDetailPage extends StatefulWidget {
const AssignmentDetailPage(this.assignmentId, {this.initialTab})
: assert(assignmentId != null);

final Id<Assignment> assignmentId;
final String initialTab;

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

class _AssignmentDetailPageState extends State<AssignmentDetailPage>
with TickerProviderStateMixin {
TabController _controller;

@override
Widget build(BuildContext context) {
final s = context.s;

return EntityBuilder<Assignment>(
id: widget.assignmentId,
builder: handleLoadingError((context, assignment, _) {
return EntityBuilder<User>(
id: services.storage.userId,
builder: handleLoadingError((context, user, fetch) {
final showSubmissionTab = assignment.isPrivate || !user.isTeacher;
final showFeedbackTab = assignment.isPublic && !user.isTeacher;
final showSubmissionsTab = assignment.isPublic &&
(user.isTeacher || assignment.hasPublicSubmissions);

final tabs = [
'extended',
if (showSubmissionTab) 'submission',
if (showFeedbackTab) 'feedback',
if (showSubmissionsTab) 'submissions',
];
var initialTabIndex = tabs.indexOf(widget.initialTab);
if (initialTabIndex < 0) {
initialTabIndex = null;
}

if (_controller == null || _controller.length != tabs.length) {
_controller = TabController(
initialIndex: math.min(initialTabIndex ?? 0, tabs.length - 1),
MarcelGarus marked this conversation as resolved.
Show resolved Hide resolved
length: tabs.length,
vsync: this,
);
}

return FancyTabbedScaffold(
initialTabIndex: initialTabIndex,
controller: _controller,
appBarBuilder: (_) => FancyAppBar(
title: Text(assignment.name),
subtitle: CourseName.orNull(assignment.courseId),
actions: <Widget>[
if (user.hasPermission(Permission.assignmentEdit))
_buildArchiveAction(context, assignment),
],
bottom: TabBar(
controller: _controller,
tabs: [
Tab(text: s.assignment_assignmentDetails_details),
if (showSubmissionTab)
Tab(text: s.assignment_assignmentDetails_submission),
if (showFeedbackTab)
Tab(text: s.assignment_assignmentDetails_feedback),
if (showSubmissionsTab)
Tab(text: s.assignment_assignmentDetails_submissions),
],
),
// We want a permanent elevation so tabs are more noticeable.
forceElevated: true,
),
tabs: [
DetailsTab(assignment: assignment),
if (showSubmissionTab) SubmissionTab(assignment: assignment),
if (showFeedbackTab) FeedbackTab(assignment: assignment),
if (showSubmissionsTab) SubmissionsTab(),
],
);
}),
);
}),
);
}

Widget _buildArchiveAction(BuildContext context, Assignment assignment) {
final s = context.s;

return IconButton(
icon: Icon(assignment.isArchived ? Icons.unarchive : Icons.archive),
tooltip: assignment.isArchived
? s.assignment_assignmentDetails_unarchive
: s.assignment_assignmentDetails_archive,
onPressed: () async {
await assignment.toggleArchived();
context.scaffold.showSnackBar(SnackBar(
content: Text(
assignment.isArchived
? s.assignment_assignmentDetails_unarchived
: s.assignment_assignmentDetails_archived,
),
action: SnackBarAction(
label: s.general_undo,
onPressed: assignment.toggleArchived,
),
));
},
);
}
}
85 changes: 85 additions & 0 deletions lib/assignment/pages/assignment_detail_page/tab_details.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import 'package:flutter/material.dart';
import 'package:schulcloud/app/app.dart';

import '../../data.dart';
import 'utils.dart';

class DetailsTab extends StatelessWidget {
const DetailsTab({Key key, @required this.assignment})
: assert(assignment != null),
super(key: key);

final Assignment assignment;

@override
Widget build(BuildContext context) {
final textTheme = context.textTheme;
final s = context.s;

final datesText = [
s.assignment_assignmentDetails_details_available(
assignment.availableAt.longDateTimeString),
if (assignment.dueAt != null)
s.assignment_assignmentDetails_details_due(
assignment.dueAt.longDateTimeString),
].join('\n');

return TabContent(
pageStorageKey: PageStorageKey<String>('details'),
omitHorizontalPadding: true,
sliver: SliverList(
delegate: SliverChildListDelegate.fixed([
Padding(
padding: EdgeInsets.symmetric(horizontal: 16),
child: ChipGroup(children: _buildChips(context)),
),
Padding(
padding: EdgeInsets.symmetric(horizontal: 16),
child: Text(
datesText,
style: textTheme.bodyText2,
textAlign: TextAlign.end,
),
),
SizedBox(height: 4),
Padding(
padding: EdgeInsets.symmetric(horizontal: 16),
child: FancyText.rich(assignment.description),
),
...buildFileSection(context, assignment.fileIds),
]),
),
);
}

List<Widget> _buildChips(BuildContext context) {
final s = context.s;

return <Widget>[
if (assignment.isOverdue)
ActionChip(
avatar: Icon(
Icons.flag,
color: context.theme.errorColor,
),
label: Text(s.assignment_assignment_overdue),
onPressed: () {},
),
if (assignment.isArchived)
Chip(
avatar: Icon(Icons.archive),
label: Text(s.assignment_assignment_isArchived),
),
if (assignment.isPrivate)
Chip(
avatar: Icon(Icons.lock),
label: Text(s.assignment_assignment_isPrivate),
),
if (assignment.hasPublicSubmissions)
Chip(
avatar: Icon(Icons.public),
label: Text(s.assignment_assignment_property_hasPublicSubmissions),
),
];
}
}
46 changes: 46 additions & 0 deletions lib/assignment/pages/assignment_detail_page/tab_feedback.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import 'package:flutter/material.dart';
import 'package:schulcloud/app/app.dart';

import '../../data.dart';
import '../../widgets/grade_indicator.dart';

class FeedbackTab extends StatelessWidget {
const FeedbackTab({Key key, @required this.assignment})
: assert(assignment != null),
super(key: key);

final Assignment assignment;

@override
Widget build(BuildContext context) {
final s = context.s;

return ConnectionBuilder.populated<Submission>(
connection: assignment.mySubmission,
builder: handleLoadingError((context, submission, isFetching) {
return TabContent(
pageStorageKey: PageStorageKey<String>('feedback'),
omitHorizontalPadding: true,
sliver: SliverList(
delegate: SliverChildListDelegate.fixed([
if (submission?.grade != null) ...[
ListTile(
leading: GradeIndicator(grade: submission.grade),
title: Text(s.assignment_assignmentDetails_feedback_grade(
submission.grade)),
),
SizedBox(height: 8),
],
Padding(
padding: EdgeInsets.symmetric(horizontal: 16),
child: submission?.gradeComment != null
? FancyText.rich(submission.gradeComment)
: Text(s.assignment_assignmentDetails_feedback_textEmpty),
),
]),
),
);
}),
);
}
}
86 changes: 86 additions & 0 deletions lib/assignment/pages/assignment_detail_page/tab_submission.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import 'package:flutter/material.dart';
import 'package:schulcloud/app/app.dart';

import '../../data.dart';
import 'utils.dart';

class SubmissionTab extends StatelessWidget {
const SubmissionTab({Key key, @required this.assignment})
: assert(assignment != null),
super(key: key);

final Assignment assignment;

@override
Widget build(BuildContext context) {
return ConnectionBuilder.populated<Submission>(
connection: assignment.mySubmission,
builder: handleLoadingError((context, submission, isFetching) {
// TODO(JonasWanke): differentiate between loading and no submission
return Stack(
children: <Widget>[
TabContent(
pageStorageKey: PageStorageKey<String>('submission'),
omitHorizontalPadding: true,
sliver: _buildContent(context, submission),
),
_buildOverlay(context, submission),
],
);
}),
);
}

Widget _buildContent(BuildContext context, Submission submission) {
return SliverList(
delegate: SliverChildListDelegate([
Padding(
padding: EdgeInsets.symmetric(horizontal: 16),
child: submission == null
? Text(context.s.assignment_assignmentDetails_submission_empty)
: FancyText.rich(submission.comment),
),
if (submission != null)
...buildFileSection(context, submission.fileIds),
if (!assignment.isOverdue) FabSpacer(),
]),
);
}

Widget _buildOverlay(BuildContext context, Submission submission) {
if (assignment.isOverdue) {
return SizedBox.shrink();
}

final s = context.s;

return EntityBuilder<User>(
id: services.storage.userId,
builder: handleLoadingError((context, user, isFetching) {
String labelText;
if (submission == null &&
user.hasPermission(Permission.submissionsCreate)) {
labelText = s.assignment_assignmentDetails_submission_create;
} else if (submission != null &&
user.hasPermission(Permission.submissionsEdit)) {
labelText = s.assignment_assignmentDetails_submission_edit;
}
if (labelText == null) {
return SizedBox.shrink();
}

return Scaffold(
backgroundColor: Colors.transparent,
floatingActionButton: Builder(
builder: (context) => FloatingActionButton.extended(
onPressed: () => context.navigator
.pushNamed('/homework/${assignment.id}/submission'),
label: Text(labelText),
icon: Icon(Icons.edit),
),
),
);
}),
);
}
}
21 changes: 21 additions & 0 deletions lib/assignment/pages/assignment_detail_page/tab_submissions.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import 'package:flutter/material.dart';
import 'package:schulcloud/app/app.dart';

class SubmissionsTab extends StatelessWidget {
const SubmissionsTab({Key key}) : super(key: key);

@override
Widget build(BuildContext context) {
return TabContent(
pageStorageKey: PageStorageKey<String>('submissions'),
sliver: SliverFillRemaining(
child: Center(
child: Text(
context.s.assignment_assignmentDetails_submissions_placeholder,
textAlign: TextAlign.center,
),
),
),
);
}
}
Loading