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 691899a8466 [Tour of Beam] [Frontend] UI refinement from 01.06.23 
(#26964)
691899a8466 is described below

commit 691899a84669c24fd59a659ca5acbaeaf4232b2f
Author: Darkhan Nausharipov <[email protected]>
AuthorDate: Wed Jun 21 21:11:40 2023 +0600

    [Tour of Beam] [Frontend] UI refinement from 01.06.23 (#26964)
    
    * default sdk, solution warning, bottom panel alignment
    
    * deleted code
    
    * markdown font sizes
    
    * review
    
    * removed Until we select an SDK the dropdown is not shown
    
    * _checkSdksLoadedCorrectly fix
    
    * _sdk in tour page test
    
    * relaunch ci
    
    * relaunch ci
    
    * deleted import, print exception
    
    * separate google forms url for tob
    
    * relaunch ci
    
    * relaunch ci
    
    * prints in tabbed_snippet_editor for debugging
    
    * reverted TabbedSnippetEditor with todo
    
    ---------
    
    Co-authored-by: darkhan.nausharipov <[email protected]>
---
 .../frontend/integration_test/tour_page_test.dart  | 18 +++++-----
 .../integration_test/welcome_page_test.dart        | 17 +---------
 .../frontend/lib/cache/unit_progress.dart          | 18 ++++------
 .../frontend/lib/components/footer.dart            |  8 ++---
 .../tour-of-beam/frontend/lib/components/logo.dart |  4 +--
 .../frontend/lib/components/scaffold.dart          | 15 ++++-----
 .../tour-of-beam/frontend/lib/constants/links.dart | 13 ++------
 .../params.dart}                                   | 22 +-----------
 .../lib/pages/tour/controllers/content_tree.dart   |  8 ++---
 .../tour-of-beam/frontend/lib/pages/tour/page.dart |  4 ---
 .../frontend/lib/pages/tour/screen.dart            | 39 ++++++++++++++--------
 .../frontend/lib/pages/tour/state.dart             | 23 +++++++------
 .../lib/pages/tour/widgets/content_tree.dart       | 26 +++++++++++++--
 .../pages/tour/widgets/markdown/code_builder.dart  | 16 +++++++--
 .../lib/pages/tour/widgets/unit_content.dart       | 14 ++++++--
 .../frontend/lib/pages/welcome/screen.dart         | 21 ++++++------
 learning/tour-of-beam/frontend/lib/state.dart      | 13 +++-----
 .../copyright.dart => lib/constants/links.dart}    | 13 ++------
 .../modules/messages/parsers/messages_parser.dart  |  1 +
 .../widgets/playground_page_footer.dart            |  2 ++
 playground/frontend/lib/playground_app.dart        |  1 -
 .../lib/src/constants/links.dart                   |  7 ++--
 .../playground_components/lib/src/theme/theme.dart | 30 +++++++++++------
 .../lib/src/widgets/copyright.dart                 |  2 +-
 .../lib/src/widgets/feedback.dart                  | 13 +++++---
 .../lib/src/widgets/tabbed_snippet_editor.dart     |  1 +
 26 files changed, 171 insertions(+), 178 deletions(-)

diff --git 
a/learning/tour-of-beam/frontend/integration_test/tour_page_test.dart 
b/learning/tour-of-beam/frontend/integration_test/tour_page_test.dart
index a683eeff00c..bb6846c28e0 100644
--- a/learning/tour-of-beam/frontend/integration_test/tour_page_test.dart
+++ b/learning/tour-of-beam/frontend/integration_test/tour_page_test.dart
@@ -43,16 +43,15 @@ import 'package:tour_of_beam/state.dart';
 import 'common/common.dart';
 import 'common/common_finders.dart';
 
