Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/develop' into release/1.50
Browse files Browse the repository at this point in the history
  • Loading branch information
ivan-magda committed Feb 7, 2024
2 parents 3531f32 + 4751fb2 commit cb9c9a4
Show file tree
Hide file tree
Showing 32 changed files with 512 additions and 40 deletions.
16 changes: 16 additions & 0 deletions iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -426,6 +426,8 @@
2CCCA3A12862E62F00D98089 /* StepQuizStringViewData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CCCA3A02862E62F00D98089 /* StepQuizStringViewData.swift */; };
2CCF3B5828004FC40075D12C /* UserAgentBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CCF3B5728004FC40075D12C /* UserAgentBuilder.swift */; };
2CCF3B5A280050890075D12C /* DeviceInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CCF3B59280050890075D12C /* DeviceInfo.swift */; };
2CD20ED12B73475400FB5269 /* ApplicationShortcutsService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CD20ED02B73475400FB5269 /* ApplicationShortcutsService.swift */; };
2CD20ED42B73484200FB5269 /* ApplicationShortcutIdentifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CD20ED32B73484200FB5269 /* ApplicationShortcutIdentifier.swift */; };
2CD316C028A3B2040002B2B2 /* ApplicationTheme+SharedTheme.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CD316BF28A3B2040002B2B2 /* ApplicationTheme+SharedTheme.swift */; };
2CD3652528796C4300D61855 /* ProfileViewDataMapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CD3652428796C4300D61855 /* ProfileViewDataMapper.swift */; };
2CD3652828797D3600D61855 /* Formatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CD3652728797D3600D61855 /* Formatter.swift */; };
Expand Down Expand Up @@ -1109,6 +1111,8 @@
2CCCA3A02862E62F00D98089 /* StepQuizStringViewData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StepQuizStringViewData.swift; sourceTree = "<group>"; };
2CCF3B5728004FC40075D12C /* UserAgentBuilder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserAgentBuilder.swift; sourceTree = "<group>"; };
2CCF3B59280050890075D12C /* DeviceInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceInfo.swift; sourceTree = "<group>"; };
2CD20ED02B73475400FB5269 /* ApplicationShortcutsService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ApplicationShortcutsService.swift; sourceTree = "<group>"; };
2CD20ED32B73484200FB5269 /* ApplicationShortcutIdentifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ApplicationShortcutIdentifier.swift; sourceTree = "<group>"; };
2CD316BF28A3B2040002B2B2 /* ApplicationTheme+SharedTheme.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ApplicationTheme+SharedTheme.swift"; sourceTree = "<group>"; };
2CD3652428796C4300D61855 /* ProfileViewDataMapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileViewDataMapper.swift; sourceTree = "<group>"; };
2CD3652728797D3600D61855 /* Formatter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Formatter.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -1772,6 +1776,7 @@
2C1F5880280D5B8200372A37 /* Services */ = {
isa = PBXGroup;
children = (
2CD20ED22B73481800FB5269 /* ApplicationShortcuts */,
2C336D112865C46400C91342 /* ApplicationTheme */,
2C1F5883280D5BE600372A37 /* Auth */,
);
Expand Down Expand Up @@ -3246,6 +3251,15 @@
path = Modals;
sourceTree = "<group>";
};
2CD20ED22B73481800FB5269 /* ApplicationShortcuts */ = {
isa = PBXGroup;
children = (
2CD20ED32B73484200FB5269 /* ApplicationShortcutIdentifier.swift */,
2CD20ED02B73475400FB5269 /* ApplicationShortcutsService.swift */,
);
path = ApplicationShortcuts;
sourceTree = "<group>";
};
2CD3652328796C3000D61855 /* ViewData */ = {
isa = PBXGroup;
children = (
Expand Down Expand Up @@ -4492,6 +4506,7 @@
2C5CA2412A20242E00DBF2F9 /* ProjectSelectionDetailsContentView.swift in Sources */,
2C5CA23E2A2022CB00DBF2F9 /* ProjectSelectionDetailsProviderView.swift in Sources */,
2CFD32442AAEFC4D00B9B6EA /* IosFCMTokenProviderImpl.swift in Sources */,
2CD20ED12B73475400FB5269 /* ApplicationShortcutsService.swift in Sources */,
2C5F4A5A2971C71200677530 /* GamificationToolbarContent.swift in Sources */,
2C5B2A1F286595AF0097B270 /* CodeCompletionTableViewController.swift in Sources */,
2C8E4FB12848C9050011ADFA /* StepQuizTableSelectColumnsView.swift in Sources */,
Expand Down Expand Up @@ -4907,6 +4922,7 @@
2C82BA322844B01D004C9013 /* PlaceholderView+Configurations.swift in Sources */,
2C25BFD52851F8F00036C689 /* UIColor+DesignSystem.swift in Sources */,
2C023C8D285DCA4300D2D5A9 /* DatasetExtensions.swift in Sources */,
2CD20ED42B73484200FB5269 /* ApplicationShortcutIdentifier.swift in Sources */,
2C186ADF2B46A0A300DADB26 /* InterviewPreparationCompletedModalView.swift in Sources */,
E91017152832975C002E70F5 /* CheckboxButton.swift in Sources */,
2C99B1002A14255F0018627B /* StudyPlanWidgetViewStateSectionItemStateWrapper.swift in Sources */,
Expand Down
20 changes: 20 additions & 0 deletions iosHyperskillApp/iosHyperskillApp/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -69,5 +69,25 @@
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationPortraitUpsideDown</string>
</array>
<key>UIApplicationShortcutItems</key>
<array>
<dict>
<key>UIApplicationShortcutItemType</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER).SendFeedback</string>
<key>UIApplicationShortcutItemTitle</key>
<string>We Value Your Feedback</string>
<key>UIApplicationShortcutItemSubtitle</key>
<string>Help us enhance your experience</string>
<key>UIApplicationShortcutItemIconSymbolName</key>
<string>lightbulb.circle.fill</string>
<key>UIApplicationShortcutItemIconType</key>
<string>UIApplicationShortcutIconTypeLove</string>
<key>UIApplicationShortcutItemUserInfo</key>
<dict>
<key>version</key>
<string>1</string>
</dict>
</dict>
</array>
</dict>
</plist>
18 changes: 18 additions & 0 deletions iosHyperskillApp/iosHyperskillApp/Sources/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
private lazy var notificationPermissionStatusSettingsObserver = NotificationPermissionStatusSettingsObserver.default
private lazy var notificationsRegistrationService = NotificationsRegistrationService.shared

private lazy var applicationShortcutsService: ApplicationShortcutsServiceProtocol = ApplicationShortcutsService()

// MARK: Initializing the App

func application(
Expand Down Expand Up @@ -48,6 +50,12 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
userNotificationsCenterDelegate.attachNotificationDelegate()
notificationPermissionStatusSettingsObserver.startObserving()

// If app launched using a quick action, perform the requested quick action and return a value of false
// to prevent call the application:performActionForShortcutItem:completionHandler: method.
if applicationShortcutsService.handleLaunchOptions(launchOptions) {
return false
}

return true
}

Expand Down Expand Up @@ -89,6 +97,16 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
notificationsService.handleLocalNotification(with: notification.userInfo)
}

