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

Merged
merged 23 commits into from
Dec 5, 2024
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
931d72c
show resume lesson modal
rachaelblue Oct 31, 2024
7b70dce
present resume lesson modal from lessonViewModel instead
rachaelblue Nov 7, 2024
757c417
add blurred background view
rachaelblue Nov 13, 2024
2a0091d
adjust modal spacing
rachaelblue Nov 13, 2024
7ca5933
add use case and view model to get interface strings
rachaelblue Nov 14, 2024
c631a10
center subtitle
rachaelblue Nov 15, 2024
66a9264
Merge branch 'develop' of https://github.com/CruGlobal/godtools-swift…
rachaelblue Nov 15, 2024
8649ed9
remove unused initialPageConfig param
rachaelblue Nov 15, 2024
d16ac6d
remove unused bool
rachaelblue Nov 15, 2024
7268346
clean up
rachaelblue Nov 15, 2024
582f5f9
Merge branch 'develop' of https://github.com/CruGlobal/godtools-swift…
rachaelblue Nov 19, 2024
2de0a4f
present modal from flow and remove closures
rachaelblue Nov 20, 2024
04ed3d3
change font size
rachaelblue Nov 20, 2024
46ac16a
present lesson view after appears
rachaelblue Nov 25, 2024
0b1dda9
actually commit changes
rachaelblue Nov 25, 2024
7f367ba
bump min version to iOS15
rachaelblue Nov 25, 2024
b7cec39
Update pod to minimum iOS 15
levieggertcru Nov 25, 2024
7d1c74b
Remove iOS 14 obsoleted files
levieggertcru Nov 25, 2024
8ef7cdb
Add method getLessonView
levieggertcru Nov 25, 2024
00f9ae7
Add computed properties
levieggertcru Nov 26, 2024
67d9948
Merge pull request #2357 from CruGlobal/GT-2465-create-extension-for-…
levieggertcru Dec 4, 2024
5ec0e70
Update tests target for iOS 15
levieggertcru Dec 4, 2024
f58b7b4
Set ui tests min deployment to ios 15
levieggertcru Dec 5, 2024
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 @@ -22,9 +22,16 @@ class LessonViewModel: MobileContentPagesViewModel {
self.flowDelegate = flowDelegate
self.storeLessonProgressUseCase = storeLessonProgressUseCase

let initialPageConfig = MobileContentPagesInitialPageConfig(shouldNavigateToStartPageIfLastPage: true, shouldNavigateToPreviousVisiblePageIfHiddenPage: true)
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(restartAtBeginning: false, 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(restartAtBeginning: Bool, 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
3 changes: 3 additions & 0 deletions godtools/App/Flows/Flow/FlowStep.swift
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,9 @@ enum FlowStep {
case backTappedFromLessonLanguageFilter

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

Expand Down
35 changes: 34 additions & 1 deletion godtools/App/Flows/Lesson/LessonFlow.swift
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ class LessonFlow: ToolNavigationFlow, Flow {
renderer: renderer,
resource: renderer.resource,
primaryLanguage: renderer.languages.primaryLanguage,
initialPage: initialPageOrPreviousProgress,
initialPage: initialPageOrPreviousProgress,
resourcesRepository: appDiContainer.dataLayer.getResourcesRepository(),
translationsRepository: appDiContainer.dataLayer.getTranslationsRepository(),
mobileContentEventAnalytics: appDiContainer.getMobileContentRendererEventAnalyticsTracking(),
Expand Down 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
Loading
Loading