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

GT-2465 resume lesson modal #2352

Open
wants to merge 16 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 10 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ SPEC REPOS:
- GodToolsShared

SPEC CHECKSUMS:
GodToolsShared: 404a619e53dc1890091397ac4e1da69afb5eabcd
GodToolsShared: 0b717d26f71b2eb7748b6999ad3d2d6fe7a8c401

PODFILE CHECKSUM: 1816453b60fe876696e3cc9b1da49602bb07e16b
PODFILE CHECKSUM: 4afa67206a45df71c21cb5eb808888f3037d2463

COCOAPODS: 1.15.2
169 changes: 78 additions & 91 deletions godtools.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,13 @@ class LessonViewModel: MobileContentPagesViewModel {
let initialPageConfig = MobileContentPagesInitialPageConfig(shouldNavigateToStartPageIfLastPage: true, shouldNavigateToPreviousVisiblePageIfHiddenPage: true)

super.init(renderer: renderer, initialPage: initialPage, initialPageConfig: initialPageConfig, resourcesRepository: resourcesRepository, translationsRepository: translationsRepository, mobileContentEventAnalytics: mobileContentEventAnalytics, getCurrentAppLanguageUseCase: getCurrentAppLanguageUseCase, getTranslatedLanguageName: getTranslatedLanguageName, initialPageRenderingType: .visiblePages, trainingTipsEnabled: trainingTipsEnabled, incrementUserCounterUseCase: incrementUserCounterUseCase, selectedLanguageIndex: nil)

if let initialPage = initialPage, isFirstOrLastVisiblePage(page: initialPage) == false {

flowDelegate.navigate(step: .presentResumeLessonModal {
self.navigateToFirstPage(animated: true)
})
}
}

