Unverified Commit 3c6c2aa5 authored by Alex's avatar Alex Committed by GitHub

Alex chen conductor ui3 (#91118)

parent 46a52d03
// Copyright 2014 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 'package:conductor_core/conductor_core.dart';
import 'package:conductor_core/proto.dart' as pb;
import 'package:flutter/material.dart';
/// Display the current conductor state
class ConductorStatus extends StatefulWidget {
const ConductorStatus({
Key? key,
this.releaseState,
required this.stateFilePath,
}) : super(key: key);
final pb.ConductorState? releaseState;
final String stateFilePath;
@override
ConductorStatusState createState() => ConductorStatusState();
}
class ConductorStatusState extends State<ConductorStatus> {
@override
Widget build(BuildContext context) {
return SelectableText(
widget.releaseState != null
? presentState(widget.releaseState!)
: 'No persistent state file found at ${widget.stateFilePath}',
);
}
}
......@@ -2,10 +2,12 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:conductor_core/conductor_core.dart';
import 'package:conductor_core/proto.dart' as pb;
import 'package:flutter/material.dart';
import 'conductor_status.dart';
import 'substeps.dart';
/// Displays the progression and each step of the release from the conductor.
///
// TODO(Yugue): Add documentation to explain
......@@ -22,20 +24,116 @@ class MainProgression extends StatefulWidget {
@override
MainProgressionState createState() => MainProgressionState();
static const List<String> _stepTitles = <String>[
'Initialize a New Flutter Release',
'Flutter Engine Cherrypicks',
'Flutter Framework Cherrypicks',
'Publish the Release',
'Release is Successfully published'
];
}
class MainProgressionState extends State<MainProgression> {
int _completedStep = 0;
// Move forward the stepper to the next step of the release.
void nextStep() {
if (_completedStep < MainProgression._stepTitles.length - 1) {
setState(() {
_completedStep += 1;
});
}
}
/// Change each step's state according to [_completedStep].
StepState handleStepState(int index) {
if (_completedStep > index) {
return StepState.complete;
} else if (_completedStep == index) {
return StepState.indexed;
} else {
return StepState.disabled;
}
}
final ScrollController _scrollController = ScrollController();
@override
Widget build(BuildContext context) {
return Expanded(
child: Scrollbar(
isAlwaysShown: true,
controller: _scrollController,
child: ListView(
controller: _scrollController,
padding: const EdgeInsets.symmetric(vertical: 12.0),
physics: const ClampingScrollPhysics(),
children: <Widget>[
SelectableText(
widget.releaseState != null
? presentState(widget.releaseState!)
: 'No persistent state file found at ${widget.stateFilePath}',
ConductorStatus(
releaseState: widget.releaseState,
stateFilePath: widget.stateFilePath,
),
Stepper(
controlsBuilder: (BuildContext context, ControlsDetails details) {
return Row(
children: const <Widget>[],
);
},
type: StepperType.vertical,
physics: const ScrollPhysics(),
currentStep: _completedStep,
onStepContinue: nextStep,
steps: <Step>[
Step(
title: Text(MainProgression._stepTitles[0]),
content: Column(
children: <Widget>[
ConductorSubsteps(nextStep: nextStep),
],
),
isActive: true,
state: handleStepState(0),
),
Step(
title: Text(MainProgression._stepTitles[1]),
content: Column(
children: <Widget>[
ConductorSubsteps(nextStep: nextStep),
],
),
isActive: true,
state: handleStepState(1),
),
Step(
title: Text(MainProgression._stepTitles[2]),
content: Column(
children: <Widget>[
ConductorSubsteps(nextStep: nextStep),
],
),
isActive: true,
state: handleStepState(2),
),
Step(
title: Text(MainProgression._stepTitles[3]),
content: Column(
children: <Widget>[
ConductorSubsteps(nextStep: nextStep),
],
),
isActive: true,
state: handleStepState(3),
),
Step(
title: Text(MainProgression._stepTitles[4]),
content: Column(
children: const <Widget>[],
),
isActive: true,
state: handleStepState(4),
),
],
),
],
),
......
// Copyright 2014 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 'package:flutter/material.dart';
/// Group and display all substeps within a step into a widget.
///
/// When all substeps are checked, [nextStep] can be executed to proceed to the next step.
class ConductorSubsteps extends StatefulWidget {
const ConductorSubsteps({
Key? key,
required this.nextStep,
}) : super(key: key);
final VoidCallback nextStep;
@override
ConductorSubstepsState createState() => ConductorSubstepsState();
static const List<String> _substepTitles = <String>[
'Substep 1',
'Substep 2',
'Substep 3',
];
}
class ConductorSubstepsState extends State<ConductorSubsteps> {
List<bool> substepChecked = List<bool>.filled(ConductorSubsteps._substepTitles.length, false);
bool _nextStepPressed = false;
// Hide the continue button once it is pressed.
void tapped() {
setState(() => _nextStepPressed = true);
}
// When substepChecked[0] is true, the first substep is checked. If it false, it is unchecked.
void substepPressed(int index) {
setState(() {
substepChecked[index] = !substepChecked[index];
});
}
@override
Widget build(BuildContext context) {
return Column(
children: <Widget>[
CheckboxListTile(
value: substepChecked[0],
onChanged: (bool? newValue) {
substepPressed(0);
},
title: Text(ConductorSubsteps._substepTitles[0]),
controlAffinity: ListTileControlAffinity.leading,
activeColor: Colors.grey,
selected: substepChecked[0],
),
CheckboxListTile(
value: substepChecked[1],
onChanged: (bool? newValue) {
substepPressed(1);
},
title: Text(ConductorSubsteps._substepTitles[1]),
controlAffinity: ListTileControlAffinity.leading,
activeColor: Colors.grey,
selected: substepChecked[1],
),
CheckboxListTile(
value: substepChecked[2],
onChanged: (bool? newValue) {
substepPressed(2);
},
title: Text(ConductorSubsteps._substepTitles[2]),
controlAffinity: ListTileControlAffinity.leading,
activeColor: Colors.grey,
selected: substepChecked[2],
),
if (!substepChecked.contains(false) && !_nextStepPressed)
ElevatedButton(
onPressed: () {
tapped();
widget.nextStep();
},
child: const Text('Continue'),
),
],
);
}
}
// Copyright 2014 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 'package:conductor_ui/widgets/progression.dart';
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
void main() {
testWidgets(
'All substeps of the current step must be checked before able to continue to the next step',
(WidgetTester tester) async {
await tester.pumpWidget(
StatefulBuilder(
builder: (BuildContext context, StateSetter setState) {
return MaterialApp(
home: Material(
child: Column(
children: const <Widget>[
MainProgression(
releaseState: null,
stateFilePath: './testPath',
),
],
),
),
);
},
),
);
expect(find.byType(Stepper), findsOneWidget);
expect(find.text('Initialize a New Flutter Release'), findsOneWidget);
expect(find.text('Continue'), findsNWidgets(0));
await tester.tap(find.text('Substep 1').first);
await tester.tap(find.text('Substep 2').first);
await tester.pumpAndSettle();
expect(find.text('Continue'), findsNWidgets(0));
await tester.tap(find.text('Substep 3').first);
await tester.pumpAndSettle();
expect(find.text('Continue'), findsOneWidget);
expect(tester.widget<Stepper>(find.byType(Stepper)).steps[0].state, equals(StepState.indexed));
expect(tester.widget<Stepper>(find.byType(Stepper)).steps[1].state, equals(StepState.disabled));
await tester.tap(find.text('Continue'));
await tester.pumpAndSettle();
expect(tester.widget<Stepper>(find.byType(Stepper)).steps[0].state,
equals(StepState.complete));
expect(tester.widget<Stepper>(find.byType(Stepper)).steps[1].state,
equals(StepState.indexed));
});
testWidgets('When user clicks on a previously completed step, Stepper does not navigate back.',
(WidgetTester tester) async {
await tester.pumpWidget(
StatefulBuilder(
builder: (BuildContext context, StateSetter setState) {
return MaterialApp(
home: Material(
child: Column(
children: const <Widget>[
MainProgression(
releaseState: null,
stateFilePath: './testPath',
),
],
),
),
);
},
),
);
await tester.tap(find.text('Substep 1').first);
await tester.tap(find.text('Substep 2').first);
await tester.tap(find.text('Substep 3').first);
await tester.pumpAndSettle();
await tester.tap(find.text('Continue'));
await tester.tap(find.text('Initialize a New Flutter Release'));
await tester.pumpAndSettle();
expect(tester.widget<Stepper>(find.byType(Stepper)).currentStep, equals(1));
});
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment