Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Tour of Beam] Save code, Delete account & other tasks #26010

Merged
merged 10 commits into from
Apr 24, 2023
23 changes: 19 additions & 4 deletions learning/tour-of-beam/frontend/assets/translations/en.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,18 @@

ui:
about: About Tour of Beam
builtWith: Built with Apache Beam
builtWith: Built with Apache Beam {beamSdkVersion}
cancel: Cancel
continueGitHub: Continue with GitHub
continueGoogle: Continue with Google
hint: Hint
deleteAccount: Delete my account
copyright: © The Apache Software Foundation
deleteMyAccount: Delete my account
deleteTobAccount: Delete my Tour of Beam account
feedbackTitle: Enjoying Tour of Beam?
privacyPolicy: Privacy Policy
reportIssue: Report Issue in GitHub
signIn: Sign in
signOut: Sign out
solution: Solution
toWebsite: To Apache Beam website

pages:
Expand All @@ -35,11 +39,22 @@ pages:
startTour: Start your tour
title: Welcome to the Tour of Beam!
tour:
assignment: Assignment
completeUnit: Complete Unit
content: Content
hint: Hint
showSolution: Show the solution
solution: Solution
solveYourself: Before revealing the solution, try solving the challenge on your own. Remember, the more you practice, the better you will become. Give it a shot and see how far you can get.
example: Example
myCode: My code
playground: Playground
saving: Saving...
summaryTitle: Table of Contents

dialogs:
signInIf: If you would like to save your progress and track completed modules
deleteAccountWarning: Are you sure you want to delete your Tour of Beam account? This will permanently erase your learning progress.

complexity:
basic: Basic level
Expand Down
24 changes: 23 additions & 1 deletion learning/tour-of-beam/frontend/lib/assets/assets.gen.dart

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

21 changes: 20 additions & 1 deletion learning/tour-of-beam/frontend/lib/auth/notifier.dart
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ import 'dart:async';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:firebase_auth_platform_interface/firebase_auth_platform_interface.dart';
import 'package:flutter/material.dart';
import 'package:get_it/get_it.dart';
import 'package:playground_components/playground_components.dart';

import '../cache/unit_progress.dart';

