You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@beam.apache.org by da...@apache.org on 2022/11/16 16:34:00 UTC
[beam] branch master updated: [Tour of Beam] [Frontend] Content tree URLs (#23776)
This is an automated email from the ASF dual-hosted git repository.
damccorm pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/beam.git
The following commit(s) were added to refs/heads/master by this push:
new 6e9187e67e1 [Tour of Beam] [Frontend] Content tree URLs (#23776)
6e9187e67e1 is described below
commit 6e9187e67e1bd8f73997f437f0ed4c29880ed73b
Author: Darkhan Nausharipov <31...@users.noreply.github.com>
AuthorDate: Wed Nov 16 22:33:50 2022 +0600
[Tour of Beam] [Frontend] Content tree URLs (#23776)
* Content tree navigation (#23593)
Unit content navigation (#23593)
Update URL on node click (#23593)
Active unit color (#23593)
removeListener in unit (#23593)
First unit is opened on group title click (#23593)
WIP by Alexey Inkin (#23593)
selectedUnitColor (#23593)
Unit borderRadius (#23593)
RegExp todo (#23593)
added referenced collection package to remove warning (#23593)
small refinement (#23593)
expand on group tap, padding, openNode (#23593)
group expansion bug fix (#23593)
selected & unselected progress indicators (#23593)
* AnimatedBuilders instead of StatefulWidgets in unit & group (#23593)
* fixed _getNodeAncestors (#23593)
* get sdkId (#23593)
* addressing comments (#23593)
* sdkId getter & StatelessExpansionTile (#23593)
* expand & collapse group (#23593)
* StatelessExpansionTile (#23593)
* license (#23593)
* ValueChanged and ValueKey in StatelessExpansionTile (#23593)
Co-authored-by: darkhan.nausharipov <da...@kzn.akvelon.com>
Co-authored-by: Alexey Inkin <al...@akvelon.com>
---
.../frontend/lib/components/filler_text.dart | 29 ---------
.../frontend/lib/models/content_tree.dart | 20 ++++--
.../tour-of-beam/frontend/lib/models/group.dart | 29 ++++++---
.../tour-of-beam/frontend/lib/models/module.dart | 29 ++++++---
.../tour-of-beam/frontend/lib/models/node.dart | 20 ++++--
.../frontend/lib/models/parent_node.dart | 18 ++++++
.../tour-of-beam/frontend/lib/models/unit.dart | 15 ++++-
.../lib/pages/tour/controllers/content_tree.dart | 71 ++++++++++++++++++++--
.../tour-of-beam/frontend/lib/pages/tour/path.dart | 9 ++-
.../frontend/lib/pages/tour/state.dart | 2 +
.../frontend/lib/pages/tour/widgets/group.dart | 40 +++++++-----
.../lib/pages/tour/widgets/group_title.dart | 5 +-
.../frontend/lib/pages/tour/widgets/module.dart | 2 +-
.../{group.dart => stateless_expansion_tile.dart} | 35 +++++------
.../tour/widgets/tour_progress_indicator.dart | 16 ++++-
.../frontend/lib/pages/tour/widgets/unit.dart | 38 ++++++++----
.../frontend/lib/pages/welcome/screen.dart | 3 -
learning/tour-of-beam/frontend/pubspec.lock | 4 +-
learning/tour-of-beam/frontend/pubspec.yaml | 1 +
.../lib/src/constants/colors.dart | 46 ++++++++------
.../playground_components/lib/src/theme/theme.dart | 26 ++++++++
21 files changed, 317 insertions(+), 141 deletions(-)
diff --git a/learning/tour-of-beam/frontend/lib/components/filler_text.dart b/learning/tour-of-beam/frontend/lib/components/filler_text.dart
deleted file mode 100644
index ca6099e6d9d..00000000000
--- a/learning/tour-of-beam/frontend/lib/components/filler_text.dart
+++ /dev/null
@@ -1,29 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-import 'package:flutter/material.dart';
-
-class FillerText extends StatelessWidget {
- final int width;
- const FillerText({required this.width});
-
- @override
- Widget build(BuildContext context) {
- return Text(''.padRight(width, 'Just a filler text. '));
- }
-}
diff --git a/learning/tour-of-beam/frontend/lib/models/content_tree.dart b/learning/tour-of-beam/frontend/lib/models/content_tree.dart
index 4c3ba29378a..471bb6734fe 100644
--- a/learning/tour-of-beam/frontend/lib/models/content_tree.dart
+++ b/learning/tour-of-beam/frontend/lib/models/content_tree.dart
@@ -18,19 +18,29 @@
import '../repositories/models/get_content_tree_response.dart';
import 'module.dart';
+import 'node.dart';
+import 'parent_node.dart';
-class ContentTreeModel {
- final String sdkId;
+class ContentTreeModel extends ParentNodeModel {
final List<ModuleModel> modules;
+ String get sdkId => id;
+
+ @override
+ List<NodeModel> get nodes => modules;
+
const ContentTreeModel({
- required this.sdkId,
+ required super.id,
required this.modules,
- });
+ }) : super(
+ parent: null,
+ title: '',
+ nodes: modules,
+ );
ContentTreeModel.fromResponse(GetContentTreeResponse response)
: this(
- sdkId: response.sdkId,
+ id: response.sdkId,
modules: response.modules
.map(ModuleModel.fromResponse)
.toList(growable: false),
diff --git a/learning/tour-of-beam/frontend/lib/models/group.dart b/learning/tour-of-beam/frontend/lib/models/group.dart
index 22086e6303e..ba1d4047a57 100644
--- a/learning/tour-of-beam/frontend/lib/models/group.dart
+++ b/learning/tour-of-beam/frontend/lib/models/group.dart
@@ -23,15 +23,28 @@ import 'parent_node.dart';
class GroupModel extends ParentNodeModel {
const GroupModel({
required super.id,
- required super.title,
required super.nodes,
+ required super.parent,
+ required super.title,
});
- GroupModel.fromResponse(GroupResponseModel group)
- : super(
- id: group.id,
- title: group.title,
- nodes:
- group.nodes.map(NodeModel.fromResponse).toList(growable: false),
- );
+ factory GroupModel.fromResponse(
+ GroupResponseModel groupResponse,
+ ParentNodeModel parent,
+ ) {
+ final group = GroupModel(
+ id: groupResponse.id,
+ nodes: [],
+ parent: parent,
+ title: groupResponse.title,
+ );
+
+ group.nodes.addAll(
+ groupResponse.nodes.map<NodeModel>(
+ (node) => NodeModel.fromResponse(node, group),
+ ),
+ );
+
+ return group;
+ }
}
diff --git a/learning/tour-of-beam/frontend/lib/models/module.dart b/learning/tour-of-beam/frontend/lib/models/module.dart
index 81f8c1b6d61..eb1f7e50633 100644
--- a/learning/tour-of-beam/frontend/lib/models/module.dart
+++ b/learning/tour-of-beam/frontend/lib/models/module.dart
@@ -27,18 +27,27 @@ class ModuleModel extends ParentNodeModel {
const ModuleModel({
required super.id,
- required super.title,
required super.nodes,
+ required super.parent,
+ required super.title,
required this.complexity,
});
- ModuleModel.fromResponse(ModuleResponseModel module)
- : complexity = module.complexity,
- super(
- id: module.id,
- title: module.title,
- nodes: module.nodes
- .map<NodeModel>(NodeModel.fromResponse)
- .toList(growable: false),
- );
+ factory ModuleModel.fromResponse(ModuleResponseModel moduleResponse) {
+ final module = ModuleModel(
+ complexity: moduleResponse.complexity,
+ nodes: [],
+ id: moduleResponse.id,
+ parent: null,
+ title: moduleResponse.title,
+ );
+
+ module.nodes.addAll(
+ moduleResponse.nodes.map<NodeModel>(
+ (node) => NodeModel.fromResponse(node, module),
+ ),
+ );
+
+ return module;
+ }
}
diff --git a/learning/tour-of-beam/frontend/lib/models/node.dart b/learning/tour-of-beam/frontend/lib/models/node.dart
index d13ceea2d28..7a653de1e3a 100644
--- a/learning/tour-of-beam/frontend/lib/models/node.dart
+++ b/learning/tour-of-beam/frontend/lib/models/node.dart
@@ -19,15 +19,18 @@
import '../repositories/models/node.dart';
import '../repositories/models/node_type_enum.dart';
import 'group.dart';
+import 'parent_node.dart';
import 'unit.dart';
abstract class NodeModel {
final String id;
final String title;
+ final NodeModel? parent;
const NodeModel({
required this.id,
required this.title,
+ required this.parent,
});
/// Constructs nodes from the response data.
@@ -36,20 +39,27 @@ abstract class NodeModel {
/// because they come from a golang backend which does not
/// support inheritance, and so they use an extra layer of composition
/// which is inconvenient in Flutter.
- static List<NodeModel> fromMaps(List json) {
+ static List<NodeModel> fromMaps(List json, ParentNodeModel parent) {
return json
.cast<Map<String, dynamic>>()
.map<NodeResponseModel>(NodeResponseModel.fromJson)
- .map(fromResponse)
+ .map((nodeResponse) => fromResponse(nodeResponse, parent))
.toList();
}
- static NodeModel fromResponse(NodeResponseModel node) {
+ static NodeModel fromResponse(
+ NodeResponseModel node,
+ ParentNodeModel parent,
+ ) {
switch (node.type) {
case NodeType.group:
- return GroupModel.fromResponse(node.group!);
+ return GroupModel.fromResponse(node.group!, parent);
case NodeType.unit:
- return UnitModel.fromResponse(node.unit!);
+ return UnitModel.fromResponse(node.unit!, parent);
}
}
+
+ NodeModel getFirstUnit();
+
+ NodeModel? getNodeByTreeIds(List<String> treeIds);
}
diff --git a/learning/tour-of-beam/frontend/lib/models/parent_node.dart b/learning/tour-of-beam/frontend/lib/models/parent_node.dart
index 0271cfb9508..53f3c7a1766 100644
--- a/learning/tour-of-beam/frontend/lib/models/parent_node.dart
+++ b/learning/tour-of-beam/frontend/lib/models/parent_node.dart
@@ -16,6 +16,8 @@
* limitations under the License.
*/
+import 'package:collection/collection.dart';
+
import 'node.dart';
abstract class ParentNodeModel extends NodeModel {
@@ -23,7 +25,23 @@ abstract class ParentNodeModel extends NodeModel {
const ParentNodeModel({
required super.id,
+ required super.parent,
required super.title,
required this.nodes,
});
+
+ @override
+ NodeModel getFirstUnit() => nodes[0].getFirstUnit();
+
+ @override
+ NodeModel? getNodeByTreeIds(List<String> treeIds) {
+ final firstId = treeIds.firstOrNull;
+ final child = nodes.firstWhereOrNull((node) => node.id == firstId);
+
+ if (child == null) {
+ return null;
+ }
+
+ return child.getNodeByTreeIds(treeIds.sublist(1));
+ }
}
diff --git a/learning/tour-of-beam/frontend/lib/models/unit.dart b/learning/tour-of-beam/frontend/lib/models/unit.dart
index 48b55af33d1..eb2e158ddf6 100644
--- a/learning/tour-of-beam/frontend/lib/models/unit.dart
+++ b/learning/tour-of-beam/frontend/lib/models/unit.dart
@@ -18,8 +18,19 @@
import '../repositories/models/unit.dart';
import 'node.dart';
+import 'parent_node.dart';
class UnitModel extends NodeModel {
- UnitModel.fromResponse(UnitResponseModel unit)
- : super(id: unit.id, title: unit.title);
+ UnitModel.fromResponse(UnitResponseModel unit, ParentNodeModel parent)
+ : super(
+ id: unit.id,
+ parent: parent,
+ title: unit.title,
+ );
+
+ @override
+ NodeModel getFirstUnit() => this;
+
+ @override
+ NodeModel? getNodeByTreeIds(List<String> treeIds) => this;
}
diff --git a/learning/tour-of-beam/frontend/lib/pages/tour/controllers/content_tree.dart b/learning/tour-of-beam/frontend/lib/pages/tour/controllers/content_tree.dart
index dc5fc5a15ce..bfa63c94df4 100644
--- a/learning/tour-of-beam/frontend/lib/pages/tour/controllers/content_tree.dart
+++ b/learning/tour-of-beam/frontend/lib/pages/tour/controllers/content_tree.dart
@@ -17,33 +17,96 @@
*/
import 'package:flutter/widgets.dart';
+import 'package:get_it/get_it.dart';
import 'package:playground_components/playground_components.dart';
+import '../../../cache/content_tree.dart';
+import '../../../models/group.dart';
import '../../../models/node.dart';
+import '../../../models/unit.dart';
class ContentTreeController extends ChangeNotifier {
String _sdkId;
List<String> _treeIds;
NodeModel? _currentNode;
+ final _contentTreeCache = GetIt.instance.get<ContentTreeCache>();
+ final _expandedIds = <String>{};
+
+ Set<String> get expandedIds => _expandedIds;
ContentTreeController({
required String initialSdkId,
List<String> initialTreeIds = const [],
}) : _sdkId = initialSdkId,
- _treeIds = initialTreeIds;
+ _treeIds = initialTreeIds {
+ _expandedIds.addAll(initialTreeIds);
+
+ _contentTreeCache.addListener(_onContentTreeCacheChange);
+ _onContentTreeCacheChange();
+ }
Sdk get sdk => Sdk.parseOrCreate(_sdkId);
String get sdkId => _sdkId;
List<String> get treeIds => _treeIds;
NodeModel? get currentNode => _currentNode;
- void onNodeTap(NodeModel node) {
+ void openNode(NodeModel node) {
+ if (!_expandedIds.contains(node.id)) {
+ _expandedIds.add(node.id);
+ }
+
if (node == _currentNode) {
return;
}
- _currentNode = node;
- // TODO(alexeyinkin): Set _treeIds from node.
+ if (node is GroupModel) {
+ openNode(node.nodes.first);
+ } else if (node is UnitModel) {
+ _currentNode = node;
+ }
+
+ if (_currentNode != null) {
+ _treeIds = _getNodeAncestors(_currentNode!, [_currentNode!.id]);
+ }
+ notifyListeners();
+ }
+
+ void expandGroup(GroupModel group) {
+ _expandedIds.add(group.id);
+ notifyListeners();
+ }
+
+ void collapseGroup(GroupModel group) {
+ _expandedIds.remove(group.id);
+ notifyListeners();
+ }
+
+ List<String> _getNodeAncestors(NodeModel node, List<String> ancestorIds) {
+ if (node.parent != null) {
+ return _getNodeAncestors(
+ node.parent!,
+ [...ancestorIds, node.parent!.id],
+ );
+ }
+ return ancestorIds.reversed.toList();
+ }
+
+ void _onContentTreeCacheChange() {
+ final contentTree = _contentTreeCache.getContentTree(_sdkId);
+ if (contentTree == null) {
+ return;
+ }
+
+ openNode(
+ contentTree.getNodeByTreeIds(_treeIds) ?? contentTree.getFirstUnit(),
+ );
+
notifyListeners();
}
+
+ @override
+ void dispose() {
+ _contentTreeCache.removeListener(_onContentTreeCacheChange);
+ super.dispose();
+ }
}
diff --git a/learning/tour-of-beam/frontend/lib/pages/tour/path.dart b/learning/tour-of-beam/frontend/lib/pages/tour/path.dart
index 5f8971852f9..07dd386bdfc 100644
--- a/learning/tour-of-beam/frontend/lib/pages/tour/path.dart
+++ b/learning/tour-of-beam/frontend/lib/pages/tour/path.dart
@@ -26,7 +26,7 @@ class TourPath extends PagePath {
final String sdkId;
final List<String> treeIds;
- static final _regExp = RegExp(r'^/tour/([a-z]+)(/[/-a-zA-Z0-9]+)?$');
+ static final _regExp = RegExp(r'^/tour/([a-z]+)((/[-a-zA-Z0-9]+)*)$');
TourPath({
required this.sdkId,
@@ -47,7 +47,12 @@ class TourPath extends PagePath {
if (matches == null) return null;
final sdkId = matches[1] ?? (throw Error());
- final treeIds = matches[2]?.split('/') ?? const [];
+ final treeIdsString = matches[2];
+
+ final treeIds = (treeIdsString == null)
+ ? const <String>[]
+ // TODO(nausharipov): use RegExp to remove the slash
+ : treeIdsString.substring(1).split('/');
return TourPath(
sdkId: sdkId,
diff --git a/learning/tour-of-beam/frontend/lib/pages/tour/state.dart b/learning/tour-of-beam/frontend/lib/pages/tour/state.dart
index ae8fc0e1e70..e709839e915 100644
--- a/learning/tour-of-beam/frontend/lib/pages/tour/state.dart
+++ b/learning/tour-of-beam/frontend/lib/pages/tour/state.dart
@@ -44,6 +44,7 @@ class TourNotifier extends ChangeNotifier with PageStateMixin<void> {
playgroundController = _createPlaygroundController(initialSdkId) {
contentTreeController.addListener(_onChanged);
_unitContentCache.addListener(_onChanged);
+ _onChanged();
}
@override
@@ -53,6 +54,7 @@ class TourNotifier extends ChangeNotifier with PageStateMixin<void> {
);
void _onChanged() {
+ emitPathChanged();
final currentNode = contentTreeController.currentNode;
if (currentNode is UnitModel) {
final content = _unitContentCache.getUnitContent(
diff --git a/learning/tour-of-beam/frontend/lib/pages/tour/widgets/group.dart b/learning/tour-of-beam/frontend/lib/pages/tour/widgets/group.dart
index bdebcfc507b..fad732b105b 100644
--- a/learning/tour-of-beam/frontend/lib/pages/tour/widgets/group.dart
+++ b/learning/tour-of-beam/frontend/lib/pages/tour/widgets/group.dart
@@ -17,13 +17,12 @@
*/
import 'package:flutter/material.dart';
-import 'package:playground_components/playground_components.dart';
-import '../../../components/expansion_tile_wrapper.dart';
import '../../../models/group.dart';
import '../controllers/content_tree.dart';
import 'group_nodes.dart';
import 'group_title.dart';
+import 'stateless_expansion_tile.dart';
class GroupWidget extends StatelessWidget {
final GroupModel group;
@@ -36,23 +35,32 @@ class GroupWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
- return ExpansionTileWrapper(
- ExpansionTile(
- tilePadding: EdgeInsets.zero,
- title: GroupTitleWidget(
- group: group,
- onTap: () => contentTreeController.onNodeTap(group),
- ),
- childrenPadding: const EdgeInsets.only(
- left: BeamSizes.size24,
- ),
- children: [
- GroupNodesWidget(
+ return AnimatedBuilder(
+ animation: contentTreeController,
+ builder: (context, child) {
+ final isExpanded = contentTreeController.expandedIds.contains(group.id);
+
+ return StatelessExpansionTile(
+ isExpanded: isExpanded,
+ onExpansionChanged: (isExpanding) {
+ if (isExpanding) {
+ contentTreeController.expandGroup(group);
+ } else {
+ contentTreeController.collapseGroup(group);
+ }
+ },
+ title: GroupTitleWidget(
+ group: group,
+ onTap: () {
+ contentTreeController.openNode(group);
+ },
+ ),
+ child: GroupNodesWidget(
nodes: group.nodes,
contentTreeController: contentTreeController,
),
- ],
- ),
+ );
+ },
);
}
}
diff --git a/learning/tour-of-beam/frontend/lib/pages/tour/widgets/group_title.dart b/learning/tour-of-beam/frontend/lib/pages/tour/widgets/group_title.dart
index a25c5498bd9..974199946cb 100644
--- a/learning/tour-of-beam/frontend/lib/pages/tour/widgets/group_title.dart
+++ b/learning/tour-of-beam/frontend/lib/pages/tour/widgets/group_title.dart
@@ -38,7 +38,10 @@ class GroupTitleWidget extends StatelessWidget {
onTap: onTap,
child: Row(
children: [
- TourProgressIndicator(assetPath: Assets.svg.unitProgress0),
+ TourProgressIndicator(
+ assetPath: Assets.svg.unitProgress0,
+ isSelected: false,
+ ),
Text(
group.title,
style: Theme.of(context).textTheme.headlineMedium,
diff --git a/learning/tour-of-beam/frontend/lib/pages/tour/widgets/module.dart b/learning/tour-of-beam/frontend/lib/pages/tour/widgets/module.dart
index 886e9f98d86..b01987bf0a7 100644
--- a/learning/tour-of-beam/frontend/lib/pages/tour/widgets/module.dart
+++ b/learning/tour-of-beam/frontend/lib/pages/tour/widgets/module.dart
@@ -39,7 +39,7 @@ class ModuleWidget extends StatelessWidget {
children: [
ModuleTitleWidget(
module: module,
- onTap: () => contentTreeController.onNodeTap(module),
+ onTap: () => contentTreeController.openNode(module),
),
...module.nodes
.map(
diff --git a/learning/tour-of-beam/frontend/lib/pages/tour/widgets/group.dart b/learning/tour-of-beam/frontend/lib/pages/tour/widgets/stateless_expansion_tile.dart
similarity index 66%
copy from learning/tour-of-beam/frontend/lib/pages/tour/widgets/group.dart
copy to learning/tour-of-beam/frontend/lib/pages/tour/widgets/stateless_expansion_tile.dart
index bdebcfc507b..149bd04a586 100644
--- a/learning/tour-of-beam/frontend/lib/pages/tour/widgets/group.dart
+++ b/learning/tour-of-beam/frontend/lib/pages/tour/widgets/stateless_expansion_tile.dart
@@ -20,38 +20,33 @@ import 'package:flutter/material.dart';
import 'package:playground_components/playground_components.dart';
import '../../../components/expansion_tile_wrapper.dart';
-import '../../../models/group.dart';
-import '../controllers/content_tree.dart';
-import 'group_nodes.dart';
-import 'group_title.dart';
-class GroupWidget extends StatelessWidget {
- final GroupModel group;
- final ContentTreeController contentTreeController;
+class StatelessExpansionTile extends StatelessWidget {
+ final bool isExpanded;
+ final ValueChanged<bool>? onExpansionChanged;
+ final Widget title;
+ final Widget child;
- const GroupWidget({
- required this.group,
- required this.contentTreeController,
+ const StatelessExpansionTile({
+ required this.isExpanded,
+ required this.onExpansionChanged,
+ required this.title,
+ required this.child,
});
@override
Widget build(BuildContext context) {
return ExpansionTileWrapper(
ExpansionTile(
+ key: ValueKey(isExpanded),
+ initiallyExpanded: isExpanded,
tilePadding: EdgeInsets.zero,
- title: GroupTitleWidget(
- group: group,
- onTap: () => contentTreeController.onNodeTap(group),
- ),
+ onExpansionChanged: onExpansionChanged,
+ title: title,
childrenPadding: const EdgeInsets.only(
left: BeamSizes.size24,
),
- children: [
- GroupNodesWidget(
- nodes: group.nodes,
- contentTreeController: contentTreeController,
- ),
- ],
+ children: [child],
),
);
}
diff --git a/learning/tour-of-beam/frontend/lib/pages/tour/widgets/tour_progress_indicator.dart b/learning/tour-of-beam/frontend/lib/pages/tour/widgets/tour_progress_indicator.dart
index 6184a22a9d4..6f3d6ba5608 100644
--- a/learning/tour-of-beam/frontend/lib/pages/tour/widgets/tour_progress_indicator.dart
+++ b/learning/tour-of-beam/frontend/lib/pages/tour/widgets/tour_progress_indicator.dart
@@ -21,18 +21,30 @@ import 'package:flutter_svg/svg.dart';
import 'package:playground_components/playground_components.dart';
class TourProgressIndicator extends StatelessWidget {
+ // TODO(nausharipov): replace assetPath with progress enum
final String assetPath;
+ final bool isSelected;
- const TourProgressIndicator({required this.assetPath});
+ const TourProgressIndicator({
+ required this.assetPath,
+ required this.isSelected,
+ });
@override
Widget build(BuildContext context) {
+ final ext = Theme.of(context).extension<BeamThemeExtension>()!;
+
return Padding(
padding: const EdgeInsets.only(
left: BeamSizes.size4,
right: BeamSizes.size8,
),
- child: SvgPicture.asset(assetPath),
+ child: SvgPicture.asset(
+ assetPath,
+ color: isSelected
+ ? ext.selectedProgressColor
+ : ext.unselectedProgressColor,
+ ),
);
}
}
diff --git a/learning/tour-of-beam/frontend/lib/pages/tour/widgets/unit.dart b/learning/tour-of-beam/frontend/lib/pages/tour/widgets/unit.dart
index 914361a347a..cfc0e32235a 100644
--- a/learning/tour-of-beam/frontend/lib/pages/tour/widgets/unit.dart
+++ b/learning/tour-of-beam/frontend/lib/pages/tour/widgets/unit.dart
@@ -35,17 +35,33 @@ class UnitWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
- return ClickableWidget(
- onTap: () => contentTreeController.onNodeTap(unit),
- child: Padding(
- padding: const EdgeInsets.symmetric(vertical: BeamSizes.size10),
- child: Row(
- children: [
- TourProgressIndicator(assetPath: Assets.svg.unitProgress0),
- Expanded(child: Text(unit.title)),
- ],
- ),
- ),
+ return AnimatedBuilder(
+ animation: contentTreeController,
+ builder: (context, child) {
+ final isSelected = contentTreeController.currentNode?.id == unit.id;
+
+ return ClickableWidget(
+ onTap: () => contentTreeController.openNode(unit),
+ child: Container(
+ decoration: BoxDecoration(
+ color: isSelected ? Theme.of(context).selectedRowColor : null,
+ borderRadius: BorderRadius.circular(BeamSizes.size3),
+ ),
+ padding: const EdgeInsets.symmetric(vertical: BeamSizes.size10),
+ child: Row(
+ children: [
+ TourProgressIndicator(
+ assetPath: Assets.svg.unitProgress0,
+ isSelected: isSelected,
+ ),
+ Expanded(
+ child: Text(unit.title),
+ ),
+ ],
+ ),
+ ),
+ );
+ },
);
}
}
diff --git a/learning/tour-of-beam/frontend/lib/pages/welcome/screen.dart b/learning/tour-of-beam/frontend/lib/pages/welcome/screen.dart
index 42159356267..af6e91969bd 100644
--- a/learning/tour-of-beam/frontend/lib/pages/welcome/screen.dart
+++ b/learning/tour-of-beam/frontend/lib/pages/welcome/screen.dart
@@ -24,7 +24,6 @@ import 'package:playground_components/playground_components.dart';
import '../../components/builders/content_tree.dart';
import '../../components/builders/sdks.dart';
-import '../../components/filler_text.dart';
import '../../components/scaffold.dart';
import '../../constants/sizes.dart';
import '../../generated/assets.gen.dart';
@@ -397,7 +396,6 @@ class _ModuleBody extends StatelessWidget {
padding: _modulePadding,
child: Column(
children: [
- const FillerText(width: 20),
const SizedBox(height: BeamSizes.size16),
Divider(
color: themeData.dividerColor,
@@ -416,7 +414,6 @@ class _LastModuleBody extends StatelessWidget {
return Container(
margin: _moduleLeftMargin,
padding: _modulePadding,
- child: const FillerText(width: 20),
);
}
}
diff --git a/learning/tour-of-beam/frontend/pubspec.lock b/learning/tour-of-beam/frontend/pubspec.lock
index e1ed198ef56..51fb2a0fd73 100644
--- a/learning/tour-of-beam/frontend/pubspec.lock
+++ b/learning/tour-of-beam/frontend/pubspec.lock
@@ -156,7 +156,7 @@ packages:
source: hosted
version: "4.2.0"
collection:
- dependency: transitive
+ dependency: "direct main"
description:
name: collection
url: "https://pub.dartlang.org"
@@ -278,7 +278,7 @@ packages:
name: flutter_code_editor
url: "https://pub.dartlang.org"
source: hosted
- version: "0.1.4"
+ version: "0.1.8"
flutter_driver:
dependency: transitive
description: flutter
diff --git a/learning/tour-of-beam/frontend/pubspec.yaml b/learning/tour-of-beam/frontend/pubspec.yaml
index a6e829542e0..a8f4fd9a4ce 100644
--- a/learning/tour-of-beam/frontend/pubspec.yaml
+++ b/learning/tour-of-beam/frontend/pubspec.yaml
@@ -28,6 +28,7 @@ environment:
dependencies:
app_state: ^0.8.1
+ collection: ^1.16.0
easy_localization: ^3.0.1
easy_localization_ext: ^0.1.0
easy_localization_loader: ^1.0.0
diff --git a/playground/frontend/playground_components/lib/src/constants/colors.dart b/playground/frontend/playground_components/lib/src/constants/colors.dart
index 447d564056e..6db92295c16 100644
--- a/playground/frontend/playground_components/lib/src/constants/colors.dart
+++ b/playground/frontend/playground_components/lib/src/constants/colors.dart
@@ -36,45 +36,51 @@ class BeamColors {
class BeamGraphColors {
static const node = BeamColors.grey3;
- static const border = Color(0xFF45454E);
+ static const border = Color(0xff45454E);
static const edge = BeamLightThemeColors.primary;
}
class BeamNotificationColors {
- static const error = Color(0xFFE54545);
- static const info = Color(0xFF3E67F6);
- static const success = Color(0xFF37AC66);
- static const warning = Color(0xFFEEAB00);
+ static const error = Color(0xffE54545);
+ static const info = Color(0xff3E67F6);
+ static const success = Color(0xff37AC66);
+ static const warning = Color(0xffEEAB00);
}
class BeamLightThemeColors {
- static const border = Color(0xFFE5E5E5);
+ static const border = Color(0xffE5E5E5);
static const primaryBackground = BeamColors.white;
static const secondaryBackground = Color(0xffFCFCFC);
+ static const selectedUnitColor = Color(0xffE6E7E9);
+ static const selectedProgressColor = BeamColors.grey3;
+ static const unselectedProgressColor = selectedUnitColor;
static const grey = Color(0xffE5E5E5);
- static const listBackground = Color(0xFFA0A4AB);
+ static const listBackground = BeamColors.grey3;
static const text = BeamColors.darkBlue;
static const primary = Color(0xffE74D1A);
- static const icon = Color(0xFFA0A4AB);
+ static const icon = BeamColors.grey3;
- static const code1 = Color(0xFFDA2833);
- static const code2 = Color(0xFF5929B4);
- static const codeComment = Color(0xFF4C6B60);
- static const codeBackground = Color(0xFFFEF6F3);
+ static const code1 = Color(0xffDA2833);
+ static const code2 = Color(0xff5929B4);
+ static const codeComment = Color(0xff4C6B60);
+ static const codeBackground = Color(0xffFEF6F3);
}
class BeamDarkThemeColors {
- static const border = Color(0xFFA0A4AB);
+ static const border = BeamColors.grey3;
static const primaryBackground = Color(0xff18181B);
static const secondaryBackground = BeamColors.darkGrey;
+ static const selectedUnitColor = Color(0xff626267);
+ static const selectedProgressColor = BeamColors.grey1;
+ static const unselectedProgressColor = selectedUnitColor;
static const grey = Color(0xff3F3F46);
- static const listBackground = Color(0xFF606772);
- static const text = Color(0xffFFFFFF);
+ static const listBackground = Color(0xff606772);
+ static const text = Color(0xffffffff);
static const primary = Color(0xffF26628);
- static const icon = Color(0xFF606772);
+ static const icon = Color(0xff606772);
- static const code1 = Color(0xFFDA2833);
- static const code2 = Color(0xFF5929B4);
- static const codeComment = Color(0xFF4C6B60);
- static const codeBackground = Color(0xFF231B1B);
+ static const code1 = Color(0xffDA2833);
+ static const code2 = Color(0xff5929B4);
+ static const codeComment = Color(0xff4C6B60);
+ static const codeBackground = Color(0xff231B1B);
}
diff --git a/playground/frontend/playground_components/lib/src/theme/theme.dart b/playground/frontend/playground_components/lib/src/theme/theme.dart
index 14c811abe93..287eef0a14f 100644
--- a/playground/frontend/playground_components/lib/src/theme/theme.dart
+++ b/playground/frontend/playground_components/lib/src/theme/theme.dart
@@ -32,6 +32,9 @@ class BeamThemeExtension extends ThemeExtension<BeamThemeExtension> {
final Color primaryBackgroundTextColor;
final Color lightGreyBackgroundTextColor;
final Color secondaryBackgroundColor;
+ // TODO(nausharipov): simplify new color addition
+ final Color selectedProgressColor;
+ final Color unselectedProgressColor;
final Color codeBackgroundColor;
final TextStyle codeRootStyle;
@@ -50,6 +53,8 @@ class BeamThemeExtension extends ThemeExtension<BeamThemeExtension> {
required this.codeBackgroundColor,
required this.codeRootStyle,
required this.codeTheme,
+ required this.selectedProgressColor,
+ required this.unselectedProgressColor,
});
@override
@@ -64,6 +69,8 @@ class BeamThemeExtension extends ThemeExtension<BeamThemeExtension> {
Color? codeBackgroundColor,
TextStyle? codeRootStyle,
CodeThemeData? codeTheme,
+ Color? selectedProgressColor,
+ Color? unselectedProgressColor,
}) {
return BeamThemeExtension(
borderColor: borderColor ?? this.borderColor,
@@ -79,6 +86,10 @@ class BeamThemeExtension extends ThemeExtension<BeamThemeExtension> {
codeBackgroundColor: codeBackgroundColor ?? this.codeBackgroundColor,
codeRootStyle: codeRootStyle ?? this.codeRootStyle,
codeTheme: codeTheme ?? this.codeTheme,
+ selectedProgressColor:
+ selectedProgressColor ?? this.selectedProgressColor,
+ unselectedProgressColor:
+ unselectedProgressColor ?? this.unselectedProgressColor,
);
}
@@ -104,6 +115,13 @@ class BeamThemeExtension extends ThemeExtension<BeamThemeExtension> {
Color.lerp(codeBackgroundColor, other?.codeBackgroundColor, t)!,
codeRootStyle: TextStyle.lerp(codeRootStyle, other?.codeRootStyle, t)!,
codeTheme: t == 0.0 ? codeTheme : other?.codeTheme ?? codeTheme,
+ selectedProgressColor:
+ Color.lerp(selectedProgressColor, other?.selectedProgressColor, t)!,
+ unselectedProgressColor: Color.lerp(
+ unselectedProgressColor,
+ other?.unselectedProgressColor,
+ t,
+ )!,
);
}
}
@@ -121,6 +139,7 @@ final kLightTheme = ThemeData(
),
primaryColor: BeamLightThemeColors.primary,
scaffoldBackgroundColor: BeamLightThemeColors.secondaryBackground,
+ selectedRowColor: BeamLightThemeColors.selectedUnitColor,
tabBarTheme: _getTabBarTheme(
textColor: BeamLightThemeColors.text,
indicatorColor: BeamLightThemeColors.primary,
@@ -136,6 +155,8 @@ final kLightTheme = ThemeData(
lightGreyBackgroundTextColor: BeamColors.black,
markdownStyle: _getMarkdownStyle(Brightness.light),
secondaryBackgroundColor: BeamLightThemeColors.secondaryBackground,
+ selectedProgressColor: BeamLightThemeColors.selectedProgressColor,
+ unselectedProgressColor: BeamLightThemeColors.unselectedProgressColor,
codeBackgroundColor: BeamLightThemeColors.codeBackground,
codeRootStyle: GoogleFonts.sourceCodePro(
color: BeamLightThemeColors.text,
@@ -194,6 +215,7 @@ final kDarkTheme = ThemeData(
),
primaryColor: BeamDarkThemeColors.primary,
scaffoldBackgroundColor: BeamDarkThemeColors.secondaryBackground,
+ selectedRowColor: BeamDarkThemeColors.selectedUnitColor,
tabBarTheme: _getTabBarTheme(
textColor: BeamDarkThemeColors.text,
indicatorColor: BeamDarkThemeColors.primary,
@@ -209,6 +231,8 @@ final kDarkTheme = ThemeData(
lightGreyBackgroundTextColor: BeamColors.black,
markdownStyle: _getMarkdownStyle(Brightness.dark),
secondaryBackgroundColor: BeamDarkThemeColors.secondaryBackground,
+ selectedProgressColor: BeamDarkThemeColors.selectedProgressColor,
+ unselectedProgressColor: BeamDarkThemeColors.unselectedProgressColor,
codeBackgroundColor: BeamDarkThemeColors.codeBackground,
codeRootStyle: GoogleFonts.sourceCodePro(
color: BeamDarkThemeColors.text,
@@ -396,8 +420,10 @@ MarkdownStyleSheet _getMarkdownStyle(Brightness brightness) {
return MarkdownStyleSheet(
p: textTheme.bodyMedium,
+ pPadding: EdgeInsets.only(top: BeamSizes.size2),
h1: textTheme.headlineLarge,
h3: textTheme.headlineMedium,
+ h3Padding: EdgeInsets.only(top: BeamSizes.size4),
code: GoogleFonts.sourceCodePro(
color: textColor,
backgroundColor: BeamColors.transparent,