+const _sdk = Sdk.java;
+
 void main() {
   IntegrationTestWidgetsFlutterBinding.ensureInitialized();
   testWidgets(
     'ToB miscellaneous ui',
     (wt) async {
       await init(wt);
-
-      final sdkCache = GetIt.instance.get<SdkCache>();
-      final sdks = sdkCache.getSdks();
-      await wt.tapAndSettle(find.text(sdks.first.title));
+      await wt.tapAndSettle(find.outlinedButtonWithText(_sdk.title));
       await wt.tapAndSettle(find.startTourButton());
 
       await _checkContentTreeBuildsProperly(wt);
@@ -80,8 +79,7 @@ Future<void> _checkContentTreeBuildsProperly(WidgetTester wt) 
async {
 
 List<ModuleModel> _getModules(WidgetTester wt) {
   final contentTreeCache = GetIt.instance.get<ContentTreeCache>();
-  final controller = getContentTreeController(wt);
-  final contentTree = contentTreeCache.getContentTree(controller.sdk);
+  final contentTree = contentTreeCache.getContentTree(_sdk);
   return contentTree?.nodes ?? (throw Exception('Cannot load modules'));
 }
 
@@ -123,7 +121,7 @@ Future<void> _checkUnitContentLoadsProperly(
 ) async {
   await wt.tapAndSettle(find.byKey(Key(unit.id)));
 
-  final hasSnippet = _getTourNotifier(wt).isUnitContainsSnippet;
+  final hasSnippet = _getTourNotifier(wt).isPlaygroundShown;
 
   expect(
     find.byType(PlaygroundWidget),
@@ -177,7 +175,7 @@ public class MyClass {
 }
 ''';
 
-  await _selectExampleWithSnippet(wt);
+  await _selectUnitWithSnippet(wt);
   await wt.pumpAndSettle();
 
   await wt.enterText(find.snippetCodeField(), code);
@@ -222,7 +220,7 @@ Future<void> _checkResizeUnitContent(WidgetTester wt) async 
{
   expectSimilar(startHandlePosition.dx, movedHandlePosition.dx - 100);
 }
 
-Future<void> _selectExampleWithSnippet(WidgetTester wt) async {
+Future<void> _selectUnitWithSnippet(WidgetTester wt) async {
   final tourNotifier = _getTourNotifier(wt);
   final modules = _getModules(wt);
 
@@ -230,7 +228,7 @@ Future<void> _selectExampleWithSnippet(WidgetTester wt) 
async {
     for (final node in module.nodes) {
       if (node is UnitModel) {
         await _checkNode(node, wt);
-        if (tourNotifier.isUnitContainsSnippet) {
+        if (tourNotifier.isPlaygroundShown) {
           return;
         }
       }
diff --git 
a/learning/tour-of-beam/frontend/integration_test/welcome_page_test.dart 
b/learning/tour-of-beam/frontend/integration_test/welcome_page_test.dart
index 4af4029991b..6e54d02188c 100644
--- a/learning/tour-of-beam/frontend/integration_test/welcome_page_test.dart
+++ b/learning/tour-of-beam/frontend/integration_test/welcome_page_test.dart
@@ -52,18 +52,7 @@ Future<void> _checkSdksLoadedCorrectly(WidgetTester wt) 
async {
     );
   }
 
-  // Until we select an SDK the dropdown is not shown.
-  expect(
-    find.sdkDropdown(),
-    findsNothing,
-  );
-
-  var button = wt.widget<ElevatedButton>(find.startTourButton());
-  expect(button.onPressed, isNull); // Verify it is disabled.
-
-  await wt.tapAndSettle(find.outlinedButtonWithText(sdks[0].title));
-
-  button = wt.widget<ElevatedButton>(find.startTourButton());
+  final button = wt.widget<ElevatedButton>(find.startTourButton());
   expect(button.onPressed, isNotNull); // Verify it is enabled.
 
   await wt.tapAndSettle(find.sdkDropdown());
@@ -105,10 +94,6 @@ void _checkModulesDisplayed() {
   final appNotifier = GetIt.instance.get<AppNotifier>();
   final sdkId = appNotifier.sdk;
 
-  if (sdkId == null) {
-    throw Exception('sdkId is null');
-  }
-
   final contentTree = contentTreeCache.getContentTree(sdkId);
   if (contentTree == null) {
     throw Exception('contentTree is null');
diff --git a/learning/tour-of-beam/frontend/lib/cache/unit_progress.dart 
b/learning/tour-of-beam/frontend/lib/cache/unit_progress.dart
index db72d000331..a13f9c4df84 100644
--- a/learning/tour-of-beam/frontend/lib/cache/unit_progress.dart
+++ b/learning/tour-of-beam/frontend/lib/cache/unit_progress.dart
@@ -75,7 +75,7 @@ class UnitProgressCache extends ChangeNotifier {
 
   List<UnitProgressModel> _getUnitProgress() {
     if (_future == null) {
-      unawaited(loadUnitProgress(GetIt.instance.get<AppNotifier>().sdk!));
+      unawaited(loadUnitProgress(GetIt.instance.get<AppNotifier>().sdk));
     }
     return _unitProgress;
   }
@@ -84,16 +84,14 @@ class UnitProgressCache extends ChangeNotifier {
 
   Future<void> completeUnit(String sdkId, String unitId) async {
     try {
-      addUpdatingUnitId(unitId);
+      _addUpdatingUnitId(unitId);
       await _getUserProgressRepository().completeUnit(sdkId, unitId);
     } finally {
-      await loadUnitProgress(GetIt.instance.get<AppNotifier>().sdk!);
-      clearUpdatingUnitId(unitId);
+      await loadUnitProgress(GetIt.instance.get<AppNotifier>().sdk);
+      _clearUpdatingUnitId(unitId);
     }
   }
 
-  Set<String> getUpdatingUnitIds() => _updatingUnitIds;
-
   Set<String> getCompletedUnits() {
     _completedUnitIds.clear();
     for (final unitProgress in _getUnitProgress()) {
@@ -104,12 +102,12 @@ class UnitProgressCache extends ChangeNotifier {
     return _completedUnitIds;
   }
 
-  void addUpdatingUnitId(String unitId) {
+  void _addUpdatingUnitId(String unitId) {
     _updatingUnitIds.add(unitId);
     notifyListeners();
   }
 
-  void clearUpdatingUnitId(String unitId) {
+  void _clearUpdatingUnitId(String unitId) {
     _updatingUnitIds.remove(unitId);
     notifyListeners();
   }
@@ -125,10 +123,6 @@ class UnitProgressCache extends ChangeNotifier {
     return getCompletedUnits().contains(unitId);
   }
 
-  String? getUnitSavedSnippetId(String? unitId) {
-    return _unitProgressByUnitId[unitId]?.userSnippetId;
-  }
-
   UnitCompletion _getUnitCompletion(String unitId) {
     final authNotifier = GetIt.instance.get<AuthNotifier>();
     if (!authNotifier.isAuthenticated) {
diff --git a/learning/tour-of-beam/frontend/lib/components/footer.dart 
b/learning/tour-of-beam/frontend/lib/components/footer.dart
index daba2dbe6e9..91ebf08f539 100644
--- a/learning/tour-of-beam/frontend/lib/components/footer.dart
+++ b/learning/tour-of-beam/frontend/lib/components/footer.dart
@@ -21,6 +21,7 @@ import 'package:flutter/material.dart';
 import 'package:get_it/get_it.dart';
 import 'package:playground_components/playground_components.dart';
 
+import '../constants/links.dart';
 import '../constants/sizes.dart';
 import '../state.dart';
 
@@ -44,6 +45,7 @@ class Footer extends StatelessWidget {
             children: [
               FeedbackWidget(
                 controller: GetIt.instance.get<FeedbackController>(),
+                feedbackFormUrl: tobFeedbackGoogleFormsUrl,
                 title: 'ui.feedbackTitle'.tr(),
               ),
               ReportIssueButton(playgroundController: playgroundController),
@@ -90,13 +92,9 @@ class _BeamVersion extends StatelessWidget {
   const _BeamVersion();
 
   Future<String?> _getBeamSdkVersion() async {
-    final sdk = GetIt.instance.get<AppNotifier>().sdk;
-    if (sdk == null) {
-      return null;
-    }
     final runnerVersion = await GetIt.instance
         .get<BuildMetadataController>()
-        .getRunnerVersion(sdk);
+        .getRunnerVersion(GetIt.instance.get<AppNotifier>().sdk);
     return runnerVersion.beamSdkVersion;
   }
 
diff --git a/learning/tour-of-beam/frontend/lib/components/logo.dart 
b/learning/tour-of-beam/frontend/lib/components/logo.dart
index 913678c76bd..ef3aeef7267 100644
--- a/learning/tour-of-beam/frontend/lib/components/logo.dart
+++ b/learning/tour-of-beam/frontend/lib/components/logo.dart
@@ -24,9 +24,9 @@ class Logo extends StatelessWidget {
 
   @override
   Widget build(BuildContext context) {
-    return Row(
+    return const Row(
       mainAxisSize: MainAxisSize.min,
-      children: const [
+      children: [
         BeamLogo(),
         _Text(),
       ],
diff --git a/learning/tour-of-beam/frontend/lib/components/scaffold.dart 
b/learning/tour-of-beam/frontend/lib/components/scaffold.dart
index 6a4df44a171..da41b8cfbfa 100644
--- a/learning/tour-of-beam/frontend/lib/components/scaffold.dart
+++ b/learning/tour-of-beam/frontend/lib/components/scaffold.dart
@@ -126,15 +126,12 @@ class _SdkSelector extends StatelessWidget {
     return AnimatedBuilder(
       animation: appNotifier,
       builder: (context, child) {
-        final sdk = appNotifier.sdk;
-        return sdk == null
-            ? Container()
-            : SdkDropdown(
-                value: sdk,
-                onChanged: (value) {
-                  appNotifier.sdk = value;
-                },
-              );
+        return SdkDropdown(
+          value: appNotifier.sdk,
+          onChanged: (value) {
+            appNotifier.sdk = value;
+          },
+        );
       },
     );
   }
diff --git 
a/playground/frontend/playground_components/lib/src/widgets/copyright.dart 
b/learning/tour-of-beam/frontend/lib/constants/links.dart
similarity index 74%
copy from 
playground/frontend/playground_components/lib/src/widgets/copyright.dart
copy to learning/tour-of-beam/frontend/lib/constants/links.dart
index 86b0346f285..de299ca23d4 100644
--- a/playground/frontend/playground_components/lib/src/widgets/copyright.dart
+++ b/learning/tour-of-beam/frontend/lib/constants/links.dart
@@ -16,14 +16,5 @@
  * limitations under the License.
  */
 
-import 'package:easy_localization/easy_localization.dart';
-import 'package:flutter/widgets.dart';
-
-class CopyrightWidget extends StatelessWidget {
-  const CopyrightWidget();
-
-  @override
-  Widget build(BuildContext context) {
-    return const Text('ui.copyright').tr();
-  }
-}
+const tobFeedbackGoogleFormsUrl =
+    
'https://docs.google.com/forms/d/e/1FAIpQLSdI3yTmsCtk_neVPt0zQOPSmxDBlz3uX2AcmUpoNT6iGEwkUQ/viewform?usp=sharing';
diff --git 
a/learning/tour-of-beam/frontend/lib/pages/tour/widgets/content_tree_title.dart 
b/learning/tour-of-beam/frontend/lib/constants/params.dart
similarity index 59%
rename from 
learning/tour-of-beam/frontend/lib/pages/tour/widgets/content_tree_title.dart
rename to learning/tour-of-beam/frontend/lib/constants/params.dart
index d92ff7605d8..306a73fdef3 100644
--- 
a/learning/tour-of-beam/frontend/lib/pages/tour/widgets/content_tree_title.dart
+++ b/learning/tour-of-beam/frontend/lib/constants/params.dart
@@ -16,26 +16,6 @@
  * limitations under the License.
  */
 
-import 'package:easy_localization/easy_localization.dart';
-import 'package:flutter/material.dart';
 import 'package:playground_components/playground_components.dart';
 
-class ContentTreeTitleWidget extends StatelessWidget {
-  const ContentTreeTitleWidget();
-
-  @override
-  Widget build(BuildContext context) {
-    return Padding(
-      padding: const EdgeInsets.symmetric(vertical: BeamSizes.size12),
-      child: Row(
-        mainAxisAlignment: MainAxisAlignment.spaceBetween,
-        children: [
-          Text(
-            'pages.tour.summaryTitle',
-            style: Theme.of(context).textTheme.headlineLarge,
-          ).tr(),
-        ],
-      ),
-    );
-  }
-}
+const defaultSdk = Sdk.java;
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 5f1aa132066..32f76a1a130 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
@@ -18,7 +18,6 @@
 
 import 'package:flutter/widgets.dart';
 import 'package:get_it/get_it.dart';
-import 'package:playground_components/playground_components.dart';
 
 import '../../../cache/content_tree.dart';
 
@@ -28,7 +27,6 @@ import '../../../models/unit.dart';
 import '../../../state.dart';
 
 class ContentTreeController extends ChangeNotifier {
-  final Sdk initialSdk;
   List<String> _breadcrumbIds;
   NodeModel? _currentNode;
   final _contentTreeCache = GetIt.instance.get<ContentTreeCache>();
@@ -40,7 +38,6 @@ class ContentTreeController extends ChangeNotifier {
   Set<String> get expandedIds => _expandedIds;
 
   ContentTreeController({
-    required this.initialSdk,
     List<String> initialBreadcrumbIds = const [],
   }) : _breadcrumbIds = initialBreadcrumbIds {
     _expandedIds.addAll(initialBreadcrumbIds);
@@ -49,7 +46,6 @@ class ContentTreeController extends ChangeNotifier {
     _onContentTreeCacheChange();
   }
 
-  Sdk get sdk => GetIt.instance.get<AppNotifier>().sdk ?? initialSdk;
   List<String> get breadcrumbIds => _breadcrumbIds;
   NodeModel? get currentNode => _currentNode;
 
@@ -109,7 +105,9 @@ class ContentTreeController extends ChangeNotifier {
   }
 
   void _onContentTreeCacheChange() {
-    final contentTree = _contentTreeCache.getContentTree(sdk);
+    final contentTree = _contentTreeCache.getContentTree(
+      GetIt.instance.get<AppNotifier>().sdk,
+    );
     if (contentTree == null) {
       return;
     }
diff --git a/learning/tour-of-beam/frontend/lib/pages/tour/page.dart 
b/learning/tour-of-beam/frontend/lib/pages/tour/page.dart
index 929b280fa79..08a4a6ad839 100644
--- a/learning/tour-of-beam/frontend/lib/pages/tour/page.dart
+++ b/learning/tour-of-beam/frontend/lib/pages/tour/page.dart
@@ -18,7 +18,6 @@
 
 import 'package:app_state/app_state.dart';
 import 'package:flutter/widgets.dart';
-import 'package:playground_components/playground_components.dart';
 
 import 'screen.dart';
 import 'state.dart';
@@ -28,12 +27,10 @@ class TourPage extends StatefulMaterialPage<void, 
TourNotifier> {
 
   /// Called when navigating to the page programmatically.
   TourPage({
-    required Sdk sdk,
     List<String> breadcrumbIds = const [],
   }) : super(
           key: const ValueKey(classFactoryKey),
           state: TourNotifier(
-            initialSdk: sdk,
             initialBreadcrumbIds: breadcrumbIds,
           ),
           createScreen: TourScreen.new,
@@ -44,7 +41,6 @@ class TourPage extends StatefulMaterialPage<void, 
TourNotifier> {
     final breadcrumbIds = state['breadcrumbIds'];
 
     return TourPage(
-      sdk: Sdk.parseOrCreate(state['sdkId']),
       breadcrumbIds:
           breadcrumbIds is List ? breadcrumbIds.cast<String>() : const [],
     );
diff --git a/learning/tour-of-beam/frontend/lib/pages/tour/screen.dart 
b/learning/tour-of-beam/frontend/lib/pages/tour/screen.dart
index 75cc77e6b9c..c9cc88fa3d4 100644
--- a/learning/tour-of-beam/frontend/lib/pages/tour/screen.dart
+++ b/learning/tour-of-beam/frontend/lib/pages/tour/screen.dart
@@ -44,7 +44,7 @@ class TourScreen extends StatelessWidget {
         return TobShortcutsManager(
           tourNotifier: tourNotifier,
           child: TobScaffold(
-            playgroundController: tourNotifier.isUnitContainsSnippet
+            playgroundController: tourNotifier.isPlaygroundShown
                 ? tourNotifier.playgroundController
                 : null,
             child:
@@ -87,23 +87,36 @@ class _UnitContentWidget extends StatelessWidget {
     return AnimatedBuilder(
       animation: tourNotifier,
       builder: (context, widget) {
-        return !tourNotifier.isUnitContainsSnippet
-            ? UnitContentWidget(tourNotifier)
-            : SplitView(
-                direction: Axis.horizontal,
-                dragHandleKey: TourScreen.dragHandleKey,
-                first: UnitContentWidget(tourNotifier),
-                second: tourNotifier.isSnippetLoading
-                    ? const Center(child: CircularProgressIndicator())
-                    : PlaygroundWidget(
-                        tourNotifier: tourNotifier,
-                      ),
-              );
+        return tourNotifier.isPlaygroundShown
+            ? _UnitContentWithPlaygroundSplitView(tourNotifier)
+            : UnitContentWidget(tourNotifier);
       },
     );
   }
 }
 
+class _UnitContentWithPlaygroundSplitView extends StatelessWidget {
+  final TourNotifier tourNotifier;
+  const _UnitContentWithPlaygroundSplitView(this.tourNotifier);
+
+  @override
+  Widget build(BuildContext context) {
+    final isPlaygroundLoading = tourNotifier.currentUnitContent == null ||
+        tourNotifier.isSnippetLoading;
+
+    return SplitView(
+      direction: Axis.horizontal,
+      dragHandleKey: TourScreen.dragHandleKey,
+      first: UnitContentWidget(tourNotifier),
+      second: isPlaygroundLoading
+          ? const Center(child: CircularProgressIndicator())
+          : PlaygroundWidget(
+              tourNotifier: tourNotifier,
+            ),
+    );
+  }
+}
+
 class _NarrowTour extends StatelessWidget {
   final TourNotifier tourNotifier;
 
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 c99df29d4c8..8b2a91f1d02 100644
--- a/learning/tour-of-beam/frontend/lib/pages/tour/state.dart
+++ b/learning/tour-of-beam/frontend/lib/pages/tour/state.dart
@@ -50,20 +50,18 @@ class TourNotifier extends ChangeNotifier with 
PageStateMixin<void> {
   final _unitContentCache = GetIt.instance.get<UnitContentCache>();
   final _unitProgressCache = GetIt.instance.get<UnitProgressCache>();
   UnitContentModel? _currentUnitContent;
+  bool _isPlaygroundShown = false;
   DateTime? _currentUnitOpenedAt;
 
   TobEventContext _tobEventContext = TobEventContext.empty;
   TobEventContext get tobEventContext => _tobEventContext;
 
   TourNotifier({
-    required Sdk initialSdk,
     List<String> initialBreadcrumbIds = const [],
   })  : contentTreeController = ContentTreeController(
-          initialSdk: initialSdk,
           initialBreadcrumbIds: initialBreadcrumbIds,
         ),
-        playgroundController = _createPlaygroundController(initialSdk.id) {
-    _appNotifier.sdk ??= initialSdk;
+        playgroundController = _createPlaygroundController() {
     contentTreeController.addListener(_onUnitChanged);
     _appNotifier.addListener(_onAppNotifierChanged);
     _authNotifier.addListener(_onAuthChanged);
@@ -84,17 +82,17 @@ class TourNotifier extends ChangeNotifier with 
PageStateMixin<void> {
 
   @override
   PagePath get path => TourPath(
-        sdkId: contentTreeController.sdk.id,
+        sdkId: GetIt.instance.get<AppNotifier>().sdk.id,
         breadcrumbIds: contentTreeController.breadcrumbIds,
       );
 
   bool get isAuthenticated => _authNotifier.isAuthenticated;
 
-  Sdk get currentSdk => _appNotifier.sdk!;
+  Sdk get currentSdk => _appNotifier.sdk;
   String? get currentUnitId => _currentUnitContent?.id;
   UnitContentModel? get currentUnitContent => _currentUnitContent;
 
-  bool get hasSolution => currentUnitContent?.solutionSnippetId != null;
+  bool get hasSolution => _currentUnitContent?.solutionSnippetId != null;
   bool get isCodeSaved => _unitProgressCache.hasSavedSnippet(currentUnitId);
 
   SnippetType _snippetType = SnippetType.original;
@@ -109,7 +107,7 @@ class TourNotifier extends ChangeNotifier with 
PageStateMixin<void> {
     notifyListeners();
   }
 
-  bool get isUnitContainsSnippet => currentUnitContent?.taskSnippetId != null;
+  bool get isPlaygroundShown => _isPlaygroundShown;
   bool get isSnippetLoading => _isLoadingSnippet;
 
   Future<void> _onAuthChanged() async {
@@ -171,6 +169,9 @@ class TourNotifier extends ChangeNotifier with 
PageStateMixin<void> {
       _trackUnitClosed();
     }
 
+    if (_currentUnitContent != null) {
+      _isPlaygroundShown = _currentUnitContent!.taskSnippetId != null;
+    }
     _currentUnitContent = unitContent;
 
     if (_currentUnitContent != null) {
@@ -358,7 +359,7 @@ class TourNotifier extends ChangeNotifier with 
PageStateMixin<void> {
 
   // Playground controller.
 
-  static PlaygroundController _createPlaygroundController(String initialSdkId) 
{
+  static PlaygroundController _createPlaygroundController() {
     final playgroundController = PlaygroundController(
       codeClient: GetIt.instance.get<CodeClient>(),
       exampleCache: ExampleCache(
@@ -371,7 +372,9 @@ class TourNotifier extends ChangeNotifier with 
PageStateMixin<void> {
       playgroundController.examplesLoader.loadIfNew(
         ExamplesLoadingDescriptor(
           descriptors: [
-            EmptyExampleLoadingDescriptor(sdk: 
Sdk.parseOrCreate(initialSdkId)),
+            EmptyExampleLoadingDescriptor(
+              sdk: GetIt.instance.get<AppNotifier>().sdk,
+            ),
           ],
         ),
       ),
diff --git 
a/learning/tour-of-beam/frontend/lib/pages/tour/widgets/content_tree.dart 
b/learning/tour-of-beam/frontend/lib/pages/tour/widgets/content_tree.dart
index 9fd96b5e93b..587dc275696 100644
--- a/learning/tour-of-beam/frontend/lib/pages/tour/widgets/content_tree.dart
+++ b/learning/tour-of-beam/frontend/lib/pages/tour/widgets/content_tree.dart
@@ -16,6 +16,7 @@
  * limitations under the License.
  */
 
+import 'package:easy_localization/easy_localization.dart';
 import 'package:flutter/material.dart';
 import 'package:get_it/get_it.dart';
 import 'package:playground_components/playground_components.dart';
@@ -23,7 +24,6 @@ import 
'package:playground_components/playground_components.dart';
 import '../../../components/builders/content_tree.dart';
 import '../../../state.dart';
 import '../controllers/content_tree.dart';
-import 'content_tree_title.dart';
 import 'module.dart';
 
 // TODO(nausharipov): make it collapsible
@@ -41,7 +41,7 @@ class ContentTreeWidget extends StatelessWidget {
       child: AnimatedBuilder(
         animation: GetIt.instance.get<AppNotifier>(),
         builder: (context, child) => ContentTreeBuilder(
-          sdk: controller.sdk,
+          sdk: GetIt.instance.get<AppNotifier>().sdk,
           builder: (context, contentTree, child) {
             if (contentTree == null) {
               return const Center(child: CircularProgressIndicator());
@@ -53,7 +53,7 @@ class ContentTreeWidget extends StatelessWidget {
               ),
               child: Column(
                 children: [
-                  const ContentTreeTitleWidget(),
+                  const _Title(),
                   ...contentTree.nodes.map(
                     (module) => ModuleWidget(
                       module: module,
@@ -70,3 +70,23 @@ class ContentTreeWidget extends StatelessWidget {
     );
   }
 }
+
+class _Title extends StatelessWidget {
+  const _Title();
+
+  @override
+  Widget build(BuildContext context) {
+    return Padding(
+      padding: const EdgeInsets.symmetric(vertical: BeamSizes.size12),
+      child: Row(
+        mainAxisAlignment: MainAxisAlignment.spaceBetween,
+        children: [
+          Text(
+            'pages.tour.summaryTitle',
+            style: Theme.of(context).textTheme.headlineLarge,
+          ).tr(),
+        ],
+      ),
+    );
+  }
+}
diff --git 
a/learning/tour-of-beam/frontend/lib/pages/tour/widgets/markdown/code_builder.dart
 
b/learning/tour-of-beam/frontend/lib/pages/tour/widgets/markdown/code_builder.dart
index b770f777a7e..62e7ab6aab7 100644
--- 
a/learning/tour-of-beam/frontend/lib/pages/tour/widgets/markdown/code_builder.dart
+++ 
b/learning/tour-of-beam/frontend/lib/pages/tour/widgets/markdown/code_builder.dart
@@ -50,7 +50,13 @@ class _CodeBlock extends StatelessWidget {
           controller: scrollController,
           padding: const EdgeInsets.all(BeamSizes.size10),
           scrollDirection: Axis.horizontal,
-          child: Text(text),
+          child: Text(
+            text,
+            style: Theme.of(context)
+                .extension<BeamThemeExtension>()!
+                .markdownStyle
+                .code,
+          ),
         ),
       ),
     );
@@ -76,7 +82,13 @@ class _InlineCode extends StatelessWidget {
       ),
       child: Text(
         text,
-        style: TextStyle(color: Theme.of(context).primaryColor),
+        style: Theme.of(context)
+            .extension<BeamThemeExtension>()!
+            .markdownStyle
+            .p!
+            .copyWith(
+              color: Theme.of(context).primaryColor,
+            ),
       ),
     );
   }
diff --git 
a/learning/tour-of-beam/frontend/lib/pages/tour/widgets/unit_content.dart 
b/learning/tour-of-beam/frontend/lib/pages/tour/widgets/unit_content.dart
index 777ae3011ca..866538c53ee 100644
--- a/learning/tour-of-beam/frontend/lib/pages/tour/widgets/unit_content.dart
+++ b/learning/tour-of-beam/frontend/lib/pages/tour/widgets/unit_content.dart
@@ -128,7 +128,7 @@ class _Title extends StatelessWidget {
       ),
       child: Text(
         title,
-        style: Theme.of(context).textTheme.headlineLarge,
+        style: Theme.of(context).textTheme.titleLarge,
         textAlign: TextAlign.start,
       ),
     );
@@ -196,7 +196,15 @@ class _SnippetTypeSwitcher extends StatelessWidget {
                 title: 'pages.tour.solution'.tr(),
                 value: SnippetType.solution,
                 onChanged: () async {
-                  await _setSnippetByType(SnippetType.solution);
+                  final confirmed = await ConfirmDialog.show(
+                    context: context,
+                    confirmButtonText: 'pages.tour.showSolution'.tr(),
+                    subtitle: 'pages.tour.solveYourself'.tr(),
+                    title: 'pages.tour.solution'.tr(),
+                  );
+                  if (confirmed) {
+                    await _setSnippetByType(SnippetType.solution);
+                  }
                 },
               ),
             if (tourNotifier.hasSolution || tourNotifier.isCodeSaved)
@@ -285,7 +293,7 @@ class _ContentFooter extends StatelessWidget {
             
themeData.extension<BeamThemeExtension>()?.secondaryBackgroundColor,
       ),
       width: double.infinity,
-      padding: const EdgeInsets.all(BeamSizes.size20),
+      padding: const EdgeInsets.all(BeamSizes.size10),
       child: Row(
         mainAxisAlignment: MainAxisAlignment.center,
         children: [
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 eab7557ac56..a15b05bbc7c 100644
--- a/learning/tour-of-beam/frontend/lib/pages/welcome/screen.dart
+++ b/learning/tour-of-beam/frontend/lib/pages/welcome/screen.dart
@@ -16,6 +16,8 @@
  * limitations under the License.
  */
 
+import 'dart:async';
+
 import 'package:app_state/app_state.dart';
 import 'package:easy_localization/easy_localization.dart';
 import 'package:flutter/gestures.dart';
@@ -60,10 +62,10 @@ class _WideWelcome extends StatelessWidget {
 
   @override
   Widget build(BuildContext context) {
-    return IntrinsicHeight(
+    return const IntrinsicHeight(
       child: Row(
         crossAxisAlignment: CrossAxisAlignment.start,
-        children: const [
+        children: [
           Expanded(
             child: _SdkSelection(),
           ),
@@ -83,8 +85,8 @@ class _NarrowWelcome extends StatelessWidget {
 
   @override
   Widget build(BuildContext context) {
-    return Column(
-      children: const [
+    return const Column(
+      children: [
         _SdkSelection(),
         _TourSummary(),
       ],
@@ -157,7 +159,7 @@ class _SdkSelection extends StatelessWidget {
     if (sdk == null) {
       return;
     }
-    await GetIt.instance.get<PageStack>().push(TourPage(sdk: sdk));
+    await GetIt.instance.get<PageStack>().push(TourPage());
   }
 }
 
@@ -171,9 +173,6 @@ class _TourSummary extends StatelessWidget {
       animation: appNotifier,
       builder: (context, child) {
         final sdk = appNotifier.sdk;
-        if (sdk == null) {
-          return Container();
-        }
 
         return Padding(
           padding: const EdgeInsets.symmetric(
@@ -259,7 +258,7 @@ class _IntroTextBody extends StatelessWidget {
                     .copyWith(color: Theme.of(context).primaryColor),
                 recognizer: TapGestureRecognizer()
                   ..onTap = () {
-                    _openLoginDialog(context);
+                    unawaited(_openLoginDialog(context));
                   },
               ),
             TextSpan(text: '\n\n${'pages.welcome.selectLanguage'.tr()}'),
@@ -269,8 +268,8 @@ class _IntroTextBody extends StatelessWidget {
     );
   }
 
-  void _openLoginDialog(BuildContext context) {
-    showDialog(
+  Future<void> _openLoginDialog(BuildContext context) async {
+    await showDialog(
       context: context,
       builder: (context) => Dialog(
         child: LoginContent(
diff --git a/learning/tour-of-beam/frontend/lib/state.dart 
b/learning/tour-of-beam/frontend/lib/state.dart
index e690e398fae..3209d48a55a 100644
--- a/learning/tour-of-beam/frontend/lib/state.dart
+++ b/learning/tour-of-beam/frontend/lib/state.dart
@@ -22,6 +22,7 @@ import 'package:flutter/material.dart';
 import 'package:playground_components/playground_components.dart';
 import 'package:shared_preferences/shared_preferences.dart';
 
+import 'constants/params.dart';
 import 'constants/storage_keys.dart';
 
 class AppNotifier extends ChangeNotifier {
@@ -31,21 +32,17 @@ class AppNotifier extends ChangeNotifier {
     unawaited(_readSdk());
   }
 
-  Sdk? get sdk => _sdk;
+  Sdk get sdk => _sdk ?? defaultSdk;
 
-  set sdk(Sdk? newValue) {
+  set sdk(Sdk newValue) {
     _sdk = newValue;
     unawaited(_writeSdk(newValue));
     notifyListeners();
   }
 
-  Future<void> _writeSdk(Sdk? value) async {
+  Future<void> _writeSdk(Sdk value) async {
     final preferences = await SharedPreferences.getInstance();
-    if (value != null) {
-      await preferences.setString(StorageKeys.sdkId, value.id);
-    } else {
-      await preferences.remove(StorageKeys.sdkId);
-    }
+    await preferences.setString(StorageKeys.sdkId, value.id);
   }
 
   Future<void> _readSdk() async {
diff --git 
a/playground/frontend/playground_components/lib/src/widgets/copyright.dart 
b/playground/frontend/lib/constants/links.dart
similarity index 74%
copy from 
playground/frontend/playground_components/lib/src/widgets/copyright.dart
copy to playground/frontend/lib/constants/links.dart
index 86b0346f285..5307c4dd18f 100644
--- a/playground/frontend/playground_components/lib/src/widgets/copyright.dart
+++ b/playground/frontend/lib/constants/links.dart
@@ -16,14 +16,5 @@
  * limitations under the License.
  */
 
-import 'package:easy_localization/easy_localization.dart';
-import 'package:flutter/widgets.dart';
-
-class CopyrightWidget extends StatelessWidget {
-  const CopyrightWidget();
-
-  @override
-  Widget build(BuildContext context) {
-    return const Text('ui.copyright').tr();
-  }
-}
+const playgroundFeedbackGoogleFormsUrl =
+    
'https://docs.google.com/forms/d/e/1FAIpQLSd5_5XeOwwW2yjEVHUXmiBad8Lxk-4OtNcgG45pbyAZzd4EbA/viewform?usp=pp_url';
diff --git 
a/playground/frontend/lib/modules/messages/parsers/messages_parser.dart 
b/playground/frontend/lib/modules/messages/parsers/messages_parser.dart
index 34e1b5b653c..b2e98bf42d1 100644
--- a/playground/frontend/lib/modules/messages/parsers/messages_parser.dart
+++ b/playground/frontend/lib/modules/messages/parsers/messages_parser.dart
@@ -45,6 +45,7 @@ class MessagesParser {
         }
       } on FormatException catch (ex) {
         // TODO: Log
+        print('_tryParseIfJson FormatException: $ex');
       }
     }
 
diff --git 
a/playground/frontend/lib/pages/standalone_playground/widgets/playground_page_footer.dart
 
b/playground/frontend/lib/pages/standalone_playground/widgets/playground_page_footer.dart
index 8af1ab6eca3..22bbb71aaf6 100644
--- 
a/playground/frontend/lib/pages/standalone_playground/widgets/playground_page_footer.dart
+++ 
b/playground/frontend/lib/pages/standalone_playground/widgets/playground_page_footer.dart
@@ -23,6 +23,7 @@ import 
'package:playground_components/playground_components.dart';
 import 'package:provider/provider.dart';
 
 import '../../../constants/sizes.dart';
+import '../../../constants/links.dart';
 
 class PlaygroundPageFooter extends StatelessWidget {
   const PlaygroundPageFooter({Key? key}) : super(key: key);
@@ -46,6 +47,7 @@ class PlaygroundPageFooter extends StatelessWidget {
             children: [
               FeedbackWidget(
                 controller: GetIt.instance.get<FeedbackController>(),
+                feedbackFormUrl: playgroundFeedbackGoogleFormsUrl,
                 title: 'ui.feedbackTitle'.tr(),
               ),
               ReportIssueButton(playgroundController: playgroundController),
diff --git a/playground/frontend/lib/playground_app.dart 
b/playground/frontend/lib/playground_app.dart
index d1777896758..001cd4c07f0 100644
--- a/playground/frontend/lib/playground_app.dart
+++ b/playground/frontend/lib/playground_app.dart
@@ -16,7 +16,6 @@
  * limitations under the License.
  */
 
-import 'package:app_state/app_state.dart';
 import 'package:easy_localization/easy_localization.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter_gen/gen_l10n/app_localizations.dart';
diff --git 
a/playground/frontend/playground_components/lib/src/constants/links.dart 
b/playground/frontend/playground_components/lib/src/constants/links.dart
index 9a29bf72983..c5addcfcc26 100644
--- a/playground/frontend/playground_components/lib/src/constants/links.dart
+++ b/playground/frontend/playground_components/lib/src/constants/links.dart
@@ -24,15 +24,12 @@ class BeamLinks {
 
   // GitHub
   static const github = 'https://github.com/apache/beam';
-  static const newExample = 
'https://github.com/apache/beam/blob/master/playground/load_your_code.md';
+  static const newExample =
+      
'https://github.com/apache/beam/blob/master/playground/load_your_code.md';
   static const reportIssue = 'https://github.com/apache/beam/issues';
 
   // Projects
   static const playgroundGitHub =
       'https://github.com/apache/beam/tree/master/playground';
   static const scioGitHub = 'https://github.com/spotify/scio';
-
-  // Forms
-  static const feedbackGoogleForms =
-      
'https://docs.google.com/forms/d/e/1FAIpQLSd5_5XeOwwW2yjEVHUXmiBad8Lxk-4OtNcgG45pbyAZzd4EbA/viewform?usp=pp_url';
 }
diff --git a/playground/frontend/playground_components/lib/src/theme/theme.dart 
b/playground/frontend/playground_components/lib/src/theme/theme.dart
index 25db8d562dd..28efa7f3a25 100644
--- a/playground/frontend/playground_components/lib/src/theme/theme.dart
+++ b/playground/frontend/playground_components/lib/src/theme/theme.dart
@@ -442,32 +442,40 @@ RoundedRectangleBorder _getButtonBorder(double radius) {
 MarkdownStyleSheet _getMarkdownStyle(Brightness brightness) {
   final Color primaryColor;
   final Color codeblockBackgroundColor;
-  final Color textColor;
   if (brightness == Brightness.light) {
     primaryColor = BeamLightThemeColors.primary;
     codeblockBackgroundColor = BeamLightThemeColors.codeBackground;
-    textColor = BeamLightThemeColors.text;
   } else {
     primaryColor = BeamDarkThemeColors.primary;
     codeblockBackgroundColor = BeamDarkThemeColors.codeBackground;
-    textColor = BeamDarkThemeColors.text;
   }
-  final textTheme = _getTextTheme(textColor);
 
   return MarkdownStyleSheet(
-    p: textTheme.bodyMedium,
-    pPadding: EdgeInsets.only(top: BeamSizes.size2),
-    h1: textTheme.headlineMedium,
-    h3: textTheme.headlineSmall,
-    h3Padding: EdgeInsets.only(top: BeamSizes.size4),
+    p: const TextStyle(
+      fontSize: 16,
+      fontWeight: FontWeight.w400,
+    ),
+    pPadding: const EdgeInsets.only(top: BeamSizes.size2),
+    h1: const TextStyle(
+      fontSize: 18,
+      fontWeight: FontWeight.w600,
+    ),
+    h2: const TextStyle(
+      fontSize: 17,
+      fontWeight: FontWeight.w600,
+    ),
+    h3: const TextStyle(
+      fontSize: 16,
+      fontWeight: FontWeight.w600,
+    ),
+    h3Padding: const EdgeInsets.only(top: BeamSizes.size4),
     blockquoteDecoration: BoxDecoration(
       color: codeblockBackgroundColor,
       borderRadius: BorderRadius.circular(BeamSizes.size6),
     ),
     code: GoogleFonts.sourceCodePro(
-      color: textColor,
       backgroundColor: BeamColors.transparent,
-      fontSize: BeamSizes.size12,
+      fontSize: 14,
     ),
     codeblockDecoration: BoxDecoration(
       color: codeblockBackgroundColor,
diff --git 
a/playground/frontend/playground_components/lib/src/widgets/copyright.dart 
b/playground/frontend/playground_components/lib/src/widgets/copyright.dart
index 86b0346f285..e0f3a92e8b2 100644
--- a/playground/frontend/playground_components/lib/src/widgets/copyright.dart
+++ b/playground/frontend/playground_components/lib/src/widgets/copyright.dart
@@ -17,7 +17,7 @@
  */
 
 import 'package:easy_localization/easy_localization.dart';
-import 'package:flutter/widgets.dart';
+import 'package:flutter/material.dart';
 
 class CopyrightWidget extends StatelessWidget {
   const CopyrightWidget();
diff --git 
a/playground/frontend/playground_components/lib/src/widgets/feedback.dart 
b/playground/frontend/playground_components/lib/src/widgets/feedback.dart
index e42f17dae7e..62fd515dfad 100644
--- a/playground/frontend/playground_components/lib/src/widgets/feedback.dart
+++ b/playground/frontend/playground_components/lib/src/widgets/feedback.dart
@@ -29,10 +29,12 @@ class FeedbackWidget extends StatelessWidget {
   static const negativeRatingButtonKey = Key('negative');
 
   final FeedbackController controller;
+  final String feedbackFormUrl;
   final String title;
 
   const FeedbackWidget({
     required this.controller,
+    required this.feedbackFormUrl,
     required this.title,
   });
 
@@ -58,6 +60,7 @@ class FeedbackWidget extends StatelessWidget {
           child: FeedbackDropdown(
             close: closeNotifier.notifyPublic,
             controller: controller,
+            feedbackFormUrl: feedbackFormUrl,
             rating: rating,
             title: 'widgets.feedback.title'.tr(),
             subtitle: 'widgets.feedback.hint'.tr(),
@@ -143,17 +146,19 @@ class FeedbackDropdown extends StatelessWidget {
   static const sendButtonKey = Key('sendFeedbackButtonKey');
   static const textFieldKey = Key('feedbackTextFieldKey');
 
-  final FeedbackController controller;
   final VoidCallback close;
+  final FeedbackController controller;
+  final String feedbackFormUrl;
   final FeedbackRating rating;
   final String title;
   final String subtitle;
 
   const FeedbackDropdown({
+    required this.close,
     required this.controller,
+    required this.feedbackFormUrl,
     required this.title,
     required this.rating,
-    required this.close,
     required this.subtitle,
   });
 
@@ -181,9 +186,9 @@ class FeedbackDropdown extends StatelessWidget {
               textAlign: TextAlign.center,
             ),
             const SizedBox(height: BeamSizes.size16),
-            const Expanded(
+            Expanded(
               child: IFrameWidget(
-                url: BeamLinks.feedbackGoogleForms,
+                url: feedbackFormUrl,
                 viewType: 'feedbackGoogleForms',
               ),
             ),
diff --git 
a/playground/frontend/playground_components/lib/src/widgets/tabbed_snippet_editor.dart
 
b/playground/frontend/playground_components/lib/src/widgets/tabbed_snippet_editor.dart
index 0c1222cfded..4b78b3277e9 100644
--- 
a/playground/frontend/playground_components/lib/src/widgets/tabbed_snippet_editor.dart
+++ 
b/playground/frontend/playground_components/lib/src/widgets/tabbed_snippet_editor.dart
@@ -47,6 +47,7 @@ class TabbedSnippetEditor extends StatelessWidget {
     final keys = files.map((f) => f.name).toList(growable: false);
     final initialKey = files.firstWhereOrNull((f) => f.isMain)?.name;
 
+    // TODO(nausharipov): fork keyed_collection_widgets and put prints.
     return DefaultKeyedTabController<String>.fromKeys(
       animationDuration: Duration.zero,
       initialKey: initialKey,


Reply via email to