deinit {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
//
// GetResumeLessonProgressModalInterfaceStringsRepository.swift
// godtools
//
// Created by Rachael Skeath on 11/14/24.
// Copyright © 2024 Cru. All rights reserved.
//

import Foundation
import Combine
import LocalizationServices

class GetResumeLessonProgressModalInterfaceStringsRepository: GetResumeLessonProgressModalInterfaceStringsRepositoryInterface {

private let localizationServices: LocalizationServices

init(localizationServices: LocalizationServices) {
self.localizationServices = localizationServices
}

func getStringsPublisher(translateInLanguage: AppLanguageDomainModel) -> AnyPublisher<ResumeLessonProgressModalInterfaceStringsDomainModel, Never> {

let localeId: String = translateInLanguage.localeId

let interfaceStrings = ResumeLessonProgressModalInterfaceStringsDomainModel(
title: localizationServices.stringForLocaleElseEnglish(localeIdentifier: localeId, key: "lessons.resumeLessonModal.title"),
subtitle: localizationServices.stringForLocaleElseEnglish(localeIdentifier: localeId, key: "lessons.resumeLessonModal.subtitle"),
startOverButtonText: localizationServices.stringForLocaleElseEnglish(localeIdentifier: localeId, key: "lessons.resumeLessonModal.startOverButton"),
continueButtonText: localizationServices.stringForLocaleElseEnglish(localeIdentifier: localeId, key: "lessons.resumeLessonModal.continueButton")
)

return Just(interfaceStrings)
.eraseToAnyPublisher()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,12 @@ class UserLessonProgressDataLayerDependencies {

// MARK: - Domain Interface

func getResumeLessonProgressModalInterfaceStringsRepositoryInterface() -> GetResumeLessonProgressModalInterfaceStringsRepositoryInterface {
return GetResumeLessonProgressModalInterfaceStringsRepository(
localizationServices: coreDataLayer.getLocalizationServices()
)
}

func getStoreUserLessonProgressRepositoryInterface() -> StoreUserLessonProgressRepositoryInterface {
return StoreUserLessonProgressRepository(
lessonProgressRepository: coreDataLayer.getUserLessonProgressRepository()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,12 @@ class UserLessonProgressDomainLayerDependencies {
self.coreDataLayer = coreDataLayer
}

func getResumeLessonProgressModalInterfaceStringsUseCase() -> GetResumeLessonProgressModalInterfaceStringsUseCase {
return GetResumeLessonProgressModalInterfaceStringsUseCase(
getResumeLessonModalInterfaceStringsRepo: dataLayer.getResumeLessonProgressModalInterfaceStringsRepositoryInterface()
)
}

func getStoreUserLessonProgressUseCase() -> StoreUserLessonProgressUseCase {
return StoreUserLessonProgressUseCase(
storeLessonProgressRepository: dataLayer.getStoreUserLessonProgressRepositoryInterface()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
//
// ResumeLessonProgressModalInterfaceStringsDomainModel.swift
// godtools
//
// Created by Rachael Skeath on 11/14/24.
// Copyright © 2024 Cru. All rights reserved.
//

import Foundation

struct ResumeLessonProgressModalInterfaceStringsDomainModel {
let title: String
let subtitle: String
let startOverButtonText: String
let continueButtonText: String

static func emptyStrings() -> ResumeLessonProgressModalInterfaceStringsDomainModel {
return ResumeLessonProgressModalInterfaceStringsDomainModel(title: "", subtitle: "", startOverButtonText: "", continueButtonText: "")
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
//
// GetResumeLessonProgressModalInterfaceStringsRepositoryInterface.swift
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey @levieggertcru, how do you feel about the naming convention making some names super long? Was debating to take out the word "progress", but still gonna be pretty long either way 🤔

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey @rachaelblue I know some names get super long. I know we suffix a lot of key names such as UseCase, DomainModel, RepositoryInterface, etc.

And some names get descriptive based on feature so there isn't collisions with other features. (If we could namespace that would help here).

I think we could start omitting Repository from the interface naming.

Maybe GetResumeLessonInterfaceStringsInterface or even GetResumeLessonStringsInterface. A tiny bit shorter.

// godtools
//
// Created by Rachael Skeath on 11/14/24.
// Copyright © 2024 Cru. All rights reserved.
//

import Foundation
import Combine

protocol GetResumeLessonProgressModalInterfaceStringsRepositoryInterface {

func getStringsPublisher(translateInLanguage: AppLanguageDomainModel) -> AnyPublisher<ResumeLessonProgressModalInterfaceStringsDomainModel, Never>
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
//
// GetResumeLessonProgressModalInterfaceStringsUseCase.swift
// godtools
//
// Created by Rachael Skeath on 11/14/24.
// Copyright © 2024 Cru. All rights reserved.
//

import Foundation
import Combine

class GetResumeLessonProgressModalInterfaceStringsUseCase {

private let getResumeLessonModalInterfaceStringsRepo: GetResumeLessonProgressModalInterfaceStringsRepositoryInterface

init(getResumeLessonModalInterfaceStringsRepo: GetResumeLessonProgressModalInterfaceStringsRepositoryInterface) {
self.getResumeLessonModalInterfaceStringsRepo = getResumeLessonModalInterfaceStringsRepo
}

func getStringsPublisher(appLanguage: AppLanguageDomainModel) -> AnyPublisher<ResumeLessonProgressModalInterfaceStringsDomainModel, Never> {

return getResumeLessonModalInterfaceStringsRepo
.getStringsPublisher(translateInLanguage: appLanguage)
.eraseToAnyPublisher()
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
//
// ResumeLessonProgressModal.swift
// godtools
//
// Created by Rachael Skeath on 10/29/24.
// Copyright © 2024 Cru. All rights reserved.
//

import SwiftUI

struct ResumeLessonProgressModal: View {

private let buttonHeight: CGFloat = 48
private let modalInset: CGFloat = 28
private let buttonInset: CGFloat = 20
private let buttonSpace: CGFloat = 12

@ObservedObject private var viewModel: ResumeLessonProgressModalViewModel

init(viewModel: ResumeLessonProgressModalViewModel) {
self.viewModel = viewModel
}

var body: some View {
GeometryReader { geometry in
let totalSpaceAroundModal = modalInset * 2
let modalWidth = geometry.size.width - totalSpaceAroundModal
let totalSpaceAroundButtons = (buttonInset * 2) + buttonSpace
let buttonWidth = (modalWidth - totalSpaceAroundButtons) / 2

ZStack {
if #available(iOS 15.0, *) {
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also @levieggertcru, do you think we can bump the minimum deployment target to 15?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With iOS 18 out I feel like we could. Let me check the analytics data and see where user count is at. Would like to confirm with godtools team as well.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We got the go ahead to bump to iOS 15 so feel free to make that change in this PR.

Color.clear
.edgesIgnoringSafeArea(.all)
.background(.ultraThinMaterial)
} else {
VisualEffectView(effect: UIBlurEffect(style: .systemUltraThinMaterial))
.edgesIgnoringSafeArea(.all)
}

VStack(spacing: 0) {
Text(viewModel.interfaceStringsDomainModel.title)
.font(FontLibrary.sfProTextRegular.font(size: 28))
.foregroundColor(ColorPalette.gtGrey.color)
.padding(.top, 30)
.padding(.bottom, 15)

Text(viewModel.interfaceStringsDomainModel.subtitle)
.font(FontLibrary.sfProTextRegular.font(size: 16))
.foregroundColor(ColorPalette.gtGrey.color)
.multilineTextAlignment(.center)
.padding(.horizontal, buttonInset + 20)
.padding(.bottom, 35)

HStack(spacing: buttonSpace) {
GTWhiteButton(title: viewModel.interfaceStringsDomainModel.startOverButtonText, width: buttonWidth, height: buttonHeight) {
levieggertcru marked this conversation as resolved.
Show resolved Hide resolved
viewModel.startOverButtonTapped()
}
GTBlueButton(title: viewModel.interfaceStringsDomainModel.continueButtonText, width: buttonWidth, height: buttonHeight) {
viewModel.continueButtonTapped()
}
}
.padding(.horizontal, buttonInset)
.padding(.bottom, 21)
}
.background(Color.white)
.cornerRadius(6)
.shadow(color: Color.black.opacity(0.25), radius: 3, y: 3)
.frame(width: modalWidth)
.overlay(
RoundedRectangle(cornerRadius: 6)
.strokeBorder(Color.clear, lineWidth: 2)
)
}
}

}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
//
// ResumeLessonProgressModalViewModel.swift
// godtools
//
// Created by Rachael Skeath on 11/14/24.
// Copyright © 2024 Cru. All rights reserved.
//

import Foundation
import Combine

class ResumeLessonProgressModalViewModel: ObservableObject {

private let getInterfaceStringsUseCase: GetResumeLessonProgressModalInterfaceStringsUseCase
private let getCurrentAppLanguageUseCase: GetCurrentAppLanguageUseCase
private let startOverClosure: () -> Void
private let continueClosure: () -> Void

private var cancellables: Set<AnyCancellable> = Set()

@Published private var appLanguage: AppLanguageDomainModel = LanguageCodeDomainModel.english.rawValue

@Published var interfaceStringsDomainModel: ResumeLessonProgressModalInterfaceStringsDomainModel = ResumeLessonProgressModalInterfaceStringsDomainModel.emptyStrings()

init(getInterfaceStringsUseCase: GetResumeLessonProgressModalInterfaceStringsUseCase, getCurrentAppLanguageUseCase: GetCurrentAppLanguageUseCase, startOverClosure: @escaping () -> Void, continueClosure: @escaping () -> Void) {
self.getInterfaceStringsUseCase = getInterfaceStringsUseCase
self.getCurrentAppLanguageUseCase = getCurrentAppLanguageUseCase
self.startOverClosure = startOverClosure
self.continueClosure = continueClosure

getCurrentAppLanguageUseCase
.getLanguagePublisher()
.receive(on: DispatchQueue.main)
.assign(to: &$appLanguage)

$appLanguage
.dropFirst()
.map { appLanguage in
getInterfaceStringsUseCase.getStringsPublisher(appLanguage: appLanguage)
}
.switchToLatest()
.receive(on: DispatchQueue.main)
.sink { [weak self] interfaceStrings in

self?.interfaceStringsDomainModel = interfaceStrings
}
.store(in: &cancellables)
}

// MARK: - Inputs

func startOverButtonTapped() {
startOverClosure()
}

func continueButtonTapped() {
continueClosure()
}
}
18 changes: 11 additions & 7 deletions godtools/App/Flows/App/AppFlow.swift
Original file line number Diff line number Diff line change
Expand Up @@ -203,13 +203,8 @@ class AppFlow: NSObject, ToolNavigationFlow, Flow {
}

case .lessonTappedFromLessonsList(let lessonListItem, let languageFilter):

if let languageFilter = languageFilter {
navigateToTool(toolDataModelId: lessonListItem.dataModelId, languageIds: [languageFilter.languageId], selectedLanguageIndex: 0, trainingTipsEnabled: false)
} else {
navigateToToolInAppLanguage(toolDataModelId: lessonListItem.dataModelId, trainingTipsEnabled: false)
}

navigateToLesson(lessonListItem: lessonListItem, languageFilter: languageFilter)

case .lessonLanguageFilterTappedFromLessons:
navigationController.pushViewController(getLessonLanguageFilterSelection(), animated: true)

Expand Down Expand Up @@ -809,6 +804,15 @@ extension AppFlow {

navigateToTool(toolDataModelId: toolDataModelId, languageIds: languageIds, selectedLanguageIndex: selectedLanguageIndex, trainingTipsEnabled: trainingTipsEnabled, shouldPersistToolSettings: shouldPersistToolSettings)
}

private func navigateToLesson(lessonListItem: LessonListItemDomainModel, languageFilter: LessonFilterLanguageDomainModel?) {

if let languageFilter = languageFilter {
navigateToTool(toolDataModelId: lessonListItem.dataModelId, languageIds: [languageFilter.languageId], selectedLanguageIndex: 0, trainingTipsEnabled: false)
} else {
navigateToToolInAppLanguage(toolDataModelId: lessonListItem.dataModelId, trainingTipsEnabled: false)
}
}

private func navigateToTool(toolDataModelId: String, languageIds: [String], selectedLanguageIndex: Int?, trainingTipsEnabled: Bool, shouldPersistToolSettings: Bool = false) {

Expand Down
1 change: 1 addition & 0 deletions godtools/App/Flows/Flow/FlowStep.swift
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ enum FlowStep {
case backTappedFromLessonLanguageFilter

// lesson
case presentResumeLessonModal(startOverClosure: () -> Void)
case closeTappedFromLesson(lessonId: String, highestPageNumberViewed: Int)
case lessonFlowCompleted(state: LessonFlowCompletedState)

Expand Down
33 changes: 33 additions & 0 deletions godtools/App/Flows/Lesson/LessonFlow.swift
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,17 @@ class LessonFlow: ToolNavigationFlow, Flow {
case .deepLink( _):
break

case .presentResumeLessonModal(let startOverClosure):
let resumeLessonModal = getResumeLessonModal(startOverClosure: {
startOverClosure()
self.navigationController.dismissPresented(animated: true, completion: nil)

}, continueClosure: {
self.navigationController.dismissPresented(animated: true, completion: nil)
})

navigationController.present(resumeLessonModal, animated: true)

case .closeTappedFromLesson(let lessonId, let highestPageNumberViewed):
closeTool(lessonId: lessonId, highestPageNumberViewed: highestPageNumberViewed)

Expand Down Expand Up @@ -135,6 +146,28 @@ class LessonFlow: ToolNavigationFlow, Flow {
}
}

private func getResumeLessonModal(startOverClosure: @escaping () -> Void, continueClosure: @escaping () -> Void) -> UIViewController {
let viewModel = ResumeLessonProgressModalViewModel(
getInterfaceStringsUseCase: appDiContainer.feature.lessonProgress.domainLayer.getResumeLessonProgressModalInterfaceStringsUseCase(),
getCurrentAppLanguageUseCase: appDiContainer.feature.appLanguage.domainLayer.getCurrentAppLanguageUseCase(),
startOverClosure: startOverClosure,
continueClosure: continueClosure
)

let resumeLessonModal = ResumeLessonProgressModal(viewModel: viewModel)

let hostingView = AppHostingController<ResumeLessonProgressModal>(
rootView: resumeLessonModal,
navigationBar: nil
)

hostingView.view.backgroundColor = .clear
hostingView.modalPresentationStyle = .overFullScreen
hostingView.modalTransitionStyle = .crossDissolve

return hostingView
}

private func closeTool(lessonId: String, highestPageNumberViewed: Int) {

flowDelegate?.navigate(step: .lessonFlowCompleted(state: .userClosedLesson(lessonId: lessonId, highestPageNumberViewed: highestPageNumberViewed)))
Expand Down
4 changes: 2 additions & 2 deletions godtools/App/Flows/ToolNavigation/ToolNavigationFlow.swift
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ extension ToolNavigationFlow {
liveShareStream: toolDeepLink.liveShareStream,
selectedLanguageIndex: toolDeepLink.selectedLanguageIndex,
trainingTipsEnabled: false,
initialPage: toolDeepLink.mobileContentPage,
initialPage: toolDeepLink.mobileContentPage,
shouldPersistToolSettings: false
)
}
Expand All @@ -56,7 +56,7 @@ extension ToolNavigationFlow {
liveShareStream: liveShareStream,
selectedLanguageIndex: selectedLanguageIndex,
trainingTipsEnabled: trainingTipsEnabled,
initialPage: initialPage,
initialPage: initialPage,
shouldPersistToolSettings: shouldPersistToolSettings
)
}
Expand Down
Loading
Loading