| // Copyright 2019 The Flutter Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| import 'dart:async'; |
| |
| import 'package:cocoon_common/rpc_model.dart'; |
| import 'package:flutter/material.dart'; |
| import 'package:flutter/services.dart'; |
| import 'package:url_launcher/link.dart'; |
| |
| import 'commit_author_avatar.dart'; |
| import 'progress_button.dart'; |
| |
| // TODO(ianh): Factor out the logic in task_overlay.dart and use it here as well, |
| // so that all our popups have the same look and feel and we don't duplicate code. |
| |
| /// Displays Git commit information. |
| /// |
| /// On click, it will open an [OverlayEntry] with [CommitOverlayContents] |
| /// to show the information provided. Otherwise, it just shows the avatar |
| /// for the author of this commit. Clicking outside of the [OverlayEntry] |
| /// will close it. |
| class CommitBox extends StatefulWidget { |
| const CommitBox({ |
| super.key, |
| required this.commit, |
| this.schedulePostsubmitBuild, |
| }); |
| |
| /// The commit being shown |
| final Commit commit; |
| |
| /// Whether to provide a 'schedule build' button'. |
| final Future<void> Function()? schedulePostsubmitBuild; |
| |
| @override |
| CommitBoxState createState() => CommitBoxState(); |
| } |
| |
| class CommitBoxState extends State<CommitBox> { |
| OverlayEntry? _commitOverlay; |
| |
| @override |
| Widget build(BuildContext context) { |
| return InkWell( |
| onTap: _handleTap, |
| child: Padding( |
| padding: const EdgeInsets.all(4.0), |
| child: CommitAuthorAvatar(commit: widget.commit), |
| ), |
| ); |
| } |
| |
| void _handleTap() { |
| _commitOverlay = OverlayEntry( |
| builder: (_) => CommitOverlayContents( |
| parentContext: context, |
| commit: widget.commit, |
| closeCallback: _closeOverlay, |
| schedulePostsubmitBuild: widget.schedulePostsubmitBuild, |
| ), |
| ); |
| |
| Overlay.of(context).insert(_commitOverlay!); |
| } |
| |
| void _closeOverlay() => _commitOverlay?.remove(); |
| } |
| |
| /// Displays the information from a Git commit. |
| /// |
| /// This is intended to be inserted in an [OverlayEntry] as it requires |
| /// [closeCallback] that will remove the widget from the tree. |
| class CommitOverlayContents extends StatelessWidget { |
| const CommitOverlayContents({ |
| super.key, |
| required this.parentContext, |
| required this.commit, |
| required this.closeCallback, |
| required this.schedulePostsubmitBuild, |
| }); |
| |
| /// The parent context that has the size of the whole screen |
| final BuildContext parentContext; |
| |
| /// The commit data to display in the overlay |
| final Commit commit; |
| |
| /// This callback removes the parent overlay from the widget tree. |
| /// |
| /// On a click that is outside the area of the overlay (the rest of the screen), |
| /// this callback is called closing the overlay. |
| final void Function() closeCallback; |
| |
| /// This callback schedules a post-submit build. |
| final Future<void> Function()? schedulePostsubmitBuild; |
| |
| @override |
| Widget build(BuildContext context) { |
| final theme = Theme.of(context); |
| final renderBox = parentContext.findRenderObject() as RenderBox; |
| final offsetLeft = renderBox.localToGlobal(Offset.zero); |
| return Stack( |
| children: <Widget>[ |
| // This is the area a user can click (the rest of the screen) to close the overlay. |
| GestureDetector( |
| onTap: closeCallback, |
| child: Container( |
| width: MediaQuery.of(parentContext).size.width, |
| height: MediaQuery.of(parentContext).size.height, |
| // Color must be defined otherwise the container can't be clicked on |
| color: Colors.transparent, |
| ), |
| ), |
| Positioned( |
| width: 300, |
| // Move this overlay to be where the parent is |
| top: offsetLeft.dy + (renderBox.size.height / 2), |
| left: offsetLeft.dx + (renderBox.size.width / 2), |
| child: Card( |
| child: SafeArea( |
| minimum: const EdgeInsets.all(16), |
| child: Row( |
| crossAxisAlignment: CrossAxisAlignment.start, |
| children: <Widget>[ |
| CommitAuthorAvatar(commit: commit), |
| Expanded( |
| child: Padding( |
| padding: const EdgeInsetsDirectional.only(start: 16), |
| child: Column( |
| crossAxisAlignment: CrossAxisAlignment.stretch, |
| children: <Widget>[ |
| Align( |
| alignment: Alignment.centerLeft, |
| child: AnimatedDefaultTextStyle( |
| style: theme.textTheme.titleMedium!, |
| duration: kThemeChangeDuration, |
| child: Row( |
| children: <Widget>[ |
| Link( |
| uri: Uri.https( |
| 'github.com', |
| '${commit.repository}/commit/${commit.sha}', |
| ), |
| builder: (context, open) { |
| return ElevatedButton( |
| onPressed: open, |
| child: Text(commit.sha.substring(0, 7)), |
| ); |
| }, |
| ), |
| IconButton( |
| icon: const Icon(Icons.copy), |
| onPressed: () => unawaited( |
| Clipboard.setData( |
| ClipboardData(text: commit.sha), |
| ), |
| ), |
| ), |
| ], |
| ), |
| ), |
| ), |
| const SizedBox(height: 8), |
| Padding( |
| padding: const EdgeInsets.only(bottom: 4), |
| child: AnimatedDefaultTextStyle( |
| style: theme.textTheme.bodyMedium!.copyWith( |
| color: theme.textTheme.bodySmall!.color, |
| ), |
| duration: kThemeChangeDuration, |
| child: SelectableText( |
| commit.message.split('\n').first, |
| ), |
| ), |
| ), |
| SelectableText(commit.author.login), |
| Tooltip( |
| key: const ValueKey('schedulePostsubmit'), |
| message: schedulePostsubmitBuild == null |
| ? 'Only enabled for release branches' |
| : '' |
| 'For release branches, the post-submit artifacts are not ' |
| 'immediately available and must be manually scheduled.', |
| child: Padding( |
| padding: const EdgeInsets.only(top: 8.0), |
| child: ProgressButton( |
| child: const Text('Run all tasks'), |
| onPressed: schedulePostsubmitBuild, |
| ), |
| ), |
| ), |
| ], |
| ), |
| ), |
| ), |
| ], |
| ), |
| ), |
| ), |
| ), |
| ], |
| ); |
| } |
| } |