Skip to content

Commit

Permalink
Progress through artefact dialog UI
Browse files Browse the repository at this point in the history
  • Loading branch information
omar-selo committed Jun 26, 2023
1 parent 0021adb commit e9fcb31
Show file tree
Hide file tree
Showing 4 changed files with 210 additions and 40 deletions.
52 changes: 51 additions & 1 deletion frontend/lib/models/test_execution.dart
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
import 'package:flutter/material.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:yaru/yaru.dart';
import 'package:yaru_icons/yaru_icons.dart';

import 'environment.dart';

Expand All @@ -11,10 +14,57 @@ class TestExecution with _$TestExecution {
required int id,
@JsonKey(name: 'jenkins_link') required String? jenkinsLink,
@JsonKey(name: 'c3_link') required String? c3Link,
required String status,
required TestExecutionStatus status,
required Environment environment,
}) = _TestExecution;

factory TestExecution.fromJson(Map<String, Object?> json) =>
_$TestExecutionFromJson(json);
}

enum TestExecutionStatus {
@JsonValue('NOT_STARTED')
notStarted,
@JsonValue('IN_PROGRESS')
inProgress,
@JsonValue('PASSED')
passed,
@JsonValue('FAILED')
failed,
@JsonValue('NOT_TESTED')
notTested;

String get name {
switch (this) {
case notStarted:
return 'Not Started';
case inProgress:
return 'In Progress';
case passed:
return 'Passed';
case failed:
return 'Failed';
case notTested:
return 'Not Tested';
default:
throw Exception('Unknown TestExecutionStatus: $this');
}
}

Icon get icon {
switch (this) {
case notStarted:
return const Icon(YaruIcons.media_play);
case inProgress:
return const Icon(YaruIcons.refresh);
case passed:
return const Icon(YaruIcons.ok, color: YaruColors.success);
case failed:
return const Icon(YaruIcons.error, color: YaruColors.red);
case notTested:
return const Icon(YaruIcons.information);
default:
throw Exception('Unknown TestExecutionStatus: $this');
}
}
}
133 changes: 111 additions & 22 deletions frontend/lib/ui/dashboard/artefact_dialog.dart
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
import 'dart:math';

import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:yaru_icons/yaru_icons.dart';
import 'package:yaru_widgets/widgets.dart';

import '../../models/artefact.dart';
import '../../models/artefact_build.dart';
import '../../models/test_execution.dart';
import '../../providers/artefact_builds.dart';
import '../spacing.dart';

Expand All @@ -15,9 +19,9 @@ class ArtefactDialog extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Dialog(
child: FractionallySizedBox(
heightFactor: 0.8,
widthFactor: 0.8,
child: SizedBox(
height: min(800, MediaQuery.of(context).size.height * 0.8),
width: min(1200, MediaQuery.of(context).size.width * 0.8),
child: Padding(
padding: const EdgeInsets.symmetric(
horizontal: Spacing.level5,
Expand All @@ -26,11 +30,11 @@ class ArtefactDialog extends StatelessWidget {
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_Header(title: artefact.name),
_ArtefactHeader(title: artefact.name),
const SizedBox(height: Spacing.level4),
_Info(artefact: artefact),
_ArtefactInfoSection(artefact: artefact),
const SizedBox(height: Spacing.level4),
_Environments(artefact: artefact),
Expanded(child: _EnvironmentsSection(artefact: artefact)),
],
),
),
Expand All @@ -39,8 +43,8 @@ class ArtefactDialog extends StatelessWidget {
}
}

class _Header extends StatelessWidget {
const _Header({required this.title});
class _ArtefactHeader extends StatelessWidget {
const _ArtefactHeader({required this.title});

final String title;

Expand Down Expand Up @@ -70,8 +74,8 @@ class _Header extends StatelessWidget {
}
}

class _Info extends StatelessWidget {
const _Info({required this.artefact});
class _ArtefactInfoSection extends StatelessWidget {
const _ArtefactInfoSection({required this.artefact});

final Artefact artefact;

Expand All @@ -82,24 +86,47 @@ class _Info extends StatelessWidget {
...artefact.source.entries.map((entry) => '${entry.key}: ${entry.value}'),
];

return Column(
return Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
...artefactDetails
.expand(
(detail) => [
Text(detail, style: Theme.of(context).textTheme.bodyLarge),
const SizedBox(height: Spacing.level2),
],
)
.toList()
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
...artefactDetails
.expand(
(detail) => [
Text(detail, style: Theme.of(context).textTheme.bodyLarge),
const SizedBox(height: Spacing.level2),
],
)
.toList(),
],
),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: TestExecutionStatus.values
.map(
(status) => Row(
children: [
status.icon,
const SizedBox(width: Spacing.level2),
Text(
status.name,
style: Theme.of(context).textTheme.bodyMedium,
),
],
),
)
.toList(),
),
],
);
}
}