// MARK: Continuing User Activity and Handling Quick Actions

func application(
_ application: UIApplication,
performActionFor shortcutItem: UIApplicationShortcutItem,
completionHandler: @escaping (Bool) -> Void
) {
completionHandler(applicationShortcutsService.handleShortcutItem(shortcutItem))
}

// MARK: Opening a URL-Specified Resource

func application(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import UIKit
final class SendEmailFeedbackController: NSObject {
private weak var presentationController: UIViewController?

var onDidFinish: (() -> Void)?

func sendFeedback(feedbackEmailData: FeedbackEmailData, presentationController: UIViewController) {
self.presentationController = presentationController

Expand Down Expand Up @@ -67,5 +69,7 @@ extension SendEmailFeedbackController: MFMailComposeViewControllerDelegate {
}
)
}

onDidFinish?()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import Foundation

enum ApplicationShortcutIdentifier: String {
case sendFeedback = "SendFeedback"

init?(fullIdentifier: String) {
guard let shortIdentifier = fullIdentifier.components(separatedBy: ".").last else {
return nil
}
self.init(rawValue: shortIdentifier)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import shared
import UIKit

protocol ApplicationShortcutsServiceProtocol: AnyObject {
func handleShortcutItem(_ shortcutItem: UIApplicationShortcutItem) -> Bool
func handleLaunchOptions(_ launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool
}

final class ApplicationShortcutsService: ApplicationShortcutsServiceProtocol {
private lazy var applicationShortcutsInteractor: ApplicationShortcutsInteractor =
AppGraphBridge.sharedAppGraph.buildApplicationShortcutsDataComponent().applicationShortcutsInteractor

private lazy var analyticInteractor = AnalyticInteractor.default

private var sendEmailFeedbackController: SendEmailFeedbackController?

// MARK: Protocol Conforming

func handleLaunchOptions(_ launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
if let shortcutItem = launchOptions?[.shortcutItem] as? UIApplicationShortcutItem {
_ = handleShortcutItem(shortcutItem)
return true
}
return false
}

func handleShortcutItem(_ shortcutItem: UIApplicationShortcutItem) -> Bool {
let shortcutType = shortcutItem.type

analyticInteractor.logEvent(
event: ApplicationShortcutItemClickedHyperskillAnalyticEvent(shortcutItemIdentifier: shortcutType)
)

guard let shortcutIdentifier = ApplicationShortcutIdentifier(fullIdentifier: shortcutType) else {
#if DEBUG
print("ApplicationShortcutsService: Did receive unknown shortcut identifier: \(shortcutType)")
#endif
return false
}

DispatchQueue.main.async {
self.performAction(for: shortcutIdentifier)
}

return true
}

// MARK: Private API

private func performAction(for shortcutIdentifier: ApplicationShortcutIdentifier) {
switch shortcutIdentifier {
case .sendFeedback:
performSendFeedback()
}
}

private func performSendFeedback() {
applicationShortcutsInteractor.getSendFeedbackEmailData { [weak self] feedbackEmailData, error in
if let error {
#if DEBUG
print("ApplicationShortcutsService: SendFeedback, failed get email data: \(error)")
#endif
return
}

guard let feedbackEmailData else {
#if DEBUG
print("ApplicationShortcutsService: SendFeedback, no email data")
#endif
return
}

assert(Thread.current.isMainThread)

guard let currentPresentedViewController = SourcelessRouter().currentPresentedViewController() else {
#if DEBUG
print("ApplicationShortcutsService: SendFeedback, no current presented view controller")
#endif
return
}

let sendEmailFeedbackController = SendEmailFeedbackController()
sendEmailFeedbackController.onDidFinish = { [weak self] in
self?.sendEmailFeedbackController = nil
}
self?.sendEmailFeedbackController = sendEmailFeedbackController

sendEmailFeedbackController.sendFeedback(
feedbackEmailData: feedbackEmailData,
presentationController: currentPresentedViewController
)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -146,4 +146,19 @@ sealed class HyperskillAnalyticRoute {
override val path: String =
"/search"
}

/**
* Represents a special route that is used to track the first time the app is launched (ALTAPPS-1139).
*/
internal class AppLaunchFirstTime : HyperskillAnalyticRoute() {
override val path: String = "app-launch-first-time"
}

/**
* Springboard, or Home Screen is the standard application that manages the home screen of Apple devices.
*/
class IosSpringBoard : HyperskillAnalyticRoute() {
override val path: String =
"SpringBoard"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -110,5 +110,6 @@ enum class HyperskillAnalyticTarget(val targetName: String) {
DAILY_STUDY_REMINDERS_HOUR_INTERVAL_PICKER_MODAL("daily_study_reminders_hour_interval_picker_modal"),
CONFIRM("confirm"),
GO_TO_FIRST_PROBLEM("go_to_first_problem"),
INTERVIEW_PREPARATION_COMPLETED_MODAL("interview_preparation_completed_modal")
INTERVIEW_PREPARATION_COMPLETED_MODAL("interview_preparation_completed_modal"),
HOME_SCREEN_QUICK_ACTION("home_screen_quick_action")
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package org.hyperskill.app.main.cache

import com.russhwolf.settings.Settings
import org.hyperskill.app.main.data.source.AppCacheDataSource

internal class AppCacheDataSourceImpl(
private val settings: Settings
) : AppCacheDataSource {
override fun isAppDidLaunchFirstTime(): Boolean =
settings.getBoolean(AppCacheKeyValues.APP_DID_LAUNCH_FIRST_TIME, defaultValue = false)

override fun setAppDidLaunchFirstTime() {
settings.putBoolean(AppCacheKeyValues.APP_DID_LAUNCH_FIRST_TIME, true)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package org.hyperskill.app.main.cache

internal object AppCacheKeyValues {
const val APP_DID_LAUNCH_FIRST_TIME = "app_did_launch_first_time"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package org.hyperskill.app.main.data.repository

import org.hyperskill.app.main.data.source.AppCacheDataSource
import org.hyperskill.app.main.domain.repository.AppRepository

internal class AppRepositoryImpl(
private val appCacheDataSource: AppCacheDataSource
) : AppRepository {
override fun isAppDidLaunchFirstTime(): Boolean =
appCacheDataSource.isAppDidLaunchFirstTime()

override fun setAppDidLaunchFirstTime() {
appCacheDataSource.setAppDidLaunchFirstTime()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package org.hyperskill.app.main.data.source

interface AppCacheDataSource {
fun isAppDidLaunchFirstTime(): Boolean
fun setAppDidLaunchFirstTime()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package org.hyperskill.app.main.domain.analytic

import org.hyperskill.app.analytic.domain.model.hyperskill.HyperskillAnalyticAction
import org.hyperskill.app.analytic.domain.model.hyperskill.HyperskillAnalyticEvent
import org.hyperskill.app.analytic.domain.model.hyperskill.HyperskillAnalyticRoute

/**
* Represents first time app launch analytic event.
*
* JSON payload:
* ```
* {
* "route": "app-launch-first-time",
* "action": "view"
* }
* ```
*
* @see HyperskillAnalyticEvent
*/
object AppLaunchFirstTimeHyperskillAnalyticEvent : HyperskillAnalyticEvent(
route = HyperskillAnalyticRoute.AppLaunchFirstTime(),
action = HyperskillAnalyticAction.VIEW
)
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ package org.hyperskill.app.main.domain.interactor

import org.hyperskill.app.analytic.domain.interactor.AnalyticInteractor
import org.hyperskill.app.auth.domain.interactor.AuthInteractor
import org.hyperskill.app.main.domain.analytic.AppLaunchFirstTimeHyperskillAnalyticEvent
import org.hyperskill.app.main.domain.repository.AppRepository
import org.hyperskill.app.notification.remote.domain.interactor.PushNotificationsInteractor
import org.hyperskill.app.profile.domain.repository.CurrentProfileStateRepository
import org.hyperskill.app.progresses.domain.repository.ProgressesRepository
Expand All @@ -12,6 +14,7 @@ import org.hyperskill.app.track.domain.repository.TrackRepository
import org.hyperskill.app.user_storage.domain.interactor.UserStorageInteractor

class AppInteractor(
private val appRepository: AppRepository,
private val authInteractor: AuthInteractor,
private val currentProfileStateRepository: CurrentProfileStateRepository,
private val userStorageInteractor: UserStorageInteractor,
Expand Down Expand Up @@ -40,4 +43,11 @@ class AppInteractor(
projectsRepository.clearCache()
shareStreakRepository.clearCache()
}

suspend fun logAppLaunchFirstTimeAnalyticEventIfNeeded() {
if (!appRepository.isAppDidLaunchFirstTime()) {
appRepository.setAppDidLaunchFirstTime()
analyticInteractor.logEvent(AppLaunchFirstTimeHyperskillAnalyticEvent)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package org.hyperskill.app.main.domain.repository

interface AppRepository {
fun isAppDidLaunchFirstTime(): Boolean
fun setAppDidLaunchFirstTime()
}
Loading

0 comments on commit cb9c9a4

Please sign in to comment.