blob: cf0494a7b8c19e7a0e08242dae59e5c94eacee70 [file] [log] [blame]
// 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,
),
),
),
],
),
),
),
],
),
),
),
),
],
);
}
}