class AuthNotifier extends ChangeNotifier {
AuthNotifier() {
Expand All @@ -36,10 +40,25 @@ class AuthNotifier extends ChangeNotifier {
}

Future<void> logIn(AuthProvider authProvider) async {
await FirebaseAuth.instance.signInWithPopup(authProvider);
try {
await FirebaseAuth.instance.signInWithPopup(authProvider);
} on Exception catch (e) {
PlaygroundComponents.toastNotifier.addException(e);
}
}

Future<void> logOut() async {
await FirebaseAuth.instance.signOut();
}

Future<void> deleteAccount() async {
try {
// If there are more things to do before account deletion,
// add final _accountDeletionListeners = <AccountDeletionListener>[].
await GetIt.instance.get<UnitProgressCache>().deleteUserProgress();
await FirebaseAuth.instance.currentUser?.delete();
} on Exception catch (e) {
PlaygroundComponents.toastNotifier.addException(e);
}
}
}
10 changes: 6 additions & 4 deletions learning/tour-of-beam/frontend/lib/cache/content_tree.dart
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@

import 'dart:async';

import 'package:playground_components/playground_components.dart';

import '../models/content_tree.dart';
import 'cache.dart';

Expand All @@ -29,12 +31,12 @@ class ContentTreeCache extends Cache {
final _treesBySdkId = <String, ContentTreeModel>{};
final _futuresBySdkId = <String, Future<ContentTreeModel>>{};

ContentTreeModel? getContentTree(String sdkId) {
if (!_futuresBySdkId.containsKey(sdkId)) {
unawaited(_loadContentTree(sdkId));
ContentTreeModel? getContentTree(Sdk sdk) {
if (!_futuresBySdkId.containsKey(sdk.id)) {
unawaited(_loadContentTree(sdk.id));
}

return _treesBySdkId[sdkId];
return _treesBySdkId[sdk.id];
}

Future<ContentTreeModel> _loadContentTree(String sdkId) async {
Expand Down
9 changes: 6 additions & 3 deletions learning/tour-of-beam/frontend/lib/cache/unit_content.dart
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,16 @@ class UnitContentCache extends Cache {
final _unitContents = <String, Map<String, UnitContentModel>>{};
final _futures = <String, Map<String, Future<UnitContentModel>>>{};

UnitContentModel? getUnitContent(String sdkId, String unitId) {
Future<UnitContentModel> getUnitContent(
String sdkId,
String unitId,
) async {
final future = _futures[sdkId]?[unitId];
if (future == null) {
unawaited(_loadUnitContent(sdkId, unitId));
await _loadUnitContent(sdkId, unitId);
}

return _unitContents[sdkId]?[unitId];
return _unitContents[sdkId]![unitId]!;
}

Future<UnitContentModel> _loadUnitContent(String sdkId, String unitId) async {
Expand Down
141 changes: 110 additions & 31 deletions learning/tour-of-beam/frontend/lib/cache/unit_progress.dart
Original file line number Diff line number Diff line change
Expand Up @@ -18,23 +18,92 @@

import 'dart:async';

import 'package:flutter/foundation.dart';
import 'package:get_it/get_it.dart';
import 'package:playground_components/playground_components.dart';

import '../auth/notifier.dart';
import '../enums/snippet_type.dart';
import '../enums/unit_completion.dart';
import '../models/unit_progress.dart';
import '../repositories/client/client.dart';
import '../repositories/models/get_user_progress_response.dart';
import '../repositories/user_progress/abstract.dart';
import '../repositories/user_progress/cloud.dart';
import '../repositories/user_progress/hive.dart';
import '../state.dart';
import 'cache.dart';

class UnitProgressCache extends Cache {
UnitProgressCache({required super.client});
class UnitProgressCache extends ChangeNotifier {
final _cloudUserProgressRepository = CloudUserProgressRepository(
client: GetIt.instance.get<TobClient>(),
);
final _localStorageUserProgressRepository = HiveUserProgressRepository();

AbstractUserProgressRepository _getUserProgressRepository() {
if (isAuthenticated) {
return _cloudUserProgressRepository;
}
return _localStorageUserProgressRepository;
}

Future<GetUserProgressResponse?>? _future;

var _unitProgress = <UnitProgressModel>[];
final _unitProgressByUnitId = <String, UnitProgressModel>{};

final _completedUnitIds = <String>{};
final _updatingUnitIds = <String>{};
Future<GetUserProgressResponse?>? _future;

bool get isAuthenticated =>
GetIt.instance.get<AuthNotifier>().isAuthenticated;

Future<void> loadUnitProgress(Sdk sdk) async {
_future = _getUserProgressRepository().getUserProgress(sdk);
final result = await _future;

_unitProgressByUnitId.clear();
if (result != null) {
_unitProgress = result.units;
for (final unitProgress in _unitProgress) {
_unitProgressByUnitId[unitProgress.id] = unitProgress;
}
} else {
_unitProgress = [];
}
notifyListeners();
}

List<UnitProgressModel> _getUnitProgress() {
if (_future == null) {
unawaited(loadUnitProgress(GetIt.instance.get<AppNotifier>().sdk!));
}
return _unitProgress;
}

// Completion

Future<void> completeUnit(String sdkId, String unitId) async {
try {
addUpdatingUnitId(unitId);
await _getUserProgressRepository().completeUnit(sdkId, unitId);
} finally {
await loadUnitProgress(GetIt.instance.get<AppNotifier>().sdk!);
clearUpdatingUnitId(unitId);
}
}

Set<String> getUpdatingUnitIds() => _updatingUnitIds;

Set<String> getCompletedUnits() {
_completedUnitIds.clear();
for (final unitProgress in _getUnitProgress()) {
if (unitProgress.isCompleted) {
_completedUnitIds.add(unitProgress.id);
}
}
return _completedUnitIds;
}

void addUpdatingUnitId(String unitId) {
_updatingUnitIds.add(unitId);
notifyListeners();
Expand All @@ -52,6 +121,14 @@ class UnitProgressCache extends Cache {
return _getUnitCompletion(unitId) == UnitCompletion.uncompleted;
}

bool isUnitCompleted(String? unitId) {
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) {
Expand All @@ -66,38 +143,40 @@ class UnitProgressCache extends Cache {
return UnitCompletion.uncompleted;
}

bool isUnitCompleted(String? unitId) {
return getCompletedUnits().contains(unitId);
}
// Snippet

Future<void> updateCompletedUnits() async {
final sdkId = GetIt.instance.get<AppNotifier>().sdkId;
if (sdkId != null) {
await _loadCompletedUnits(sdkId);
}
bool hasSavedSnippet(String? unitId) {
return _unitProgressByUnitId[unitId]?.userSnippetId != null;
}

Set<String> getCompletedUnits() {
if (_future == null) {
unawaited(updateCompletedUnits());
}

return _completedUnitIds;
Future<void> saveSnippet({
required Sdk sdk,
required List<SnippetFile> snippetFiles,
required SnippetType snippetType,
required String unitId,
}) async {
await _getUserProgressRepository().saveUnitSnippet(
sdk: sdk,
snippetFiles: snippetFiles,
snippetType: snippetType,
unitId: unitId,
);
}

Future<void> _loadCompletedUnits(String sdkId) async {
_future = client.getUserProgress(sdkId);
final result = await _future;

_completedUnitIds.clear();
if (result != null) {
for (final unitProgress in result.units) {
if (unitProgress.isCompleted) {
_completedUnitIds.add(unitProgress.id);
}
}
}
Future<ExampleLoadingDescriptor> getSavedDescriptor({
required Sdk sdk,
required String unitId,
}) async {
return _getUserProgressRepository().getSavedDescriptor(
sdk: sdk,
unitId: unitId,
);
}

notifyListeners();
Future<void> deleteUserProgress() async {
await Future.wait([
_localStorageUserProgressRepository.deleteUserProgress(),
_cloudUserProgressRepository.deleteUserProgress(),
]);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,16 +18,17 @@

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/content_tree.dart';

class ContentTreeBuilder extends StatelessWidget {
final String sdkId;
final Sdk sdk;
final ValueWidgetBuilder<ContentTreeModel?> builder;

const ContentTreeBuilder({
required this.sdkId,
required this.sdk,
required this.builder,
});

Expand All @@ -39,7 +40,7 @@ class ContentTreeBuilder extends StatelessWidget {
animation: contentTreeCache,
builder: (context, child) => builder(
context,
contentTreeCache.getContentTree(sdkId),
contentTreeCache.getContentTree(sdk),
child,
),
);
Expand Down
Loading