class _Environments extends ConsumerWidget {
const _Environments({required this.artefact});
class _EnvironmentsSection extends ConsumerWidget {
const _EnvironmentsSection({required this.artefact});

final Artefact artefact;

Expand All @@ -109,9 +136,16 @@ class _Environments extends ConsumerWidget {

return artefactBuilds.when(
data: (artefactBuilds) => Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('Environments', style: Theme.of(context).textTheme.titleLarge),
...artefactBuilds.map((build) => Text(build.architecture))
Expanded(
child: ListView.builder(
itemCount: artefactBuilds.length,
itemBuilder: (_, i) =>
_ArtefactBuildView(artefactBuild: artefactBuilds[i]),
),
),
],
),
loading: () => const YaruCircularProgressIndicator(),
Expand All @@ -121,3 +155,58 @@ class _Environments extends ConsumerWidget {
);
}
}

class _ArtefactBuildView extends StatelessWidget {
const _ArtefactBuildView({required this.artefactBuild});

final ArtefactBuild artefactBuild;

@override
Widget build(BuildContext context) {
return YaruExpandable(
expandButtonPosition: YaruExpandableButtonPosition.start,
isExpanded: true,
header: Text(
artefactBuild.architecture,
style: Theme.of(context).textTheme.titleLarge,
),
child: Padding(
padding: const EdgeInsets.only(left: Spacing.level4),
child: Column(
children: artefactBuild.testExecutions
.map(
(testExecution) =>
_TestExecutionView(testExecution: testExecution),
)
.toList(),
),
),
);
}
}

class _TestExecutionView extends StatelessWidget {
const _TestExecutionView({required this.testExecution});

final TestExecution testExecution;

@override
Widget build(BuildContext context) {
return YaruExpandable(
header: Row(
children: [
testExecution.status.icon,
const SizedBox(width: Spacing.level2),
Text(
testExecution.environment.name,
style: Theme.of(context).textTheme.titleLarge,
),
const Spacer(),
Text('links'),
],
),
expandButtonPosition: YaruExpandableButtonPosition.start,
child: Container(),
);
}
}
23 changes: 6 additions & 17 deletions frontend/lib/ui/footer.dart
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:url_launcher/url_launcher_string.dart';

import 'spacing.dart';
import 'inline_url_text.dart';

class Footer extends StatelessWidget {
const Footer({Key? key}) : super(key: key);
Expand All @@ -26,22 +26,11 @@ class Footer extends StatelessWidget {
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
RichText(
text: TextSpan(
style: fontStyle,
children: [
const TextSpan(text: 'Powered by '),
TextSpan(
text: 'Canonical Ltd.',
style: fontStyle?.apply(
decoration: TextDecoration.underline,
color: Colors.blue,
),
recognizer: TapGestureRecognizer()
..onTap = () => launchUrlString('https://canonical.com/'),
),
],
),
InlineUrlText(
url: 'https://canonical.com/',
fontStyle: fontStyle,
urlText: 'Canonical Ltd.',
leadingText: 'Powered by ',
),
const SizedBox(height: Spacing.level3),
GestureDetector(
Expand Down
42 changes: 42 additions & 0 deletions frontend/lib/ui/inline_url_text.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:url_launcher/url_launcher_string.dart';

class InlineUrlText extends StatelessWidget {
const InlineUrlText({
super.key,
this.fontStyle,
required this.url,
this.trailingText,
this.leadingText,
this.urlText,
});

final TextStyle? fontStyle;
final String url;
final String? urlText;
final String? trailingText;
final String? leadingText;

@override
Widget build(BuildContext context) {
return RichText(
text: TextSpan(
style: fontStyle,
children: [
if (leadingText != null) TextSpan(text: leadingText),
TextSpan(
text: urlText ?? url,
style: fontStyle?.apply(
decoration: TextDecoration.underline,
color: Colors.blue,
),
recognizer: TapGestureRecognizer()
..onTap = () => launchUrlString(url),
),
if (trailingText != null) TextSpan(text: trailingText),
],
),
);
}
}

0 comments on commit e9fcb31

Please sign in to comment.