From e9ce1ceb20ddcc774885468a438b8a4fb29e30a3 Mon Sep 17 00:00:00 2001 From: Ivan Magda Date: Fri, 12 Jan 2024 19:55:28 +0700 Subject: [PATCH 001/104] ALTAPPS-1119: Allow deploy from feature/mobile-only-subscriptions branch --- .github/workflows/android_release_deployment.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/android_release_deployment.yml b/.github/workflows/android_release_deployment.yml index ef148ce713..91e76278b2 100644 --- a/.github/workflows/android_release_deployment.yml +++ b/.github/workflows/android_release_deployment.yml @@ -19,7 +19,7 @@ defaults: jobs: # Run Gradle Wrapper Validation Action to verify the wrapper's checksum gradle-wrapper-validation: - if: ${{ github.ref == 'refs/heads/main' }} + if: ${{ github.ref == 'refs/heads/main' || github.ref == 'refs/heads/feature/mobile-only-subscriptions' }} name: Gradle Wrapper Validation runs-on: ubuntu-22.04 steps: From 04abb1983a05b779daff5e09f5205e84118c153d Mon Sep 17 00:00:00 2001 From: Ivan Magda Date: Fri, 12 Jan 2024 20:04:33 +0700 Subject: [PATCH 002/104] ALTAPPS-1119: Temporary disable conditional job execution --- .github/workflows/android_release_deployment.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/android_release_deployment.yml b/.github/workflows/android_release_deployment.yml index 91e76278b2..af8c7fda26 100644 --- a/.github/workflows/android_release_deployment.yml +++ b/.github/workflows/android_release_deployment.yml @@ -19,7 +19,7 @@ defaults: jobs: # Run Gradle Wrapper Validation Action to verify the wrapper's checksum gradle-wrapper-validation: - if: ${{ github.ref == 'refs/heads/main' || github.ref == 'refs/heads/feature/mobile-only-subscriptions' }} + # if: ${{ github.ref == 'refs/heads/main' }} name: Gradle Wrapper Validation runs-on: ubuntu-22.04 steps: From 9ad1c6c5b0007f29fddfc88be1bfb8630609226c Mon Sep 17 00:00:00 2001 From: github-actions Date: Mon, 15 Jan 2024 10:53:20 +0000 Subject: [PATCH 003/104] Set version number to 1.50 --- gradle/app.versions.toml | 2 +- iosHyperskillApp/NotificationServiceExtension/Info.plist | 2 +- iosHyperskillApp/iosHyperskillApp/Info.plist | 2 +- iosHyperskillApp/iosHyperskillAppTests/Info.plist | 2 +- iosHyperskillApp/iosHyperskillAppUITests/Info.plist | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/gradle/app.versions.toml b/gradle/app.versions.toml index 4f93fa703e..79f6f63c57 100644 --- a/gradle/app.versions.toml +++ b/gradle/app.versions.toml @@ -2,5 +2,5 @@ minSdk = '24' targetSdk = '33' compileSdk = '33' -versionName = '1.49' +versionName = '1.50' versionCode = '305' \ No newline at end of file diff --git a/iosHyperskillApp/NotificationServiceExtension/Info.plist b/iosHyperskillApp/NotificationServiceExtension/Info.plist index 928c39fa81..fca1a74d9d 100644 --- a/iosHyperskillApp/NotificationServiceExtension/Info.plist +++ b/iosHyperskillApp/NotificationServiceExtension/Info.plist @@ -11,7 +11,7 @@ CFBundleVersion 306 CFBundleShortVersionString - 1.49 + 1.50 CFBundlePackageType $(PRODUCT_BUNDLE_PACKAGE_TYPE) CFBundleExecutable diff --git a/iosHyperskillApp/iosHyperskillApp/Info.plist b/iosHyperskillApp/iosHyperskillApp/Info.plist index ca65c4eb21..2f503668a1 100644 --- a/iosHyperskillApp/iosHyperskillApp/Info.plist +++ b/iosHyperskillApp/iosHyperskillApp/Info.plist @@ -23,7 +23,7 @@ CFBundlePackageType $(PRODUCT_BUNDLE_PACKAGE_TYPE) CFBundleShortVersionString - 1.49 + 1.50 CFBundleURLTypes diff --git a/iosHyperskillApp/iosHyperskillAppTests/Info.plist b/iosHyperskillApp/iosHyperskillAppTests/Info.plist index 62b66dcb7f..7717f3e6b4 100644 --- a/iosHyperskillApp/iosHyperskillAppTests/Info.plist +++ b/iosHyperskillApp/iosHyperskillAppTests/Info.plist @@ -13,7 +13,7 @@ CFBundlePackageType BNDL CFBundleShortVersionString - 1.49 + 1.50 CFBundleVersion 306 diff --git a/iosHyperskillApp/iosHyperskillAppUITests/Info.plist b/iosHyperskillApp/iosHyperskillAppUITests/Info.plist index 4d6900935f..e893974006 100644 --- a/iosHyperskillApp/iosHyperskillAppUITests/Info.plist +++ b/iosHyperskillApp/iosHyperskillAppUITests/Info.plist @@ -13,7 +13,7 @@ CFBundlePackageType $(PRODUCT_BUNDLE_PACKAGE_TYPE) CFBundleShortVersionString - 1.49 + 1.50 CFBundleVersion 306 From e9fe588fedfba83e8019e95628ea23486d0aad0d Mon Sep 17 00:00:00 2001 From: github-actions Date: Mon, 15 Jan 2024 10:53:24 +0000 Subject: [PATCH 004/104] Bump build number --- gradle/app.versions.toml | 2 +- .../NotificationServiceExtension/Info.plist | 2 +- .../iosHyperskillApp.xcodeproj/project.pbxproj | 16 ++++++++-------- iosHyperskillApp/iosHyperskillApp/Info.plist | 2 +- .../iosHyperskillAppTests/Info.plist | 2 +- .../iosHyperskillAppUITests/Info.plist | 2 +- 6 files changed, 13 insertions(+), 13 deletions(-) diff --git a/gradle/app.versions.toml b/gradle/app.versions.toml index 79f6f63c57..0805a85359 100644 --- a/gradle/app.versions.toml +++ b/gradle/app.versions.toml @@ -3,4 +3,4 @@ minSdk = '24' targetSdk = '33' compileSdk = '33' versionName = '1.50' -versionCode = '305' \ No newline at end of file +versionCode = '306' \ No newline at end of file diff --git a/iosHyperskillApp/NotificationServiceExtension/Info.plist b/iosHyperskillApp/NotificationServiceExtension/Info.plist index fca1a74d9d..bb350abd0b 100644 --- a/iosHyperskillApp/NotificationServiceExtension/Info.plist +++ b/iosHyperskillApp/NotificationServiceExtension/Info.plist @@ -9,7 +9,7 @@ CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleVersion - 306 + 307 CFBundleShortVersionString 1.50 CFBundlePackageType diff --git a/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj b/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj index 80961eddf4..36c91fad0a 100644 --- a/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj +++ b/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj @@ -4985,7 +4985,7 @@ buildSettings = { CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 306; + CURRENT_PROJECT_VERSION = 307; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = 3DWS674B2M; GENERATE_INFOPLIST_FILE = NO; @@ -5006,7 +5006,7 @@ buildSettings = { CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 306; + CURRENT_PROJECT_VERSION = 307; DEVELOPMENT_TEAM = 3DWS674B2M; GENERATE_INFOPLIST_FILE = NO; INFOPLIST_FILE = iosHyperskillAppUITests/Info.plist; @@ -5027,7 +5027,7 @@ BUNDLE_LOADER = "$(TEST_HOST)"; CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 306; + CURRENT_PROJECT_VERSION = 307; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = 3DWS674B2M; INFOPLIST_FILE = iosHyperskillAppTests/Info.plist; @@ -5048,7 +5048,7 @@ BUNDLE_LOADER = "$(TEST_HOST)"; CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 306; + CURRENT_PROJECT_VERSION = 307; DEVELOPMENT_TEAM = 3DWS674B2M; INFOPLIST_FILE = iosHyperskillAppTests/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; @@ -5069,7 +5069,7 @@ CODE_SIGN_ENTITLEMENTS = NotificationServiceExtension/NotificationServiceExtension.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 306; + CURRENT_PROJECT_VERSION = 307; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = 3DWS674B2M; INFOPLIST_FILE = NotificationServiceExtension/Info.plist; @@ -5097,7 +5097,7 @@ CODE_SIGN_ENTITLEMENTS = NotificationServiceExtension/NotificationServiceExtension.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 306; + CURRENT_PROJECT_VERSION = 307; DEVELOPMENT_TEAM = 3DWS674B2M; INFOPLIST_FILE = NotificationServiceExtension/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; @@ -5242,7 +5242,7 @@ CODE_SIGN_ENTITLEMENTS = iosHyperskillApp/iosHyperskillApp.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 306; + CURRENT_PROJECT_VERSION = 307; DEVELOPMENT_ASSET_PATHS = "\"iosHyperskillApp/Preview Content\""; DEVELOPMENT_TEAM = 3DWS674B2M; ENABLE_PREVIEWS = YES; @@ -5278,7 +5278,7 @@ CODE_SIGN_ENTITLEMENTS = iosHyperskillApp/iosHyperskillApp.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 306; + CURRENT_PROJECT_VERSION = 307; DEVELOPMENT_ASSET_PATHS = "\"iosHyperskillApp/Preview Content\""; DEVELOPMENT_TEAM = 3DWS674B2M; ENABLE_PREVIEWS = YES; diff --git a/iosHyperskillApp/iosHyperskillApp/Info.plist b/iosHyperskillApp/iosHyperskillApp/Info.plist index 2f503668a1..180ef52bcd 100644 --- a/iosHyperskillApp/iosHyperskillApp/Info.plist +++ b/iosHyperskillApp/iosHyperskillApp/Info.plist @@ -36,7 +36,7 @@ CFBundleVersion - 306 + 307 FirebaseAppDelegateProxyEnabled FirebaseMessagingAutoInitEnabled diff --git a/iosHyperskillApp/iosHyperskillAppTests/Info.plist b/iosHyperskillApp/iosHyperskillAppTests/Info.plist index 7717f3e6b4..09a54bf660 100644 --- a/iosHyperskillApp/iosHyperskillAppTests/Info.plist +++ b/iosHyperskillApp/iosHyperskillAppTests/Info.plist @@ -15,6 +15,6 @@ CFBundleShortVersionString 1.50 CFBundleVersion - 306 + 307 diff --git a/iosHyperskillApp/iosHyperskillAppUITests/Info.plist b/iosHyperskillApp/iosHyperskillAppUITests/Info.plist index e893974006..41b638f7d6 100644 --- a/iosHyperskillApp/iosHyperskillAppUITests/Info.plist +++ b/iosHyperskillApp/iosHyperskillAppUITests/Info.plist @@ -15,6 +15,6 @@ CFBundleShortVersionString 1.50 CFBundleVersion - 306 + 307 From 41323cca8e74405621256f405f3f100036d7e14b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 29 Jan 2024 09:24:21 +0800 Subject: [PATCH 005/104] GitHub Actions: Bump actions/cache from 3.3.3 to 4.0.0 (#856) * GitHub Actions: Bump actions/cache from 3.3.3 to 4.0.0 Bumps [actions/cache](https://github.com/actions/cache) from 3.3.3 to 4.0.0. - [Release notes](https://github.com/actions/cache/releases) - [Changelog](https://github.com/actions/cache/blob/main/RELEASES.md) - [Commits](https://github.com/actions/cache/compare/v3.3.3...v4.0.0) --- updated-dependencies: - dependency-name: actions/cache dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] * Bump remaining usages --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Ivan Magda --- .github/actions/setup-android/action.yml | 6 +++--- .github/actions/setup-ios/action.yml | 10 +++++----- .github/workflows/ci.yml | 2 +- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/.github/actions/setup-android/action.yml b/.github/actions/setup-android/action.yml index 04193f520c..23a6fd0012 100644 --- a/.github/actions/setup-android/action.yml +++ b/.github/actions/setup-android/action.yml @@ -76,7 +76,7 @@ runs: # Cache Gradle dependencies - name: Setup Gradle Dependencies Cache - uses: actions/cache@v3.3.2 + uses: actions/cache@v4.0.0 with: path: ~/.gradle/caches key: ${{ runner.os }}-gradle-caches-${{ hashFiles('**/*.gradle', '**/*.gradle.kts') }} @@ -85,7 +85,7 @@ runs: # Cache Gradle Wrapper - name: Setup Gradle Wrapper Cache - uses: actions/cache@v3.3.2 + uses: actions/cache@v4.0.0 with: path: ~/.gradle/wrapper key: ${{ runner.os }}-gradle-wrapper-${{ hashFiles('**/gradle*properties') }} @@ -94,7 +94,7 @@ runs: # Cache Kotlin/Native compiler - name: Setup Kotlin/Native Compiler Cache - uses: actions/cache@v3.3.2 + uses: actions/cache@v4.0.0 with: path: ~/.konan key: ${{ runner.os }}-kotlin-native-compiler-${{ hashFiles('gradle/libs.versions.toml') }} diff --git a/.github/actions/setup-ios/action.yml b/.github/actions/setup-ios/action.yml index 9cd7b9d42e..be329d5603 100644 --- a/.github/actions/setup-ios/action.yml +++ b/.github/actions/setup-ios/action.yml @@ -63,7 +63,7 @@ runs: # Cache Gradle dependencies - name: Setup Gradle Dependencies Cache - uses: actions/cache@v3.3.2 + uses: actions/cache@v4.0.0 with: path: ~/.gradle/caches key: ${{ runner.os }}-gradle-caches-${{ hashFiles('**/*.gradle', '**/*.gradle.kts') }} @@ -72,7 +72,7 @@ runs: # Cache Gradle Wrapper - name: Setup Gradle Wrapper Cache - uses: actions/cache@v3.3.2 + uses: actions/cache@v4.0.0 with: path: ~/.gradle/wrapper key: ${{ runner.os }}-gradle-wrapper-${{ hashFiles('**/gradle*properties') }} @@ -81,7 +81,7 @@ runs: # Cache Kotlin/Native compiler - name: Setup Kotlin/Native Compiler Cache - uses: actions/cache@v3.3.2 + uses: actions/cache@v4.0.0 with: path: ~/.konan key: ${{ runner.os }}-kotlin-native-compiler-${{ hashFiles('gradle/libs.versions.toml') }} @@ -100,7 +100,7 @@ runs: # Cache Pods dependencies - name: Cache Pods - uses: actions/cache@v3.3.2 + uses: actions/cache@v4.0.0 id: cache-pods with: path: './iosHyperskillApp/Pods' @@ -111,7 +111,7 @@ runs: # Cache CocoaPods - name: Cache CocoaPods if: steps.cache-pods.outputs.cache-hit != 'true' - uses: actions/cache@v3.3.2 + uses: actions/cache@v4.0.0 with: path: | ~/.cocoapods diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index edeec5903a..4cf28da039 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -94,7 +94,7 @@ jobs: working-directory: './iosHyperskillApp' - name: Cache Pods - uses: actions/cache@v3.3.3 + uses: actions/cache@v4.0.0 id: cache-pods with: path: './iosHyperskillApp/Pods' From 682ea76e7ff6a2bd79d2e3ad809d3b95807232d7 Mon Sep 17 00:00:00 2001 From: github-actions Date: Mon, 29 Jan 2024 01:25:57 +0000 Subject: [PATCH 006/104] iOS: Bump build number --- .../NotificationServiceExtension/Info.plist | 2 +- .../iosHyperskillApp.xcodeproj/project.pbxproj | 16 ++++++++-------- iosHyperskillApp/iosHyperskillApp/Info.plist | 2 +- .../iosHyperskillAppTests/Info.plist | 2 +- .../iosHyperskillAppUITests/Info.plist | 2 +- 5 files changed, 12 insertions(+), 12 deletions(-) diff --git a/iosHyperskillApp/NotificationServiceExtension/Info.plist b/iosHyperskillApp/NotificationServiceExtension/Info.plist index bb350abd0b..b2364bf533 100644 --- a/iosHyperskillApp/NotificationServiceExtension/Info.plist +++ b/iosHyperskillApp/NotificationServiceExtension/Info.plist @@ -9,7 +9,7 @@ CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleVersion - 307 + 308 CFBundleShortVersionString 1.50 CFBundlePackageType diff --git a/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj b/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj index 36c91fad0a..23a31fb2b1 100644 --- a/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj +++ b/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj @@ -4985,7 +4985,7 @@ buildSettings = { CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 307; + CURRENT_PROJECT_VERSION = 308; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = 3DWS674B2M; GENERATE_INFOPLIST_FILE = NO; @@ -5006,7 +5006,7 @@ buildSettings = { CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 307; + CURRENT_PROJECT_VERSION = 308; DEVELOPMENT_TEAM = 3DWS674B2M; GENERATE_INFOPLIST_FILE = NO; INFOPLIST_FILE = iosHyperskillAppUITests/Info.plist; @@ -5027,7 +5027,7 @@ BUNDLE_LOADER = "$(TEST_HOST)"; CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 307; + CURRENT_PROJECT_VERSION = 308; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = 3DWS674B2M; INFOPLIST_FILE = iosHyperskillAppTests/Info.plist; @@ -5048,7 +5048,7 @@ BUNDLE_LOADER = "$(TEST_HOST)"; CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 307; + CURRENT_PROJECT_VERSION = 308; DEVELOPMENT_TEAM = 3DWS674B2M; INFOPLIST_FILE = iosHyperskillAppTests/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; @@ -5069,7 +5069,7 @@ CODE_SIGN_ENTITLEMENTS = NotificationServiceExtension/NotificationServiceExtension.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 307; + CURRENT_PROJECT_VERSION = 308; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = 3DWS674B2M; INFOPLIST_FILE = NotificationServiceExtension/Info.plist; @@ -5097,7 +5097,7 @@ CODE_SIGN_ENTITLEMENTS = NotificationServiceExtension/NotificationServiceExtension.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 307; + CURRENT_PROJECT_VERSION = 308; DEVELOPMENT_TEAM = 3DWS674B2M; INFOPLIST_FILE = NotificationServiceExtension/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; @@ -5242,7 +5242,7 @@ CODE_SIGN_ENTITLEMENTS = iosHyperskillApp/iosHyperskillApp.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 307; + CURRENT_PROJECT_VERSION = 308; DEVELOPMENT_ASSET_PATHS = "\"iosHyperskillApp/Preview Content\""; DEVELOPMENT_TEAM = 3DWS674B2M; ENABLE_PREVIEWS = YES; @@ -5278,7 +5278,7 @@ CODE_SIGN_ENTITLEMENTS = iosHyperskillApp/iosHyperskillApp.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 307; + CURRENT_PROJECT_VERSION = 308; DEVELOPMENT_ASSET_PATHS = "\"iosHyperskillApp/Preview Content\""; DEVELOPMENT_TEAM = 3DWS674B2M; ENABLE_PREVIEWS = YES; diff --git a/iosHyperskillApp/iosHyperskillApp/Info.plist b/iosHyperskillApp/iosHyperskillApp/Info.plist index 180ef52bcd..453f5ace84 100644 --- a/iosHyperskillApp/iosHyperskillApp/Info.plist +++ b/iosHyperskillApp/iosHyperskillApp/Info.plist @@ -36,7 +36,7 @@ CFBundleVersion - 307 + 308 FirebaseAppDelegateProxyEnabled FirebaseMessagingAutoInitEnabled diff --git a/iosHyperskillApp/iosHyperskillAppTests/Info.plist b/iosHyperskillApp/iosHyperskillAppTests/Info.plist index 09a54bf660..8cfae0b38e 100644 --- a/iosHyperskillApp/iosHyperskillAppTests/Info.plist +++ b/iosHyperskillApp/iosHyperskillAppTests/Info.plist @@ -15,6 +15,6 @@ CFBundleShortVersionString 1.50 CFBundleVersion - 307 + 308 diff --git a/iosHyperskillApp/iosHyperskillAppUITests/Info.plist b/iosHyperskillApp/iosHyperskillAppUITests/Info.plist index 41b638f7d6..86376b61c4 100644 --- a/iosHyperskillApp/iosHyperskillAppUITests/Info.plist +++ b/iosHyperskillApp/iosHyperskillAppUITests/Info.plist @@ -15,6 +15,6 @@ CFBundleShortVersionString 1.50 CFBundleVersion - 307 + 308 From cb178b94187800bef3db0338808ac4ce0a099196 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 29 Jan 2024 09:38:10 +0800 Subject: [PATCH 007/104] GitHub Actions: Bump actions/deploy-pages from 4.0.2 to 4.0.3 (#857) Bumps [actions/deploy-pages](https://github.com/actions/deploy-pages) from 4.0.2 to 4.0.3. - [Release notes](https://github.com/actions/deploy-pages/releases) - [Commits](https://github.com/actions/deploy-pages/compare/v4.0.2...v4.0.3) --- updated-dependencies: - dependency-name: actions/deploy-pages dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/gh_pages_analytics.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/gh_pages_analytics.yml b/.github/workflows/gh_pages_analytics.yml index a40692bd3c..49987ca7b0 100644 --- a/.github/workflows/gh_pages_analytics.yml +++ b/.github/workflows/gh_pages_analytics.yml @@ -69,6 +69,6 @@ jobs: steps: - name: Deploy to GitHub Pages id: deployment - uses: actions/deploy-pages@v4.0.2 + uses: actions/deploy-pages@v4.0.3 with: artifact_name: 'github-pages-analytics' \ No newline at end of file From 85e8eee040e0dc167823517aeb29424fb67aac60 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 29 Jan 2024 09:47:00 +0800 Subject: [PATCH 008/104] GitHub Actions: Bump toshimaru/auto-author-assign from 2.0.1 to 2.1.0 (#858) Bumps [toshimaru/auto-author-assign](https://github.com/toshimaru/auto-author-assign) from 2.0.1 to 2.1.0. - [Release notes](https://github.com/toshimaru/auto-author-assign/releases) - [Changelog](https://github.com/toshimaru/auto-author-assign/blob/main/CHANGELOG.md) - [Commits](https://github.com/toshimaru/auto-author-assign/compare/v2.0.1...v2.1.0) --- updated-dependencies: - dependency-name: toshimaru/auto-author-assign dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/auto_author_assign.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/auto_author_assign.yml b/.github/workflows/auto_author_assign.yml index fa63bf354a..5c082fbe96 100644 --- a/.github/workflows/auto_author_assign.yml +++ b/.github/workflows/auto_author_assign.yml @@ -12,6 +12,6 @@ jobs: runs-on: ubuntu-22.04 if: ${{ !github.event.pull_request.assignee }} steps: - - uses: toshimaru/auto-author-assign@v2.0.1 + - uses: toshimaru/auto-author-assign@v2.1.0 with: repo-token: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file From 5a4ca40363266b18b37ecabbf55620e3f944c05a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 29 Jan 2024 09:49:18 +0800 Subject: [PATCH 009/104] GitHub Actions: Bump dorny/paths-filter from 2.11.1 to 3.0.0 (#861) Bumps [dorny/paths-filter](https://github.com/dorny/paths-filter) from 2.11.1 to 3.0.0. - [Release notes](https://github.com/dorny/paths-filter/releases) - [Changelog](https://github.com/dorny/paths-filter/blob/master/CHANGELOG.md) - [Commits](https://github.com/dorny/paths-filter/compare/v2.11.1...v3.0.0) --- updated-dependencies: - dependency-name: dorny/paths-filter dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/detect_changed_files_reusable_workflow.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/detect_changed_files_reusable_workflow.yml b/.github/workflows/detect_changed_files_reusable_workflow.yml index d45f50695b..ec08918f90 100644 --- a/.github/workflows/detect_changed_files_reusable_workflow.yml +++ b/.github/workflows/detect_changed_files_reusable_workflow.yml @@ -55,7 +55,7 @@ jobs: uses: actions/checkout@v4.1.1 - name: Detect changes - uses: dorny/paths-filter@v2.11.1 + uses: dorny/paths-filter@v3.0.0 id: changes with: base: ${{ inputs.base }} From 08d19bf08e50ece351ce9de04a573f413294fb95 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 29 Jan 2024 10:00:17 +0800 Subject: [PATCH 010/104] Bundler: Bump cocoapods from 1.14.3 to 1.15.0 in /iosHyperskillApp (#862) * Bundler: Bump cocoapods from 1.14.3 to 1.15.0 in /iosHyperskillApp Bumps [cocoapods](https://github.com/CocoaPods/CocoaPods) from 1.14.3 to 1.15.0. - [Release notes](https://github.com/CocoaPods/CocoaPods/releases) - [Changelog](https://github.com/CocoaPods/CocoaPods/blob/master/CHANGELOG.md) - [Commits](https://github.com/CocoaPods/CocoaPods/compare/1.14.3...1.15.0) --- updated-dependencies: - dependency-name: cocoapods dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] * Run bundle update --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Ivan Magda --- iosHyperskillApp/Gemfile | 2 +- iosHyperskillApp/Gemfile.lock | 46 +++++++++++++++++------------------ 2 files changed, 23 insertions(+), 25 deletions(-) diff --git a/iosHyperskillApp/Gemfile b/iosHyperskillApp/Gemfile index 35b10d4c11..aab6408311 100644 --- a/iosHyperskillApp/Gemfile +++ b/iosHyperskillApp/Gemfile @@ -2,7 +2,7 @@ source "https://rubygems.org" ruby "3.1.0" gem "fastlane", "2.219.0" -gem "cocoapods", "1.14.3" +gem "cocoapods", "1.15.0" gem "generamba", git: "https://github.com/ivan-magda/Generamba.git", branch: "develop" eval_gemfile("fastlane/Pluginfile") \ No newline at end of file diff --git a/iosHyperskillApp/Gemfile.lock b/iosHyperskillApp/Gemfile.lock index ba046f3905..01b2e31c9c 100644 --- a/iosHyperskillApp/Gemfile.lock +++ b/iosHyperskillApp/Gemfile.lock @@ -16,7 +16,7 @@ GEM specs: CFPropertyList (3.0.6) rexml - activesupport (7.1.2) + activesupport (7.1.3) base64 bigdecimal concurrent-ruby (~> 1.0, >= 1.0.2) @@ -34,29 +34,29 @@ GEM artifactory (3.0.15) atomos (0.1.3) aws-eventstream (1.3.0) - aws-partitions (1.877.0) - aws-sdk-core (3.190.1) + aws-partitions (1.883.0) + aws-sdk-core (3.191.0) aws-eventstream (~> 1, >= 1.3.0) aws-partitions (~> 1, >= 1.651.0) aws-sigv4 (~> 1.8) jmespath (~> 1, >= 1.6.1) - aws-sdk-kms (1.75.0) - aws-sdk-core (~> 3, >= 3.188.0) + aws-sdk-kms (1.77.0) + aws-sdk-core (~> 3, >= 3.191.0) aws-sigv4 (~> 1.1) - aws-sdk-s3 (1.142.0) - aws-sdk-core (~> 3, >= 3.189.0) + aws-sdk-s3 (1.143.0) + aws-sdk-core (~> 3, >= 3.191.0) aws-sdk-kms (~> 1) aws-sigv4 (~> 1.8) aws-sigv4 (1.8.0) aws-eventstream (~> 1, >= 1.0.2) babosa (1.0.4) base64 (0.2.0) - bigdecimal (3.1.5) + bigdecimal (3.1.6) claide (1.1.0) - cocoapods (1.14.3) + cocoapods (1.15.0) addressable (~> 2.8) claide (>= 1.0.2, < 2.0) - cocoapods-core (= 1.14.3) + cocoapods-core (= 1.15.0) cocoapods-deintegrate (>= 1.0.3, < 2.0) cocoapods-downloader (>= 2.1, < 3.0) cocoapods-plugins (>= 1.0.0, < 2.0) @@ -71,7 +71,7 @@ GEM nap (~> 1.0) ruby-macho (>= 2.3.0, < 3.0) xcodeproj (>= 1.23.0, < 2.0) - cocoapods-core (1.14.3) + cocoapods-core (1.15.0) activesupport (>= 5.0, < 8) addressable (~> 2.8) algoliasearch (~> 1.0) @@ -94,12 +94,12 @@ GEM colored2 (3.1.2) commander (4.6.0) highline (~> 2.0.0) - concurrent-ruby (1.2.2) + concurrent-ruby (1.2.3) connection_pool (2.4.1) declarative (0.0.20) digest-crc (0.6.5) rake (>= 12.0.0, < 14.0.0) - domain_name (0.6.20231109) + domain_name (0.6.20240107) dotenv (2.8.1) drb (2.2.0) ruby2_keywords @@ -178,10 +178,10 @@ GEM xcodeproj (>= 1.13.0, < 2.0.0) xcpretty (~> 0.3.0) xcpretty-travis-formatter (>= 0.0.3) - fastlane-plugin-firebase_app_distribution (0.8.0) + fastlane-plugin-firebase_app_distribution (0.8.1) google-apis-firebaseappdistribution_v1 (~> 0.3.0) google-apis-firebaseappdistribution_v1alpha (~> 0.2.0) - fastlane-plugin-sentry (1.17.0) + fastlane-plugin-sentry (1.18.0) os (~> 1.1, >= 1.1.4) ffi (1.16.3) fourflusher (2.3.1) @@ -192,7 +192,7 @@ GEM rchardet (~> 1.8) google-apis-androidpublisher_v3 (0.54.0) google-apis-core (>= 0.11.0, < 2.a) - google-apis-core (0.11.2) + google-apis-core (0.11.3) addressable (~> 2.5, >= 2.5.1) googleauth (>= 0.16.2, < 2.a) httpclient (>= 2.8.1, < 3.a) @@ -200,7 +200,6 @@ GEM representable (~> 3.0) retriable (>= 2.0, < 4.a) rexml - webrick google-apis-firebaseappdistribution_v1 (0.3.0) google-apis-core (>= 0.11.0, < 2.a) google-apis-firebaseappdistribution_v1alpha (0.2.0) @@ -209,7 +208,7 @@ GEM google-apis-core (>= 0.11.0, < 2.a) google-apis-playcustomapp_v1 (0.13.0) google-apis-core (>= 0.11.0, < 2.a) - google-apis-storage_v1 (0.29.0) + google-apis-storage_v1 (0.31.0) google-apis-core (>= 0.11.0, < 2.a) google-cloud-core (1.6.1) google-cloud-env (>= 1.0, < 3.a) @@ -217,11 +216,11 @@ GEM google-cloud-env (1.6.0) faraday (>= 0.17.3, < 3.0) google-cloud-errors (1.3.1) - google-cloud-storage (1.45.0) + google-cloud-storage (1.47.0) addressable (~> 2.8) digest-crc (~> 0.4) google-apis-iamcredentials_v1 (~> 0.1) - google-apis-storage_v1 (~> 0.29.0) + google-apis-storage_v1 (~> 0.31.0) google-cloud-core (~> 1.6) googleauth (>= 0.16.2, < 2.a) mini_mime (~> 1.0) @@ -243,7 +242,7 @@ GEM liquid (4.0.4) mini_magick (4.12.0) mini_mime (1.1.5) - minitest (5.20.0) + minitest (5.21.2) molinillo (0.8.0) multi_json (1.15.0) multipart-post (2.3.0) @@ -292,9 +291,8 @@ GEM concurrent-ruby (~> 1.0) uber (0.1.0) unicode-display_width (2.5.0) - webrick (1.8.1) word_wrap (1.0.0) - xcodeproj (1.23.0) + xcodeproj (1.24.0) CFPropertyList (>= 2.3.3, < 4.0) atomos (~> 0.1.3) claide (>= 1.0.2, < 2.0) @@ -313,7 +311,7 @@ PLATFORMS x86_64-linux DEPENDENCIES - cocoapods (= 1.14.3) + cocoapods (= 1.15.0) fastlane (= 2.219.0) fastlane-plugin-firebase_app_distribution fastlane-plugin-sentry From ba69771f735aae58fdee948db88d40655a89cacc Mon Sep 17 00:00:00 2001 From: Ivan Magda Date: Mon, 29 Jan 2024 10:07:27 +0800 Subject: [PATCH 011/104] GitHub Actions: Bump Xcode version from 15.1 to 15.2 (#863) --- .github/actions/setup-ios/action.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/actions/setup-ios/action.yml b/.github/actions/setup-ios/action.yml index be329d5603..25bfd68e7c 100644 --- a/.github/actions/setup-ios/action.yml +++ b/.github/actions/setup-ios/action.yml @@ -20,7 +20,7 @@ runs: - name: Setup Xcode version uses: maxim-lobanov/setup-xcode@v1.6.0 with: - xcode-version: '15.1' + xcode-version: '15.2' - name: Homebrew install git-crypt run: brew install git-crypt From 7b42cf54500836c9d4a5478b6f0c6ec95f14252b Mon Sep 17 00:00:00 2001 From: Ivan Magda Date: Mon, 29 Jan 2024 11:09:14 +0800 Subject: [PATCH 012/104] Bump Ruby version from 3.1.0 to 3.3.0 (#864) --- .github/actions/setup-android/action.yml | 4 +-- .github/actions/setup-ios/action.yml | 4 +-- .github/workflows/automerge_into_release.yml | 4 +-- .github/workflows/ci.yml | 4 +-- androidHyperskillApp/.ruby-version | 2 +- androidHyperskillApp/Gemfile | 2 +- androidHyperskillApp/Gemfile.lock | 34 ++++++++++---------- iosHyperskillApp/.ruby-version | 2 +- iosHyperskillApp/Gemfile | 2 +- iosHyperskillApp/Gemfile.lock | 6 ++-- iosHyperskillApp/Podfile.lock | 2 +- 11 files changed, 34 insertions(+), 32 deletions(-) diff --git a/.github/actions/setup-android/action.yml b/.github/actions/setup-android/action.yml index 23a6fd0012..ccd66a7877 100644 --- a/.github/actions/setup-android/action.yml +++ b/.github/actions/setup-android/action.yml @@ -62,9 +62,9 @@ runs: - name: Setup Ruby if: ${{ inputs.setup-ruby == 'true' }} - uses: ruby/setup-ruby@v1.150.0 + uses: ruby/setup-ruby@v1.170.0 with: - ruby-version: '3.1.0' + ruby-version: '3.3.0' bundler-cache: true working-directory: './androidHyperskillApp' diff --git a/.github/actions/setup-ios/action.yml b/.github/actions/setup-ios/action.yml index 25bfd68e7c..caf7b3c04b 100644 --- a/.github/actions/setup-ios/action.yml +++ b/.github/actions/setup-ios/action.yml @@ -49,9 +49,9 @@ runs: shell: bash - name: Setup Ruby - uses: ruby/setup-ruby@v1.150.0 + uses: ruby/setup-ruby@v1.170.0 with: - ruby-version: '3.1.0' + ruby-version: '3.3.0' bundler-cache: true working-directory: './iosHyperskillApp' diff --git a/.github/workflows/automerge_into_release.yml b/.github/workflows/automerge_into_release.yml index 3fdd0523f0..e875e6c1be 100644 --- a/.github/workflows/automerge_into_release.yml +++ b/.github/workflows/automerge_into_release.yml @@ -37,9 +37,9 @@ jobs: token: ${{ secrets.GH_PAT }} - name: Setup Ruby - uses: ruby/setup-ruby@v1.150.0 + uses: ruby/setup-ruby@v1.170.0 with: - ruby-version: "3.1.0" + ruby-version: "3.3.0" bundler-cache: true working-directory: "./iosHyperskillApp" diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4cf28da039..d7b386b40a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -87,9 +87,9 @@ jobs: uses: actions/checkout@v4.1.1 - name: Setup Ruby - uses: ruby/setup-ruby@v1.150.0 + uses: ruby/setup-ruby@v1.170.0 with: - ruby-version: '3.1.0' + ruby-version: '3.3.0' bundler-cache: true working-directory: './iosHyperskillApp' diff --git a/androidHyperskillApp/.ruby-version b/androidHyperskillApp/.ruby-version index fd2a01863f..15a2799817 100644 --- a/androidHyperskillApp/.ruby-version +++ b/androidHyperskillApp/.ruby-version @@ -1 +1 @@ -3.1.0 +3.3.0 diff --git a/androidHyperskillApp/Gemfile b/androidHyperskillApp/Gemfile index 6c85b6405a..3dda334cd0 100644 --- a/androidHyperskillApp/Gemfile +++ b/androidHyperskillApp/Gemfile @@ -1,5 +1,5 @@ source "https://rubygems.org" -ruby "3.1.0" +ruby "3.3.0" gem "fastlane", "2.219.0" diff --git a/androidHyperskillApp/Gemfile.lock b/androidHyperskillApp/Gemfile.lock index 3bcbb62c40..b0f9a5b16e 100644 --- a/androidHyperskillApp/Gemfile.lock +++ b/androidHyperskillApp/Gemfile.lock @@ -8,17 +8,17 @@ GEM artifactory (3.0.15) atomos (0.1.3) aws-eventstream (1.3.0) - aws-partitions (1.877.0) - aws-sdk-core (3.190.1) + aws-partitions (1.883.0) + aws-sdk-core (3.191.0) aws-eventstream (~> 1, >= 1.3.0) aws-partitions (~> 1, >= 1.651.0) aws-sigv4 (~> 1.8) jmespath (~> 1, >= 1.6.1) - aws-sdk-kms (1.75.0) - aws-sdk-core (~> 3, >= 3.188.0) + aws-sdk-kms (1.77.0) + aws-sdk-core (~> 3, >= 3.191.0) aws-sigv4 (~> 1.1) - aws-sdk-s3 (1.142.0) - aws-sdk-core (~> 3, >= 3.189.0) + aws-sdk-s3 (1.143.0) + aws-sdk-core (~> 3, >= 3.191.0) aws-sdk-kms (~> 1) aws-sigv4 (~> 1.8) aws-sigv4 (1.8.0) @@ -32,7 +32,7 @@ GEM declarative (0.0.20) digest-crc (0.6.5) rake (>= 12.0.0, < 14.0.0) - domain_name (0.6.20231109) + domain_name (0.6.20240107) dotenv (2.8.1) emoji_regex (3.2.3) excon (0.109.0) @@ -106,13 +106,13 @@ GEM xcodeproj (>= 1.13.0, < 2.0.0) xcpretty (~> 0.3.0) xcpretty-travis-formatter (>= 0.0.3) - fastlane-plugin-firebase_app_distribution (0.8.0) + fastlane-plugin-firebase_app_distribution (0.8.1) google-apis-firebaseappdistribution_v1 (~> 0.3.0) google-apis-firebaseappdistribution_v1alpha (~> 0.2.0) gh_inspector (1.1.3) google-apis-androidpublisher_v3 (0.54.0) google-apis-core (>= 0.11.0, < 2.a) - google-apis-core (0.11.2) + google-apis-core (0.11.3) addressable (~> 2.5, >= 2.5.1) googleauth (>= 0.16.2, < 2.a) httpclient (>= 2.8.1, < 3.a) @@ -120,7 +120,6 @@ GEM representable (~> 3.0) retriable (>= 2.0, < 4.a) rexml - webrick google-apis-firebaseappdistribution_v1 (0.3.0) google-apis-core (>= 0.11.0, < 2.a) google-apis-firebaseappdistribution_v1alpha (0.2.0) @@ -129,7 +128,7 @@ GEM google-apis-core (>= 0.11.0, < 2.a) google-apis-playcustomapp_v1 (0.13.0) google-apis-core (>= 0.11.0, < 2.a) - google-apis-storage_v1 (0.29.0) + google-apis-storage_v1 (0.31.0) google-apis-core (>= 0.11.0, < 2.a) google-cloud-core (1.6.1) google-cloud-env (>= 1.0, < 3.a) @@ -137,11 +136,11 @@ GEM google-cloud-env (1.6.0) faraday (>= 0.17.3, < 3.0) google-cloud-errors (1.3.1) - google-cloud-storage (1.45.0) + google-cloud-storage (1.47.0) addressable (~> 2.8) digest-crc (~> 0.4) google-apis-iamcredentials_v1 (~> 0.1) - google-apis-storage_v1 (~> 0.29.0) + google-apis-storage_v1 (~> 0.31.0) google-cloud-core (~> 1.6) googleauth (>= 0.16.2, < 2.a) mini_mime (~> 1.0) @@ -197,9 +196,8 @@ GEM tty-cursor (~> 0.7) uber (0.1.0) unicode-display_width (2.5.0) - webrick (1.8.1) word_wrap (1.0.0) - xcodeproj (1.23.0) + xcodeproj (1.24.0) CFPropertyList (>= 2.3.3, < 4.0) atomos (~> 0.1.3) claide (>= 1.0.2, < 2.0) @@ -213,7 +211,9 @@ GEM PLATFORMS arm64-darwin-22 + arm64-darwin-23 x86_64-darwin-19 + x86_64-darwin-20 x86_64-linux DEPENDENCIES @@ -221,7 +221,7 @@ DEPENDENCIES fastlane-plugin-firebase_app_distribution RUBY VERSION - ruby 3.1.0p0 + ruby 3.3.0p0 BUNDLED WITH - 2.4.4 + 2.5.5 diff --git a/iosHyperskillApp/.ruby-version b/iosHyperskillApp/.ruby-version index fd2a01863f..15a2799817 100644 --- a/iosHyperskillApp/.ruby-version +++ b/iosHyperskillApp/.ruby-version @@ -1 +1 @@ -3.1.0 +3.3.0 diff --git a/iosHyperskillApp/Gemfile b/iosHyperskillApp/Gemfile index aab6408311..4688dcb969 100644 --- a/iosHyperskillApp/Gemfile +++ b/iosHyperskillApp/Gemfile @@ -1,5 +1,5 @@ source "https://rubygems.org" -ruby "3.1.0" +ruby "3.3.0" gem "fastlane", "2.219.0" gem "cocoapods", "1.15.0" diff --git a/iosHyperskillApp/Gemfile.lock b/iosHyperskillApp/Gemfile.lock index 01b2e31c9c..448853f75b 100644 --- a/iosHyperskillApp/Gemfile.lock +++ b/iosHyperskillApp/Gemfile.lock @@ -306,7 +306,9 @@ GEM PLATFORMS arm64-darwin-22 + arm64-darwin-23 x86_64-darwin-19 + x86_64-darwin-20 x86_64-darwin-22 x86_64-linux @@ -318,7 +320,7 @@ DEPENDENCIES generamba! RUBY VERSION - ruby 3.1.0p0 + ruby 3.3.0p0 BUNDLED WITH - 2.4.4 + 2.5.5 diff --git a/iosHyperskillApp/Podfile.lock b/iosHyperskillApp/Podfile.lock index 34ec506632..fc9c004246 100644 --- a/iosHyperskillApp/Podfile.lock +++ b/iosHyperskillApp/Podfile.lock @@ -225,4 +225,4 @@ SPEC CHECKSUMS: PODFILE CHECKSUM: 1613f08afc0d23eb5c1805973d23b2f5c885203e -COCOAPODS: 1.14.3 +COCOAPODS: 1.15.0 From a2f3675b2ae05085f0b501cbc98abd15aeff5416 Mon Sep 17 00:00:00 2001 From: Ivan Magda Date: Mon, 29 Jan 2024 11:23:05 +0800 Subject: [PATCH 013/104] Bump build number --- gradle/app.versions.toml | 2 +- .../NotificationServiceExtension/Info.plist | 2 +- .../iosHyperskillApp.xcodeproj/project.pbxproj | 16 ++++++++-------- iosHyperskillApp/iosHyperskillApp/Info.plist | 2 +- .../iosHyperskillAppTests/Info.plist | 2 +- .../iosHyperskillAppUITests/Info.plist | 2 +- 6 files changed, 13 insertions(+), 13 deletions(-) diff --git a/gradle/app.versions.toml b/gradle/app.versions.toml index 0805a85359..ed800a1fb8 100644 --- a/gradle/app.versions.toml +++ b/gradle/app.versions.toml @@ -3,4 +3,4 @@ minSdk = '24' targetSdk = '33' compileSdk = '33' versionName = '1.50' -versionCode = '306' \ No newline at end of file +versionCode = '307' \ No newline at end of file diff --git a/iosHyperskillApp/NotificationServiceExtension/Info.plist b/iosHyperskillApp/NotificationServiceExtension/Info.plist index b2364bf533..bb2103db54 100644 --- a/iosHyperskillApp/NotificationServiceExtension/Info.plist +++ b/iosHyperskillApp/NotificationServiceExtension/Info.plist @@ -9,7 +9,7 @@ CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleVersion - 308 + 309 CFBundleShortVersionString 1.50 CFBundlePackageType diff --git a/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj b/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj index 23a31fb2b1..d23cfbb2d7 100644 --- a/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj +++ b/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj @@ -4985,7 +4985,7 @@ buildSettings = { CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 308; + CURRENT_PROJECT_VERSION = 309; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = 3DWS674B2M; GENERATE_INFOPLIST_FILE = NO; @@ -5006,7 +5006,7 @@ buildSettings = { CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 308; + CURRENT_PROJECT_VERSION = 309; DEVELOPMENT_TEAM = 3DWS674B2M; GENERATE_INFOPLIST_FILE = NO; INFOPLIST_FILE = iosHyperskillAppUITests/Info.plist; @@ -5027,7 +5027,7 @@ BUNDLE_LOADER = "$(TEST_HOST)"; CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 308; + CURRENT_PROJECT_VERSION = 309; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = 3DWS674B2M; INFOPLIST_FILE = iosHyperskillAppTests/Info.plist; @@ -5048,7 +5048,7 @@ BUNDLE_LOADER = "$(TEST_HOST)"; CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 308; + CURRENT_PROJECT_VERSION = 309; DEVELOPMENT_TEAM = 3DWS674B2M; INFOPLIST_FILE = iosHyperskillAppTests/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; @@ -5069,7 +5069,7 @@ CODE_SIGN_ENTITLEMENTS = NotificationServiceExtension/NotificationServiceExtension.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 308; + CURRENT_PROJECT_VERSION = 309; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = 3DWS674B2M; INFOPLIST_FILE = NotificationServiceExtension/Info.plist; @@ -5097,7 +5097,7 @@ CODE_SIGN_ENTITLEMENTS = NotificationServiceExtension/NotificationServiceExtension.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 308; + CURRENT_PROJECT_VERSION = 309; DEVELOPMENT_TEAM = 3DWS674B2M; INFOPLIST_FILE = NotificationServiceExtension/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; @@ -5242,7 +5242,7 @@ CODE_SIGN_ENTITLEMENTS = iosHyperskillApp/iosHyperskillApp.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 308; + CURRENT_PROJECT_VERSION = 309; DEVELOPMENT_ASSET_PATHS = "\"iosHyperskillApp/Preview Content\""; DEVELOPMENT_TEAM = 3DWS674B2M; ENABLE_PREVIEWS = YES; @@ -5278,7 +5278,7 @@ CODE_SIGN_ENTITLEMENTS = iosHyperskillApp/iosHyperskillApp.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 308; + CURRENT_PROJECT_VERSION = 309; DEVELOPMENT_ASSET_PATHS = "\"iosHyperskillApp/Preview Content\""; DEVELOPMENT_TEAM = 3DWS674B2M; ENABLE_PREVIEWS = YES; diff --git a/iosHyperskillApp/iosHyperskillApp/Info.plist b/iosHyperskillApp/iosHyperskillApp/Info.plist index 453f5ace84..8e831df9c0 100644 --- a/iosHyperskillApp/iosHyperskillApp/Info.plist +++ b/iosHyperskillApp/iosHyperskillApp/Info.plist @@ -36,7 +36,7 @@ CFBundleVersion - 308 + 309 FirebaseAppDelegateProxyEnabled FirebaseMessagingAutoInitEnabled diff --git a/iosHyperskillApp/iosHyperskillAppTests/Info.plist b/iosHyperskillApp/iosHyperskillAppTests/Info.plist index 8cfae0b38e..204f070231 100644 --- a/iosHyperskillApp/iosHyperskillAppTests/Info.plist +++ b/iosHyperskillApp/iosHyperskillAppTests/Info.plist @@ -15,6 +15,6 @@ CFBundleShortVersionString 1.50 CFBundleVersion - 308 + 309 diff --git a/iosHyperskillApp/iosHyperskillAppUITests/Info.plist b/iosHyperskillApp/iosHyperskillAppUITests/Info.plist index 86376b61c4..387dc0a57a 100644 --- a/iosHyperskillApp/iosHyperskillAppUITests/Info.plist +++ b/iosHyperskillApp/iosHyperskillAppUITests/Info.plist @@ -15,6 +15,6 @@ CFBundleShortVersionString 1.50 CFBundleVersion - 308 + 309 From 6e4bd201c2bf65ad4a902fb5228df90cc441e6ad Mon Sep 17 00:00:00 2001 From: Ivan Magda Date: Mon, 29 Jan 2024 12:01:22 +0800 Subject: [PATCH 014/104] GitHub Actions: Bump actions/setup-java from 3.10.0 to 4.0.0 (#865) --- .github/actions/setup-android/action.yml | 2 +- .github/actions/setup-ios/action.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/actions/setup-android/action.yml b/.github/actions/setup-android/action.yml index ccd66a7877..22c08999b7 100644 --- a/.github/actions/setup-android/action.yml +++ b/.github/actions/setup-android/action.yml @@ -69,7 +69,7 @@ runs: working-directory: './androidHyperskillApp' - name: Setup Java JDK - uses: actions/setup-java@v3.10.0 + uses: actions/setup-java@v4.0.0 with: java-version: '17' distribution: 'temurin' diff --git a/.github/actions/setup-ios/action.yml b/.github/actions/setup-ios/action.yml index caf7b3c04b..40c29cb820 100644 --- a/.github/actions/setup-ios/action.yml +++ b/.github/actions/setup-ios/action.yml @@ -56,7 +56,7 @@ runs: working-directory: './iosHyperskillApp' - name: Setup Java JDK - uses: actions/setup-java@v3.10.0 + uses: actions/setup-java@v4.0.0 with: java-version: '17' distribution: 'temurin' From 041b33f6a5b25f9733b691ae8aee93db93061a3c Mon Sep 17 00:00:00 2001 From: Ivan Magda Date: Mon, 29 Jan 2024 12:13:11 +0800 Subject: [PATCH 015/104] Bump moko-kswift version from 0.6.1 to 0.7.0 (#866) --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index f0191c8c00..cb31f70b35 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -4,7 +4,7 @@ kotlin = '1.8.22' kotlinCoroutines = '1.7.2' ktor = '2.3.3' mokoResources = "0.23.0" -mokoKswift = "0.6.1" +mokoKswift = "0.7.0" ktlintRules = '1.0.0' adapters = '1.1.1' sentry = '7.1.0' From 501f9f59a88b1a903bd2b1e865f21cbf57597131 Mon Sep 17 00:00:00 2001 From: Ivan Magda Date: Mon, 29 Jan 2024 12:25:36 +0800 Subject: [PATCH 016/104] Bump build number --- gradle/app.versions.toml | 2 +- .../NotificationServiceExtension/Info.plist | 2 +- .../iosHyperskillApp.xcodeproj/project.pbxproj | 16 ++++++++-------- iosHyperskillApp/iosHyperskillApp/Info.plist | 2 +- .../iosHyperskillAppTests/Info.plist | 2 +- .../iosHyperskillAppUITests/Info.plist | 2 +- 6 files changed, 13 insertions(+), 13 deletions(-) diff --git a/gradle/app.versions.toml b/gradle/app.versions.toml index ed800a1fb8..2636136490 100644 --- a/gradle/app.versions.toml +++ b/gradle/app.versions.toml @@ -3,4 +3,4 @@ minSdk = '24' targetSdk = '33' compileSdk = '33' versionName = '1.50' -versionCode = '307' \ No newline at end of file +versionCode = '308' \ No newline at end of file diff --git a/iosHyperskillApp/NotificationServiceExtension/Info.plist b/iosHyperskillApp/NotificationServiceExtension/Info.plist index bb2103db54..f73fbde338 100644 --- a/iosHyperskillApp/NotificationServiceExtension/Info.plist +++ b/iosHyperskillApp/NotificationServiceExtension/Info.plist @@ -9,7 +9,7 @@ CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleVersion - 309 + 310 CFBundleShortVersionString 1.50 CFBundlePackageType diff --git a/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj b/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj index d23cfbb2d7..856ad4c8c9 100644 --- a/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj +++ b/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj @@ -4985,7 +4985,7 @@ buildSettings = { CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 309; + CURRENT_PROJECT_VERSION = 310; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = 3DWS674B2M; GENERATE_INFOPLIST_FILE = NO; @@ -5006,7 +5006,7 @@ buildSettings = { CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 309; + CURRENT_PROJECT_VERSION = 310; DEVELOPMENT_TEAM = 3DWS674B2M; GENERATE_INFOPLIST_FILE = NO; INFOPLIST_FILE = iosHyperskillAppUITests/Info.plist; @@ -5027,7 +5027,7 @@ BUNDLE_LOADER = "$(TEST_HOST)"; CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 309; + CURRENT_PROJECT_VERSION = 310; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = 3DWS674B2M; INFOPLIST_FILE = iosHyperskillAppTests/Info.plist; @@ -5048,7 +5048,7 @@ BUNDLE_LOADER = "$(TEST_HOST)"; CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 309; + CURRENT_PROJECT_VERSION = 310; DEVELOPMENT_TEAM = 3DWS674B2M; INFOPLIST_FILE = iosHyperskillAppTests/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; @@ -5069,7 +5069,7 @@ CODE_SIGN_ENTITLEMENTS = NotificationServiceExtension/NotificationServiceExtension.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 309; + CURRENT_PROJECT_VERSION = 310; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = 3DWS674B2M; INFOPLIST_FILE = NotificationServiceExtension/Info.plist; @@ -5097,7 +5097,7 @@ CODE_SIGN_ENTITLEMENTS = NotificationServiceExtension/NotificationServiceExtension.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 309; + CURRENT_PROJECT_VERSION = 310; DEVELOPMENT_TEAM = 3DWS674B2M; INFOPLIST_FILE = NotificationServiceExtension/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; @@ -5242,7 +5242,7 @@ CODE_SIGN_ENTITLEMENTS = iosHyperskillApp/iosHyperskillApp.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 309; + CURRENT_PROJECT_VERSION = 310; DEVELOPMENT_ASSET_PATHS = "\"iosHyperskillApp/Preview Content\""; DEVELOPMENT_TEAM = 3DWS674B2M; ENABLE_PREVIEWS = YES; @@ -5278,7 +5278,7 @@ CODE_SIGN_ENTITLEMENTS = iosHyperskillApp/iosHyperskillApp.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 309; + CURRENT_PROJECT_VERSION = 310; DEVELOPMENT_ASSET_PATHS = "\"iosHyperskillApp/Preview Content\""; DEVELOPMENT_TEAM = 3DWS674B2M; ENABLE_PREVIEWS = YES; diff --git a/iosHyperskillApp/iosHyperskillApp/Info.plist b/iosHyperskillApp/iosHyperskillApp/Info.plist index 8e831df9c0..1f8b6fb67e 100644 --- a/iosHyperskillApp/iosHyperskillApp/Info.plist +++ b/iosHyperskillApp/iosHyperskillApp/Info.plist @@ -36,7 +36,7 @@ CFBundleVersion - 309 + 310 FirebaseAppDelegateProxyEnabled FirebaseMessagingAutoInitEnabled diff --git a/iosHyperskillApp/iosHyperskillAppTests/Info.plist b/iosHyperskillApp/iosHyperskillAppTests/Info.plist index 204f070231..6b2846e190 100644 --- a/iosHyperskillApp/iosHyperskillAppTests/Info.plist +++ b/iosHyperskillApp/iosHyperskillAppTests/Info.plist @@ -15,6 +15,6 @@ CFBundleShortVersionString 1.50 CFBundleVersion - 309 + 310 diff --git a/iosHyperskillApp/iosHyperskillAppUITests/Info.plist b/iosHyperskillApp/iosHyperskillAppUITests/Info.plist index 387dc0a57a..d1b9cdc8ec 100644 --- a/iosHyperskillApp/iosHyperskillAppUITests/Info.plist +++ b/iosHyperskillApp/iosHyperskillAppUITests/Info.plist @@ -15,6 +15,6 @@ CFBundleShortVersionString 1.50 CFBundleVersion - 309 + 310 From 8a90cb7c58a90e1dba8b9301102400e3ceecb352 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 2 Feb 2024 10:21:27 +0800 Subject: [PATCH 017/104] GitHub Actions: Bump gradle/wrapper-validation-action (#870) Bumps [gradle/wrapper-validation-action](https://github.com/gradle/wrapper-validation-action) from 1.1.0 to 2.0.0. - [Release notes](https://github.com/gradle/wrapper-validation-action/releases) - [Commits](https://github.com/gradle/wrapper-validation-action/compare/v1.1.0...v2.0.0) --- updated-dependencies: - dependency-name: gradle/wrapper-validation-action dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/android_beta_deployment.yml | 2 +- .github/workflows/android_release_deployment.yml | 2 +- .github/workflows/ci.yml | 2 +- .github/workflows/ios_beta_deployment.yml | 2 +- .github/workflows/ios_release_deployment.yml | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/android_beta_deployment.yml b/.github/workflows/android_beta_deployment.yml index 1a95ffdaf1..791fb5d1dd 100644 --- a/.github/workflows/android_beta_deployment.yml +++ b/.github/workflows/android_beta_deployment.yml @@ -28,7 +28,7 @@ jobs: uses: actions/checkout@v4.1.1 - name: Gradle Wrapper Validation - uses: gradle/wrapper-validation-action@v1.1.0 + uses: gradle/wrapper-validation-action@v2.0.0 # Build and submit to the Firebase App Distribution firebase-deployment: diff --git a/.github/workflows/android_release_deployment.yml b/.github/workflows/android_release_deployment.yml index af8c7fda26..1ac501455a 100644 --- a/.github/workflows/android_release_deployment.yml +++ b/.github/workflows/android_release_deployment.yml @@ -27,7 +27,7 @@ jobs: uses: actions/checkout@v4.1.1 - name: Gradle Wrapper Validation - uses: gradle/wrapper-validation-action@v1.1.0 + uses: gradle/wrapper-validation-action@v2.0.0 # Build and submit to the Google Play deployment: diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d7b386b40a..038524213b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -44,7 +44,7 @@ jobs: uses: actions/checkout@v4.1.1 - name: Gradle Wrapper Validation - uses: gradle/wrapper-validation-action@v1.1.0 + uses: gradle/wrapper-validation-action@v2.0.0 files-changed: name: Detect changes diff --git a/.github/workflows/ios_beta_deployment.yml b/.github/workflows/ios_beta_deployment.yml index dce27be79f..8b833d9100 100644 --- a/.github/workflows/ios_beta_deployment.yml +++ b/.github/workflows/ios_beta_deployment.yml @@ -28,7 +28,7 @@ jobs: uses: actions/checkout@v4.1.1 - name: Gradle Wrapper Validation - uses: gradle/wrapper-validation-action@v1.1.0 + uses: gradle/wrapper-validation-action@v2.0.0 # Build, archive for ad-hoc and submit to Firebase App Distribution deployment: diff --git a/.github/workflows/ios_release_deployment.yml b/.github/workflows/ios_release_deployment.yml index 11a1a61112..1c71256737 100644 --- a/.github/workflows/ios_release_deployment.yml +++ b/.github/workflows/ios_release_deployment.yml @@ -24,7 +24,7 @@ jobs: uses: actions/checkout@v4.1.1 - name: Gradle Wrapper Validation - uses: gradle/wrapper-validation-action@v1.1.0 + uses: gradle/wrapper-validation-action@v2.0.0 # Build, archive for app-store and submit to App Store Connect deployment: From ccd83ce0062f1e18b0f6aee08a79cdfa878aee4f Mon Sep 17 00:00:00 2001 From: github-actions Date: Fri, 2 Feb 2024 02:22:20 +0000 Subject: [PATCH 018/104] Bump build number --- gradle/app.versions.toml | 2 +- .../NotificationServiceExtension/Info.plist | 2 +- .../iosHyperskillApp.xcodeproj/project.pbxproj | 16 ++++++++-------- iosHyperskillApp/iosHyperskillApp/Info.plist | 2 +- .../iosHyperskillAppTests/Info.plist | 2 +- .../iosHyperskillAppUITests/Info.plist | 2 +- 6 files changed, 13 insertions(+), 13 deletions(-) diff --git a/gradle/app.versions.toml b/gradle/app.versions.toml index 2636136490..294e88ad4c 100644 --- a/gradle/app.versions.toml +++ b/gradle/app.versions.toml @@ -3,4 +3,4 @@ minSdk = '24' targetSdk = '33' compileSdk = '33' versionName = '1.50' -versionCode = '308' \ No newline at end of file +versionCode = '309' \ No newline at end of file diff --git a/iosHyperskillApp/NotificationServiceExtension/Info.plist b/iosHyperskillApp/NotificationServiceExtension/Info.plist index f73fbde338..33e17d9769 100644 --- a/iosHyperskillApp/NotificationServiceExtension/Info.plist +++ b/iosHyperskillApp/NotificationServiceExtension/Info.plist @@ -9,7 +9,7 @@ CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleVersion - 310 + 311 CFBundleShortVersionString 1.50 CFBundlePackageType diff --git a/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj b/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj index 856ad4c8c9..8ba13e2c08 100644 --- a/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj +++ b/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj @@ -4985,7 +4985,7 @@ buildSettings = { CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 310; + CURRENT_PROJECT_VERSION = 311; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = 3DWS674B2M; GENERATE_INFOPLIST_FILE = NO; @@ -5006,7 +5006,7 @@ buildSettings = { CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 310; + CURRENT_PROJECT_VERSION = 311; DEVELOPMENT_TEAM = 3DWS674B2M; GENERATE_INFOPLIST_FILE = NO; INFOPLIST_FILE = iosHyperskillAppUITests/Info.plist; @@ -5027,7 +5027,7 @@ BUNDLE_LOADER = "$(TEST_HOST)"; CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 310; + CURRENT_PROJECT_VERSION = 311; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = 3DWS674B2M; INFOPLIST_FILE = iosHyperskillAppTests/Info.plist; @@ -5048,7 +5048,7 @@ BUNDLE_LOADER = "$(TEST_HOST)"; CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 310; + CURRENT_PROJECT_VERSION = 311; DEVELOPMENT_TEAM = 3DWS674B2M; INFOPLIST_FILE = iosHyperskillAppTests/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; @@ -5069,7 +5069,7 @@ CODE_SIGN_ENTITLEMENTS = NotificationServiceExtension/NotificationServiceExtension.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 310; + CURRENT_PROJECT_VERSION = 311; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = 3DWS674B2M; INFOPLIST_FILE = NotificationServiceExtension/Info.plist; @@ -5097,7 +5097,7 @@ CODE_SIGN_ENTITLEMENTS = NotificationServiceExtension/NotificationServiceExtension.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 310; + CURRENT_PROJECT_VERSION = 311; DEVELOPMENT_TEAM = 3DWS674B2M; INFOPLIST_FILE = NotificationServiceExtension/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; @@ -5242,7 +5242,7 @@ CODE_SIGN_ENTITLEMENTS = iosHyperskillApp/iosHyperskillApp.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 310; + CURRENT_PROJECT_VERSION = 311; DEVELOPMENT_ASSET_PATHS = "\"iosHyperskillApp/Preview Content\""; DEVELOPMENT_TEAM = 3DWS674B2M; ENABLE_PREVIEWS = YES; @@ -5278,7 +5278,7 @@ CODE_SIGN_ENTITLEMENTS = iosHyperskillApp/iosHyperskillApp.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 310; + CURRENT_PROJECT_VERSION = 311; DEVELOPMENT_ASSET_PATHS = "\"iosHyperskillApp/Preview Content\""; DEVELOPMENT_TEAM = 3DWS674B2M; ENABLE_PREVIEWS = YES; diff --git a/iosHyperskillApp/iosHyperskillApp/Info.plist b/iosHyperskillApp/iosHyperskillApp/Info.plist index 1f8b6fb67e..19ebe945df 100644 --- a/iosHyperskillApp/iosHyperskillApp/Info.plist +++ b/iosHyperskillApp/iosHyperskillApp/Info.plist @@ -36,7 +36,7 @@ CFBundleVersion - 310 + 311 FirebaseAppDelegateProxyEnabled FirebaseMessagingAutoInitEnabled diff --git a/iosHyperskillApp/iosHyperskillAppTests/Info.plist b/iosHyperskillApp/iosHyperskillAppTests/Info.plist index 6b2846e190..3875459c9f 100644 --- a/iosHyperskillApp/iosHyperskillAppTests/Info.plist +++ b/iosHyperskillApp/iosHyperskillAppTests/Info.plist @@ -15,6 +15,6 @@ CFBundleShortVersionString 1.50 CFBundleVersion - 310 + 311 diff --git a/iosHyperskillApp/iosHyperskillAppUITests/Info.plist b/iosHyperskillApp/iosHyperskillAppUITests/Info.plist index d1b9cdc8ec..df037faa50 100644 --- a/iosHyperskillApp/iosHyperskillAppUITests/Info.plist +++ b/iosHyperskillApp/iosHyperskillAppUITests/Info.plist @@ -15,6 +15,6 @@ CFBundleShortVersionString 1.50 CFBundleVersion - 310 + 311 From 94f02f65c11a9ca6fabc87499d405a2df962f575 Mon Sep 17 00:00:00 2001 From: Ivan Magda Date: Fri, 2 Feb 2024 19:20:36 +0800 Subject: [PATCH 019/104] GitHub Actions: Deploy Android manually to Firebase App Distribution (#871) --- .editorconfig | 5 ++- .github/workflows/android_beta_deployment.yml | 32 +--------------- .../workflows/android_deploy_to_firebase.yml | 38 +++++++++++++++++++ .../android_deploy_to_firebase_manually.yml | 26 +++++++++++++ 4 files changed, 69 insertions(+), 32 deletions(-) create mode 100644 .github/workflows/android_deploy_to_firebase.yml create mode 100644 .github/workflows/android_deploy_to_firebase_manually.yml diff --git a/.editorconfig b/.editorconfig index 28612472b7..2500ae9f34 100644 --- a/.editorconfig +++ b/.editorconfig @@ -98,4 +98,7 @@ ij_kotlin_variable_annotation_wrap = off ij_kotlin_while_on_new_line = false ij_kotlin_wrap_elvis_expressions = 1 ij_kotlin_wrap_expression_body_functions = 1 -ij_kotlin_wrap_first_method_in_call_chain = false \ No newline at end of file +ij_kotlin_wrap_first_method_in_call_chain = false + +[*.yml] +indent_size = 2 \ No newline at end of file diff --git a/.github/workflows/android_beta_deployment.yml b/.github/workflows/android_beta_deployment.yml index 791fb5d1dd..8767e25b76 100644 --- a/.github/workflows/android_beta_deployment.yml +++ b/.github/workflows/android_beta_deployment.yml @@ -34,37 +34,7 @@ jobs: firebase-deployment: name: Deploy to Firebase App Distribution needs: gradle-wrapper-validation - runs-on: ubuntu-22.04 - environment: android_production - timeout-minutes: 60 - - steps: - - name: Checkout - uses: actions/checkout@v4.1.1 - - - name: Setup CI - id: setup - uses: ./.github/actions/setup-android - with: - git-crypt-key: ${{ secrets.GIT_CRYPT_KEY }} - release-keystore-content: ${{ secrets.HYPERSKILL_RELEASE_KEYSTORE_CONTENT }} - setup-ruby: true - - - name: Submit a new Beta Build to Firebase App Distribution - working-directory: "./androidHyperskillApp" - run: | - bundle exec fastlane beta \ - firebase_app_id:"${{ secrets.FIREBASE_APP_ID }}" \ - firebase_cli_token:"${{ secrets.FIREBASE_TOKEN }}" - env: - HYPERSKILL_IS_INTERNAL_TESTING: true - HYPERSKILL_KEYSTORE_PATH: ${{ steps.setup.outputs.release-keystore-path }} - HYPERSKILL_RELEASE_STORE_PASSWORD: ${{ secrets.HYPERSKILL_RELEASE_STORE_PASSWORD }} - HYPERSKILL_RELEASE_KEY_ALIAS: ${{ secrets.HYPERSKILL_RELEASE_KEY_ALIAS }} - HYPERSKILL_RELEASE_KEY_PASSWORD: ${{ secrets.HYPERSKILL_RELEASE_KEY_PASSWORD }} - IS_GIT_CRYPT_UNLOCKED: ${{ steps.setup.outputs.is-git-crypt-unlocked }} - GITHUB_USER: ${{ github.actor }} - GITHUB_PERSONAL_ACCESS_TOKEN: ${{ secrets.GITHUB_TOKEN }} + uses: ./.github/workflows/android_deploy_to_firebase.yml # Build and submit to the Google Play google-play-deployment: diff --git a/.github/workflows/android_deploy_to_firebase.yml b/.github/workflows/android_deploy_to_firebase.yml new file mode 100644 index 0000000000..408aafa1bf --- /dev/null +++ b/.github/workflows/android_deploy_to_firebase.yml @@ -0,0 +1,38 @@ +name: Deploy to Firebase App Distribution + +on: + workflow_call: + +jobs: + deploy: + runs-on: ubuntu-22.04 + environment: android_production + timeout-minutes: 30 + + steps: + - name: Checkout + uses: actions/checkout@v4.1.1 + + - name: Setup CI + id: setup + uses: ./.github/actions/setup-android + with: + git-crypt-key: ${{ secrets.GIT_CRYPT_KEY }} + release-keystore-content: ${{ secrets.HYPERSKILL_RELEASE_KEYSTORE_CONTENT }} + setup-ruby: true + + - name: Submit a new Beta Build to Firebase App Distribution + working-directory: "./androidHyperskillApp" + run: | + bundle exec fastlane beta \ + firebase_app_id:"${{ secrets.FIREBASE_APP_ID }}" \ + firebase_cli_token:"${{ secrets.FIREBASE_TOKEN }}" + env: + HYPERSKILL_IS_INTERNAL_TESTING: true + HYPERSKILL_KEYSTORE_PATH: ${{ steps.setup.outputs.release-keystore-path }} + HYPERSKILL_RELEASE_STORE_PASSWORD: ${{ secrets.HYPERSKILL_RELEASE_STORE_PASSWORD }} + HYPERSKILL_RELEASE_KEY_ALIAS: ${{ secrets.HYPERSKILL_RELEASE_KEY_ALIAS }} + HYPERSKILL_RELEASE_KEY_PASSWORD: ${{ secrets.HYPERSKILL_RELEASE_KEY_PASSWORD }} + IS_GIT_CRYPT_UNLOCKED: ${{ steps.setup.outputs.is-git-crypt-unlocked }} + GITHUB_USER: ${{ github.actor }} + GITHUB_PERSONAL_ACCESS_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/android_deploy_to_firebase_manually.yml b/.github/workflows/android_deploy_to_firebase_manually.yml new file mode 100644 index 0000000000..4adb05c269 --- /dev/null +++ b/.github/workflows/android_deploy_to_firebase_manually.yml @@ -0,0 +1,26 @@ +name: Android Deploy Manually to Firebase App Distribution + +on: + workflow_dispatch: + +concurrency: + group: ${{ github.workflow }}-${{ github.ref_name }} + cancel-in-progress: true + +jobs: + # Run Gradle Wrapper Validation Action to verify the wrapper's checksum + gradle-wrapper-validation: + name: Gradle Wrapper Validation + runs-on: ubuntu-22.04 + steps: + - name: Checkout + uses: actions/checkout@v4.1.1 + + - name: Gradle Wrapper Validation + uses: gradle/wrapper-validation-action@v2.0.0 + + # Build and submit to the Firebase App Distribution + firebase-deployment: + name: Deploy to Firebase App Distribution + needs: gradle-wrapper-validation + uses: ./.github/workflows/android_deploy_to_firebase.yml From 88ce671e8ac165eab73b07150f68f46bdce0004f Mon Sep 17 00:00:00 2001 From: github-actions Date: Fri, 2 Feb 2024 11:21:34 +0000 Subject: [PATCH 020/104] Bump build number --- gradle/app.versions.toml | 2 +- .../NotificationServiceExtension/Info.plist | 2 +- .../iosHyperskillApp.xcodeproj/project.pbxproj | 16 ++++++++-------- iosHyperskillApp/iosHyperskillApp/Info.plist | 2 +- .../iosHyperskillAppTests/Info.plist | 2 +- .../iosHyperskillAppUITests/Info.plist | 2 +- 6 files changed, 13 insertions(+), 13 deletions(-) diff --git a/gradle/app.versions.toml b/gradle/app.versions.toml index 294e88ad4c..e2c8d14132 100644 --- a/gradle/app.versions.toml +++ b/gradle/app.versions.toml @@ -3,4 +3,4 @@ minSdk = '24' targetSdk = '33' compileSdk = '33' versionName = '1.50' -versionCode = '309' \ No newline at end of file +versionCode = '310' \ No newline at end of file diff --git a/iosHyperskillApp/NotificationServiceExtension/Info.plist b/iosHyperskillApp/NotificationServiceExtension/Info.plist index 33e17d9769..1fde851dd6 100644 --- a/iosHyperskillApp/NotificationServiceExtension/Info.plist +++ b/iosHyperskillApp/NotificationServiceExtension/Info.plist @@ -9,7 +9,7 @@ CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleVersion - 311 + 312 CFBundleShortVersionString 1.50 CFBundlePackageType diff --git a/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj b/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj index 8ba13e2c08..cbd58840eb 100644 --- a/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj +++ b/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj @@ -4985,7 +4985,7 @@ buildSettings = { CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 311; + CURRENT_PROJECT_VERSION = 312; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = 3DWS674B2M; GENERATE_INFOPLIST_FILE = NO; @@ -5006,7 +5006,7 @@ buildSettings = { CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 311; + CURRENT_PROJECT_VERSION = 312; DEVELOPMENT_TEAM = 3DWS674B2M; GENERATE_INFOPLIST_FILE = NO; INFOPLIST_FILE = iosHyperskillAppUITests/Info.plist; @@ -5027,7 +5027,7 @@ BUNDLE_LOADER = "$(TEST_HOST)"; CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 311; + CURRENT_PROJECT_VERSION = 312; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = 3DWS674B2M; INFOPLIST_FILE = iosHyperskillAppTests/Info.plist; @@ -5048,7 +5048,7 @@ BUNDLE_LOADER = "$(TEST_HOST)"; CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 311; + CURRENT_PROJECT_VERSION = 312; DEVELOPMENT_TEAM = 3DWS674B2M; INFOPLIST_FILE = iosHyperskillAppTests/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; @@ -5069,7 +5069,7 @@ CODE_SIGN_ENTITLEMENTS = NotificationServiceExtension/NotificationServiceExtension.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 311; + CURRENT_PROJECT_VERSION = 312; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = 3DWS674B2M; INFOPLIST_FILE = NotificationServiceExtension/Info.plist; @@ -5097,7 +5097,7 @@ CODE_SIGN_ENTITLEMENTS = NotificationServiceExtension/NotificationServiceExtension.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 311; + CURRENT_PROJECT_VERSION = 312; DEVELOPMENT_TEAM = 3DWS674B2M; INFOPLIST_FILE = NotificationServiceExtension/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; @@ -5242,7 +5242,7 @@ CODE_SIGN_ENTITLEMENTS = iosHyperskillApp/iosHyperskillApp.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 311; + CURRENT_PROJECT_VERSION = 312; DEVELOPMENT_ASSET_PATHS = "\"iosHyperskillApp/Preview Content\""; DEVELOPMENT_TEAM = 3DWS674B2M; ENABLE_PREVIEWS = YES; @@ -5278,7 +5278,7 @@ CODE_SIGN_ENTITLEMENTS = iosHyperskillApp/iosHyperskillApp.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 311; + CURRENT_PROJECT_VERSION = 312; DEVELOPMENT_ASSET_PATHS = "\"iosHyperskillApp/Preview Content\""; DEVELOPMENT_TEAM = 3DWS674B2M; ENABLE_PREVIEWS = YES; diff --git a/iosHyperskillApp/iosHyperskillApp/Info.plist b/iosHyperskillApp/iosHyperskillApp/Info.plist index 19ebe945df..8143e6a210 100644 --- a/iosHyperskillApp/iosHyperskillApp/Info.plist +++ b/iosHyperskillApp/iosHyperskillApp/Info.plist @@ -36,7 +36,7 @@ CFBundleVersion - 311 + 312 FirebaseAppDelegateProxyEnabled FirebaseMessagingAutoInitEnabled diff --git a/iosHyperskillApp/iosHyperskillAppTests/Info.plist b/iosHyperskillApp/iosHyperskillAppTests/Info.plist index 3875459c9f..e9768115cd 100644 --- a/iosHyperskillApp/iosHyperskillAppTests/Info.plist +++ b/iosHyperskillApp/iosHyperskillAppTests/Info.plist @@ -15,6 +15,6 @@ CFBundleShortVersionString 1.50 CFBundleVersion - 311 + 312 diff --git a/iosHyperskillApp/iosHyperskillAppUITests/Info.plist b/iosHyperskillApp/iosHyperskillAppUITests/Info.plist index df037faa50..4229881a43 100644 --- a/iosHyperskillApp/iosHyperskillAppUITests/Info.plist +++ b/iosHyperskillApp/iosHyperskillAppUITests/Info.plist @@ -15,6 +15,6 @@ CFBundleShortVersionString 1.50 CFBundleVersion - 311 + 312 From e0f566f0eb89ad082a75fc74176d6a6bb45c27c6 Mon Sep 17 00:00:00 2001 From: Ivan Magda Date: Fri, 2 Feb 2024 20:04:16 +0800 Subject: [PATCH 021/104] GitHub Actions: Fix deploy Android manually to Firebase App Distribution (#872) --- .github/workflows/android_beta_deployment.yml | 1 + .github/workflows/android_deploy_to_firebase_manually.yml | 1 + 2 files changed, 2 insertions(+) diff --git a/.github/workflows/android_beta_deployment.yml b/.github/workflows/android_beta_deployment.yml index 8767e25b76..92bf14572f 100644 --- a/.github/workflows/android_beta_deployment.yml +++ b/.github/workflows/android_beta_deployment.yml @@ -35,6 +35,7 @@ jobs: name: Deploy to Firebase App Distribution needs: gradle-wrapper-validation uses: ./.github/workflows/android_deploy_to_firebase.yml + secrets: inherit # Build and submit to the Google Play google-play-deployment: diff --git a/.github/workflows/android_deploy_to_firebase_manually.yml b/.github/workflows/android_deploy_to_firebase_manually.yml index 4adb05c269..dbc0bf8e0d 100644 --- a/.github/workflows/android_deploy_to_firebase_manually.yml +++ b/.github/workflows/android_deploy_to_firebase_manually.yml @@ -24,3 +24,4 @@ jobs: name: Deploy to Firebase App Distribution needs: gradle-wrapper-validation uses: ./.github/workflows/android_deploy_to_firebase.yml + secrets: inherit From 8cdcfb70460be184c5491c8326350794d8ba9215 Mon Sep 17 00:00:00 2001 From: github-actions Date: Fri, 2 Feb 2024 12:05:08 +0000 Subject: [PATCH 022/104] Bump build number --- gradle/app.versions.toml | 2 +- .../NotificationServiceExtension/Info.plist | 2 +- .../iosHyperskillApp.xcodeproj/project.pbxproj | 16 ++++++++-------- iosHyperskillApp/iosHyperskillApp/Info.plist | 2 +- .../iosHyperskillAppTests/Info.plist | 2 +- .../iosHyperskillAppUITests/Info.plist | 2 +- 6 files changed, 13 insertions(+), 13 deletions(-) diff --git a/gradle/app.versions.toml b/gradle/app.versions.toml index e2c8d14132..1100580f88 100644 --- a/gradle/app.versions.toml +++ b/gradle/app.versions.toml @@ -3,4 +3,4 @@ minSdk = '24' targetSdk = '33' compileSdk = '33' versionName = '1.50' -versionCode = '310' \ No newline at end of file +versionCode = '311' \ No newline at end of file diff --git a/iosHyperskillApp/NotificationServiceExtension/Info.plist b/iosHyperskillApp/NotificationServiceExtension/Info.plist index 1fde851dd6..c6335e9928 100644 --- a/iosHyperskillApp/NotificationServiceExtension/Info.plist +++ b/iosHyperskillApp/NotificationServiceExtension/Info.plist @@ -9,7 +9,7 @@ CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleVersion - 312 + 313 CFBundleShortVersionString 1.50 CFBundlePackageType diff --git a/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj b/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj index cbd58840eb..26fcc0fcf3 100644 --- a/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj +++ b/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj @@ -4985,7 +4985,7 @@ buildSettings = { CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 312; + CURRENT_PROJECT_VERSION = 313; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = 3DWS674B2M; GENERATE_INFOPLIST_FILE = NO; @@ -5006,7 +5006,7 @@ buildSettings = { CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 312; + CURRENT_PROJECT_VERSION = 313; DEVELOPMENT_TEAM = 3DWS674B2M; GENERATE_INFOPLIST_FILE = NO; INFOPLIST_FILE = iosHyperskillAppUITests/Info.plist; @@ -5027,7 +5027,7 @@ BUNDLE_LOADER = "$(TEST_HOST)"; CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 312; + CURRENT_PROJECT_VERSION = 313; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = 3DWS674B2M; INFOPLIST_FILE = iosHyperskillAppTests/Info.plist; @@ -5048,7 +5048,7 @@ BUNDLE_LOADER = "$(TEST_HOST)"; CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 312; + CURRENT_PROJECT_VERSION = 313; DEVELOPMENT_TEAM = 3DWS674B2M; INFOPLIST_FILE = iosHyperskillAppTests/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; @@ -5069,7 +5069,7 @@ CODE_SIGN_ENTITLEMENTS = NotificationServiceExtension/NotificationServiceExtension.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 312; + CURRENT_PROJECT_VERSION = 313; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = 3DWS674B2M; INFOPLIST_FILE = NotificationServiceExtension/Info.plist; @@ -5097,7 +5097,7 @@ CODE_SIGN_ENTITLEMENTS = NotificationServiceExtension/NotificationServiceExtension.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 312; + CURRENT_PROJECT_VERSION = 313; DEVELOPMENT_TEAM = 3DWS674B2M; INFOPLIST_FILE = NotificationServiceExtension/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; @@ -5242,7 +5242,7 @@ CODE_SIGN_ENTITLEMENTS = iosHyperskillApp/iosHyperskillApp.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 312; + CURRENT_PROJECT_VERSION = 313; DEVELOPMENT_ASSET_PATHS = "\"iosHyperskillApp/Preview Content\""; DEVELOPMENT_TEAM = 3DWS674B2M; ENABLE_PREVIEWS = YES; @@ -5278,7 +5278,7 @@ CODE_SIGN_ENTITLEMENTS = iosHyperskillApp/iosHyperskillApp.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 312; + CURRENT_PROJECT_VERSION = 313; DEVELOPMENT_ASSET_PATHS = "\"iosHyperskillApp/Preview Content\""; DEVELOPMENT_TEAM = 3DWS674B2M; ENABLE_PREVIEWS = YES; diff --git a/iosHyperskillApp/iosHyperskillApp/Info.plist b/iosHyperskillApp/iosHyperskillApp/Info.plist index 8143e6a210..9b780f9948 100644 --- a/iosHyperskillApp/iosHyperskillApp/Info.plist +++ b/iosHyperskillApp/iosHyperskillApp/Info.plist @@ -36,7 +36,7 @@ CFBundleVersion - 312 + 313 FirebaseAppDelegateProxyEnabled FirebaseMessagingAutoInitEnabled diff --git a/iosHyperskillApp/iosHyperskillAppTests/Info.plist b/iosHyperskillApp/iosHyperskillAppTests/Info.plist index e9768115cd..8609e7a198 100644 --- a/iosHyperskillApp/iosHyperskillAppTests/Info.plist +++ b/iosHyperskillApp/iosHyperskillAppTests/Info.plist @@ -15,6 +15,6 @@ CFBundleShortVersionString 1.50 CFBundleVersion - 312 + 313 diff --git a/iosHyperskillApp/iosHyperskillAppUITests/Info.plist b/iosHyperskillApp/iosHyperskillAppUITests/Info.plist index 4229881a43..37ddbadc22 100644 --- a/iosHyperskillApp/iosHyperskillAppUITests/Info.plist +++ b/iosHyperskillApp/iosHyperskillAppUITests/Info.plist @@ -15,6 +15,6 @@ CFBundleShortVersionString 1.50 CFBundleVersion - 312 + 313 From 5674a75cc5e535d6a900b315c9b8a51d1d1b77ba Mon Sep 17 00:00:00 2001 From: Ivan Magda Date: Fri, 2 Feb 2024 22:10:54 +0800 Subject: [PATCH 023/104] Bump build number --- gradle/app.versions.toml | 2 +- .../NotificationServiceExtension/Info.plist | 2 +- .../iosHyperskillApp.xcodeproj/project.pbxproj | 16 ++++++++-------- iosHyperskillApp/iosHyperskillApp/Info.plist | 2 +- .../iosHyperskillAppTests/Info.plist | 2 +- .../iosHyperskillAppUITests/Info.plist | 2 +- 6 files changed, 13 insertions(+), 13 deletions(-) diff --git a/gradle/app.versions.toml b/gradle/app.versions.toml index cfdbadc56f..4236efc3a8 100644 --- a/gradle/app.versions.toml +++ b/gradle/app.versions.toml @@ -3,4 +3,4 @@ minSdk = '24' targetSdk = '33' compileSdk = '33' versionName = '1.50' -versionCode = '312' \ No newline at end of file +versionCode = '313' \ No newline at end of file diff --git a/iosHyperskillApp/NotificationServiceExtension/Info.plist b/iosHyperskillApp/NotificationServiceExtension/Info.plist index c6335e9928..a9c45af5e9 100644 --- a/iosHyperskillApp/NotificationServiceExtension/Info.plist +++ b/iosHyperskillApp/NotificationServiceExtension/Info.plist @@ -9,7 +9,7 @@ CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleVersion - 313 + 314 CFBundleShortVersionString 1.50 CFBundlePackageType diff --git a/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj b/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj index 26fcc0fcf3..3a1db3d933 100644 --- a/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj +++ b/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj @@ -4985,7 +4985,7 @@ buildSettings = { CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 313; + CURRENT_PROJECT_VERSION = 314; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = 3DWS674B2M; GENERATE_INFOPLIST_FILE = NO; @@ -5006,7 +5006,7 @@ buildSettings = { CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 313; + CURRENT_PROJECT_VERSION = 314; DEVELOPMENT_TEAM = 3DWS674B2M; GENERATE_INFOPLIST_FILE = NO; INFOPLIST_FILE = iosHyperskillAppUITests/Info.plist; @@ -5027,7 +5027,7 @@ BUNDLE_LOADER = "$(TEST_HOST)"; CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 313; + CURRENT_PROJECT_VERSION = 314; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = 3DWS674B2M; INFOPLIST_FILE = iosHyperskillAppTests/Info.plist; @@ -5048,7 +5048,7 @@ BUNDLE_LOADER = "$(TEST_HOST)"; CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 313; + CURRENT_PROJECT_VERSION = 314; DEVELOPMENT_TEAM = 3DWS674B2M; INFOPLIST_FILE = iosHyperskillAppTests/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; @@ -5069,7 +5069,7 @@ CODE_SIGN_ENTITLEMENTS = NotificationServiceExtension/NotificationServiceExtension.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 313; + CURRENT_PROJECT_VERSION = 314; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = 3DWS674B2M; INFOPLIST_FILE = NotificationServiceExtension/Info.plist; @@ -5097,7 +5097,7 @@ CODE_SIGN_ENTITLEMENTS = NotificationServiceExtension/NotificationServiceExtension.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 313; + CURRENT_PROJECT_VERSION = 314; DEVELOPMENT_TEAM = 3DWS674B2M; INFOPLIST_FILE = NotificationServiceExtension/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; @@ -5242,7 +5242,7 @@ CODE_SIGN_ENTITLEMENTS = iosHyperskillApp/iosHyperskillApp.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 313; + CURRENT_PROJECT_VERSION = 314; DEVELOPMENT_ASSET_PATHS = "\"iosHyperskillApp/Preview Content\""; DEVELOPMENT_TEAM = 3DWS674B2M; ENABLE_PREVIEWS = YES; @@ -5278,7 +5278,7 @@ CODE_SIGN_ENTITLEMENTS = iosHyperskillApp/iosHyperskillApp.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 313; + CURRENT_PROJECT_VERSION = 314; DEVELOPMENT_ASSET_PATHS = "\"iosHyperskillApp/Preview Content\""; DEVELOPMENT_TEAM = 3DWS674B2M; ENABLE_PREVIEWS = YES; diff --git a/iosHyperskillApp/iosHyperskillApp/Info.plist b/iosHyperskillApp/iosHyperskillApp/Info.plist index 9b780f9948..bcb55d32ee 100644 --- a/iosHyperskillApp/iosHyperskillApp/Info.plist +++ b/iosHyperskillApp/iosHyperskillApp/Info.plist @@ -36,7 +36,7 @@ CFBundleVersion - 313 + 314 FirebaseAppDelegateProxyEnabled FirebaseMessagingAutoInitEnabled diff --git a/iosHyperskillApp/iosHyperskillAppTests/Info.plist b/iosHyperskillApp/iosHyperskillAppTests/Info.plist index 8609e7a198..4ec6aee5e7 100644 --- a/iosHyperskillApp/iosHyperskillAppTests/Info.plist +++ b/iosHyperskillApp/iosHyperskillAppTests/Info.plist @@ -15,6 +15,6 @@ CFBundleShortVersionString 1.50 CFBundleVersion - 313 + 314 diff --git a/iosHyperskillApp/iosHyperskillAppUITests/Info.plist b/iosHyperskillApp/iosHyperskillAppUITests/Info.plist index 37ddbadc22..6023249d04 100644 --- a/iosHyperskillApp/iosHyperskillAppUITests/Info.plist +++ b/iosHyperskillApp/iosHyperskillAppUITests/Info.plist @@ -15,6 +15,6 @@ CFBundleShortVersionString 1.50 CFBundleVersion - 313 + 314 From fa307926107bcf4ec3fcc102eafda609c9344a4a Mon Sep 17 00:00:00 2001 From: Ivan Magda Date: Mon, 5 Feb 2024 11:05:40 +0800 Subject: [PATCH 024/104] GitHub Actions: Bump macOS runner from 13 to 14 (#874) * Upgrade to macOS 14 runner * Bump ruby/setup-ruby from 1.170.0 to 1.171.0 --- .github/actions/setup-android/action.yml | 2 +- .github/actions/setup-ios/action.yml | 2 +- .github/workflows/automerge_into_release.yml | 4 ++-- .github/workflows/build_caches.yml | 2 +- .github/workflows/ci.yml | 6 +++--- .github/workflows/ios_beta_deployment.yml | 2 +- .github/workflows/ios_release_deployment.yml | 2 +- .github/workflows/ios_unit_testing.yml | 2 +- 8 files changed, 11 insertions(+), 11 deletions(-) diff --git a/.github/actions/setup-android/action.yml b/.github/actions/setup-android/action.yml index 22c08999b7..ae07aa057d 100644 --- a/.github/actions/setup-android/action.yml +++ b/.github/actions/setup-android/action.yml @@ -62,7 +62,7 @@ runs: - name: Setup Ruby if: ${{ inputs.setup-ruby == 'true' }} - uses: ruby/setup-ruby@v1.170.0 + uses: ruby/setup-ruby@v1.171.0 with: ruby-version: '3.3.0' bundler-cache: true diff --git a/.github/actions/setup-ios/action.yml b/.github/actions/setup-ios/action.yml index 40c29cb820..c33b951bdd 100644 --- a/.github/actions/setup-ios/action.yml +++ b/.github/actions/setup-ios/action.yml @@ -49,7 +49,7 @@ runs: shell: bash - name: Setup Ruby - uses: ruby/setup-ruby@v1.170.0 + uses: ruby/setup-ruby@v1.171.0 with: ruby-version: '3.3.0' bundler-cache: true diff --git a/.github/workflows/automerge_into_release.yml b/.github/workflows/automerge_into_release.yml index e875e6c1be..b63f9dfd0b 100644 --- a/.github/workflows/automerge_into_release.yml +++ b/.github/workflows/automerge_into_release.yml @@ -27,7 +27,7 @@ jobs: automerge: needs: files-changed name: Automerge - runs-on: macos-13 + runs-on: macos-14 steps: - name: Checkout @@ -37,7 +37,7 @@ jobs: token: ${{ secrets.GH_PAT }} - name: Setup Ruby - uses: ruby/setup-ruby@v1.170.0 + uses: ruby/setup-ruby@v1.171.0 with: ruby-version: "3.3.0" bundler-cache: true diff --git a/.github/workflows/build_caches.yml b/.github/workflows/build_caches.yml index a35c69db8f..fec50846a1 100644 --- a/.github/workflows/build_caches.yml +++ b/.github/workflows/build_caches.yml @@ -50,7 +50,7 @@ jobs: needs: files-changed if: ${{ github.event_name == 'workflow_dispatch' || needs.files-changed.outputs.ios == 'true' }} name: Build iOS Caches - runs-on: macos-13 + runs-on: macos-14 timeout-minutes: 60 steps: diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 038524213b..60ae02680e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -79,7 +79,7 @@ jobs: needs: files-changed if: ${{ needs.files-changed.outputs.ios == 'true' || needs.files-changed.outputs.shared == 'true' }} name: Run SwiftLint - runs-on: macos-13 + runs-on: macos-14 timeout-minutes: 10 steps: @@ -87,7 +87,7 @@ jobs: uses: actions/checkout@v4.1.1 - name: Setup Ruby - uses: ruby/setup-ruby@v1.170.0 + uses: ruby/setup-ruby@v1.171.0 with: ruby-version: '3.3.0' bundler-cache: true @@ -162,7 +162,7 @@ jobs: build-ios: needs: swiftlint name: Build iOS - runs-on: macos-13 + runs-on: macos-14 timeout-minutes: 30 steps: diff --git a/.github/workflows/ios_beta_deployment.yml b/.github/workflows/ios_beta_deployment.yml index 8b833d9100..ae6381d8f1 100644 --- a/.github/workflows/ios_beta_deployment.yml +++ b/.github/workflows/ios_beta_deployment.yml @@ -34,7 +34,7 @@ jobs: deployment: name: iOS Beta Deployment needs: gradle-wrapper-validation - runs-on: macos-13 + runs-on: macos-14 environment: ios_production timeout-minutes: 60 diff --git a/.github/workflows/ios_release_deployment.yml b/.github/workflows/ios_release_deployment.yml index 1c71256737..041911dca6 100644 --- a/.github/workflows/ios_release_deployment.yml +++ b/.github/workflows/ios_release_deployment.yml @@ -30,7 +30,7 @@ jobs: deployment: name: iOS Release Deployment needs: gradle-wrapper-validation - runs-on: macos-13 + runs-on: macos-14 environment: ios_production timeout-minutes: 60 diff --git a/.github/workflows/ios_unit_testing.yml b/.github/workflows/ios_unit_testing.yml index d64c2d5bbe..5823a4e92e 100644 --- a/.github/workflows/ios_unit_testing.yml +++ b/.github/workflows/ios_unit_testing.yml @@ -18,7 +18,7 @@ jobs: test: if: ${{ vars.IS_IOS_UNIT_TESTING_ENABLED == 'true' }} name: Run iOS unit tests - runs-on: macos-13 + runs-on: macos-14 timeout-minutes: 60 steps: From 7b6ac852601059e4a211e6984743e338c4fec437 Mon Sep 17 00:00:00 2001 From: Ivan Magda Date: Mon, 5 Feb 2024 11:11:40 +0800 Subject: [PATCH 025/104] iOS: Bump build number --- .../NotificationServiceExtension/Info.plist | 2 +- .../iosHyperskillApp.xcodeproj/project.pbxproj | 16 ++++++++-------- iosHyperskillApp/iosHyperskillApp/Info.plist | 2 +- .../iosHyperskillAppTests/Info.plist | 2 +- .../iosHyperskillAppUITests/Info.plist | 2 +- 5 files changed, 12 insertions(+), 12 deletions(-) diff --git a/iosHyperskillApp/NotificationServiceExtension/Info.plist b/iosHyperskillApp/NotificationServiceExtension/Info.plist index a9c45af5e9..b724b7aa06 100644 --- a/iosHyperskillApp/NotificationServiceExtension/Info.plist +++ b/iosHyperskillApp/NotificationServiceExtension/Info.plist @@ -9,7 +9,7 @@ CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleVersion - 314 + 315 CFBundleShortVersionString 1.50 CFBundlePackageType diff --git a/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj b/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj index 3a1db3d933..1099535d3e 100644 --- a/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj +++ b/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj @@ -4985,7 +4985,7 @@ buildSettings = { CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 314; + CURRENT_PROJECT_VERSION = 315; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = 3DWS674B2M; GENERATE_INFOPLIST_FILE = NO; @@ -5006,7 +5006,7 @@ buildSettings = { CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 314; + CURRENT_PROJECT_VERSION = 315; DEVELOPMENT_TEAM = 3DWS674B2M; GENERATE_INFOPLIST_FILE = NO; INFOPLIST_FILE = iosHyperskillAppUITests/Info.plist; @@ -5027,7 +5027,7 @@ BUNDLE_LOADER = "$(TEST_HOST)"; CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 314; + CURRENT_PROJECT_VERSION = 315; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = 3DWS674B2M; INFOPLIST_FILE = iosHyperskillAppTests/Info.plist; @@ -5048,7 +5048,7 @@ BUNDLE_LOADER = "$(TEST_HOST)"; CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 314; + CURRENT_PROJECT_VERSION = 315; DEVELOPMENT_TEAM = 3DWS674B2M; INFOPLIST_FILE = iosHyperskillAppTests/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; @@ -5069,7 +5069,7 @@ CODE_SIGN_ENTITLEMENTS = NotificationServiceExtension/NotificationServiceExtension.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 314; + CURRENT_PROJECT_VERSION = 315; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = 3DWS674B2M; INFOPLIST_FILE = NotificationServiceExtension/Info.plist; @@ -5097,7 +5097,7 @@ CODE_SIGN_ENTITLEMENTS = NotificationServiceExtension/NotificationServiceExtension.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 314; + CURRENT_PROJECT_VERSION = 315; DEVELOPMENT_TEAM = 3DWS674B2M; INFOPLIST_FILE = NotificationServiceExtension/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; @@ -5242,7 +5242,7 @@ CODE_SIGN_ENTITLEMENTS = iosHyperskillApp/iosHyperskillApp.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 314; + CURRENT_PROJECT_VERSION = 315; DEVELOPMENT_ASSET_PATHS = "\"iosHyperskillApp/Preview Content\""; DEVELOPMENT_TEAM = 3DWS674B2M; ENABLE_PREVIEWS = YES; @@ -5278,7 +5278,7 @@ CODE_SIGN_ENTITLEMENTS = iosHyperskillApp/iosHyperskillApp.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 314; + CURRENT_PROJECT_VERSION = 315; DEVELOPMENT_ASSET_PATHS = "\"iosHyperskillApp/Preview Content\""; DEVELOPMENT_TEAM = 3DWS674B2M; ENABLE_PREVIEWS = YES; diff --git a/iosHyperskillApp/iosHyperskillApp/Info.plist b/iosHyperskillApp/iosHyperskillApp/Info.plist index bcb55d32ee..2c7d96cdd2 100644 --- a/iosHyperskillApp/iosHyperskillApp/Info.plist +++ b/iosHyperskillApp/iosHyperskillApp/Info.plist @@ -36,7 +36,7 @@ CFBundleVersion - 314 + 315 FirebaseAppDelegateProxyEnabled FirebaseMessagingAutoInitEnabled diff --git a/iosHyperskillApp/iosHyperskillAppTests/Info.plist b/iosHyperskillApp/iosHyperskillAppTests/Info.plist index 4ec6aee5e7..f48083864c 100644 --- a/iosHyperskillApp/iosHyperskillAppTests/Info.plist +++ b/iosHyperskillApp/iosHyperskillAppTests/Info.plist @@ -15,6 +15,6 @@ CFBundleShortVersionString 1.50 CFBundleVersion - 314 + 315 diff --git a/iosHyperskillApp/iosHyperskillAppUITests/Info.plist b/iosHyperskillApp/iosHyperskillAppUITests/Info.plist index 6023249d04..a6e2322db3 100644 --- a/iosHyperskillApp/iosHyperskillAppUITests/Info.plist +++ b/iosHyperskillApp/iosHyperskillAppUITests/Info.plist @@ -15,6 +15,6 @@ CFBundleShortVersionString 1.50 CFBundleVersion - 314 + 315 From 300031713fb760b4c3184ba82457eeecf943e48a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 7 Feb 2024 10:59:39 +0800 Subject: [PATCH 026/104] GitHub Actions: Bump actions/deploy-pages from 4.0.3 to 4.0.4 (#882) Bumps [actions/deploy-pages](https://github.com/actions/deploy-pages) from 4.0.3 to 4.0.4. - [Release notes](https://github.com/actions/deploy-pages/releases) - [Commits](https://github.com/actions/deploy-pages/compare/v4.0.3...v4.0.4) --- updated-dependencies: - dependency-name: actions/deploy-pages dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/gh_pages_analytics.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/gh_pages_analytics.yml b/.github/workflows/gh_pages_analytics.yml index 49987ca7b0..342470e986 100644 --- a/.github/workflows/gh_pages_analytics.yml +++ b/.github/workflows/gh_pages_analytics.yml @@ -69,6 +69,6 @@ jobs: steps: - name: Deploy to GitHub Pages id: deployment - uses: actions/deploy-pages@v4.0.3 + uses: actions/deploy-pages@v4.0.4 with: artifact_name: 'github-pages-analytics' \ No newline at end of file From bac297b6bdab2568872e6c2d8d03fcbcd2b2572d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 7 Feb 2024 11:04:37 +0800 Subject: [PATCH 027/104] GitHub Actions: Bump gradle/wrapper-validation-action (#883) Bumps [gradle/wrapper-validation-action](https://github.com/gradle/wrapper-validation-action) from 2.0.0 to 2.0.1. - [Release notes](https://github.com/gradle/wrapper-validation-action/releases) - [Commits](https://github.com/gradle/wrapper-validation-action/compare/v2.0.0...v2.0.1) --- updated-dependencies: - dependency-name: gradle/wrapper-validation-action dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/android_beta_deployment.yml | 2 +- .github/workflows/android_deploy_to_firebase_manually.yml | 2 +- .github/workflows/android_release_deployment.yml | 2 +- .github/workflows/ci.yml | 2 +- .github/workflows/ios_beta_deployment.yml | 2 +- .github/workflows/ios_release_deployment.yml | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/android_beta_deployment.yml b/.github/workflows/android_beta_deployment.yml index 92bf14572f..c069395311 100644 --- a/.github/workflows/android_beta_deployment.yml +++ b/.github/workflows/android_beta_deployment.yml @@ -28,7 +28,7 @@ jobs: uses: actions/checkout@v4.1.1 - name: Gradle Wrapper Validation - uses: gradle/wrapper-validation-action@v2.0.0 + uses: gradle/wrapper-validation-action@v2.0.1 # Build and submit to the Firebase App Distribution firebase-deployment: diff --git a/.github/workflows/android_deploy_to_firebase_manually.yml b/.github/workflows/android_deploy_to_firebase_manually.yml index dbc0bf8e0d..ba830a6f4a 100644 --- a/.github/workflows/android_deploy_to_firebase_manually.yml +++ b/.github/workflows/android_deploy_to_firebase_manually.yml @@ -17,7 +17,7 @@ jobs: uses: actions/checkout@v4.1.1 - name: Gradle Wrapper Validation - uses: gradle/wrapper-validation-action@v2.0.0 + uses: gradle/wrapper-validation-action@v2.0.1 # Build and submit to the Firebase App Distribution firebase-deployment: diff --git a/.github/workflows/android_release_deployment.yml b/.github/workflows/android_release_deployment.yml index 1ac501455a..0af2be86f3 100644 --- a/.github/workflows/android_release_deployment.yml +++ b/.github/workflows/android_release_deployment.yml @@ -27,7 +27,7 @@ jobs: uses: actions/checkout@v4.1.1 - name: Gradle Wrapper Validation - uses: gradle/wrapper-validation-action@v2.0.0 + uses: gradle/wrapper-validation-action@v2.0.1 # Build and submit to the Google Play deployment: diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 60ae02680e..97d102c360 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -44,7 +44,7 @@ jobs: uses: actions/checkout@v4.1.1 - name: Gradle Wrapper Validation - uses: gradle/wrapper-validation-action@v2.0.0 + uses: gradle/wrapper-validation-action@v2.0.1 files-changed: name: Detect changes diff --git a/.github/workflows/ios_beta_deployment.yml b/.github/workflows/ios_beta_deployment.yml index ae6381d8f1..fe01e31189 100644 --- a/.github/workflows/ios_beta_deployment.yml +++ b/.github/workflows/ios_beta_deployment.yml @@ -28,7 +28,7 @@ jobs: uses: actions/checkout@v4.1.1 - name: Gradle Wrapper Validation - uses: gradle/wrapper-validation-action@v2.0.0 + uses: gradle/wrapper-validation-action@v2.0.1 # Build, archive for ad-hoc and submit to Firebase App Distribution deployment: diff --git a/.github/workflows/ios_release_deployment.yml b/.github/workflows/ios_release_deployment.yml index 041911dca6..5ef6720436 100644 --- a/.github/workflows/ios_release_deployment.yml +++ b/.github/workflows/ios_release_deployment.yml @@ -24,7 +24,7 @@ jobs: uses: actions/checkout@v4.1.1 - name: Gradle Wrapper Validation - uses: gradle/wrapper-validation-action@v2.0.0 + uses: gradle/wrapper-validation-action@v2.0.1 # Build, archive for app-store and submit to App Store Connect deployment: From 9df005c1369660650ba6108f49e33f9134ec08fd Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 7 Feb 2024 11:19:27 +0800 Subject: [PATCH 028/104] Bundler: Bump cocoapods from 1.15.0 to 1.15.2 in /iosHyperskillApp (#884) * Bundler: Bump cocoapods from 1.15.0 to 1.15.2 in /iosHyperskillApp Bumps [cocoapods](https://github.com/CocoaPods/CocoaPods) from 1.15.0 to 1.15.2. - [Release notes](https://github.com/CocoaPods/CocoaPods/releases) - [Changelog](https://github.com/CocoaPods/CocoaPods/blob/master/CHANGELOG.md) - [Commits](https://github.com/CocoaPods/CocoaPods/compare/1.15.0...1.15.2) --- updated-dependencies: - dependency-name: cocoapods dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] * bundle exec pod install --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Ivan Magda --- iosHyperskillApp/Gemfile | 2 +- iosHyperskillApp/Gemfile.lock | 10 +++++----- iosHyperskillApp/Podfile.lock | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/iosHyperskillApp/Gemfile b/iosHyperskillApp/Gemfile index 4688dcb969..fad1d3f1c7 100644 --- a/iosHyperskillApp/Gemfile +++ b/iosHyperskillApp/Gemfile @@ -2,7 +2,7 @@ source "https://rubygems.org" ruby "3.3.0" gem "fastlane", "2.219.0" -gem "cocoapods", "1.15.0" +gem "cocoapods", "1.15.2" gem "generamba", git: "https://github.com/ivan-magda/Generamba.git", branch: "develop" eval_gemfile("fastlane/Pluginfile") \ No newline at end of file diff --git a/iosHyperskillApp/Gemfile.lock b/iosHyperskillApp/Gemfile.lock index 448853f75b..a82abce1d3 100644 --- a/iosHyperskillApp/Gemfile.lock +++ b/iosHyperskillApp/Gemfile.lock @@ -53,10 +53,10 @@ GEM base64 (0.2.0) bigdecimal (3.1.6) claide (1.1.0) - cocoapods (1.15.0) + cocoapods (1.15.2) addressable (~> 2.8) claide (>= 1.0.2, < 2.0) - cocoapods-core (= 1.15.0) + cocoapods-core (= 1.15.2) cocoapods-deintegrate (>= 1.0.3, < 2.0) cocoapods-downloader (>= 2.1, < 3.0) cocoapods-plugins (>= 1.0.0, < 2.0) @@ -71,7 +71,7 @@ GEM nap (~> 1.0) ruby-macho (>= 2.3.0, < 3.0) xcodeproj (>= 1.23.0, < 2.0) - cocoapods-core (1.15.0) + cocoapods-core (1.15.2) activesupport (>= 5.0, < 8) addressable (~> 2.8) algoliasearch (~> 1.0) @@ -242,7 +242,7 @@ GEM liquid (4.0.4) mini_magick (4.12.0) mini_mime (1.1.5) - minitest (5.21.2) + minitest (5.22.0) molinillo (0.8.0) multi_json (1.15.0) multipart-post (2.3.0) @@ -313,7 +313,7 @@ PLATFORMS x86_64-linux DEPENDENCIES - cocoapods (= 1.15.0) + cocoapods (= 1.15.2) fastlane (= 2.219.0) fastlane-plugin-firebase_app_distribution fastlane-plugin-sentry diff --git a/iosHyperskillApp/Podfile.lock b/iosHyperskillApp/Podfile.lock index fc9c004246..206fa6cffc 100644 --- a/iosHyperskillApp/Podfile.lock +++ b/iosHyperskillApp/Podfile.lock @@ -225,4 +225,4 @@ SPEC CHECKSUMS: PODFILE CHECKSUM: 1613f08afc0d23eb5c1805973d23b2f5c885203e -COCOAPODS: 1.15.0 +COCOAPODS: 1.15.2 From 3531f32546ac308c667b539b419ef89ed541b9c0 Mon Sep 17 00:00:00 2001 From: Ivan Magda Date: Wed, 7 Feb 2024 11:35:20 +0800 Subject: [PATCH 029/104] Bump build number --- gradle/app.versions.toml | 2 +- .../NotificationServiceExtension/Info.plist | 2 +- .../iosHyperskillApp.xcodeproj/project.pbxproj | 16 ++++++++-------- iosHyperskillApp/iosHyperskillApp/Info.plist | 2 +- .../iosHyperskillAppTests/Info.plist | 2 +- .../iosHyperskillAppUITests/Info.plist | 2 +- 6 files changed, 13 insertions(+), 13 deletions(-) diff --git a/gradle/app.versions.toml b/gradle/app.versions.toml index 4236efc3a8..06cc8f3f9f 100644 --- a/gradle/app.versions.toml +++ b/gradle/app.versions.toml @@ -3,4 +3,4 @@ minSdk = '24' targetSdk = '33' compileSdk = '33' versionName = '1.50' -versionCode = '313' \ No newline at end of file +versionCode = '314' \ No newline at end of file diff --git a/iosHyperskillApp/NotificationServiceExtension/Info.plist b/iosHyperskillApp/NotificationServiceExtension/Info.plist index b724b7aa06..16b354ec76 100644 --- a/iosHyperskillApp/NotificationServiceExtension/Info.plist +++ b/iosHyperskillApp/NotificationServiceExtension/Info.plist @@ -9,7 +9,7 @@ CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleVersion - 315 + 316 CFBundleShortVersionString 1.50 CFBundlePackageType diff --git a/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj b/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj index 1099535d3e..d2533e298b 100644 --- a/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj +++ b/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj @@ -4985,7 +4985,7 @@ buildSettings = { CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 315; + CURRENT_PROJECT_VERSION = 316; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = 3DWS674B2M; GENERATE_INFOPLIST_FILE = NO; @@ -5006,7 +5006,7 @@ buildSettings = { CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 315; + CURRENT_PROJECT_VERSION = 316; DEVELOPMENT_TEAM = 3DWS674B2M; GENERATE_INFOPLIST_FILE = NO; INFOPLIST_FILE = iosHyperskillAppUITests/Info.plist; @@ -5027,7 +5027,7 @@ BUNDLE_LOADER = "$(TEST_HOST)"; CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 315; + CURRENT_PROJECT_VERSION = 316; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = 3DWS674B2M; INFOPLIST_FILE = iosHyperskillAppTests/Info.plist; @@ -5048,7 +5048,7 @@ BUNDLE_LOADER = "$(TEST_HOST)"; CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 315; + CURRENT_PROJECT_VERSION = 316; DEVELOPMENT_TEAM = 3DWS674B2M; INFOPLIST_FILE = iosHyperskillAppTests/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; @@ -5069,7 +5069,7 @@ CODE_SIGN_ENTITLEMENTS = NotificationServiceExtension/NotificationServiceExtension.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 315; + CURRENT_PROJECT_VERSION = 316; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = 3DWS674B2M; INFOPLIST_FILE = NotificationServiceExtension/Info.plist; @@ -5097,7 +5097,7 @@ CODE_SIGN_ENTITLEMENTS = NotificationServiceExtension/NotificationServiceExtension.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 315; + CURRENT_PROJECT_VERSION = 316; DEVELOPMENT_TEAM = 3DWS674B2M; INFOPLIST_FILE = NotificationServiceExtension/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; @@ -5242,7 +5242,7 @@ CODE_SIGN_ENTITLEMENTS = iosHyperskillApp/iosHyperskillApp.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 315; + CURRENT_PROJECT_VERSION = 316; DEVELOPMENT_ASSET_PATHS = "\"iosHyperskillApp/Preview Content\""; DEVELOPMENT_TEAM = 3DWS674B2M; ENABLE_PREVIEWS = YES; @@ -5278,7 +5278,7 @@ CODE_SIGN_ENTITLEMENTS = iosHyperskillApp/iosHyperskillApp.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 315; + CURRENT_PROJECT_VERSION = 316; DEVELOPMENT_ASSET_PATHS = "\"iosHyperskillApp/Preview Content\""; DEVELOPMENT_TEAM = 3DWS674B2M; ENABLE_PREVIEWS = YES; diff --git a/iosHyperskillApp/iosHyperskillApp/Info.plist b/iosHyperskillApp/iosHyperskillApp/Info.plist index 2c7d96cdd2..4835448387 100644 --- a/iosHyperskillApp/iosHyperskillApp/Info.plist +++ b/iosHyperskillApp/iosHyperskillApp/Info.plist @@ -36,7 +36,7 @@ CFBundleVersion - 315 + 316 FirebaseAppDelegateProxyEnabled FirebaseMessagingAutoInitEnabled diff --git a/iosHyperskillApp/iosHyperskillAppTests/Info.plist b/iosHyperskillApp/iosHyperskillAppTests/Info.plist index f48083864c..7647e77ade 100644 --- a/iosHyperskillApp/iosHyperskillAppTests/Info.plist +++ b/iosHyperskillApp/iosHyperskillAppTests/Info.plist @@ -15,6 +15,6 @@ CFBundleShortVersionString 1.50 CFBundleVersion - 315 + 316 diff --git a/iosHyperskillApp/iosHyperskillAppUITests/Info.plist b/iosHyperskillApp/iosHyperskillAppUITests/Info.plist index a6e2322db3..14f133761f 100644 --- a/iosHyperskillApp/iosHyperskillAppUITests/Info.plist +++ b/iosHyperskillApp/iosHyperskillAppUITests/Info.plist @@ -15,6 +15,6 @@ CFBundleShortVersionString 1.50 CFBundleVersion - 315 + 316 From aa774a1f5b2aa5e50b07868ef1f2d97a8d807c08 Mon Sep 17 00:00:00 2001 From: Ivan Magda Date: Wed, 7 Feb 2024 20:36:12 +0800 Subject: [PATCH 030/104] Shared: Open next learning activity after notification click (#880) ^ALTAPPS-1127 --- .../app/main/injection/AppFeatureBuilder.kt | 6 +-- .../app/main/injection/MainComponentImpl.kt | 2 +- .../NotificationClickHandlingComponent.kt | 4 +- .../NotificationClickHandlingComponentImpl.kt | 21 +++++--- ...ificationClickHandlingActionDispatcher.kt} | 49 ++++++++++++++----- .../NotificationClickHandlingFeature.kt | 12 ++++- .../NotificationClickHandlingReducer.kt | 47 +++++++++++++++++- 7 files changed, 113 insertions(+), 28 deletions(-) rename shared/src/commonMain/kotlin/org/hyperskill/app/notification/click_handling/presentation/{NotificationClickHandlingDispatcher.kt => NotificationClickHandlingActionDispatcher.kt} (52%) diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/main/injection/AppFeatureBuilder.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/main/injection/AppFeatureBuilder.kt index f333089632..b6fd090400 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/main/injection/AppFeatureBuilder.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/main/injection/AppFeatureBuilder.kt @@ -13,7 +13,7 @@ import org.hyperskill.app.main.presentation.AppFeature.Action import org.hyperskill.app.main.presentation.AppFeature.Message import org.hyperskill.app.main.presentation.AppFeature.State import org.hyperskill.app.main.presentation.AppReducer -import org.hyperskill.app.notification.click_handling.presentation.NotificationClickHandlingDispatcher +import org.hyperskill.app.notification.click_handling.presentation.NotificationClickHandlingActionDispatcher import org.hyperskill.app.notification.click_handling.presentation.NotificationClickHandlingReducer import org.hyperskill.app.notification.local.domain.interactor.NotificationInteractor import org.hyperskill.app.notification.remote.domain.interactor.PushNotificationsInteractor @@ -42,7 +42,7 @@ internal object AppFeatureBuilder { streakRecoveryReducer: StreakRecoveryReducer, streakRecoveryActionDispatcher: StreakRecoveryActionDispatcher, clickedNotificationReducer: NotificationClickHandlingReducer, - notificationClickHandlingDispatcher: NotificationClickHandlingDispatcher, + notificationClickHandlingActionDispatcher: NotificationClickHandlingActionDispatcher, notificationsInteractor: NotificationInteractor, pushNotificationsInteractor: PushNotificationsInteractor, welcomeOnboardingReducer: WelcomeOnboardingReducer, @@ -77,7 +77,7 @@ internal object AppFeatureBuilder { ) ) .wrapWithActionDispatcher( - notificationClickHandlingDispatcher.transform( + notificationClickHandlingActionDispatcher.transform( transformAction = { it.safeCast()?.action }, transformMessage = Message::NotificationClickHandlingMessage ) diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/main/injection/MainComponentImpl.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/main/injection/MainComponentImpl.kt index fee9010251..fd2eb9c781 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/main/injection/MainComponentImpl.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/main/injection/MainComponentImpl.kt @@ -30,7 +30,7 @@ internal class MainComponentImpl(private val appGraph: AppGraph) : MainComponent streakRecoveryComponent.streakRecoveryReducer, streakRecoveryComponent.streakRecoveryActionDispatcher, clickedNotificationComponent.notificationClickHandlingReducer, - clickedNotificationComponent.notificationClickHandlingDispatcher, + clickedNotificationComponent.notificationClickHandlingActionDispatcher, appGraph.buildNotificationComponent().notificationInteractor, appGraph.buildPushNotificationsComponent().pushNotificationsInteractor, welcomeOnboardingComponent.welcomeOnboardingReducer, diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/notification/click_handling/injection/NotificationClickHandlingComponent.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/notification/click_handling/injection/NotificationClickHandlingComponent.kt index 2939620b2f..59c006a8f2 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/notification/click_handling/injection/NotificationClickHandlingComponent.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/notification/click_handling/injection/NotificationClickHandlingComponent.kt @@ -1,9 +1,9 @@ package org.hyperskill.app.notification.click_handling.injection -import org.hyperskill.app.notification.click_handling.presentation.NotificationClickHandlingDispatcher +import org.hyperskill.app.notification.click_handling.presentation.NotificationClickHandlingActionDispatcher import org.hyperskill.app.notification.click_handling.presentation.NotificationClickHandlingReducer interface NotificationClickHandlingComponent { val notificationClickHandlingReducer: NotificationClickHandlingReducer - val notificationClickHandlingDispatcher: NotificationClickHandlingDispatcher + val notificationClickHandlingActionDispatcher: NotificationClickHandlingActionDispatcher } \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/notification/click_handling/injection/NotificationClickHandlingComponentImpl.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/notification/click_handling/injection/NotificationClickHandlingComponentImpl.kt index 9ce1634478..8b0f191c40 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/notification/click_handling/injection/NotificationClickHandlingComponentImpl.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/notification/click_handling/injection/NotificationClickHandlingComponentImpl.kt @@ -2,18 +2,23 @@ package org.hyperskill.app.notification.click_handling.injection import org.hyperskill.app.core.injection.AppGraph import org.hyperskill.app.core.presentation.ActionDispatcherOptions -import org.hyperskill.app.notification.click_handling.presentation.NotificationClickHandlingDispatcher +import org.hyperskill.app.notification.click_handling.presentation.NotificationClickHandlingActionDispatcher import org.hyperskill.app.notification.click_handling.presentation.NotificationClickHandlingReducer -class NotificationClickHandlingComponentImpl(private val appGraph: AppGraph) : NotificationClickHandlingComponent { +internal class NotificationClickHandlingComponentImpl( + private val appGraph: AppGraph +) : NotificationClickHandlingComponent { override val notificationClickHandlingReducer: NotificationClickHandlingReducer get() = NotificationClickHandlingReducer() - override val notificationClickHandlingDispatcher: NotificationClickHandlingDispatcher - get() = NotificationClickHandlingDispatcher( + + override val notificationClickHandlingActionDispatcher: NotificationClickHandlingActionDispatcher + get() = NotificationClickHandlingActionDispatcher( ActionDispatcherOptions(), - appGraph.analyticComponent.analyticInteractor, - appGraph.profileDataComponent.currentProfileStateRepository, - appGraph.buildBadgesDataComponent().badgesRepository, - appGraph.sentryComponent.sentryInteractor + analyticInteractor = appGraph.analyticComponent.analyticInteractor, + currentProfileStateRepository = appGraph.profileDataComponent.currentProfileStateRepository, + nextLearningActivityStateRepository = appGraph.stateRepositoriesComponent + .nextLearningActivityStateRepository, + badgesRepository = appGraph.buildBadgesDataComponent().badgesRepository, + logger = appGraph.loggerComponent.logger ) } \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/notification/click_handling/presentation/NotificationClickHandlingDispatcher.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/notification/click_handling/presentation/NotificationClickHandlingActionDispatcher.kt similarity index 52% rename from shared/src/commonMain/kotlin/org/hyperskill/app/notification/click_handling/presentation/NotificationClickHandlingDispatcher.kt rename to shared/src/commonMain/kotlin/org/hyperskill/app/notification/click_handling/presentation/NotificationClickHandlingActionDispatcher.kt index 234feb0407..62de7281ad 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/notification/click_handling/presentation/NotificationClickHandlingDispatcher.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/notification/click_handling/presentation/NotificationClickHandlingActionDispatcher.kt @@ -1,32 +1,42 @@ package org.hyperskill.app.notification.click_handling.presentation +import co.touchlab.kermit.Logger +import co.touchlab.kermit.Severity import org.hyperskill.app.analytic.domain.interactor.AnalyticInteractor import org.hyperskill.app.badges.domain.repository.BadgesRepository import org.hyperskill.app.core.presentation.ActionDispatcherOptions +import org.hyperskill.app.learning_activities.domain.repository.NextLearningActivityStateRepository import org.hyperskill.app.notification.click_handling.presentation.NotificationClickHandlingFeature.Action import org.hyperskill.app.notification.click_handling.presentation.NotificationClickHandlingFeature.Message import org.hyperskill.app.profile.domain.repository.CurrentProfileStateRepository -import org.hyperskill.app.sentry.domain.interactor.SentryInteractor import ru.nobird.app.presentation.redux.dispatcher.CoroutineActionDispatcher -class NotificationClickHandlingDispatcher( - scopeConfig: ActionDispatcherOptions, +class NotificationClickHandlingActionDispatcher( + config: ActionDispatcherOptions, private val analyticInteractor: AnalyticInteractor, private val currentProfileStateRepository: CurrentProfileStateRepository, + private val nextLearningActivityStateRepository: NextLearningActivityStateRepository, private val badgesRepository: BadgesRepository, - private val sentryInteractor: SentryInteractor -) : CoroutineActionDispatcher(scopeConfig.createConfig()) { + private val logger: Logger +) : CoroutineActionDispatcher(config.createConfig()) { + companion object { + private const val LOG_TAG = "NotificationClickHandlingDispatcher" + } + override suspend fun doSuspendableAction(action: Action) { when (action) { is NotificationClickHandlingFeature.InternalAction.LogAnalyticEvent -> - analyticInteractor.logEvent(action.event) + analyticInteractor.logEvent(action.analyticEvent) is NotificationClickHandlingFeature.InternalAction.FetchProfile -> { val profile = currentProfileStateRepository .getState() .getOrElse { - sentryInteractor.captureErrorMessage( - "NotificationClickHandlingDispatcher: can't fetch profile\n$it" + logger.log( + severity = Severity.Error, + tag = LOG_TAG, + throwable = null, + message = "can't fetch profile\n$it" ) onNewMessage(NotificationClickHandlingFeature.ProfileFetchResult.Error) return @@ -37,15 +47,32 @@ class NotificationClickHandlingDispatcher( val badge = badgesRepository .getBadge(action.badgeId) .getOrElse { - sentryInteractor.captureErrorMessage( - "NotificationClickHandlingDispatcher: can't fetch badge\n$it" + logger.log( + severity = Severity.Error, + tag = LOG_TAG, + throwable = null, + message = "can't fetch badge\n$it" ) onNewMessage(NotificationClickHandlingFeature.EarnedBadgeFetchResult.Error) return } - onNewMessage(NotificationClickHandlingFeature.EarnedBadgeFetchResult.Success(badge)) } + is NotificationClickHandlingFeature.InternalAction.FetchNextLearningActivity -> { + val activity = nextLearningActivityStateRepository + .getState(forceUpdate = true) + .getOrElse { + logger.log( + severity = Severity.Error, + tag = LOG_TAG, + throwable = null, + message = "can't fetch next learning activity\n$it" + ) + onNewMessage(NotificationClickHandlingFeature.NextLearningActivityFetchResult.Error) + return + } + onNewMessage(NotificationClickHandlingFeature.NextLearningActivityFetchResult.Success(activity)) + } else -> { // no op } diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/notification/click_handling/presentation/NotificationClickHandlingFeature.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/notification/click_handling/presentation/NotificationClickHandlingFeature.kt index eeafaa3aab..bffbae9b05 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/notification/click_handling/presentation/NotificationClickHandlingFeature.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/notification/click_handling/presentation/NotificationClickHandlingFeature.kt @@ -1,8 +1,9 @@ package org.hyperskill.app.notification.click_handling.presentation -import org.hyperskill.app.analytic.domain.model.hyperskill.HyperskillAnalyticEvent +import org.hyperskill.app.analytic.domain.model.AnalyticEvent import org.hyperskill.app.badges.domain.model.Badge import org.hyperskill.app.badges.domain.model.BadgeKind +import org.hyperskill.app.learning_activities.domain.model.LearningActivity import org.hyperskill.app.notification.remote.domain.model.PushNotificationData import org.hyperskill.app.profile.domain.model.Profile import org.hyperskill.app.step.domain.model.StepRoute @@ -39,6 +40,11 @@ object NotificationClickHandlingFeature { object Error : EarnedBadgeFetchResult } + internal sealed interface NextLearningActivityFetchResult : Message { + data class Success(val learningActivity: LearningActivity?) : NextLearningActivityFetchResult + object Error : NextLearningActivityFetchResult + } + sealed interface Action { sealed interface ViewAction : Action { @@ -59,10 +65,12 @@ object NotificationClickHandlingFeature { } internal interface InternalAction : Action { - data class LogAnalyticEvent(val event: HyperskillAnalyticEvent) : InternalAction + data class LogAnalyticEvent(val analyticEvent: AnalyticEvent) : InternalAction object FetchProfile : InternalAction data class FetchEarnedBadge(val badgeId: Long) : InternalAction + + object FetchNextLearningActivity : InternalAction } } \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/notification/click_handling/presentation/NotificationClickHandlingReducer.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/notification/click_handling/presentation/NotificationClickHandlingReducer.kt index 57dfad4e21..2b67e0575d 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/notification/click_handling/presentation/NotificationClickHandlingReducer.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/notification/click_handling/presentation/NotificationClickHandlingReducer.kt @@ -1,9 +1,13 @@ package org.hyperskill.app.notification.click_handling.presentation +import org.hyperskill.app.learning_activities.domain.model.LearningActivity +import org.hyperskill.app.learning_activities.presentation.mapper.LearningActivityTargetViewActionMapper +import org.hyperskill.app.learning_activities.presentation.model.LearningActivityTargetViewAction import org.hyperskill.app.notification.click_handling.presentation.NotificationClickHandlingFeature.Action import org.hyperskill.app.notification.click_handling.presentation.NotificationClickHandlingFeature.EarnedBadgeFetchResult import org.hyperskill.app.notification.click_handling.presentation.NotificationClickHandlingFeature.InternalAction import org.hyperskill.app.notification.click_handling.presentation.NotificationClickHandlingFeature.Message +import org.hyperskill.app.notification.click_handling.presentation.NotificationClickHandlingFeature.NextLearningActivityFetchResult import org.hyperskill.app.notification.click_handling.presentation.NotificationClickHandlingFeature.ProfileFetchResult import org.hyperskill.app.notification.click_handling.presentation.NotificationClickHandlingFeature.State import org.hyperskill.app.notification.remote.domain.analytic.PushNotificationClickedHyperskillAnalyticEvent @@ -21,6 +25,7 @@ class NotificationClickHandlingReducer : StateReducer { is Message.NotificationClicked -> handleNotificationClicked(message) is ProfileFetchResult -> handleProfileFetchResult(message) is EarnedBadgeFetchResult -> handleEarnedBadgeFetchResult(message) + is NextLearningActivityFetchResult -> handleNextLearningActivityFetchResult(message) /** * Analytic */ @@ -48,13 +53,18 @@ class NotificationClickHandlingReducer : StateReducer { PushNotificationType.STREAK_RECORD_START, PushNotificationType.STREAK_RECORD_NEAR, PushNotificationType.STREAK_RECORD_COMPLETE, - PushNotificationType.DAILY_REMINDER, PushNotificationType.STREAK_NEW -> setOf( Action.ViewAction.SetLoadingShowed(true), InternalAction.FetchProfile ) + PushNotificationType.DAILY_REMINDER -> + setOf( + Action.ViewAction.SetLoadingShowed(true), + InternalAction.FetchNextLearningActivity + ) + PushNotificationType.LEARN_TOPIC, PushNotificationType.REMIND_SHORT -> setOf(Action.ViewAction.NavigateTo.StudyPlan) @@ -119,4 +129,39 @@ class NotificationClickHandlingReducer : StateReducer { EarnedBadgeFetchResult.Error -> null } ) + + private fun handleNextLearningActivityFetchResult( + message: NextLearningActivityFetchResult + ): Set = + setOfNotNull( + Action.ViewAction.SetLoadingShowed(false), + when (message) { + is NextLearningActivityFetchResult.Success -> { + getStepRouteForNextLearningActivity(message.learningActivity)?.let { stepRoute -> + Action.ViewAction.NavigateTo.StepScreen(stepRoute) + } + } + NextLearningActivityFetchResult.Error -> null + } + ) + + private fun getStepRouteForNextLearningActivity(learningActivity: LearningActivity?): StepRoute? { + if (learningActivity == null) { + return null + } + + val learningActivityTargetViewAction = LearningActivityTargetViewActionMapper + .mapLearningActivityToTargetViewAction( + activity = learningActivity, + trackId = null, + projectId = null + ) + .getOrElse { return null } + + return if (learningActivityTargetViewAction is LearningActivityTargetViewAction.NavigateTo.Step) { + learningActivityTargetViewAction.stepRoute + } else { + null + } + } } \ No newline at end of file From b8e04459b20221f4e1ba61b13d8640e49e43385b Mon Sep 17 00:00:00 2001 From: Ivan Magda Date: Wed, 7 Feb 2024 20:44:11 +0800 Subject: [PATCH 031/104] Shared: Add app launch first time analytic event (#881) ^ALTAPPS-1139 --- .../hyperskill/HyperskillAnalyticRoute.kt | 7 +++++ .../app/main/cache/AppCacheDataSourceImpl.kt | 15 +++++++++ .../app/main/cache/AppCacheKeyValues.kt | 5 +++ .../main/data/repository/AppRepositoryImpl.kt | 15 +++++++++ .../main/data/source/AppCacheDataSource.kt | 6 ++++ ...pLaunchFirstTimeHyperskillAnalyticEvent.kt | 23 ++++++++++++++ .../main/domain/interactor/AppInteractor.kt | 10 ++++++ .../main/domain/repository/AppRepository.kt | 6 ++++ .../main/injection/MainDataComponentImpl.kt | 31 +++++++++++++------ .../main/presentation/AppActionDispatcher.kt | 3 ++ .../app/main/presentation/AppFeature.kt | 2 ++ .../app/main/presentation/AppReducer.kt | 5 ++- 12 files changed, 117 insertions(+), 11 deletions(-) create mode 100644 shared/src/commonMain/kotlin/org/hyperskill/app/main/cache/AppCacheDataSourceImpl.kt create mode 100644 shared/src/commonMain/kotlin/org/hyperskill/app/main/cache/AppCacheKeyValues.kt create mode 100644 shared/src/commonMain/kotlin/org/hyperskill/app/main/data/repository/AppRepositoryImpl.kt create mode 100644 shared/src/commonMain/kotlin/org/hyperskill/app/main/data/source/AppCacheDataSource.kt create mode 100644 shared/src/commonMain/kotlin/org/hyperskill/app/main/domain/analytic/AppLaunchFirstTimeHyperskillAnalyticEvent.kt create mode 100644 shared/src/commonMain/kotlin/org/hyperskill/app/main/domain/repository/AppRepository.kt diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/analytic/domain/model/hyperskill/HyperskillAnalyticRoute.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/analytic/domain/model/hyperskill/HyperskillAnalyticRoute.kt index e57fba4923..6e7b115396 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/analytic/domain/model/hyperskill/HyperskillAnalyticRoute.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/analytic/domain/model/hyperskill/HyperskillAnalyticRoute.kt @@ -146,4 +146,11 @@ 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" + } } \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/main/cache/AppCacheDataSourceImpl.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/main/cache/AppCacheDataSourceImpl.kt new file mode 100644 index 0000000000..0f72b9ccc3 --- /dev/null +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/main/cache/AppCacheDataSourceImpl.kt @@ -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) + } +} \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/main/cache/AppCacheKeyValues.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/main/cache/AppCacheKeyValues.kt new file mode 100644 index 0000000000..9e3ed32097 --- /dev/null +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/main/cache/AppCacheKeyValues.kt @@ -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" +} \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/main/data/repository/AppRepositoryImpl.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/main/data/repository/AppRepositoryImpl.kt new file mode 100644 index 0000000000..b8295d0b73 --- /dev/null +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/main/data/repository/AppRepositoryImpl.kt @@ -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() + } +} \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/main/data/source/AppCacheDataSource.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/main/data/source/AppCacheDataSource.kt new file mode 100644 index 0000000000..a0fbfdfd7e --- /dev/null +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/main/data/source/AppCacheDataSource.kt @@ -0,0 +1,6 @@ +package org.hyperskill.app.main.data.source + +interface AppCacheDataSource { + fun isAppDidLaunchFirstTime(): Boolean + fun setAppDidLaunchFirstTime() +} \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/main/domain/analytic/AppLaunchFirstTimeHyperskillAnalyticEvent.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/main/domain/analytic/AppLaunchFirstTimeHyperskillAnalyticEvent.kt new file mode 100644 index 0000000000..a56ff20b7f --- /dev/null +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/main/domain/analytic/AppLaunchFirstTimeHyperskillAnalyticEvent.kt @@ -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 +) \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/main/domain/interactor/AppInteractor.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/main/domain/interactor/AppInteractor.kt index 224aea580b..f6c349ac79 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/main/domain/interactor/AppInteractor.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/main/domain/interactor/AppInteractor.kt @@ -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 @@ -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, @@ -40,4 +43,11 @@ class AppInteractor( projectsRepository.clearCache() shareStreakRepository.clearCache() } + + suspend fun logAppLaunchFirstTimeAnalyticEventIfNeeded() { + if (!appRepository.isAppDidLaunchFirstTime()) { + appRepository.setAppDidLaunchFirstTime() + analyticInteractor.logEvent(AppLaunchFirstTimeHyperskillAnalyticEvent) + } + } } \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/main/domain/repository/AppRepository.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/main/domain/repository/AppRepository.kt new file mode 100644 index 0000000000..0b9dce37bb --- /dev/null +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/main/domain/repository/AppRepository.kt @@ -0,0 +1,6 @@ +package org.hyperskill.app.main.domain.repository + +interface AppRepository { + fun isAppDidLaunchFirstTime(): Boolean + fun setAppDidLaunchFirstTime() +} \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/main/injection/MainDataComponentImpl.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/main/injection/MainDataComponentImpl.kt index 86039e5562..7941032c88 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/main/injection/MainDataComponentImpl.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/main/injection/MainDataComponentImpl.kt @@ -1,20 +1,31 @@ package org.hyperskill.app.main.injection import org.hyperskill.app.core.injection.AppGraph +import org.hyperskill.app.main.cache.AppCacheDataSourceImpl +import org.hyperskill.app.main.data.repository.AppRepositoryImpl +import org.hyperskill.app.main.data.source.AppCacheDataSource import org.hyperskill.app.main.domain.interactor.AppInteractor +import org.hyperskill.app.main.domain.repository.AppRepository internal class MainDataComponentImpl(private val appGraph: AppGraph) : MainDataComponent { + private val appCacheDataSource: AppCacheDataSource = + AppCacheDataSourceImpl(appGraph.commonComponent.settings) + + private val appRepository: AppRepository = + AppRepositoryImpl(appCacheDataSource) + override val appInteractor: AppInteractor get() = AppInteractor( - appGraph.authComponent.authInteractor, - appGraph.profileDataComponent.currentProfileStateRepository, - appGraph.buildUserStorageComponent().userStorageInteractor, - appGraph.analyticComponent.analyticInteractor, - appGraph.buildProgressesDataComponent().progressesRepository, - appGraph.buildTrackDataComponent().trackRepository, - appGraph.buildProvidersDataComponent().providersRepository, - appGraph.buildProjectsDataComponent().projectsRepository, - appGraph.buildShareStreakDataComponent().shareStreakRepository, - appGraph.buildPushNotificationsComponent().pushNotificationsInteractor + appRepository = appRepository, + authInteractor = appGraph.authComponent.authInteractor, + currentProfileStateRepository = appGraph.profileDataComponent.currentProfileStateRepository, + userStorageInteractor = appGraph.buildUserStorageComponent().userStorageInteractor, + analyticInteractor = appGraph.analyticComponent.analyticInteractor, + progressesRepository = appGraph.buildProgressesDataComponent().progressesRepository, + trackRepository = appGraph.buildTrackDataComponent().trackRepository, + providersRepository = appGraph.buildProvidersDataComponent().providersRepository, + projectsRepository = appGraph.buildProjectsDataComponent().projectsRepository, + shareStreakRepository = appGraph.buildShareStreakDataComponent().shareStreakRepository, + pushNotificationsInteractor = appGraph.buildPushNotificationsComponent().pushNotificationsInteractor ) } \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/main/presentation/AppActionDispatcher.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/main/presentation/AppActionDispatcher.kt index d47eee3418..06484459b9 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/main/presentation/AppActionDispatcher.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/main/presentation/AppActionDispatcher.kt @@ -112,6 +112,9 @@ internal class AppActionDispatcher( is Action.SendPushNotificationsToken -> { pushNotificationsInteractor.renewFCMToken() } + is Action.LogAppLaunchFirstTimeAnalyticEventIfNeeded -> { + appInteractor.logAppLaunchFirstTimeAnalyticEventIfNeeded() + } else -> {} } } diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/main/presentation/AppFeature.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/main/presentation/AppFeature.kt index d0bb39a7ab..23289395aa 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/main/presentation/AppFeature.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/main/presentation/AppFeature.kt @@ -77,6 +77,8 @@ interface AppFeature { object SendPushNotificationsToken : Action + object LogAppLaunchFirstTimeAnalyticEventIfNeeded : Action + /** * Action Wrappers */ diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/main/presentation/AppReducer.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/main/presentation/AppReducer.kt index c6783b55cb..b3df28e921 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/main/presentation/AppReducer.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/main/presentation/AppReducer.kt @@ -33,7 +33,10 @@ internal class AppReducer( when (message) { is Message.Initialize -> { if (state is State.Idle || (state is State.NetworkError && message.forceUpdate)) { - State.Loading to setOf(Action.DetermineUserAccountStatus(message.pushNotificationData)) + State.Loading to setOf( + Action.DetermineUserAccountStatus(message.pushNotificationData), + Action.LogAppLaunchFirstTimeAnalyticEventIfNeeded + ) } else { null } From 4751fb2197480b79266ec47956a5922d922d0cfe Mon Sep 17 00:00:00 2001 From: Ivan Magda Date: Wed, 7 Feb 2024 20:55:56 +0800 Subject: [PATCH 032/104] iOS: Add quick action for feedback (#885) ^ALTAPPS-1140 --- .../project.pbxproj | 16 ++++ iosHyperskillApp/iosHyperskillApp/Info.plist | 20 ++++ .../Sources/AppDelegate.swift | 18 ++++ .../SendEmailFeedbackController.swift | 4 + .../ApplicationShortcutIdentifier.swift | 12 +++ .../ApplicationShortcutsService.swift | 94 +++++++++++++++++++ .../hyperskill/HyperskillAnalyticRoute.kt | 8 ++ .../hyperskill/HyperskillAnalyticTarget.kt | 3 +- ...rtcutItemClickedHyperskillAnalyticEvent.kt | 47 ++++++++++ .../ApplicationShortcutsInteractor.kt | 30 ++++++ .../ApplicationShortcutsDataComponent.kt | 7 ++ .../ApplicationShortcutsDataComponentImpl.kt | 16 ++++ .../app/core/injection/IosAppComponent.kt | 3 + .../app/core/injection/IosAppComponentImpl.kt | 5 + 14 files changed, 282 insertions(+), 1 deletion(-) create mode 100644 iosHyperskillApp/iosHyperskillApp/Sources/Services/ApplicationShortcuts/ApplicationShortcutIdentifier.swift create mode 100644 iosHyperskillApp/iosHyperskillApp/Sources/Services/ApplicationShortcuts/ApplicationShortcutsService.swift create mode 100644 shared/src/iosMain/kotlin/org/hyperskill/app/application_shortcuts/domain/analytic/ApplicationShortcutItemClickedHyperskillAnalyticEvent.kt create mode 100644 shared/src/iosMain/kotlin/org/hyperskill/app/application_shortcuts/domain/interactor/ApplicationShortcutsInteractor.kt create mode 100644 shared/src/iosMain/kotlin/org/hyperskill/app/application_shortcuts/injection/ApplicationShortcutsDataComponent.kt create mode 100644 shared/src/iosMain/kotlin/org/hyperskill/app/application_shortcuts/injection/ApplicationShortcutsDataComponentImpl.kt diff --git a/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj b/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj index 80961eddf4..88dac8e497 100644 --- a/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj +++ b/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj @@ -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 */; }; @@ -1109,6 +1111,8 @@ 2CCCA3A02862E62F00D98089 /* StepQuizStringViewData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StepQuizStringViewData.swift; sourceTree = ""; }; 2CCF3B5728004FC40075D12C /* UserAgentBuilder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserAgentBuilder.swift; sourceTree = ""; }; 2CCF3B59280050890075D12C /* DeviceInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceInfo.swift; sourceTree = ""; }; + 2CD20ED02B73475400FB5269 /* ApplicationShortcutsService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ApplicationShortcutsService.swift; sourceTree = ""; }; + 2CD20ED32B73484200FB5269 /* ApplicationShortcutIdentifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ApplicationShortcutIdentifier.swift; sourceTree = ""; }; 2CD316BF28A3B2040002B2B2 /* ApplicationTheme+SharedTheme.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ApplicationTheme+SharedTheme.swift"; sourceTree = ""; }; 2CD3652428796C4300D61855 /* ProfileViewDataMapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileViewDataMapper.swift; sourceTree = ""; }; 2CD3652728797D3600D61855 /* Formatter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Formatter.swift; sourceTree = ""; }; @@ -1772,6 +1776,7 @@ 2C1F5880280D5B8200372A37 /* Services */ = { isa = PBXGroup; children = ( + 2CD20ED22B73481800FB5269 /* ApplicationShortcuts */, 2C336D112865C46400C91342 /* ApplicationTheme */, 2C1F5883280D5BE600372A37 /* Auth */, ); @@ -3246,6 +3251,15 @@ path = Modals; sourceTree = ""; }; + 2CD20ED22B73481800FB5269 /* ApplicationShortcuts */ = { + isa = PBXGroup; + children = ( + 2CD20ED32B73484200FB5269 /* ApplicationShortcutIdentifier.swift */, + 2CD20ED02B73475400FB5269 /* ApplicationShortcutsService.swift */, + ); + path = ApplicationShortcuts; + sourceTree = ""; + }; 2CD3652328796C3000D61855 /* ViewData */ = { isa = PBXGroup; children = ( @@ -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 */, @@ -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 */, diff --git a/iosHyperskillApp/iosHyperskillApp/Info.plist b/iosHyperskillApp/iosHyperskillApp/Info.plist index 8e79dfe900..49b41c8827 100644 --- a/iosHyperskillApp/iosHyperskillApp/Info.plist +++ b/iosHyperskillApp/iosHyperskillApp/Info.plist @@ -69,5 +69,25 @@ UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown + UIApplicationShortcutItems + + + UIApplicationShortcutItemType + $(PRODUCT_BUNDLE_IDENTIFIER).SendFeedback + UIApplicationShortcutItemTitle + We Value Your Feedback + UIApplicationShortcutItemSubtitle + Help us enhance your experience + UIApplicationShortcutItemIconSymbolName + lightbulb.circle.fill + UIApplicationShortcutItemIconType + UIApplicationShortcutIconTypeLove + UIApplicationShortcutItemUserInfo + + version + 1 + + + diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/AppDelegate.swift b/iosHyperskillApp/iosHyperskillApp/Sources/AppDelegate.swift index 65d732c863..cca4ab857f 100644 --- a/iosHyperskillApp/iosHyperskillApp/Sources/AppDelegate.swift +++ b/iosHyperskillApp/iosHyperskillApp/Sources/AppDelegate.swift @@ -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( @@ -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 } @@ -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( diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/ProfileSettings/Controllers/SendEmailFeedbackController.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/ProfileSettings/Controllers/SendEmailFeedbackController.swift index 4d4beb603f..c6a3a651c0 100644 --- a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/ProfileSettings/Controllers/SendEmailFeedbackController.swift +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/ProfileSettings/Controllers/SendEmailFeedbackController.swift @@ -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 @@ -67,5 +69,7 @@ extension SendEmailFeedbackController: MFMailComposeViewControllerDelegate { } ) } + + onDidFinish?() } } diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Services/ApplicationShortcuts/ApplicationShortcutIdentifier.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Services/ApplicationShortcuts/ApplicationShortcutIdentifier.swift new file mode 100644 index 0000000000..9bff938c8c --- /dev/null +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Services/ApplicationShortcuts/ApplicationShortcutIdentifier.swift @@ -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) + } +} diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Services/ApplicationShortcuts/ApplicationShortcutsService.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Services/ApplicationShortcuts/ApplicationShortcutsService.swift new file mode 100644 index 0000000000..e23895621e --- /dev/null +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Services/ApplicationShortcuts/ApplicationShortcutsService.swift @@ -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 + ) + } + } +} diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/analytic/domain/model/hyperskill/HyperskillAnalyticRoute.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/analytic/domain/model/hyperskill/HyperskillAnalyticRoute.kt index 6e7b115396..1512aac855 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/analytic/domain/model/hyperskill/HyperskillAnalyticRoute.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/analytic/domain/model/hyperskill/HyperskillAnalyticRoute.kt @@ -153,4 +153,12 @@ sealed class HyperskillAnalyticRoute { 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" + } } \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/analytic/domain/model/hyperskill/HyperskillAnalyticTarget.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/analytic/domain/model/hyperskill/HyperskillAnalyticTarget.kt index c8bc613d8c..d2ab8a9ee9 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/analytic/domain/model/hyperskill/HyperskillAnalyticTarget.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/analytic/domain/model/hyperskill/HyperskillAnalyticTarget.kt @@ -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") } \ No newline at end of file diff --git a/shared/src/iosMain/kotlin/org/hyperskill/app/application_shortcuts/domain/analytic/ApplicationShortcutItemClickedHyperskillAnalyticEvent.kt b/shared/src/iosMain/kotlin/org/hyperskill/app/application_shortcuts/domain/analytic/ApplicationShortcutItemClickedHyperskillAnalyticEvent.kt new file mode 100644 index 0000000000..300cc019f6 --- /dev/null +++ b/shared/src/iosMain/kotlin/org/hyperskill/app/application_shortcuts/domain/analytic/ApplicationShortcutItemClickedHyperskillAnalyticEvent.kt @@ -0,0 +1,47 @@ +package org.hyperskill.app.application_shortcuts.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.HyperskillAnalyticPart +import org.hyperskill.app.analytic.domain.model.hyperskill.HyperskillAnalyticRoute +import org.hyperskill.app.analytic.domain.model.hyperskill.HyperskillAnalyticTarget + +/** + * Represents click on the home screen quick action analytic event. + * + * JSON payload: + * ``` + * { + * "route": "SpringBoard", + * "action": "click", + * "part": "main", + * "target": "home_screen_quick_action", + * "context": + * { + * "type": "org.hyperskill.App.SendFeedback" + * } + * } + * ``` + * + * @property shortcutItemIdentifier The identifier of the clicked quick action. + * @see HyperskillAnalyticEvent + */ +class ApplicationShortcutItemClickedHyperskillAnalyticEvent( + private val shortcutItemIdentifier: String +) : HyperskillAnalyticEvent( + HyperskillAnalyticRoute.IosSpringBoard(), + HyperskillAnalyticAction.CLICK, + HyperskillAnalyticPart.MAIN, + HyperskillAnalyticTarget.HOME_SCREEN_QUICK_ACTION +) { + companion object { + private const val PARAM_TYPE = "type" + } + + override val params: Map + get() = super.params + mapOf( + PARAM_CONTEXT to mapOf( + PARAM_TYPE to shortcutItemIdentifier + ) + ) +} \ No newline at end of file diff --git a/shared/src/iosMain/kotlin/org/hyperskill/app/application_shortcuts/domain/interactor/ApplicationShortcutsInteractor.kt b/shared/src/iosMain/kotlin/org/hyperskill/app/application_shortcuts/domain/interactor/ApplicationShortcutsInteractor.kt new file mode 100644 index 0000000000..2400cdd0cc --- /dev/null +++ b/shared/src/iosMain/kotlin/org/hyperskill/app/application_shortcuts/domain/interactor/ApplicationShortcutsInteractor.kt @@ -0,0 +1,30 @@ +package org.hyperskill.app.application_shortcuts.domain.interactor + +import org.hyperskill.app.SharedResources +import org.hyperskill.app.core.domain.platform.Platform +import org.hyperskill.app.core.remote.UserAgentInfo +import org.hyperskill.app.core.view.mapper.ResourceProvider +import org.hyperskill.app.profile.domain.repository.CurrentProfileStateRepository +import org.hyperskill.app.profile_settings.domain.model.FeedbackEmailData +import org.hyperskill.app.profile_settings.domain.model.FeedbackEmailDataBuilder + +class ApplicationShortcutsInteractor( + private val currentProfileStateRepository: CurrentProfileStateRepository, + private val platform: Platform, + private val userAgentInfo: UserAgentInfo, + private val resourceProvider: ResourceProvider +) { + suspend fun getSendFeedbackEmailData(): FeedbackEmailData { + val currentProfile = currentProfileStateRepository + .getState() + .getOrNull() + + return FeedbackEmailDataBuilder.build( + supportEmail = resourceProvider.getString(SharedResources.strings.settings_send_feedback_support_email), + applicationName = resourceProvider.getString(platform.appNameResource), + platform = platform, + userId = currentProfile?.id, + applicationVersion = userAgentInfo.versionCode + ) + } +} \ No newline at end of file diff --git a/shared/src/iosMain/kotlin/org/hyperskill/app/application_shortcuts/injection/ApplicationShortcutsDataComponent.kt b/shared/src/iosMain/kotlin/org/hyperskill/app/application_shortcuts/injection/ApplicationShortcutsDataComponent.kt new file mode 100644 index 0000000000..4ee77d3ea2 --- /dev/null +++ b/shared/src/iosMain/kotlin/org/hyperskill/app/application_shortcuts/injection/ApplicationShortcutsDataComponent.kt @@ -0,0 +1,7 @@ +package org.hyperskill.app.application_shortcuts.injection + +import org.hyperskill.app.application_shortcuts.domain.interactor.ApplicationShortcutsInteractor + +interface ApplicationShortcutsDataComponent { + val applicationShortcutsInteractor: ApplicationShortcutsInteractor +} \ No newline at end of file diff --git a/shared/src/iosMain/kotlin/org/hyperskill/app/application_shortcuts/injection/ApplicationShortcutsDataComponentImpl.kt b/shared/src/iosMain/kotlin/org/hyperskill/app/application_shortcuts/injection/ApplicationShortcutsDataComponentImpl.kt new file mode 100644 index 0000000000..33aa7fe56e --- /dev/null +++ b/shared/src/iosMain/kotlin/org/hyperskill/app/application_shortcuts/injection/ApplicationShortcutsDataComponentImpl.kt @@ -0,0 +1,16 @@ +package org.hyperskill.app.application_shortcuts.injection + +import org.hyperskill.app.application_shortcuts.domain.interactor.ApplicationShortcutsInteractor +import org.hyperskill.app.core.injection.AppGraph + +internal class ApplicationShortcutsDataComponentImpl( + private val appGraph: AppGraph +) : ApplicationShortcutsDataComponent { + override val applicationShortcutsInteractor: ApplicationShortcutsInteractor + get() = ApplicationShortcutsInteractor( + currentProfileStateRepository = appGraph.profileDataComponent.currentProfileStateRepository, + platform = appGraph.commonComponent.platform, + userAgentInfo = appGraph.commonComponent.userAgentInfo, + resourceProvider = appGraph.commonComponent.resourceProvider + ) +} \ No newline at end of file diff --git a/shared/src/iosMain/kotlin/org/hyperskill/app/core/injection/IosAppComponent.kt b/shared/src/iosMain/kotlin/org/hyperskill/app/core/injection/IosAppComponent.kt index a4addb3af5..344cd1ca26 100644 --- a/shared/src/iosMain/kotlin/org/hyperskill/app/core/injection/IosAppComponent.kt +++ b/shared/src/iosMain/kotlin/org/hyperskill/app/core/injection/IosAppComponent.kt @@ -1,7 +1,10 @@ package org.hyperskill.app.core.injection +import org.hyperskill.app.application_shortcuts.injection.ApplicationShortcutsDataComponent import org.hyperskill.app.notification.remote.data.repository.IosFCMTokenProvider interface IosAppComponent : AppGraph { fun getIosFCMTokenProvider(): IosFCMTokenProvider + + fun buildApplicationShortcutsDataComponent(): ApplicationShortcutsDataComponent } \ No newline at end of file diff --git a/shared/src/iosMain/kotlin/org/hyperskill/app/core/injection/IosAppComponentImpl.kt b/shared/src/iosMain/kotlin/org/hyperskill/app/core/injection/IosAppComponentImpl.kt index b2561d18d6..6991f95821 100644 --- a/shared/src/iosMain/kotlin/org/hyperskill/app/core/injection/IosAppComponentImpl.kt +++ b/shared/src/iosMain/kotlin/org/hyperskill/app/core/injection/IosAppComponentImpl.kt @@ -3,6 +3,8 @@ package org.hyperskill.app.core.injection import org.hyperskill.app.analytic.domain.model.AnalyticEngine import org.hyperskill.app.analytic.injection.AnalyticComponent import org.hyperskill.app.analytic.injection.AnalyticComponentImpl +import org.hyperskill.app.application_shortcuts.injection.ApplicationShortcutsDataComponent +import org.hyperskill.app.application_shortcuts.injection.ApplicationShortcutsDataComponentImpl import org.hyperskill.app.core.domain.BuildVariant import org.hyperskill.app.core.remote.UserAgentInfo import org.hyperskill.app.notification.remote.injection.IosPlatformPushNotificationsDataComponent @@ -37,4 +39,7 @@ abstract class IosAppComponentImpl( IosPlatformPushNotificationsDataComponent( iosFCMTokenProvider = getIosFCMTokenProvider() ) + + override fun buildApplicationShortcutsDataComponent(): ApplicationShortcutsDataComponent = + ApplicationShortcutsDataComponentImpl(this) } \ No newline at end of file From 00ec249791f4ad6cbc2c1b3b5445b2d4079d96b6 Mon Sep 17 00:00:00 2001 From: Ivan Magda Date: Wed, 7 Feb 2024 20:57:22 +0800 Subject: [PATCH 033/104] Bump build number --- gradle/app.versions.toml | 2 +- .../NotificationServiceExtension/Info.plist | 2 +- .../iosHyperskillApp.xcodeproj/project.pbxproj | 16 ++++++++-------- iosHyperskillApp/iosHyperskillApp/Info.plist | 2 +- .../iosHyperskillAppTests/Info.plist | 2 +- .../iosHyperskillAppUITests/Info.plist | 2 +- 6 files changed, 13 insertions(+), 13 deletions(-) diff --git a/gradle/app.versions.toml b/gradle/app.versions.toml index 06cc8f3f9f..ace2b19001 100644 --- a/gradle/app.versions.toml +++ b/gradle/app.versions.toml @@ -3,4 +3,4 @@ minSdk = '24' targetSdk = '33' compileSdk = '33' versionName = '1.50' -versionCode = '314' \ No newline at end of file +versionCode = '315' \ No newline at end of file diff --git a/iosHyperskillApp/NotificationServiceExtension/Info.plist b/iosHyperskillApp/NotificationServiceExtension/Info.plist index 16b354ec76..8940a268fc 100644 --- a/iosHyperskillApp/NotificationServiceExtension/Info.plist +++ b/iosHyperskillApp/NotificationServiceExtension/Info.plist @@ -9,7 +9,7 @@ CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleVersion - 316 + 317 CFBundleShortVersionString 1.50 CFBundlePackageType diff --git a/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj b/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj index 1aa854bf87..e3fd78edc0 100644 --- a/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj +++ b/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj @@ -5001,7 +5001,7 @@ buildSettings = { CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 316; + CURRENT_PROJECT_VERSION = 317; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = 3DWS674B2M; GENERATE_INFOPLIST_FILE = NO; @@ -5022,7 +5022,7 @@ buildSettings = { CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 316; + CURRENT_PROJECT_VERSION = 317; DEVELOPMENT_TEAM = 3DWS674B2M; GENERATE_INFOPLIST_FILE = NO; INFOPLIST_FILE = iosHyperskillAppUITests/Info.plist; @@ -5043,7 +5043,7 @@ BUNDLE_LOADER = "$(TEST_HOST)"; CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 316; + CURRENT_PROJECT_VERSION = 317; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = 3DWS674B2M; INFOPLIST_FILE = iosHyperskillAppTests/Info.plist; @@ -5064,7 +5064,7 @@ BUNDLE_LOADER = "$(TEST_HOST)"; CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 316; + CURRENT_PROJECT_VERSION = 317; DEVELOPMENT_TEAM = 3DWS674B2M; INFOPLIST_FILE = iosHyperskillAppTests/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; @@ -5085,7 +5085,7 @@ CODE_SIGN_ENTITLEMENTS = NotificationServiceExtension/NotificationServiceExtension.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 316; + CURRENT_PROJECT_VERSION = 317; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = 3DWS674B2M; INFOPLIST_FILE = NotificationServiceExtension/Info.plist; @@ -5113,7 +5113,7 @@ CODE_SIGN_ENTITLEMENTS = NotificationServiceExtension/NotificationServiceExtension.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 316; + CURRENT_PROJECT_VERSION = 317; DEVELOPMENT_TEAM = 3DWS674B2M; INFOPLIST_FILE = NotificationServiceExtension/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; @@ -5258,7 +5258,7 @@ CODE_SIGN_ENTITLEMENTS = iosHyperskillApp/iosHyperskillApp.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 316; + CURRENT_PROJECT_VERSION = 317; DEVELOPMENT_ASSET_PATHS = "\"iosHyperskillApp/Preview Content\""; DEVELOPMENT_TEAM = 3DWS674B2M; ENABLE_PREVIEWS = YES; @@ -5294,7 +5294,7 @@ CODE_SIGN_ENTITLEMENTS = iosHyperskillApp/iosHyperskillApp.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 316; + CURRENT_PROJECT_VERSION = 317; DEVELOPMENT_ASSET_PATHS = "\"iosHyperskillApp/Preview Content\""; DEVELOPMENT_TEAM = 3DWS674B2M; ENABLE_PREVIEWS = YES; diff --git a/iosHyperskillApp/iosHyperskillApp/Info.plist b/iosHyperskillApp/iosHyperskillApp/Info.plist index 9b19b6e599..065fb3a1fa 100644 --- a/iosHyperskillApp/iosHyperskillApp/Info.plist +++ b/iosHyperskillApp/iosHyperskillApp/Info.plist @@ -36,7 +36,7 @@ CFBundleVersion - 316 + 317 FirebaseAppDelegateProxyEnabled FirebaseMessagingAutoInitEnabled diff --git a/iosHyperskillApp/iosHyperskillAppTests/Info.plist b/iosHyperskillApp/iosHyperskillAppTests/Info.plist index 7647e77ade..6b7093a8c8 100644 --- a/iosHyperskillApp/iosHyperskillAppTests/Info.plist +++ b/iosHyperskillApp/iosHyperskillAppTests/Info.plist @@ -15,6 +15,6 @@ CFBundleShortVersionString 1.50 CFBundleVersion - 316 + 317 diff --git a/iosHyperskillApp/iosHyperskillAppUITests/Info.plist b/iosHyperskillApp/iosHyperskillAppUITests/Info.plist index 14f133761f..8c223b6655 100644 --- a/iosHyperskillApp/iosHyperskillAppUITests/Info.plist +++ b/iosHyperskillApp/iosHyperskillAppUITests/Info.plist @@ -15,6 +15,6 @@ CFBundleShortVersionString 1.50 CFBundleVersion - 316 + 317 From f2e5ec517f3d3dd9ff07ae82ed8dff39b4af0537 Mon Sep 17 00:00:00 2001 From: Ivan Magda Date: Thu, 8 Feb 2024 09:45:32 +0800 Subject: [PATCH 034/104] Don't show +0 gems after solving a problem of the day (#879) ^ALTAPPS-1124 --- .../CompletedStepOfTheDayDialogFragment.kt | 34 +++++++++++-------- .../Sources/Modules/Step/Views/StepView.swift | 2 +- ...roblemOfDaySolvedModalViewController.swift | 21 ++++++++---- .../StepCompletionActionDispatcher.kt | 14 +++++--- .../presentation/StepCompletionFeature.kt | 4 +-- 5 files changed, 46 insertions(+), 29 deletions(-) diff --git a/androidHyperskillApp/src/main/java/org/hyperskill/app/android/step_quiz/view/dialog/CompletedStepOfTheDayDialogFragment.kt b/androidHyperskillApp/src/main/java/org/hyperskill/app/android/step_quiz/view/dialog/CompletedStepOfTheDayDialogFragment.kt index 3273625254..aca14d6b7f 100644 --- a/androidHyperskillApp/src/main/java/org/hyperskill/app/android/step_quiz/view/dialog/CompletedStepOfTheDayDialogFragment.kt +++ b/androidHyperskillApp/src/main/java/org/hyperskill/app/android/step_quiz/view/dialog/CompletedStepOfTheDayDialogFragment.kt @@ -13,25 +13,28 @@ import by.kirich1409.viewbindingdelegate.viewBinding import com.google.android.material.bottomsheet.BottomSheetBehavior import com.google.android.material.bottomsheet.BottomSheetDialog import com.google.android.material.bottomsheet.BottomSheetDialogFragment +import kotlinx.serialization.Serializable import org.hyperskill.app.android.R import org.hyperskill.app.android.core.extensions.argument import org.hyperskill.app.android.databinding.FragmentCompletedDailyStepBinding import org.hyperskill.app.step.presentation.StepFeature import org.hyperskill.app.step.presentation.StepViewModel import org.hyperskill.app.step_completion.presentation.StepCompletionFeature -import ru.nobird.android.view.base.ui.extension.argument class CompletedStepOfTheDayDialogFragment : BottomSheetDialogFragment() { + companion object { const val TAG = "CompletedStepOfTheDayDialogFragment" fun newInstance( - earnedGemsText: String, + earnedGemsText: String?, shareStreakData: StepCompletionFeature.ShareStreakData ): CompletedStepOfTheDayDialogFragment = CompletedStepOfTheDayDialogFragment().apply { - this.earnedGemsText = earnedGemsText - this.shareStreakData = shareStreakData + this.params = Params( + earnedGemsText = earnedGemsText, + shareStreakData = shareStreakData + ) } } @@ -40,11 +43,7 @@ class CompletedStepOfTheDayDialogFragment : BottomSheetDialogFragment() { private val viewBinding: FragmentCompletedDailyStepBinding by viewBinding(FragmentCompletedDailyStepBinding::bind) - private var earnedGemsText: String by argument() - - private var shareStreakData: StepCompletionFeature.ShareStreakData by argument( - StepCompletionFeature.ShareStreakData.serializer() - ) + private var params: Params by argument(Params.serializer()) override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -80,12 +79,13 @@ class CompletedStepOfTheDayDialogFragment : BottomSheetDialogFragment() { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) with(viewBinding) { - completedDailyStepEarnedGemsTextView.text = earnedGemsText + completedDailyStepEarnedGemsTextView.isVisible = params.earnedGemsText != null + completedDailyStepEarnedGemsTextView.text = params.earnedGemsText completedDailyStepStreakTextView.isVisible = - shareStreakData is StepCompletionFeature.ShareStreakData.Content + params.shareStreakData is StepCompletionFeature.ShareStreakData.Content completedDailyStepStreakTextView.text = - (shareStreakData as? StepCompletionFeature.ShareStreakData.Content)?.streakText + (params.shareStreakData as? StepCompletionFeature.ShareStreakData.Content)?.streakText completedDailyStepGoBackButton.setOnClickListener { stepViewModel.onNewMessage( @@ -97,8 +97,8 @@ class CompletedStepOfTheDayDialogFragment : BottomSheetDialogFragment() { } completedDailyStepShareStreakButton.isVisible = - shareStreakData is StepCompletionFeature.ShareStreakData.Content - (shareStreakData as? StepCompletionFeature.ShareStreakData.Content)?.streak?.let { streak -> + params.shareStreakData is StepCompletionFeature.ShareStreakData.Content + (params.shareStreakData as? StepCompletionFeature.ShareStreakData.Content)?.streak?.let { streak -> completedDailyStepShareStreakButton.setOnClickListener { stepViewModel.onNewMessage( StepFeature.Message.StepCompletionMessage( @@ -118,4 +118,10 @@ class CompletedStepOfTheDayDialogFragment : BottomSheetDialogFragment() { ) ) } + + @Serializable + private data class Params( + val earnedGemsText: String?, + val shareStreakData: StepCompletionFeature.ShareStreakData + ) } \ No newline at end of file diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/Step/Views/StepView.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/Step/Views/StepView.swift index cf64aa8136..a5201a0d94 100644 --- a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/Step/Views/StepView.swift +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/Step/Views/StepView.swift @@ -155,7 +155,7 @@ private extension StepView { } func presentDailyStepCompletedModal( - earnedGemsText: String, + earnedGemsText: String?, shareStreakData: StepCompletionFeatureShareStreakData ) { let modal = ProblemOfDaySolvedModalViewController( diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuiz/Views/Modals/ProblemOfDaySolvedModalViewController.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuiz/Views/Modals/ProblemOfDaySolvedModalViewController.swift index 401034c07a..1fed23add5 100644 --- a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuiz/Views/Modals/ProblemOfDaySolvedModalViewController.swift +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuiz/Views/Modals/ProblemOfDaySolvedModalViewController.swift @@ -40,7 +40,7 @@ final class ProblemOfDaySolvedModalViewController: PanModalPresentableViewContro private(set) var appearance = Appearance() - private let earnedGemsText: String + private let earnedGemsText: String? private let shareStreakData: StepCompletionFeatureShareStreakDataKs private lazy var contentStackView: UIStackView = { @@ -61,7 +61,7 @@ final class ProblemOfDaySolvedModalViewController: PanModalPresentableViewContro override var longFormHeight: PanModalHeight { shortFormHeight } init( - earnedGemsText: String, + earnedGemsText: String?, shareStreakData: StepCompletionFeatureShareStreakDataKs, delegate: ProblemOfDaySolvedModalViewControllerDelegate? ) { @@ -183,6 +183,11 @@ final class ProblemOfDaySolvedModalViewController: PanModalPresentableViewContro return containerStackView } + if earnedGemsText == nil, + case .empty = shareStreakData { + return + } + let itemsStackView = UIStackView() itemsStackView.axis = .vertical itemsStackView.spacing = LayoutInsets.defaultInset @@ -191,12 +196,14 @@ final class ProblemOfDaySolvedModalViewController: PanModalPresentableViewContro contentStackView.addArrangedSubview(itemsStackView) - itemsStackView.addArrangedSubview( - makeItemView( - imageResource: .problemOfDaySolvedModalGemsBadge, - text: earnedGemsText + if let earnedGemsText { + itemsStackView.addArrangedSubview( + makeItemView( + imageResource: .problemOfDaySolvedModalGemsBadge, + text: earnedGemsText + ) ) - ) + } switch shareStreakData { case .content(let data): diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/step_completion/presentation/StepCompletionActionDispatcher.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/step_completion/presentation/StepCompletionActionDispatcher.kt index 956fd5150c..b415938dfa 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/step_completion/presentation/StepCompletionActionDispatcher.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/step_completion/presentation/StepCompletionActionDispatcher.kt @@ -234,11 +234,15 @@ class StepCompletionActionDispatcher( val currentProfileHypercoinsBalance = updateCurrentProfileHypercoinsBalanceRemotely() if (currentProfileHypercoinsBalance != null) { val gemsEarned = currentProfileHypercoinsBalance - cachedProfile.gamification.hypercoinsBalance - val earnedGemsText = resourceProvider.getQuantityString( - SharedResources.plurals.earned_gems, - gemsEarned, - gemsEarned - ) + val earnedGemsText = if (gemsEarned > 0) { + resourceProvider.getQuantityString( + SharedResources.plurals.earned_gems, + gemsEarned, + gemsEarned + ) + } else { + null + } val shareStreakData = if (shouldShareStreak && streakToShare != null) { val daysText = resourceProvider.getQuantityString( diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/step_completion/presentation/StepCompletionFeature.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/step_completion/presentation/StepCompletionFeature.kt index d45cbc99f8..38b6aa31d2 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/step_completion/presentation/StepCompletionFeature.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/step_completion/presentation/StepCompletionFeature.kt @@ -96,7 +96,7 @@ object StepCompletionFeature { * Show problem of day solve modal */ data class ProblemOfDaySolved( - val earnedGemsText: String, + val earnedGemsText: String?, val shareStreakData: ShareStreakData ) : Message object ProblemOfDaySolvedModalGoBackClicked : Message @@ -151,7 +151,7 @@ object StepCompletionFeature { ) : ViewAction data class ShowProblemOfDaySolvedModal( - val earnedGemsText: String, + val earnedGemsText: String?, val shareStreakData: ShareStreakData ) : ViewAction From 2b5290199aaa908b76b171dc164ad72f6c1241bc Mon Sep 17 00:00:00 2001 From: github-actions Date: Thu, 8 Feb 2024 01:46:09 +0000 Subject: [PATCH 035/104] Bump build number --- gradle/app.versions.toml | 2 +- .../NotificationServiceExtension/Info.plist | 2 +- .../iosHyperskillApp.xcodeproj/project.pbxproj | 16 ++++++++-------- iosHyperskillApp/iosHyperskillApp/Info.plist | 2 +- .../iosHyperskillAppTests/Info.plist | 2 +- .../iosHyperskillAppUITests/Info.plist | 2 +- 6 files changed, 13 insertions(+), 13 deletions(-) diff --git a/gradle/app.versions.toml b/gradle/app.versions.toml index ace2b19001..4bdccacfbb 100644 --- a/gradle/app.versions.toml +++ b/gradle/app.versions.toml @@ -3,4 +3,4 @@ minSdk = '24' targetSdk = '33' compileSdk = '33' versionName = '1.50' -versionCode = '315' \ No newline at end of file +versionCode = '316' \ No newline at end of file diff --git a/iosHyperskillApp/NotificationServiceExtension/Info.plist b/iosHyperskillApp/NotificationServiceExtension/Info.plist index 8940a268fc..8d073c7d9a 100644 --- a/iosHyperskillApp/NotificationServiceExtension/Info.plist +++ b/iosHyperskillApp/NotificationServiceExtension/Info.plist @@ -9,7 +9,7 @@ CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleVersion - 317 + 318 CFBundleShortVersionString 1.50 CFBundlePackageType diff --git a/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj b/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj index e3fd78edc0..490bffe2fd 100644 --- a/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj +++ b/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj @@ -5001,7 +5001,7 @@ buildSettings = { CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 317; + CURRENT_PROJECT_VERSION = 318; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = 3DWS674B2M; GENERATE_INFOPLIST_FILE = NO; @@ -5022,7 +5022,7 @@ buildSettings = { CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 317; + CURRENT_PROJECT_VERSION = 318; DEVELOPMENT_TEAM = 3DWS674B2M; GENERATE_INFOPLIST_FILE = NO; INFOPLIST_FILE = iosHyperskillAppUITests/Info.plist; @@ -5043,7 +5043,7 @@ BUNDLE_LOADER = "$(TEST_HOST)"; CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 317; + CURRENT_PROJECT_VERSION = 318; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = 3DWS674B2M; INFOPLIST_FILE = iosHyperskillAppTests/Info.plist; @@ -5064,7 +5064,7 @@ BUNDLE_LOADER = "$(TEST_HOST)"; CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 317; + CURRENT_PROJECT_VERSION = 318; DEVELOPMENT_TEAM = 3DWS674B2M; INFOPLIST_FILE = iosHyperskillAppTests/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; @@ -5085,7 +5085,7 @@ CODE_SIGN_ENTITLEMENTS = NotificationServiceExtension/NotificationServiceExtension.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 317; + CURRENT_PROJECT_VERSION = 318; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = 3DWS674B2M; INFOPLIST_FILE = NotificationServiceExtension/Info.plist; @@ -5113,7 +5113,7 @@ CODE_SIGN_ENTITLEMENTS = NotificationServiceExtension/NotificationServiceExtension.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 317; + CURRENT_PROJECT_VERSION = 318; DEVELOPMENT_TEAM = 3DWS674B2M; INFOPLIST_FILE = NotificationServiceExtension/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; @@ -5258,7 +5258,7 @@ CODE_SIGN_ENTITLEMENTS = iosHyperskillApp/iosHyperskillApp.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 317; + CURRENT_PROJECT_VERSION = 318; DEVELOPMENT_ASSET_PATHS = "\"iosHyperskillApp/Preview Content\""; DEVELOPMENT_TEAM = 3DWS674B2M; ENABLE_PREVIEWS = YES; @@ -5294,7 +5294,7 @@ CODE_SIGN_ENTITLEMENTS = iosHyperskillApp/iosHyperskillApp.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 317; + CURRENT_PROJECT_VERSION = 318; DEVELOPMENT_ASSET_PATHS = "\"iosHyperskillApp/Preview Content\""; DEVELOPMENT_TEAM = 3DWS674B2M; ENABLE_PREVIEWS = YES; diff --git a/iosHyperskillApp/iosHyperskillApp/Info.plist b/iosHyperskillApp/iosHyperskillApp/Info.plist index 065fb3a1fa..d87db559de 100644 --- a/iosHyperskillApp/iosHyperskillApp/Info.plist +++ b/iosHyperskillApp/iosHyperskillApp/Info.plist @@ -36,7 +36,7 @@ CFBundleVersion - 317 + 318 FirebaseAppDelegateProxyEnabled FirebaseMessagingAutoInitEnabled diff --git a/iosHyperskillApp/iosHyperskillAppTests/Info.plist b/iosHyperskillApp/iosHyperskillAppTests/Info.plist index 6b7093a8c8..cee8c7b388 100644 --- a/iosHyperskillApp/iosHyperskillAppTests/Info.plist +++ b/iosHyperskillApp/iosHyperskillAppTests/Info.plist @@ -15,6 +15,6 @@ CFBundleShortVersionString 1.50 CFBundleVersion - 317 + 318 diff --git a/iosHyperskillApp/iosHyperskillAppUITests/Info.plist b/iosHyperskillApp/iosHyperskillAppUITests/Info.plist index 8c223b6655..85444a2554 100644 --- a/iosHyperskillApp/iosHyperskillAppUITests/Info.plist +++ b/iosHyperskillApp/iosHyperskillAppUITests/Info.plist @@ -15,6 +15,6 @@ CFBundleShortVersionString 1.50 CFBundleVersion - 317 + 318 From bbcf037e9c983e60250c75ebfc5c16f7fcac4181 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 9 Feb 2024 10:10:54 +0800 Subject: [PATCH 036/104] GitHub Actions: Bump gradle/wrapper-validation-action (#891) Bumps [gradle/wrapper-validation-action](https://github.com/gradle/wrapper-validation-action) from 2.0.1 to 2.1.0. - [Release notes](https://github.com/gradle/wrapper-validation-action/releases) - [Commits](https://github.com/gradle/wrapper-validation-action/compare/v2.0.1...v2.1.0) --- updated-dependencies: - dependency-name: gradle/wrapper-validation-action dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/android_beta_deployment.yml | 2 +- .github/workflows/android_deploy_to_firebase_manually.yml | 2 +- .github/workflows/android_release_deployment.yml | 2 +- .github/workflows/ci.yml | 2 +- .github/workflows/ios_beta_deployment.yml | 2 +- .github/workflows/ios_release_deployment.yml | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/android_beta_deployment.yml b/.github/workflows/android_beta_deployment.yml index c069395311..54180ff52c 100644 --- a/.github/workflows/android_beta_deployment.yml +++ b/.github/workflows/android_beta_deployment.yml @@ -28,7 +28,7 @@ jobs: uses: actions/checkout@v4.1.1 - name: Gradle Wrapper Validation - uses: gradle/wrapper-validation-action@v2.0.1 + uses: gradle/wrapper-validation-action@v2.1.0 # Build and submit to the Firebase App Distribution firebase-deployment: diff --git a/.github/workflows/android_deploy_to_firebase_manually.yml b/.github/workflows/android_deploy_to_firebase_manually.yml index ba830a6f4a..53151f825a 100644 --- a/.github/workflows/android_deploy_to_firebase_manually.yml +++ b/.github/workflows/android_deploy_to_firebase_manually.yml @@ -17,7 +17,7 @@ jobs: uses: actions/checkout@v4.1.1 - name: Gradle Wrapper Validation - uses: gradle/wrapper-validation-action@v2.0.1 + uses: gradle/wrapper-validation-action@v2.1.0 # Build and submit to the Firebase App Distribution firebase-deployment: diff --git a/.github/workflows/android_release_deployment.yml b/.github/workflows/android_release_deployment.yml index 0af2be86f3..c6cdb90cb8 100644 --- a/.github/workflows/android_release_deployment.yml +++ b/.github/workflows/android_release_deployment.yml @@ -27,7 +27,7 @@ jobs: uses: actions/checkout@v4.1.1 - name: Gradle Wrapper Validation - uses: gradle/wrapper-validation-action@v2.0.1 + uses: gradle/wrapper-validation-action@v2.1.0 # Build and submit to the Google Play deployment: diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 97d102c360..ef96b15f40 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -44,7 +44,7 @@ jobs: uses: actions/checkout@v4.1.1 - name: Gradle Wrapper Validation - uses: gradle/wrapper-validation-action@v2.0.1 + uses: gradle/wrapper-validation-action@v2.1.0 files-changed: name: Detect changes diff --git a/.github/workflows/ios_beta_deployment.yml b/.github/workflows/ios_beta_deployment.yml index fe01e31189..704b37d0e9 100644 --- a/.github/workflows/ios_beta_deployment.yml +++ b/.github/workflows/ios_beta_deployment.yml @@ -28,7 +28,7 @@ jobs: uses: actions/checkout@v4.1.1 - name: Gradle Wrapper Validation - uses: gradle/wrapper-validation-action@v2.0.1 + uses: gradle/wrapper-validation-action@v2.1.0 # Build, archive for ad-hoc and submit to Firebase App Distribution deployment: diff --git a/.github/workflows/ios_release_deployment.yml b/.github/workflows/ios_release_deployment.yml index 5ef6720436..3f53162c4c 100644 --- a/.github/workflows/ios_release_deployment.yml +++ b/.github/workflows/ios_release_deployment.yml @@ -24,7 +24,7 @@ jobs: uses: actions/checkout@v4.1.1 - name: Gradle Wrapper Validation - uses: gradle/wrapper-validation-action@v2.0.1 + uses: gradle/wrapper-validation-action@v2.1.0 # Build, archive for app-store and submit to App Store Connect deployment: From d2a5fbdc7d62cb5c956e3c43d35f0fc2e40d0e9d Mon Sep 17 00:00:00 2001 From: github-actions Date: Fri, 9 Feb 2024 02:11:39 +0000 Subject: [PATCH 037/104] Bump build number --- gradle/app.versions.toml | 2 +- .../NotificationServiceExtension/Info.plist | 2 +- .../iosHyperskillApp.xcodeproj/project.pbxproj | 16 ++++++++-------- iosHyperskillApp/iosHyperskillApp/Info.plist | 2 +- .../iosHyperskillAppTests/Info.plist | 2 +- .../iosHyperskillAppUITests/Info.plist | 2 +- 6 files changed, 13 insertions(+), 13 deletions(-) diff --git a/gradle/app.versions.toml b/gradle/app.versions.toml index 4bdccacfbb..69df46938b 100644 --- a/gradle/app.versions.toml +++ b/gradle/app.versions.toml @@ -3,4 +3,4 @@ minSdk = '24' targetSdk = '33' compileSdk = '33' versionName = '1.50' -versionCode = '316' \ No newline at end of file +versionCode = '317' \ No newline at end of file diff --git a/iosHyperskillApp/NotificationServiceExtension/Info.plist b/iosHyperskillApp/NotificationServiceExtension/Info.plist index 8d073c7d9a..d1d117dc80 100644 --- a/iosHyperskillApp/NotificationServiceExtension/Info.plist +++ b/iosHyperskillApp/NotificationServiceExtension/Info.plist @@ -9,7 +9,7 @@ CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleVersion - 318 + 319 CFBundleShortVersionString 1.50 CFBundlePackageType diff --git a/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj b/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj index 490bffe2fd..fe312cc475 100644 --- a/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj +++ b/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj @@ -5001,7 +5001,7 @@ buildSettings = { CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 318; + CURRENT_PROJECT_VERSION = 319; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = 3DWS674B2M; GENERATE_INFOPLIST_FILE = NO; @@ -5022,7 +5022,7 @@ buildSettings = { CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 318; + CURRENT_PROJECT_VERSION = 319; DEVELOPMENT_TEAM = 3DWS674B2M; GENERATE_INFOPLIST_FILE = NO; INFOPLIST_FILE = iosHyperskillAppUITests/Info.plist; @@ -5043,7 +5043,7 @@ BUNDLE_LOADER = "$(TEST_HOST)"; CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 318; + CURRENT_PROJECT_VERSION = 319; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = 3DWS674B2M; INFOPLIST_FILE = iosHyperskillAppTests/Info.plist; @@ -5064,7 +5064,7 @@ BUNDLE_LOADER = "$(TEST_HOST)"; CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 318; + CURRENT_PROJECT_VERSION = 319; DEVELOPMENT_TEAM = 3DWS674B2M; INFOPLIST_FILE = iosHyperskillAppTests/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; @@ -5085,7 +5085,7 @@ CODE_SIGN_ENTITLEMENTS = NotificationServiceExtension/NotificationServiceExtension.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 318; + CURRENT_PROJECT_VERSION = 319; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = 3DWS674B2M; INFOPLIST_FILE = NotificationServiceExtension/Info.plist; @@ -5113,7 +5113,7 @@ CODE_SIGN_ENTITLEMENTS = NotificationServiceExtension/NotificationServiceExtension.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 318; + CURRENT_PROJECT_VERSION = 319; DEVELOPMENT_TEAM = 3DWS674B2M; INFOPLIST_FILE = NotificationServiceExtension/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; @@ -5258,7 +5258,7 @@ CODE_SIGN_ENTITLEMENTS = iosHyperskillApp/iosHyperskillApp.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 318; + CURRENT_PROJECT_VERSION = 319; DEVELOPMENT_ASSET_PATHS = "\"iosHyperskillApp/Preview Content\""; DEVELOPMENT_TEAM = 3DWS674B2M; ENABLE_PREVIEWS = YES; @@ -5294,7 +5294,7 @@ CODE_SIGN_ENTITLEMENTS = iosHyperskillApp/iosHyperskillApp.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 318; + CURRENT_PROJECT_VERSION = 319; DEVELOPMENT_ASSET_PATHS = "\"iosHyperskillApp/Preview Content\""; DEVELOPMENT_TEAM = 3DWS674B2M; ENABLE_PREVIEWS = YES; diff --git a/iosHyperskillApp/iosHyperskillApp/Info.plist b/iosHyperskillApp/iosHyperskillApp/Info.plist index d87db559de..d1226d1250 100644 --- a/iosHyperskillApp/iosHyperskillApp/Info.plist +++ b/iosHyperskillApp/iosHyperskillApp/Info.plist @@ -36,7 +36,7 @@ CFBundleVersion - 318 + 319 FirebaseAppDelegateProxyEnabled FirebaseMessagingAutoInitEnabled diff --git a/iosHyperskillApp/iosHyperskillAppTests/Info.plist b/iosHyperskillApp/iosHyperskillAppTests/Info.plist index cee8c7b388..7df87083a4 100644 --- a/iosHyperskillApp/iosHyperskillAppTests/Info.plist +++ b/iosHyperskillApp/iosHyperskillAppTests/Info.plist @@ -15,6 +15,6 @@ CFBundleShortVersionString 1.50 CFBundleVersion - 318 + 319 diff --git a/iosHyperskillApp/iosHyperskillAppUITests/Info.plist b/iosHyperskillApp/iosHyperskillAppUITests/Info.plist index 85444a2554..bf7154714b 100644 --- a/iosHyperskillApp/iosHyperskillAppUITests/Info.plist +++ b/iosHyperskillApp/iosHyperskillAppUITests/Info.plist @@ -15,6 +15,6 @@ CFBundleShortVersionString 1.50 CFBundleVersion - 318 + 319 From 63dead1f4ece859a84d3655445900ab4423cbdb5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 9 Feb 2024 10:13:05 +0800 Subject: [PATCH 038/104] GitHub Actions: Bump actions/upload-pages-artifact from 3.0.0 to 3.0.1 (#890) Bumps [actions/upload-pages-artifact](https://github.com/actions/upload-pages-artifact) from 3.0.0 to 3.0.1. - [Release notes](https://github.com/actions/upload-pages-artifact/releases) - [Commits](https://github.com/actions/upload-pages-artifact/compare/v3.0.0...v3.0.1) --- updated-dependencies: - dependency-name: actions/upload-pages-artifact dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Ivan Magda --- .github/workflows/gh_pages_analytics.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/gh_pages_analytics.yml b/.github/workflows/gh_pages_analytics.yml index 342470e986..43da10c456 100644 --- a/.github/workflows/gh_pages_analytics.yml +++ b/.github/workflows/gh_pages_analytics.yml @@ -52,7 +52,7 @@ jobs: GITHUB_PERSONAL_ACCESS_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Upload Artifact - uses: actions/upload-pages-artifact@v3.0.0 + uses: actions/upload-pages-artifact@v3.0.1 with: name: 'github-pages-analytics' path: 'shared/build/dokka/analytics' From 1ae5203ceba476f75c507411574054e2bb3515a8 Mon Sep 17 00:00:00 2001 From: github-actions Date: Fri, 9 Feb 2024 02:13:40 +0000 Subject: [PATCH 039/104] Bump build number --- gradle/app.versions.toml | 2 +- .../NotificationServiceExtension/Info.plist | 2 +- .../iosHyperskillApp.xcodeproj/project.pbxproj | 16 ++++++++-------- iosHyperskillApp/iosHyperskillApp/Info.plist | 2 +- .../iosHyperskillAppTests/Info.plist | 2 +- .../iosHyperskillAppUITests/Info.plist | 2 +- 6 files changed, 13 insertions(+), 13 deletions(-) diff --git a/gradle/app.versions.toml b/gradle/app.versions.toml index 69df46938b..64507582bd 100644 --- a/gradle/app.versions.toml +++ b/gradle/app.versions.toml @@ -3,4 +3,4 @@ minSdk = '24' targetSdk = '33' compileSdk = '33' versionName = '1.50' -versionCode = '317' \ No newline at end of file +versionCode = '318' \ No newline at end of file diff --git a/iosHyperskillApp/NotificationServiceExtension/Info.plist b/iosHyperskillApp/NotificationServiceExtension/Info.plist index d1d117dc80..bcde6259cb 100644 --- a/iosHyperskillApp/NotificationServiceExtension/Info.plist +++ b/iosHyperskillApp/NotificationServiceExtension/Info.plist @@ -9,7 +9,7 @@ CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleVersion - 319 + 320 CFBundleShortVersionString 1.50 CFBundlePackageType diff --git a/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj b/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj index fe312cc475..e09e1cbfe7 100644 --- a/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj +++ b/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj @@ -5001,7 +5001,7 @@ buildSettings = { CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 319; + CURRENT_PROJECT_VERSION = 320; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = 3DWS674B2M; GENERATE_INFOPLIST_FILE = NO; @@ -5022,7 +5022,7 @@ buildSettings = { CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 319; + CURRENT_PROJECT_VERSION = 320; DEVELOPMENT_TEAM = 3DWS674B2M; GENERATE_INFOPLIST_FILE = NO; INFOPLIST_FILE = iosHyperskillAppUITests/Info.plist; @@ -5043,7 +5043,7 @@ BUNDLE_LOADER = "$(TEST_HOST)"; CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 319; + CURRENT_PROJECT_VERSION = 320; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = 3DWS674B2M; INFOPLIST_FILE = iosHyperskillAppTests/Info.plist; @@ -5064,7 +5064,7 @@ BUNDLE_LOADER = "$(TEST_HOST)"; CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 319; + CURRENT_PROJECT_VERSION = 320; DEVELOPMENT_TEAM = 3DWS674B2M; INFOPLIST_FILE = iosHyperskillAppTests/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; @@ -5085,7 +5085,7 @@ CODE_SIGN_ENTITLEMENTS = NotificationServiceExtension/NotificationServiceExtension.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 319; + CURRENT_PROJECT_VERSION = 320; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = 3DWS674B2M; INFOPLIST_FILE = NotificationServiceExtension/Info.plist; @@ -5113,7 +5113,7 @@ CODE_SIGN_ENTITLEMENTS = NotificationServiceExtension/NotificationServiceExtension.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 319; + CURRENT_PROJECT_VERSION = 320; DEVELOPMENT_TEAM = 3DWS674B2M; INFOPLIST_FILE = NotificationServiceExtension/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; @@ -5258,7 +5258,7 @@ CODE_SIGN_ENTITLEMENTS = iosHyperskillApp/iosHyperskillApp.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 319; + CURRENT_PROJECT_VERSION = 320; DEVELOPMENT_ASSET_PATHS = "\"iosHyperskillApp/Preview Content\""; DEVELOPMENT_TEAM = 3DWS674B2M; ENABLE_PREVIEWS = YES; @@ -5294,7 +5294,7 @@ CODE_SIGN_ENTITLEMENTS = iosHyperskillApp/iosHyperskillApp.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 319; + CURRENT_PROJECT_VERSION = 320; DEVELOPMENT_ASSET_PATHS = "\"iosHyperskillApp/Preview Content\""; DEVELOPMENT_TEAM = 3DWS674B2M; ENABLE_PREVIEWS = YES; diff --git a/iosHyperskillApp/iosHyperskillApp/Info.plist b/iosHyperskillApp/iosHyperskillApp/Info.plist index d1226d1250..5febe5601d 100644 --- a/iosHyperskillApp/iosHyperskillApp/Info.plist +++ b/iosHyperskillApp/iosHyperskillApp/Info.plist @@ -36,7 +36,7 @@ CFBundleVersion - 319 + 320 FirebaseAppDelegateProxyEnabled FirebaseMessagingAutoInitEnabled diff --git a/iosHyperskillApp/iosHyperskillAppTests/Info.plist b/iosHyperskillApp/iosHyperskillAppTests/Info.plist index 7df87083a4..3b1fbee922 100644 --- a/iosHyperskillApp/iosHyperskillAppTests/Info.plist +++ b/iosHyperskillApp/iosHyperskillAppTests/Info.plist @@ -15,6 +15,6 @@ CFBundleShortVersionString 1.50 CFBundleVersion - 319 + 320 diff --git a/iosHyperskillApp/iosHyperskillAppUITests/Info.plist b/iosHyperskillApp/iosHyperskillAppUITests/Info.plist index bf7154714b..2da548769b 100644 --- a/iosHyperskillApp/iosHyperskillAppUITests/Info.plist +++ b/iosHyperskillApp/iosHyperskillAppUITests/Info.plist @@ -15,6 +15,6 @@ CFBundleShortVersionString 1.50 CFBundleVersion - 319 + 320 From 82089d1471cfb502abee21d67f54bb654ef10523 Mon Sep 17 00:00:00 2001 From: Ivan Magda Date: Fri, 9 Feb 2024 16:05:10 +0700 Subject: [PATCH 040/104] iOS: Remove elements with the attribute data-mobile-hidden (#893) ^ALTAPPS-1104 --- .../Processing/ContentProcessingInjection.swift | 17 +++++++++++++++++ .../Processing/ContentProcessor.swift | 3 ++- 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Frameworks/ContentProcessor/Processing/ContentProcessingInjection.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Frameworks/ContentProcessor/Processing/ContentProcessingInjection.swift index ca8524c93c..aba6f05389 100644 --- a/iosHyperskillApp/iosHyperskillApp/Sources/Frameworks/ContentProcessor/Processing/ContentProcessingInjection.swift +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Frameworks/ContentProcessor/Processing/ContentProcessingInjection.swift @@ -99,6 +99,23 @@ final class ClickableImagesInjection: ContentProcessingInjection { } } +/// Removes all elements that are marked as hidden on mobile devices. +final class DataMobileHiddenElementsInjection: ContentProcessingInjection { + var headScript: String { + """ + + """ + } +} + /// Disable images callout on long tap final class WebkitImagesCalloutDisableInjection: ContentProcessingInjection { var headScript: String { diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Frameworks/ContentProcessor/Processing/ContentProcessor.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Frameworks/ContentProcessor/Processing/ContentProcessor.swift index 47edadbb05..7fbf68bdc3 100644 --- a/iosHyperskillApp/iosHyperskillApp/Sources/Frameworks/ContentProcessor/Processing/ContentProcessor.swift +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Frameworks/ContentProcessor/Processing/ContentProcessor.swift @@ -16,7 +16,8 @@ final class ContentProcessor: ContentProcessorProtocol { MetaViewportInjection(), HightlightJSInjection(), WebkitImagesCalloutDisableInjection(), - WebScriptInjection() + WebScriptInjection(), + DataMobileHiddenElementsInjection() ] static let defaultRules: [ContentProcessingRule] = [ From e7a0b5cf1eae225ca8cdf26fc73a0dff6cf62b12 Mon Sep 17 00:00:00 2001 From: github-actions Date: Fri, 9 Feb 2024 09:05:47 +0000 Subject: [PATCH 041/104] iOS: Bump build number --- .../NotificationServiceExtension/Info.plist | 2 +- .../iosHyperskillApp.xcodeproj/project.pbxproj | 16 ++++++++-------- iosHyperskillApp/iosHyperskillApp/Info.plist | 2 +- .../iosHyperskillAppTests/Info.plist | 2 +- .../iosHyperskillAppUITests/Info.plist | 2 +- 5 files changed, 12 insertions(+), 12 deletions(-) diff --git a/iosHyperskillApp/NotificationServiceExtension/Info.plist b/iosHyperskillApp/NotificationServiceExtension/Info.plist index bcde6259cb..572a616745 100644 --- a/iosHyperskillApp/NotificationServiceExtension/Info.plist +++ b/iosHyperskillApp/NotificationServiceExtension/Info.plist @@ -9,7 +9,7 @@ CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleVersion - 320 + 321 CFBundleShortVersionString 1.50 CFBundlePackageType diff --git a/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj b/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj index e09e1cbfe7..e715920065 100644 --- a/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj +++ b/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj @@ -5001,7 +5001,7 @@ buildSettings = { CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 320; + CURRENT_PROJECT_VERSION = 321; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = 3DWS674B2M; GENERATE_INFOPLIST_FILE = NO; @@ -5022,7 +5022,7 @@ buildSettings = { CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 320; + CURRENT_PROJECT_VERSION = 321; DEVELOPMENT_TEAM = 3DWS674B2M; GENERATE_INFOPLIST_FILE = NO; INFOPLIST_FILE = iosHyperskillAppUITests/Info.plist; @@ -5043,7 +5043,7 @@ BUNDLE_LOADER = "$(TEST_HOST)"; CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 320; + CURRENT_PROJECT_VERSION = 321; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = 3DWS674B2M; INFOPLIST_FILE = iosHyperskillAppTests/Info.plist; @@ -5064,7 +5064,7 @@ BUNDLE_LOADER = "$(TEST_HOST)"; CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 320; + CURRENT_PROJECT_VERSION = 321; DEVELOPMENT_TEAM = 3DWS674B2M; INFOPLIST_FILE = iosHyperskillAppTests/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; @@ -5085,7 +5085,7 @@ CODE_SIGN_ENTITLEMENTS = NotificationServiceExtension/NotificationServiceExtension.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 320; + CURRENT_PROJECT_VERSION = 321; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = 3DWS674B2M; INFOPLIST_FILE = NotificationServiceExtension/Info.plist; @@ -5113,7 +5113,7 @@ CODE_SIGN_ENTITLEMENTS = NotificationServiceExtension/NotificationServiceExtension.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 320; + CURRENT_PROJECT_VERSION = 321; DEVELOPMENT_TEAM = 3DWS674B2M; INFOPLIST_FILE = NotificationServiceExtension/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; @@ -5258,7 +5258,7 @@ CODE_SIGN_ENTITLEMENTS = iosHyperskillApp/iosHyperskillApp.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 320; + CURRENT_PROJECT_VERSION = 321; DEVELOPMENT_ASSET_PATHS = "\"iosHyperskillApp/Preview Content\""; DEVELOPMENT_TEAM = 3DWS674B2M; ENABLE_PREVIEWS = YES; @@ -5294,7 +5294,7 @@ CODE_SIGN_ENTITLEMENTS = iosHyperskillApp/iosHyperskillApp.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 320; + CURRENT_PROJECT_VERSION = 321; DEVELOPMENT_ASSET_PATHS = "\"iosHyperskillApp/Preview Content\""; DEVELOPMENT_TEAM = 3DWS674B2M; ENABLE_PREVIEWS = YES; diff --git a/iosHyperskillApp/iosHyperskillApp/Info.plist b/iosHyperskillApp/iosHyperskillApp/Info.plist index 5febe5601d..81ec148e16 100644 --- a/iosHyperskillApp/iosHyperskillApp/Info.plist +++ b/iosHyperskillApp/iosHyperskillApp/Info.plist @@ -36,7 +36,7 @@ CFBundleVersion - 320 + 321 FirebaseAppDelegateProxyEnabled FirebaseMessagingAutoInitEnabled diff --git a/iosHyperskillApp/iosHyperskillAppTests/Info.plist b/iosHyperskillApp/iosHyperskillAppTests/Info.plist index 3b1fbee922..39f97dd65c 100644 --- a/iosHyperskillApp/iosHyperskillAppTests/Info.plist +++ b/iosHyperskillApp/iosHyperskillAppTests/Info.plist @@ -15,6 +15,6 @@ CFBundleShortVersionString 1.50 CFBundleVersion - 320 + 321 diff --git a/iosHyperskillApp/iosHyperskillAppUITests/Info.plist b/iosHyperskillApp/iosHyperskillAppUITests/Info.plist index 2da548769b..7d1d1ee4be 100644 --- a/iosHyperskillApp/iosHyperskillAppUITests/Info.plist +++ b/iosHyperskillApp/iosHyperskillAppUITests/Info.plist @@ -15,6 +15,6 @@ CFBundleShortVersionString 1.50 CFBundleVersion - 320 + 321 From 5140dc357d8adc359e9fe0b3e04ec6dffcbdbaee Mon Sep 17 00:00:00 2001 From: Ivan Magda Date: Fri, 9 Feb 2024 17:38:35 +0800 Subject: [PATCH 042/104] ALTAPPS-1127: Add fallback to study plan --- .../presentation/NotificationClickHandlingReducer.kt | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/notification/click_handling/presentation/NotificationClickHandlingReducer.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/notification/click_handling/presentation/NotificationClickHandlingReducer.kt index 2b67e0575d..fed058a9f6 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/notification/click_handling/presentation/NotificationClickHandlingReducer.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/notification/click_handling/presentation/NotificationClickHandlingReducer.kt @@ -137,11 +137,14 @@ class NotificationClickHandlingReducer : StateReducer { Action.ViewAction.SetLoadingShowed(false), when (message) { is NextLearningActivityFetchResult.Success -> { - getStepRouteForNextLearningActivity(message.learningActivity)?.let { stepRoute -> + val stepRoute = getStepRouteForNextLearningActivity(message.learningActivity) + if (stepRoute != null) { Action.ViewAction.NavigateTo.StepScreen(stepRoute) + } else { + Action.ViewAction.NavigateTo.StudyPlan } } - NextLearningActivityFetchResult.Error -> null + NextLearningActivityFetchResult.Error -> Action.ViewAction.NavigateTo.StudyPlan } ) From fcb55bf447315e55565f445e7032aa5a2e0aaaf8 Mon Sep 17 00:00:00 2001 From: github-actions Date: Fri, 9 Feb 2024 09:39:18 +0000 Subject: [PATCH 043/104] Bump build number --- gradle/app.versions.toml | 2 +- .../NotificationServiceExtension/Info.plist | 2 +- .../iosHyperskillApp.xcodeproj/project.pbxproj | 16 ++++++++-------- iosHyperskillApp/iosHyperskillApp/Info.plist | 2 +- .../iosHyperskillAppTests/Info.plist | 2 +- .../iosHyperskillAppUITests/Info.plist | 2 +- 6 files changed, 13 insertions(+), 13 deletions(-) diff --git a/gradle/app.versions.toml b/gradle/app.versions.toml index 64507582bd..aecca36860 100644 --- a/gradle/app.versions.toml +++ b/gradle/app.versions.toml @@ -3,4 +3,4 @@ minSdk = '24' targetSdk = '33' compileSdk = '33' versionName = '1.50' -versionCode = '318' \ No newline at end of file +versionCode = '319' \ No newline at end of file diff --git a/iosHyperskillApp/NotificationServiceExtension/Info.plist b/iosHyperskillApp/NotificationServiceExtension/Info.plist index 572a616745..6e5f7870bd 100644 --- a/iosHyperskillApp/NotificationServiceExtension/Info.plist +++ b/iosHyperskillApp/NotificationServiceExtension/Info.plist @@ -9,7 +9,7 @@ CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleVersion - 321 + 322 CFBundleShortVersionString 1.50 CFBundlePackageType diff --git a/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj b/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj index e715920065..298ca040fb 100644 --- a/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj +++ b/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj @@ -5001,7 +5001,7 @@ buildSettings = { CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 321; + CURRENT_PROJECT_VERSION = 322; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = 3DWS674B2M; GENERATE_INFOPLIST_FILE = NO; @@ -5022,7 +5022,7 @@ buildSettings = { CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 321; + CURRENT_PROJECT_VERSION = 322; DEVELOPMENT_TEAM = 3DWS674B2M; GENERATE_INFOPLIST_FILE = NO; INFOPLIST_FILE = iosHyperskillAppUITests/Info.plist; @@ -5043,7 +5043,7 @@ BUNDLE_LOADER = "$(TEST_HOST)"; CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 321; + CURRENT_PROJECT_VERSION = 322; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = 3DWS674B2M; INFOPLIST_FILE = iosHyperskillAppTests/Info.plist; @@ -5064,7 +5064,7 @@ BUNDLE_LOADER = "$(TEST_HOST)"; CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 321; + CURRENT_PROJECT_VERSION = 322; DEVELOPMENT_TEAM = 3DWS674B2M; INFOPLIST_FILE = iosHyperskillAppTests/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; @@ -5085,7 +5085,7 @@ CODE_SIGN_ENTITLEMENTS = NotificationServiceExtension/NotificationServiceExtension.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 321; + CURRENT_PROJECT_VERSION = 322; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = 3DWS674B2M; INFOPLIST_FILE = NotificationServiceExtension/Info.plist; @@ -5113,7 +5113,7 @@ CODE_SIGN_ENTITLEMENTS = NotificationServiceExtension/NotificationServiceExtension.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 321; + CURRENT_PROJECT_VERSION = 322; DEVELOPMENT_TEAM = 3DWS674B2M; INFOPLIST_FILE = NotificationServiceExtension/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; @@ -5258,7 +5258,7 @@ CODE_SIGN_ENTITLEMENTS = iosHyperskillApp/iosHyperskillApp.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 321; + CURRENT_PROJECT_VERSION = 322; DEVELOPMENT_ASSET_PATHS = "\"iosHyperskillApp/Preview Content\""; DEVELOPMENT_TEAM = 3DWS674B2M; ENABLE_PREVIEWS = YES; @@ -5294,7 +5294,7 @@ CODE_SIGN_ENTITLEMENTS = iosHyperskillApp/iosHyperskillApp.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 321; + CURRENT_PROJECT_VERSION = 322; DEVELOPMENT_ASSET_PATHS = "\"iosHyperskillApp/Preview Content\""; DEVELOPMENT_TEAM = 3DWS674B2M; ENABLE_PREVIEWS = YES; diff --git a/iosHyperskillApp/iosHyperskillApp/Info.plist b/iosHyperskillApp/iosHyperskillApp/Info.plist index 81ec148e16..74bb835283 100644 --- a/iosHyperskillApp/iosHyperskillApp/Info.plist +++ b/iosHyperskillApp/iosHyperskillApp/Info.plist @@ -36,7 +36,7 @@ CFBundleVersion - 321 + 322 FirebaseAppDelegateProxyEnabled FirebaseMessagingAutoInitEnabled diff --git a/iosHyperskillApp/iosHyperskillAppTests/Info.plist b/iosHyperskillApp/iosHyperskillAppTests/Info.plist index 39f97dd65c..eb2ae2e56d 100644 --- a/iosHyperskillApp/iosHyperskillAppTests/Info.plist +++ b/iosHyperskillApp/iosHyperskillAppTests/Info.plist @@ -15,6 +15,6 @@ CFBundleShortVersionString 1.50 CFBundleVersion - 321 + 322 diff --git a/iosHyperskillApp/iosHyperskillAppUITests/Info.plist b/iosHyperskillApp/iosHyperskillAppUITests/Info.plist index 7d1d1ee4be..43108936fa 100644 --- a/iosHyperskillApp/iosHyperskillAppUITests/Info.plist +++ b/iosHyperskillApp/iosHyperskillAppUITests/Info.plist @@ -15,6 +15,6 @@ CFBundleShortVersionString 1.50 CFBundleVersion - 321 + 322 From f9572f40e5aac64567cfeab05e50bf0eb63c6660 Mon Sep 17 00:00:00 2001 From: Ivan Magda Date: Fri, 9 Feb 2024 21:56:33 +0700 Subject: [PATCH 044/104] Shared, iOS: Ask learners to leave review in App Store (#889) ^ALTAPPS-1135 --- .../step/view/delegate/StepDelegate.kt | 2 + .../project.pbxproj | 28 +++ .../UIKit/UIFont+PreferredFont.swift | 10 + .../Sources/Models/Constants/Strings.swift | 3 + .../ProfileSettings/ProfileSettingsView.swift | 3 + .../ProfileSettingsViewModel.swift | 17 ++ .../RequestReviewModalAssembly.swift | 27 +++ .../RequestReviewModalView.swift | 214 ++++++++++++++++++ .../RequestReviewModalViewController.swift | 93 ++++++++ .../RequestReviewModalViewModel.swift | 63 ++++++ .../StageImplementUnsupportedModalView.swift | 5 +- .../TopicCompletedModalViewController.swift | 2 +- .../Sources/Modules/Step/Views/StepView.swift | 7 + ...roblemOfDaySolvedModalViewController.swift | 2 +- ...blemsLimitReachedModalViewController.swift | 2 +- .../StreakFreezeModalViewController.swift | 2 +- .../UIKit/UIKitRoundedRectangleButton.swift | 13 ++ .../hyperskill/HyperskillAnalyticPart.kt | 3 +- .../hyperskill/HyperskillAnalyticTarget.kt | 6 +- .../hyperskill/app/core/injection/AppGraph.kt | 4 + .../app/core/injection/BaseAppGraph.kt | 10 + ...eSettingsClickedHyperskillAnalyticEvent.kt | 18 +- .../presentation/ProfileSettingsFeature.kt | 2 + .../presentation/ProfileSettingsReducer.kt | 8 + .../cache/RequestReviewCacheDataSourceImpl.kt | 22 ++ .../cache/RequestReviewCacheKeyValues.kt | 6 + .../repository/RequestReviewRepositoryImpl.kt | 22 ++ .../source/RequestReviewCacheDataSource.kt | 9 + .../interactor/RequestReviewInteractor.kt | 40 ++++ .../repository/RequestReviewRepository.kt | 9 + .../injection/RequestReviewDataComponent.kt | 7 + .../RequestReviewDataComponentImpl.kt | 24 ++ ...ReviewModalClickHyperskillAnalyticEvent.kt | 62 +++++ ...eviewModalHiddenHyperskillAnalyticEvent.kt | 30 +++ ...ReviewModalShownHyperskillAnalyticEvent.kt | 31 +++ .../injection/RequestReviewModalComponent.kt | 10 + .../RequestReviewModalComponentImpl.kt | 23 ++ .../RequestReviewModalFeatureBuilder.kt | 52 +++++ .../RequestReviewModalActionDispatcher.kt | 23 ++ .../presentation/RequestReviewModalFeature.kt | 45 ++++ .../presentation/RequestReviewModalReducer.kt | 97 ++++++++ .../RequestReviewModalViewStateMapper.kt | 45 ++++ .../injection/StepCompletionComponentImpl.kt | 36 +-- .../StepCompletionActionDispatcher.kt | 18 +- .../presentation/StepCompletionFeature.kt | 7 + .../presentation/StepCompletionReducer.kt | 3 + .../commonMain/resources/MR/base/strings.xml | 12 + 47 files changed, 1147 insertions(+), 30 deletions(-) create mode 100644 iosHyperskillApp/iosHyperskillApp/Sources/Extensions/UIKit/UIFont+PreferredFont.swift create mode 100644 iosHyperskillApp/iosHyperskillApp/Sources/Modules/RequestReview/RequestReviewModalAssembly.swift create mode 100644 iosHyperskillApp/iosHyperskillApp/Sources/Modules/RequestReview/RequestReviewModalView.swift create mode 100644 iosHyperskillApp/iosHyperskillApp/Sources/Modules/RequestReview/RequestReviewModalViewController.swift create mode 100644 iosHyperskillApp/iosHyperskillApp/Sources/Modules/RequestReview/RequestReviewModalViewModel.swift create mode 100644 shared/src/commonMain/kotlin/org/hyperskill/app/request_review/cache/RequestReviewCacheDataSourceImpl.kt create mode 100644 shared/src/commonMain/kotlin/org/hyperskill/app/request_review/cache/RequestReviewCacheKeyValues.kt create mode 100644 shared/src/commonMain/kotlin/org/hyperskill/app/request_review/data/repository/RequestReviewRepositoryImpl.kt create mode 100644 shared/src/commonMain/kotlin/org/hyperskill/app/request_review/data/source/RequestReviewCacheDataSource.kt create mode 100644 shared/src/commonMain/kotlin/org/hyperskill/app/request_review/domain/interactor/RequestReviewInteractor.kt create mode 100644 shared/src/commonMain/kotlin/org/hyperskill/app/request_review/domain/repository/RequestReviewRepository.kt create mode 100644 shared/src/commonMain/kotlin/org/hyperskill/app/request_review/injection/RequestReviewDataComponent.kt create mode 100644 shared/src/commonMain/kotlin/org/hyperskill/app/request_review/injection/RequestReviewDataComponentImpl.kt create mode 100644 shared/src/commonMain/kotlin/org/hyperskill/app/request_review/modal/domain/analytic/RequestReviewModalClickHyperskillAnalyticEvent.kt create mode 100644 shared/src/commonMain/kotlin/org/hyperskill/app/request_review/modal/domain/analytic/RequestReviewModalHiddenHyperskillAnalyticEvent.kt create mode 100644 shared/src/commonMain/kotlin/org/hyperskill/app/request_review/modal/domain/analytic/RequestReviewModalShownHyperskillAnalyticEvent.kt create mode 100644 shared/src/commonMain/kotlin/org/hyperskill/app/request_review/modal/injection/RequestReviewModalComponent.kt create mode 100644 shared/src/commonMain/kotlin/org/hyperskill/app/request_review/modal/injection/RequestReviewModalComponentImpl.kt create mode 100644 shared/src/commonMain/kotlin/org/hyperskill/app/request_review/modal/injection/RequestReviewModalFeatureBuilder.kt create mode 100644 shared/src/commonMain/kotlin/org/hyperskill/app/request_review/modal/presentation/RequestReviewModalActionDispatcher.kt create mode 100644 shared/src/commonMain/kotlin/org/hyperskill/app/request_review/modal/presentation/RequestReviewModalFeature.kt create mode 100644 shared/src/commonMain/kotlin/org/hyperskill/app/request_review/modal/presentation/RequestReviewModalReducer.kt create mode 100644 shared/src/commonMain/kotlin/org/hyperskill/app/request_review/modal/view/mapper/RequestReviewModalViewStateMapper.kt diff --git a/androidHyperskillApp/src/main/java/org/hyperskill/app/android/step/view/delegate/StepDelegate.kt b/androidHyperskillApp/src/main/java/org/hyperskill/app/android/step/view/delegate/StepDelegate.kt index b7f686dc84..951fec4533 100644 --- a/androidHyperskillApp/src/main/java/org/hyperskill/app/android/step/view/delegate/StepDelegate.kt +++ b/androidHyperskillApp/src/main/java/org/hyperskill/app/android/step/view/delegate/StepDelegate.kt @@ -102,6 +102,8 @@ class StepDelegate( manager = fragment.childFragmentManager, tag = InterviewPreparationFinishedDialogFragment.TAG ) + is StepCompletionFeature.Action.ViewAction.ShowRequestUserReviewModal -> + TODO("ALTAPPS-1136: Implement request user review modal") } } } diff --git a/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj b/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj index 88dac8e497..970c5bbed1 100644 --- a/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj +++ b/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj @@ -13,6 +13,7 @@ 058557D9273AAEEB004C7B11 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 058557D8273AAEEB004C7B11 /* Preview Assets.xcassets */; }; 0809817CFCC9D4C45457B3C8 /* ProgressScreenAssembly.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9AACF19B25D42FD4AE322D5A /* ProgressScreenAssembly.swift */; }; 0C3BB55AA2B8FB7F5ED9CADB /* InterviewPreparationOnboardingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A7D7125CEB88C2B8E29ABBB /* InterviewPreparationOnboardingView.swift */; }; + 0F98394636E12DEC98B7953A /* RequestReviewModalAssembly.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4FF61AAE06DC019B8C49543C /* RequestReviewModalAssembly.swift */; }; 2C005DCC27EF5B0300DC6503 /* GoogleServiceInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C005DCB27EF5B0300DC6503 /* GoogleServiceInfo.swift */; }; 2C0146AA28FDF2350083DA9C /* StepQuizCodeFullScreenInputProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C0146A928FDF2350083DA9C /* StepQuizCodeFullScreenInputProtocol.swift */; }; 2C023C86285D927A00D2D5A9 /* StepQuizTableAssembly.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C023C85285D927A00D2D5A9 /* StepQuizTableAssembly.swift */; }; @@ -131,6 +132,9 @@ 2C27C77C28772F8A006A641A /* ImageDecoders+SVG.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C27C77B28772F8A006A641A /* ImageDecoders+SVG.swift */; }; 2C27C77E28773042006A641A /* NukeManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C27C77D28773042006A641A /* NukeManager.swift */; }; 2C2B7DD22946EF2800FAB55D /* WebViewNavigationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C2B7DD12946EF2800FAB55D /* WebViewNavigationController.swift */; }; + 2C2CCB442B74D0E800D1E596 /* RequestReviewModalViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C2CCB432B74D0E800D1E596 /* RequestReviewModalViewController.swift */; }; + 2C2CCB472B74E71600D1E596 /* RequestReviewModalView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C2CCB462B74E71600D1E596 /* RequestReviewModalView.swift */; }; + 2C2CCB492B74FA6600D1E596 /* UIFont+PreferredFont.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C2CCB482B74FA6600D1E596 /* UIFont+PreferredFont.swift */; }; 2C2D492E281151E100753F16 /* AuthCredentialsAssembly.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C2D492D281151E100753F16 /* AuthCredentialsAssembly.swift */; }; 2C2D4930281151EB00753F16 /* AuthCredentialsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C2D492F281151EB00753F16 /* AuthCredentialsViewModel.swift */; }; 2C2D4932281154CB00753F16 /* AppGraph.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C2D4931281154CB00753F16 /* AppGraph.swift */; }; @@ -513,6 +517,7 @@ 63FC2C36279DBA43CCEA1360 /* InterviewPreparationOnboardingViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D9224BDAA50119E9135E1B74 /* InterviewPreparationOnboardingViewModel.swift */; }; 7A628C36D862C98ED2046D4F /* SearchView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 907B10B0F7D4970530A478A2 /* SearchView.swift */; }; 8E154CD6AF7D45A2CA013F85 /* SearchAssembly.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7F55BD539626D22DCF0E1344 /* SearchAssembly.swift */; }; + 91046416561EE431760D7D48 /* RequestReviewModalViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 46D1A5B08EE626D2D612CEAE /* RequestReviewModalViewModel.swift */; }; 9195A8624F8058A7D5F936F8 /* NotificationsOnboardingViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = E3570563AEEEEF2F5495BCA6 /* NotificationsOnboardingViewModel.swift */; }; AE0B2D1D267B8904498FA371 /* ProjectSelectionDetailsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = FEDCC11294912B8656C8B264 /* ProjectSelectionDetailsViewModel.swift */; }; B2B30D0486FC13DCC80F4263 /* NotificationsOnboardingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3944E4546DEF47A28B2E7292 /* NotificationsOnboardingView.swift */; }; @@ -811,6 +816,9 @@ 2C27C77B28772F8A006A641A /* ImageDecoders+SVG.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ImageDecoders+SVG.swift"; sourceTree = ""; }; 2C27C77D28773042006A641A /* NukeManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NukeManager.swift; sourceTree = ""; }; 2C2B7DD12946EF2800FAB55D /* WebViewNavigationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebViewNavigationController.swift; sourceTree = ""; }; + 2C2CCB432B74D0E800D1E596 /* RequestReviewModalViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RequestReviewModalViewController.swift; sourceTree = ""; }; + 2C2CCB462B74E71600D1E596 /* RequestReviewModalView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RequestReviewModalView.swift; sourceTree = ""; }; + 2C2CCB482B74FA6600D1E596 /* UIFont+PreferredFont.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIFont+PreferredFont.swift"; sourceTree = ""; }; 2C2CD8A52AB44CD5007B2581 /* NotificationServiceExtension.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = NotificationServiceExtension.entitlements; sourceTree = ""; }; 2C2D492D281151E100753F16 /* AuthCredentialsAssembly.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthCredentialsAssembly.swift; sourceTree = ""; }; 2C2D492F281151EB00753F16 /* AuthCredentialsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthCredentialsViewModel.swift; sourceTree = ""; }; @@ -1195,6 +1203,8 @@ 2D7F5C51275BBB18DCE9ACE9 /* Pods-iosHyperskillApp.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-iosHyperskillApp.debug.xcconfig"; path = "Target Support Files/Pods-iosHyperskillApp/Pods-iosHyperskillApp.debug.xcconfig"; sourceTree = ""; }; 2E205DEF27554501F7BE01AA /* LeaderboardViewModel.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = LeaderboardViewModel.swift; sourceTree = ""; }; 3944E4546DEF47A28B2E7292 /* NotificationsOnboardingView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = NotificationsOnboardingView.swift; sourceTree = ""; }; + 46D1A5B08EE626D2D612CEAE /* RequestReviewModalViewModel.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = RequestReviewModalViewModel.swift; sourceTree = ""; }; + 4FF61AAE06DC019B8C49543C /* RequestReviewModalAssembly.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = RequestReviewModalAssembly.swift; sourceTree = ""; }; 515FEBC2A5D8EFBA7FB80795 /* Pods-iosHyperskillApp.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-iosHyperskillApp.release.xcconfig"; path = "Target Support Files/Pods-iosHyperskillApp/Pods-iosHyperskillApp.release.xcconfig"; sourceTree = ""; }; 522FFA89F67884772E338BD7 /* LeaderboardView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = LeaderboardView.swift; sourceTree = ""; }; 5FB20AE82459AAF98DA40D48 /* TrackSelectionDetailsViewModel.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = TrackSelectionDetailsViewModel.swift; sourceTree = ""; }; @@ -1711,6 +1721,7 @@ 2C963BC82812D3410036DD53 /* ProfileSettings */, 69443CBBFA46C4A121EA173F /* ProgressScreen */, 2C5CA2452A203C4500DBF2F9 /* ProjectSelection */, + 9C3B04E6CBDCDDFE888D5DAF /* RequestReview */, 3C00014807122833363E303F /* Search */, 2C9E5E8229B211DD003AEC16 /* StageImplement */, 2CAE8CEE280525A100E6C83D /* Step */, @@ -2867,6 +2878,7 @@ 2C5B2A24286596A80097B270 /* UICollectionView+RegisterReusable.swift */, 2C58DE282803D197002A2774 /* UIColor+DynamicColor.swift */, 2C20FBAF284F1D8B006D879E /* UIColor+Hex.swift */, + 2C2CCB482B74FA6600D1E596 /* UIFont+PreferredFont.swift */, 2C7CB6812ADFDB45006F78DA /* UIFont+SizeOfString.swift */, 2CDF14D728EF1E080060D972 /* UINavigationControllerExtensions.swift */, 2C5B2A22286596400097B270 /* UITableView+RegisterReusable.swift */, @@ -3711,6 +3723,17 @@ path = iosHyperskillApp; sourceTree = ""; }; + 9C3B04E6CBDCDDFE888D5DAF /* RequestReview */ = { + isa = PBXGroup; + children = ( + 4FF61AAE06DC019B8C49543C /* RequestReviewModalAssembly.swift */, + 2C2CCB462B74E71600D1E596 /* RequestReviewModalView.swift */, + 2C2CCB432B74D0E800D1E596 /* RequestReviewModalViewController.swift */, + 46D1A5B08EE626D2D612CEAE /* RequestReviewModalViewModel.swift */, + ); + path = RequestReview; + sourceTree = ""; + }; B58361EACE24BF4B761F10BA /* NotificationsOnboarding */ = { isa = PBXGroup; children = ( @@ -4390,6 +4413,7 @@ E9886D3228ABCE5C003724F9 /* WelcomeOutputProtocol.swift in Sources */, 2C5CBBE12948EBEA00113007 /* StepQuizSQLViewDataMapper.swift in Sources */, 2CF4341228126C79002893CD /* View+EndEditing.swift in Sources */, + 2C2CCB472B74E71600D1E596 /* RequestReviewModalView.swift in Sources */, E9F2CC5329223C0200691540 /* StyledHostingController.swift in Sources */, E94F4C152923B46200DE0F7F /* TopicsRepetitionsChartAxis.swift in Sources */, 2C05AC5A2A0ECED90039C7EF /* ProjectSelectionListFeatureViewStateContent+Placeholder.swift in Sources */, @@ -4592,6 +4616,7 @@ 2C93C2D8292EBBB5004D1861 /* AuthSocialFeatureStateKsExtensions.swift in Sources */, 2C4D6EF42AEF9ECC000064C7 /* StepQuizFillBlanksSkeletonView.swift in Sources */, E9101713283296F3002E70F5 /* RadioButton.swift in Sources */, + 2C2CCB492B74FA6600D1E596 /* UIFont+PreferredFont.swift in Sources */, 2C68FD7C2ABC1FF700D9EBE2 /* NotificationsOnboardingContentView.swift in Sources */, 2C20FBC2284F66FC006D879E /* NSAttributedString+TrimmingCharacters.swift in Sources */, 2C20B28A286C350C000F458A /* CodeEditor.swift in Sources */, @@ -4892,6 +4917,7 @@ 2C186ADB2B46989700DADB26 /* TopicsRepetitionsCountView.swift in Sources */, 2CF7C1B12A8355D3006B07ED /* BadgeLockedImageView.swift in Sources */, 2CE8EE6F2B066C2F004EB545 /* ChallengeWidgetContentStateCollectRewardButton.swift in Sources */, + 2C2CCB442B74D0E800D1E596 /* RequestReviewModalViewController.swift in Sources */, 2C32375328380C340062CAF6 /* NavigationToolbarInfoItem.swift in Sources */, 2C406C372A440E8200FA838E /* BuildVariant+Current.swift in Sources */, 2CAA3C6A2AA9C7B6004F6CE6 /* LottieAnimations.swift in Sources */, @@ -4961,6 +4987,8 @@ 4F5F2FD2F3BCAC06612FCAE8 /* InterviewPreparationOnboardingAssembly.swift in Sources */, 0C3BB55AA2B8FB7F5ED9CADB /* InterviewPreparationOnboardingView.swift in Sources */, 63FC2C36279DBA43CCEA1360 /* InterviewPreparationOnboardingViewModel.swift in Sources */, + 0F98394636E12DEC98B7953A /* RequestReviewModalAssembly.swift in Sources */, + 91046416561EE431760D7D48 /* RequestReviewModalViewModel.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Extensions/UIKit/UIFont+PreferredFont.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Extensions/UIKit/UIFont+PreferredFont.swift new file mode 100644 index 0000000000..074f2557af --- /dev/null +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Extensions/UIKit/UIFont+PreferredFont.swift @@ -0,0 +1,10 @@ +import UIKit + +extension UIFont { + static func preferredFont(for style: TextStyle, weight: Weight) -> UIFont { + let metrics = UIFontMetrics(forTextStyle: style) + let desc = UIFontDescriptor.preferredFontDescriptor(withTextStyle: style) + let font = UIFont.systemFont(ofSize: desc.pointSize, weight: weight) + return metrics.scaledFont(for: font) + } +} diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Models/Constants/Strings.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Models/Constants/Strings.swift index 64ced883c4..11a0e78a7e 100644 --- a/iosHyperskillApp/iosHyperskillApp/Sources/Models/Constants/Strings.swift +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Models/Constants/Strings.swift @@ -363,6 +363,9 @@ enum Strings { static let privacyPolicyURL = sharedStrings.settings_privacy_policy_url.localized() static let reportProblemURL = sharedStrings.settings_report_problem_url.localized() + static let rateInAppStore = sharedStrings.settings_rate_in_app_store.localized() + static let rateInAppStoreURL = sharedStrings.settings_rate_in_app_store_url.localized() + enum Theme { static let title = sharedStrings.settings_theme.localized() diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/ProfileSettings/ProfileSettingsView.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/ProfileSettings/ProfileSettingsView.swift index af79f84096..1503f5f416 100644 --- a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/ProfileSettings/ProfileSettingsView.swift +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/ProfileSettings/ProfileSettingsView.swift @@ -129,6 +129,9 @@ struct ProfileSettingsView: View { onTap: viewModel.logClickedReportProblemEvent ) .foregroundColor(.primaryText) + + Button(Strings.Settings.rateInAppStore, action: viewModel.doRateInAppStorePresentation) + .foregroundColor(.primaryText) } Section { diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/ProfileSettings/ProfileSettingsViewModel.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/ProfileSettings/ProfileSettingsViewModel.swift index c18de6eb9b..3395565a0f 100644 --- a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/ProfileSettings/ProfileSettingsViewModel.swift +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/ProfileSettings/ProfileSettingsViewModel.swift @@ -76,6 +76,23 @@ final class ProfileSettingsViewModel: FeatureViewModel< onNewMessage(ProfileSettingsFeatureMessageDeleteAccountNoticeHidden(isConfirmed: isConfirmed)) } + func doRateInAppStorePresentation() { + onNewMessage(ProfileSettingsFeatureMessageClickedRateUsInAppStoreEventMessage()) + + guard let url = URL(string: Strings.Settings.rateInAppStoreURL) else { + return assertionFailure("Invalid URL") + } + + UIApplication.shared.open(url, options: [:]) { success in + if !success { + WebControllerManager.shared.presentWebControllerWithURL( + url, + controllerType: .safari + ) + } + } + } + // MARK: Analytic func logViewedEvent() { diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/RequestReview/RequestReviewModalAssembly.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/RequestReview/RequestReviewModalAssembly.swift new file mode 100644 index 0000000000..4c37d52ee4 --- /dev/null +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/RequestReview/RequestReviewModalAssembly.swift @@ -0,0 +1,27 @@ +import shared +import UIKit + +final class RequestReviewModalAssembly: UIKitAssembly { + private let stepRoute: StepRoute + + init(stepRoute: StepRoute) { + self.stepRoute = stepRoute + } + + func makeModule() -> UIViewController { + let requestReviewModalComponent = AppGraphBridge.sharedAppGraph.buildRequestReviewModalComponent( + stepRoute: stepRoute + ) + + let requestReviewModalViewModel = RequestReviewModalViewModel( + feature: requestReviewModalComponent.requestReviewModalFeature + ) + + let requestReviewModalViewController = RequestReviewModalViewController( + viewModel: requestReviewModalViewModel + ) + requestReviewModalViewModel.viewController = requestReviewModalViewController + + return requestReviewModalViewController + } +} diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/RequestReview/RequestReviewModalView.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/RequestReview/RequestReviewModalView.swift new file mode 100644 index 0000000000..57ef8f4b73 --- /dev/null +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/RequestReview/RequestReviewModalView.swift @@ -0,0 +1,214 @@ +import shared +import SnapKit +import UIKit + +extension RequestReviewModalView { + struct Appearance { + let contentStackViewSpacing: CGFloat = LayoutInsets.defaultInset * 2 + let contentStackViewInsets = LayoutInsets.default + + let textContainerStackViewSpacing: CGFloat = LayoutInsets.defaultInset + let buttonsContainerStackViewSpacing: CGFloat = LayoutInsets.defaultInset + + let titleLabelTextFont = UIFont.preferredFont(for: .largeTitle, weight: .bold) + let titleLabelTextColor = UIColor.newPrimaryText + + let descriptionLabelTextFont = UIFont.preferredFont(forTextStyle: .headline) + let descriptionLabelTextColor = UIColor.newPrimaryText + + let positiveButtonButtonHeight: CGFloat = 50 + let negativeButtonButtonHeight: CGFloat = 44 + + let backgroundColor = UIColor.systemBackground + } +} + +final class RequestReviewModalView: UIView { + let appearance: Appearance + + private lazy var contentStackView: UIStackView = { + let stackView = UIStackView() + stackView.axis = .vertical + stackView.spacing = appearance.contentStackViewSpacing + stackView.alignment = .leading + stackView.distribution = .fill + return stackView + }() + + private lazy var textContainerStackView: UIStackView = { + let stackView = UIStackView() + stackView.axis = .vertical + stackView.spacing = appearance.textContainerStackViewSpacing + stackView.alignment = .leading + stackView.distribution = .fill + return stackView + }() + + private lazy var buttonsContainerStackView: UIStackView = { + let stackView = UIStackView() + stackView.axis = .vertical + stackView.spacing = appearance.buttonsContainerStackViewSpacing + stackView.alignment = .leading + stackView.distribution = .fill + return stackView + }() + + private lazy var titleLabel: UILabel = { + let label = UILabel() + label.font = appearance.titleLabelTextFont + label.textColor = appearance.titleLabelTextColor + label.lineBreakMode = .byWordWrapping + label.numberOfLines = 0 + return label + }() + + private lazy var descriptionLabel: UILabel = { + let label = UILabel() + label.font = appearance.descriptionLabelTextFont + label.textColor = appearance.descriptionLabelTextColor + label.lineBreakMode = .byWordWrapping + label.numberOfLines = 0 + label.isHidden = true + return label + }() + + private lazy var positiveButton: UIButton = { + let button = UIKitRoundedRectangleButton.primary + button.addTarget(self, action: #selector(positiveButtonTapped), for: .touchUpInside) + return button + }() + + private lazy var negativeButton: UIButton = { + let button = UIButton(type: .system) + button.addTarget(self, action: #selector(negativeButtonTapped), for: .touchUpInside) + return button + }() + + var onPositiveButtonTap: (() -> Void)? + var onNegativeButtonTap: (() -> Void)? + + override var intrinsicContentSize: CGSize { + let contentStackViewSize = contentStackView.systemLayoutSizeFitting(UIView.layoutFittingCompressedSize) + + let height = appearance.contentStackViewInsets.top + + contentStackViewSize.height + + appearance.contentStackViewInsets.bottom + + return CGSize(width: UIView.noIntrinsicMetric, height: height) + } + + init( + frame: CGRect = .zero, + appearance: Appearance = Appearance() + ) { + self.appearance = appearance + super.init(frame: frame) + + self.setupView() + self.addSubviews() + self.makeConstraints() + } + + @available(*, unavailable) + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + func renderState(_ state: RequestReviewModalFeature.ViewState) { + titleLabel.text = state.title + + descriptionLabel.text = state.description_ + descriptionLabel.isHidden = state.description_?.isEmpty ?? true + + positiveButton.setTitle(state.positiveButtonText, for: .normal) + negativeButton.setTitle(state.negativeButtonText, for: .normal) + + layoutIfNeeded() + invalidateIntrinsicContentSize() + } + + @objc + private func positiveButtonTapped() { + onPositiveButtonTap?() + } + + @objc + private func negativeButtonTapped() { + onNegativeButtonTap?() + } +} + +extension RequestReviewModalView: ProgrammaticallyInitializableViewProtocol { + func setupView() { + backgroundColor = appearance.backgroundColor + } + + func addSubviews() { + addSubview(contentStackView) + + contentStackView.addArrangedSubview(textContainerStackView) + textContainerStackView.addArrangedSubview(titleLabel) + textContainerStackView.addArrangedSubview(descriptionLabel) + + contentStackView.addArrangedSubview(buttonsContainerStackView) + buttonsContainerStackView.addArrangedSubview(positiveButton) + buttonsContainerStackView.addArrangedSubview(negativeButton) + } + + func makeConstraints() { + contentStackView.translatesAutoresizingMaskIntoConstraints = false + contentStackView.snp.makeConstraints { make in + make.top.equalToSuperview().offset(appearance.contentStackViewInsets.top) + make.leading.equalTo(safeAreaLayoutGuide).offset(appearance.contentStackViewInsets.leading) + make.bottom.lessThanOrEqualToSuperview().offset(-appearance.contentStackViewInsets.bottom) + make.trailing.equalTo(safeAreaLayoutGuide).offset(-appearance.contentStackViewInsets.trailing) + } + + buttonsContainerStackView.translatesAutoresizingMaskIntoConstraints = false + buttonsContainerStackView.snp.makeConstraints { make in + make.width.equalToSuperview() + } + + positiveButton.translatesAutoresizingMaskIntoConstraints = false + positiveButton.snp.makeConstraints { make in + make.width.equalToSuperview() + make.height.equalTo(appearance.positiveButtonButtonHeight) + } + + negativeButton.translatesAutoresizingMaskIntoConstraints = false + negativeButton.snp.makeConstraints { make in + make.width.equalToSuperview() + make.height.equalTo(appearance.negativeButtonButtonHeight) + } + } +} + +#if DEBUG +@available(iOS 17.0, *) +#Preview { + let view = RequestReviewModalView() + view.renderState( + RequestReviewModalFeature.ViewState( + title: "Do you enjoy\nHyperskill app?", + description: nil, + positiveButtonText: "Yes", + negativeButtonText: "No" + ) + ) + return view +} + +@available(iOS 17.0, *) +#Preview { + let view = RequestReviewModalView() + view.renderState( + RequestReviewModalFeature.ViewState( + title: "Thank you!", + description: "Share what you disliked to help us improve your experience.", + positiveButtonText: "Write a request", + negativeButtonText: "Maybe later" + ) + ) + return view +} +#endif diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/RequestReview/RequestReviewModalViewController.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/RequestReview/RequestReviewModalViewController.swift new file mode 100644 index 0000000000..35b6a422fc --- /dev/null +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/RequestReview/RequestReviewModalViewController.swift @@ -0,0 +1,93 @@ +import PanModal +import shared +import StoreKit +import UIKit + +protocol RequestReviewModalViewControllerProtocol: AnyObject { + func displayState(_ state: RequestReviewModalFeature.ViewState) + func displayViewAction(_ viewAction: RequestReviewModalFeatureActionViewActionKs) +} + +final class RequestReviewModalViewController: PanModalPresentableViewController { + private let viewModel: RequestReviewModalViewModel + + var requestReviewModalView: RequestReviewModalView? { view as? RequestReviewModalView } + + override var shortFormHeight: PanModalHeight { .contentHeight(view.intrinsicContentSize.height) } + + override var longFormHeight: PanModalHeight { shortFormHeight } + + init(viewModel: RequestReviewModalViewModel) { + self.viewModel = viewModel + super.init() + } + + override func loadView() { + let view = RequestReviewModalView() + view.onPositiveButtonTap = { [weak self] in + FeedbackGenerator(feedbackType: .selection).triggerFeedback() + self?.viewModel.doPositiveButtonAction() + } + view.onNegativeButtonTap = { [weak self] in + FeedbackGenerator(feedbackType: .selection).triggerFeedback() + self?.viewModel.doNegativeButtonAction() + } + self.view = view + } + + override func viewDidLoad() { + super.viewDidLoad() + viewModel.logShownEvent() + } + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + viewModel.startListening() + } + + override func viewWillDisappear(_ animated: Bool) { + super.viewWillDisappear(animated) + viewModel.stopListening() + } + + override func panModalWillDismiss() { + viewModel.logHiddenEvent() + } +} + +extension RequestReviewModalViewController: RequestReviewModalViewControllerProtocol { + func displayState(_ state: RequestReviewModalFeature.ViewState) { + requestReviewModalView?.renderState(state) + + panModalSetNeedsLayoutUpdate() + panModalTransition(to: .shortForm) + } + + func displayViewAction(_ viewAction: RequestReviewModalFeatureActionViewActionKs) { + switch viewAction { + case .dismiss: + dismiss(animated: true) + case .requestUserReview: + dismiss( + animated: true, + completion: { + if let scene = UIApplication.shared.connectedScenes.first( + where: { $0.activationState == .foregroundActive } + ) as? UIWindowScene { + SKStoreReviewController.requestReview(in: scene) + } + } + ) + case .submitSupportRequest(let submitSupportRequestViewAction): + dismiss( + animated: true, + completion: { + WebControllerManager.shared.presentWebControllerWithURLString( + submitSupportRequestViewAction.url, + controllerType: .safari + ) + } + ) + } + } +} diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/RequestReview/RequestReviewModalViewModel.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/RequestReview/RequestReviewModalViewModel.swift new file mode 100644 index 0000000000..9e8df30a11 --- /dev/null +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/RequestReview/RequestReviewModalViewModel.swift @@ -0,0 +1,63 @@ +import Combine +import Foundation +import shared + +final class RequestReviewModalViewModel: FeatureViewModel< + RequestReviewModalFeature.ViewState, + RequestReviewModalFeatureMessage, + RequestReviewModalFeatureActionViewAction +> { + weak var viewController: RequestReviewModalViewController? + + private var isFirstStateDidChange = true + private var objectWillChangeSubscription: AnyCancellable? + + init(feature: Presentation_reduxFeature) { + super.init(feature: feature) + + self.objectWillChangeSubscription = objectWillChange.sink { [weak self] _ in + self?.mainScheduler.schedule { [weak self] in + if let strongSelf = self { + strongSelf.viewController?.displayState(strongSelf.state) + } + } + } + self.onViewAction = { [weak self] viewAction in + self?.mainScheduler.schedule { [weak self] in + if let strongSelf = self { + strongSelf.viewController?.displayViewAction( + RequestReviewModalFeatureActionViewActionKs(viewAction) + ) + } + } + } + } + + override func shouldNotifyStateDidChange( + oldState: RequestReviewModalFeature.ViewState, + newState: RequestReviewModalFeature.ViewState + ) -> Bool { + if isFirstStateDidChange { + isFirstStateDidChange = false + return true + } else { + return !oldState.isEqual(newState) + } + } + + func doPositiveButtonAction() { + onNewMessage(RequestReviewModalFeatureMessagePositiveButtonClicked()) + } + + func doNegativeButtonAction() { + onNewMessage(RequestReviewModalFeatureMessageNegativeButtonClicked()) + } + + func logShownEvent() { + onNewMessage(RequestReviewModalFeatureMessageShownEventMessage()) + } + + func logHiddenEvent() { + onNewMessage(RequestReviewModalFeatureMessageHiddenEventMessage()) + } +} diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StageImplement/Modals/Unsupported/StageImplementUnsupportedModalView.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StageImplement/Modals/Unsupported/StageImplementUnsupportedModalView.swift index 36bd2fd746..71d1d78969 100644 --- a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StageImplement/Modals/Unsupported/StageImplementUnsupportedModalView.swift +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StageImplement/Modals/Unsupported/StageImplementUnsupportedModalView.swift @@ -15,10 +15,7 @@ extension StageImplementUnsupportedModalView { let textContainerStackViewSpacing: CGFloat = LayoutInsets.defaultInset let titleLabelText = Strings.StageImplement.UnsupportedModal.title - let titleLabelTextFont = UIFont.preferredFont( - forTextStyle: .title2, - compatibleWith: .init(legibilityWeight: .bold) - ) + let titleLabelTextFont = UIFont.preferredFont(for: .title2, weight: .bold) let titleLabelTextColor = UIColor.primaryText let descriptionLabelText = Strings.StageImplement.UnsupportedModal.description diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/Step/Views/Modals/TopicCompletedModalViewController.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/Step/Views/Modals/TopicCompletedModalViewController.swift index 6660d56748..5e2a17d43d 100644 --- a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/Step/Views/Modals/TopicCompletedModalViewController.swift +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/Step/Views/Modals/TopicCompletedModalViewController.swift @@ -123,7 +123,7 @@ final class TopicCompletedModalViewController: PanModalPresentableViewController let titleLabel = UILabel() titleLabel.text = Strings.Common.goodJob - titleLabel.font = .preferredFont(forTextStyle: .largeTitle, compatibleWith: .init(legibilityWeight: .bold)) + titleLabel.font = .preferredFont(for: .largeTitle, weight: .bold) titleLabel.textColor = .primaryText titleLabel.lineBreakMode = .byWordWrapping titleLabel.numberOfLines = 0 diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/Step/Views/StepView.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/Step/Views/StepView.swift index a5201a0d94..47aed9a8d2 100644 --- a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/Step/Views/StepView.swift +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/Step/Views/StepView.swift @@ -127,6 +127,8 @@ struct StepView: View { presentShareStreakSystemModal(streak: Int(showShareStreakSystemModalViewAction.streak)) case .showInterviewPreparationCompletedModal: presentInterviewPreparationFinishedModal() + case .showRequestUserReviewModal(let showRequestUserReviewModalViewAction): + presentRequestReviewModal(stepRoute: showRequestUserReviewModalViewAction.stepRoute) } } @@ -183,6 +185,11 @@ private extension StepView { let modal = InterviewPreparationCompletedModalViewController(delegate: viewModel) panModalPresenter.presentPanModal(modal) } + + func presentRequestReviewModal(stepRoute: StepRoute) { + let assembly = RequestReviewModalAssembly(stepRoute: stepRoute) + panModalPresenter.presentIfPanModal(assembly.makeModule()) + } } // MARK: - StepView_Previews: PreviewProvider - diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuiz/Views/Modals/ProblemOfDaySolvedModalViewController.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuiz/Views/Modals/ProblemOfDaySolvedModalViewController.swift index 1fed23add5..1b19ceb890 100644 --- a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuiz/Views/Modals/ProblemOfDaySolvedModalViewController.swift +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuiz/Views/Modals/ProblemOfDaySolvedModalViewController.swift @@ -137,7 +137,7 @@ final class ProblemOfDaySolvedModalViewController: PanModalPresentableViewContro private func setupTitleView() { let label = UILabel() label.text = Strings.StepQuiz.ProblemOfDaySolvedModal.title - label.font = .preferredFont(forTextStyle: .largeTitle, compatibleWith: .init(legibilityWeight: .bold)) + label.font = .preferredFont(for: .largeTitle, weight: .bold) label.textColor = .primaryText label.lineBreakMode = .byWordWrapping label.numberOfLines = 0 diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuiz/Views/Modals/ProblemsLimitReachedModalViewController.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuiz/Views/Modals/ProblemsLimitReachedModalViewController.swift index 93873148b0..c4aee175a7 100644 --- a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuiz/Views/Modals/ProblemsLimitReachedModalViewController.swift +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuiz/Views/Modals/ProblemsLimitReachedModalViewController.swift @@ -131,7 +131,7 @@ final class ProblemsLimitReachedModalViewController: PanModalPresentableViewCont let titleLabel = UILabel() titleLabel.text = Strings.StepQuiz.ProblemsLimitReachedModal.title - titleLabel.font = .preferredFont(forTextStyle: .title2, compatibleWith: .init(legibilityWeight: .bold)) + titleLabel.font = .preferredFont(for: .title2, weight: .bold) titleLabel.textColor = .primaryText titleLabel.lineBreakMode = .byWordWrapping titleLabel.numberOfLines = 0 diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/Streak/Views/Modals/StreakFreezeModalViewController.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/Streak/Views/Modals/StreakFreezeModalViewController.swift index 88383b3fac..cbb549aac8 100644 --- a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/Streak/Views/Modals/StreakFreezeModalViewController.swift +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/Streak/Views/Modals/StreakFreezeModalViewController.swift @@ -109,7 +109,7 @@ final class StreakFreezeModalViewController: PanModalPresentableViewController { private func setupTitleView() { let label = UILabel() label.text = streakFreezeState.title - label.font = .preferredFont(forTextStyle: .largeTitle, compatibleWith: .init(legibilityWeight: .bold)) + label.font = .preferredFont(for: .largeTitle, weight: .bold) label.textColor = .primaryText label.lineBreakMode = .byWordWrapping label.numberOfLines = 0 diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Views/UIKit/UIKitRoundedRectangleButton.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Views/UIKit/UIKitRoundedRectangleButton.swift index 20df28715a..3c876ac59c 100644 --- a/iosHyperskillApp/iosHyperskillApp/Sources/Views/UIKit/UIKitRoundedRectangleButton.swift +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Views/UIKit/UIKitRoundedRectangleButton.swift @@ -115,3 +115,16 @@ final class UIKitRoundedRectangleButton: UIKitBounceButton { } } } + +extension UIKitRoundedRectangleButton { + static var primary: UIKitRoundedRectangleButton { + UIKitRoundedRectangleButton( + style: .violet, + appearance: .init( + font: .preferredFont(for: .body, weight: .bold), + intrinsicHeight: 50, + cornerRadius: 13 + ) + ) + } +} diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/analytic/domain/model/hyperskill/HyperskillAnalyticPart.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/analytic/domain/model/hyperskill/HyperskillAnalyticPart.kt index f9b0857952..d4ca91ea09 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/analytic/domain/model/hyperskill/HyperskillAnalyticPart.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/analytic/domain/model/hyperskill/HyperskillAnalyticPart.kt @@ -42,5 +42,6 @@ enum class HyperskillAnalyticPart(val partName: String) { SEARCH_RESULTS("search_results"), DAILY_STUDY_REMINDERS_HOUR_INTERVAL_PICKER_MODAL("daily_study_reminders_hour_interval_picker_modal"), INTERVIEW_PREPARATION_WIDGET("interview_preparation_widget"), - INTERVIEW_PREPARATION_COMPLETED_MODAL("interview_preparation_completed_modal") + INTERVIEW_PREPARATION_COMPLETED_MODAL("interview_preparation_completed_modal"), + REQUEST_REVIEW_MODAL("request_review_modal") } \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/analytic/domain/model/hyperskill/HyperskillAnalyticTarget.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/analytic/domain/model/hyperskill/HyperskillAnalyticTarget.kt index d2ab8a9ee9..03e27ab952 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/analytic/domain/model/hyperskill/HyperskillAnalyticTarget.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/analytic/domain/model/hyperskill/HyperskillAnalyticTarget.kt @@ -27,6 +27,7 @@ enum class HyperskillAnalyticTarget(val targetName: String) { SEND_FEEDBACK("send_feedback"), DELETE_ACCOUNT("delete_account"), DELETE_ACCOUNT_NOTICE("delete_account_notice"), + RATE_US_IN_APP_STORE("rate_us_in_app_store"), SIGN_OUT_NOTICE("sign_out_notice"), NOTIFICATIONS_SYSTEM_NOTICE("notifications_system_notice"), VIEW_FULL_PROFILE("view_full_profile"), @@ -111,5 +112,8 @@ enum class HyperskillAnalyticTarget(val targetName: String) { CONFIRM("confirm"), GO_TO_FIRST_PROBLEM("go_to_first_problem"), INTERVIEW_PREPARATION_COMPLETED_MODAL("interview_preparation_completed_modal"), - HOME_SCREEN_QUICK_ACTION("home_screen_quick_action") + HOME_SCREEN_QUICK_ACTION("home_screen_quick_action"), + REQUEST_REVIEW_MODAL("request_review_modal"), + WRITE_A_REQUEST("write_a_request"), + MAYBE_LATER("maybe_later") } \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/core/injection/AppGraph.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/core/injection/AppGraph.kt index 4e10af041d..32f9004546 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/core/injection/AppGraph.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/core/injection/AppGraph.kt @@ -51,6 +51,8 @@ import org.hyperskill.app.project_selection.list.injection.ProjectSelectionListC import org.hyperskill.app.projects.injection.ProjectsDataComponent import org.hyperskill.app.providers.injection.ProvidersDataComponent import org.hyperskill.app.reactions.injection.ReactionsDataComponent +import org.hyperskill.app.request_review.injection.RequestReviewDataComponent +import org.hyperskill.app.request_review.modal.injection.RequestReviewModalComponent import org.hyperskill.app.search.injection.SearchComponent import org.hyperskill.app.search_results.injection.SearchResultsDataComponent import org.hyperskill.app.sentry.injection.SentryComponent @@ -175,4 +177,6 @@ interface AppGraph { fun buildWelcomeOnboardingComponent(): WelcomeOnboardingComponent fun buildInterviewPreparationWidgetComponent(): InterviewPreparationWidgetComponent fun buildInterviewPreparationOnboardingComponent(): InterviewPreparationOnboardingComponent + fun buildRequestReviewDataComponent(): RequestReviewDataComponent + fun buildRequestReviewModalComponent(stepRoute: StepRoute): RequestReviewModalComponent } \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/core/injection/BaseAppGraph.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/core/injection/BaseAppGraph.kt index 905bed3667..3514a791d8 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/core/injection/BaseAppGraph.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/core/injection/BaseAppGraph.kt @@ -96,6 +96,10 @@ import org.hyperskill.app.providers.injection.ProvidersDataComponent import org.hyperskill.app.providers.injection.ProvidersDataComponentImpl import org.hyperskill.app.reactions.injection.ReactionsDataComponent import org.hyperskill.app.reactions.injection.ReactionsDataComponentImpl +import org.hyperskill.app.request_review.injection.RequestReviewDataComponent +import org.hyperskill.app.request_review.injection.RequestReviewDataComponentImpl +import org.hyperskill.app.request_review.modal.injection.RequestReviewModalComponent +import org.hyperskill.app.request_review.modal.injection.RequestReviewModalComponentImpl import org.hyperskill.app.search.injection.SearchComponent import org.hyperskill.app.search.injection.SearchComponentImpl import org.hyperskill.app.search_results.injection.SearchResultsDataComponent @@ -474,4 +478,10 @@ abstract class BaseAppGraph : AppGraph { override fun buildInterviewPreparationOnboardingComponent(): InterviewPreparationOnboardingComponent = InterviewPreparationOnboardingComponentImpl(this) + + override fun buildRequestReviewDataComponent(): RequestReviewDataComponent = + RequestReviewDataComponentImpl(this) + + override fun buildRequestReviewModalComponent(stepRoute: StepRoute): RequestReviewModalComponent = + RequestReviewModalComponentImpl(appGraph = this, stepRoute = stepRoute) } \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/profile_settings/domain/analytic/ProfileSettingsClickedHyperskillAnalyticEvent.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/profile_settings/domain/analytic/ProfileSettingsClickedHyperskillAnalyticEvent.kt index 36f6c28160..4089b8234c 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/profile_settings/domain/analytic/ProfileSettingsClickedHyperskillAnalyticEvent.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/profile_settings/domain/analytic/ProfileSettingsClickedHyperskillAnalyticEvent.kt @@ -78,9 +78,25 @@ import org.hyperskill.app.analytic.domain.model.hyperskill.HyperskillAnalyticTar * "target": "delete_account" * } * ``` + * + * Click on the "Rate us in the App Store" button: + * ``` + * { + * "route": "/profile/settings", + * "action": "click", + * "part": "main", + * "target": "rate_us_in_app_store" + * } + * ``` + * * @see HyperskillAnalyticEvent */ class ProfileSettingsClickedHyperskillAnalyticEvent( part: HyperskillAnalyticPart = HyperskillAnalyticPart.MAIN, target: HyperskillAnalyticTarget -) : HyperskillAnalyticEvent(HyperskillAnalyticRoute.Profile.Settings(), HyperskillAnalyticAction.CLICK, part, target) \ No newline at end of file +) : HyperskillAnalyticEvent( + HyperskillAnalyticRoute.Profile.Settings(), + HyperskillAnalyticAction.CLICK, + part, + target +) \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/profile_settings/presentation/ProfileSettingsFeature.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/profile_settings/presentation/ProfileSettingsFeature.kt index f345e3b3e7..2bd1685de0 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/profile_settings/presentation/ProfileSettingsFeature.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/profile_settings/presentation/ProfileSettingsFeature.kt @@ -54,6 +54,8 @@ interface ProfileSettingsFeature { data class SignOutNoticeHiddenEventMessage(val isConfirmed: Boolean) : Message object ClickedDeleteAccountEventMessage : Message object DeleteAccountNoticeShownEventMessage : Message + + object ClickedRateUsInAppStoreEventMessage : Message } sealed interface Action { diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/profile_settings/presentation/ProfileSettingsReducer.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/profile_settings/presentation/ProfileSettingsReducer.kt index a9cb159c76..87faee287f 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/profile_settings/presentation/ProfileSettingsReducer.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/profile_settings/presentation/ProfileSettingsReducer.kt @@ -135,6 +135,14 @@ class ProfileSettingsReducer : StateReducer { state to setOf(analyticsAction) } } + Message.ClickedRateUsInAppStoreEventMessage -> + state to setOf( + Action.LogAnalyticEvent( + ProfileSettingsClickedHyperskillAnalyticEvent( + target = HyperskillAnalyticTarget.RATE_US_IN_APP_STORE + ) + ) + ) is Message.GetMagicLinkReceiveSuccess -> { if (state is State.Content) { state.copy(isLoadingMagicLink = false) to setOf(Action.ViewAction.OpenUrl(message.url)) diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/request_review/cache/RequestReviewCacheDataSourceImpl.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/request_review/cache/RequestReviewCacheDataSourceImpl.kt new file mode 100644 index 0000000000..5fad942016 --- /dev/null +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/request_review/cache/RequestReviewCacheDataSourceImpl.kt @@ -0,0 +1,22 @@ +package org.hyperskill.app.request_review.cache + +import com.russhwolf.settings.Settings +import org.hyperskill.app.request_review.data.source.RequestReviewCacheDataSource + +internal class RequestReviewCacheDataSourceImpl( + private val settings: Settings +) : RequestReviewCacheDataSource { + override fun getLastRequestReviewTimestamp(): Long? = + settings.getLongOrNull(RequestReviewCacheKeyValues.LAST_REQUEST_REVIEW_TIMESTAMP) + + override fun setLastRequestReviewTimestamp(timestamp: Long) { + settings.putLong(RequestReviewCacheKeyValues.LAST_REQUEST_REVIEW_TIMESTAMP, timestamp) + } + + override fun getRequestReviewCount(): Int = + settings.getInt(RequestReviewCacheKeyValues.REQUEST_REVIEW_COUNT, 0) + + override fun incrementRequestReviewCount() { + settings.putInt(RequestReviewCacheKeyValues.REQUEST_REVIEW_COUNT, getRequestReviewCount() + 1) + } +} \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/request_review/cache/RequestReviewCacheKeyValues.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/request_review/cache/RequestReviewCacheKeyValues.kt new file mode 100644 index 0000000000..bb6b938f7c --- /dev/null +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/request_review/cache/RequestReviewCacheKeyValues.kt @@ -0,0 +1,6 @@ +package org.hyperskill.app.request_review.cache + +internal object RequestReviewCacheKeyValues { + const val LAST_REQUEST_REVIEW_TIMESTAMP = "last_request_review_timestamp" + const val REQUEST_REVIEW_COUNT = "request_review_count" +} \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/request_review/data/repository/RequestReviewRepositoryImpl.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/request_review/data/repository/RequestReviewRepositoryImpl.kt new file mode 100644 index 0000000000..50154168b4 --- /dev/null +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/request_review/data/repository/RequestReviewRepositoryImpl.kt @@ -0,0 +1,22 @@ +package org.hyperskill.app.request_review.data.repository + +import org.hyperskill.app.request_review.data.source.RequestReviewCacheDataSource +import org.hyperskill.app.request_review.domain.repository.RequestReviewRepository + +internal class RequestReviewRepositoryImpl( + private val requestReviewCacheDataSource: RequestReviewCacheDataSource +) : RequestReviewRepository { + override fun getLastRequestReviewTimestamp(): Long? = + requestReviewCacheDataSource.getLastRequestReviewTimestamp() + + override fun setLastRequestReviewTimestamp(timestamp: Long) { + requestReviewCacheDataSource.setLastRequestReviewTimestamp(timestamp) + } + + override fun getRequestReviewCount(): Int = + requestReviewCacheDataSource.getRequestReviewCount() + + override fun incrementRequestReviewCount() { + requestReviewCacheDataSource.incrementRequestReviewCount() + } +} \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/request_review/data/source/RequestReviewCacheDataSource.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/request_review/data/source/RequestReviewCacheDataSource.kt new file mode 100644 index 0000000000..892e624f76 --- /dev/null +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/request_review/data/source/RequestReviewCacheDataSource.kt @@ -0,0 +1,9 @@ +package org.hyperskill.app.request_review.data.source + +interface RequestReviewCacheDataSource { + fun getLastRequestReviewTimestamp(): Long? + fun setLastRequestReviewTimestamp(timestamp: Long) + + fun getRequestReviewCount(): Int + fun incrementRequestReviewCount() +} \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/request_review/domain/interactor/RequestReviewInteractor.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/request_review/domain/interactor/RequestReviewInteractor.kt new file mode 100644 index 0000000000..206687b905 --- /dev/null +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/request_review/domain/interactor/RequestReviewInteractor.kt @@ -0,0 +1,40 @@ +package org.hyperskill.app.request_review.domain.interactor + +import kotlin.time.DurationUnit +import kotlin.time.toDuration +import kotlinx.datetime.Clock +import org.hyperskill.app.request_review.domain.repository.RequestReviewRepository +import org.hyperskill.app.step_quiz.domain.repository.SubmissionRepository + +class RequestReviewInteractor( + private val requestReviewRepository: RequestReviewRepository, + private val submissionRepository: SubmissionRepository +) { + companion object { + private const val SOLVED_STEPS_FREQUENCY_TO_REQUEST_REVIEW = 10 + private const val MAX_REQUEST_REVIEW_COUNT = 3 + private val ONE_WEEK_IN_MILLIS = 7.toDuration(DurationUnit.DAYS).inWholeMilliseconds + } + + fun shouldRequestReviewAfterStepSolved(): Boolean { + val solvedStepsCount = submissionRepository.getSolvedStepsCount() + val isPassedSolvedStepsFrequency = solvedStepsCount % SOLVED_STEPS_FREQUENCY_TO_REQUEST_REVIEW == 0L + if (!isPassedSolvedStepsFrequency) { + return false + } + + val requestReviewCount = requestReviewRepository.getRequestReviewCount() + if (requestReviewCount >= MAX_REQUEST_REVIEW_COUNT) { + return false + } + + // Request review once a week + val lastRequestReviewTimestamp = requestReviewRepository.getLastRequestReviewTimestamp() ?: return true + return (lastRequestReviewTimestamp + ONE_WEEK_IN_MILLIS) <= Clock.System.now().toEpochMilliseconds() + } + + fun handleRequestReviewPerformed() { + requestReviewRepository.incrementRequestReviewCount() + requestReviewRepository.setLastRequestReviewTimestamp(Clock.System.now().toEpochMilliseconds()) + } +} \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/request_review/domain/repository/RequestReviewRepository.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/request_review/domain/repository/RequestReviewRepository.kt new file mode 100644 index 0000000000..b5f264274f --- /dev/null +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/request_review/domain/repository/RequestReviewRepository.kt @@ -0,0 +1,9 @@ +package org.hyperskill.app.request_review.domain.repository + +interface RequestReviewRepository { + fun getLastRequestReviewTimestamp(): Long? + fun setLastRequestReviewTimestamp(timestamp: Long) + + fun getRequestReviewCount(): Int + fun incrementRequestReviewCount() +} \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/request_review/injection/RequestReviewDataComponent.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/request_review/injection/RequestReviewDataComponent.kt new file mode 100644 index 0000000000..c7ff6671c5 --- /dev/null +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/request_review/injection/RequestReviewDataComponent.kt @@ -0,0 +1,7 @@ +package org.hyperskill.app.request_review.injection + +import org.hyperskill.app.request_review.domain.interactor.RequestReviewInteractor + +interface RequestReviewDataComponent { + val requestReviewInteractor: RequestReviewInteractor +} \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/request_review/injection/RequestReviewDataComponentImpl.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/request_review/injection/RequestReviewDataComponentImpl.kt new file mode 100644 index 0000000000..74e0ef3711 --- /dev/null +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/request_review/injection/RequestReviewDataComponentImpl.kt @@ -0,0 +1,24 @@ +package org.hyperskill.app.request_review.injection + +import org.hyperskill.app.core.injection.AppGraph +import org.hyperskill.app.request_review.cache.RequestReviewCacheDataSourceImpl +import org.hyperskill.app.request_review.data.repository.RequestReviewRepositoryImpl +import org.hyperskill.app.request_review.data.source.RequestReviewCacheDataSource +import org.hyperskill.app.request_review.domain.interactor.RequestReviewInteractor +import org.hyperskill.app.request_review.domain.repository.RequestReviewRepository + +internal class RequestReviewDataComponentImpl( + private val appGraph: AppGraph +) : RequestReviewDataComponent { + private val requestReviewCacheDataSource: RequestReviewCacheDataSource = + RequestReviewCacheDataSourceImpl(appGraph.commonComponent.settings) + + private val requestReviewRepository: RequestReviewRepository = + RequestReviewRepositoryImpl(requestReviewCacheDataSource) + + override val requestReviewInteractor: RequestReviewInteractor + get() = RequestReviewInteractor( + requestReviewRepository = requestReviewRepository, + submissionRepository = appGraph.submissionDataComponent.submissionRepository + ) +} \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/request_review/modal/domain/analytic/RequestReviewModalClickHyperskillAnalyticEvent.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/request_review/modal/domain/analytic/RequestReviewModalClickHyperskillAnalyticEvent.kt new file mode 100644 index 0000000000..70ff44ef6e --- /dev/null +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/request_review/modal/domain/analytic/RequestReviewModalClickHyperskillAnalyticEvent.kt @@ -0,0 +1,62 @@ +package org.hyperskill.app.request_review.modal.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.HyperskillAnalyticPart +import org.hyperskill.app.analytic.domain.model.hyperskill.HyperskillAnalyticRoute +import org.hyperskill.app.analytic.domain.model.hyperskill.HyperskillAnalyticTarget + +/** + * Represents a common click analytic event for the request review modal. + * + * Click on the "Yes" button: + * ``` + * { + * "route": "/learn/step/1", + * "action": "click", + * "part": "request_review_modal", + * "target": "yes" + * } + * ``` + * + * Click on the "No" button: + * ``` + * { + * "route": "/learn/step/1", + * "action": "click", + * "part": "request_review_modal", + * "target": "no" + * } + * ``` + * + * Click on the "Write a request" button: + * ``` + * { + * "route": "/learn/step/1", + * "action": "click", + * "part": "request_review_modal", + * "target": "write_a_request" + * } + * ``` + * + * Click on the "Maybe later" button: + * ``` + * { + * "route": "/learn/step/1", + * "action": "click", + * "part": "request_review_modal", + * "target": "maybe_later" + * } + * ``` + * + * @see HyperskillAnalyticEvent + */ +class RequestReviewModalClickHyperskillAnalyticEvent( + route: HyperskillAnalyticRoute, + target: HyperskillAnalyticTarget +) : HyperskillAnalyticEvent( + route, + HyperskillAnalyticAction.CLICK, + HyperskillAnalyticPart.REQUEST_REVIEW_MODAL, + target +) \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/request_review/modal/domain/analytic/RequestReviewModalHiddenHyperskillAnalyticEvent.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/request_review/modal/domain/analytic/RequestReviewModalHiddenHyperskillAnalyticEvent.kt new file mode 100644 index 0000000000..c3416cf4ed --- /dev/null +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/request_review/modal/domain/analytic/RequestReviewModalHiddenHyperskillAnalyticEvent.kt @@ -0,0 +1,30 @@ +package org.hyperskill.app.request_review.modal.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.HyperskillAnalyticPart +import org.hyperskill.app.analytic.domain.model.hyperskill.HyperskillAnalyticRoute +import org.hyperskill.app.analytic.domain.model.hyperskill.HyperskillAnalyticTarget + +/** + * Represents a hidden analytic event of the request review modal. + * + * JSON payload: + * ``` + * { + * "route": "/learn/step/1", + * "action": "hidden", + * "part": "request_review_modal", + * "target": "close" + * } + * ``` + * @see HyperskillAnalyticEvent + */ +class RequestReviewModalHiddenHyperskillAnalyticEvent( + route: HyperskillAnalyticRoute +) : HyperskillAnalyticEvent( + route, + HyperskillAnalyticAction.HIDDEN, + HyperskillAnalyticPart.REQUEST_REVIEW_MODAL, + HyperskillAnalyticTarget.CLOSE +) \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/request_review/modal/domain/analytic/RequestReviewModalShownHyperskillAnalyticEvent.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/request_review/modal/domain/analytic/RequestReviewModalShownHyperskillAnalyticEvent.kt new file mode 100644 index 0000000000..e9fb4a6744 --- /dev/null +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/request_review/modal/domain/analytic/RequestReviewModalShownHyperskillAnalyticEvent.kt @@ -0,0 +1,31 @@ +package org.hyperskill.app.request_review.modal.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.HyperskillAnalyticPart +import org.hyperskill.app.analytic.domain.model.hyperskill.HyperskillAnalyticRoute +import org.hyperskill.app.analytic.domain.model.hyperskill.HyperskillAnalyticTarget + +/** + * Represents a shown analytic event of the request review modal. + * + * JSON payload: + * ``` + * { + * "route": "/learn/step/1", + * "action": "shown", + * "part": "modal", + * "target": "request_review_modal" + * } + * ``` + * + * @see HyperskillAnalyticEvent + */ +class RequestReviewModalShownHyperskillAnalyticEvent( + route: HyperskillAnalyticRoute +) : HyperskillAnalyticEvent( + route, + HyperskillAnalyticAction.SHOWN, + HyperskillAnalyticPart.MODAL, + HyperskillAnalyticTarget.REQUEST_REVIEW_MODAL +) \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/request_review/modal/injection/RequestReviewModalComponent.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/request_review/modal/injection/RequestReviewModalComponent.kt new file mode 100644 index 0000000000..67d4ffdd2d --- /dev/null +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/request_review/modal/injection/RequestReviewModalComponent.kt @@ -0,0 +1,10 @@ +package org.hyperskill.app.request_review.modal.injection + +import org.hyperskill.app.request_review.modal.presentation.RequestReviewModalFeature.Action +import org.hyperskill.app.request_review.modal.presentation.RequestReviewModalFeature.Message +import org.hyperskill.app.request_review.modal.presentation.RequestReviewModalFeature.ViewState +import ru.nobird.app.presentation.redux.feature.Feature + +interface RequestReviewModalComponent { + val requestReviewModalFeature: Feature +} \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/request_review/modal/injection/RequestReviewModalComponentImpl.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/request_review/modal/injection/RequestReviewModalComponentImpl.kt new file mode 100644 index 0000000000..41e5a0b73e --- /dev/null +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/request_review/modal/injection/RequestReviewModalComponentImpl.kt @@ -0,0 +1,23 @@ +package org.hyperskill.app.request_review.modal.injection + +import org.hyperskill.app.core.injection.AppGraph +import org.hyperskill.app.request_review.modal.presentation.RequestReviewModalFeature.Action +import org.hyperskill.app.request_review.modal.presentation.RequestReviewModalFeature.Message +import org.hyperskill.app.request_review.modal.presentation.RequestReviewModalFeature.ViewState +import org.hyperskill.app.step.domain.model.StepRoute +import ru.nobird.app.presentation.redux.feature.Feature + +internal class RequestReviewModalComponentImpl( + private val appGraph: AppGraph, + private val stepRoute: StepRoute +) : RequestReviewModalComponent { + override val requestReviewModalFeature: Feature + get() = RequestReviewModalFeatureBuilder.build( + stepRoute = stepRoute, + analyticInteractor = appGraph.analyticComponent.analyticInteractor, + logger = appGraph.loggerComponent.logger, + buildVariant = appGraph.commonComponent.buildKonfig.buildVariant, + platform = appGraph.commonComponent.platform, + resourceProvider = appGraph.commonComponent.resourceProvider + ) +} \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/request_review/modal/injection/RequestReviewModalFeatureBuilder.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/request_review/modal/injection/RequestReviewModalFeatureBuilder.kt new file mode 100644 index 0000000000..6c533d74cb --- /dev/null +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/request_review/modal/injection/RequestReviewModalFeatureBuilder.kt @@ -0,0 +1,52 @@ +package org.hyperskill.app.request_review.modal.injection + +import co.touchlab.kermit.Logger +import org.hyperskill.app.analytic.domain.interactor.AnalyticInteractor +import org.hyperskill.app.core.domain.BuildVariant +import org.hyperskill.app.core.domain.platform.Platform +import org.hyperskill.app.core.presentation.ActionDispatcherOptions +import org.hyperskill.app.core.presentation.transformState +import org.hyperskill.app.core.view.mapper.ResourceProvider +import org.hyperskill.app.logging.presentation.wrapWithLogger +import org.hyperskill.app.request_review.modal.presentation.RequestReviewModalActionDispatcher +import org.hyperskill.app.request_review.modal.presentation.RequestReviewModalFeature.Action +import org.hyperskill.app.request_review.modal.presentation.RequestReviewModalFeature.Message +import org.hyperskill.app.request_review.modal.presentation.RequestReviewModalFeature.State +import org.hyperskill.app.request_review.modal.presentation.RequestReviewModalFeature.ViewState +import org.hyperskill.app.request_review.modal.presentation.RequestReviewModalReducer +import org.hyperskill.app.request_review.modal.view.mapper.RequestReviewModalViewStateMapper +import org.hyperskill.app.step.domain.model.StepRoute +import ru.nobird.app.presentation.redux.dispatcher.wrapWithActionDispatcher +import ru.nobird.app.presentation.redux.feature.Feature +import ru.nobird.app.presentation.redux.feature.ReduxFeature + +internal object RequestReviewModalFeatureBuilder { + private const val LOG_TAG = "RequestReviewModalFeature" + + fun build( + stepRoute: StepRoute, + analyticInteractor: AnalyticInteractor, + logger: Logger, + buildVariant: BuildVariant, + platform: Platform, + resourceProvider: ResourceProvider + ): Feature { + val requestReviewModalReducer = RequestReviewModalReducer( + stepRoute = stepRoute, + resourceProvider = resourceProvider + ).wrapWithLogger(buildVariant, logger, LOG_TAG) + val requestReviewModalActionDispatcher = RequestReviewModalActionDispatcher( + ActionDispatcherOptions(), + analyticInteractor = analyticInteractor + ) + + val requestReviewModalViewStateMapper = RequestReviewModalViewStateMapper( + platform = platform, + resourceProvider = resourceProvider + ) + + return ReduxFeature(State.Awaiting, requestReviewModalReducer) + .wrapWithActionDispatcher(requestReviewModalActionDispatcher) + .transformState(requestReviewModalViewStateMapper::map) + } +} \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/request_review/modal/presentation/RequestReviewModalActionDispatcher.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/request_review/modal/presentation/RequestReviewModalActionDispatcher.kt new file mode 100644 index 0000000000..6ae4082409 --- /dev/null +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/request_review/modal/presentation/RequestReviewModalActionDispatcher.kt @@ -0,0 +1,23 @@ +package org.hyperskill.app.request_review.modal.presentation + +import org.hyperskill.app.analytic.domain.interactor.AnalyticInteractor +import org.hyperskill.app.core.presentation.ActionDispatcherOptions +import org.hyperskill.app.request_review.modal.presentation.RequestReviewModalFeature.Action +import org.hyperskill.app.request_review.modal.presentation.RequestReviewModalFeature.InternalAction +import org.hyperskill.app.request_review.modal.presentation.RequestReviewModalFeature.Message +import ru.nobird.app.presentation.redux.dispatcher.CoroutineActionDispatcher + +internal class RequestReviewModalActionDispatcher( + config: ActionDispatcherOptions, + private val analyticInteractor: AnalyticInteractor +) : CoroutineActionDispatcher(config.createConfig()) { + override suspend fun doSuspendableAction(action: Action) { + when (action) { + is InternalAction.LogAnalyticEvent -> + analyticInteractor.logEvent(action.analyticEvent) + else -> { + // no op + } + } + } +} \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/request_review/modal/presentation/RequestReviewModalFeature.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/request_review/modal/presentation/RequestReviewModalFeature.kt new file mode 100644 index 0000000000..85e59b31d2 --- /dev/null +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/request_review/modal/presentation/RequestReviewModalFeature.kt @@ -0,0 +1,45 @@ +package org.hyperskill.app.request_review.modal.presentation + +import org.hyperskill.app.analytic.domain.model.AnalyticEvent + +object RequestReviewModalFeature { + internal sealed interface State { + object Awaiting : State + object Negative : State + object Positive : State + } + + data class ViewState( + val title: String, + val description: String?, + val positiveButtonText: String, + val negativeButtonText: String + ) + + sealed interface Message { + object PositiveButtonClicked : Message + object NegativeButtonClicked : Message + + /** + * Analytic + */ + object ShownEventMessage : Message + object HiddenEventMessage : Message + } + + internal sealed interface InternalMessage : Message + + sealed interface Action { + sealed interface ViewAction : Action { + object RequestUserReview : ViewAction + + data class SubmitSupportRequest(val url: String) : ViewAction + + object Dismiss : ViewAction + } + } + + internal sealed interface InternalAction : Action { + data class LogAnalyticEvent(val analyticEvent: AnalyticEvent) : InternalAction + } +} \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/request_review/modal/presentation/RequestReviewModalReducer.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/request_review/modal/presentation/RequestReviewModalReducer.kt new file mode 100644 index 0000000000..ad58d7a7cc --- /dev/null +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/request_review/modal/presentation/RequestReviewModalReducer.kt @@ -0,0 +1,97 @@ +package org.hyperskill.app.request_review.modal.presentation + +import org.hyperskill.app.SharedResources +import org.hyperskill.app.analytic.domain.model.hyperskill.HyperskillAnalyticTarget +import org.hyperskill.app.core.view.mapper.ResourceProvider +import org.hyperskill.app.request_review.modal.domain.analytic.RequestReviewModalClickHyperskillAnalyticEvent +import org.hyperskill.app.request_review.modal.domain.analytic.RequestReviewModalHiddenHyperskillAnalyticEvent +import org.hyperskill.app.request_review.modal.domain.analytic.RequestReviewModalShownHyperskillAnalyticEvent +import org.hyperskill.app.request_review.modal.presentation.RequestReviewModalFeature.Action +import org.hyperskill.app.request_review.modal.presentation.RequestReviewModalFeature.InternalAction +import org.hyperskill.app.request_review.modal.presentation.RequestReviewModalFeature.Message +import org.hyperskill.app.request_review.modal.presentation.RequestReviewModalFeature.State +import org.hyperskill.app.step.domain.model.StepRoute +import ru.nobird.app.presentation.redux.reducer.StateReducer + +private typealias ReducerResult = Pair> + +internal class RequestReviewModalReducer( + private val stepRoute: StepRoute, + private val resourceProvider: ResourceProvider +) : StateReducer { + override fun reduce(state: State, message: Message): ReducerResult = + when (message) { + Message.PositiveButtonClicked -> + handlePositiveButtonClickedMessage(state) + Message.NegativeButtonClicked -> + handleNegativeButtonClickedMessage(state) + // Analytic + Message.ShownEventMessage -> + state to setOf( + InternalAction.LogAnalyticEvent( + RequestReviewModalShownHyperskillAnalyticEvent(stepRoute.analyticRoute) + ) + ) + Message.HiddenEventMessage -> + state to setOf( + InternalAction.LogAnalyticEvent( + RequestReviewModalHiddenHyperskillAnalyticEvent(stepRoute.analyticRoute) + ) + ) + } ?: (state to emptySet()) + + private fun handlePositiveButtonClickedMessage( + state: State + ): ReducerResult? = + when (state) { + State.Awaiting -> + State.Positive to setOf( + InternalAction.LogAnalyticEvent( + RequestReviewModalClickHyperskillAnalyticEvent( + route = stepRoute.analyticRoute, + target = HyperskillAnalyticTarget.YES + ) + ), + Action.ViewAction.RequestUserReview + ) + State.Negative -> + state to setOf( + InternalAction.LogAnalyticEvent( + RequestReviewModalClickHyperskillAnalyticEvent( + route = stepRoute.analyticRoute, + target = HyperskillAnalyticTarget.WRITE_A_REQUEST + ) + ), + Action.ViewAction.SubmitSupportRequest( + resourceProvider.getString(SharedResources.strings.settings_report_problem_url) + ) + ) + State.Positive -> null + } + + private fun handleNegativeButtonClickedMessage( + state: State + ): ReducerResult? = + when (state) { + State.Awaiting -> + State.Negative to setOf( + InternalAction.LogAnalyticEvent( + RequestReviewModalClickHyperskillAnalyticEvent( + route = stepRoute.analyticRoute, + target = HyperskillAnalyticTarget.NO + ) + ) + ) + State.Negative -> + state to setOf( + InternalAction.LogAnalyticEvent( + RequestReviewModalClickHyperskillAnalyticEvent( + route = stepRoute.analyticRoute, + target = HyperskillAnalyticTarget.MAYBE_LATER + ) + ), + Action.ViewAction.Dismiss + ) + State.Positive -> null + } +} \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/request_review/modal/view/mapper/RequestReviewModalViewStateMapper.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/request_review/modal/view/mapper/RequestReviewModalViewStateMapper.kt new file mode 100644 index 0000000000..5038733a6b --- /dev/null +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/request_review/modal/view/mapper/RequestReviewModalViewStateMapper.kt @@ -0,0 +1,45 @@ +package org.hyperskill.app.request_review.modal.view.mapper + +import org.hyperskill.app.SharedResources +import org.hyperskill.app.core.domain.platform.Platform +import org.hyperskill.app.core.view.mapper.ResourceProvider +import org.hyperskill.app.request_review.modal.presentation.RequestReviewModalFeature.State +import org.hyperskill.app.request_review.modal.presentation.RequestReviewModalFeature.ViewState + +internal class RequestReviewModalViewStateMapper( + private val platform: Platform, + private val resourceProvider: ResourceProvider +) { + fun map(state: State): ViewState = + when (state) { + State.Awaiting, State.Positive -> + ViewState( + title = resourceProvider.getString( + SharedResources.strings.request_review_modal_state_awaiting_title, + resourceProvider.getString(platform.appNameResource) + ), + description = null, + positiveButtonText = resourceProvider.getString( + SharedResources.strings.request_review_modal_state_awaiting_positive_button_text + ), + negativeButtonText = resourceProvider.getString( + SharedResources.strings.request_review_modal_state_awaiting_negative_button_text + ) + ) + State.Negative -> + ViewState( + title = resourceProvider.getString( + SharedResources.strings.request_review_modal_state_negative_title + ), + description = resourceProvider.getString( + SharedResources.strings.request_review_modal_state_negative_description + ), + positiveButtonText = resourceProvider.getString( + SharedResources.strings.request_review_modal_state_negative_positive_button_text + ), + negativeButtonText = resourceProvider.getString( + SharedResources.strings.request_review_modal_state_negative_negative_button_text + ) + ) + } +} \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/step_completion/injection/StepCompletionComponentImpl.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/step_completion/injection/StepCompletionComponentImpl.kt index d85b166be3..098b194bc8 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/step_completion/injection/StepCompletionComponentImpl.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/step_completion/injection/StepCompletionComponentImpl.kt @@ -16,21 +16,25 @@ internal class StepCompletionComponentImpl( override val stepCompletionActionDispatcher: StepCompletionActionDispatcher get() = StepCompletionActionDispatcher( ActionDispatcherOptions(), - appGraph.submissionDataComponent.submissionRepository, - appGraph.buildStepDataComponent().stepInteractor, - appGraph.buildProgressesDataComponent().progressesInteractor, - appGraph.buildTopicsDataComponent().topicsRepository, - appGraph.analyticComponent.analyticInteractor, - appGraph.commonComponent.resourceProvider, - appGraph.sentryComponent.sentryInteractor, - appGraph.buildFreemiumDataComponent().freemiumInteractor, - appGraph.buildShareStreakDataComponent().shareStreakInteractor, - appGraph.stateRepositoriesComponent.nextLearningActivityStateRepository, - appGraph.profileDataComponent.currentProfileStateRepository, - appGraph.stateRepositoriesComponent.currentGamificationToolbarDataStateRepository, - appGraph.stepCompletionFlowDataComponent.dailyStepCompletedFlow, - appGraph.stepCompletionFlowDataComponent.topicCompletedFlow, - appGraph.progressesFlowDataComponent.topicProgressFlow, - appGraph.stateRepositoriesComponent.interviewStepsStateRepository + submissionRepository = appGraph.submissionDataComponent.submissionRepository, + stepInteractor = appGraph.buildStepDataComponent().stepInteractor, + progressesInteractor = appGraph.buildProgressesDataComponent().progressesInteractor, + topicsRepository = appGraph.buildTopicsDataComponent().topicsRepository, + analyticInteractor = appGraph.analyticComponent.analyticInteractor, + resourceProvider = appGraph.commonComponent.resourceProvider, + sentryInteractor = appGraph.sentryComponent.sentryInteractor, + freemiumInteractor = appGraph.buildFreemiumDataComponent().freemiumInteractor, + shareStreakInteractor = appGraph.buildShareStreakDataComponent().shareStreakInteractor, + requestReviewInteractor = appGraph.buildRequestReviewDataComponent().requestReviewInteractor, + nextLearningActivityStateRepository = appGraph.stateRepositoriesComponent + .nextLearningActivityStateRepository, + currentProfileStateRepository = appGraph.profileDataComponent.currentProfileStateRepository, + currentGamificationToolbarDataStateRepository = appGraph.stateRepositoriesComponent + .currentGamificationToolbarDataStateRepository, + dailyStepCompletedFlow = appGraph.stepCompletionFlowDataComponent.dailyStepCompletedFlow, + topicCompletedFlow = appGraph.stepCompletionFlowDataComponent.topicCompletedFlow, + topicProgressFlow = appGraph.progressesFlowDataComponent.topicProgressFlow, + interviewStepsStateRepository = appGraph.stateRepositoriesComponent.interviewStepsStateRepository, + platform = appGraph.commonComponent.platform ) } \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/step_completion/presentation/StepCompletionActionDispatcher.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/step_completion/presentation/StepCompletionActionDispatcher.kt index b415938dfa..b7606f0fb3 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/step_completion/presentation/StepCompletionActionDispatcher.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/step_completion/presentation/StepCompletionActionDispatcher.kt @@ -6,6 +6,8 @@ import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import org.hyperskill.app.SharedResources import org.hyperskill.app.analytic.domain.interactor.AnalyticInteractor +import org.hyperskill.app.core.domain.platform.Platform +import org.hyperskill.app.core.domain.platform.PlatformType import org.hyperskill.app.core.domain.repository.updateState import org.hyperskill.app.core.presentation.ActionDispatcherOptions import org.hyperskill.app.core.view.mapper.ResourceProvider @@ -16,6 +18,7 @@ import org.hyperskill.app.learning_activities.domain.repository.NextLearningActi import org.hyperskill.app.profile.domain.repository.CurrentProfileStateRepository import org.hyperskill.app.progresses.domain.flow.TopicProgressFlow import org.hyperskill.app.progresses.domain.interactor.ProgressesInteractor +import org.hyperskill.app.request_review.domain.interactor.RequestReviewInteractor import org.hyperskill.app.sentry.domain.interactor.SentryInteractor import org.hyperskill.app.sentry.domain.model.transaction.HyperskillSentryTransactionBuilder import org.hyperskill.app.sentry.domain.withTransaction @@ -48,13 +51,15 @@ class StepCompletionActionDispatcher( private val sentryInteractor: SentryInteractor, private val freemiumInteractor: FreemiumInteractor, private val shareStreakInteractor: ShareStreakInteractor, + private val requestReviewInteractor: RequestReviewInteractor, private val nextLearningActivityStateRepository: NextLearningActivityStateRepository, private val currentProfileStateRepository: CurrentProfileStateRepository, private val currentGamificationToolbarDataStateRepository: CurrentGamificationToolbarDataStateRepository, private val dailyStepCompletedFlow: DailyStepCompletedFlow, private val topicCompletedFlow: TopicCompletedFlow, private val topicProgressFlow: TopicProgressFlow, - private val interviewStepsStateRepository: InterviewStepsStateRepository + private val interviewStepsStateRepository: InterviewStepsStateRepository, + private val platform: Platform ) : CoroutineActionDispatcher(config.createConfig()) { init { @@ -271,12 +276,19 @@ class StepCompletionActionDispatcher( } } + // TODO: ALTAPPS-1136 remove platformType check after implementing request review for Android + val shouldRequestReview = + platform.platformType == PlatformType.IOS && requestReviewInteractor.shouldRequestReviewAfterStepSolved() + if (shouldShareStreak && streakToShare != null) { shareStreakInteractor.setLastTimeShareStreakShown() onNewMessage(Message.ShareStreak(streak = streakToShare)) - - updateCurrentProfileHypercoinsBalanceRemotely() + } else if (shouldRequestReview) { + requestReviewInteractor.handleRequestReviewPerformed() + onNewMessage(Message.RequestUserReview) } + + updateCurrentProfileHypercoinsBalanceRemotely() } private suspend fun updateCurrentProfileHypercoinsBalanceRemotely(): Int? = diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/step_completion/presentation/StepCompletionFeature.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/step_completion/presentation/StepCompletionFeature.kt index 38b6aa31d2..b6ac67fd05 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/step_completion/presentation/StepCompletionFeature.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/step_completion/presentation/StepCompletionFeature.kt @@ -118,6 +118,11 @@ object StepCompletionFeature { object InterviewPreparationCompletedModalHiddenEventMessage : Message object InterviewPreparationCompletedModalGoToTrainingClicked : Message + /** + * Ask user to rate or review the app + */ + object RequestUserReview : Message + /** * Analytic */ @@ -160,6 +165,8 @@ object StepCompletionFeature { object ShowInterviewPreparationCompletedModal : ViewAction + data class ShowRequestUserReviewModal(val stepRoute: StepRoute) : ViewAction + data class ShowStartPracticingError(val message: String) : ViewAction data class ReloadStep(val stepRoute: StepRoute) : ViewAction diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/step_completion/presentation/StepCompletionReducer.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/step_completion/presentation/StepCompletionReducer.kt index f45cedf40d..e3bdc043e0 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/step_completion/presentation/StepCompletionReducer.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/step_completion/presentation/StepCompletionReducer.kt @@ -206,6 +206,9 @@ class StepCompletionReducer(private val stepRoute: StepRoute) : StateReducer { + state to setOf(Action.ViewAction.ShowRequestUserReviewModal(stepRoute)) + } /** * Analytic * */ diff --git a/shared/src/commonMain/resources/MR/base/strings.xml b/shared/src/commonMain/resources/MR/base/strings.xml index ad4d74c066..227fd5a657 100644 --- a/shared/src/commonMain/resources/MR/base/strings.xml +++ b/shared/src/commonMain/resources/MR/base/strings.xml @@ -258,6 +258,8 @@ https://www.jetbrains.com/legal/terms/jetbrains-academy.html https://hi.hyperskill.org/terms https://support.hyperskill.org/hc/en-us/requests/new + Rate us in the App Store + https://apps.apple.com/app/id1637230833?action=write-review Leaderboard @@ -559,6 +561,16 @@ Find topic Search all of Hyperskill for topic theory + + Do you enjoy\n%s app? + Yes + No + + Thank you! + Share what you disliked to help us improve your experience. + Write a request + Maybe later + Project Mastery Topic Mastery From fadca619f3f771200628c25024fe9adb033d2272 Mon Sep 17 00:00:00 2001 From: github-actions Date: Fri, 9 Feb 2024 14:57:14 +0000 Subject: [PATCH 045/104] Bump build number --- gradle/app.versions.toml | 2 +- .../NotificationServiceExtension/Info.plist | 2 +- .../iosHyperskillApp.xcodeproj/project.pbxproj | 16 ++++++++-------- iosHyperskillApp/iosHyperskillApp/Info.plist | 2 +- .../iosHyperskillAppTests/Info.plist | 2 +- .../iosHyperskillAppUITests/Info.plist | 2 +- 6 files changed, 13 insertions(+), 13 deletions(-) diff --git a/gradle/app.versions.toml b/gradle/app.versions.toml index aecca36860..302dccab33 100644 --- a/gradle/app.versions.toml +++ b/gradle/app.versions.toml @@ -3,4 +3,4 @@ minSdk = '24' targetSdk = '33' compileSdk = '33' versionName = '1.50' -versionCode = '319' \ No newline at end of file +versionCode = '320' \ No newline at end of file diff --git a/iosHyperskillApp/NotificationServiceExtension/Info.plist b/iosHyperskillApp/NotificationServiceExtension/Info.plist index 6e5f7870bd..b9d07ca251 100644 --- a/iosHyperskillApp/NotificationServiceExtension/Info.plist +++ b/iosHyperskillApp/NotificationServiceExtension/Info.plist @@ -9,7 +9,7 @@ CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleVersion - 322 + 323 CFBundleShortVersionString 1.50 CFBundlePackageType diff --git a/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj b/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj index 06b416d405..b517cfc988 100644 --- a/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj +++ b/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj @@ -5029,7 +5029,7 @@ buildSettings = { CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 322; + CURRENT_PROJECT_VERSION = 323; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = 3DWS674B2M; GENERATE_INFOPLIST_FILE = NO; @@ -5050,7 +5050,7 @@ buildSettings = { CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 322; + CURRENT_PROJECT_VERSION = 323; DEVELOPMENT_TEAM = 3DWS674B2M; GENERATE_INFOPLIST_FILE = NO; INFOPLIST_FILE = iosHyperskillAppUITests/Info.plist; @@ -5071,7 +5071,7 @@ BUNDLE_LOADER = "$(TEST_HOST)"; CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 322; + CURRENT_PROJECT_VERSION = 323; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = 3DWS674B2M; INFOPLIST_FILE = iosHyperskillAppTests/Info.plist; @@ -5092,7 +5092,7 @@ BUNDLE_LOADER = "$(TEST_HOST)"; CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 322; + CURRENT_PROJECT_VERSION = 323; DEVELOPMENT_TEAM = 3DWS674B2M; INFOPLIST_FILE = iosHyperskillAppTests/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; @@ -5113,7 +5113,7 @@ CODE_SIGN_ENTITLEMENTS = NotificationServiceExtension/NotificationServiceExtension.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 322; + CURRENT_PROJECT_VERSION = 323; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = 3DWS674B2M; INFOPLIST_FILE = NotificationServiceExtension/Info.plist; @@ -5141,7 +5141,7 @@ CODE_SIGN_ENTITLEMENTS = NotificationServiceExtension/NotificationServiceExtension.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 322; + CURRENT_PROJECT_VERSION = 323; DEVELOPMENT_TEAM = 3DWS674B2M; INFOPLIST_FILE = NotificationServiceExtension/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; @@ -5286,7 +5286,7 @@ CODE_SIGN_ENTITLEMENTS = iosHyperskillApp/iosHyperskillApp.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 322; + CURRENT_PROJECT_VERSION = 323; DEVELOPMENT_ASSET_PATHS = "\"iosHyperskillApp/Preview Content\""; DEVELOPMENT_TEAM = 3DWS674B2M; ENABLE_PREVIEWS = YES; @@ -5322,7 +5322,7 @@ CODE_SIGN_ENTITLEMENTS = iosHyperskillApp/iosHyperskillApp.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 322; + CURRENT_PROJECT_VERSION = 323; DEVELOPMENT_ASSET_PATHS = "\"iosHyperskillApp/Preview Content\""; DEVELOPMENT_TEAM = 3DWS674B2M; ENABLE_PREVIEWS = YES; diff --git a/iosHyperskillApp/iosHyperskillApp/Info.plist b/iosHyperskillApp/iosHyperskillApp/Info.plist index 74bb835283..40353ec0df 100644 --- a/iosHyperskillApp/iosHyperskillApp/Info.plist +++ b/iosHyperskillApp/iosHyperskillApp/Info.plist @@ -36,7 +36,7 @@ CFBundleVersion - 322 + 323 FirebaseAppDelegateProxyEnabled FirebaseMessagingAutoInitEnabled diff --git a/iosHyperskillApp/iosHyperskillAppTests/Info.plist b/iosHyperskillApp/iosHyperskillAppTests/Info.plist index eb2ae2e56d..49c94c7548 100644 --- a/iosHyperskillApp/iosHyperskillAppTests/Info.plist +++ b/iosHyperskillApp/iosHyperskillAppTests/Info.plist @@ -15,6 +15,6 @@ CFBundleShortVersionString 1.50 CFBundleVersion - 322 + 323 diff --git a/iosHyperskillApp/iosHyperskillAppUITests/Info.plist b/iosHyperskillApp/iosHyperskillAppUITests/Info.plist index 43108936fa..72fde18012 100644 --- a/iosHyperskillApp/iosHyperskillAppUITests/Info.plist +++ b/iosHyperskillApp/iosHyperskillAppUITests/Info.plist @@ -15,6 +15,6 @@ CFBundleShortVersionString 1.50 CFBundleVersion - 322 + 323 From 021924efff6d75fcec5f4b133fc58a1be8623572 Mon Sep 17 00:00:00 2001 From: Ivan Magda Date: Mon, 12 Feb 2024 12:19:16 +0700 Subject: [PATCH 046/104] iOS: Polish up footer views (#894) --- .../project.pbxproj | 8 +++ .../SwiftUI/View/View+SafeAreaInset.swift | 18 ++++++ .../ProjectSelectionDetailsContentView.swift | 64 ++++++------------- .../ProjectSelectionDetailsSkeletonView.swift | 2 +- .../Modules/StepQuiz/Views/StepQuizView.swift | 22 +------ .../StepQuizFillBlanksSelectOptionsView.swift | 2 +- .../TrackSelectionDetailsContentView.swift | 64 ++++++------------- .../TrackSelectionDetailsSkeletonView.swift | 2 +- .../Wrappers/TransparentBlurView.swift | 25 ++++++++ 9 files changed, 98 insertions(+), 109 deletions(-) create mode 100644 iosHyperskillApp/iosHyperskillApp/Sources/Extensions/SwiftUI/View/View+SafeAreaInset.swift create mode 100644 iosHyperskillApp/iosHyperskillApp/Sources/Views/SwiftUI/Wrappers/TransparentBlurView.swift diff --git a/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj b/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj index 970c5bbed1..d07267be49 100644 --- a/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj +++ b/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj @@ -443,6 +443,8 @@ 2CD48D892858657100CFCC4A /* StepQuizView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CD48D882858657100CFCC4A /* StepQuizView.swift */; }; 2CD48D8B2858684100CFCC4A /* StepQuizViewData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CD48D8A2858684100CFCC4A /* StepQuizViewData.swift */; }; 2CD48D8E28586B6F00CFCC4A /* StepQuizViewDataMapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CD48D8D28586B6F00CFCC4A /* StepQuizViewDataMapper.swift */; }; + 2CD4EDF92B79D51E0091F0B2 /* View+SafeAreaInset.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CD4EDF82B79D51E0091F0B2 /* View+SafeAreaInset.swift */; }; + 2CD4EDFB2B79D74B0091F0B2 /* TransparentBlurView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CD4EDFA2B79D74B0091F0B2 /* TransparentBlurView.swift */; }; 2CDA9838294432C900ADE539 /* SkeletonCircleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CDA9837294432C900ADE539 /* SkeletonCircleView.swift */; }; 2CDA98412944512D00ADE539 /* ProfileSkeletonView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CDA98402944512D00ADE539 /* ProfileSkeletonView.swift */; }; 2CDA98432944524D00ADE539 /* HomeSkeletonView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CDA98422944524D00ADE539 /* HomeSkeletonView.swift */; }; @@ -1132,6 +1134,8 @@ 2CD48D882858657100CFCC4A /* StepQuizView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StepQuizView.swift; sourceTree = ""; }; 2CD48D8A2858684100CFCC4A /* StepQuizViewData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StepQuizViewData.swift; sourceTree = ""; }; 2CD48D8D28586B6F00CFCC4A /* StepQuizViewDataMapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StepQuizViewDataMapper.swift; sourceTree = ""; }; + 2CD4EDF82B79D51E0091F0B2 /* View+SafeAreaInset.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "View+SafeAreaInset.swift"; sourceTree = ""; }; + 2CD4EDFA2B79D74B0091F0B2 /* TransparentBlurView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TransparentBlurView.swift; sourceTree = ""; }; 2CDA9837294432C900ADE539 /* SkeletonCircleView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SkeletonCircleView.swift; sourceTree = ""; }; 2CDA98402944512D00ADE539 /* ProfileSkeletonView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileSkeletonView.swift; sourceTree = ""; }; 2CDA98422944524D00ADE539 /* HomeSkeletonView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeSkeletonView.swift; sourceTree = ""; }; @@ -1944,6 +1948,7 @@ 2CF4341128126C79002893CD /* View+EndEditing.swift */, 2C177EC22837B65500D841DB /* View+Frame.swift */, E9FAF38E299F61AE001FC596 /* View+MeasureSize.swift */, + 2CD4EDF82B79D51E0091F0B2 /* View+SafeAreaInset.swift */, 2C4605B02ABD75FC003C17E9 /* View+ScrollBounceBehavior.swift */, ); path = View; @@ -2198,6 +2203,7 @@ 2CE7B4832AB0593F00DCBE4D /* AttributedTextLabelWrapper.swift */, 2CAA3C622AA9C196004F6CE6 /* LottieAnimationViewWrapper.swift */, 2C46D0622807E4C100B3636E /* TextFieldWrapper.swift */, + 2CD4EDFA2B79D74B0091F0B2 /* TransparentBlurView.swift */, 2CC78D0A28C74EAF0006EF91 /* UIViewControllerEvents */, ); path = Wrappers; @@ -4536,6 +4542,7 @@ 2C8E4FB12848C9050011ADFA /* StepQuizTableSelectColumnsView.swift in Sources */, 2CCCA3A12862E62F00D98089 /* StepQuizStringViewData.swift in Sources */, 2C1061A2285C349400EBD614 /* StepQuizChildQuizAssembly.swift in Sources */, + 2CD4EDF92B79D51E0091F0B2 /* View+SafeAreaInset.swift in Sources */, 2C11D5CA2A11311900C59238 /* FeedbackGeneratorPreviewView.swift in Sources */, 2C971B852AC2F5DC00868FCE /* StepExpandableStepTextView.swift in Sources */, 2CB279AF28C72AA400EDDCC8 /* DeepLinkRouterProtocol.swift in Sources */, @@ -4729,6 +4736,7 @@ 2C906CBE280E5D9C0079C594 /* ProgressHUD.swift in Sources */, 2CBC97D02A555BE60078E445 /* HypercoinsAwardView.swift in Sources */, 2CA854DA2B1DE4780045CA1B /* LeaderboardPlaceholderEmptyView.swift in Sources */, + 2CD4EDFB2B79D74B0091F0B2 /* TransparentBlurView.swift in Sources */, 2CA8E094281039EB00154088 /* RoundedRectangleButtonStyle.swift in Sources */, E9D537D02A71056100F21828 /* ProfileBadgesGridItemView.swift in Sources */, 2CB0ADEE2B04AD6D0089D557 /* ChallengeWidgetViewModel.swift in Sources */, diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Extensions/SwiftUI/View/View+SafeAreaInset.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Extensions/SwiftUI/View/View+SafeAreaInset.swift new file mode 100644 index 0000000000..1be5706323 --- /dev/null +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Extensions/SwiftUI/View/View+SafeAreaInset.swift @@ -0,0 +1,18 @@ +import SwiftUI + +@available(iOS, introduced: 13, deprecated: 15, message: "Use .safeAreaInset() directly") +extension View { + @ViewBuilder + func safeAreaInsetBottomCompatibility(_ content: Content) -> some View { + if #available(iOS 15.0, *) { + safeAreaInset( + edge: .bottom, + alignment: .center, + spacing: 0, + content: { content } + ) + } else { + overlay(content, alignment: .bottom) + } + } +} diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/ProjectSelection/Details/Views/Content/ProjectSelectionDetailsContentView.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/ProjectSelection/Details/Views/Content/ProjectSelectionDetailsContentView.swift index de9055e9ba..408358d4e7 100644 --- a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/ProjectSelection/Details/Views/Content/ProjectSelectionDetailsContentView.swift +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/ProjectSelection/Details/Views/Content/ProjectSelectionDetailsContentView.swift @@ -3,18 +3,6 @@ import SwiftUI extension ProjectSelectionDetailsContentView { struct Appearance { let spacing = LayoutInsets.defaultInset - - let callToActionBlurInsets = LayoutInsets( - horizontal: LayoutInsets.smallInset, - vertical: -LayoutInsets.smallInset - ) - - func makeCallToActionButtonStyle(isEnabled: Bool) -> RoundedRectangleButtonStyle { - var style = RoundedRectangleButtonStyle(style: .violet) - style.backgroundDisabledOpacity = 1 - style.foregroundColor = isEnabled ? Color(ColorPalette.onPrimary) : Color(ColorPalette.onPrimaryAlpha60) - return style - } } } @@ -42,13 +30,7 @@ struct ProjectSelectionDetailsContentView: View { let isCallToActionButtonEnabled: Bool let onCallToActionButtonTap: () -> Void - private let callToActionButtonFeedbackGenerator = FeedbackGenerator(feedbackType: .selection) - var body: some View { - let callToActionButtonStyle = appearance.makeCallToActionButtonStyle( - isEnabled: isCallToActionButtonEnabled - ) - ScrollView { VStack(spacing: appearance.spacing) { ProjectSelectionDetailsLearningOutcomesView( @@ -72,36 +54,32 @@ struct ProjectSelectionDetailsContentView: View { ProjectSelectionDetailsProviderView(title: providerName) } .padding() - .padding(.bottom, callToActionButtonStyle.minHeight) } .frame(maxWidth: .infinity) .navigationTitle(navigationTitle) - .overlay( - buildCallToActionButton(buttonStyle: callToActionButtonStyle), - alignment: .bottom - ) + .safeAreaInsetBottomCompatibility(footerView) } - @MainActor - @ViewBuilder - private func buildCallToActionButton( - buttonStyle: RoundedRectangleButtonStyle - ) -> some View { - Button( - Strings.ProjectSelectionDetails.callToActionButtonTitle, - action: { - callToActionButtonFeedbackGenerator.triggerFeedback() - onCallToActionButtonTap() - } - ) - .buttonStyle(buttonStyle) - .padding(.horizontal) - .background( - Color(ColorPalette.surface) - .padding(appearance.callToActionBlurInsets.edgeInsets) - .blur(radius: buttonStyle.cornerRadius) - ) - .disabled(!isCallToActionButtonEnabled) + @MainActor @ViewBuilder private var footerView: some View { + if isCallToActionButtonEnabled { + Button( + Strings.ProjectSelectionDetails.callToActionButtonTitle, + action: { + FeedbackGenerator(feedbackType: .selection).triggerFeedback() + onCallToActionButtonTap() + } + ) + .buttonStyle(.primary) + .shineEffect() + .padding() + .background( + TransparentBlurView() + .edgesIgnoringSafeArea(.all) + ) + .fixedSize(horizontal: false, vertical: true) + } else { + EmptyView() + } } } diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/ProjectSelection/Details/Views/Skeleton/ProjectSelectionDetailsSkeletonView.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/ProjectSelection/Details/Views/Skeleton/ProjectSelectionDetailsSkeletonView.swift index ee40b8dfd4..b365d69c11 100644 --- a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/ProjectSelection/Details/Views/Skeleton/ProjectSelectionDetailsSkeletonView.swift +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/ProjectSelection/Details/Views/Skeleton/ProjectSelectionDetailsSkeletonView.swift @@ -16,7 +16,7 @@ struct ProjectSelectionDetailsSkeletonView: View { .frame(height: 78) SkeletonRoundedView() - .frame(height: 44) + .frame(height: RoundedRectangleButtonStyle.primary.minHeight) } .padding() } diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuiz/Views/StepQuizView.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuiz/Views/StepQuizView.swift index 4197fe16db..1dd82dc09b 100644 --- a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuiz/Views/StepQuizView.swift +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuiz/Views/StepQuizView.swift @@ -98,7 +98,7 @@ struct StepQuizView: View { Spacer(minLength: fillBlanksSelectOptionsViewHeight) } } - .bottomFillBlanksSelectOptionsOverlay( + .safeAreaInsetBottomCompatibility( buildFillBlanksSelectOptionsView( quizType: viewData.quizType, attemptLoadedState: StepQuizStateExtentionsKt.attemptLoadedState(viewModel.state.stepQuizState) @@ -285,6 +285,7 @@ struct StepQuizView: View { } } ) + .background(TransparentBlurView()) .edgesIgnoringSafeArea(.all) .frame(height: fillBlanksSelectOptionsViewHeight) .disabled(!StepQuizResolver.shared.isQuizEnabled(state: attemptLoadedState)) @@ -366,22 +367,3 @@ private extension StepQuizView { panModalPresenter.presentPanModal(panModal) } } - -// MARK: - View (bottomFillBlanksSelectOptionsOverlay) - - -@available(iOS, introduced: 13, deprecated: 15, message: "Use .safeAreaInset() directly") -private extension View { - @ViewBuilder - func bottomFillBlanksSelectOptionsOverlay(_ overlayContent: OverlayContent) -> some View { - if #available(iOS 15.0, *) { - self.safeAreaInset( - edge: .bottom, - alignment: .center, - spacing: 0, - content: { overlayContent } - ) - } else { - self.overlay(overlayContent, alignment: .bottom) - } - } -} diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizFillBlanksSelectOptions/Views/UIKit/StepQuizFillBlanksSelectOptionsView.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizFillBlanksSelectOptions/Views/UIKit/StepQuizFillBlanksSelectOptionsView.swift index d5f3c2b8df..dd992ce224 100644 --- a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizFillBlanksSelectOptions/Views/UIKit/StepQuizFillBlanksSelectOptionsView.swift +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizFillBlanksSelectOptions/Views/UIKit/StepQuizFillBlanksSelectOptionsView.swift @@ -9,7 +9,7 @@ extension StepQuizFillBlanksSelectOptionsView { let collectionViewMinInteritemSpacing = LayoutInsets.defaultInset let collectionViewSectionInset = LayoutInsets.default.uiEdgeInsets - let backgroundColor = UIColor.systemBackground + let backgroundColor = UIColor.clear } } diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/TrackSelection/Details/Views/Content/TrackSelectionDetailsContentView.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/TrackSelection/Details/Views/Content/TrackSelectionDetailsContentView.swift index 052286b469..d1281ce8e7 100644 --- a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/TrackSelection/Details/Views/Content/TrackSelectionDetailsContentView.swift +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/TrackSelection/Details/Views/Content/TrackSelectionDetailsContentView.swift @@ -3,18 +3,6 @@ import SwiftUI extension TrackSelectionDetailsContentView { struct Appearance { let spacing = LayoutInsets.defaultInset - - let callToActionBlurInsets = LayoutInsets( - horizontal: LayoutInsets.smallInset, - vertical: -LayoutInsets.smallInset - ) - - func makeCallToActionButtonStyle(isEnabled: Bool) -> RoundedRectangleButtonStyle { - var style = RoundedRectangleButtonStyle(style: .violet) - style.backgroundDisabledOpacity = 1 - style.foregroundColor = isEnabled ? Color(ColorPalette.onPrimary) : Color(ColorPalette.onPrimaryAlpha60) - return style - } } } @@ -42,13 +30,7 @@ struct TrackSelectionDetailsContentView: View { let isCallToActionButtonEnabled: Bool let onCallToActionButtonTap: () -> Void - private let callToActionButtonFeedbackGenerator = FeedbackGenerator(feedbackType: .selection) - var body: some View { - let callToActionButtonStyle = appearance.makeCallToActionButtonStyle( - isEnabled: isCallToActionButtonEnabled - ) - ScrollView { VStack(spacing: appearance.spacing) { TrackSelectionDetailsDescriptionView( @@ -74,36 +56,32 @@ struct TrackSelectionDetailsContentView: View { ) } .padding() - .padding(.bottom, callToActionButtonStyle.minHeight) } .frame(maxWidth: .infinity) .navigationTitle(navigationTitle) - .overlay( - buildCallToActionButton(buttonStyle: callToActionButtonStyle), - alignment: .bottom - ) + .safeAreaInsetBottomCompatibility(footerView) } - @MainActor - @ViewBuilder - private func buildCallToActionButton( - buttonStyle: RoundedRectangleButtonStyle - ) -> some View { - Button( - Strings.TrackSelectionDetails.callToActionButtonTitle, - action: { - callToActionButtonFeedbackGenerator.triggerFeedback() - onCallToActionButtonTap() - } - ) - .buttonStyle(buttonStyle) - .padding(.horizontal) - .background( - Color(ColorPalette.surface) - .padding(appearance.callToActionBlurInsets.edgeInsets) - .blur(radius: buttonStyle.cornerRadius) - ) - .disabled(!isCallToActionButtonEnabled) + @MainActor @ViewBuilder private var footerView: some View { + if isCallToActionButtonEnabled { + Button( + Strings.TrackSelectionDetails.callToActionButtonTitle, + action: { + FeedbackGenerator(feedbackType: .selection).triggerFeedback() + onCallToActionButtonTap() + } + ) + .buttonStyle(.primary) + .shineEffect() + .padding() + .background( + TransparentBlurView() + .edgesIgnoringSafeArea(.all) + ) + .fixedSize(horizontal: false, vertical: true) + } else { + EmptyView() + } } } diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/TrackSelection/Details/Views/Skeleton/TrackSelectionDetailsSkeletonView.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/TrackSelection/Details/Views/Skeleton/TrackSelectionDetailsSkeletonView.swift index 9d6c3c37ae..2ee1e29af0 100644 --- a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/TrackSelection/Details/Views/Skeleton/TrackSelectionDetailsSkeletonView.swift +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/TrackSelection/Details/Views/Skeleton/TrackSelectionDetailsSkeletonView.swift @@ -16,7 +16,7 @@ struct TrackSelectionDetailsSkeletonView: View { .frame(height: 240) SkeletonRoundedView() - .frame(height: 44) + .frame(height: RoundedRectangleButtonStyle.primary.minHeight) } .padding() } diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Views/SwiftUI/Wrappers/TransparentBlurView.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Views/SwiftUI/Wrappers/TransparentBlurView.swift new file mode 100644 index 0000000000..fbdf2a3ece --- /dev/null +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Views/SwiftUI/Wrappers/TransparentBlurView.swift @@ -0,0 +1,25 @@ +import SwiftUI +import UIKit + +struct TransparentBlurView: UIViewRepresentable { + func makeUIView(context: Context) -> UIVisualEffectView { + UIVisualEffectView(effect: UIBlurEffect(style: .systemUltraThinMaterial)) + } + + func updateUIView(_ uiView: UIVisualEffectView, context: Context) { + DispatchQueue.main.async { + guard let backdropLayer = uiView.layer.sublayers?.first else { + return + } + + backdropLayer.filters?.removeAll { filter in + String(describing: filter) != "gaussianBlur" + } + } + } +} + +#Preview { + TransparentBlurView() + .padding() +} From 68f3b6aba2ca8261d1b7ff9d28571483d0c8b9ea Mon Sep 17 00:00:00 2001 From: github-actions Date: Mon, 12 Feb 2024 05:19:48 +0000 Subject: [PATCH 047/104] iOS: Bump build number --- .../NotificationServiceExtension/Info.plist | 2 +- .../iosHyperskillApp.xcodeproj/project.pbxproj | 16 ++++++++-------- iosHyperskillApp/iosHyperskillApp/Info.plist | 2 +- .../iosHyperskillAppTests/Info.plist | 2 +- .../iosHyperskillAppUITests/Info.plist | 2 +- 5 files changed, 12 insertions(+), 12 deletions(-) diff --git a/iosHyperskillApp/NotificationServiceExtension/Info.plist b/iosHyperskillApp/NotificationServiceExtension/Info.plist index b9d07ca251..a2ddeb89d4 100644 --- a/iosHyperskillApp/NotificationServiceExtension/Info.plist +++ b/iosHyperskillApp/NotificationServiceExtension/Info.plist @@ -9,7 +9,7 @@ CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleVersion - 323 + 324 CFBundleShortVersionString 1.50 CFBundlePackageType diff --git a/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj b/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj index 6fbaa7c08d..b14c424db5 100644 --- a/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj +++ b/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj @@ -5037,7 +5037,7 @@ buildSettings = { CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 323; + CURRENT_PROJECT_VERSION = 324; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = 3DWS674B2M; GENERATE_INFOPLIST_FILE = NO; @@ -5058,7 +5058,7 @@ buildSettings = { CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 323; + CURRENT_PROJECT_VERSION = 324; DEVELOPMENT_TEAM = 3DWS674B2M; GENERATE_INFOPLIST_FILE = NO; INFOPLIST_FILE = iosHyperskillAppUITests/Info.plist; @@ -5079,7 +5079,7 @@ BUNDLE_LOADER = "$(TEST_HOST)"; CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 323; + CURRENT_PROJECT_VERSION = 324; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = 3DWS674B2M; INFOPLIST_FILE = iosHyperskillAppTests/Info.plist; @@ -5100,7 +5100,7 @@ BUNDLE_LOADER = "$(TEST_HOST)"; CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 323; + CURRENT_PROJECT_VERSION = 324; DEVELOPMENT_TEAM = 3DWS674B2M; INFOPLIST_FILE = iosHyperskillAppTests/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; @@ -5121,7 +5121,7 @@ CODE_SIGN_ENTITLEMENTS = NotificationServiceExtension/NotificationServiceExtension.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 323; + CURRENT_PROJECT_VERSION = 324; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = 3DWS674B2M; INFOPLIST_FILE = NotificationServiceExtension/Info.plist; @@ -5149,7 +5149,7 @@ CODE_SIGN_ENTITLEMENTS = NotificationServiceExtension/NotificationServiceExtension.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 323; + CURRENT_PROJECT_VERSION = 324; DEVELOPMENT_TEAM = 3DWS674B2M; INFOPLIST_FILE = NotificationServiceExtension/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; @@ -5294,7 +5294,7 @@ CODE_SIGN_ENTITLEMENTS = iosHyperskillApp/iosHyperskillApp.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 323; + CURRENT_PROJECT_VERSION = 324; DEVELOPMENT_ASSET_PATHS = "\"iosHyperskillApp/Preview Content\""; DEVELOPMENT_TEAM = 3DWS674B2M; ENABLE_PREVIEWS = YES; @@ -5330,7 +5330,7 @@ CODE_SIGN_ENTITLEMENTS = iosHyperskillApp/iosHyperskillApp.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 323; + CURRENT_PROJECT_VERSION = 324; DEVELOPMENT_ASSET_PATHS = "\"iosHyperskillApp/Preview Content\""; DEVELOPMENT_TEAM = 3DWS674B2M; ENABLE_PREVIEWS = YES; diff --git a/iosHyperskillApp/iosHyperskillApp/Info.plist b/iosHyperskillApp/iosHyperskillApp/Info.plist index 40353ec0df..025c59553e 100644 --- a/iosHyperskillApp/iosHyperskillApp/Info.plist +++ b/iosHyperskillApp/iosHyperskillApp/Info.plist @@ -36,7 +36,7 @@ CFBundleVersion - 323 + 324 FirebaseAppDelegateProxyEnabled FirebaseMessagingAutoInitEnabled diff --git a/iosHyperskillApp/iosHyperskillAppTests/Info.plist b/iosHyperskillApp/iosHyperskillAppTests/Info.plist index 49c94c7548..9ce339cb37 100644 --- a/iosHyperskillApp/iosHyperskillAppTests/Info.plist +++ b/iosHyperskillApp/iosHyperskillAppTests/Info.plist @@ -15,6 +15,6 @@ CFBundleShortVersionString 1.50 CFBundleVersion - 323 + 324 diff --git a/iosHyperskillApp/iosHyperskillAppUITests/Info.plist b/iosHyperskillApp/iosHyperskillAppUITests/Info.plist index 72fde18012..81bd848a04 100644 --- a/iosHyperskillApp/iosHyperskillAppUITests/Info.plist +++ b/iosHyperskillApp/iosHyperskillAppUITests/Info.plist @@ -15,6 +15,6 @@ CFBundleShortVersionString 1.50 CFBundleVersion - 323 + 324 From 8f1eca8ddf69fcf39ab796ebba01c013a70ba8f0 Mon Sep 17 00:00:00 2001 From: Ivan Magda Date: Tue, 13 Feb 2024 10:58:11 +0800 Subject: [PATCH 048/104] iOS: Use default animation for Home, StudyPlan & Profile --- .../iosHyperskillApp/Sources/Modules/Home/Views/HomeView.swift | 1 + .../Sources/Modules/Profile/Views/ProfileView.swift | 1 + .../Sources/Modules/StudyPlan/Views/StudyPlanView.swift | 1 + 3 files changed, 3 insertions(+) diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/Home/Views/HomeView.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/Home/Views/HomeView.swift index 574de8d397..84b6b5eb43 100644 --- a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/Home/Views/HomeView.swift +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/Home/Views/HomeView.swift @@ -30,6 +30,7 @@ struct HomeView: View { BackgroundView(color: appearance.backgroundColor) buildBody() + .animation(.default, value: viewModel.state) } .navigationTitle(Strings.Home.title) .navigationViewStyle(StackNavigationViewStyle()) diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/Profile/Views/ProfileView.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/Profile/Views/ProfileView.swift index b0488898e0..6df9bfdcfa 100644 --- a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/Profile/Views/ProfileView.swift +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/Profile/Views/ProfileView.swift @@ -36,6 +36,7 @@ struct ProfileView: View { BackgroundView(color: appearance.backgroundColor) buildBody() + .animation(.default, value: viewModel.stateKs) } .navigationTitle(Strings.Profile.title) .toolbar { diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StudyPlan/Views/StudyPlanView.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StudyPlan/Views/StudyPlanView.swift index 1d0381d351..9c579521ce 100644 --- a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StudyPlan/Views/StudyPlanView.swift +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StudyPlan/Views/StudyPlanView.swift @@ -29,6 +29,7 @@ struct StudyPlanView: View { BackgroundView(color: appearance.backgroundColor) buildBody() + .animation(.default, value: viewModel.state) } .navigationTitle(Strings.StudyPlan.title) .navigationViewStyle(StackNavigationViewStyle()) From 4cd2463afbfa5487cae15dad9de8685e8e26594a Mon Sep 17 00:00:00 2001 From: Ivan Magda Date: Tue, 13 Feb 2024 11:16:47 +0800 Subject: [PATCH 049/104] iOS: Remove unnecessary StateObject for tabs routers --- .../Sources/Modules/Home/HomeAssembly.swift | 2 +- .../Sources/Modules/Home/Views/HomeView.swift | 10 ++-------- .../Modules/Leaderboard/Views/LeaderboardView.swift | 9 +-------- .../Sources/Modules/Profile/Views/ProfileView.swift | 9 +-------- .../Sources/Modules/StudyPlan/StudyPlanAssembly.swift | 2 +- .../Modules/StudyPlan/Views/StudyPlanView.swift | 4 ++-- 6 files changed, 8 insertions(+), 28 deletions(-) diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/Home/HomeAssembly.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/Home/HomeAssembly.swift index 4b51e29dc4..195f0d7512 100644 --- a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/Home/HomeAssembly.swift +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/Home/HomeAssembly.swift @@ -9,7 +9,7 @@ final class HomeAssembly: UIKitAssembly { feature: homeComponent.homeFeature ) - let stackRouter = SwiftUIStackRouter() + let stackRouter = StackRouter() let panModalPresenter = PanModalPresenter() let homeView = HomeView( diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/Home/Views/HomeView.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/Home/Views/HomeView.swift index 84b6b5eb43..963f0ad248 100644 --- a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/Home/Views/HomeView.swift +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/Home/Views/HomeView.swift @@ -14,9 +14,8 @@ struct HomeView: View { @StateObject var viewModel: HomeViewModel - @StateObject var stackRouter: SwiftUIStackRouter - - @StateObject var panModalPresenter: PanModalPresenter + let stackRouter: StackRouterProtocol + let panModalPresenter: PanModalPresenter var body: some View { ZStack { @@ -193,8 +192,3 @@ private extension HomeView { } } } - -@available(iOS 17, *) -#Preview { - HomeAssembly().makeModule() -} diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/Leaderboard/Views/LeaderboardView.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/Leaderboard/Views/LeaderboardView.swift index 598bbe26ac..7a9064ff5f 100644 --- a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/Leaderboard/Views/LeaderboardView.swift +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/Leaderboard/Views/LeaderboardView.swift @@ -12,7 +12,7 @@ struct LeaderboardView: View { @StateObject var viewModel: LeaderboardViewModel - var stackRouter: StackRouterProtocol + let stackRouter: StackRouterProtocol var body: some View { ZStack { @@ -109,10 +109,3 @@ private extension LeaderboardView { } } } - -// MARK: - LeaderboardView (Preview) - - -@available(iOS 17, *) -#Preview { - LeaderboardAssembly().makeModule() -} diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/Profile/Views/ProfileView.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/Profile/Views/ProfileView.swift index 6df9bfdcfa..ba7c747982 100644 --- a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/Profile/Views/ProfileView.swift +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/Profile/Views/ProfileView.swift @@ -16,7 +16,7 @@ struct ProfileView: View { @StateObject var viewModel: ProfileViewModel - private(set) var panModalPresenter: PanModalPresenter + let panModalPresenter: PanModalPresenter @State private var presentingSettings = false @@ -62,7 +62,6 @@ struct ProfileView: View { viewModel.onViewAction = nil } .navigationViewStyle(StackNavigationViewStyle()) - .environmentObject(PanModalPresenter()) } // MARK: Private API @@ -215,9 +214,3 @@ struct ProfileView: View { panModalPresenter.presentPanModal(panModal) } } - -struct ProfileView_Previews: PreviewProvider { - static var previews: some View { - ProfileAssembly.currentUser().makeModule() - } -} diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StudyPlan/StudyPlanAssembly.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StudyPlan/StudyPlanAssembly.swift index 58e6925ebc..0006834f6b 100644 --- a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StudyPlan/StudyPlanAssembly.swift +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StudyPlan/StudyPlanAssembly.swift @@ -8,7 +8,7 @@ final class StudyPlanAssembly: UIKitAssembly { feature: studyPlanScreenComponent.studyPlanScreenFeature ) - let stackRouter = SwiftUIStackRouter() + let stackRouter = StackRouter() let panModalPresenter = PanModalPresenter() let trackView = StudyPlanView( diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StudyPlan/Views/StudyPlanView.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StudyPlan/Views/StudyPlanView.swift index 9c579521ce..dc4b7494c9 100644 --- a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StudyPlan/Views/StudyPlanView.swift +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StudyPlan/Views/StudyPlanView.swift @@ -14,8 +14,8 @@ struct StudyPlanView: View { @StateObject var viewModel: StudyPlanViewModel - @StateObject var stackRouter: SwiftUIStackRouter - @StateObject var panModalPresenter: PanModalPresenter + let stackRouter: StackRouterProtocol + let panModalPresenter: PanModalPresenter var body: some View { ZStack { From cb09a03810ce8b76f9cbbcd104bc96b24309a11e Mon Sep 17 00:00:00 2001 From: github-actions Date: Tue, 13 Feb 2024 03:17:37 +0000 Subject: [PATCH 050/104] iOS: Bump build number --- .../NotificationServiceExtension/Info.plist | 2 +- .../iosHyperskillApp.xcodeproj/project.pbxproj | 16 ++++++++-------- iosHyperskillApp/iosHyperskillApp/Info.plist | 2 +- .../iosHyperskillAppTests/Info.plist | 2 +- .../iosHyperskillAppUITests/Info.plist | 2 +- 5 files changed, 12 insertions(+), 12 deletions(-) diff --git a/iosHyperskillApp/NotificationServiceExtension/Info.plist b/iosHyperskillApp/NotificationServiceExtension/Info.plist index a2ddeb89d4..b082215b93 100644 --- a/iosHyperskillApp/NotificationServiceExtension/Info.plist +++ b/iosHyperskillApp/NotificationServiceExtension/Info.plist @@ -9,7 +9,7 @@ CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleVersion - 324 + 325 CFBundleShortVersionString 1.50 CFBundlePackageType diff --git a/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj b/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj index b14c424db5..fea2b6cc50 100644 --- a/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj +++ b/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj @@ -5037,7 +5037,7 @@ buildSettings = { CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 324; + CURRENT_PROJECT_VERSION = 325; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = 3DWS674B2M; GENERATE_INFOPLIST_FILE = NO; @@ -5058,7 +5058,7 @@ buildSettings = { CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 324; + CURRENT_PROJECT_VERSION = 325; DEVELOPMENT_TEAM = 3DWS674B2M; GENERATE_INFOPLIST_FILE = NO; INFOPLIST_FILE = iosHyperskillAppUITests/Info.plist; @@ -5079,7 +5079,7 @@ BUNDLE_LOADER = "$(TEST_HOST)"; CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 324; + CURRENT_PROJECT_VERSION = 325; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = 3DWS674B2M; INFOPLIST_FILE = iosHyperskillAppTests/Info.plist; @@ -5100,7 +5100,7 @@ BUNDLE_LOADER = "$(TEST_HOST)"; CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 324; + CURRENT_PROJECT_VERSION = 325; DEVELOPMENT_TEAM = 3DWS674B2M; INFOPLIST_FILE = iosHyperskillAppTests/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; @@ -5121,7 +5121,7 @@ CODE_SIGN_ENTITLEMENTS = NotificationServiceExtension/NotificationServiceExtension.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 324; + CURRENT_PROJECT_VERSION = 325; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = 3DWS674B2M; INFOPLIST_FILE = NotificationServiceExtension/Info.plist; @@ -5149,7 +5149,7 @@ CODE_SIGN_ENTITLEMENTS = NotificationServiceExtension/NotificationServiceExtension.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 324; + CURRENT_PROJECT_VERSION = 325; DEVELOPMENT_TEAM = 3DWS674B2M; INFOPLIST_FILE = NotificationServiceExtension/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; @@ -5294,7 +5294,7 @@ CODE_SIGN_ENTITLEMENTS = iosHyperskillApp/iosHyperskillApp.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 324; + CURRENT_PROJECT_VERSION = 325; DEVELOPMENT_ASSET_PATHS = "\"iosHyperskillApp/Preview Content\""; DEVELOPMENT_TEAM = 3DWS674B2M; ENABLE_PREVIEWS = YES; @@ -5330,7 +5330,7 @@ CODE_SIGN_ENTITLEMENTS = iosHyperskillApp/iosHyperskillApp.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 324; + CURRENT_PROJECT_VERSION = 325; DEVELOPMENT_ASSET_PATHS = "\"iosHyperskillApp/Preview Content\""; DEVELOPMENT_TEAM = 3DWS674B2M; ENABLE_PREVIEWS = YES; diff --git a/iosHyperskillApp/iosHyperskillApp/Info.plist b/iosHyperskillApp/iosHyperskillApp/Info.plist index 025c59553e..5b01667a95 100644 --- a/iosHyperskillApp/iosHyperskillApp/Info.plist +++ b/iosHyperskillApp/iosHyperskillApp/Info.plist @@ -36,7 +36,7 @@ CFBundleVersion - 324 + 325 FirebaseAppDelegateProxyEnabled FirebaseMessagingAutoInitEnabled diff --git a/iosHyperskillApp/iosHyperskillAppTests/Info.plist b/iosHyperskillApp/iosHyperskillAppTests/Info.plist index 9ce339cb37..6dc0eda816 100644 --- a/iosHyperskillApp/iosHyperskillAppTests/Info.plist +++ b/iosHyperskillApp/iosHyperskillAppTests/Info.plist @@ -15,6 +15,6 @@ CFBundleShortVersionString 1.50 CFBundleVersion - 324 + 325 diff --git a/iosHyperskillApp/iosHyperskillAppUITests/Info.plist b/iosHyperskillApp/iosHyperskillAppUITests/Info.plist index 81bd848a04..799078cdf7 100644 --- a/iosHyperskillApp/iosHyperskillAppUITests/Info.plist +++ b/iosHyperskillApp/iosHyperskillAppUITests/Info.plist @@ -15,6 +15,6 @@ CFBundleShortVersionString 1.50 CFBundleVersion - 324 + 325 From 33624694079cdd28a44114a68506c08258357e0f Mon Sep 17 00:00:00 2001 From: Ivan Magda Date: Tue, 13 Feb 2024 12:42:00 +0800 Subject: [PATCH 051/104] ALTAPPS-1104: Add RemoveInlineFrameElementsInjection --- .../ContentProcessingInjection.swift | 21 +++++++++++++++++++ .../Processing/ContentProcessor.swift | 3 ++- 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Frameworks/ContentProcessor/Processing/ContentProcessingInjection.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Frameworks/ContentProcessor/Processing/ContentProcessingInjection.swift index aba6f05389..ad817d575f 100644 --- a/iosHyperskillApp/iosHyperskillApp/Sources/Frameworks/ContentProcessor/Processing/ContentProcessingInjection.swift +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Frameworks/ContentProcessor/Processing/ContentProcessingInjection.swift @@ -116,6 +116,27 @@ final class DataMobileHiddenElementsInjection: ContentProcessingInjection { } } +/// Removes all iframe elements. +final class RemoveInlineFrameElementsInjection: ContentProcessingInjection { + var headScript: String { + """ + + """ + } + + func shouldInject(to code: String) -> Bool { + code.contains(" Date: Tue, 13 Feb 2024 04:43:13 +0000 Subject: [PATCH 052/104] iOS: Bump build number --- .../NotificationServiceExtension/Info.plist | 2 +- .../iosHyperskillApp.xcodeproj/project.pbxproj | 16 ++++++++-------- iosHyperskillApp/iosHyperskillApp/Info.plist | 2 +- .../iosHyperskillAppTests/Info.plist | 2 +- .../iosHyperskillAppUITests/Info.plist | 2 +- 5 files changed, 12 insertions(+), 12 deletions(-) diff --git a/iosHyperskillApp/NotificationServiceExtension/Info.plist b/iosHyperskillApp/NotificationServiceExtension/Info.plist index b082215b93..ecb4c3589d 100644 --- a/iosHyperskillApp/NotificationServiceExtension/Info.plist +++ b/iosHyperskillApp/NotificationServiceExtension/Info.plist @@ -9,7 +9,7 @@ CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleVersion - 325 + 326 CFBundleShortVersionString 1.50 CFBundlePackageType diff --git a/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj b/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj index fea2b6cc50..60e401a1fe 100644 --- a/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj +++ b/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj @@ -5037,7 +5037,7 @@ buildSettings = { CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 325; + CURRENT_PROJECT_VERSION = 326; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = 3DWS674B2M; GENERATE_INFOPLIST_FILE = NO; @@ -5058,7 +5058,7 @@ buildSettings = { CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 325; + CURRENT_PROJECT_VERSION = 326; DEVELOPMENT_TEAM = 3DWS674B2M; GENERATE_INFOPLIST_FILE = NO; INFOPLIST_FILE = iosHyperskillAppUITests/Info.plist; @@ -5079,7 +5079,7 @@ BUNDLE_LOADER = "$(TEST_HOST)"; CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 325; + CURRENT_PROJECT_VERSION = 326; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = 3DWS674B2M; INFOPLIST_FILE = iosHyperskillAppTests/Info.plist; @@ -5100,7 +5100,7 @@ BUNDLE_LOADER = "$(TEST_HOST)"; CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 325; + CURRENT_PROJECT_VERSION = 326; DEVELOPMENT_TEAM = 3DWS674B2M; INFOPLIST_FILE = iosHyperskillAppTests/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; @@ -5121,7 +5121,7 @@ CODE_SIGN_ENTITLEMENTS = NotificationServiceExtension/NotificationServiceExtension.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 325; + CURRENT_PROJECT_VERSION = 326; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = 3DWS674B2M; INFOPLIST_FILE = NotificationServiceExtension/Info.plist; @@ -5149,7 +5149,7 @@ CODE_SIGN_ENTITLEMENTS = NotificationServiceExtension/NotificationServiceExtension.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 325; + CURRENT_PROJECT_VERSION = 326; DEVELOPMENT_TEAM = 3DWS674B2M; INFOPLIST_FILE = NotificationServiceExtension/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; @@ -5294,7 +5294,7 @@ CODE_SIGN_ENTITLEMENTS = iosHyperskillApp/iosHyperskillApp.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 325; + CURRENT_PROJECT_VERSION = 326; DEVELOPMENT_ASSET_PATHS = "\"iosHyperskillApp/Preview Content\""; DEVELOPMENT_TEAM = 3DWS674B2M; ENABLE_PREVIEWS = YES; @@ -5330,7 +5330,7 @@ CODE_SIGN_ENTITLEMENTS = iosHyperskillApp/iosHyperskillApp.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 325; + CURRENT_PROJECT_VERSION = 326; DEVELOPMENT_ASSET_PATHS = "\"iosHyperskillApp/Preview Content\""; DEVELOPMENT_TEAM = 3DWS674B2M; ENABLE_PREVIEWS = YES; diff --git a/iosHyperskillApp/iosHyperskillApp/Info.plist b/iosHyperskillApp/iosHyperskillApp/Info.plist index 5b01667a95..04ca128aa0 100644 --- a/iosHyperskillApp/iosHyperskillApp/Info.plist +++ b/iosHyperskillApp/iosHyperskillApp/Info.plist @@ -36,7 +36,7 @@ CFBundleVersion - 325 + 326 FirebaseAppDelegateProxyEnabled FirebaseMessagingAutoInitEnabled diff --git a/iosHyperskillApp/iosHyperskillAppTests/Info.plist b/iosHyperskillApp/iosHyperskillAppTests/Info.plist index 6dc0eda816..6e52bb36f9 100644 --- a/iosHyperskillApp/iosHyperskillAppTests/Info.plist +++ b/iosHyperskillApp/iosHyperskillAppTests/Info.plist @@ -15,6 +15,6 @@ CFBundleShortVersionString 1.50 CFBundleVersion - 325 + 326 diff --git a/iosHyperskillApp/iosHyperskillAppUITests/Info.plist b/iosHyperskillApp/iosHyperskillAppUITests/Info.plist index 799078cdf7..a516d77527 100644 --- a/iosHyperskillApp/iosHyperskillAppUITests/Info.plist +++ b/iosHyperskillApp/iosHyperskillAppUITests/Info.plist @@ -15,6 +15,6 @@ CFBundleShortVersionString 1.50 CFBundleVersion - 325 + 326 From 8390d3c66d8ac9031ec3a76ab175a1365a64d1a5 Mon Sep 17 00:00:00 2001 From: Ivan Magda Date: Tue, 13 Feb 2024 15:10:19 +0700 Subject: [PATCH 053/104] Shared: Add Pull-to-refresh for problems limit widget on study plan (#896) ^ALTAPPS-1087 --- .../view/ui/delegate/ProblemsLimitDelegate.kt | 4 +- .../Views/ProblemsLimitWidgetView.swift | 18 ++++--- .../StudyPlan/StudyPlanViewModel.swift | 2 +- .../hyperskill/HyperskillAnalyticPart.kt | 1 + ...ryContentLoadingHyperskillAnalyticEvent.kt | 30 +++++++++++ .../domain/model/ProblemsLimitScreen.kt | 7 +++ .../injection/ProblemsLimitComponentImpl.kt | 12 +++-- .../ProblemsLimitActionDispatcher.kt | 22 +++++--- .../presentation/ProblemsLimitFeature.kt | 39 ++++++++------ .../presentation/ProblemsLimitReducer.kt | 42 ++++++++++----- .../presentation/StudyPlanScreenFeature.kt | 5 +- .../presentation/StudyPlanScreenReducer.kt | 52 +++++++++++-------- 12 files changed, 157 insertions(+), 77 deletions(-) create mode 100644 shared/src/commonMain/kotlin/org/hyperskill/app/problems_limit/domain/analytic/ProblemsLimitClickedRetryContentLoadingHyperskillAnalyticEvent.kt diff --git a/androidHyperskillApp/src/main/java/org/hyperskill/app/android/problems_limit/view/ui/delegate/ProblemsLimitDelegate.kt b/androidHyperskillApp/src/main/java/org/hyperskill/app/android/problems_limit/view/ui/delegate/ProblemsLimitDelegate.kt index a6b7f6b5ad..303f513f86 100644 --- a/androidHyperskillApp/src/main/java/org/hyperskill/app/android/problems_limit/view/ui/delegate/ProblemsLimitDelegate.kt +++ b/androidHyperskillApp/src/main/java/org/hyperskill/app/android/problems_limit/view/ui/delegate/ProblemsLimitDelegate.kt @@ -33,9 +33,7 @@ class ProblemsLimitDelegate( } viewBinding.problemsLimitRetryButton.setOnClickListener { - onNewMessage( - ProblemsLimitFeature.Message.Initialize(forceUpdate = true) - ) + onNewMessage(ProblemsLimitFeature.Message.RetryContentLoading) } } diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/ProblemsLimit/Views/ProblemsLimitWidgetView.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/ProblemsLimit/Views/ProblemsLimitWidgetView.swift index 2d6883fb17..0f2efddaa7 100644 --- a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/ProblemsLimit/Views/ProblemsLimitWidgetView.swift +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/ProblemsLimit/Views/ProblemsLimitWidgetView.swift @@ -26,23 +26,25 @@ struct ProblemsLimitWidgetView: View { Text(stepsLimitLabel) .font(.subheadline) .foregroundColor(.primaryText) + .animation(.default, value: stepsLimitLabel) if let updateInLabel { Text(updateInLabel) .font(.caption) .foregroundColor(.secondaryText) + .animation(.default, value: updateInLabel) } } } } } -struct ProblemsLimitWidgetView_Previews: PreviewProvider { - static var previews: some View { - ProblemsLimitWidgetView( - stepsLimitProgress: 0.6, - stepsLimitLabel: "3/5 problems limit", - updateInLabel: "Update in 2 hours" - ) - } +#if DEBUG +#Preview { + ProblemsLimitWidgetView( + stepsLimitProgress: 0.6, + stepsLimitLabel: "3/5 problems limit", + updateInLabel: "Update in 2 hours" + ) } +#endif diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StudyPlan/StudyPlanViewModel.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StudyPlan/StudyPlanViewModel.swift index 9a505f67c4..c80ee89608 100644 --- a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StudyPlan/StudyPlanViewModel.swift +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StudyPlan/StudyPlanViewModel.swift @@ -91,7 +91,7 @@ final class StudyPlanViewModel: FeatureViewModel< func doReloadProblemsLimit() { onNewMessage( StudyPlanScreenFeatureMessageProblemsLimitMessage( - message: ProblemsLimitFeatureMessageInitialize(forceUpdate: true) + message: ProblemsLimitFeatureMessageRetryContentLoading() ) ) } diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/analytic/domain/model/hyperskill/HyperskillAnalyticPart.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/analytic/domain/model/hyperskill/HyperskillAnalyticPart.kt index d4ca91ea09..598d4558d0 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/analytic/domain/model/hyperskill/HyperskillAnalyticPart.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/analytic/domain/model/hyperskill/HyperskillAnalyticPart.kt @@ -21,6 +21,7 @@ enum class HyperskillAnalyticPart(val partName: String) { DAILY_STEP_COMPLETED_MODAL("daily_step_completed_modal"), TOPIC_COMPLETED_MODAL("topic_completed_modal"), PROBLEMS_LIMIT_REACHED_MODAL("problems_limit_reached_modal"), + PROBLEMS_LIMIT_WIDGET("problems_limit_widget"), PARSONS_PROBLEM_ONBOARDING_MODAL("parsons_problem_onboarding_modal"), FILL_BLANKS_INPUT_MODE_ONBOARDING_MODAL("fill_blanks_input_mode_onboarding_modal"), FILL_BLANKS_SELECT_MODE_ONBOARDING_MODAL("fill_blanks_select_mode_onboarding_modal"), diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/problems_limit/domain/analytic/ProblemsLimitClickedRetryContentLoadingHyperskillAnalyticEvent.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/problems_limit/domain/analytic/ProblemsLimitClickedRetryContentLoadingHyperskillAnalyticEvent.kt new file mode 100644 index 0000000000..b85410907e --- /dev/null +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/problems_limit/domain/analytic/ProblemsLimitClickedRetryContentLoadingHyperskillAnalyticEvent.kt @@ -0,0 +1,30 @@ +package org.hyperskill.app.problems_limit.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.HyperskillAnalyticPart +import org.hyperskill.app.analytic.domain.model.hyperskill.HyperskillAnalyticTarget +import org.hyperskill.app.problems_limit.domain.model.ProblemsLimitScreen + +/** + * Represents a click analytic event of the error state placeholder retry button. + * + * JSON payload: + * ``` + * { + * "route": "/home", + * "action": "click", + * "part": "problems_limit_widget", + * "target": "retry" + * } + * ``` + * @see HyperskillAnalyticEvent + */ +class ProblemsLimitClickedRetryContentLoadingHyperskillAnalyticEvent( + screen: ProblemsLimitScreen +) : HyperskillAnalyticEvent( + screen.analyticRoute, + HyperskillAnalyticAction.CLICK, + HyperskillAnalyticPart.PROBLEMS_LIMIT_WIDGET, + HyperskillAnalyticTarget.RETRY +) \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/problems_limit/domain/model/ProblemsLimitScreen.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/problems_limit/domain/model/ProblemsLimitScreen.kt index 96801e50d0..bf13c0e5ba 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/problems_limit/domain/model/ProblemsLimitScreen.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/problems_limit/domain/model/ProblemsLimitScreen.kt @@ -1,5 +1,6 @@ package org.hyperskill.app.problems_limit.domain.model +import org.hyperskill.app.analytic.domain.model.hyperskill.HyperskillAnalyticRoute import org.hyperskill.app.sentry.domain.model.transaction.HyperskillSentryTransaction import org.hyperskill.app.sentry.domain.model.transaction.HyperskillSentryTransactionBuilder @@ -12,4 +13,10 @@ enum class ProblemsLimitScreen { HOME -> HyperskillSentryTransactionBuilder.buildProblemsLimitHomeScreenRemoteDataLoading() STUDY_PLAN -> HyperskillSentryTransactionBuilder.buildProblemsLimitStudyPlanScreenRemoteDataLoading() } + + internal val analyticRoute: HyperskillAnalyticRoute + get() = when (this) { + HOME -> HyperskillAnalyticRoute.Home() + STUDY_PLAN -> HyperskillAnalyticRoute.StudyPlan() + } } \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/problems_limit/injection/ProblemsLimitComponentImpl.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/problems_limit/injection/ProblemsLimitComponentImpl.kt index d6f4ccc3f8..c0d77c428f 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/problems_limit/injection/ProblemsLimitComponentImpl.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/problems_limit/injection/ProblemsLimitComponentImpl.kt @@ -7,7 +7,7 @@ import org.hyperskill.app.problems_limit.presentation.ProblemsLimitActionDispatc import org.hyperskill.app.problems_limit.presentation.ProblemsLimitReducer import org.hyperskill.app.problems_limit.view.mapper.ProblemsLimitViewStateMapper -class ProblemsLimitComponentImpl( +internal class ProblemsLimitComponentImpl( private val screen: ProblemsLimitScreen, private val appGraph: AppGraph ) : ProblemsLimitComponent { @@ -16,11 +16,13 @@ class ProblemsLimitComponentImpl( override val problemsLimitActionDispatcher: ProblemsLimitActionDispatcher get() = ProblemsLimitActionDispatcher( - ActionDispatcherOptions(), - appGraph.buildFreemiumDataComponent().freemiumInteractor, - appGraph.sentryComponent.sentryInteractor, - appGraph.stateRepositoriesComponent.currentSubscriptionStateRepository + config = ActionDispatcherOptions(), + freemiumInteractor = appGraph.buildFreemiumDataComponent().freemiumInteractor, + sentryInteractor = appGraph.sentryComponent.sentryInteractor, + analyticInteractor = appGraph.analyticComponent.analyticInteractor, + currentSubscriptionStateRepository = appGraph.stateRepositoriesComponent.currentSubscriptionStateRepository ) + override val problemsLimitViewStateMapper: ProblemsLimitViewStateMapper get() = ProblemsLimitViewStateMapper( appGraph.commonComponent.resourceProvider, diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/problems_limit/presentation/ProblemsLimitActionDispatcher.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/problems_limit/presentation/ProblemsLimitActionDispatcher.kt index 02ef4fc3aa..ed6c5362ed 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/problems_limit/presentation/ProblemsLimitActionDispatcher.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/problems_limit/presentation/ProblemsLimitActionDispatcher.kt @@ -4,10 +4,13 @@ import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock +import org.hyperskill.app.analytic.domain.interactor.AnalyticInteractor import org.hyperskill.app.core.presentation.ActionDispatcherOptions import org.hyperskill.app.core.presentation.Timer import org.hyperskill.app.freemium.domain.interactor.FreemiumInteractor import org.hyperskill.app.problems_limit.presentation.ProblemsLimitFeature.Action +import org.hyperskill.app.problems_limit.presentation.ProblemsLimitFeature.InternalAction +import org.hyperskill.app.problems_limit.presentation.ProblemsLimitFeature.InternalMessage import org.hyperskill.app.problems_limit.presentation.ProblemsLimitFeature.Message import org.hyperskill.app.sentry.domain.interactor.SentryInteractor import org.hyperskill.app.subscriptions.domain.repository.CurrentSubscriptionStateRepository @@ -17,12 +20,13 @@ class ProblemsLimitActionDispatcher( config: ActionDispatcherOptions, private val freemiumInteractor: FreemiumInteractor, private val sentryInteractor: SentryInteractor, + private val analyticInteractor: AnalyticInteractor, private val currentSubscriptionStateRepository: CurrentSubscriptionStateRepository ) : CoroutineActionDispatcher(config.createConfig()) { init { currentSubscriptionStateRepository.changes .onEach { - onNewMessage(Message.SubscriptionChanged(it)) + onNewMessage(InternalMessage.SubscriptionChanged(it)) } .launchIn(actionScope) } @@ -32,7 +36,7 @@ class ProblemsLimitActionDispatcher( override suspend fun doSuspendableAction(action: Action) { when (action) { - is Action.LoadSubscription -> { + is InternalAction.LoadSubscription -> { val sentryTransaction = action.screen.sentryTransaction sentryInteractor.startTransaction(sentryTransaction) @@ -40,7 +44,7 @@ class ProblemsLimitActionDispatcher( .isFreemiumEnabled() .getOrElse { sentryInteractor.finishTransaction(sentryTransaction, throwable = it) - return onNewMessage(Message.SubscriptionLoadingResult.Error) + return onNewMessage(InternalMessage.LoadSubscriptionResultError) } onNewMessage( @@ -48,28 +52,28 @@ class ProblemsLimitActionDispatcher( .fold( onSuccess = { sentryInteractor.finishTransaction(sentryTransaction) - Message.SubscriptionLoadingResult.Success( + InternalMessage.LoadSubscriptionResultSuccess( subscription = it, isFreemiumEnabled = isFreemiumEnabled ) }, onFailure = { sentryInteractor.finishTransaction(sentryTransaction, throwable = it) - Message.SubscriptionLoadingResult.Error + InternalMessage.LoadSubscriptionResultError } ) ) } - is Action.LaunchTimer -> { + is InternalAction.LaunchTimer -> { timerMutex.withLock { timer?.stop() timer = Timer( duration = action.updateIn, - onChange = { onNewMessage(Message.UpdateInChanged(it)) }, + onChange = { onNewMessage(InternalMessage.UpdateInChanged(it)) }, onFinish = { currentSubscriptionStateRepository.resetState() - onNewMessage(Message.Initialize(forceUpdate = true)) + onNewMessage(InternalMessage.Initialize(forceUpdate = true)) }, launchIn = actionScope ) @@ -77,6 +81,8 @@ class ProblemsLimitActionDispatcher( timer?.start() } } + is InternalAction.LogAnalyticEvent -> + analyticInteractor.logEvent(action.analyticEvent) } } } \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/problems_limit/presentation/ProblemsLimitFeature.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/problems_limit/presentation/ProblemsLimitFeature.kt index babbc9c4e2..2a2341c8d5 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/problems_limit/presentation/ProblemsLimitFeature.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/problems_limit/presentation/ProblemsLimitFeature.kt @@ -1,6 +1,7 @@ package org.hyperskill.app.problems_limit.presentation import kotlin.time.Duration +import org.hyperskill.app.analytic.domain.model.AnalyticEvent import org.hyperskill.app.problems_limit.domain.model.ProblemsLimitScreen import org.hyperskill.app.subscriptions.domain.model.Subscription @@ -47,29 +48,35 @@ object ProblemsLimitFeature { } sealed interface Message { - data class Initialize(val forceUpdate: Boolean = false) : Message - - object PullToRefresh : Message + object RetryContentLoading : Message + } - sealed interface SubscriptionLoadingResult : Message { - data class Success( - val subscription: Subscription, - val isFreemiumEnabled: Boolean - ) : SubscriptionLoadingResult + internal sealed interface InternalMessage : Message { + data class Initialize(val forceUpdate: Boolean = false) : InternalMessage + object PullToRefresh : InternalMessage - object Error : SubscriptionLoadingResult - } - - data class UpdateInChanged(val newUpdateIn: Duration) : Message + object LoadSubscriptionResultError : InternalMessage + data class LoadSubscriptionResultSuccess( + val subscription: Subscription, + val isFreemiumEnabled: Boolean + ) : InternalMessage - data class SubscriptionChanged(val newSubscription: Subscription) : Message + data class UpdateInChanged(val newUpdateIn: Duration) : InternalMessage + data class SubscriptionChanged(val newSubscription: Subscription) : InternalMessage } sealed interface Action { - data class LoadSubscription(val screen: ProblemsLimitScreen, val forceUpdate: Boolean) : Action + sealed interface ViewAction : Action + } - data class LaunchTimer(val updateIn: Duration) : Action + internal sealed interface InternalAction : Action { + data class LoadSubscription( + val screen: ProblemsLimitScreen, + val forceUpdate: Boolean + ) : InternalAction - sealed interface ViewAction : Action + data class LaunchTimer(val updateIn: Duration) : InternalAction + + data class LogAnalyticEvent(val analyticEvent: AnalyticEvent) : InternalAction } } \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/problems_limit/presentation/ProblemsLimitReducer.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/problems_limit/presentation/ProblemsLimitReducer.kt index 909a17b5e4..077b75c12e 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/problems_limit/presentation/ProblemsLimitReducer.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/problems_limit/presentation/ProblemsLimitReducer.kt @@ -2,8 +2,11 @@ package org.hyperskill.app.problems_limit.presentation import kotlin.time.Duration import kotlinx.datetime.Clock +import org.hyperskill.app.problems_limit.domain.analytic.ProblemsLimitClickedRetryContentLoadingHyperskillAnalyticEvent import org.hyperskill.app.problems_limit.domain.model.ProblemsLimitScreen import org.hyperskill.app.problems_limit.presentation.ProblemsLimitFeature.Action +import org.hyperskill.app.problems_limit.presentation.ProblemsLimitFeature.InternalAction +import org.hyperskill.app.problems_limit.presentation.ProblemsLimitFeature.InternalMessage import org.hyperskill.app.problems_limit.presentation.ProblemsLimitFeature.Message import org.hyperskill.app.problems_limit.presentation.ProblemsLimitFeature.State import org.hyperskill.app.subscriptions.domain.model.Subscription @@ -12,54 +15,69 @@ import ru.nobird.app.presentation.redux.reducer.StateReducer class ProblemsLimitReducer(private val screen: ProblemsLimitScreen) : StateReducer { override fun reduce(state: State, message: Message): Pair> = when (message) { - is Message.Initialize -> + is InternalMessage.Initialize -> if (state is State.Idle || message.forceUpdate) { State.Loading to setOf( - Action.LoadSubscription(screen = screen, forceUpdate = message.forceUpdate) + InternalAction.LoadSubscription(screen = screen, forceUpdate = message.forceUpdate) ) } else { null } - Message.SubscriptionLoadingResult.Error -> + Message.RetryContentLoading -> + if (state is State.NetworkError) { + State.Loading to setOf( + InternalAction.LoadSubscription(screen = screen, forceUpdate = true), + InternalAction.LogAnalyticEvent( + ProblemsLimitClickedRetryContentLoadingHyperskillAnalyticEvent(screen) + ) + ) + } else { + null + } + InternalMessage.LoadSubscriptionResultError -> State.NetworkError to emptySet() - is Message.SubscriptionLoadingResult.Success -> { + is InternalMessage.LoadSubscriptionResultSuccess -> { val updateIn = calculateUpdateInDuration(message.subscription) - State.Content(message.subscription, message.isFreemiumEnabled, updateIn) to buildSet { + State.Content( + subscription = message.subscription, + isFreemiumEnabled = message.isFreemiumEnabled, + updateIn = updateIn + ) to buildSet { if (updateIn != null) { - add(Action.LaunchTimer(updateIn)) + add(InternalAction.LaunchTimer(updateIn)) } } } - is Message.UpdateInChanged -> + is InternalMessage.UpdateInChanged -> if (state is State.Content) { state.copy(updateIn = message.newUpdateIn) to emptySet() } else { null } - is Message.SubscriptionChanged -> + is InternalMessage.SubscriptionChanged -> if (state is State.Content) { val updateIn = calculateUpdateInDuration(message.newSubscription) state.copy(subscription = message.newSubscription) to buildSet { if (updateIn != null) { - add(Action.LaunchTimer(updateIn)) + add(InternalAction.LaunchTimer(updateIn)) } } } else { null } - is Message.PullToRefresh -> + is InternalMessage.PullToRefresh -> when (state) { is State.Content -> if (state.isRefreshing) { null } else state.copy(isRefreshing = true) to setOf( - Action.LoadSubscription(screen = screen, forceUpdate = true) + InternalAction.LoadSubscription(screen = screen, forceUpdate = true) ) is State.NetworkError -> State.Loading to setOf( - Action.LoadSubscription(screen = screen, forceUpdate = false) + InternalAction.LoadSubscription(screen = screen, forceUpdate = false) ) else -> null diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/study_plan/screen/presentation/StudyPlanScreenFeature.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/study_plan/screen/presentation/StudyPlanScreenFeature.kt index 59360e265c..7e88745086 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/study_plan/screen/presentation/StudyPlanScreenFeature.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/study_plan/screen/presentation/StudyPlanScreenFeature.kt @@ -9,7 +9,6 @@ import org.hyperskill.app.study_plan.widget.presentation.StudyPlanWidgetFeature import org.hyperskill.app.study_plan.widget.view.model.StudyPlanWidgetViewState object StudyPlanScreenFeature { - internal data class State( val toolbarState: GamificationToolbarFeature.State, val problemsLimitState: ProblemsLimitFeature.State, @@ -30,7 +29,6 @@ object StudyPlanScreenFeature { ) sealed interface Message { - object Initialize : Message object RetryContentLoading : Message @@ -59,9 +57,11 @@ object StudyPlanScreenFeature { data class GamificationToolbarViewAction( val viewAction: GamificationToolbarFeature.Action.ViewAction ) : ViewAction + data class ProblemsLimitViewAction( val viewAction: ProblemsLimitFeature.Action.ViewAction ) : ViewAction + data class StudyPlanWidgetViewAction( val viewAction: StudyPlanWidgetFeature.Action.ViewAction ) : ViewAction @@ -69,7 +69,6 @@ object StudyPlanScreenFeature { } internal sealed interface InternalAction : Action { - data class LogAnalyticEvent(val analyticEvent: AnalyticEvent) : InternalAction data class GamificationToolbarAction( diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/study_plan/screen/presentation/StudyPlanScreenReducer.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/study_plan/screen/presentation/StudyPlanScreenReducer.kt index 1480fa0e6d..cadeb88003 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/study_plan/screen/presentation/StudyPlanScreenReducer.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/study_plan/screen/presentation/StudyPlanScreenReducer.kt @@ -27,26 +27,8 @@ internal class StudyPlanScreenReducer( initializeFeatures(state) is StudyPlanScreenFeature.Message.RetryContentLoading -> initializeFeatures(state, retryContentLoadingClicked = true) - is StudyPlanScreenFeature.Message.PullToRefresh -> { - val (widgetState, widgetActions) = reduceStudyPlanWidgetMessage( - state.studyPlanWidgetState, - StudyPlanWidgetFeature.Message.PullToRefresh - ) - - val (toolbarState, toolbarActions) = reduceToolbarMessage( - state.toolbarState, - GamificationToolbarFeature.InternalMessage.PullToRefresh - ) - - state.copy( - studyPlanWidgetState = widgetState, - toolbarState = toolbarState - ) to widgetActions + toolbarActions + setOf( - StudyPlanScreenFeature.InternalAction.LogAnalyticEvent( - StudyPlanClickedPullToRefreshHyperskillAnalyticEvent() - ) - ) - } + is StudyPlanScreenFeature.Message.PullToRefresh -> + handlePullToRefreshMessage(state) is StudyPlanScreenFeature.Message.ScreenBecomesActive -> { val (widgetState, widgetActions) = reduceStudyPlanWidgetMessage( state.studyPlanWidgetState, @@ -90,7 +72,7 @@ internal class StudyPlanScreenReducer( val (problemsLimitState, problemsLimitActions) = reduceProblemsLimitMessage( state.problemsLimitState, - ProblemsLimitFeature.Message.Initialize(forceUpdate = retryContentLoadingClicked) + ProblemsLimitFeature.InternalMessage.Initialize(forceUpdate = retryContentLoadingClicked) ) val (studyPlanState, studyPlanActions) = reduceStudyPlanWidgetMessage( @@ -115,6 +97,34 @@ internal class StudyPlanScreenReducer( ) to (toolbarActions + problemsLimitActions + studyPlanActions + analyticActions) } + private fun handlePullToRefreshMessage( + state: StudyPlanScreenFeature.State + ): StudyPlanScreenReducerResult { + val (toolbarState, toolbarActions) = reduceToolbarMessage( + state.toolbarState, + GamificationToolbarFeature.InternalMessage.PullToRefresh + ) + val (problemsLimitState, problemsLimitActions) = + reduceProblemsLimitMessage( + state.problemsLimitState, + ProblemsLimitFeature.InternalMessage.PullToRefresh + ) + val (studyPlanWidgetState, studyPlanWidgetActions) = reduceStudyPlanWidgetMessage( + state.studyPlanWidgetState, + StudyPlanWidgetFeature.Message.PullToRefresh + ) + + return state.copy( + toolbarState = toolbarState, + problemsLimitState = problemsLimitState, + studyPlanWidgetState = studyPlanWidgetState + ) to toolbarActions + studyPlanWidgetActions + problemsLimitActions + setOf( + StudyPlanScreenFeature.InternalAction.LogAnalyticEvent( + StudyPlanClickedPullToRefreshHyperskillAnalyticEvent() + ) + ) + } + private fun reduceToolbarMessage( state: GamificationToolbarFeature.State, message: GamificationToolbarFeature.Message From c56f3bd7ae083d09abcab7efb7eecf7a25cbe30d Mon Sep 17 00:00:00 2001 From: github-actions Date: Tue, 13 Feb 2024 08:11:05 +0000 Subject: [PATCH 054/104] Bump build number --- gradle/app.versions.toml | 2 +- .../NotificationServiceExtension/Info.plist | 2 +- .../iosHyperskillApp.xcodeproj/project.pbxproj | 16 ++++++++-------- iosHyperskillApp/iosHyperskillApp/Info.plist | 2 +- .../iosHyperskillAppTests/Info.plist | 2 +- .../iosHyperskillAppUITests/Info.plist | 2 +- 6 files changed, 13 insertions(+), 13 deletions(-) diff --git a/gradle/app.versions.toml b/gradle/app.versions.toml index 302dccab33..84b61ec886 100644 --- a/gradle/app.versions.toml +++ b/gradle/app.versions.toml @@ -3,4 +3,4 @@ minSdk = '24' targetSdk = '33' compileSdk = '33' versionName = '1.50' -versionCode = '320' \ No newline at end of file +versionCode = '321' \ No newline at end of file diff --git a/iosHyperskillApp/NotificationServiceExtension/Info.plist b/iosHyperskillApp/NotificationServiceExtension/Info.plist index ecb4c3589d..fb6e02d602 100644 --- a/iosHyperskillApp/NotificationServiceExtension/Info.plist +++ b/iosHyperskillApp/NotificationServiceExtension/Info.plist @@ -9,7 +9,7 @@ CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleVersion - 326 + 327 CFBundleShortVersionString 1.50 CFBundlePackageType diff --git a/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj b/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj index 60e401a1fe..61c1c52689 100644 --- a/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj +++ b/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj @@ -5037,7 +5037,7 @@ buildSettings = { CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 326; + CURRENT_PROJECT_VERSION = 327; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = 3DWS674B2M; GENERATE_INFOPLIST_FILE = NO; @@ -5058,7 +5058,7 @@ buildSettings = { CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 326; + CURRENT_PROJECT_VERSION = 327; DEVELOPMENT_TEAM = 3DWS674B2M; GENERATE_INFOPLIST_FILE = NO; INFOPLIST_FILE = iosHyperskillAppUITests/Info.plist; @@ -5079,7 +5079,7 @@ BUNDLE_LOADER = "$(TEST_HOST)"; CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 326; + CURRENT_PROJECT_VERSION = 327; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = 3DWS674B2M; INFOPLIST_FILE = iosHyperskillAppTests/Info.plist; @@ -5100,7 +5100,7 @@ BUNDLE_LOADER = "$(TEST_HOST)"; CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 326; + CURRENT_PROJECT_VERSION = 327; DEVELOPMENT_TEAM = 3DWS674B2M; INFOPLIST_FILE = iosHyperskillAppTests/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; @@ -5121,7 +5121,7 @@ CODE_SIGN_ENTITLEMENTS = NotificationServiceExtension/NotificationServiceExtension.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 326; + CURRENT_PROJECT_VERSION = 327; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = 3DWS674B2M; INFOPLIST_FILE = NotificationServiceExtension/Info.plist; @@ -5149,7 +5149,7 @@ CODE_SIGN_ENTITLEMENTS = NotificationServiceExtension/NotificationServiceExtension.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 326; + CURRENT_PROJECT_VERSION = 327; DEVELOPMENT_TEAM = 3DWS674B2M; INFOPLIST_FILE = NotificationServiceExtension/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; @@ -5294,7 +5294,7 @@ CODE_SIGN_ENTITLEMENTS = iosHyperskillApp/iosHyperskillApp.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 326; + CURRENT_PROJECT_VERSION = 327; DEVELOPMENT_ASSET_PATHS = "\"iosHyperskillApp/Preview Content\""; DEVELOPMENT_TEAM = 3DWS674B2M; ENABLE_PREVIEWS = YES; @@ -5330,7 +5330,7 @@ CODE_SIGN_ENTITLEMENTS = iosHyperskillApp/iosHyperskillApp.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 326; + CURRENT_PROJECT_VERSION = 327; DEVELOPMENT_ASSET_PATHS = "\"iosHyperskillApp/Preview Content\""; DEVELOPMENT_TEAM = 3DWS674B2M; ENABLE_PREVIEWS = YES; diff --git a/iosHyperskillApp/iosHyperskillApp/Info.plist b/iosHyperskillApp/iosHyperskillApp/Info.plist index 04ca128aa0..e548ec5cc6 100644 --- a/iosHyperskillApp/iosHyperskillApp/Info.plist +++ b/iosHyperskillApp/iosHyperskillApp/Info.plist @@ -36,7 +36,7 @@ CFBundleVersion - 326 + 327 FirebaseAppDelegateProxyEnabled FirebaseMessagingAutoInitEnabled diff --git a/iosHyperskillApp/iosHyperskillAppTests/Info.plist b/iosHyperskillApp/iosHyperskillAppTests/Info.plist index 6e52bb36f9..afc5602bcd 100644 --- a/iosHyperskillApp/iosHyperskillAppTests/Info.plist +++ b/iosHyperskillApp/iosHyperskillAppTests/Info.plist @@ -15,6 +15,6 @@ CFBundleShortVersionString 1.50 CFBundleVersion - 326 + 327 diff --git a/iosHyperskillApp/iosHyperskillAppUITests/Info.plist b/iosHyperskillApp/iosHyperskillAppUITests/Info.plist index a516d77527..6c55aa0243 100644 --- a/iosHyperskillApp/iosHyperskillAppUITests/Info.plist +++ b/iosHyperskillApp/iosHyperskillAppUITests/Info.plist @@ -15,6 +15,6 @@ CFBundleShortVersionString 1.50 CFBundleVersion - 326 + 327 From f4441bedccb631851f0a4dd21fe3df0b9e89d098 Mon Sep 17 00:00:00 2001 From: Ivan Magda Date: Wed, 14 Feb 2024 19:11:36 +0800 Subject: [PATCH 055/104] ALTAPPS-1135: Update request review modal layout --- .../RequestReviewModalView.swift | 32 +++++++++++------ .../UIKit/UIKitRoundedRectangleButton.swift | 4 ++- .../presentation/RequestReviewModalFeature.kt | 11 ++++-- .../RequestReviewModalViewStateMapper.kt | 35 +++++++++++++++---- .../commonMain/resources/MR/base/strings.xml | 6 ++-- 5 files changed, 66 insertions(+), 22 deletions(-) diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/RequestReview/RequestReviewModalView.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/RequestReview/RequestReviewModalView.swift index 57ef8f4b73..e9bb2df83d 100644 --- a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/RequestReview/RequestReviewModalView.swift +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/RequestReview/RequestReviewModalView.swift @@ -16,7 +16,7 @@ extension RequestReviewModalView { let descriptionLabelTextFont = UIFont.preferredFont(forTextStyle: .headline) let descriptionLabelTextColor = UIColor.newPrimaryText - let positiveButtonButtonHeight: CGFloat = 50 + let positiveButtonButtonHeight: CGFloat = 44 let negativeButtonButtonHeight: CGFloat = 44 let backgroundColor = UIColor.systemBackground @@ -48,7 +48,7 @@ final class RequestReviewModalView: UIView { let stackView = UIStackView() stackView.axis = .vertical stackView.spacing = appearance.buttonsContainerStackViewSpacing - stackView.alignment = .leading + stackView.alignment = .fill stackView.distribution = .fill return stackView }() @@ -72,14 +72,14 @@ final class RequestReviewModalView: UIView { return label }() - private lazy var positiveButton: UIButton = { - let button = UIKitRoundedRectangleButton.primary + private lazy var positiveButton: UIKitRoundedRectangleButton = { + let button = UIKitRoundedRectangleButton() button.addTarget(self, action: #selector(positiveButtonTapped), for: .touchUpInside) return button }() - private lazy var negativeButton: UIButton = { - let button = UIButton(type: .system) + private lazy var negativeButton: UIKitRoundedRectangleButton = { + let button = UIKitRoundedRectangleButton(style: .outline) button.addTarget(self, action: #selector(negativeButtonTapped), for: .touchUpInside) return button }() @@ -123,6 +123,18 @@ final class RequestReviewModalView: UIView { positiveButton.setTitle(state.positiveButtonText, for: .normal) negativeButton.setTitle(state.negativeButtonText, for: .normal) + if state.state == .negative { + positiveButton.style = .violet + + buttonsContainerStackView.axis = .vertical + buttonsContainerStackView.distribution = .fill + } else { + positiveButton.style = .outline + + buttonsContainerStackView.axis = .horizontal + buttonsContainerStackView.distribution = .fillEqually + } + layoutIfNeeded() invalidateIntrinsicContentSize() } @@ -171,13 +183,11 @@ extension RequestReviewModalView: ProgrammaticallyInitializableViewProtocol { positiveButton.translatesAutoresizingMaskIntoConstraints = false positiveButton.snp.makeConstraints { make in - make.width.equalToSuperview() make.height.equalTo(appearance.positiveButtonButtonHeight) } negativeButton.translatesAutoresizingMaskIntoConstraints = false negativeButton.snp.makeConstraints { make in - make.width.equalToSuperview() make.height.equalTo(appearance.negativeButtonButtonHeight) } } @@ -192,7 +202,8 @@ extension RequestReviewModalView: ProgrammaticallyInitializableViewProtocol { title: "Do you enjoy\nHyperskill app?", description: nil, positiveButtonText: "Yes", - negativeButtonText: "No" + negativeButtonText: "No", + state: RequestReviewModalFeature.ViewStateState.awaiting ) ) return view @@ -206,7 +217,8 @@ extension RequestReviewModalView: ProgrammaticallyInitializableViewProtocol { title: "Thank you!", description: "Share what you disliked to help us improve your experience.", positiveButtonText: "Write a request", - negativeButtonText: "Maybe later" + negativeButtonText: "Maybe later", + state: RequestReviewModalFeature.ViewStateState.negative ) ) return view diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Views/UIKit/UIKitRoundedRectangleButton.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Views/UIKit/UIKitRoundedRectangleButton.swift index 3c876ac59c..fe5da72d1e 100644 --- a/iosHyperskillApp/iosHyperskillApp/Sources/Views/UIKit/UIKitRoundedRectangleButton.swift +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Views/UIKit/UIKitRoundedRectangleButton.swift @@ -17,7 +17,9 @@ final class UIKitRoundedRectangleButton: UIKitBounceButton { var style: Style { didSet { - updateAppearance() + if style != oldValue { + updateAppearance() + } } } diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/request_review/modal/presentation/RequestReviewModalFeature.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/request_review/modal/presentation/RequestReviewModalFeature.kt index 85e59b31d2..2c3a902cc0 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/request_review/modal/presentation/RequestReviewModalFeature.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/request_review/modal/presentation/RequestReviewModalFeature.kt @@ -13,8 +13,15 @@ object RequestReviewModalFeature { val title: String, val description: String?, val positiveButtonText: String, - val negativeButtonText: String - ) + val negativeButtonText: String, + val state: State + ) { + enum class State { + AWAITING, + NEGATIVE, + POSITIVE + } + } sealed interface Message { object PositiveButtonClicked : Message diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/request_review/modal/view/mapper/RequestReviewModalViewStateMapper.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/request_review/modal/view/mapper/RequestReviewModalViewStateMapper.kt index 5038733a6b..6e931e5771 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/request_review/modal/view/mapper/RequestReviewModalViewStateMapper.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/request_review/modal/view/mapper/RequestReviewModalViewStateMapper.kt @@ -2,6 +2,7 @@ package org.hyperskill.app.request_review.modal.view.mapper import org.hyperskill.app.SharedResources import org.hyperskill.app.core.domain.platform.Platform +import org.hyperskill.app.core.domain.platform.PlatformType import org.hyperskill.app.core.view.mapper.ResourceProvider import org.hyperskill.app.request_review.modal.presentation.RequestReviewModalFeature.State import org.hyperskill.app.request_review.modal.presentation.RequestReviewModalFeature.ViewState @@ -19,12 +20,31 @@ internal class RequestReviewModalViewStateMapper( resourceProvider.getString(platform.appNameResource) ), description = null, - positiveButtonText = resourceProvider.getString( - SharedResources.strings.request_review_modal_state_awaiting_positive_button_text - ), - negativeButtonText = resourceProvider.getString( - SharedResources.strings.request_review_modal_state_awaiting_negative_button_text - ) + positiveButtonText = when (platform.platformType) { + PlatformType.IOS -> + resourceProvider.getString( + SharedResources.strings.request_review_modal_state_awaiting_positive_button_text_ios + ) + PlatformType.ANDROID -> + resourceProvider.getString( + SharedResources.strings.request_review_modal_state_awaiting_positive_button_text_android + ) + }, + negativeButtonText = when (platform.platformType) { + PlatformType.IOS -> + resourceProvider.getString( + SharedResources.strings.request_review_modal_state_awaiting_negative_button_text_ios + ) + PlatformType.ANDROID -> + resourceProvider.getString( + SharedResources.strings.request_review_modal_state_awaiting_negative_button_text_android + ) + }, + state = when (state) { + State.Awaiting -> ViewState.State.AWAITING + State.Positive -> ViewState.State.POSITIVE + State.Negative -> throw IllegalStateException("State.Negative shouldn't be mapped to ViewState") + } ) State.Negative -> ViewState( @@ -39,7 +59,8 @@ internal class RequestReviewModalViewStateMapper( ), negativeButtonText = resourceProvider.getString( SharedResources.strings.request_review_modal_state_negative_negative_button_text - ) + ), + state = ViewState.State.NEGATIVE ) } } \ No newline at end of file diff --git a/shared/src/commonMain/resources/MR/base/strings.xml b/shared/src/commonMain/resources/MR/base/strings.xml index 227fd5a657..fc7b45e811 100644 --- a/shared/src/commonMain/resources/MR/base/strings.xml +++ b/shared/src/commonMain/resources/MR/base/strings.xml @@ -563,8 +563,10 @@ Do you enjoy\n%s app? - Yes - No + \u1F44D Yes + \u1F44E No + 👍 Yes + 👎 No Thank you! Share what you disliked to help us improve your experience. From b3f03967d8b4bd66706a372d6f31b1d3d31a4b39 Mon Sep 17 00:00:00 2001 From: github-actions Date: Wed, 14 Feb 2024 11:12:29 +0000 Subject: [PATCH 056/104] Bump build number --- gradle/app.versions.toml | 2 +- .../NotificationServiceExtension/Info.plist | 2 +- .../iosHyperskillApp.xcodeproj/project.pbxproj | 16 ++++++++-------- iosHyperskillApp/iosHyperskillApp/Info.plist | 2 +- .../iosHyperskillAppTests/Info.plist | 2 +- .../iosHyperskillAppUITests/Info.plist | 2 +- 6 files changed, 13 insertions(+), 13 deletions(-) diff --git a/gradle/app.versions.toml b/gradle/app.versions.toml index 84b61ec886..8c539f2222 100644 --- a/gradle/app.versions.toml +++ b/gradle/app.versions.toml @@ -3,4 +3,4 @@ minSdk = '24' targetSdk = '33' compileSdk = '33' versionName = '1.50' -versionCode = '321' \ No newline at end of file +versionCode = '322' \ No newline at end of file diff --git a/iosHyperskillApp/NotificationServiceExtension/Info.plist b/iosHyperskillApp/NotificationServiceExtension/Info.plist index fb6e02d602..708b81e56c 100644 --- a/iosHyperskillApp/NotificationServiceExtension/Info.plist +++ b/iosHyperskillApp/NotificationServiceExtension/Info.plist @@ -9,7 +9,7 @@ CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleVersion - 327 + 328 CFBundleShortVersionString 1.50 CFBundlePackageType diff --git a/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj b/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj index 61c1c52689..138143fa81 100644 --- a/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj +++ b/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj @@ -5037,7 +5037,7 @@ buildSettings = { CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 327; + CURRENT_PROJECT_VERSION = 328; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = 3DWS674B2M; GENERATE_INFOPLIST_FILE = NO; @@ -5058,7 +5058,7 @@ buildSettings = { CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 327; + CURRENT_PROJECT_VERSION = 328; DEVELOPMENT_TEAM = 3DWS674B2M; GENERATE_INFOPLIST_FILE = NO; INFOPLIST_FILE = iosHyperskillAppUITests/Info.plist; @@ -5079,7 +5079,7 @@ BUNDLE_LOADER = "$(TEST_HOST)"; CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 327; + CURRENT_PROJECT_VERSION = 328; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = 3DWS674B2M; INFOPLIST_FILE = iosHyperskillAppTests/Info.plist; @@ -5100,7 +5100,7 @@ BUNDLE_LOADER = "$(TEST_HOST)"; CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 327; + CURRENT_PROJECT_VERSION = 328; DEVELOPMENT_TEAM = 3DWS674B2M; INFOPLIST_FILE = iosHyperskillAppTests/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; @@ -5121,7 +5121,7 @@ CODE_SIGN_ENTITLEMENTS = NotificationServiceExtension/NotificationServiceExtension.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 327; + CURRENT_PROJECT_VERSION = 328; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = 3DWS674B2M; INFOPLIST_FILE = NotificationServiceExtension/Info.plist; @@ -5149,7 +5149,7 @@ CODE_SIGN_ENTITLEMENTS = NotificationServiceExtension/NotificationServiceExtension.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 327; + CURRENT_PROJECT_VERSION = 328; DEVELOPMENT_TEAM = 3DWS674B2M; INFOPLIST_FILE = NotificationServiceExtension/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; @@ -5294,7 +5294,7 @@ CODE_SIGN_ENTITLEMENTS = iosHyperskillApp/iosHyperskillApp.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 327; + CURRENT_PROJECT_VERSION = 328; DEVELOPMENT_ASSET_PATHS = "\"iosHyperskillApp/Preview Content\""; DEVELOPMENT_TEAM = 3DWS674B2M; ENABLE_PREVIEWS = YES; @@ -5330,7 +5330,7 @@ CODE_SIGN_ENTITLEMENTS = iosHyperskillApp/iosHyperskillApp.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 327; + CURRENT_PROJECT_VERSION = 328; DEVELOPMENT_ASSET_PATHS = "\"iosHyperskillApp/Preview Content\""; DEVELOPMENT_TEAM = 3DWS674B2M; ENABLE_PREVIEWS = YES; diff --git a/iosHyperskillApp/iosHyperskillApp/Info.plist b/iosHyperskillApp/iosHyperskillApp/Info.plist index e548ec5cc6..e79ac8fc5e 100644 --- a/iosHyperskillApp/iosHyperskillApp/Info.plist +++ b/iosHyperskillApp/iosHyperskillApp/Info.plist @@ -36,7 +36,7 @@ CFBundleVersion - 327 + 328 FirebaseAppDelegateProxyEnabled FirebaseMessagingAutoInitEnabled diff --git a/iosHyperskillApp/iosHyperskillAppTests/Info.plist b/iosHyperskillApp/iosHyperskillAppTests/Info.plist index afc5602bcd..ce48746d2a 100644 --- a/iosHyperskillApp/iosHyperskillAppTests/Info.plist +++ b/iosHyperskillApp/iosHyperskillAppTests/Info.plist @@ -15,6 +15,6 @@ CFBundleShortVersionString 1.50 CFBundleVersion - 327 + 328 diff --git a/iosHyperskillApp/iosHyperskillAppUITests/Info.plist b/iosHyperskillApp/iosHyperskillAppUITests/Info.plist index 6c55aa0243..a63726246c 100644 --- a/iosHyperskillApp/iosHyperskillAppUITests/Info.plist +++ b/iosHyperskillApp/iosHyperskillAppUITests/Info.plist @@ -15,6 +15,6 @@ CFBundleShortVersionString 1.50 CFBundleVersion - 327 + 328 From 20d0fb2dfa52ee154b8f7c5433c034a1ba20728c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 15 Feb 2024 13:32:18 +0800 Subject: [PATCH 057/104] GitHub Actions: Bump gradle/wrapper-validation-action (#900) Bumps [gradle/wrapper-validation-action](https://github.com/gradle/wrapper-validation-action) from 2.1.0 to 2.1.1. - [Release notes](https://github.com/gradle/wrapper-validation-action/releases) - [Commits](https://github.com/gradle/wrapper-validation-action/compare/v2.1.0...v2.1.1) --- updated-dependencies: - dependency-name: gradle/wrapper-validation-action dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/android_beta_deployment.yml | 2 +- .github/workflows/android_deploy_to_firebase_manually.yml | 2 +- .github/workflows/android_release_deployment.yml | 2 +- .github/workflows/ci.yml | 2 +- .github/workflows/ios_beta_deployment.yml | 2 +- .github/workflows/ios_release_deployment.yml | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/android_beta_deployment.yml b/.github/workflows/android_beta_deployment.yml index 54180ff52c..64b02cbfb1 100644 --- a/.github/workflows/android_beta_deployment.yml +++ b/.github/workflows/android_beta_deployment.yml @@ -28,7 +28,7 @@ jobs: uses: actions/checkout@v4.1.1 - name: Gradle Wrapper Validation - uses: gradle/wrapper-validation-action@v2.1.0 + uses: gradle/wrapper-validation-action@v2.1.1 # Build and submit to the Firebase App Distribution firebase-deployment: diff --git a/.github/workflows/android_deploy_to_firebase_manually.yml b/.github/workflows/android_deploy_to_firebase_manually.yml index 53151f825a..4647b3305e 100644 --- a/.github/workflows/android_deploy_to_firebase_manually.yml +++ b/.github/workflows/android_deploy_to_firebase_manually.yml @@ -17,7 +17,7 @@ jobs: uses: actions/checkout@v4.1.1 - name: Gradle Wrapper Validation - uses: gradle/wrapper-validation-action@v2.1.0 + uses: gradle/wrapper-validation-action@v2.1.1 # Build and submit to the Firebase App Distribution firebase-deployment: diff --git a/.github/workflows/android_release_deployment.yml b/.github/workflows/android_release_deployment.yml index c6cdb90cb8..83e650a2e9 100644 --- a/.github/workflows/android_release_deployment.yml +++ b/.github/workflows/android_release_deployment.yml @@ -27,7 +27,7 @@ jobs: uses: actions/checkout@v4.1.1 - name: Gradle Wrapper Validation - uses: gradle/wrapper-validation-action@v2.1.0 + uses: gradle/wrapper-validation-action@v2.1.1 # Build and submit to the Google Play deployment: diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ef96b15f40..4dc55e9011 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -44,7 +44,7 @@ jobs: uses: actions/checkout@v4.1.1 - name: Gradle Wrapper Validation - uses: gradle/wrapper-validation-action@v2.1.0 + uses: gradle/wrapper-validation-action@v2.1.1 files-changed: name: Detect changes diff --git a/.github/workflows/ios_beta_deployment.yml b/.github/workflows/ios_beta_deployment.yml index 704b37d0e9..fdba903a12 100644 --- a/.github/workflows/ios_beta_deployment.yml +++ b/.github/workflows/ios_beta_deployment.yml @@ -28,7 +28,7 @@ jobs: uses: actions/checkout@v4.1.1 - name: Gradle Wrapper Validation - uses: gradle/wrapper-validation-action@v2.1.0 + uses: gradle/wrapper-validation-action@v2.1.1 # Build, archive for ad-hoc and submit to Firebase App Distribution deployment: diff --git a/.github/workflows/ios_release_deployment.yml b/.github/workflows/ios_release_deployment.yml index 3f53162c4c..f2a707e490 100644 --- a/.github/workflows/ios_release_deployment.yml +++ b/.github/workflows/ios_release_deployment.yml @@ -24,7 +24,7 @@ jobs: uses: actions/checkout@v4.1.1 - name: Gradle Wrapper Validation - uses: gradle/wrapper-validation-action@v2.1.0 + uses: gradle/wrapper-validation-action@v2.1.1 # Build, archive for app-store and submit to App Store Connect deployment: From 3b4a1d0c0641e0ca126413be2c98e6f191ca78e3 Mon Sep 17 00:00:00 2001 From: github-actions Date: Thu, 15 Feb 2024 05:32:58 +0000 Subject: [PATCH 058/104] Bump build number --- gradle/app.versions.toml | 2 +- .../NotificationServiceExtension/Info.plist | 2 +- .../iosHyperskillApp.xcodeproj/project.pbxproj | 16 ++++++++-------- iosHyperskillApp/iosHyperskillApp/Info.plist | 2 +- .../iosHyperskillAppTests/Info.plist | 2 +- .../iosHyperskillAppUITests/Info.plist | 2 +- 6 files changed, 13 insertions(+), 13 deletions(-) diff --git a/gradle/app.versions.toml b/gradle/app.versions.toml index 8c539f2222..948577c064 100644 --- a/gradle/app.versions.toml +++ b/gradle/app.versions.toml @@ -3,4 +3,4 @@ minSdk = '24' targetSdk = '33' compileSdk = '33' versionName = '1.50' -versionCode = '322' \ No newline at end of file +versionCode = '323' \ No newline at end of file diff --git a/iosHyperskillApp/NotificationServiceExtension/Info.plist b/iosHyperskillApp/NotificationServiceExtension/Info.plist index 708b81e56c..e02403682b 100644 --- a/iosHyperskillApp/NotificationServiceExtension/Info.plist +++ b/iosHyperskillApp/NotificationServiceExtension/Info.plist @@ -9,7 +9,7 @@ CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleVersion - 328 + 329 CFBundleShortVersionString 1.50 CFBundlePackageType diff --git a/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj b/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj index 138143fa81..aa7e350370 100644 --- a/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj +++ b/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj @@ -5037,7 +5037,7 @@ buildSettings = { CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 328; + CURRENT_PROJECT_VERSION = 329; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = 3DWS674B2M; GENERATE_INFOPLIST_FILE = NO; @@ -5058,7 +5058,7 @@ buildSettings = { CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 328; + CURRENT_PROJECT_VERSION = 329; DEVELOPMENT_TEAM = 3DWS674B2M; GENERATE_INFOPLIST_FILE = NO; INFOPLIST_FILE = iosHyperskillAppUITests/Info.plist; @@ -5079,7 +5079,7 @@ BUNDLE_LOADER = "$(TEST_HOST)"; CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 328; + CURRENT_PROJECT_VERSION = 329; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = 3DWS674B2M; INFOPLIST_FILE = iosHyperskillAppTests/Info.plist; @@ -5100,7 +5100,7 @@ BUNDLE_LOADER = "$(TEST_HOST)"; CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 328; + CURRENT_PROJECT_VERSION = 329; DEVELOPMENT_TEAM = 3DWS674B2M; INFOPLIST_FILE = iosHyperskillAppTests/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; @@ -5121,7 +5121,7 @@ CODE_SIGN_ENTITLEMENTS = NotificationServiceExtension/NotificationServiceExtension.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 328; + CURRENT_PROJECT_VERSION = 329; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = 3DWS674B2M; INFOPLIST_FILE = NotificationServiceExtension/Info.plist; @@ -5149,7 +5149,7 @@ CODE_SIGN_ENTITLEMENTS = NotificationServiceExtension/NotificationServiceExtension.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 328; + CURRENT_PROJECT_VERSION = 329; DEVELOPMENT_TEAM = 3DWS674B2M; INFOPLIST_FILE = NotificationServiceExtension/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; @@ -5294,7 +5294,7 @@ CODE_SIGN_ENTITLEMENTS = iosHyperskillApp/iosHyperskillApp.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 328; + CURRENT_PROJECT_VERSION = 329; DEVELOPMENT_ASSET_PATHS = "\"iosHyperskillApp/Preview Content\""; DEVELOPMENT_TEAM = 3DWS674B2M; ENABLE_PREVIEWS = YES; @@ -5330,7 +5330,7 @@ CODE_SIGN_ENTITLEMENTS = iosHyperskillApp/iosHyperskillApp.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 328; + CURRENT_PROJECT_VERSION = 329; DEVELOPMENT_ASSET_PATHS = "\"iosHyperskillApp/Preview Content\""; DEVELOPMENT_TEAM = 3DWS674B2M; ENABLE_PREVIEWS = YES; diff --git a/iosHyperskillApp/iosHyperskillApp/Info.plist b/iosHyperskillApp/iosHyperskillApp/Info.plist index e79ac8fc5e..bab3b1fb32 100644 --- a/iosHyperskillApp/iosHyperskillApp/Info.plist +++ b/iosHyperskillApp/iosHyperskillApp/Info.plist @@ -36,7 +36,7 @@ CFBundleVersion - 328 + 329 FirebaseAppDelegateProxyEnabled FirebaseMessagingAutoInitEnabled diff --git a/iosHyperskillApp/iosHyperskillAppTests/Info.plist b/iosHyperskillApp/iosHyperskillAppTests/Info.plist index ce48746d2a..503d565354 100644 --- a/iosHyperskillApp/iosHyperskillAppTests/Info.plist +++ b/iosHyperskillApp/iosHyperskillAppTests/Info.plist @@ -15,6 +15,6 @@ CFBundleShortVersionString 1.50 CFBundleVersion - 328 + 329 diff --git a/iosHyperskillApp/iosHyperskillAppUITests/Info.plist b/iosHyperskillApp/iosHyperskillAppUITests/Info.plist index a63726246c..f85a9734cf 100644 --- a/iosHyperskillApp/iosHyperskillAppUITests/Info.plist +++ b/iosHyperskillApp/iosHyperskillAppUITests/Info.plist @@ -15,6 +15,6 @@ CFBundleShortVersionString 1.50 CFBundleVersion - 328 + 329 From 7c5b702037a8ea2da6e992779c67996aedb26d3e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 16 Feb 2024 10:39:16 +0800 Subject: [PATCH 059/104] GitHub Actions: Bump dorny/paths-filter from 3.0.0 to 3.0.1 (#902) Bumps [dorny/paths-filter](https://github.com/dorny/paths-filter) from 3.0.0 to 3.0.1. - [Release notes](https://github.com/dorny/paths-filter/releases) - [Changelog](https://github.com/dorny/paths-filter/blob/master/CHANGELOG.md) - [Commits](https://github.com/dorny/paths-filter/compare/v3.0.0...v3.0.1) --- updated-dependencies: - dependency-name: dorny/paths-filter dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/detect_changed_files_reusable_workflow.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/detect_changed_files_reusable_workflow.yml b/.github/workflows/detect_changed_files_reusable_workflow.yml index ec08918f90..f88d1f64ab 100644 --- a/.github/workflows/detect_changed_files_reusable_workflow.yml +++ b/.github/workflows/detect_changed_files_reusable_workflow.yml @@ -55,7 +55,7 @@ jobs: uses: actions/checkout@v4.1.1 - name: Detect changes - uses: dorny/paths-filter@v3.0.0 + uses: dorny/paths-filter@v3.0.1 id: changes with: base: ${{ inputs.base }} From fa0004cf943c1ef21df3e12270d5a9b8dcf9ec22 Mon Sep 17 00:00:00 2001 From: github-actions Date: Fri, 16 Feb 2024 02:39:56 +0000 Subject: [PATCH 060/104] Bump build number --- gradle/app.versions.toml | 2 +- .../NotificationServiceExtension/Info.plist | 2 +- .../iosHyperskillApp.xcodeproj/project.pbxproj | 16 ++++++++-------- iosHyperskillApp/iosHyperskillApp/Info.plist | 2 +- .../iosHyperskillAppTests/Info.plist | 2 +- .../iosHyperskillAppUITests/Info.plist | 2 +- 6 files changed, 13 insertions(+), 13 deletions(-) diff --git a/gradle/app.versions.toml b/gradle/app.versions.toml index 948577c064..e3c7f4c85a 100644 --- a/gradle/app.versions.toml +++ b/gradle/app.versions.toml @@ -3,4 +3,4 @@ minSdk = '24' targetSdk = '33' compileSdk = '33' versionName = '1.50' -versionCode = '323' \ No newline at end of file +versionCode = '324' \ No newline at end of file diff --git a/iosHyperskillApp/NotificationServiceExtension/Info.plist b/iosHyperskillApp/NotificationServiceExtension/Info.plist index e02403682b..c69d17d40b 100644 --- a/iosHyperskillApp/NotificationServiceExtension/Info.plist +++ b/iosHyperskillApp/NotificationServiceExtension/Info.plist @@ -9,7 +9,7 @@ CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleVersion - 329 + 330 CFBundleShortVersionString 1.50 CFBundlePackageType diff --git a/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj b/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj index aa7e350370..6a5ccf3997 100644 --- a/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj +++ b/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj @@ -5037,7 +5037,7 @@ buildSettings = { CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 329; + CURRENT_PROJECT_VERSION = 330; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = 3DWS674B2M; GENERATE_INFOPLIST_FILE = NO; @@ -5058,7 +5058,7 @@ buildSettings = { CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 329; + CURRENT_PROJECT_VERSION = 330; DEVELOPMENT_TEAM = 3DWS674B2M; GENERATE_INFOPLIST_FILE = NO; INFOPLIST_FILE = iosHyperskillAppUITests/Info.plist; @@ -5079,7 +5079,7 @@ BUNDLE_LOADER = "$(TEST_HOST)"; CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 329; + CURRENT_PROJECT_VERSION = 330; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = 3DWS674B2M; INFOPLIST_FILE = iosHyperskillAppTests/Info.plist; @@ -5100,7 +5100,7 @@ BUNDLE_LOADER = "$(TEST_HOST)"; CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 329; + CURRENT_PROJECT_VERSION = 330; DEVELOPMENT_TEAM = 3DWS674B2M; INFOPLIST_FILE = iosHyperskillAppTests/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; @@ -5121,7 +5121,7 @@ CODE_SIGN_ENTITLEMENTS = NotificationServiceExtension/NotificationServiceExtension.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 329; + CURRENT_PROJECT_VERSION = 330; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = 3DWS674B2M; INFOPLIST_FILE = NotificationServiceExtension/Info.plist; @@ -5149,7 +5149,7 @@ CODE_SIGN_ENTITLEMENTS = NotificationServiceExtension/NotificationServiceExtension.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 329; + CURRENT_PROJECT_VERSION = 330; DEVELOPMENT_TEAM = 3DWS674B2M; INFOPLIST_FILE = NotificationServiceExtension/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; @@ -5294,7 +5294,7 @@ CODE_SIGN_ENTITLEMENTS = iosHyperskillApp/iosHyperskillApp.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 329; + CURRENT_PROJECT_VERSION = 330; DEVELOPMENT_ASSET_PATHS = "\"iosHyperskillApp/Preview Content\""; DEVELOPMENT_TEAM = 3DWS674B2M; ENABLE_PREVIEWS = YES; @@ -5330,7 +5330,7 @@ CODE_SIGN_ENTITLEMENTS = iosHyperskillApp/iosHyperskillApp.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 329; + CURRENT_PROJECT_VERSION = 330; DEVELOPMENT_ASSET_PATHS = "\"iosHyperskillApp/Preview Content\""; DEVELOPMENT_TEAM = 3DWS674B2M; ENABLE_PREVIEWS = YES; diff --git a/iosHyperskillApp/iosHyperskillApp/Info.plist b/iosHyperskillApp/iosHyperskillApp/Info.plist index bab3b1fb32..3261da8041 100644 --- a/iosHyperskillApp/iosHyperskillApp/Info.plist +++ b/iosHyperskillApp/iosHyperskillApp/Info.plist @@ -36,7 +36,7 @@ CFBundleVersion - 329 + 330 FirebaseAppDelegateProxyEnabled FirebaseMessagingAutoInitEnabled diff --git a/iosHyperskillApp/iosHyperskillAppTests/Info.plist b/iosHyperskillApp/iosHyperskillAppTests/Info.plist index 503d565354..e068ec8619 100644 --- a/iosHyperskillApp/iosHyperskillAppTests/Info.plist +++ b/iosHyperskillApp/iosHyperskillAppTests/Info.plist @@ -15,6 +15,6 @@ CFBundleShortVersionString 1.50 CFBundleVersion - 329 + 330 diff --git a/iosHyperskillApp/iosHyperskillAppUITests/Info.plist b/iosHyperskillApp/iosHyperskillAppUITests/Info.plist index f85a9734cf..8c6f3a3f53 100644 --- a/iosHyperskillApp/iosHyperskillAppUITests/Info.plist +++ b/iosHyperskillApp/iosHyperskillAppUITests/Info.plist @@ -15,6 +15,6 @@ CFBundleShortVersionString 1.50 CFBundleVersion - 329 + 330 From 518a08af44edb597f55a556004b5de1fa6580696 Mon Sep 17 00:00:00 2001 From: Ivan Magda Date: Fri, 16 Feb 2024 09:52:50 +0700 Subject: [PATCH 061/104] GitHub Actions: Update fastlane plugins (#903) * fastlane-plugin-firebase_app_distribution from 0.8.1 to 0.9.0 * fastlane-plugin-sentry from 1.18.0 to 1.19.0 --- androidHyperskillApp/Gemfile.lock | 6 +++--- iosHyperskillApp/Gemfile.lock | 8 ++++---- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/androidHyperskillApp/Gemfile.lock b/androidHyperskillApp/Gemfile.lock index b0f9a5b16e..db879a5683 100644 --- a/androidHyperskillApp/Gemfile.lock +++ b/androidHyperskillApp/Gemfile.lock @@ -106,7 +106,7 @@ GEM xcodeproj (>= 1.13.0, < 2.0.0) xcpretty (~> 0.3.0) xcpretty-travis-formatter (>= 0.0.3) - fastlane-plugin-firebase_app_distribution (0.8.1) + fastlane-plugin-firebase_app_distribution (0.9.0) google-apis-firebaseappdistribution_v1 (~> 0.3.0) google-apis-firebaseappdistribution_v1alpha (~> 0.2.0) gh_inspector (1.1.3) @@ -160,7 +160,7 @@ GEM mini_magick (4.12.0) mini_mime (1.1.5) multi_json (1.15.0) - multipart-post (2.3.0) + multipart-post (2.4.0) nanaimo (0.3.0) naturally (2.2.1) optparse (0.4.0) @@ -178,7 +178,7 @@ GEM ruby2_keywords (0.0.5) rubyzip (2.3.2) security (0.1.3) - signet (0.18.0) + signet (0.19.0) addressable (~> 2.8) faraday (>= 0.17.5, < 3.a) jwt (>= 1.5, < 3.0) diff --git a/iosHyperskillApp/Gemfile.lock b/iosHyperskillApp/Gemfile.lock index a82abce1d3..34af8071bf 100644 --- a/iosHyperskillApp/Gemfile.lock +++ b/iosHyperskillApp/Gemfile.lock @@ -178,10 +178,10 @@ GEM xcodeproj (>= 1.13.0, < 2.0.0) xcpretty (~> 0.3.0) xcpretty-travis-formatter (>= 0.0.3) - fastlane-plugin-firebase_app_distribution (0.8.1) + fastlane-plugin-firebase_app_distribution (0.9.0) google-apis-firebaseappdistribution_v1 (~> 0.3.0) google-apis-firebaseappdistribution_v1alpha (~> 0.2.0) - fastlane-plugin-sentry (1.18.0) + fastlane-plugin-sentry (1.19.0) os (~> 1.1, >= 1.1.4) ffi (1.16.3) fourflusher (2.3.1) @@ -245,7 +245,7 @@ GEM minitest (5.22.0) molinillo (0.8.0) multi_json (1.15.0) - multipart-post (2.3.0) + multipart-post (2.4.0) mutex_m (0.2.0) nanaimo (0.3.0) nap (1.1.0) @@ -268,7 +268,7 @@ GEM ruby2_keywords (0.0.5) rubyzip (2.3.2) security (0.1.3) - signet (0.18.0) + signet (0.19.0) addressable (~> 2.8) faraday (>= 0.17.5, < 3.a) jwt (>= 1.5, < 3.0) From a95d39296310061bba9fe2651daf1ff46c30ab51 Mon Sep 17 00:00:00 2001 From: github-actions Date: Fri, 16 Feb 2024 02:53:37 +0000 Subject: [PATCH 062/104] Bump build number --- gradle/app.versions.toml | 2 +- .../NotificationServiceExtension/Info.plist | 2 +- .../iosHyperskillApp.xcodeproj/project.pbxproj | 16 ++++++++-------- iosHyperskillApp/iosHyperskillApp/Info.plist | 2 +- .../iosHyperskillAppTests/Info.plist | 2 +- .../iosHyperskillAppUITests/Info.plist | 2 +- 6 files changed, 13 insertions(+), 13 deletions(-) diff --git a/gradle/app.versions.toml b/gradle/app.versions.toml index e3c7f4c85a..f9d2b483aa 100644 --- a/gradle/app.versions.toml +++ b/gradle/app.versions.toml @@ -3,4 +3,4 @@ minSdk = '24' targetSdk = '33' compileSdk = '33' versionName = '1.50' -versionCode = '324' \ No newline at end of file +versionCode = '325' \ No newline at end of file diff --git a/iosHyperskillApp/NotificationServiceExtension/Info.plist b/iosHyperskillApp/NotificationServiceExtension/Info.plist index c69d17d40b..2601c67758 100644 --- a/iosHyperskillApp/NotificationServiceExtension/Info.plist +++ b/iosHyperskillApp/NotificationServiceExtension/Info.plist @@ -9,7 +9,7 @@ CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleVersion - 330 + 331 CFBundleShortVersionString 1.50 CFBundlePackageType diff --git a/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj b/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj index 6a5ccf3997..d8152a6fdb 100644 --- a/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj +++ b/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj @@ -5037,7 +5037,7 @@ buildSettings = { CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 330; + CURRENT_PROJECT_VERSION = 331; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = 3DWS674B2M; GENERATE_INFOPLIST_FILE = NO; @@ -5058,7 +5058,7 @@ buildSettings = { CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 330; + CURRENT_PROJECT_VERSION = 331; DEVELOPMENT_TEAM = 3DWS674B2M; GENERATE_INFOPLIST_FILE = NO; INFOPLIST_FILE = iosHyperskillAppUITests/Info.plist; @@ -5079,7 +5079,7 @@ BUNDLE_LOADER = "$(TEST_HOST)"; CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 330; + CURRENT_PROJECT_VERSION = 331; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = 3DWS674B2M; INFOPLIST_FILE = iosHyperskillAppTests/Info.plist; @@ -5100,7 +5100,7 @@ BUNDLE_LOADER = "$(TEST_HOST)"; CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 330; + CURRENT_PROJECT_VERSION = 331; DEVELOPMENT_TEAM = 3DWS674B2M; INFOPLIST_FILE = iosHyperskillAppTests/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; @@ -5121,7 +5121,7 @@ CODE_SIGN_ENTITLEMENTS = NotificationServiceExtension/NotificationServiceExtension.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 330; + CURRENT_PROJECT_VERSION = 331; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = 3DWS674B2M; INFOPLIST_FILE = NotificationServiceExtension/Info.plist; @@ -5149,7 +5149,7 @@ CODE_SIGN_ENTITLEMENTS = NotificationServiceExtension/NotificationServiceExtension.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 330; + CURRENT_PROJECT_VERSION = 331; DEVELOPMENT_TEAM = 3DWS674B2M; INFOPLIST_FILE = NotificationServiceExtension/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; @@ -5294,7 +5294,7 @@ CODE_SIGN_ENTITLEMENTS = iosHyperskillApp/iosHyperskillApp.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 330; + CURRENT_PROJECT_VERSION = 331; DEVELOPMENT_ASSET_PATHS = "\"iosHyperskillApp/Preview Content\""; DEVELOPMENT_TEAM = 3DWS674B2M; ENABLE_PREVIEWS = YES; @@ -5330,7 +5330,7 @@ CODE_SIGN_ENTITLEMENTS = iosHyperskillApp/iosHyperskillApp.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 330; + CURRENT_PROJECT_VERSION = 331; DEVELOPMENT_ASSET_PATHS = "\"iosHyperskillApp/Preview Content\""; DEVELOPMENT_TEAM = 3DWS674B2M; ENABLE_PREVIEWS = YES; diff --git a/iosHyperskillApp/iosHyperskillApp/Info.plist b/iosHyperskillApp/iosHyperskillApp/Info.plist index 3261da8041..7754ee0448 100644 --- a/iosHyperskillApp/iosHyperskillApp/Info.plist +++ b/iosHyperskillApp/iosHyperskillApp/Info.plist @@ -36,7 +36,7 @@ CFBundleVersion - 330 + 331 FirebaseAppDelegateProxyEnabled FirebaseMessagingAutoInitEnabled diff --git a/iosHyperskillApp/iosHyperskillAppTests/Info.plist b/iosHyperskillApp/iosHyperskillAppTests/Info.plist index e068ec8619..8ef5ec80da 100644 --- a/iosHyperskillApp/iosHyperskillAppTests/Info.plist +++ b/iosHyperskillApp/iosHyperskillAppTests/Info.plist @@ -15,6 +15,6 @@ CFBundleShortVersionString 1.50 CFBundleVersion - 330 + 331 diff --git a/iosHyperskillApp/iosHyperskillAppUITests/Info.plist b/iosHyperskillApp/iosHyperskillAppUITests/Info.plist index 8c6f3a3f53..30f6a147b2 100644 --- a/iosHyperskillApp/iosHyperskillAppUITests/Info.plist +++ b/iosHyperskillApp/iosHyperskillAppUITests/Info.plist @@ -15,6 +15,6 @@ CFBundleShortVersionString 1.50 CFBundleVersion - 330 + 331 From 6767fa46b24087ade7447c7e38dd3642fac31b73 Mon Sep 17 00:00:00 2001 From: Ivan Magda Date: Fri, 16 Feb 2024 12:05:02 +0700 Subject: [PATCH 063/104] Shared: Remove check for wasNotificationOnboardingShown flag (#905) ^ALTAPPS-1091 --- .../cache/OnboardingCacheDataSourceImpl.kt | 7 ------ .../cache/OnboardingCacheKeyValues.kt | 1 - .../repository/OnboardingRepositoryImpl.kt | 7 ------ .../data/source/OnboardingCacheDataSource.kt | 3 --- .../domain/interactor/OnboardingInteractor.kt | 7 ------ .../domain/repository/OnboardingRepository.kt | 3 --- .../WelcomeOnboardingActionDispatcher.kt | 7 ------ .../presentation/WelcomeOnboardingFeature.kt | 6 ----- .../presentation/WelcomeOnboardingReducer.kt | 23 +------------------ 9 files changed, 1 insertion(+), 63 deletions(-) diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/onboarding/cache/OnboardingCacheDataSourceImpl.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/onboarding/cache/OnboardingCacheDataSourceImpl.kt index 903fbd6226..c5d995c025 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/onboarding/cache/OnboardingCacheDataSourceImpl.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/onboarding/cache/OnboardingCacheDataSourceImpl.kt @@ -27,13 +27,6 @@ internal class OnboardingCacheDataSourceImpl( settings.putBoolean(OnboardingCacheKeyValues.IS_FILL_BLANKS_SELECT_MODE_ONBOARDING_SHOWN, isShown) } - override fun wasNotificationOnboardingShown(): Boolean = - settings.getBoolean(OnboardingCacheKeyValues.IS_NOTIFICATIONS_ONBOARDING_SHOWN, defaultValue = false) - - override fun setNotificationOnboardingWasShown(wasShown: Boolean) { - settings.putBoolean(OnboardingCacheKeyValues.IS_NOTIFICATIONS_ONBOARDING_SHOWN, wasShown) - } - override fun wasFirstProblemOnboardingShown(): Boolean = settings.getBoolean(OnboardingCacheKeyValues.IS_FIRST_PROBLEM_ONBOARDING_SHOWN, defaultValue = false) diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/onboarding/cache/OnboardingCacheKeyValues.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/onboarding/cache/OnboardingCacheKeyValues.kt index 10c8570bc0..89e49d4ebc 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/onboarding/cache/OnboardingCacheKeyValues.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/onboarding/cache/OnboardingCacheKeyValues.kt @@ -5,7 +5,6 @@ internal object OnboardingCacheKeyValues { const val IS_FILL_BLANKS_INPUT_MODE_ONBOARDING_SHOWN = "is_fill_blanks_input_mode_onboarding_shown" const val IS_FILL_BLANKS_SELECT_MODE_ONBOARDING_SHOWN = "is_fill_blanks_select_mode_onboarding_shown" - const val IS_NOTIFICATIONS_ONBOARDING_SHOWN = "is_notifications_onboarding_shown" const val IS_FIRST_PROBLEM_ONBOARDING_SHOWN = "is_first_problem_onboarding_shown" const val IS_INTERVIEW_PREPARATION_ONBOARDING_SHOWN = "is_interview_preparation_onboarding_shown" diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/onboarding/data/repository/OnboardingRepositoryImpl.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/onboarding/data/repository/OnboardingRepositoryImpl.kt index 4d1776d1a4..9f59d6e8cb 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/onboarding/data/repository/OnboardingRepositoryImpl.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/onboarding/data/repository/OnboardingRepositoryImpl.kt @@ -28,13 +28,6 @@ internal class OnboardingRepositoryImpl( onboardingCacheDataSource.setFillBlanksSelectModeOnboardingShown(isShown) } - override fun wasNotificationOnboardingShown(): Boolean = - onboardingCacheDataSource.wasNotificationOnboardingShown() - - override fun setNotificationOnboardingWasShown(wasShown: Boolean) { - onboardingCacheDataSource.setNotificationOnboardingWasShown(wasShown) - } - override fun wasFirstProblemOnboardingShown(): Boolean = onboardingCacheDataSource.wasFirstProblemOnboardingShown() diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/onboarding/data/source/OnboardingCacheDataSource.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/onboarding/data/source/OnboardingCacheDataSource.kt index 2372068d66..f4adea9ab4 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/onboarding/data/source/OnboardingCacheDataSource.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/onboarding/data/source/OnboardingCacheDataSource.kt @@ -9,9 +9,6 @@ interface OnboardingCacheDataSource { fun isFillBlanksSelectModeOnboardingShown(): Boolean fun setFillBlanksSelectModeOnboardingShown(isShown: Boolean) - fun wasNotificationOnboardingShown(): Boolean - fun setNotificationOnboardingWasShown(wasShown: Boolean) - fun wasFirstProblemOnboardingShown(): Boolean fun setFirstProblemOnboardingWasShown(wasShown: Boolean) diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/onboarding/domain/interactor/OnboardingInteractor.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/onboarding/domain/interactor/OnboardingInteractor.kt index 05c1e43be9..00d4254490 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/onboarding/domain/interactor/OnboardingInteractor.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/onboarding/domain/interactor/OnboardingInteractor.kt @@ -22,13 +22,6 @@ class OnboardingInteractor( fun getProblemsOnboardingFlags(): ProblemsOnboardingFlags = onboardingRepository.getProblemsOnboardingFlags() - fun wasNotificationOnboardingShown(): Boolean = - onboardingRepository.wasNotificationOnboardingShown() - - fun setNotificationOnboardingWasShown(wasShown: Boolean) { - onboardingRepository.setNotificationOnboardingWasShown(wasShown) - } - fun wasFirstProblemOnboardingShown(): Boolean = onboardingRepository.wasFirstProblemOnboardingShown() diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/onboarding/domain/repository/OnboardingRepository.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/onboarding/domain/repository/OnboardingRepository.kt index ded53ca078..6feaf38fd2 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/onboarding/domain/repository/OnboardingRepository.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/onboarding/domain/repository/OnboardingRepository.kt @@ -11,9 +11,6 @@ interface OnboardingRepository { fun isFillBlanksSelectModeOnboardingShown(): Boolean fun setFillBlanksSelectModeOnboardingShown(isShown: Boolean) - fun wasNotificationOnboardingShown(): Boolean - fun setNotificationOnboardingWasShown(wasShown: Boolean) - fun wasFirstProblemOnboardingShown(): Boolean fun setFirstProblemOnboardingWasShown(wasShown: Boolean) diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/welcome_onboarding/presentation/WelcomeOnboardingActionDispatcher.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/welcome_onboarding/presentation/WelcomeOnboardingActionDispatcher.kt index f412f6ce3f..4fe055e31d 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/welcome_onboarding/presentation/WelcomeOnboardingActionDispatcher.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/welcome_onboarding/presentation/WelcomeOnboardingActionDispatcher.kt @@ -14,13 +14,6 @@ class WelcomeOnboardingActionDispatcher( ) : CoroutineActionDispatcher(config.createConfig()) { override suspend fun doSuspendableAction(action: Action) { when (action) { - InternalAction.FetchNotificationOnboardingData -> { - onNewMessage( - InternalMessage.NotificationOnboardingDataFetched( - wasNotificationOnboardingShown = onboardingInteractor.wasNotificationOnboardingShown() - ) - ) - } InternalAction.FetchFirstProblemOnboardingData -> { onNewMessage( InternalMessage.FirstProblemOnboardingDataFetched( diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/welcome_onboarding/presentation/WelcomeOnboardingFeature.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/welcome_onboarding/presentation/WelcomeOnboardingFeature.kt index 7a90b0cbb8..4b990b6e65 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/welcome_onboarding/presentation/WelcomeOnboardingFeature.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/welcome_onboarding/presentation/WelcomeOnboardingFeature.kt @@ -6,7 +6,6 @@ import org.hyperskill.app.step.domain.model.StepRoute import org.hyperskill.app.welcome_onboarding.presentation.WelcomeOnboardingFeature.Action object WelcomeOnboardingFeature { - @Serializable data class State(val profile: Profile? = null) @@ -22,10 +21,6 @@ object WelcomeOnboardingFeature { val isNotificationPermissionGranted: Boolean ) : InternalMessage - data class NotificationOnboardingDataFetched( - val wasNotificationOnboardingShown: Boolean - ) : InternalMessage - data class FirstProblemOnboardingDataFetched( val wasFirstProblemOnboardingShown: Boolean ) : InternalMessage @@ -53,7 +48,6 @@ object WelcomeOnboardingFeature { } internal sealed interface InternalAction : Action { - object FetchNotificationOnboardingData : InternalAction object FetchFirstProblemOnboardingData : InternalAction } } diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/welcome_onboarding/presentation/WelcomeOnboardingReducer.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/welcome_onboarding/presentation/WelcomeOnboardingReducer.kt index 61a0aef50e..913e63fc9a 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/welcome_onboarding/presentation/WelcomeOnboardingReducer.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/welcome_onboarding/presentation/WelcomeOnboardingReducer.kt @@ -14,14 +14,11 @@ import ru.nobird.app.presentation.redux.reducer.StateReducer private typealias ReducerResult = Pair> class WelcomeOnboardingReducer : StateReducer { - override fun reduce(state: State, message: Message): ReducerResult = when (message) { is InternalMessage.OnboardingFlowRequested -> handleOnboardingFlowRequested(message) - is InternalMessage.NotificationOnboardingDataFetched -> - handleNotificationOnboardingDataFetched(state, message) Message.NotificationOnboardingCompleted -> handleNotificationOnboardingCompleted(state) @@ -36,29 +33,11 @@ class WelcomeOnboardingReducer : StateReducer { ): ReducerResult = if (!message.isNotificationPermissionGranted) { State(message.profile) to - setOf(InternalAction.FetchNotificationOnboardingData) + setOf(ViewAction.NavigateTo.NotificationOnboardingScreen) } else { onNotificationOnboardingCompleted(message.profile) } - private fun handleNotificationOnboardingDataFetched( - state: State, - message: InternalMessage.NotificationOnboardingDataFetched - ): ReducerResult = - if (state.profile != null) { - if (!message.wasNotificationOnboardingShown) { - state to setOf(ViewAction.NavigateTo.NotificationOnboardingScreen) - } else { - onNotificationOnboardingCompleted(state.profile) - } - } else { - state to setOf( - Action.OnboardingFlowFinished( - OnboardingFlowFinishReason.NotificationOnboardingFinished(state.profile) - ) - ) - } - private fun handleNotificationOnboardingCompleted( state: State ): ReducerResult = From e900d62fe8b2365f5de15d35848cde9d8b5f36d2 Mon Sep 17 00:00:00 2001 From: github-actions Date: Fri, 16 Feb 2024 05:05:43 +0000 Subject: [PATCH 064/104] Bump build number --- gradle/app.versions.toml | 2 +- .../NotificationServiceExtension/Info.plist | 2 +- .../iosHyperskillApp.xcodeproj/project.pbxproj | 16 ++++++++-------- iosHyperskillApp/iosHyperskillApp/Info.plist | 2 +- .../iosHyperskillAppTests/Info.plist | 2 +- .../iosHyperskillAppUITests/Info.plist | 2 +- 6 files changed, 13 insertions(+), 13 deletions(-) diff --git a/gradle/app.versions.toml b/gradle/app.versions.toml index f9d2b483aa..459b5bad43 100644 --- a/gradle/app.versions.toml +++ b/gradle/app.versions.toml @@ -3,4 +3,4 @@ minSdk = '24' targetSdk = '33' compileSdk = '33' versionName = '1.50' -versionCode = '325' \ No newline at end of file +versionCode = '326' \ No newline at end of file diff --git a/iosHyperskillApp/NotificationServiceExtension/Info.plist b/iosHyperskillApp/NotificationServiceExtension/Info.plist index 2601c67758..f285bb9896 100644 --- a/iosHyperskillApp/NotificationServiceExtension/Info.plist +++ b/iosHyperskillApp/NotificationServiceExtension/Info.plist @@ -9,7 +9,7 @@ CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleVersion - 331 + 332 CFBundleShortVersionString 1.50 CFBundlePackageType diff --git a/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj b/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj index d8152a6fdb..fe81409fc7 100644 --- a/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj +++ b/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj @@ -5037,7 +5037,7 @@ buildSettings = { CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 331; + CURRENT_PROJECT_VERSION = 332; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = 3DWS674B2M; GENERATE_INFOPLIST_FILE = NO; @@ -5058,7 +5058,7 @@ buildSettings = { CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 331; + CURRENT_PROJECT_VERSION = 332; DEVELOPMENT_TEAM = 3DWS674B2M; GENERATE_INFOPLIST_FILE = NO; INFOPLIST_FILE = iosHyperskillAppUITests/Info.plist; @@ -5079,7 +5079,7 @@ BUNDLE_LOADER = "$(TEST_HOST)"; CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 331; + CURRENT_PROJECT_VERSION = 332; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = 3DWS674B2M; INFOPLIST_FILE = iosHyperskillAppTests/Info.plist; @@ -5100,7 +5100,7 @@ BUNDLE_LOADER = "$(TEST_HOST)"; CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 331; + CURRENT_PROJECT_VERSION = 332; DEVELOPMENT_TEAM = 3DWS674B2M; INFOPLIST_FILE = iosHyperskillAppTests/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; @@ -5121,7 +5121,7 @@ CODE_SIGN_ENTITLEMENTS = NotificationServiceExtension/NotificationServiceExtension.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 331; + CURRENT_PROJECT_VERSION = 332; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = 3DWS674B2M; INFOPLIST_FILE = NotificationServiceExtension/Info.plist; @@ -5149,7 +5149,7 @@ CODE_SIGN_ENTITLEMENTS = NotificationServiceExtension/NotificationServiceExtension.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 331; + CURRENT_PROJECT_VERSION = 332; DEVELOPMENT_TEAM = 3DWS674B2M; INFOPLIST_FILE = NotificationServiceExtension/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; @@ -5294,7 +5294,7 @@ CODE_SIGN_ENTITLEMENTS = iosHyperskillApp/iosHyperskillApp.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 331; + CURRENT_PROJECT_VERSION = 332; DEVELOPMENT_ASSET_PATHS = "\"iosHyperskillApp/Preview Content\""; DEVELOPMENT_TEAM = 3DWS674B2M; ENABLE_PREVIEWS = YES; @@ -5330,7 +5330,7 @@ CODE_SIGN_ENTITLEMENTS = iosHyperskillApp/iosHyperskillApp.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 331; + CURRENT_PROJECT_VERSION = 332; DEVELOPMENT_ASSET_PATHS = "\"iosHyperskillApp/Preview Content\""; DEVELOPMENT_TEAM = 3DWS674B2M; ENABLE_PREVIEWS = YES; diff --git a/iosHyperskillApp/iosHyperskillApp/Info.plist b/iosHyperskillApp/iosHyperskillApp/Info.plist index 7754ee0448..86111fb1ba 100644 --- a/iosHyperskillApp/iosHyperskillApp/Info.plist +++ b/iosHyperskillApp/iosHyperskillApp/Info.plist @@ -36,7 +36,7 @@ CFBundleVersion - 331 + 332 FirebaseAppDelegateProxyEnabled FirebaseMessagingAutoInitEnabled diff --git a/iosHyperskillApp/iosHyperskillAppTests/Info.plist b/iosHyperskillApp/iosHyperskillAppTests/Info.plist index 8ef5ec80da..13adb013eb 100644 --- a/iosHyperskillApp/iosHyperskillAppTests/Info.plist +++ b/iosHyperskillApp/iosHyperskillAppTests/Info.plist @@ -15,6 +15,6 @@ CFBundleShortVersionString 1.50 CFBundleVersion - 331 + 332 diff --git a/iosHyperskillApp/iosHyperskillAppUITests/Info.plist b/iosHyperskillApp/iosHyperskillAppUITests/Info.plist index 30f6a147b2..b2b1ef5f83 100644 --- a/iosHyperskillApp/iosHyperskillAppUITests/Info.plist +++ b/iosHyperskillApp/iosHyperskillAppUITests/Info.plist @@ -15,6 +15,6 @@ CFBundleShortVersionString 1.50 CFBundleVersion - 331 + 332 From 2896022e6a341430f0a8da4378258bcfb19594d1 Mon Sep 17 00:00:00 2001 From: Ivan Magda Date: Fri, 16 Feb 2024 14:43:13 +0700 Subject: [PATCH 065/104] Shared: Use 'None' value as default one for route in analytic events (#906) ^ALTAPPS-1086 --- ...yStudyReminderLocalNotificationDelegate.kt | 2 -- .../NotificationsRegistrationService.swift | 6 ++-- .../hyperskill/HyperskillAnalyticRoute.kt | 36 ++++++++----------- ...dLearningActionHyperskillAnalyticEvent.kt} | 3 +- ...nboardingViewedHyperskillAnalyticEvent.kt} | 7 ++-- .../FirstProblemOnboardingReducer.kt | 8 ++--- .../app/home/presentation/HomeReducer.kt | 6 ++-- ...onWidgetClickedHyperskillAnalyticEvent.kt} | 2 +- ...yContentLoadingHyperskillAnalyticEvent.kt} | 2 +- ...ionWidgetViewedHyperskillAnalyticEvent.kt} | 2 +- .../InterviewPreparationWidgetReducer.kt | 12 +++---- ...oardingGoToProblemClickedAnalyticEvent.kt} | 2 +- ...eparationOnboardingViewedAnalyticEvent.kt} | 2 +- ...rviewPreparationOnboardingComponentImpl.kt | 2 +- ...viewPreparationOnboardingFeatureBuilder.kt | 4 +-- .../InterviewPreparationOnboardingReducer.kt | 8 ++--- .../NotificationClickHandlingFeature.kt | 2 +- .../NotificationClickHandlingReducer.kt | 10 +++--- ...yReminderClickedHyperskillAnalyticEvent.kt | 4 +-- ...udyReminderShownHyperskillAnalyticEvent.kt | 5 ++- ...stemNoticeHiddenHyperskillAnalyticEvent.kt | 4 +-- ...ystemNoticeShownHyperskillAnalyticEvent.kt | 4 +-- ...ificationClickedHyperskillAnalyticEvent.kt | 4 +-- ...otificationShownHyperskillAnalyticEvent.kt | 4 +-- ...adgeModalHiddenHyperskillAnalyticEvent.kt} | 4 +-- ...dBadgeModalShownHyperskillAnalyticEvent.kt | 3 +- ...adgeModalHiddenHyperskillAnalyticEvent.kt} | 4 +-- ...eBadgeModalShownHyperskillAnalyticEvent.kt | 2 +- ...lickedBadgeCardHyperskillAnalyticEvent.kt} | 5 +-- ...isibilityButtonHyperskillAnalyticEvent.kt} | 5 +-- .../profile/presentation/ProfileReducer.kt | 12 +++---- .../presentation/ProfileSettingsReducer.kt | 6 ++-- ...yContentLoadingHyperskillAnalyticEvent.kt} | 2 +- ...lectThisProjectHyperskillAnalyticEvent.kt} | 2 +- .../ProjectSelectionDetailsReducer.kt | 8 ++--- ...tClickedProjectHyperskillAnalyticEvent.kt} | 2 +- ...yContentLoadingHyperskillAnalyticEvent.kt} | 2 +- .../ProjectSelectionListReducer.kt | 8 ++--- ...lClickedNoThanksHyperskillAnalyticEvent.kt | 5 +-- ...kedRestoreStreakHyperskillAnalyticEvent.kt | 5 +-- ...overyModalHiddenHyperskillAnalyticEvent.kt | 5 +-- ...coveryModalShownHyperskillAnalyticEvent.kt | 5 +-- ...yContentLoadingHyperskillAnalyticEvent.kt} | 2 +- .../TrackSelectionDetailsReducer.kt | 10 +++--- ...yContentLoadingHyperskillAnalyticEvent.kt} | 2 +- ...ectionListViewedHyperskillAnalyticEvent.kt | 7 ++-- .../presentation/TrackSelectionListReducer.kt | 8 ++--- .../projects_selection/ProjectsListTest.kt | 4 +-- 48 files changed, 132 insertions(+), 127 deletions(-) rename shared/src/commonMain/kotlin/org/hyperskill/app/first_problem_onboarding/domain/analytic/{FirstProblemOnboardingClickedLearningActionHyperskillAnalyticsEvent.kt => FirstProblemOnboardingClickedLearningActionHyperskillAnalyticEvent.kt} (98%) rename shared/src/commonMain/kotlin/org/hyperskill/app/first_problem_onboarding/domain/analytic/{FirstProblemOnboardingViewedHyperskillAnalyticsEvent.kt => FirstProblemOnboardingViewedHyperskillAnalyticEvent.kt} (74%) rename shared/src/commonMain/kotlin/org/hyperskill/app/interview_preparation/domain/analytic/{InterviewPreparationWidgetClickedHyperskillAnalyticsEvent.kt => InterviewPreparationWidgetClickedHyperskillAnalyticEvent.kt} (89%) rename shared/src/commonMain/kotlin/org/hyperskill/app/interview_preparation/domain/analytic/{InterviewPreparationWidgetClickedRetryContentLoadingHyperskillAnalyticsEvent.kt => InterviewPreparationWidgetClickedRetryContentLoadingHyperskillAnalyticEvent.kt} (95%) rename shared/src/commonMain/kotlin/org/hyperskill/app/interview_preparation/domain/analytic/{InterviewPreparationWidgetViewedHyperskillAnalyticsEvent.kt => InterviewPreparationWidgetViewedHyperskillAnalyticEvent.kt} (87%) rename shared/src/commonMain/kotlin/org/hyperskill/app/interview_preparation_onboarding/domain/analytic/{InterviewPreparationOnboardingGoToProblemClickedAnalyticsEvent.kt => InterviewPreparationOnboardingGoToProblemClickedAnalyticEvent.kt} (91%) rename shared/src/commonMain/kotlin/org/hyperskill/app/interview_preparation_onboarding/domain/analytic/{InterviewPreparationOnboardingViewedAnalyticsEvent.kt => InterviewPreparationOnboardingViewedAnalyticEvent.kt} (88%) rename shared/src/commonMain/kotlin/org/hyperskill/app/profile/domain/analytic/badges/{EarnedBadgeModalHiddenHyperskillAnalyticsEvent.kt => EarnedBadgeModalHiddenHyperskillAnalyticEvent.kt} (94%) rename shared/src/commonMain/kotlin/org/hyperskill/app/profile/domain/analytic/badges/{ProfileBadgeModalHiddenHyperskillAnalyticsEvent.kt => ProfileBadgeModalHiddenHyperskillAnalyticEvent.kt} (93%) rename shared/src/commonMain/kotlin/org/hyperskill/app/profile/domain/analytic/badges/{ProfileClickedBadgeCardHyperskillAnalyticsEvent.kt => ProfileClickedBadgeCardHyperskillAnalyticEvent.kt} (92%) rename shared/src/commonMain/kotlin/org/hyperskill/app/profile/domain/analytic/badges/{ProfileClickedBadgesVisibilityButtonHyperskillAnalyticsEvent.kt => ProfileClickedBadgesVisibilityButtonHyperskillAnalyticEvent.kt} (94%) rename shared/src/commonMain/kotlin/org/hyperskill/app/project_selection/details/domain/analytic/{ProjectSelectionDetailsClickedRetryContentLoadingHyperskillAnalyticsEvent.kt => ProjectSelectionDetailsClickedRetryContentLoadingHyperskillAnalyticEvent.kt} (98%) rename shared/src/commonMain/kotlin/org/hyperskill/app/project_selection/details/domain/analytic/{ProjectSelectionDetailsClickedSelectThisProjectHyperskillAnalyticsEvent.kt => ProjectSelectionDetailsClickedSelectThisProjectHyperskillAnalyticEvent.kt} (98%) rename shared/src/commonMain/kotlin/org/hyperskill/app/project_selection/list/domain/analytic/{ProjectSelectionListClickedProjectHyperskillAnalyticsEvent.kt => ProjectSelectionListClickedProjectHyperskillAnalyticEvent.kt} (95%) rename shared/src/commonMain/kotlin/org/hyperskill/app/project_selection/list/domain/analytic/{ProjectSelectionListClickedRetryContentLoadingHyperskillAnalyticsEvent.kt => ProjectSelectionListClickedRetryContentLoadingHyperskillAnalyticEvent.kt} (98%) rename shared/src/commonMain/kotlin/org/hyperskill/app/track_selection/details/domain/analytic/{TrackSelectionDetailsClickedRetryContentLoadingHyperskillAnalyticsEvent.kt => TrackSelectionDetailsClickedRetryContentLoadingHyperskillAnalyticEvent.kt} (98%) rename shared/src/commonMain/kotlin/org/hyperskill/app/track_selection/list/domain/analytic/{TrackSelectionListClickedRetryContentLoadingHyperskillAnalyticsEvent.kt => TrackSelectionListClickedRetryContentLoadingHyperskillAnalyticEvent.kt} (89%) diff --git a/androidHyperskillApp/src/main/java/org/hyperskill/app/android/notification/local/DailyStudyReminderLocalNotificationDelegate.kt b/androidHyperskillApp/src/main/java/org/hyperskill/app/android/notification/local/DailyStudyReminderLocalNotificationDelegate.kt index 471486b6a4..6248936c1b 100644 --- a/androidHyperskillApp/src/main/java/org/hyperskill/app/android/notification/local/DailyStudyReminderLocalNotificationDelegate.kt +++ b/androidHyperskillApp/src/main/java/org/hyperskill/app/android/notification/local/DailyStudyReminderLocalNotificationDelegate.kt @@ -6,7 +6,6 @@ import java.text.SimpleDateFormat import java.util.Calendar import kotlinx.coroutines.runBlocking import org.hyperskill.app.analytic.domain.interactor.AnalyticInteractor -import org.hyperskill.app.analytic.domain.model.hyperskill.HyperskillAnalyticRoute import org.hyperskill.app.android.core.extensions.DateTimeHelper import org.hyperskill.app.android.notification.NotificationBuilder import org.hyperskill.app.android.notification.NotificationIntentBuilder @@ -111,7 +110,6 @@ class DailyStudyReminderLocalNotificationDelegate( private fun logShownNotificationEvent(notificationId: Int) { val event = NotificationDailyStudyReminderShownHyperskillAnalyticEvent( - route = HyperskillAnalyticRoute.Home(), notificationId = notificationId, plannedAtISO8601 = SimpleDateFormat(DateTimeHelper.ISO_PATTERN).format(Calendar.getInstance().time) ) diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Frameworks/Notifications/Registration/NotificationsRegistrationService.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Frameworks/Notifications/Registration/NotificationsRegistrationService.swift index d829622c09..1b3595426d 100644 --- a/iosHyperskillApp/iosHyperskillApp/Sources/Frameworks/Notifications/Registration/NotificationsRegistrationService.swift +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Frameworks/Notifications/Registration/NotificationsRegistrationService.swift @@ -340,7 +340,9 @@ extension NotificationsRegistrationService { extension NotificationsRegistrationService { private func logSystemNoticeShownEvent() { DispatchQueue.main.async { - let event = NotificationSystemNoticeShownHyperskillAnalyticEvent(route: HyperskillAnalyticRoute.Home()) + let event = NotificationSystemNoticeShownHyperskillAnalyticEvent( + route: HyperskillAnalyticRoute.None.shared + ) self.analyticInteractor.logEvent(event: event) } } @@ -348,7 +350,7 @@ extension NotificationsRegistrationService { private func logSystemNoticeHiddenEvent(isAllowed: Bool) { DispatchQueue.main.async { let event = NotificationSystemNoticeHiddenHyperskillAnalyticEvent( - route: HyperskillAnalyticRoute.Home(), + route: HyperskillAnalyticRoute.None.shared, isAllowed: isAllowed ) self.analyticInteractor.logEvent(event: event) diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/analytic/domain/model/hyperskill/HyperskillAnalyticRoute.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/analytic/domain/model/hyperskill/HyperskillAnalyticRoute.kt index 1512aac855..9222fefea8 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/analytic/domain/model/hyperskill/HyperskillAnalyticRoute.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/analytic/domain/model/hyperskill/HyperskillAnalyticRoute.kt @@ -31,10 +31,6 @@ sealed class HyperskillAnalyticRoute { } } - class Register : HyperskillAnalyticRoute() { - override val path: String = "/register" - } - sealed class Learn : HyperskillAnalyticRoute() { override val path: String = "/learn" @@ -96,10 +92,6 @@ sealed class HyperskillAnalyticRoute { } } - class Track : HyperskillAnalyticRoute() { - override val path: String = "/track" - } - open class Profile : HyperskillAnalyticRoute() { override val path: String = "/profile" @@ -114,13 +106,11 @@ sealed class HyperskillAnalyticRoute { } class StudyPlan : HyperskillAnalyticRoute() { - override val path: String = - "/study-plan" + override val path: String = "/study-plan" } class Leaderboard : HyperskillAnalyticRoute() { - override val path: String = - "/leaderboard" + override val path: String = "/leaderboard" } open class Tracks : HyperskillAnalyticRoute() { @@ -138,27 +128,31 @@ sealed class HyperskillAnalyticRoute { } class Progress : HyperskillAnalyticRoute() { - override val path: String = - "/progress" + override val path: String = "/progress" } class Search : HyperskillAnalyticRoute() { - override val path: String = - "/search" + override val path: String = "/search" } /** - * Represents a special route that is used to track the first time the app is launched (ALTAPPS-1139). + * Represents a special route when we do not know where the events is occurred (ALTAPPS-1086). */ - internal class AppLaunchFirstTime : HyperskillAnalyticRoute() { - override val path: String = "app-launch-first-time" + object None : HyperskillAnalyticRoute() { + override val path: String = "None" } /** * Springboard, or Home Screen is the standard application that manages the home screen of Apple devices. */ class IosSpringBoard : HyperskillAnalyticRoute() { - override val path: String = - "SpringBoard" + override val path: String = "SpringBoard" + } + + /** + * 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" } } \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/first_problem_onboarding/domain/analytic/FirstProblemOnboardingClickedLearningActionHyperskillAnalyticsEvent.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/first_problem_onboarding/domain/analytic/FirstProblemOnboardingClickedLearningActionHyperskillAnalyticEvent.kt similarity index 98% rename from shared/src/commonMain/kotlin/org/hyperskill/app/first_problem_onboarding/domain/analytic/FirstProblemOnboardingClickedLearningActionHyperskillAnalyticsEvent.kt rename to shared/src/commonMain/kotlin/org/hyperskill/app/first_problem_onboarding/domain/analytic/FirstProblemOnboardingClickedLearningActionHyperskillAnalyticEvent.kt index 6c3a010d38..e7c00d9ea2 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/first_problem_onboarding/domain/analytic/FirstProblemOnboardingClickedLearningActionHyperskillAnalyticsEvent.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/first_problem_onboarding/domain/analytic/FirstProblemOnboardingClickedLearningActionHyperskillAnalyticEvent.kt @@ -18,9 +18,10 @@ import org.hyperskill.app.analytic.domain.model.hyperskill.HyperskillAnalyticTar * "target": "start_learning/keep_learning" * } * ``` + * * @see HyperskillAnalyticEvent */ -class FirstProblemOnboardingClickedLearningActionHyperskillAnalyticsEvent( +class FirstProblemOnboardingClickedLearningActionHyperskillAnalyticEvent( target: HyperskillAnalyticTarget ) : HyperskillAnalyticEvent( route = HyperskillAnalyticRoute.Onboarding.FirstProblem, diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/first_problem_onboarding/domain/analytic/FirstProblemOnboardingViewedHyperskillAnalyticsEvent.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/first_problem_onboarding/domain/analytic/FirstProblemOnboardingViewedHyperskillAnalyticEvent.kt similarity index 74% rename from shared/src/commonMain/kotlin/org/hyperskill/app/first_problem_onboarding/domain/analytic/FirstProblemOnboardingViewedHyperskillAnalyticsEvent.kt rename to shared/src/commonMain/kotlin/org/hyperskill/app/first_problem_onboarding/domain/analytic/FirstProblemOnboardingViewedHyperskillAnalyticEvent.kt index c4c1b184a0..51f9ca30d3 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/first_problem_onboarding/domain/analytic/FirstProblemOnboardingViewedHyperskillAnalyticsEvent.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/first_problem_onboarding/domain/analytic/FirstProblemOnboardingViewedHyperskillAnalyticEvent.kt @@ -14,7 +14,10 @@ import org.hyperskill.app.analytic.domain.model.hyperskill.HyperskillAnalyticRou * "action": "view" * } * ``` + * * @see HyperskillAnalyticEvent */ -object FirstProblemOnboardingViewedHyperskillAnalyticsEvent : - HyperskillAnalyticEvent(HyperskillAnalyticRoute.Onboarding.FirstProblem, HyperskillAnalyticAction.VIEW) \ No newline at end of file +object FirstProblemOnboardingViewedHyperskillAnalyticEvent : HyperskillAnalyticEvent( + HyperskillAnalyticRoute.Onboarding.FirstProblem, + HyperskillAnalyticAction.VIEW +) \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/first_problem_onboarding/presentation/FirstProblemOnboardingReducer.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/first_problem_onboarding/presentation/FirstProblemOnboardingReducer.kt index 565d93e6f8..084214c9b9 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/first_problem_onboarding/presentation/FirstProblemOnboardingReducer.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/first_problem_onboarding/presentation/FirstProblemOnboardingReducer.kt @@ -1,8 +1,8 @@ package org.hyperskill.app.first_problem_onboarding.presentation import org.hyperskill.app.analytic.domain.model.hyperskill.HyperskillAnalyticTarget -import org.hyperskill.app.first_problem_onboarding.domain.analytic.FirstProblemOnboardingClickedLearningActionHyperskillAnalyticsEvent -import org.hyperskill.app.first_problem_onboarding.domain.analytic.FirstProblemOnboardingViewedHyperskillAnalyticsEvent +import org.hyperskill.app.first_problem_onboarding.domain.analytic.FirstProblemOnboardingClickedLearningActionHyperskillAnalyticEvent +import org.hyperskill.app.first_problem_onboarding.domain.analytic.FirstProblemOnboardingViewedHyperskillAnalyticEvent import org.hyperskill.app.first_problem_onboarding.presentation.FirstProblemOnboardingFeature.Action import org.hyperskill.app.first_problem_onboarding.presentation.FirstProblemOnboardingFeature.InternalAction import org.hyperskill.app.first_problem_onboarding.presentation.FirstProblemOnboardingFeature.Message @@ -126,7 +126,7 @@ internal class FirstProblemOnboardingReducer : StateReducer { if (state.homeState is HomeState.Content) { val (newState, newActions) = initialize(state, forceUpdate = true) - val analyticsEvent = when (state.homeState.problemOfDayState) { + val analyticEvent = when (state.homeState.problemOfDayState) { HomeFeature.ProblemOfDayState.Empty -> null is HomeFeature.ProblemOfDayState.NeedToSolve -> { HomeClickedProblemOfDayCardReloadHyperskillAnalyticEvent( @@ -170,8 +170,8 @@ internal class HomeReducer( ) } } - val logEventAction = if (analyticsEvent != null) { - setOf(InternalAction.LogAnalyticEvent(analyticsEvent)) + val logEventAction = if (analyticEvent != null) { + setOf(InternalAction.LogAnalyticEvent(analyticEvent)) } else { emptySet() } diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/interview_preparation/domain/analytic/InterviewPreparationWidgetClickedHyperskillAnalyticsEvent.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/interview_preparation/domain/analytic/InterviewPreparationWidgetClickedHyperskillAnalyticEvent.kt similarity index 89% rename from shared/src/commonMain/kotlin/org/hyperskill/app/interview_preparation/domain/analytic/InterviewPreparationWidgetClickedHyperskillAnalyticsEvent.kt rename to shared/src/commonMain/kotlin/org/hyperskill/app/interview_preparation/domain/analytic/InterviewPreparationWidgetClickedHyperskillAnalyticEvent.kt index ca3ec1f292..37f1be001f 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/interview_preparation/domain/analytic/InterviewPreparationWidgetClickedHyperskillAnalyticsEvent.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/interview_preparation/domain/analytic/InterviewPreparationWidgetClickedHyperskillAnalyticEvent.kt @@ -19,7 +19,7 @@ import org.hyperskill.app.analytic.domain.model.hyperskill.HyperskillAnalyticRou * * @see HyperskillAnalyticEvent */ -object InterviewPreparationWidgetClickedHyperskillAnalyticsEvent : HyperskillAnalyticEvent( +object InterviewPreparationWidgetClickedHyperskillAnalyticEvent : HyperskillAnalyticEvent( HyperskillAnalyticRoute.Home(), HyperskillAnalyticAction.CLICK, HyperskillAnalyticPart.INTERVIEW_PREPARATION_WIDGET diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/interview_preparation/domain/analytic/InterviewPreparationWidgetClickedRetryContentLoadingHyperskillAnalyticsEvent.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/interview_preparation/domain/analytic/InterviewPreparationWidgetClickedRetryContentLoadingHyperskillAnalyticEvent.kt similarity index 95% rename from shared/src/commonMain/kotlin/org/hyperskill/app/interview_preparation/domain/analytic/InterviewPreparationWidgetClickedRetryContentLoadingHyperskillAnalyticsEvent.kt rename to shared/src/commonMain/kotlin/org/hyperskill/app/interview_preparation/domain/analytic/InterviewPreparationWidgetClickedRetryContentLoadingHyperskillAnalyticEvent.kt index a8a550ba14..0189cb779d 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/interview_preparation/domain/analytic/InterviewPreparationWidgetClickedRetryContentLoadingHyperskillAnalyticsEvent.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/interview_preparation/domain/analytic/InterviewPreparationWidgetClickedRetryContentLoadingHyperskillAnalyticEvent.kt @@ -21,7 +21,7 @@ import org.hyperskill.app.analytic.domain.model.hyperskill.HyperskillAnalyticTar * * @see HyperskillAnalyticEvent */ -object InterviewPreparationWidgetClickedRetryContentLoadingHyperskillAnalyticsEvent : HyperskillAnalyticEvent( +object InterviewPreparationWidgetClickedRetryContentLoadingHyperskillAnalyticEvent : HyperskillAnalyticEvent( HyperskillAnalyticRoute.Home(), HyperskillAnalyticAction.CLICK, HyperskillAnalyticPart.INTERVIEW_PREPARATION_WIDGET, diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/interview_preparation/domain/analytic/InterviewPreparationWidgetViewedHyperskillAnalyticsEvent.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/interview_preparation/domain/analytic/InterviewPreparationWidgetViewedHyperskillAnalyticEvent.kt similarity index 87% rename from shared/src/commonMain/kotlin/org/hyperskill/app/interview_preparation/domain/analytic/InterviewPreparationWidgetViewedHyperskillAnalyticsEvent.kt rename to shared/src/commonMain/kotlin/org/hyperskill/app/interview_preparation/domain/analytic/InterviewPreparationWidgetViewedHyperskillAnalyticEvent.kt index ac8b1a29f7..f875014a7c 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/interview_preparation/domain/analytic/InterviewPreparationWidgetViewedHyperskillAnalyticsEvent.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/interview_preparation/domain/analytic/InterviewPreparationWidgetViewedHyperskillAnalyticEvent.kt @@ -17,7 +17,7 @@ import org.hyperskill.app.analytic.domain.model.hyperskill.HyperskillAnalyticRou * * @see HyperskillAnalyticEvent */ -object InterviewPreparationWidgetViewedHyperskillAnalyticsEvent : HyperskillAnalyticEvent( +object InterviewPreparationWidgetViewedHyperskillAnalyticEvent : HyperskillAnalyticEvent( HyperskillAnalyticRoute.Home.InterviewPreparationWidget(), HyperskillAnalyticAction.VIEW ) \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/interview_preparation/presentation/InterviewPreparationWidgetReducer.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/interview_preparation/presentation/InterviewPreparationWidgetReducer.kt index 7c126d0d5f..6ecece2579 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/interview_preparation/presentation/InterviewPreparationWidgetReducer.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/interview_preparation/presentation/InterviewPreparationWidgetReducer.kt @@ -1,8 +1,8 @@ package org.hyperskill.app.interview_preparation.presentation -import org.hyperskill.app.interview_preparation.domain.analytic.InterviewPreparationWidgetClickedHyperskillAnalyticsEvent -import org.hyperskill.app.interview_preparation.domain.analytic.InterviewPreparationWidgetClickedRetryContentLoadingHyperskillAnalyticsEvent -import org.hyperskill.app.interview_preparation.domain.analytic.InterviewPreparationWidgetViewedHyperskillAnalyticsEvent +import org.hyperskill.app.interview_preparation.domain.analytic.InterviewPreparationWidgetClickedHyperskillAnalyticEvent +import org.hyperskill.app.interview_preparation.domain.analytic.InterviewPreparationWidgetClickedRetryContentLoadingHyperskillAnalyticEvent +import org.hyperskill.app.interview_preparation.domain.analytic.InterviewPreparationWidgetViewedHyperskillAnalyticEvent import org.hyperskill.app.interview_preparation.presentation.InterviewPreparationWidgetFeature.Action import org.hyperskill.app.interview_preparation.presentation.InterviewPreparationWidgetFeature.InternalAction import org.hyperskill.app.interview_preparation.presentation.InterviewPreparationWidgetFeature.InternalMessage @@ -106,7 +106,7 @@ class InterviewPreparationWidgetReducer : StateReducer { State.Loading(isLoadingSilently = false) to setOf( InternalAction.FetchInterviewSteps(true), InternalAction.LogAnalyticEvent( - InterviewPreparationWidgetClickedRetryContentLoadingHyperskillAnalyticsEvent + InterviewPreparationWidgetClickedRetryContentLoadingHyperskillAnalyticEvent ) ) } else { @@ -119,7 +119,7 @@ class InterviewPreparationWidgetReducer : StateReducer { if (state is State.Content) { state to setOf( InternalAction.LogAnalyticEvent( - InterviewPreparationWidgetClickedHyperskillAnalyticsEvent + InterviewPreparationWidgetClickedHyperskillAnalyticEvent ), InternalAction.FetchNextInterviewStep ) @@ -147,7 +147,7 @@ class InterviewPreparationWidgetReducer : StateReducer { if (state !is State.Idle) { state to setOf( InternalAction.LogAnalyticEvent( - InterviewPreparationWidgetViewedHyperskillAnalyticsEvent + InterviewPreparationWidgetViewedHyperskillAnalyticEvent ) ) } else { diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/interview_preparation_onboarding/domain/analytic/InterviewPreparationOnboardingGoToProblemClickedAnalyticsEvent.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/interview_preparation_onboarding/domain/analytic/InterviewPreparationOnboardingGoToProblemClickedAnalyticEvent.kt similarity index 91% rename from shared/src/commonMain/kotlin/org/hyperskill/app/interview_preparation_onboarding/domain/analytic/InterviewPreparationOnboardingGoToProblemClickedAnalyticsEvent.kt rename to shared/src/commonMain/kotlin/org/hyperskill/app/interview_preparation_onboarding/domain/analytic/InterviewPreparationOnboardingGoToProblemClickedAnalyticEvent.kt index 390e77b03b..a35d994cd7 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/interview_preparation_onboarding/domain/analytic/InterviewPreparationOnboardingGoToProblemClickedAnalyticsEvent.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/interview_preparation_onboarding/domain/analytic/InterviewPreparationOnboardingGoToProblemClickedAnalyticEvent.kt @@ -21,7 +21,7 @@ import org.hyperskill.app.analytic.domain.model.hyperskill.HyperskillAnalyticTar * * @see HyperskillAnalyticEvent */ -object InterviewPreparationOnboardingGoToProblemClickedAnalyticsEvent : HyperskillAnalyticEvent( +object InterviewPreparationOnboardingGoToProblemClickedAnalyticEvent : HyperskillAnalyticEvent( route = HyperskillAnalyticRoute.Onboarding.InterviewPreparation, action = HyperskillAnalyticAction.CLICK, part = HyperskillAnalyticPart.MAIN, diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/interview_preparation_onboarding/domain/analytic/InterviewPreparationOnboardingViewedAnalyticsEvent.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/interview_preparation_onboarding/domain/analytic/InterviewPreparationOnboardingViewedAnalyticEvent.kt similarity index 88% rename from shared/src/commonMain/kotlin/org/hyperskill/app/interview_preparation_onboarding/domain/analytic/InterviewPreparationOnboardingViewedAnalyticsEvent.kt rename to shared/src/commonMain/kotlin/org/hyperskill/app/interview_preparation_onboarding/domain/analytic/InterviewPreparationOnboardingViewedAnalyticEvent.kt index 0d83dcf26a..653740522d 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/interview_preparation_onboarding/domain/analytic/InterviewPreparationOnboardingViewedAnalyticsEvent.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/interview_preparation_onboarding/domain/analytic/InterviewPreparationOnboardingViewedAnalyticEvent.kt @@ -17,7 +17,7 @@ import org.hyperskill.app.analytic.domain.model.hyperskill.HyperskillAnalyticRou * * @see HyperskillAnalyticEvent */ -object InterviewPreparationOnboardingViewedAnalyticsEvent : HyperskillAnalyticEvent( +object InterviewPreparationOnboardingViewedAnalyticEvent : HyperskillAnalyticEvent( HyperskillAnalyticRoute.Onboarding.InterviewPreparation, HyperskillAnalyticAction.VIEW ) \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/interview_preparation_onboarding/injection/InterviewPreparationOnboardingComponentImpl.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/interview_preparation_onboarding/injection/InterviewPreparationOnboardingComponentImpl.kt index 7cf84ee46d..635be33095 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/interview_preparation_onboarding/injection/InterviewPreparationOnboardingComponentImpl.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/interview_preparation_onboarding/injection/InterviewPreparationOnboardingComponentImpl.kt @@ -13,7 +13,7 @@ internal class InterviewPreparationOnboardingComponentImpl( override fun interviewPreparationOnboardingFeature(stepRoute: StepRoute): Feature = InterviewPreparationOnboardingFeatureBuilder.build( stepRoute = stepRoute, - analyticsInteractor = appGraph.analyticComponent.analyticInteractor, + analyticInteractor = appGraph.analyticComponent.analyticInteractor, onboardingInteractor = appGraph.buildOnboardingDataComponent().onboardingInteractor, logger = appGraph.loggerComponent.logger, buildVariant = appGraph.commonComponent.buildKonfig.buildVariant diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/interview_preparation_onboarding/injection/InterviewPreparationOnboardingFeatureBuilder.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/interview_preparation_onboarding/injection/InterviewPreparationOnboardingFeatureBuilder.kt index e8ef471250..9e33eec90b 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/interview_preparation_onboarding/injection/InterviewPreparationOnboardingFeatureBuilder.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/interview_preparation_onboarding/injection/InterviewPreparationOnboardingFeatureBuilder.kt @@ -21,7 +21,7 @@ internal object InterviewPreparationOnboardingFeatureBuilder { fun build( stepRoute: StepRoute, - analyticsInteractor: AnalyticInteractor, + analyticInteractor: AnalyticInteractor, onboardingInteractor: OnboardingInteractor, logger: Logger, buildVariant: BuildVariant @@ -32,7 +32,7 @@ internal object InterviewPreparationOnboardingFeatureBuilder { val actionDispatcher = InterviewPreparationOnboardingActionDispatcher( config = ActionDispatcherOptions(), - analyticInteractor = analyticsInteractor, + analyticInteractor = analyticInteractor, onboardingInteractor = onboardingInteractor ) return ReduxFeature( diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/interview_preparation_onboarding/presentation/InterviewPreparationOnboardingReducer.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/interview_preparation_onboarding/presentation/InterviewPreparationOnboardingReducer.kt index 5ac4361708..b4e3a75a47 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/interview_preparation_onboarding/presentation/InterviewPreparationOnboardingReducer.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/interview_preparation_onboarding/presentation/InterviewPreparationOnboardingReducer.kt @@ -1,7 +1,7 @@ package org.hyperskill.app.interview_preparation_onboarding.presentation -import org.hyperskill.app.interview_preparation_onboarding.domain.analytic.InterviewPreparationOnboardingGoToProblemClickedAnalyticsEvent -import org.hyperskill.app.interview_preparation_onboarding.domain.analytic.InterviewPreparationOnboardingViewedAnalyticsEvent +import org.hyperskill.app.interview_preparation_onboarding.domain.analytic.InterviewPreparationOnboardingGoToProblemClickedAnalyticEvent +import org.hyperskill.app.interview_preparation_onboarding.domain.analytic.InterviewPreparationOnboardingViewedAnalyticEvent import org.hyperskill.app.interview_preparation_onboarding.presentation.InterviewPreparationOnboardingFeature.Action import org.hyperskill.app.interview_preparation_onboarding.presentation.InterviewPreparationOnboardingFeature.InternalAction import org.hyperskill.app.interview_preparation_onboarding.presentation.InterviewPreparationOnboardingFeature.Message @@ -19,14 +19,14 @@ internal class InterviewPreparationOnboardingReducer : StateReducer state to setOf( InternalAction.LogAnalyticEvent( - InterviewPreparationOnboardingViewedAnalyticsEvent + InterviewPreparationOnboardingViewedAnalyticEvent ), InternalAction.MarkOnboardingAsViewed ) Message.GoToFirstProblemClicked -> state to setOf( InternalAction.LogAnalyticEvent( - InterviewPreparationOnboardingGoToProblemClickedAnalyticsEvent + InterviewPreparationOnboardingGoToProblemClickedAnalyticEvent ), Action.ViewAction.NavigateTo.StepScreen(state.stepRoute) ) diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/notification/click_handling/presentation/NotificationClickHandlingFeature.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/notification/click_handling/presentation/NotificationClickHandlingFeature.kt index bffbae9b05..a27d66fea0 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/notification/click_handling/presentation/NotificationClickHandlingFeature.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/notification/click_handling/presentation/NotificationClickHandlingFeature.kt @@ -14,7 +14,7 @@ object NotificationClickHandlingFeature { sealed interface Message { /** - * If [isUserAuthorized] == false, then just logs analytics event. + * If [isUserAuthorized] == false, then just logs analytic event. * Otherwise, also executes navigation to the appropriate for the [notificationData] screen. */ data class NotificationClicked( diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/notification/click_handling/presentation/NotificationClickHandlingReducer.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/notification/click_handling/presentation/NotificationClickHandlingReducer.kt index fed058a9f6..30c843ccc7 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/notification/click_handling/presentation/NotificationClickHandlingReducer.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/notification/click_handling/presentation/NotificationClickHandlingReducer.kt @@ -12,7 +12,7 @@ import org.hyperskill.app.notification.click_handling.presentation.NotificationC import org.hyperskill.app.notification.click_handling.presentation.NotificationClickHandlingFeature.State import org.hyperskill.app.notification.remote.domain.analytic.PushNotificationClickedHyperskillAnalyticEvent import org.hyperskill.app.notification.remote.domain.model.PushNotificationType -import org.hyperskill.app.profile.domain.analytic.badges.EarnedBadgeModalHiddenHyperskillAnalyticsEvent +import org.hyperskill.app.profile.domain.analytic.badges.EarnedBadgeModalHiddenHyperskillAnalyticEvent import org.hyperskill.app.profile.domain.analytic.badges.EarnedBadgeModalShownHyperskillAnalyticEvent import org.hyperskill.app.step.domain.model.StepRoute import ru.nobird.app.presentation.redux.reducer.StateReducer @@ -31,7 +31,7 @@ class NotificationClickHandlingReducer : StateReducer { */ is Message.EarnedBadgeModalHiddenEventMessage -> setOf( - InternalAction.LogAnalyticEvent(EarnedBadgeModalHiddenHyperskillAnalyticsEvent(message.badgeKind)) + InternalAction.LogAnalyticEvent(EarnedBadgeModalHiddenHyperskillAnalyticEvent(message.badgeKind)) ) is Message.EarnedBadgeModalShownEventMessage -> setOf( @@ -42,10 +42,10 @@ class NotificationClickHandlingReducer : StateReducer { private fun handleNotificationClicked( message: Message.NotificationClicked ): Set { - val analyticsAction = InternalAction.LogAnalyticEvent( + val logAnalyticEventAction = InternalAction.LogAnalyticEvent( PushNotificationClickedHyperskillAnalyticEvent(message.notificationData) ) - if (!message.isUserAuthorized) return setOf(analyticsAction) + if (!message.isUserAuthorized) return setOf(logAnalyticEventAction) val actions = when (message.notificationData.typeEnum) { PushNotificationType.STREAK_THREE, @@ -97,7 +97,7 @@ class NotificationClickHandlingReducer : StateReducer { setOf(Action.ViewAction.NavigateTo.StudyPlan) } - return actions + analyticsAction + return actions + logAnalyticEventAction } private fun handleProfileFetchResult( diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/notification/local/domain/analytic/NotificationDailyStudyReminderClickedHyperskillAnalyticEvent.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/notification/local/domain/analytic/NotificationDailyStudyReminderClickedHyperskillAnalyticEvent.kt index 3fe73d7185..a0657ab57c 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/notification/local/domain/analytic/NotificationDailyStudyReminderClickedHyperskillAnalyticEvent.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/notification/local/domain/analytic/NotificationDailyStudyReminderClickedHyperskillAnalyticEvent.kt @@ -14,7 +14,7 @@ import org.hyperskill.app.analytic.domain.model.hyperskill.HyperskillAnalyticTar * JSON payload: * ``` * { - * "route": "/home", + * "route": "None", * "action": "click", * "part": "notification", * "target": "daily_notification", @@ -29,7 +29,7 @@ import org.hyperskill.app.analytic.domain.model.hyperskill.HyperskillAnalyticTar class NotificationDailyStudyReminderClickedHyperskillAnalyticEvent( private val notificationId: Int ) : HyperskillAnalyticEvent( - HyperskillAnalyticRoute.Home(), + HyperskillAnalyticRoute.None, HyperskillAnalyticAction.CLICK, HyperskillAnalyticPart.NOTIFICATION, HyperskillAnalyticTarget.DAILY_NOTIFICATION diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/notification/local/domain/analytic/NotificationDailyStudyReminderShownHyperskillAnalyticEvent.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/notification/local/domain/analytic/NotificationDailyStudyReminderShownHyperskillAnalyticEvent.kt index 7e26cf3713..1e1eb486b6 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/notification/local/domain/analytic/NotificationDailyStudyReminderShownHyperskillAnalyticEvent.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/notification/local/domain/analytic/NotificationDailyStudyReminderShownHyperskillAnalyticEvent.kt @@ -14,7 +14,7 @@ import ru.nobird.app.core.model.mapOfNotNull * JSON payload: * ``` * { - * "route": "/home", + * "route": "None", * "action": "shown", * "part": "notification", * "target": "daily_notification", @@ -28,11 +28,10 @@ import ru.nobird.app.core.model.mapOfNotNull * @see HyperskillAnalyticEvent */ class NotificationDailyStudyReminderShownHyperskillAnalyticEvent( - route: HyperskillAnalyticRoute, private val notificationId: Int, private val plannedAtISO8601: String? ) : HyperskillAnalyticEvent( - route, + HyperskillAnalyticRoute.None, HyperskillAnalyticAction.SHOWN, HyperskillAnalyticPart.NOTIFICATION, HyperskillAnalyticTarget.DAILY_NOTIFICATION diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/notification/local/domain/analytic/NotificationSystemNoticeHiddenHyperskillAnalyticEvent.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/notification/local/domain/analytic/NotificationSystemNoticeHiddenHyperskillAnalyticEvent.kt index b3d3a2ecf1..9fb7903eb6 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/notification/local/domain/analytic/NotificationSystemNoticeHiddenHyperskillAnalyticEvent.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/notification/local/domain/analytic/NotificationSystemNoticeHiddenHyperskillAnalyticEvent.kt @@ -13,7 +13,7 @@ import org.hyperskill.app.analytic.domain.model.hyperskill.HyperskillAnalyticTar * JSON payload: * ``` * { - * "route": "/home", + * "route": "/onboarding/notifications" | "None", * "action": "hidden", * "part": "notifications_system_notice", * "target": "allow / deny" @@ -22,7 +22,7 @@ import org.hyperskill.app.analytic.domain.model.hyperskill.HyperskillAnalyticTar * @see HyperskillAnalyticEvent */ class NotificationSystemNoticeHiddenHyperskillAnalyticEvent( - route: HyperskillAnalyticRoute, + route: HyperskillAnalyticRoute = HyperskillAnalyticRoute.None, isAllowed: Boolean ) : HyperskillAnalyticEvent( route, diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/notification/local/domain/analytic/NotificationSystemNoticeShownHyperskillAnalyticEvent.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/notification/local/domain/analytic/NotificationSystemNoticeShownHyperskillAnalyticEvent.kt index 58cde0476c..d27d3ee178 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/notification/local/domain/analytic/NotificationSystemNoticeShownHyperskillAnalyticEvent.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/notification/local/domain/analytic/NotificationSystemNoticeShownHyperskillAnalyticEvent.kt @@ -13,7 +13,7 @@ import org.hyperskill.app.analytic.domain.model.hyperskill.HyperskillAnalyticTar * JSON payload: * ``` * { - * "route": "/home", + * "route": "/onboarding/notifications" | "None", * "action": "shown", * "part": "notice", * "target": "notifications_system_notice" @@ -22,7 +22,7 @@ import org.hyperskill.app.analytic.domain.model.hyperskill.HyperskillAnalyticTar * @see HyperskillAnalyticEvent */ class NotificationSystemNoticeShownHyperskillAnalyticEvent( - route: HyperskillAnalyticRoute + route: HyperskillAnalyticRoute = HyperskillAnalyticRoute.None ) : HyperskillAnalyticEvent( route, HyperskillAnalyticAction.SHOWN, diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/notification/remote/domain/analytic/PushNotificationClickedHyperskillAnalyticEvent.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/notification/remote/domain/analytic/PushNotificationClickedHyperskillAnalyticEvent.kt index 4d5377b494..bb678d82d5 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/notification/remote/domain/analytic/PushNotificationClickedHyperskillAnalyticEvent.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/notification/remote/domain/analytic/PushNotificationClickedHyperskillAnalyticEvent.kt @@ -14,7 +14,7 @@ import org.hyperskill.app.notification.remote.domain.model.PushNotificationData * JSON payload: * ``` * { - * "route": "/home", + * "route": "None", * "action": "click", * "part": "notification", * "context": @@ -32,7 +32,7 @@ import org.hyperskill.app.notification.remote.domain.model.PushNotificationData class PushNotificationClickedHyperskillAnalyticEvent( private val pushNotificationData: PushNotificationData ) : HyperskillAnalyticEvent( - HyperskillAnalyticRoute.Home(), + HyperskillAnalyticRoute.None, HyperskillAnalyticAction.CLICK, HyperskillAnalyticPart.NOTIFICATION ) { diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/notification/remote/domain/analytic/PushNotificationShownHyperskillAnalyticEvent.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/notification/remote/domain/analytic/PushNotificationShownHyperskillAnalyticEvent.kt index f5b248f3d3..53585a5f15 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/notification/remote/domain/analytic/PushNotificationShownHyperskillAnalyticEvent.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/notification/remote/domain/analytic/PushNotificationShownHyperskillAnalyticEvent.kt @@ -14,7 +14,7 @@ import org.hyperskill.app.notification.remote.domain.model.PushNotificationData * JSON payload: * ``` * { - * "route": "/home", + * "route": "None", * "action": "shown", * "part": "notification", * "context": @@ -32,7 +32,7 @@ import org.hyperskill.app.notification.remote.domain.model.PushNotificationData class PushNotificationShownHyperskillAnalyticEvent( private val pushNotificationData: PushNotificationData ) : HyperskillAnalyticEvent( - HyperskillAnalyticRoute.Home(), + HyperskillAnalyticRoute.None, HyperskillAnalyticAction.SHOWN, HyperskillAnalyticPart.NOTIFICATION ) { diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/profile/domain/analytic/badges/EarnedBadgeModalHiddenHyperskillAnalyticsEvent.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/profile/domain/analytic/badges/EarnedBadgeModalHiddenHyperskillAnalyticEvent.kt similarity index 94% rename from shared/src/commonMain/kotlin/org/hyperskill/app/profile/domain/analytic/badges/EarnedBadgeModalHiddenHyperskillAnalyticsEvent.kt rename to shared/src/commonMain/kotlin/org/hyperskill/app/profile/domain/analytic/badges/EarnedBadgeModalHiddenHyperskillAnalyticEvent.kt index 77ea6244f2..de37e08eaa 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/profile/domain/analytic/badges/EarnedBadgeModalHiddenHyperskillAnalyticsEvent.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/profile/domain/analytic/badges/EarnedBadgeModalHiddenHyperskillAnalyticEvent.kt @@ -8,7 +8,7 @@ import org.hyperskill.app.analytic.domain.model.hyperskill.HyperskillAnalyticTar import org.hyperskill.app.badges.domain.model.BadgeKind /** - * Represents a hidden analytic event of the earned badge modal analytics event. + * Represents a hidden analytic event of the earned badge modal analytic event. * * JSON payload: * ``` @@ -24,7 +24,7 @@ import org.hyperskill.app.badges.domain.model.BadgeKind * ``` * @see HyperskillAnalyticEvent */ -class EarnedBadgeModalHiddenHyperskillAnalyticsEvent( +class EarnedBadgeModalHiddenHyperskillAnalyticEvent( private val badgeKind: BadgeKind ) : HyperskillAnalyticEvent( HyperskillAnalyticRoute.Profile(), diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/profile/domain/analytic/badges/EarnedBadgeModalShownHyperskillAnalyticEvent.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/profile/domain/analytic/badges/EarnedBadgeModalShownHyperskillAnalyticEvent.kt index 98f39cc839..ae7503c8f9 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/profile/domain/analytic/badges/EarnedBadgeModalShownHyperskillAnalyticEvent.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/profile/domain/analytic/badges/EarnedBadgeModalShownHyperskillAnalyticEvent.kt @@ -8,7 +8,7 @@ import org.hyperskill.app.analytic.domain.model.hyperskill.HyperskillAnalyticTar import org.hyperskill.app.badges.domain.model.BadgeKind /** - * Represents show of the earned badge modal analytics event. + * Represents show of the earned badge modal analytic event. * * JSON payload: * ``` @@ -22,6 +22,7 @@ import org.hyperskill.app.badges.domain.model.BadgeKind * } * } * ``` + * * @see HyperskillAnalyticEvent */ class EarnedBadgeModalShownHyperskillAnalyticEvent( diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/profile/domain/analytic/badges/ProfileBadgeModalHiddenHyperskillAnalyticsEvent.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/profile/domain/analytic/badges/ProfileBadgeModalHiddenHyperskillAnalyticEvent.kt similarity index 93% rename from shared/src/commonMain/kotlin/org/hyperskill/app/profile/domain/analytic/badges/ProfileBadgeModalHiddenHyperskillAnalyticsEvent.kt rename to shared/src/commonMain/kotlin/org/hyperskill/app/profile/domain/analytic/badges/ProfileBadgeModalHiddenHyperskillAnalyticEvent.kt index 2414552214..9e0bf7e7ca 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/profile/domain/analytic/badges/ProfileBadgeModalHiddenHyperskillAnalyticsEvent.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/profile/domain/analytic/badges/ProfileBadgeModalHiddenHyperskillAnalyticEvent.kt @@ -8,7 +8,7 @@ import org.hyperskill.app.analytic.domain.model.hyperskill.HyperskillAnalyticTar import org.hyperskill.app.badges.domain.model.BadgeKind /** - * Represents a hidden analytic event of the badge detailed modal in profile analytics event. + * Represents a hidden analytic event of the badge detailed modal in profile analytic event. * * JSON payload: * ``` @@ -24,7 +24,7 @@ import org.hyperskill.app.badges.domain.model.BadgeKind * ``` * @see HyperskillAnalyticEvent */ -class ProfileBadgeModalHiddenHyperskillAnalyticsEvent( +class ProfileBadgeModalHiddenHyperskillAnalyticEvent( private val badgeKind: BadgeKind ) : HyperskillAnalyticEvent( HyperskillAnalyticRoute.Profile(), diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/profile/domain/analytic/badges/ProfileBadgeModalShownHyperskillAnalyticEvent.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/profile/domain/analytic/badges/ProfileBadgeModalShownHyperskillAnalyticEvent.kt index dae21dc972..335b31391b 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/profile/domain/analytic/badges/ProfileBadgeModalShownHyperskillAnalyticEvent.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/profile/domain/analytic/badges/ProfileBadgeModalShownHyperskillAnalyticEvent.kt @@ -8,7 +8,7 @@ import org.hyperskill.app.analytic.domain.model.hyperskill.HyperskillAnalyticTar import org.hyperskill.app.badges.domain.model.BadgeKind /** - * Represents show of the badge detailed modal in profile analytics event. + * Represents show of the badge detailed modal in profile analytic event. * * JSON payload: * ``` diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/profile/domain/analytic/badges/ProfileClickedBadgeCardHyperskillAnalyticsEvent.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/profile/domain/analytic/badges/ProfileClickedBadgeCardHyperskillAnalyticEvent.kt similarity index 92% rename from shared/src/commonMain/kotlin/org/hyperskill/app/profile/domain/analytic/badges/ProfileClickedBadgeCardHyperskillAnalyticsEvent.kt rename to shared/src/commonMain/kotlin/org/hyperskill/app/profile/domain/analytic/badges/ProfileClickedBadgeCardHyperskillAnalyticEvent.kt index d0155afe41..91a87c6faa 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/profile/domain/analytic/badges/ProfileClickedBadgeCardHyperskillAnalyticsEvent.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/profile/domain/analytic/badges/ProfileClickedBadgeCardHyperskillAnalyticEvent.kt @@ -8,7 +8,7 @@ import org.hyperskill.app.analytic.domain.model.hyperskill.HyperskillAnalyticTar import org.hyperskill.app.badges.domain.model.BadgeKind /** - * Represents click on the badge in profile analytics event. + * Represents click on the badge in profile analytic event. * * JSON payload: * ``` @@ -23,9 +23,10 @@ import org.hyperskill.app.badges.domain.model.BadgeKind * } * } * ``` + * * @see HyperskillAnalyticEvent */ -class ProfileClickedBadgeCardHyperskillAnalyticsEvent( +class ProfileClickedBadgeCardHyperskillAnalyticEvent( private val badgeKind: BadgeKind, private val isLocked: Boolean ) : HyperskillAnalyticEvent( diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/profile/domain/analytic/badges/ProfileClickedBadgesVisibilityButtonHyperskillAnalyticsEvent.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/profile/domain/analytic/badges/ProfileClickedBadgesVisibilityButtonHyperskillAnalyticEvent.kt similarity index 94% rename from shared/src/commonMain/kotlin/org/hyperskill/app/profile/domain/analytic/badges/ProfileClickedBadgesVisibilityButtonHyperskillAnalyticsEvent.kt rename to shared/src/commonMain/kotlin/org/hyperskill/app/profile/domain/analytic/badges/ProfileClickedBadgesVisibilityButtonHyperskillAnalyticEvent.kt index fa9994c6c8..e5e61c4ec6 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/profile/domain/analytic/badges/ProfileClickedBadgesVisibilityButtonHyperskillAnalyticsEvent.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/profile/domain/analytic/badges/ProfileClickedBadgesVisibilityButtonHyperskillAnalyticEvent.kt @@ -8,7 +8,7 @@ import org.hyperskill.app.analytic.domain.model.hyperskill.HyperskillAnalyticTar import org.hyperskill.app.profile.presentation.ProfileFeature /** - * Represents click on the showAll or showLess badges button in profile analytics event. + * Represents click on the showAll or showLess badges button in profile analytic event. * * JSON payload: * ``` @@ -22,9 +22,10 @@ import org.hyperskill.app.profile.presentation.ProfileFeature * } * } * ``` + * * @see HyperskillAnalyticEvent */ -class ProfileClickedBadgesVisibilityButtonHyperskillAnalyticsEvent( +class ProfileClickedBadgesVisibilityButtonHyperskillAnalyticEvent( private val visibilityButton: ProfileFeature.Message.BadgesVisibilityButton ) : HyperskillAnalyticEvent( HyperskillAnalyticRoute.Profile(), diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/profile/presentation/ProfileReducer.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/profile/presentation/ProfileReducer.kt index 59e00041ef..72a734a5b3 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/profile/presentation/ProfileReducer.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/profile/presentation/ProfileReducer.kt @@ -7,10 +7,10 @@ import org.hyperskill.app.profile.domain.analytic.ProfileClickedPullToRefreshHyp import org.hyperskill.app.profile.domain.analytic.ProfileClickedSettingsHyperskillAnalyticEvent import org.hyperskill.app.profile.domain.analytic.ProfileClickedViewFullProfileHyperskillAnalyticEvent import org.hyperskill.app.profile.domain.analytic.ProfileViewedHyperskillAnalyticEvent -import org.hyperskill.app.profile.domain.analytic.badges.ProfileBadgeModalHiddenHyperskillAnalyticsEvent +import org.hyperskill.app.profile.domain.analytic.badges.ProfileBadgeModalHiddenHyperskillAnalyticEvent import org.hyperskill.app.profile.domain.analytic.badges.ProfileBadgeModalShownHyperskillAnalyticEvent -import org.hyperskill.app.profile.domain.analytic.badges.ProfileClickedBadgeCardHyperskillAnalyticsEvent -import org.hyperskill.app.profile.domain.analytic.badges.ProfileClickedBadgesVisibilityButtonHyperskillAnalyticsEvent +import org.hyperskill.app.profile.domain.analytic.badges.ProfileClickedBadgeCardHyperskillAnalyticEvent +import org.hyperskill.app.profile.domain.analytic.badges.ProfileClickedBadgesVisibilityButtonHyperskillAnalyticEvent import org.hyperskill.app.profile.domain.analytic.streak_freeze.StreakFreezeAnalyticState import org.hyperskill.app.profile.domain.analytic.streak_freeze.StreakFreezeCardAnalyticAction import org.hyperskill.app.profile.domain.analytic.streak_freeze.StreakFreezeClickedCardActionHyperskillAnalyticEvent @@ -188,7 +188,7 @@ class ProfileReducer : StateReducer { is Message.BadgeModalHiddenEventMessage -> state to setOf( Action.LogAnalyticEvent( - ProfileBadgeModalHiddenHyperskillAnalyticsEvent(message.badgeKind) + ProfileBadgeModalHiddenHyperskillAnalyticEvent(message.badgeKind) ) ) is Message.ViewedEventMessage -> @@ -300,7 +300,7 @@ class ProfileReducer : StateReducer { ) ) to setOf( Action.LogAnalyticEvent( - ProfileClickedBadgesVisibilityButtonHyperskillAnalyticsEvent(message.visibilityButton) + ProfileClickedBadgesVisibilityButtonHyperskillAnalyticEvent(message.visibilityButton) ) ) } else { @@ -323,7 +323,7 @@ class ProfileReducer : StateReducer { state to setOf( showAction, Action.LogAnalyticEvent( - ProfileClickedBadgeCardHyperskillAnalyticsEvent( + ProfileClickedBadgeCardHyperskillAnalyticEvent( badgeKind = message.badgeKind, isLocked = message.badgeKind !in state.badgesState.badges.map { it.kind } ) diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/profile_settings/presentation/ProfileSettingsReducer.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/profile_settings/presentation/ProfileSettingsReducer.kt index 87faee287f..b3019ef42e 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/profile_settings/presentation/ProfileSettingsReducer.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/profile_settings/presentation/ProfileSettingsReducer.kt @@ -121,7 +121,7 @@ class ProfileSettingsReducer : StateReducer { Action.LogAnalyticEvent(ProfileSettingsDeleteAccountNoticeShownHyperskillAnalyticEvent()) ) is Message.DeleteAccountNoticeHidden -> { - val analyticsAction = Action.LogAnalyticEvent( + val logAnalyticEventAction = Action.LogAnalyticEvent( ProfileSettingsDeleteAccountNoticeHiddenHyperskillAnalyticEvent( message.isConfirmed ) @@ -129,10 +129,10 @@ class ProfileSettingsReducer : StateReducer { if (message.isConfirmed && state is State.Content) { state.copy(isLoadingMagicLink = true) to setOf( Action.GetMagicLink(HyperskillUrlPath.DeleteAccount()), - analyticsAction + logAnalyticEventAction ) } else { - state to setOf(analyticsAction) + state to setOf(logAnalyticEventAction) } } Message.ClickedRateUsInAppStoreEventMessage -> diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/project_selection/details/domain/analytic/ProjectSelectionDetailsClickedRetryContentLoadingHyperskillAnalyticsEvent.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/project_selection/details/domain/analytic/ProjectSelectionDetailsClickedRetryContentLoadingHyperskillAnalyticEvent.kt similarity index 98% rename from shared/src/commonMain/kotlin/org/hyperskill/app/project_selection/details/domain/analytic/ProjectSelectionDetailsClickedRetryContentLoadingHyperskillAnalyticsEvent.kt rename to shared/src/commonMain/kotlin/org/hyperskill/app/project_selection/details/domain/analytic/ProjectSelectionDetailsClickedRetryContentLoadingHyperskillAnalyticEvent.kt index d08c6492c8..e1decaaa86 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/project_selection/details/domain/analytic/ProjectSelectionDetailsClickedRetryContentLoadingHyperskillAnalyticsEvent.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/project_selection/details/domain/analytic/ProjectSelectionDetailsClickedRetryContentLoadingHyperskillAnalyticEvent.kt @@ -26,7 +26,7 @@ import org.hyperskill.app.analytic.domain.model.hyperskill.HyperskillAnalyticTar * * @see HyperskillAnalyticEvent */ -class ProjectSelectionDetailsClickedRetryContentLoadingHyperskillAnalyticsEvent( +class ProjectSelectionDetailsClickedRetryContentLoadingHyperskillAnalyticEvent( projectId: Long, trackId: Long ) : HyperskillAnalyticEvent( diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/project_selection/details/domain/analytic/ProjectSelectionDetailsClickedSelectThisProjectHyperskillAnalyticsEvent.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/project_selection/details/domain/analytic/ProjectSelectionDetailsClickedSelectThisProjectHyperskillAnalyticEvent.kt similarity index 98% rename from shared/src/commonMain/kotlin/org/hyperskill/app/project_selection/details/domain/analytic/ProjectSelectionDetailsClickedSelectThisProjectHyperskillAnalyticsEvent.kt rename to shared/src/commonMain/kotlin/org/hyperskill/app/project_selection/details/domain/analytic/ProjectSelectionDetailsClickedSelectThisProjectHyperskillAnalyticEvent.kt index de600af685..d86a80fa0b 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/project_selection/details/domain/analytic/ProjectSelectionDetailsClickedSelectThisProjectHyperskillAnalyticsEvent.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/project_selection/details/domain/analytic/ProjectSelectionDetailsClickedSelectThisProjectHyperskillAnalyticEvent.kt @@ -26,7 +26,7 @@ import org.hyperskill.app.analytic.domain.model.hyperskill.HyperskillAnalyticTar * * @see HyperskillAnalyticEvent */ -class ProjectSelectionDetailsClickedSelectThisProjectHyperskillAnalyticsEvent( +class ProjectSelectionDetailsClickedSelectThisProjectHyperskillAnalyticEvent( projectId: Long, trackId: Long ) : HyperskillAnalyticEvent( diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/project_selection/details/presentation/ProjectSelectionDetailsReducer.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/project_selection/details/presentation/ProjectSelectionDetailsReducer.kt index e8eb5d94ba..9e2df5d8f4 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/project_selection/details/presentation/ProjectSelectionDetailsReducer.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/project_selection/details/presentation/ProjectSelectionDetailsReducer.kt @@ -1,7 +1,7 @@ package org.hyperskill.app.project_selection.details.presentation -import org.hyperskill.app.project_selection.details.domain.analytic.ProjectSelectionDetailsClickedRetryContentLoadingHyperskillAnalyticsEvent -import org.hyperskill.app.project_selection.details.domain.analytic.ProjectSelectionDetailsClickedSelectThisProjectHyperskillAnalyticsEvent +import org.hyperskill.app.project_selection.details.domain.analytic.ProjectSelectionDetailsClickedRetryContentLoadingHyperskillAnalyticEvent +import org.hyperskill.app.project_selection.details.domain.analytic.ProjectSelectionDetailsClickedSelectThisProjectHyperskillAnalyticEvent import org.hyperskill.app.project_selection.details.domain.analytic.ProjectSelectionDetailsViewedHyperskillAnalyticEvent import org.hyperskill.app.project_selection.details.presentation.ProjectSelectionDetailsFeature.Action import org.hyperskill.app.project_selection.details.presentation.ProjectSelectionDetailsFeature.ContentState @@ -69,7 +69,7 @@ internal class ProjectSelectionDetailsReducer : StateReducer state to setOf( - InternalAction.LogAnalyticEvent( - TrackSelectionListViewedHyperskillAnalyticEvent() - ) + InternalAction.LogAnalyticEvent(TrackSelectionListViewedHyperskillAnalyticEvent) ) } ?: (state to emptySet()) diff --git a/shared/src/commonTest/kotlin/org/hyperskill/projects_selection/ProjectsListTest.kt b/shared/src/commonTest/kotlin/org/hyperskill/projects_selection/ProjectsListTest.kt index 948507eb32..ccab807885 100644 --- a/shared/src/commonTest/kotlin/org/hyperskill/projects_selection/ProjectsListTest.kt +++ b/shared/src/commonTest/kotlin/org/hyperskill/projects_selection/ProjectsListTest.kt @@ -9,7 +9,7 @@ import org.hyperskill.ResourceProviderStub import org.hyperskill.app.core.view.mapper.NumbersFormatter import org.hyperskill.app.core.view.mapper.date.SharedDateFormatter import org.hyperskill.app.profile.domain.model.Profile -import org.hyperskill.app.project_selection.list.domain.analytic.ProjectSelectionListClickedProjectHyperskillAnalyticsEvent +import org.hyperskill.app.project_selection.list.domain.analytic.ProjectSelectionListClickedProjectHyperskillAnalyticEvent import org.hyperskill.app.project_selection.list.presentation.ProjectSelectionListFeature import org.hyperskill.app.project_selection.list.presentation.ProjectSelectionListFeature.Action import org.hyperskill.app.project_selection.list.presentation.ProjectSelectionListFeature.ContentState @@ -182,7 +182,7 @@ class ProjectsListTest { assertTrue { actions.any { it is InternalAction.LogAnalyticEvent && - it.analyticEvent is ProjectSelectionListClickedProjectHyperskillAnalyticsEvent && + it.analyticEvent is ProjectSelectionListClickedProjectHyperskillAnalyticEvent && it.analyticEvent.projectId == projectId && it.analyticEvent.trackId == trackId } From 8e7b7f332f18a34ebeadc7eaa4914855bf7c315b Mon Sep 17 00:00:00 2001 From: github-actions Date: Fri, 16 Feb 2024 07:43:48 +0000 Subject: [PATCH 066/104] Bump build number --- gradle/app.versions.toml | 2 +- .../NotificationServiceExtension/Info.plist | 2 +- .../iosHyperskillApp.xcodeproj/project.pbxproj | 16 ++++++++-------- iosHyperskillApp/iosHyperskillApp/Info.plist | 2 +- .../iosHyperskillAppTests/Info.plist | 2 +- .../iosHyperskillAppUITests/Info.plist | 2 +- 6 files changed, 13 insertions(+), 13 deletions(-) diff --git a/gradle/app.versions.toml b/gradle/app.versions.toml index 459b5bad43..b3a2a13840 100644 --- a/gradle/app.versions.toml +++ b/gradle/app.versions.toml @@ -3,4 +3,4 @@ minSdk = '24' targetSdk = '33' compileSdk = '33' versionName = '1.50' -versionCode = '326' \ No newline at end of file +versionCode = '327' \ No newline at end of file diff --git a/iosHyperskillApp/NotificationServiceExtension/Info.plist b/iosHyperskillApp/NotificationServiceExtension/Info.plist index f285bb9896..111c24916e 100644 --- a/iosHyperskillApp/NotificationServiceExtension/Info.plist +++ b/iosHyperskillApp/NotificationServiceExtension/Info.plist @@ -9,7 +9,7 @@ CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleVersion - 332 + 333 CFBundleShortVersionString 1.50 CFBundlePackageType diff --git a/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj b/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj index fe81409fc7..3131bf34eb 100644 --- a/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj +++ b/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj @@ -5037,7 +5037,7 @@ buildSettings = { CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 332; + CURRENT_PROJECT_VERSION = 333; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = 3DWS674B2M; GENERATE_INFOPLIST_FILE = NO; @@ -5058,7 +5058,7 @@ buildSettings = { CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 332; + CURRENT_PROJECT_VERSION = 333; DEVELOPMENT_TEAM = 3DWS674B2M; GENERATE_INFOPLIST_FILE = NO; INFOPLIST_FILE = iosHyperskillAppUITests/Info.plist; @@ -5079,7 +5079,7 @@ BUNDLE_LOADER = "$(TEST_HOST)"; CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 332; + CURRENT_PROJECT_VERSION = 333; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = 3DWS674B2M; INFOPLIST_FILE = iosHyperskillAppTests/Info.plist; @@ -5100,7 +5100,7 @@ BUNDLE_LOADER = "$(TEST_HOST)"; CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 332; + CURRENT_PROJECT_VERSION = 333; DEVELOPMENT_TEAM = 3DWS674B2M; INFOPLIST_FILE = iosHyperskillAppTests/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; @@ -5121,7 +5121,7 @@ CODE_SIGN_ENTITLEMENTS = NotificationServiceExtension/NotificationServiceExtension.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 332; + CURRENT_PROJECT_VERSION = 333; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = 3DWS674B2M; INFOPLIST_FILE = NotificationServiceExtension/Info.plist; @@ -5149,7 +5149,7 @@ CODE_SIGN_ENTITLEMENTS = NotificationServiceExtension/NotificationServiceExtension.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 332; + CURRENT_PROJECT_VERSION = 333; DEVELOPMENT_TEAM = 3DWS674B2M; INFOPLIST_FILE = NotificationServiceExtension/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; @@ -5294,7 +5294,7 @@ CODE_SIGN_ENTITLEMENTS = iosHyperskillApp/iosHyperskillApp.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 332; + CURRENT_PROJECT_VERSION = 333; DEVELOPMENT_ASSET_PATHS = "\"iosHyperskillApp/Preview Content\""; DEVELOPMENT_TEAM = 3DWS674B2M; ENABLE_PREVIEWS = YES; @@ -5330,7 +5330,7 @@ CODE_SIGN_ENTITLEMENTS = iosHyperskillApp/iosHyperskillApp.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 332; + CURRENT_PROJECT_VERSION = 333; DEVELOPMENT_ASSET_PATHS = "\"iosHyperskillApp/Preview Content\""; DEVELOPMENT_TEAM = 3DWS674B2M; ENABLE_PREVIEWS = YES; diff --git a/iosHyperskillApp/iosHyperskillApp/Info.plist b/iosHyperskillApp/iosHyperskillApp/Info.plist index 86111fb1ba..1abe1c631c 100644 --- a/iosHyperskillApp/iosHyperskillApp/Info.plist +++ b/iosHyperskillApp/iosHyperskillApp/Info.plist @@ -36,7 +36,7 @@ CFBundleVersion - 332 + 333 FirebaseAppDelegateProxyEnabled FirebaseMessagingAutoInitEnabled diff --git a/iosHyperskillApp/iosHyperskillAppTests/Info.plist b/iosHyperskillApp/iosHyperskillAppTests/Info.plist index 13adb013eb..847a84b6ac 100644 --- a/iosHyperskillApp/iosHyperskillAppTests/Info.plist +++ b/iosHyperskillApp/iosHyperskillAppTests/Info.plist @@ -15,6 +15,6 @@ CFBundleShortVersionString 1.50 CFBundleVersion - 332 + 333 diff --git a/iosHyperskillApp/iosHyperskillAppUITests/Info.plist b/iosHyperskillApp/iosHyperskillAppUITests/Info.plist index b2b1ef5f83..f236eb305c 100644 --- a/iosHyperskillApp/iosHyperskillAppUITests/Info.plist +++ b/iosHyperskillApp/iosHyperskillAppUITests/Info.plist @@ -15,6 +15,6 @@ CFBundleShortVersionString 1.50 CFBundleVersion - 332 + 333 From e8eeb8452bf5bea0a91cbcec5ef2a204c62b945d Mon Sep 17 00:00:00 2001 From: Ivan Magda Date: Fri, 16 Feb 2024 16:06:52 +0800 Subject: [PATCH 067/104] Clean up --- .../leaderboard/screen/presentation/LeaderboardScreenFeature.kt | 2 +- .../kotlin/org/hyperskill/app/step/domain/model/BlockName.kt | 1 - .../choice_answer/ChoiceAnswerContentSerializer.kt | 2 +- .../domain/serialization/feedback/FeedbackContentSerializer.kt | 2 +- .../app/study_plan/widget/presentation/StateExtentions.kt | 2 +- 5 files changed, 4 insertions(+), 5 deletions(-) diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/leaderboard/screen/presentation/LeaderboardScreenFeature.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/leaderboard/screen/presentation/LeaderboardScreenFeature.kt index e5d3d615a2..e1fc1a0981 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/leaderboard/screen/presentation/LeaderboardScreenFeature.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/leaderboard/screen/presentation/LeaderboardScreenFeature.kt @@ -37,7 +37,7 @@ object LeaderboardScreenFeature { ) : ListViewState } - enum class Tab() { + enum class Tab { DAY, WEEK } diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/step/domain/model/BlockName.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/step/domain/model/BlockName.kt index dbe127f9b5..21c2c576e6 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/step/domain/model/BlockName.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/step/domain/model/BlockName.kt @@ -15,7 +15,6 @@ object BlockName { const val TEXT = "text" const val PARSONS = "parsons" const val FILL_BLANKS = "fill-blanks" - const val VIDEO = "video" val codeRelatedBlocksNames: Set = setOf(CODE, PYCHARM, SQL) diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz/domain/serialization/choice_answer/ChoiceAnswerContentSerializer.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz/domain/serialization/choice_answer/ChoiceAnswerContentSerializer.kt index a833dddbd7..924f62e696 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz/domain/serialization/choice_answer/ChoiceAnswerContentSerializer.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz/domain/serialization/choice_answer/ChoiceAnswerContentSerializer.kt @@ -7,7 +7,7 @@ import kotlinx.serialization.json.JsonObject import org.hyperskill.app.step_quiz.domain.model.submissions.ChoiceAnswer object ChoiceAnswerContentSerializer : JsonContentPolymorphicSerializer(ChoiceAnswer::class) { - override fun selectDeserializer(element: JsonElement): DeserializationStrategy = + override fun selectDeserializer(element: JsonElement): DeserializationStrategy = if (element is JsonObject) { ChoiceAnswer.Table.serializer() } else { diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz/domain/serialization/feedback/FeedbackContentSerializer.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz/domain/serialization/feedback/FeedbackContentSerializer.kt index ee4081c077..143f45f225 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz/domain/serialization/feedback/FeedbackContentSerializer.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz/domain/serialization/feedback/FeedbackContentSerializer.kt @@ -7,7 +7,7 @@ import kotlinx.serialization.json.JsonObject import org.hyperskill.app.step_quiz.domain.model.submissions.Feedback object FeedbackContentSerializer : JsonContentPolymorphicSerializer(Feedback::class) { - override fun selectDeserializer(element: JsonElement): DeserializationStrategy = + override fun selectDeserializer(element: JsonElement): DeserializationStrategy = if (element is JsonObject) { Feedback.Object.serializer() } else { diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/study_plan/widget/presentation/StateExtentions.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/study_plan/widget/presentation/StateExtentions.kt index 54c16bd4d3..99caf42b74 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/study_plan/widget/presentation/StateExtentions.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/study_plan/widget/presentation/StateExtentions.kt @@ -10,7 +10,7 @@ import org.hyperskill.app.study_plan.domain.model.StudyPlanSectionType * * `studyPlanSections` map preserves the entry iteration order, so we can use the first element as the current section. * - * @see StudyPlanWidgetReducer.handleSectionsFetchSuccess + * @see StudyPlanWidgetReducer.handleLearningActivitiesWithSectionsFetchSuccess */ internal fun StudyPlanWidgetFeature.State.getCurrentSection(): StudyPlanSection? = studyPlanSections.values.firstOrNull()?.studyPlanSection From d1085f41fd514e56cddecf439c9fb7e4fe1dff5c Mon Sep 17 00:00:00 2001 From: github-actions Date: Fri, 16 Feb 2024 08:07:45 +0000 Subject: [PATCH 068/104] Bump build number --- gradle/app.versions.toml | 2 +- .../NotificationServiceExtension/Info.plist | 2 +- .../iosHyperskillApp.xcodeproj/project.pbxproj | 16 ++++++++-------- iosHyperskillApp/iosHyperskillApp/Info.plist | 2 +- .../iosHyperskillAppTests/Info.plist | 2 +- .../iosHyperskillAppUITests/Info.plist | 2 +- 6 files changed, 13 insertions(+), 13 deletions(-) diff --git a/gradle/app.versions.toml b/gradle/app.versions.toml index b3a2a13840..b0a5163842 100644 --- a/gradle/app.versions.toml +++ b/gradle/app.versions.toml @@ -3,4 +3,4 @@ minSdk = '24' targetSdk = '33' compileSdk = '33' versionName = '1.50' -versionCode = '327' \ No newline at end of file +versionCode = '328' \ No newline at end of file diff --git a/iosHyperskillApp/NotificationServiceExtension/Info.plist b/iosHyperskillApp/NotificationServiceExtension/Info.plist index 111c24916e..bd44c44e38 100644 --- a/iosHyperskillApp/NotificationServiceExtension/Info.plist +++ b/iosHyperskillApp/NotificationServiceExtension/Info.plist @@ -9,7 +9,7 @@ CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleVersion - 333 + 334 CFBundleShortVersionString 1.50 CFBundlePackageType diff --git a/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj b/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj index 3131bf34eb..0da48fc9d6 100644 --- a/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj +++ b/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj @@ -5037,7 +5037,7 @@ buildSettings = { CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 333; + CURRENT_PROJECT_VERSION = 334; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = 3DWS674B2M; GENERATE_INFOPLIST_FILE = NO; @@ -5058,7 +5058,7 @@ buildSettings = { CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 333; + CURRENT_PROJECT_VERSION = 334; DEVELOPMENT_TEAM = 3DWS674B2M; GENERATE_INFOPLIST_FILE = NO; INFOPLIST_FILE = iosHyperskillAppUITests/Info.plist; @@ -5079,7 +5079,7 @@ BUNDLE_LOADER = "$(TEST_HOST)"; CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 333; + CURRENT_PROJECT_VERSION = 334; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = 3DWS674B2M; INFOPLIST_FILE = iosHyperskillAppTests/Info.plist; @@ -5100,7 +5100,7 @@ BUNDLE_LOADER = "$(TEST_HOST)"; CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 333; + CURRENT_PROJECT_VERSION = 334; DEVELOPMENT_TEAM = 3DWS674B2M; INFOPLIST_FILE = iosHyperskillAppTests/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; @@ -5121,7 +5121,7 @@ CODE_SIGN_ENTITLEMENTS = NotificationServiceExtension/NotificationServiceExtension.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 333; + CURRENT_PROJECT_VERSION = 334; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = 3DWS674B2M; INFOPLIST_FILE = NotificationServiceExtension/Info.plist; @@ -5149,7 +5149,7 @@ CODE_SIGN_ENTITLEMENTS = NotificationServiceExtension/NotificationServiceExtension.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 333; + CURRENT_PROJECT_VERSION = 334; DEVELOPMENT_TEAM = 3DWS674B2M; INFOPLIST_FILE = NotificationServiceExtension/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; @@ -5294,7 +5294,7 @@ CODE_SIGN_ENTITLEMENTS = iosHyperskillApp/iosHyperskillApp.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 333; + CURRENT_PROJECT_VERSION = 334; DEVELOPMENT_ASSET_PATHS = "\"iosHyperskillApp/Preview Content\""; DEVELOPMENT_TEAM = 3DWS674B2M; ENABLE_PREVIEWS = YES; @@ -5330,7 +5330,7 @@ CODE_SIGN_ENTITLEMENTS = iosHyperskillApp/iosHyperskillApp.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 333; + CURRENT_PROJECT_VERSION = 334; DEVELOPMENT_ASSET_PATHS = "\"iosHyperskillApp/Preview Content\""; DEVELOPMENT_TEAM = 3DWS674B2M; ENABLE_PREVIEWS = YES; diff --git a/iosHyperskillApp/iosHyperskillApp/Info.plist b/iosHyperskillApp/iosHyperskillApp/Info.plist index 1abe1c631c..19a4059f74 100644 --- a/iosHyperskillApp/iosHyperskillApp/Info.plist +++ b/iosHyperskillApp/iosHyperskillApp/Info.plist @@ -36,7 +36,7 @@ CFBundleVersion - 333 + 334 FirebaseAppDelegateProxyEnabled FirebaseMessagingAutoInitEnabled diff --git a/iosHyperskillApp/iosHyperskillAppTests/Info.plist b/iosHyperskillApp/iosHyperskillAppTests/Info.plist index 847a84b6ac..e8d3eff06e 100644 --- a/iosHyperskillApp/iosHyperskillAppTests/Info.plist +++ b/iosHyperskillApp/iosHyperskillAppTests/Info.plist @@ -15,6 +15,6 @@ CFBundleShortVersionString 1.50 CFBundleVersion - 333 + 334 diff --git a/iosHyperskillApp/iosHyperskillAppUITests/Info.plist b/iosHyperskillApp/iosHyperskillAppUITests/Info.plist index f236eb305c..f8cea37aec 100644 --- a/iosHyperskillApp/iosHyperskillAppUITests/Info.plist +++ b/iosHyperskillApp/iosHyperskillAppUITests/Info.plist @@ -15,6 +15,6 @@ CFBundleShortVersionString 1.50 CFBundleVersion - 333 + 334 From d873fce3f5f487261787e9c900375b69ca91f263 Mon Sep 17 00:00:00 2001 From: Ivan Magda Date: Fri, 16 Feb 2024 16:28:13 +0700 Subject: [PATCH 069/104] Shared: ProfileInteractor deprecated use SubmissionRepository (#907) ^ALTAPPS-1157 --- .../app/core/injection/BaseAppGraph.kt | 3 +- .../domain/interactor/ProfileInteractor.kt | 19 ------------ .../profile/injection/ProfileComponentImpl.kt | 6 ++-- .../profile/injection/ProfileDataComponent.kt | 3 -- .../injection/ProfileDataComponentImpl.kt | 12 ++------ .../injection/ProfileFeatureBuilder.kt | 30 +++++++++---------- .../presentation/ProfileActionDispatcher.kt | 8 ++--- .../profile/presentation/ProfileReducer.kt | 2 +- 8 files changed, 27 insertions(+), 56 deletions(-) delete mode 100644 shared/src/commonMain/kotlin/org/hyperskill/app/profile/domain/interactor/ProfileInteractor.kt diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/core/injection/BaseAppGraph.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/core/injection/BaseAppGraph.kt index 3514a791d8..53d80e8742 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/core/injection/BaseAppGraph.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/core/injection/BaseAppGraph.kt @@ -207,8 +207,7 @@ abstract class BaseAppGraph : AppGraph { override val profileDataComponent: ProfileDataComponent by lazy { ProfileDataComponentImpl( networkComponent = networkComponent, - commonComponent = commonComponent, - submissionDataComponent = submissionDataComponent + commonComponent = commonComponent ) } diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/profile/domain/interactor/ProfileInteractor.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/profile/domain/interactor/ProfileInteractor.kt deleted file mode 100644 index b54059e68b..0000000000 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/profile/domain/interactor/ProfileInteractor.kt +++ /dev/null @@ -1,19 +0,0 @@ -package org.hyperskill.app.profile.domain.interactor - -import kotlinx.coroutines.flow.SharedFlow -import org.hyperskill.app.step_quiz.domain.repository.SubmissionRepository - -/* ktlint-disable */ -@Deprecated("ProfileInteractor is going to be removed. To access solvedStepsSharedFlow use SubmissionRepository directly.") -class ProfileInteractor( - submissionRepository: SubmissionRepository -) { - @Deprecated( - "Use submissionRepository.solvedStepsMutableSharedFlow instead.", - replaceWith = ReplaceWith( - "submissionRepository.solvedStepsMutableSharedFlow", - "import org.hyperskill.app.step_quiz.domain.repository.SubmissionRepository" - ) - ) - val solvedStepsSharedFlow: SharedFlow = submissionRepository.solvedStepsMutableSharedFlow -} \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/profile/injection/ProfileComponentImpl.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/profile/injection/ProfileComponentImpl.kt index 32a064df7b..701b1497f7 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/profile/injection/ProfileComponentImpl.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/profile/injection/ProfileComponentImpl.kt @@ -5,10 +5,11 @@ import org.hyperskill.app.profile.presentation.ProfileFeature import org.hyperskill.app.profile.view.BadgesViewStateMapper import ru.nobird.app.presentation.redux.feature.Feature -class ProfileComponentImpl(private val appGraph: AppGraph) : ProfileComponent { +internal class ProfileComponentImpl( + private val appGraph: AppGraph +) : ProfileComponent { override val profileFeature: Feature get() = ProfileFeatureBuilder.build( - profileInteractor = appGraph.profileDataComponent.profileInteractor, currentProfileStateRepository = appGraph.profileDataComponent.currentProfileStateRepository, streaksInteractor = appGraph.buildStreaksDataComponent().streaksInteractor, productsInteractor = appGraph.buildProductsDataComponent().productsInteractor, @@ -18,6 +19,7 @@ class ProfileComponentImpl(private val appGraph: AppGraph) : ProfileComponent { urlPathProcessor = appGraph.buildMagicLinksDataComponent().urlPathProcessor, streakFlow = appGraph.streakFlowDataComponent.streakFlow, dailyStudyRemindersEnabledFlow = appGraph.notificationFlowDataComponent.dailyStudyRemindersEnabledFlow, + submissionRepository = appGraph.submissionDataComponent.submissionRepository, badgesRepository = appGraph.buildBadgesDataComponent().badgesRepository, logger = appGraph.loggerComponent.logger, buildVariant = appGraph.commonComponent.buildKonfig.buildVariant diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/profile/injection/ProfileDataComponent.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/profile/injection/ProfileDataComponent.kt index 71e02b6eeb..2a81198105 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/profile/injection/ProfileDataComponent.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/profile/injection/ProfileDataComponent.kt @@ -1,12 +1,9 @@ package org.hyperskill.app.profile.injection -import org.hyperskill.app.profile.domain.interactor.ProfileInteractor import org.hyperskill.app.profile.domain.repository.CurrentProfileStateRepository import org.hyperskill.app.profile.domain.repository.ProfileRepository interface ProfileDataComponent { val profileRepository: ProfileRepository val currentProfileStateRepository: CurrentProfileStateRepository - @Deprecated("Use submissionDataComponent.submissionRepository instead.") - val profileInteractor: ProfileInteractor } \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/profile/injection/ProfileDataComponentImpl.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/profile/injection/ProfileDataComponentImpl.kt index 289d8375f6..965bae52cd 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/profile/injection/ProfileDataComponentImpl.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/profile/injection/ProfileDataComponentImpl.kt @@ -7,16 +7,13 @@ import org.hyperskill.app.profile.data.repository.CurrentProfileStateRepositoryI import org.hyperskill.app.profile.data.repository.ProfileRepositoryImpl import org.hyperskill.app.profile.data.source.CurrentProfileStateHolder import org.hyperskill.app.profile.data.source.ProfileRemoteDataSource -import org.hyperskill.app.profile.domain.interactor.ProfileInteractor import org.hyperskill.app.profile.domain.repository.CurrentProfileStateRepository import org.hyperskill.app.profile.domain.repository.ProfileRepository import org.hyperskill.app.profile.remote.ProfileRemoteDataSourceImpl -import org.hyperskill.app.step_quiz.injection.SubmissionDataComponent -class ProfileDataComponentImpl( +internal class ProfileDataComponentImpl( networkComponent: NetworkComponent, - commonComponent: CommonComponent, - private val submissionDataComponent: SubmissionDataComponent + commonComponent: CommonComponent ) : ProfileDataComponent { private val profileRemoteDataSource: ProfileRemoteDataSource by lazy { @@ -41,9 +38,4 @@ class ProfileDataComponentImpl( stateHolder = currentProfileStateHolder ) } - - override val profileInteractor: ProfileInteractor - get() = ProfileInteractor( - submissionDataComponent.submissionRepository - ) } \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/profile/injection/ProfileFeatureBuilder.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/profile/injection/ProfileFeatureBuilder.kt index edcdad4682..426d06b46d 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/profile/injection/ProfileFeatureBuilder.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/profile/injection/ProfileFeatureBuilder.kt @@ -10,7 +10,6 @@ import org.hyperskill.app.magic_links.domain.interactor.UrlPathProcessor import org.hyperskill.app.notification.local.domain.flow.DailyStudyRemindersEnabledFlow import org.hyperskill.app.notification.local.domain.interactor.NotificationInteractor import org.hyperskill.app.products.domain.interactor.ProductsInteractor -import org.hyperskill.app.profile.domain.interactor.ProfileInteractor import org.hyperskill.app.profile.domain.repository.CurrentProfileStateRepository import org.hyperskill.app.profile.presentation.ProfileActionDispatcher import org.hyperskill.app.profile.presentation.ProfileFeature.Action @@ -18,17 +17,17 @@ import org.hyperskill.app.profile.presentation.ProfileFeature.Message import org.hyperskill.app.profile.presentation.ProfileFeature.State import org.hyperskill.app.profile.presentation.ProfileReducer import org.hyperskill.app.sentry.domain.interactor.SentryInteractor +import org.hyperskill.app.step_quiz.domain.repository.SubmissionRepository import org.hyperskill.app.streaks.domain.flow.StreakFlow import org.hyperskill.app.streaks.domain.interactor.StreaksInteractor import ru.nobird.app.presentation.redux.dispatcher.wrapWithActionDispatcher import ru.nobird.app.presentation.redux.feature.Feature import ru.nobird.app.presentation.redux.feature.ReduxFeature -object ProfileFeatureBuilder { +internal object ProfileFeatureBuilder { private const val LOG_TAG = "ProfileFeature" fun build( - profileInteractor: ProfileInteractor, currentProfileStateRepository: CurrentProfileStateRepository, streaksInteractor: StreaksInteractor, productsInteractor: ProductsInteractor, @@ -38,24 +37,25 @@ object ProfileFeatureBuilder { urlPathProcessor: UrlPathProcessor, streakFlow: StreakFlow, dailyStudyRemindersEnabledFlow: DailyStudyRemindersEnabledFlow, + submissionRepository: SubmissionRepository, badgesRepository: BadgesRepository, logger: Logger, buildVariant: BuildVariant ): Feature { val profileReducer = ProfileReducer().wrapWithLogger(buildVariant, logger, LOG_TAG) val profileActionDispatcher = ProfileActionDispatcher( - ActionDispatcherOptions(), - profileInteractor, - currentProfileStateRepository, - streaksInteractor, - productsInteractor, - analyticInteractor, - sentryInteractor, - notificationInteractor, - urlPathProcessor, - streakFlow, - dailyStudyRemindersEnabledFlow, - badgesRepository + config = ActionDispatcherOptions(), + currentProfileStateRepository = currentProfileStateRepository, + streaksInteractor = streaksInteractor, + productsInteractor = productsInteractor, + analyticInteractor = analyticInteractor, + sentryInteractor = sentryInteractor, + notificationInteractor = notificationInteractor, + urlPathProcessor = urlPathProcessor, + streakFlow = streakFlow, + dailyStudyRemindersEnabledFlow = dailyStudyRemindersEnabledFlow, + solvedStepsSharedFlow = submissionRepository.solvedStepsSharedFlow, + badgesRepository = badgesRepository ) return ReduxFeature(State.Idle, profileReducer) diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/profile/presentation/ProfileActionDispatcher.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/profile/presentation/ProfileActionDispatcher.kt index 52bca2ec93..88b71fb07f 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/profile/presentation/ProfileActionDispatcher.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/profile/presentation/ProfileActionDispatcher.kt @@ -2,6 +2,7 @@ package org.hyperskill.app.profile.presentation import kotlinx.coroutines.async import kotlinx.coroutines.coroutineScope +import kotlinx.coroutines.flow.SharedFlow import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach @@ -15,7 +16,6 @@ import org.hyperskill.app.notification.local.domain.flow.DailyStudyRemindersEnab import org.hyperskill.app.notification.local.domain.interactor.NotificationInteractor import org.hyperskill.app.products.domain.interactor.ProductsInteractor import org.hyperskill.app.products.domain.model.Product -import org.hyperskill.app.profile.domain.interactor.ProfileInteractor import org.hyperskill.app.profile.domain.model.copy import org.hyperskill.app.profile.domain.repository.CurrentProfileStateRepository import org.hyperskill.app.profile.presentation.ProfileFeature.Action @@ -27,9 +27,8 @@ import org.hyperskill.app.streaks.domain.interactor.StreaksInteractor import org.hyperskill.app.streaks.domain.model.Streak import ru.nobird.app.presentation.redux.dispatcher.CoroutineActionDispatcher -class ProfileActionDispatcher( +internal class ProfileActionDispatcher( config: ActionDispatcherOptions, - profileInteractor: ProfileInteractor, private val currentProfileStateRepository: CurrentProfileStateRepository, private val streaksInteractor: StreaksInteractor, private val productsInteractor: ProductsInteractor, @@ -39,11 +38,12 @@ class ProfileActionDispatcher( private val urlPathProcessor: UrlPathProcessor, private val streakFlow: StreakFlow, dailyStudyRemindersEnabledFlow: DailyStudyRemindersEnabledFlow, + solvedStepsSharedFlow: SharedFlow, private val badgesRepository: BadgesRepository ) : CoroutineActionDispatcher(config.createConfig()) { init { - profileInteractor.solvedStepsSharedFlow + solvedStepsSharedFlow .onEach { onNewMessage(Message.StepQuizSolved) } .launchIn(actionScope) diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/profile/presentation/ProfileReducer.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/profile/presentation/ProfileReducer.kt index 72a734a5b3..00b605635d 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/profile/presentation/ProfileReducer.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/profile/presentation/ProfileReducer.kt @@ -26,7 +26,7 @@ import ru.nobird.app.presentation.redux.reducer.StateReducer private typealias ReducerResult = Pair> -class ProfileReducer : StateReducer { +internal class ProfileReducer : StateReducer { override fun reduce(state: State, message: Message): ReducerResult = when (message) { is Message.Initialize -> { From 92aa50121eef32a38c97e2d8bb739f710952875c Mon Sep 17 00:00:00 2001 From: github-actions Date: Fri, 16 Feb 2024 09:28:59 +0000 Subject: [PATCH 070/104] Bump build number --- gradle/app.versions.toml | 2 +- .../NotificationServiceExtension/Info.plist | 2 +- .../iosHyperskillApp.xcodeproj/project.pbxproj | 16 ++++++++-------- iosHyperskillApp/iosHyperskillApp/Info.plist | 2 +- .../iosHyperskillAppTests/Info.plist | 2 +- .../iosHyperskillAppUITests/Info.plist | 2 +- 6 files changed, 13 insertions(+), 13 deletions(-) diff --git a/gradle/app.versions.toml b/gradle/app.versions.toml index b0a5163842..59add5fbda 100644 --- a/gradle/app.versions.toml +++ b/gradle/app.versions.toml @@ -3,4 +3,4 @@ minSdk = '24' targetSdk = '33' compileSdk = '33' versionName = '1.50' -versionCode = '328' \ No newline at end of file +versionCode = '329' \ No newline at end of file diff --git a/iosHyperskillApp/NotificationServiceExtension/Info.plist b/iosHyperskillApp/NotificationServiceExtension/Info.plist index bd44c44e38..0f1d127df6 100644 --- a/iosHyperskillApp/NotificationServiceExtension/Info.plist +++ b/iosHyperskillApp/NotificationServiceExtension/Info.plist @@ -9,7 +9,7 @@ CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleVersion - 334 + 335 CFBundleShortVersionString 1.50 CFBundlePackageType diff --git a/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj b/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj index 0da48fc9d6..4d12532829 100644 --- a/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj +++ b/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj @@ -5037,7 +5037,7 @@ buildSettings = { CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 334; + CURRENT_PROJECT_VERSION = 335; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = 3DWS674B2M; GENERATE_INFOPLIST_FILE = NO; @@ -5058,7 +5058,7 @@ buildSettings = { CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 334; + CURRENT_PROJECT_VERSION = 335; DEVELOPMENT_TEAM = 3DWS674B2M; GENERATE_INFOPLIST_FILE = NO; INFOPLIST_FILE = iosHyperskillAppUITests/Info.plist; @@ -5079,7 +5079,7 @@ BUNDLE_LOADER = "$(TEST_HOST)"; CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 334; + CURRENT_PROJECT_VERSION = 335; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = 3DWS674B2M; INFOPLIST_FILE = iosHyperskillAppTests/Info.plist; @@ -5100,7 +5100,7 @@ BUNDLE_LOADER = "$(TEST_HOST)"; CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 334; + CURRENT_PROJECT_VERSION = 335; DEVELOPMENT_TEAM = 3DWS674B2M; INFOPLIST_FILE = iosHyperskillAppTests/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; @@ -5121,7 +5121,7 @@ CODE_SIGN_ENTITLEMENTS = NotificationServiceExtension/NotificationServiceExtension.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 334; + CURRENT_PROJECT_VERSION = 335; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = 3DWS674B2M; INFOPLIST_FILE = NotificationServiceExtension/Info.plist; @@ -5149,7 +5149,7 @@ CODE_SIGN_ENTITLEMENTS = NotificationServiceExtension/NotificationServiceExtension.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 334; + CURRENT_PROJECT_VERSION = 335; DEVELOPMENT_TEAM = 3DWS674B2M; INFOPLIST_FILE = NotificationServiceExtension/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; @@ -5294,7 +5294,7 @@ CODE_SIGN_ENTITLEMENTS = iosHyperskillApp/iosHyperskillApp.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 334; + CURRENT_PROJECT_VERSION = 335; DEVELOPMENT_ASSET_PATHS = "\"iosHyperskillApp/Preview Content\""; DEVELOPMENT_TEAM = 3DWS674B2M; ENABLE_PREVIEWS = YES; @@ -5330,7 +5330,7 @@ CODE_SIGN_ENTITLEMENTS = iosHyperskillApp/iosHyperskillApp.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 334; + CURRENT_PROJECT_VERSION = 335; DEVELOPMENT_ASSET_PATHS = "\"iosHyperskillApp/Preview Content\""; DEVELOPMENT_TEAM = 3DWS674B2M; ENABLE_PREVIEWS = YES; diff --git a/iosHyperskillApp/iosHyperskillApp/Info.plist b/iosHyperskillApp/iosHyperskillApp/Info.plist index 19a4059f74..525642f780 100644 --- a/iosHyperskillApp/iosHyperskillApp/Info.plist +++ b/iosHyperskillApp/iosHyperskillApp/Info.plist @@ -36,7 +36,7 @@ CFBundleVersion - 334 + 335 FirebaseAppDelegateProxyEnabled FirebaseMessagingAutoInitEnabled diff --git a/iosHyperskillApp/iosHyperskillAppTests/Info.plist b/iosHyperskillApp/iosHyperskillAppTests/Info.plist index e8d3eff06e..d4053cec11 100644 --- a/iosHyperskillApp/iosHyperskillAppTests/Info.plist +++ b/iosHyperskillApp/iosHyperskillAppTests/Info.plist @@ -15,6 +15,6 @@ CFBundleShortVersionString 1.50 CFBundleVersion - 334 + 335 diff --git a/iosHyperskillApp/iosHyperskillAppUITests/Info.plist b/iosHyperskillApp/iosHyperskillAppUITests/Info.plist index f8cea37aec..41174d0b64 100644 --- a/iosHyperskillApp/iosHyperskillAppUITests/Info.plist +++ b/iosHyperskillApp/iosHyperskillAppUITests/Info.plist @@ -15,6 +15,6 @@ CFBundleShortVersionString 1.50 CFBundleVersion - 334 + 335 From 5d90cbaaa59455a8b8faab2ae59fcb7eb8ff2537 Mon Sep 17 00:00:00 2001 From: Aleksandr Zhukov Date: Mon, 19 Feb 2024 16:30:25 +0400 Subject: [PATCH 071/104] Android data-mobile-hidden elements from content (#898) --- .../assets/scripts/remove_data_mobile_hidden_elements.js | 6 ++++++ .../app/android/latex/view/mapper/LatexTextMapper.kt | 4 +++- .../latex/view/model/block/DataMobileHiddenBlock.kt | 7 +++++++ 3 files changed, 16 insertions(+), 1 deletion(-) create mode 100644 androidHyperskillApp/src/main/assets/scripts/remove_data_mobile_hidden_elements.js create mode 100644 androidHyperskillApp/src/main/java/org/hyperskill/app/android/latex/view/model/block/DataMobileHiddenBlock.kt diff --git a/androidHyperskillApp/src/main/assets/scripts/remove_data_mobile_hidden_elements.js b/androidHyperskillApp/src/main/assets/scripts/remove_data_mobile_hidden_elements.js new file mode 100644 index 0000000000..494861697b --- /dev/null +++ b/androidHyperskillApp/src/main/assets/scripts/remove_data_mobile_hidden_elements.js @@ -0,0 +1,6 @@ +addEventListener('DOMContentLoaded', () => { + document.querySelectorAll('[data-mobile-hidden="true"]') + .forEach(element => { + element.parentNode.removeChild(element) + }) +}); \ No newline at end of file diff --git a/androidHyperskillApp/src/main/java/org/hyperskill/app/android/latex/view/mapper/LatexTextMapper.kt b/androidHyperskillApp/src/main/java/org/hyperskill/app/android/latex/view/mapper/LatexTextMapper.kt index f683e23e46..d5752b8060 100644 --- a/androidHyperskillApp/src/main/java/org/hyperskill/app/android/latex/view/mapper/LatexTextMapper.kt +++ b/androidHyperskillApp/src/main/java/org/hyperskill/app/android/latex/view/mapper/LatexTextMapper.kt @@ -4,6 +4,7 @@ import androidx.core.text.HtmlCompat import androidx.core.text.toSpannable import org.hyperskill.app.android.latex.view.model.LatexData import org.hyperskill.app.android.latex.view.model.block.ContentBlock +import org.hyperskill.app.android.latex.view.model.block.DataMobileHiddenBlock import org.hyperskill.app.android.latex.view.model.block.HighlightScriptBlock import org.hyperskill.app.android.latex.view.model.block.HorizontalScrollBlock import org.hyperskill.app.android.latex.view.model.block.KotlinRunnableSamplesScriptBlock @@ -20,7 +21,8 @@ class LatexTextMapper(networkEndpointConfigInfo: NetworkEndpointConfigInfo) { HighlightScriptBlock(), KotlinRunnableSamplesScriptBlock(), LatexScriptBlock(), - WebScriptBlock() + WebScriptBlock(), + DataMobileHiddenBlock ) private val regularBlocks = diff --git a/androidHyperskillApp/src/main/java/org/hyperskill/app/android/latex/view/model/block/DataMobileHiddenBlock.kt b/androidHyperskillApp/src/main/java/org/hyperskill/app/android/latex/view/model/block/DataMobileHiddenBlock.kt new file mode 100644 index 0000000000..500d760cdc --- /dev/null +++ b/androidHyperskillApp/src/main/java/org/hyperskill/app/android/latex/view/model/block/DataMobileHiddenBlock.kt @@ -0,0 +1,7 @@ +package org.hyperskill.app.android.latex.view.model.block + +object DataMobileHiddenBlock : ContentBlock { + override val header: String = """ + + """.trimIndent() +} \ No newline at end of file From da8637726a766af1652d34bb9d4beb7478f21379 Mon Sep 17 00:00:00 2001 From: github-actions Date: Mon, 19 Feb 2024 12:31:02 +0000 Subject: [PATCH 072/104] Android: Bump build number --- gradle/app.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/app.versions.toml b/gradle/app.versions.toml index 59add5fbda..7749c00de6 100644 --- a/gradle/app.versions.toml +++ b/gradle/app.versions.toml @@ -3,4 +3,4 @@ minSdk = '24' targetSdk = '33' compileSdk = '33' versionName = '1.50' -versionCode = '329' \ No newline at end of file +versionCode = '330' \ No newline at end of file From 5448975a61c05c78072cd8346fffc8645de4e17d Mon Sep 17 00:00:00 2001 From: Ivan Magda Date: Tue, 20 Feb 2024 09:34:38 +0700 Subject: [PATCH 073/104] Shared: Experiment with short theory (#908) ^ALTAPPS-1156 --- .../app/profile/domain/model/FeatureKeys.kt | 3 +- .../app/profile/domain/model/FeaturesMap.kt | 5 +- .../app/step/injection/StepComponentImpl.kt | 6 +- .../step/injection/StepDataComponentImpl.kt | 2 +- .../app/step/injection/StepFeatureBuilder.kt | 18 ++-- .../step/presentation/StepActionDispatcher.kt | 94 ++++++++++++++----- .../app/step/presentation/StepFeature.kt | 27 +++--- .../app/step/presentation/StepReducer.kt | 13 +-- 8 files changed, 114 insertions(+), 54 deletions(-) diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/profile/domain/model/FeatureKeys.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/profile/domain/model/FeatureKeys.kt index 89b174dd1f..038a2af9af 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/profile/domain/model/FeatureKeys.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/profile/domain/model/FeatureKeys.kt @@ -1,6 +1,6 @@ package org.hyperskill.app.profile.domain.model -object FeatureKeys { +internal object FeatureKeys { const val RECOMMENDATIONS_JAVA_PROJECTS = "recommendations.java_projects" const val RECOMMENDATIONS_KOTLIN_PROJECTS = "recommendations.kotlin_projects" const val RECOMMENDATIONS_PYTHON_PROJECTS = "recommendations.python_projects" @@ -8,4 +8,5 @@ object FeatureKeys { const val LEARNING_PATH_DIVIDED_TRACK_TOPICS = "learning_path.divided_track_topics" const val MOBILE_LEADERBOARDS = "mobile_leaderboards" const val MOBILE_INTERVIEW_PREPARATION = "mobile.interview_preparation" + const val MOBILE_SHORT_THEORY = "mobile.short_theory" } \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/profile/domain/model/FeaturesMap.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/profile/domain/model/FeaturesMap.kt index 24ec705f55..da22734c0b 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/profile/domain/model/FeaturesMap.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/profile/domain/model/FeaturesMap.kt @@ -21,4 +21,7 @@ val FeaturesMap.isMobileLeaderboardsEnabled: Boolean get() = get(FeatureKeys.MOBILE_LEADERBOARDS) ?: false val FeaturesMap.isMobileInterviewPreparationEnabled: Boolean - get() = get(FeatureKeys.MOBILE_INTERVIEW_PREPARATION) ?: false \ No newline at end of file + get() = get(FeatureKeys.MOBILE_INTERVIEW_PREPARATION) ?: false + +val FeaturesMap.isMobileShortTheoryEnabled: Boolean + get() = get(FeatureKeys.MOBILE_SHORT_THEORY) ?: false \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/step/injection/StepComponentImpl.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/step/injection/StepComponentImpl.kt index cccebdc016..a9d98fd605 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/step/injection/StepComponentImpl.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/step/injection/StepComponentImpl.kt @@ -7,7 +7,10 @@ import org.hyperskill.app.step.view.mapper.CommentThreadTitleMapper import org.hyperskill.app.step_completion.injection.StepCompletionComponent import ru.nobird.app.presentation.redux.feature.Feature -class StepComponentImpl(private val appGraph: AppGraph, private val stepRoute: StepRoute) : StepComponent { +internal class StepComponentImpl( + private val appGraph: AppGraph, + private val stepRoute: StepRoute +) : StepComponent { override val commentThreadTitleMapper: CommentThreadTitleMapper get() = CommentThreadTitleMapper(appGraph.commonComponent.resourceProvider) @@ -19,6 +22,7 @@ class StepComponentImpl(private val appGraph: AppGraph, private val stepRoute: S stepRoute, appGraph.buildStepDataComponent().stepInteractor, appGraph.stateRepositoriesComponent.nextLearningActivityStateRepository, + appGraph.profileDataComponent.currentProfileStateRepository, appGraph.analyticComponent.analyticInteractor, appGraph.sentryComponent.sentryInteractor, stepCompletionComponent.stepCompletionReducer, diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/step/injection/StepDataComponentImpl.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/step/injection/StepDataComponentImpl.kt index 5c87c08be7..9cbfa8e90f 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/step/injection/StepDataComponentImpl.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/step/injection/StepDataComponentImpl.kt @@ -7,7 +7,7 @@ import org.hyperskill.app.step.domain.interactor.StepInteractor import org.hyperskill.app.step.domain.repository.StepRepository import org.hyperskill.app.step.remote.StepRemoteDataSourceImpl -class StepDataComponentImpl(appGraph: AppGraph) : StepDataComponent { +internal class StepDataComponentImpl(appGraph: AppGraph) : StepDataComponent { private val stepRemoteDataSource: StepRemoteDataSource = StepRemoteDataSourceImpl(appGraph.networkComponent.authorizedHttpClient) diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/step/injection/StepFeatureBuilder.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/step/injection/StepFeatureBuilder.kt index 5467686544..ba9aa668e9 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/step/injection/StepFeatureBuilder.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/step/injection/StepFeatureBuilder.kt @@ -6,11 +6,13 @@ import org.hyperskill.app.core.domain.BuildVariant import org.hyperskill.app.core.presentation.ActionDispatcherOptions import org.hyperskill.app.learning_activities.domain.repository.NextLearningActivityStateRepository import org.hyperskill.app.logging.presentation.wrapWithLogger +import org.hyperskill.app.profile.domain.repository.CurrentProfileStateRepository import org.hyperskill.app.sentry.domain.interactor.SentryInteractor import org.hyperskill.app.step.domain.interactor.StepInteractor import org.hyperskill.app.step.domain.model.StepRoute import org.hyperskill.app.step.presentation.StepActionDispatcher import org.hyperskill.app.step.presentation.StepFeature.Action +import org.hyperskill.app.step.presentation.StepFeature.InternalAction import org.hyperskill.app.step.presentation.StepFeature.Message import org.hyperskill.app.step.presentation.StepFeature.State import org.hyperskill.app.step.presentation.StepReducer @@ -22,13 +24,14 @@ import ru.nobird.app.presentation.redux.dispatcher.wrapWithActionDispatcher import ru.nobird.app.presentation.redux.feature.Feature import ru.nobird.app.presentation.redux.feature.ReduxFeature -object StepFeatureBuilder { +internal object StepFeatureBuilder { private const val LOG_TAG = "StepFeature" fun build( stepRoute: StepRoute, stepInteractor: StepInteractor, nextLearningActivityStateRepository: NextLearningActivityStateRepository, + currentProfileStateRepository: CurrentProfileStateRepository, analyticInteractor: AnalyticInteractor, sentryInteractor: SentryInteractor, stepCompletionReducer: StepCompletionReducer, @@ -38,18 +41,19 @@ object StepFeatureBuilder { ): Feature { val stepReducer = StepReducer(stepRoute, stepCompletionReducer).wrapWithLogger(buildVariant, logger, LOG_TAG) val stepActionDispatcher = StepActionDispatcher( - ActionDispatcherOptions(), - stepInteractor, - nextLearningActivityStateRepository, - analyticInteractor, - sentryInteractor + config = ActionDispatcherOptions(), + stepInteractor = stepInteractor, + nextLearningActivityStateRepository = nextLearningActivityStateRepository, + currentProfileStateRepository = currentProfileStateRepository, + analyticInteractor = analyticInteractor, + sentryInteractor = sentryInteractor ) return ReduxFeature(State.Idle, stepReducer) .wrapWithActionDispatcher(stepActionDispatcher) .wrapWithActionDispatcher( stepCompletionActionDispatcher.transform( - transformAction = { it.safeCast()?.action }, + transformAction = { it.safeCast()?.action }, transformMessage = Message::StepCompletionMessage ) ) diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/step/presentation/StepActionDispatcher.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/step/presentation/StepActionDispatcher.kt index 92ec4449e9..175d58b8be 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/step/presentation/StepActionDispatcher.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/step/presentation/StepActionDispatcher.kt @@ -4,52 +4,96 @@ import org.hyperskill.app.analytic.domain.interactor.AnalyticInteractor import org.hyperskill.app.core.domain.DataSourceType import org.hyperskill.app.core.presentation.ActionDispatcherOptions import org.hyperskill.app.learning_activities.domain.repository.NextLearningActivityStateRepository +import org.hyperskill.app.profile.domain.model.isMobileShortTheoryEnabled +import org.hyperskill.app.profile.domain.repository.CurrentProfileStateRepository import org.hyperskill.app.sentry.domain.interactor.SentryInteractor import org.hyperskill.app.sentry.domain.model.transaction.HyperskillSentryTransactionBuilder +import org.hyperskill.app.sentry.domain.withTransaction import org.hyperskill.app.step.domain.interactor.StepInteractor +import org.hyperskill.app.step.domain.model.Step import org.hyperskill.app.step.presentation.StepFeature.Action +import org.hyperskill.app.step.presentation.StepFeature.InternalAction import org.hyperskill.app.step.presentation.StepFeature.Message import ru.nobird.app.presentation.redux.dispatcher.CoroutineActionDispatcher -class StepActionDispatcher( +internal class StepActionDispatcher( config: ActionDispatcherOptions, private val stepInteractor: StepInteractor, private val nextLearningActivityStateRepository: NextLearningActivityStateRepository, + private val currentProfileStateRepository: CurrentProfileStateRepository, private val analyticInteractor: AnalyticInteractor, private val sentryInteractor: SentryInteractor ) : CoroutineActionDispatcher(config.createConfig()) { override suspend fun doSuspendableAction(action: Action) { when (action) { - is Action.FetchStep -> { - val sentryTransaction = HyperskillSentryTransactionBuilder.buildStepScreenRemoteDataLoading() - sentryInteractor.startTransaction(sentryTransaction) - - stepInteractor - .getStep(action.stepRoute.stepId) - .fold( - onSuccess = { - sentryInteractor.finishTransaction(sentryTransaction) - onNewMessage(Message.StepLoaded.Success(it)) - }, - onFailure = { - sentryInteractor.finishTransaction(sentryTransaction, throwable = it) - onNewMessage(Message.StepLoaded.Error) - } - ) - } - is Action.ViewStep -> { + is InternalAction.FetchStep -> + handleFetchStepAction(action, ::onNewMessage) + is InternalAction.ViewStep -> stepInteractor.viewStep(action.stepId, action.stepContext) - } - is Action.UpdateNextLearningActivityState -> { + is InternalAction.UpdateNextLearningActivityState -> handleUpdateNextLearningActivityStateAction(action) - } - is Action.LogAnalyticEvent -> + is InternalAction.LogAnalyticEvent -> analyticInteractor.logEvent(action.analyticEvent) - else -> {} + else -> { + // no op + } + } + } + + private suspend fun handleFetchStepAction( + action: InternalAction.FetchStep, + onNewMessage: (Message) -> Unit + ) { + sentryInteractor.withTransaction( + HyperskillSentryTransactionBuilder.buildStepScreenRemoteDataLoading(), + onError = { Message.StepLoaded.Error } + ) { + val step = stepInteractor + .getStep(action.stepRoute.stepId) + .getOrThrow() + .let { applyMobileShortTheoryFeature(it) } + Message.StepLoaded.Success(step) + }.let(onNewMessage) + } + + private suspend fun applyMobileShortTheoryFeature(step: Step): Step { + val isMobileShortTheoryEnabled = currentProfileStateRepository + .getState(forceUpdate = false) + .getOrNull() + ?.features + ?.isMobileShortTheoryEnabled + ?: false + + return if (isMobileShortTheoryEnabled && step.id == 38627L) { + step.copy( + block = step.block.copy( + text = """ +

Ever wondered why Java's logo is a steaming cup of coffee? Just as coffee fuels our day, Java powers the tech world with its robust and versatile features! So, grab your cup of coffee and join us on this exciting journey into the world of Java!

+
What is Java
+

Java language was designed by James Gosling in 1995 to be simple and powerful. It borrows syntax from C and C++, and complements it with automatic memory management, and other powerful features. Java's core principle is "Write Once, Run Anywhere" (WORA), which means that any Java program is platform-independent and can run on any operating system without modifications.

+
Where is Java Applied
+

Waking up you immediately interact with an application built with Java — your phone alarm. When you work or develop your pet projects, Java forms the backbone of development tools like IntelliJ IDEA. Even when relaxing with Netflix, Spotify, or Minecraft, you rely on Java power. Java is like a silent friend, aiding us and making our lives easier in numerous ways, from the moment we wake up till we call it a day. What amazing and helpful Java application are you going to create?

+
A sample of Java
+

Let's start with the classic "Hello, World!" program, a friendly greeting from your computer:

+
public class HelloWorld {
+                public static void main(String[] args) {
+                    System.out.println("Hello, World!");
+                }
+            }
+

This program simply prints the phrase "Hello, World!" to the console. Don't worry if it looks a bit cryptic now. We'll dive deeper into its logic during the practice part of this topic.

+
Conclusion
+

Java is a high-level object-oriented programming language. Its clear syntax, platform independence, and automatic memory management contribute to its popularity. Become a part of the vast Java community by practicing the language basics now. Don't hesitate to experiment, ask for help if you're stuck, and embrace mistakes as they fuel your learning!

+ """.trimIndent() + ) + ) + } else { + step } } - private suspend fun handleUpdateNextLearningActivityStateAction(action: Action.UpdateNextLearningActivityState) { + private suspend fun handleUpdateNextLearningActivityStateAction( + action: InternalAction.UpdateNextLearningActivityState + ) { val currentNextLearningActivityState = nextLearningActivityStateRepository .getStateWithSource(forceUpdate = false) .getOrElse { return } diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/step/presentation/StepFeature.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/step/presentation/StepFeature.kt index 1f44407482..bc58de171a 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/step/presentation/StepFeature.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/step/presentation/StepFeature.kt @@ -6,7 +6,7 @@ import org.hyperskill.app.step.domain.model.StepContext import org.hyperskill.app.step.domain.model.StepRoute import org.hyperskill.app.step_completion.presentation.StepCompletionFeature -interface StepFeature { +object StepFeature { sealed interface State { object Idle : State object Loading : State @@ -38,21 +38,24 @@ interface StepFeature { } sealed interface Action { - data class FetchStep(val stepRoute: StepRoute) : Action - data class LogAnalyticEvent(val analyticEvent: AnalyticEvent) : Action - data class ViewStep(val stepId: Long, val stepContext: StepContext) : Action - - data class UpdateNextLearningActivityState(val step: Step) : Action - - /** - * Action Wrappers - */ - data class StepCompletionAction(val action: StepCompletionFeature.Action) : Action - sealed interface ViewAction : Action { data class StepCompletionViewAction( val viewAction: StepCompletionFeature.Action.ViewAction ) : ViewAction } } + + internal sealed interface InternalAction : Action { + data class FetchStep(val stepRoute: StepRoute) : InternalAction + data class ViewStep(val stepId: Long, val stepContext: StepContext) : InternalAction + + data class UpdateNextLearningActivityState(val step: Step) : InternalAction + + data class LogAnalyticEvent(val analyticEvent: AnalyticEvent) : InternalAction + + /** + * Action Wrappers + */ + data class StepCompletionAction(val action: StepCompletionFeature.Action) : InternalAction + } } \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/step/presentation/StepReducer.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/step/presentation/StepReducer.kt index 3751a5b7bf..7afb6ccdae 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/step/presentation/StepReducer.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/step/presentation/StepReducer.kt @@ -3,13 +3,14 @@ package org.hyperskill.app.step.presentation import org.hyperskill.app.step.domain.analytic.StepViewedHyperskillAnalyticEvent import org.hyperskill.app.step.domain.model.StepRoute import org.hyperskill.app.step.presentation.StepFeature.Action +import org.hyperskill.app.step.presentation.StepFeature.InternalAction import org.hyperskill.app.step.presentation.StepFeature.Message import org.hyperskill.app.step.presentation.StepFeature.State import org.hyperskill.app.step_completion.presentation.StepCompletionFeature import org.hyperskill.app.step_completion.presentation.StepCompletionReducer import ru.nobird.app.presentation.redux.reducer.StateReducer -class StepReducer( +internal class StepReducer( private val stepRoute: StepRoute, private val stepCompletionReducer: StepCompletionReducer ) : StateReducer { @@ -20,8 +21,8 @@ class StepReducer( (message.forceUpdate && (state is State.Data || state is State.Error)) ) { State.Loading to setOf( - Action.FetchStep(stepRoute), - Action.ViewStep(stepRoute.stepId, stepRoute.stepContext) + InternalAction.FetchStep(stepRoute), + InternalAction.ViewStep(stepRoute.stepId, stepRoute.stepContext) ) } else { null @@ -42,7 +43,7 @@ class StepReducer( false }, stepCompletionState = StepCompletionFeature.createState(message.step, stepRoute) - ) to setOf(Action.UpdateNextLearningActivityState(message.step)) + ) to setOf(InternalAction.UpdateNextLearningActivityState(message.step)) } is Message.StepLoaded.Error -> State.Error to emptySet() @@ -52,7 +53,7 @@ class StepReducer( null } else { state to setOf( - Action.LogAnalyticEvent( + InternalAction.LogAnalyticEvent( StepViewedHyperskillAnalyticEvent( stepRoute.analyticRoute ) @@ -80,7 +81,7 @@ class StepReducer( if (it is StepCompletionFeature.Action.ViewAction) { Action.ViewAction.StepCompletionViewAction(it) } else { - Action.StepCompletionAction(it) + InternalAction.StepCompletionAction(it) } } .toSet() From c8261cd9f9a2a03e2542f1ca558c9bb1cd3f1002 Mon Sep 17 00:00:00 2001 From: github-actions Date: Tue, 20 Feb 2024 02:35:15 +0000 Subject: [PATCH 074/104] Bump build number --- gradle/app.versions.toml | 2 +- .../NotificationServiceExtension/Info.plist | 2 +- .../iosHyperskillApp.xcodeproj/project.pbxproj | 16 ++++++++-------- iosHyperskillApp/iosHyperskillApp/Info.plist | 2 +- .../iosHyperskillAppTests/Info.plist | 2 +- .../iosHyperskillAppUITests/Info.plist | 2 +- 6 files changed, 13 insertions(+), 13 deletions(-) diff --git a/gradle/app.versions.toml b/gradle/app.versions.toml index 7749c00de6..633f563c94 100644 --- a/gradle/app.versions.toml +++ b/gradle/app.versions.toml @@ -3,4 +3,4 @@ minSdk = '24' targetSdk = '33' compileSdk = '33' versionName = '1.50' -versionCode = '330' \ No newline at end of file +versionCode = '331' \ No newline at end of file diff --git a/iosHyperskillApp/NotificationServiceExtension/Info.plist b/iosHyperskillApp/NotificationServiceExtension/Info.plist index 0f1d127df6..42c5c47808 100644 --- a/iosHyperskillApp/NotificationServiceExtension/Info.plist +++ b/iosHyperskillApp/NotificationServiceExtension/Info.plist @@ -9,7 +9,7 @@ CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleVersion - 335 + 336 CFBundleShortVersionString 1.50 CFBundlePackageType diff --git a/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj b/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj index 4d12532829..f2a3846388 100644 --- a/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj +++ b/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj @@ -5037,7 +5037,7 @@ buildSettings = { CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 335; + CURRENT_PROJECT_VERSION = 336; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = 3DWS674B2M; GENERATE_INFOPLIST_FILE = NO; @@ -5058,7 +5058,7 @@ buildSettings = { CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 335; + CURRENT_PROJECT_VERSION = 336; DEVELOPMENT_TEAM = 3DWS674B2M; GENERATE_INFOPLIST_FILE = NO; INFOPLIST_FILE = iosHyperskillAppUITests/Info.plist; @@ -5079,7 +5079,7 @@ BUNDLE_LOADER = "$(TEST_HOST)"; CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 335; + CURRENT_PROJECT_VERSION = 336; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = 3DWS674B2M; INFOPLIST_FILE = iosHyperskillAppTests/Info.plist; @@ -5100,7 +5100,7 @@ BUNDLE_LOADER = "$(TEST_HOST)"; CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 335; + CURRENT_PROJECT_VERSION = 336; DEVELOPMENT_TEAM = 3DWS674B2M; INFOPLIST_FILE = iosHyperskillAppTests/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; @@ -5121,7 +5121,7 @@ CODE_SIGN_ENTITLEMENTS = NotificationServiceExtension/NotificationServiceExtension.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 335; + CURRENT_PROJECT_VERSION = 336; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = 3DWS674B2M; INFOPLIST_FILE = NotificationServiceExtension/Info.plist; @@ -5149,7 +5149,7 @@ CODE_SIGN_ENTITLEMENTS = NotificationServiceExtension/NotificationServiceExtension.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 335; + CURRENT_PROJECT_VERSION = 336; DEVELOPMENT_TEAM = 3DWS674B2M; INFOPLIST_FILE = NotificationServiceExtension/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; @@ -5294,7 +5294,7 @@ CODE_SIGN_ENTITLEMENTS = iosHyperskillApp/iosHyperskillApp.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 335; + CURRENT_PROJECT_VERSION = 336; DEVELOPMENT_ASSET_PATHS = "\"iosHyperskillApp/Preview Content\""; DEVELOPMENT_TEAM = 3DWS674B2M; ENABLE_PREVIEWS = YES; @@ -5330,7 +5330,7 @@ CODE_SIGN_ENTITLEMENTS = iosHyperskillApp/iosHyperskillApp.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 335; + CURRENT_PROJECT_VERSION = 336; DEVELOPMENT_ASSET_PATHS = "\"iosHyperskillApp/Preview Content\""; DEVELOPMENT_TEAM = 3DWS674B2M; ENABLE_PREVIEWS = YES; diff --git a/iosHyperskillApp/iosHyperskillApp/Info.plist b/iosHyperskillApp/iosHyperskillApp/Info.plist index 525642f780..b0e47acb5d 100644 --- a/iosHyperskillApp/iosHyperskillApp/Info.plist +++ b/iosHyperskillApp/iosHyperskillApp/Info.plist @@ -36,7 +36,7 @@ CFBundleVersion - 335 + 336 FirebaseAppDelegateProxyEnabled FirebaseMessagingAutoInitEnabled diff --git a/iosHyperskillApp/iosHyperskillAppTests/Info.plist b/iosHyperskillApp/iosHyperskillAppTests/Info.plist index d4053cec11..7a5206537e 100644 --- a/iosHyperskillApp/iosHyperskillAppTests/Info.plist +++ b/iosHyperskillApp/iosHyperskillAppTests/Info.plist @@ -15,6 +15,6 @@ CFBundleShortVersionString 1.50 CFBundleVersion - 335 + 336 diff --git a/iosHyperskillApp/iosHyperskillAppUITests/Info.plist b/iosHyperskillApp/iosHyperskillAppUITests/Info.plist index 41174d0b64..ce7193dd17 100644 --- a/iosHyperskillApp/iosHyperskillAppUITests/Info.plist +++ b/iosHyperskillApp/iosHyperskillAppUITests/Info.plist @@ -15,6 +15,6 @@ CFBundleShortVersionString 1.50 CFBundleVersion - 335 + 336 From d4d94a18046ee3d41cea40ec6717885a2c923ecc Mon Sep 17 00:00:00 2001 From: Aleksandr Zhukov Date: Tue, 20 Feb 2024 14:32:11 +0400 Subject: [PATCH 075/104] Android request google play review (#910) --- androidHyperskillApp/build.gradle.kts | 4 + .../ui/widget/compose/HyperskillButton.kt | 45 ++++- .../dialog/ProfileSettingsDialogFragment.kt | 7 + .../dialog/RequestReviewDialogFragment.kt | 135 ++++++++++++++ .../request_review/ui/RequestReviewDialog.kt | 172 ++++++++++++++++++ .../ui/RequestReviewPreviewDefaults.kt | 23 +++ .../step/view/delegate/StepDelegate.kt | 8 +- .../res/layout/fragment_profile_settings.xml | 23 +-- gradle/libs.versions.toml | 6 +- .../Sources/Models/Constants/Strings.swift | 1 - shared/build.gradle.kts | 3 +- .../core/injection/CommonAndroidAppGraph.kt | 5 + .../injection/CommonAndroidAppGraphImpl.kt | 9 + .../PlatformRequestReviewComponent.kt | 7 + .../PlatformRequestReviewComponentImpl.kt | 21 +++ .../RequestReviewModalViewModel.kt | 27 +++ .../hyperskill/HyperskillAnalyticTarget.kt | 1 + .../presentation/ProfileSettingsFeature.kt | 2 + .../presentation/ProfileSettingsReducer.kt | 8 + .../RequestReviewModalViewStateMapper.kt | 27 +-- .../injection/StepCompletionComponentImpl.kt | 3 +- .../StepCompletionActionDispatcher.kt | 9 +- .../commonMain/resources/MR/base/strings.xml | 9 +- .../commonMain/resources/MR/colors/colors.xml | 5 + 24 files changed, 506 insertions(+), 54 deletions(-) create mode 100644 androidHyperskillApp/src/main/java/org/hyperskill/app/android/request_review/dialog/RequestReviewDialogFragment.kt create mode 100644 androidHyperskillApp/src/main/java/org/hyperskill/app/android/request_review/ui/RequestReviewDialog.kt create mode 100644 androidHyperskillApp/src/main/java/org/hyperskill/app/android/request_review/ui/RequestReviewPreviewDefaults.kt create mode 100644 shared/src/androidMain/kotlin/org/hyperskill/app/request_review/injection/PlatformRequestReviewComponent.kt create mode 100644 shared/src/androidMain/kotlin/org/hyperskill/app/request_review/injection/PlatformRequestReviewComponentImpl.kt create mode 100644 shared/src/androidMain/kotlin/org/hyperskill/app/request_review/presentation/RequestReviewModalViewModel.kt diff --git a/androidHyperskillApp/build.gradle.kts b/androidHyperskillApp/build.gradle.kts index cbe0a886cc..ef699dce86 100644 --- a/androidHyperskillApp/build.gradle.kts +++ b/androidHyperskillApp/build.gradle.kts @@ -29,6 +29,8 @@ dependencies { implementation(libs.kotlin.coroutines.core) implementation(libs.kotlin.coroutines.android) + implementation(libs.kermit) + implementation(libs.kit.view.ui) implementation(libs.kit.view.injection) implementation(libs.kit.view.redux) @@ -41,6 +43,8 @@ dependencies { implementation(platform(libs.firebase.bom)) implementation(libs.firebase.messaging) + implementation(libs.google.play.review) + implementation(libs.viewbinding) implementation(libs.kit.ui.adapters) diff --git a/androidHyperskillApp/src/main/java/org/hyperskill/app/android/core/view/ui/widget/compose/HyperskillButton.kt b/androidHyperskillApp/src/main/java/org/hyperskill/app/android/core/view/ui/widget/compose/HyperskillButton.kt index 2675ee2929..d14510f12c 100644 --- a/androidHyperskillApp/src/main/java/org/hyperskill/app/android/core/view/ui/widget/compose/HyperskillButton.kt +++ b/androidHyperskillApp/src/main/java/org/hyperskill/app/android/core/view/ui/widget/compose/HyperskillButton.kt @@ -11,6 +11,7 @@ import androidx.compose.material.ButtonElevation import androidx.compose.material.MaterialTheme import androidx.compose.material.ProvideTextStyle import androidx.compose.material.TextButton +import androidx.compose.material.contentColorFor import androidx.compose.runtime.Composable import androidx.compose.runtime.remember import androidx.compose.ui.Modifier @@ -38,10 +39,12 @@ object HyperskillButtonDefaults { @Composable fun buttonColors( - backgroundColor: Color = colorResource(id = R.color.button_primary) + backgroundColor: Color = colorResource(id = R.color.button_primary), + contentColor: Color = contentColorFor(backgroundColor) ): ButtonColors = ButtonDefaults.buttonColors( - backgroundColor = backgroundColor + backgroundColor = backgroundColor, + contentColor = contentColor ) @Composable @@ -76,6 +79,44 @@ fun HyperskillButton( ) } +@Composable +fun HyperskillOutlinedButton( + onClick: () -> Unit, + modifier: Modifier = Modifier, + enabled: Boolean = true, + interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, + elevation: ButtonElevation? = null, + shape: Shape = MaterialTheme.shapes.small, + border: BorderStroke? = BorderStroke(1.dp, colorResource(id = R.color.button_tertiary)), + colors: ButtonColors = HyperskillButtonDefaults.buttonColors( + backgroundColor = Color.Transparent, + contentColor = colorResource(id = R.color.button_tertiary) + ), + contentPadding: PaddingValues = HyperskillButtonDefaults.ContentPadding, + content: @Composable RowScope.() -> Unit +) { + HyperskillButton( + onClick = onClick, + modifier = modifier, + enabled = enabled, + interactionSource = interactionSource, + elevation = elevation, + shape = shape, + border = border, + colors = colors, + contentPadding = contentPadding, + content = { + ProvideTextStyle( + value = MaterialTheme.typography.button.copy( + color = colorResource(id = R.color.button_tertiary) + ) + ) { + content() + } + } + ) +} + @Composable fun HyperskillTextButton( onClick: () -> Unit, diff --git a/androidHyperskillApp/src/main/java/org/hyperskill/app/android/profile_settings/view/dialog/ProfileSettingsDialogFragment.kt b/androidHyperskillApp/src/main/java/org/hyperskill/app/android/profile_settings/view/dialog/ProfileSettingsDialogFragment.kt index d3a18551ab..0e4bdcae5f 100644 --- a/androidHyperskillApp/src/main/java/org/hyperskill/app/android/profile_settings/view/dialog/ProfileSettingsDialogFragment.kt +++ b/androidHyperskillApp/src/main/java/org/hyperskill/app/android/profile_settings/view/dialog/ProfileSettingsDialogFragment.kt @@ -115,6 +115,13 @@ class ProfileSettingsDialogFragment : profileSettingsViewModel.onNewMessage(ProfileSettingsFeature.Message.ClickedSendFeedback) } + viewBinding.settingsRateAppButton.setOnClickListener { + profileSettingsViewModel.onNewMessage(ProfileSettingsFeature.Message.ClickedRateUsInPlayStoreEventMessage) + requireContext().openUrl( + getString(org.hyperskill.app.R.string.settings_rate_in_google_play_url) + ) + } + val userAgentInfo = HyperskillApp.graph().commonComponent.userAgentInfo viewBinding.settingsVersionTextView.text = "${userAgentInfo.versionName} (${userAgentInfo.versionCode})" diff --git a/androidHyperskillApp/src/main/java/org/hyperskill/app/android/request_review/dialog/RequestReviewDialogFragment.kt b/androidHyperskillApp/src/main/java/org/hyperskill/app/android/request_review/dialog/RequestReviewDialogFragment.kt new file mode 100644 index 0000000000..f468aff7a7 --- /dev/null +++ b/androidHyperskillApp/src/main/java/org/hyperskill/app/android/request_review/dialog/RequestReviewDialogFragment.kt @@ -0,0 +1,135 @@ +package org.hyperskill.app.android.request_review.dialog + +import android.app.Dialog +import android.content.DialogInterface +import android.net.Uri +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.browser.customtabs.CustomTabsIntent +import androidx.compose.ui.platform.ComposeView +import androidx.compose.ui.platform.ViewCompositionStrategy +import androidx.fragment.app.viewModels +import androidx.lifecycle.ViewModelProvider +import androidx.lifecycle.lifecycleScope +import co.touchlab.kermit.Logger +import com.google.android.material.bottomsheet.BottomSheetBehavior +import com.google.android.material.bottomsheet.BottomSheetDialog +import com.google.android.material.bottomsheet.BottomSheetDialogFragment +import com.google.android.play.core.ktx.launchReview +import com.google.android.play.core.ktx.requestReview +import com.google.android.play.core.review.ReviewException +import com.google.android.play.core.review.ReviewManagerFactory +import com.google.android.play.core.review.model.ReviewErrorCode +import kotlinx.coroutines.launch +import org.hyperskill.app.android.HyperskillApp +import org.hyperskill.app.android.R +import org.hyperskill.app.android.core.extensions.argument +import org.hyperskill.app.android.core.extensions.setHyperskillColors +import org.hyperskill.app.android.core.view.ui.widget.compose.HyperskillTheme +import org.hyperskill.app.android.request_review.ui.RequestReviewDialog +import org.hyperskill.app.core.view.handleActions +import org.hyperskill.app.request_review.modal.presentation.RequestReviewModalFeature +import org.hyperskill.app.request_review.presentation.RequestReviewModalViewModel +import org.hyperskill.app.step.domain.model.StepRoute + +class RequestReviewDialogFragment : BottomSheetDialogFragment() { + + companion object { + const val TAG = "RequestUserReviewDialogFragment" + + fun newInstance(stepRoute: StepRoute): RequestReviewDialogFragment = + RequestReviewDialogFragment().apply { + this.stepRoute = stepRoute + } + } + + private var stepRoute: StepRoute by argument(StepRoute.serializer()) + + private var viewModelFactory: ViewModelProvider.Factory? = null + private val requestUserReviewViewModal: RequestReviewModalViewModel by viewModels { + requireNotNull(viewModelFactory) + } + + private val logger: Logger by lazy { + HyperskillApp.graph().loggerComponent.logger.withTag(TAG) + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + injectComponent() + setStyle(STYLE_NORMAL, R.style.TopCornersRoundedBottomSheetDialog) + } + + private fun injectComponent() { + val requestReviewComponent = HyperskillApp.graph().buildPlatformRequestReviewComponent(stepRoute) + viewModelFactory = requestReviewComponent.reduxViewModelFactory + requestUserReviewViewModal.handleActions(this, onAction = ::onAction) + } + + override fun onCreateDialog(savedInstanceState: Bundle?): Dialog = + BottomSheetDialog(requireContext(), theme).also { dialog -> + dialog.setOnShowListener { + dialog.behavior.state = BottomSheetBehavior.STATE_EXPANDED + if (savedInstanceState == null) { + requestUserReviewViewModal.onShownEvent() + } + } + } + + override fun onDismiss(dialog: DialogInterface) { + super.onDismiss(dialog) + requestUserReviewViewModal.onHiddenEvent() + } + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View = + ComposeView(requireContext()).apply { + setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnLifecycleDestroyed(viewLifecycleOwner)) + setContent { + HyperskillTheme { + RequestReviewDialog(viewModel = requestUserReviewViewModal) + } + } + } + + private fun onAction(action: RequestReviewModalFeature.Action.ViewAction) { + when (action) { + RequestReviewModalFeature.Action.ViewAction.Dismiss -> dismiss() + RequestReviewModalFeature.Action.ViewAction.RequestUserReview -> { + val manager = ReviewManagerFactory.create(requireContext()) + lifecycleScope.launch { + val reviewInfo = try { + manager.requestReview() + } catch (e: ReviewException) { + logger.e(e) { + val errorDescription = when (e.errorCode) { + ReviewErrorCode.NO_ERROR -> "No error" + ReviewErrorCode.PLAY_STORE_NOT_FOUND -> "Play store not found" + ReviewErrorCode.INTERNAL_ERROR -> "Internal error" + ReviewErrorCode.INVALID_REQUEST -> " Invalid request" + else -> "" + } + "Failed to launch app review. $errorDescription" + } + dismiss() + return@launch + } + manager.launchReview(requireActivity(), reviewInfo) + dismiss() + } + } + is RequestReviewModalFeature.Action.ViewAction.SubmitSupportRequest -> { + val intent = CustomTabsIntent.Builder() + .setHyperskillColors(requireContext()) + .build() + intent.launchUrl(requireActivity(), Uri.parse(action.url)) + dismiss() + } + } + } +} \ No newline at end of file diff --git a/androidHyperskillApp/src/main/java/org/hyperskill/app/android/request_review/ui/RequestReviewDialog.kt b/androidHyperskillApp/src/main/java/org/hyperskill/app/android/request_review/ui/RequestReviewDialog.kt new file mode 100644 index 0000000000..c826fc7ae8 --- /dev/null +++ b/androidHyperskillApp/src/main/java/org/hyperskill/app/android/request_review/ui/RequestReviewDialog.kt @@ -0,0 +1,172 @@ +package org.hyperskill.app.android.request_review.ui + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.requiredSize +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.MaterialTheme +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.res.colorResource +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.tooling.preview.PreviewParameter +import androidx.compose.ui.tooling.preview.PreviewParameterProvider +import androidx.compose.ui.unit.dp +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import org.hyperskill.app.R +import org.hyperskill.app.android.core.view.ui.widget.compose.HyperskillButton +import org.hyperskill.app.android.core.view.ui.widget.compose.HyperskillOutlinedButton +import org.hyperskill.app.android.core.view.ui.widget.compose.HyperskillTheme +import org.hyperskill.app.request_review.modal.presentation.RequestReviewModalFeature.ViewState +import org.hyperskill.app.request_review.presentation.RequestReviewModalViewModel + +@Composable +fun RequestReviewDialog(viewModel: RequestReviewModalViewModel) { + val viewState: ViewState by viewModel.state.collectAsStateWithLifecycle() + RequestReviewDialog( + viewState = viewState, + onPositiveButtonClick = viewModel::onPositiveButtonClick, + onNegativeButtonClick = viewModel::onNegativeButtonClick + ) +} + +@Composable +fun RequestReviewDialog( + viewState: ViewState, + onPositiveButtonClick: () -> Unit, + onNegativeButtonClick: () -> Unit +) { + Column { + BottomSheetDragIndicator( + modifier = Modifier + .align(Alignment.CenterHorizontally) + .padding(top = 20.dp) + ) + Spacer(modifier = Modifier.height(40.dp)) + Column( + modifier = Modifier.padding(horizontal = 20.dp) + ) { + Text( + text = viewState.title, + style = MaterialTheme.typography.h4 + ) + val description = viewState.description + if (description != null) { + Spacer(modifier = Modifier.height(16.dp)) + Text( + text = description, + style = MaterialTheme.typography.body2 + ) + } + Spacer(modifier = Modifier.height(24.dp)) + when (viewState.state) { + ViewState.State.AWAITING, ViewState.State.POSITIVE -> AwaitingButtons( + positiveButtonText = viewState.positiveButtonText, + negativeButtonText = viewState.negativeButtonText, + onPositiveButtonClick = onPositiveButtonClick, + onNegativeButtonClick = onNegativeButtonClick + ) + ViewState.State.NEGATIVE -> NegativeButtons( + positiveButtonText = viewState.positiveButtonText, + negativeButtonText = viewState.negativeButtonText, + onPositiveButtonClick = onPositiveButtonClick, + onNegativeButtonClick = onNegativeButtonClick + ) + } + Spacer(modifier = Modifier.height(20.dp)) + } + } +} + +@Composable +private fun AwaitingButtons( + positiveButtonText: String, + negativeButtonText: String, + onPositiveButtonClick: () -> Unit, + onNegativeButtonClick: () -> Unit +) { + Row( + horizontalArrangement = Arrangement.spacedBy(16.dp) + ) { + HyperskillOutlinedButton( + onClick = onPositiveButtonClick, + modifier = Modifier.weight(1f) + ) { + Text(text = positiveButtonText) + } + HyperskillOutlinedButton( + onClick = onNegativeButtonClick, + modifier = Modifier.weight(1f) + ) { + Text( + text = negativeButtonText + ) + } + } +} + +@Composable +private fun NegativeButtons( + positiveButtonText: String, + negativeButtonText: String, + onPositiveButtonClick: () -> Unit, + onNegativeButtonClick: () -> Unit +) { + Column(verticalArrangement = Arrangement.spacedBy(16.dp)) { + HyperskillButton( + onClick = onPositiveButtonClick, + modifier = Modifier.fillMaxWidth() + ) { + Text(text = positiveButtonText) + } + HyperskillOutlinedButton( + onClick = onNegativeButtonClick, + modifier = Modifier.fillMaxWidth() + ) { + Text(text = negativeButtonText) + } + } +} + +@Composable +fun BottomSheetDragIndicator(modifier: Modifier = Modifier) { + Box( + modifier = modifier + .requiredSize(width = 60.dp, height = 4.dp) + .clip(RoundedCornerShape(3.dp)) + .background(colorResource(id = R.color.layer_active_2)) + ) +} + +private class RequestReviewModalPreviewProvider : PreviewParameterProvider { + override val values: Sequence + get() = sequenceOf( + RequestReviewPreviewDefaults.AwaitingViewState, + RequestReviewPreviewDefaults.NegativeViewState + ) +} + +@Preview +@Composable +private fun RequestReviewDialogPreview( + @PreviewParameter(RequestReviewModalPreviewProvider::class) viewState: ViewState +) { + HyperskillTheme { + RequestReviewDialog( + viewState = viewState, + onPositiveButtonClick = {}, + onNegativeButtonClick = {} + ) + } +} \ No newline at end of file diff --git a/androidHyperskillApp/src/main/java/org/hyperskill/app/android/request_review/ui/RequestReviewPreviewDefaults.kt b/androidHyperskillApp/src/main/java/org/hyperskill/app/android/request_review/ui/RequestReviewPreviewDefaults.kt new file mode 100644 index 0000000000..6efd41d499 --- /dev/null +++ b/androidHyperskillApp/src/main/java/org/hyperskill/app/android/request_review/ui/RequestReviewPreviewDefaults.kt @@ -0,0 +1,23 @@ +package org.hyperskill.app.android.request_review.ui + +import org.hyperskill.app.request_review.modal.presentation.RequestReviewModalFeature + +object RequestReviewPreviewDefaults { + val AwaitingViewState: RequestReviewModalFeature.ViewState + get() = RequestReviewModalFeature.ViewState( + title = "Do you enjoy Hyperskill app?", + description = null, + positiveButtonText = "\uD83D\uDC4D Yes", + negativeButtonText = "\uD83D\uDC4E No", + state = RequestReviewModalFeature.ViewState.State.AWAITING + ) + + val NegativeViewState: RequestReviewModalFeature.ViewState + get() = RequestReviewModalFeature.ViewState( + title = "Thank you", + description = "Share what you disliked to help us improve your experience.", + positiveButtonText = "Write a request", + negativeButtonText = "Maybe later", + state = RequestReviewModalFeature.ViewState.State.NEGATIVE + ) +} \ No newline at end of file diff --git a/androidHyperskillApp/src/main/java/org/hyperskill/app/android/step/view/delegate/StepDelegate.kt b/androidHyperskillApp/src/main/java/org/hyperskill/app/android/step/view/delegate/StepDelegate.kt index 951fec4533..ef3ec0e7bc 100644 --- a/androidHyperskillApp/src/main/java/org/hyperskill/app/android/step/view/delegate/StepDelegate.kt +++ b/androidHyperskillApp/src/main/java/org/hyperskill/app/android/step/view/delegate/StepDelegate.kt @@ -14,6 +14,7 @@ import org.hyperskill.app.android.main.view.ui.navigation.MainScreen import org.hyperskill.app.android.main.view.ui.navigation.MainScreenRouter import org.hyperskill.app.android.main.view.ui.navigation.Tabs import org.hyperskill.app.android.main.view.ui.navigation.switch +import org.hyperskill.app.android.request_review.dialog.RequestReviewDialogFragment import org.hyperskill.app.android.share_streak.fragment.ShareStreakDialogFragment import org.hyperskill.app.android.step.view.dialog.TopicPracticeCompletedBottomSheet import org.hyperskill.app.android.step.view.screen.StepScreen @@ -103,7 +104,12 @@ class StepDelegate( tag = InterviewPreparationFinishedDialogFragment.TAG ) is StepCompletionFeature.Action.ViewAction.ShowRequestUserReviewModal -> - TODO("ALTAPPS-1136: Implement request user review modal") + RequestReviewDialogFragment + .newInstance(stepCompletionAction.stepRoute) + .showIfNotExists( + manager = fragment.childFragmentManager, + tag = RequestReviewDialogFragment.TAG + ) } } } diff --git a/androidHyperskillApp/src/main/res/layout/fragment_profile_settings.xml b/androidHyperskillApp/src/main/res/layout/fragment_profile_settings.xml index 702b9d2207..74e58223da 100644 --- a/androidHyperskillApp/src/main/res/layout/fragment_profile_settings.xml +++ b/androidHyperskillApp/src/main/res/layout/fragment_profile_settings.xml @@ -165,13 +165,11 @@ - + android:layout_marginStart="20dp"/> - - - + + + +) : ReduxFlowViewModel(viewContainer) { + fun onPositiveButtonClick() { + onNewMessage(Message.PositiveButtonClicked) + } + + fun onNegativeButtonClick() { + onNewMessage(Message.NegativeButtonClicked) + } + + fun onShownEvent() { + onNewMessage(Message.ShownEventMessage) + } + + fun onHiddenEvent() { + onNewMessage(Message.HiddenEventMessage) + } +} \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/analytic/domain/model/hyperskill/HyperskillAnalyticTarget.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/analytic/domain/model/hyperskill/HyperskillAnalyticTarget.kt index 03e27ab952..13464938df 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/analytic/domain/model/hyperskill/HyperskillAnalyticTarget.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/analytic/domain/model/hyperskill/HyperskillAnalyticTarget.kt @@ -28,6 +28,7 @@ enum class HyperskillAnalyticTarget(val targetName: String) { DELETE_ACCOUNT("delete_account"), DELETE_ACCOUNT_NOTICE("delete_account_notice"), RATE_US_IN_APP_STORE("rate_us_in_app_store"), + RATE_US_IN_PLAY_STORE("rate_us_in_play_store"), SIGN_OUT_NOTICE("sign_out_notice"), NOTIFICATIONS_SYSTEM_NOTICE("notifications_system_notice"), VIEW_FULL_PROFILE("view_full_profile"), diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/profile_settings/presentation/ProfileSettingsFeature.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/profile_settings/presentation/ProfileSettingsFeature.kt index 2bd1685de0..69f9f5739b 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/profile_settings/presentation/ProfileSettingsFeature.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/profile_settings/presentation/ProfileSettingsFeature.kt @@ -56,6 +56,8 @@ interface ProfileSettingsFeature { object DeleteAccountNoticeShownEventMessage : Message object ClickedRateUsInAppStoreEventMessage : Message + + object ClickedRateUsInPlayStoreEventMessage : Message } sealed interface Action { diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/profile_settings/presentation/ProfileSettingsReducer.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/profile_settings/presentation/ProfileSettingsReducer.kt index b3019ef42e..79088cd2c9 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/profile_settings/presentation/ProfileSettingsReducer.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/profile_settings/presentation/ProfileSettingsReducer.kt @@ -143,6 +143,14 @@ class ProfileSettingsReducer : StateReducer { ) ) ) + Message.ClickedRateUsInPlayStoreEventMessage -> + state to setOf( + Action.LogAnalyticEvent( + ProfileSettingsClickedHyperskillAnalyticEvent( + target = HyperskillAnalyticTarget.RATE_US_IN_PLAY_STORE + ) + ) + ) is Message.GetMagicLinkReceiveSuccess -> { if (state is State.Content) { state.copy(isLoadingMagicLink = false) to setOf(Action.ViewAction.OpenUrl(message.url)) diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/request_review/modal/view/mapper/RequestReviewModalViewStateMapper.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/request_review/modal/view/mapper/RequestReviewModalViewStateMapper.kt index 6e931e5771..ffd2678fbf 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/request_review/modal/view/mapper/RequestReviewModalViewStateMapper.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/request_review/modal/view/mapper/RequestReviewModalViewStateMapper.kt @@ -2,7 +2,6 @@ package org.hyperskill.app.request_review.modal.view.mapper import org.hyperskill.app.SharedResources import org.hyperskill.app.core.domain.platform.Platform -import org.hyperskill.app.core.domain.platform.PlatformType import org.hyperskill.app.core.view.mapper.ResourceProvider import org.hyperskill.app.request_review.modal.presentation.RequestReviewModalFeature.State import org.hyperskill.app.request_review.modal.presentation.RequestReviewModalFeature.ViewState @@ -20,26 +19,12 @@ internal class RequestReviewModalViewStateMapper( resourceProvider.getString(platform.appNameResource) ), description = null, - positiveButtonText = when (platform.platformType) { - PlatformType.IOS -> - resourceProvider.getString( - SharedResources.strings.request_review_modal_state_awaiting_positive_button_text_ios - ) - PlatformType.ANDROID -> - resourceProvider.getString( - SharedResources.strings.request_review_modal_state_awaiting_positive_button_text_android - ) - }, - negativeButtonText = when (platform.platformType) { - PlatformType.IOS -> - resourceProvider.getString( - SharedResources.strings.request_review_modal_state_awaiting_negative_button_text_ios - ) - PlatformType.ANDROID -> - resourceProvider.getString( - SharedResources.strings.request_review_modal_state_awaiting_negative_button_text_android - ) - }, + positiveButtonText = resourceProvider.getString( + SharedResources.strings.request_review_modal_state_awaiting_positive_button_text + ), + negativeButtonText = resourceProvider.getString( + SharedResources.strings.request_review_modal_state_awaiting_negative_button_text + ), state = when (state) { State.Awaiting -> ViewState.State.AWAITING State.Positive -> ViewState.State.POSITIVE diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/step_completion/injection/StepCompletionComponentImpl.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/step_completion/injection/StepCompletionComponentImpl.kt index 098b194bc8..0c861dfdeb 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/step_completion/injection/StepCompletionComponentImpl.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/step_completion/injection/StepCompletionComponentImpl.kt @@ -34,7 +34,6 @@ internal class StepCompletionComponentImpl( dailyStepCompletedFlow = appGraph.stepCompletionFlowDataComponent.dailyStepCompletedFlow, topicCompletedFlow = appGraph.stepCompletionFlowDataComponent.topicCompletedFlow, topicProgressFlow = appGraph.progressesFlowDataComponent.topicProgressFlow, - interviewStepsStateRepository = appGraph.stateRepositoriesComponent.interviewStepsStateRepository, - platform = appGraph.commonComponent.platform + interviewStepsStateRepository = appGraph.stateRepositoriesComponent.interviewStepsStateRepository ) } \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/step_completion/presentation/StepCompletionActionDispatcher.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/step_completion/presentation/StepCompletionActionDispatcher.kt index b7606f0fb3..49d8cb6b68 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/step_completion/presentation/StepCompletionActionDispatcher.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/step_completion/presentation/StepCompletionActionDispatcher.kt @@ -6,8 +6,6 @@ import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import org.hyperskill.app.SharedResources import org.hyperskill.app.analytic.domain.interactor.AnalyticInteractor -import org.hyperskill.app.core.domain.platform.Platform -import org.hyperskill.app.core.domain.platform.PlatformType import org.hyperskill.app.core.domain.repository.updateState import org.hyperskill.app.core.presentation.ActionDispatcherOptions import org.hyperskill.app.core.view.mapper.ResourceProvider @@ -58,8 +56,7 @@ class StepCompletionActionDispatcher( private val dailyStepCompletedFlow: DailyStepCompletedFlow, private val topicCompletedFlow: TopicCompletedFlow, private val topicProgressFlow: TopicProgressFlow, - private val interviewStepsStateRepository: InterviewStepsStateRepository, - private val platform: Platform + private val interviewStepsStateRepository: InterviewStepsStateRepository ) : CoroutineActionDispatcher(config.createConfig()) { init { @@ -276,9 +273,7 @@ class StepCompletionActionDispatcher( } } - // TODO: ALTAPPS-1136 remove platformType check after implementing request review for Android - val shouldRequestReview = - platform.platformType == PlatformType.IOS && requestReviewInteractor.shouldRequestReviewAfterStepSolved() + val shouldRequestReview = requestReviewInteractor.shouldRequestReviewAfterStepSolved() if (shouldShareStreak && streakToShare != null) { shareStreakInteractor.setLastTimeShareStreakShown() diff --git a/shared/src/commonMain/resources/MR/base/strings.xml b/shared/src/commonMain/resources/MR/base/strings.xml index fc7b45e811..f3363cedad 100644 --- a/shared/src/commonMain/resources/MR/base/strings.xml +++ b/shared/src/commonMain/resources/MR/base/strings.xml @@ -247,7 +247,6 @@ Send feedback academy@jetbrains.com Version - Rate this application Sign out Please confirm Are you sure you want to sign out? @@ -260,6 +259,8 @@ https://support.hyperskill.org/hc/en-us/requests/new Rate us in the App Store https://apps.apple.com/app/id1637230833?action=write-review + Rate us on the Play Store + https://play.google.com/store/apps/details?id=org.hyperskill.app.android Leaderboard @@ -563,10 +564,8 @@ Do you enjoy\n%s app? - \u1F44D Yes - \u1F44E No - 👍 Yes - 👎 No + 👍 Yes + 👎 No Thank you! Share what you disliked to help us improve your experience. diff --git a/shared/src/commonMain/resources/MR/colors/colors.xml b/shared/src/commonMain/resources/MR/colors/colors.xml index e6f9cf359d..1dac85c4b4 100644 --- a/shared/src/commonMain/resources/MR/colors/colors.xml +++ b/shared/src/commonMain/resources/MR/colors/colors.xml @@ -308,4 +308,9 @@ #FFFFFF #1F2427 + + + #C1C7CD + #697077 + \ No newline at end of file From 80aa8bf9de2fdac9a26f5288c6048e9066c955fa Mon Sep 17 00:00:00 2001 From: github-actions Date: Tue, 20 Feb 2024 10:32:47 +0000 Subject: [PATCH 076/104] Bump build number --- gradle/app.versions.toml | 2 +- .../NotificationServiceExtension/Info.plist | 2 +- .../iosHyperskillApp.xcodeproj/project.pbxproj | 16 ++++++++-------- iosHyperskillApp/iosHyperskillApp/Info.plist | 2 +- .../iosHyperskillAppTests/Info.plist | 2 +- .../iosHyperskillAppUITests/Info.plist | 2 +- 6 files changed, 13 insertions(+), 13 deletions(-) diff --git a/gradle/app.versions.toml b/gradle/app.versions.toml index 633f563c94..682ebe841f 100644 --- a/gradle/app.versions.toml +++ b/gradle/app.versions.toml @@ -3,4 +3,4 @@ minSdk = '24' targetSdk = '33' compileSdk = '33' versionName = '1.50' -versionCode = '331' \ No newline at end of file +versionCode = '332' \ No newline at end of file diff --git a/iosHyperskillApp/NotificationServiceExtension/Info.plist b/iosHyperskillApp/NotificationServiceExtension/Info.plist index 42c5c47808..d7c0808032 100644 --- a/iosHyperskillApp/NotificationServiceExtension/Info.plist +++ b/iosHyperskillApp/NotificationServiceExtension/Info.plist @@ -9,7 +9,7 @@ CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleVersion - 336 + 337 CFBundleShortVersionString 1.50 CFBundlePackageType diff --git a/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj b/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj index f2a3846388..4177e83700 100644 --- a/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj +++ b/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj @@ -5037,7 +5037,7 @@ buildSettings = { CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 336; + CURRENT_PROJECT_VERSION = 337; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = 3DWS674B2M; GENERATE_INFOPLIST_FILE = NO; @@ -5058,7 +5058,7 @@ buildSettings = { CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 336; + CURRENT_PROJECT_VERSION = 337; DEVELOPMENT_TEAM = 3DWS674B2M; GENERATE_INFOPLIST_FILE = NO; INFOPLIST_FILE = iosHyperskillAppUITests/Info.plist; @@ -5079,7 +5079,7 @@ BUNDLE_LOADER = "$(TEST_HOST)"; CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 336; + CURRENT_PROJECT_VERSION = 337; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = 3DWS674B2M; INFOPLIST_FILE = iosHyperskillAppTests/Info.plist; @@ -5100,7 +5100,7 @@ BUNDLE_LOADER = "$(TEST_HOST)"; CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 336; + CURRENT_PROJECT_VERSION = 337; DEVELOPMENT_TEAM = 3DWS674B2M; INFOPLIST_FILE = iosHyperskillAppTests/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; @@ -5121,7 +5121,7 @@ CODE_SIGN_ENTITLEMENTS = NotificationServiceExtension/NotificationServiceExtension.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 336; + CURRENT_PROJECT_VERSION = 337; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = 3DWS674B2M; INFOPLIST_FILE = NotificationServiceExtension/Info.plist; @@ -5149,7 +5149,7 @@ CODE_SIGN_ENTITLEMENTS = NotificationServiceExtension/NotificationServiceExtension.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 336; + CURRENT_PROJECT_VERSION = 337; DEVELOPMENT_TEAM = 3DWS674B2M; INFOPLIST_FILE = NotificationServiceExtension/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; @@ -5294,7 +5294,7 @@ CODE_SIGN_ENTITLEMENTS = iosHyperskillApp/iosHyperskillApp.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 336; + CURRENT_PROJECT_VERSION = 337; DEVELOPMENT_ASSET_PATHS = "\"iosHyperskillApp/Preview Content\""; DEVELOPMENT_TEAM = 3DWS674B2M; ENABLE_PREVIEWS = YES; @@ -5330,7 +5330,7 @@ CODE_SIGN_ENTITLEMENTS = iosHyperskillApp/iosHyperskillApp.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 336; + CURRENT_PROJECT_VERSION = 337; DEVELOPMENT_ASSET_PATHS = "\"iosHyperskillApp/Preview Content\""; DEVELOPMENT_TEAM = 3DWS674B2M; ENABLE_PREVIEWS = YES; diff --git a/iosHyperskillApp/iosHyperskillApp/Info.plist b/iosHyperskillApp/iosHyperskillApp/Info.plist index b0e47acb5d..f97a1cca24 100644 --- a/iosHyperskillApp/iosHyperskillApp/Info.plist +++ b/iosHyperskillApp/iosHyperskillApp/Info.plist @@ -36,7 +36,7 @@ CFBundleVersion - 336 + 337 FirebaseAppDelegateProxyEnabled FirebaseMessagingAutoInitEnabled diff --git a/iosHyperskillApp/iosHyperskillAppTests/Info.plist b/iosHyperskillApp/iosHyperskillAppTests/Info.plist index 7a5206537e..393ab32655 100644 --- a/iosHyperskillApp/iosHyperskillAppTests/Info.plist +++ b/iosHyperskillApp/iosHyperskillAppTests/Info.plist @@ -15,6 +15,6 @@ CFBundleShortVersionString 1.50 CFBundleVersion - 336 + 337 diff --git a/iosHyperskillApp/iosHyperskillAppUITests/Info.plist b/iosHyperskillApp/iosHyperskillAppUITests/Info.plist index ce7193dd17..0ee3e870ff 100644 --- a/iosHyperskillApp/iosHyperskillAppUITests/Info.plist +++ b/iosHyperskillApp/iosHyperskillAppUITests/Info.plist @@ -15,6 +15,6 @@ CFBundleShortVersionString 1.50 CFBundleVersion - 336 + 337 From f8c85d1c5c3d180f07fb161259d00e8cdd2ccaac Mon Sep 17 00:00:00 2001 From: Ivan Magda Date: Wed, 21 Feb 2024 09:16:40 +0700 Subject: [PATCH 077/104] Shared, iOS: Widget for users questionnaire (#895) ^ALTAPPS-1143 --- .../study_plan/fragment/StudyPlanFragment.kt | 1 + .../project.pbxproj | 24 +++++ .../Assets.xcassets/Gradients/Contents.json | 6 ++ .../brand-gradient-3.imageset/Contents.json | 15 +++ .../brand-gradient-3.pdf | Bin 0 -> 2054 bytes .../Sources/Models/Constants/Strings.swift | 6 ++ .../StudyPlan/StudyPlanViewModel.swift | 31 +++++++ .../StudyPlan/Views/StudyPlanView.swift | 25 +++++ .../UsersQuestionnaireWidgetAssembly.swift | 26 ++++++ ...ersQuestionnaireWidgetOutputProtocol.swift | 7 ++ .../UsersQuestionnaireWidgetView.swift | 86 ++++++++++++++++++ .../UsersQuestionnaireWidgetViewModel.swift | 17 ++++ .../hyperskill/HyperskillAnalyticPart.kt | 3 +- .../hyperskill/HyperskillAnalyticRoute.kt | 7 +- .../hyperskill/app/core/injection/AppGraph.kt | 4 + .../app/core/injection/BaseAppGraph.kt | 10 ++ .../app/profile/domain/model/FeatureKeys.kt | 1 + .../app/profile/domain/model/FeaturesMap.kt | 3 + .../injection/StudyPlanScreenComponentImpl.kt | 9 +- .../StudyPlanScreenFeatureBuilder.kt | 15 +++ .../presentation/StudyPlanScreenFeature.kt | 23 ++++- .../presentation/StudyPlanScreenReducer.kt | 44 ++++++++- .../view/StudyPlanScreenViewStateMapper.kt | 1 + .../UsersQuestionnaireCacheDataSourceImpl.kt | 15 +++ .../cache/UsersQuestionnaireCacheKeyValues.kt | 5 + .../UsersQuestionnaireRepositoryImpl.kt | 15 +++ .../UsersQuestionnaireCacheDataSource.kt | 6 ++ .../UsersQuestionnaireRepository.kt | 6 ++ .../UsersQuestionnaireDataComponent.kt | 7 ++ .../UsersQuestionnaireDataComponentImpl.kt | 17 ++++ ...dgetClickedCloseHyperskillAnalyticEvent.kt | 29 ++++++ ...ireWidgetClickedHyperskillAnalyticEvent.kt | 26 ++++++ ...aireWidgetViewedHyperskillAnalyticEvent.kt | 23 +++++ .../UsersQuestionnaireWidgetComponent.kt | 9 ++ .../UsersQuestionnaireWidgetComponentImpl.kt | 21 +++++ ...sersQuestionnaireWidgetActionDispatcher.kt | 80 ++++++++++++++++ .../UsersQuestionnaireWidgetFeature.kt | 52 +++++++++++ .../UsersQuestionnaireWidgetReducer.kt | 64 +++++++++++++ .../commonMain/resources/MR/base/strings.xml | 3 + .../study_plan/screen/StudyPlanScreenTest.kt | 12 ++- 40 files changed, 745 insertions(+), 9 deletions(-) create mode 100644 iosHyperskillApp/iosHyperskillApp/Assets.xcassets/Gradients/Contents.json create mode 100644 iosHyperskillApp/iosHyperskillApp/Assets.xcassets/Gradients/brand-gradient-3.imageset/Contents.json create mode 100644 iosHyperskillApp/iosHyperskillApp/Assets.xcassets/Gradients/brand-gradient-3.imageset/brand-gradient-3.pdf create mode 100644 iosHyperskillApp/iosHyperskillApp/Sources/Modules/UsersQuestionnaireWidget/UsersQuestionnaireWidgetAssembly.swift create mode 100644 iosHyperskillApp/iosHyperskillApp/Sources/Modules/UsersQuestionnaireWidget/UsersQuestionnaireWidgetOutputProtocol.swift create mode 100644 iosHyperskillApp/iosHyperskillApp/Sources/Modules/UsersQuestionnaireWidget/UsersQuestionnaireWidgetView.swift create mode 100644 iosHyperskillApp/iosHyperskillApp/Sources/Modules/UsersQuestionnaireWidget/UsersQuestionnaireWidgetViewModel.swift create mode 100644 shared/src/commonMain/kotlin/org/hyperskill/app/users_questionnaire/cache/UsersQuestionnaireCacheDataSourceImpl.kt create mode 100644 shared/src/commonMain/kotlin/org/hyperskill/app/users_questionnaire/cache/UsersQuestionnaireCacheKeyValues.kt create mode 100644 shared/src/commonMain/kotlin/org/hyperskill/app/users_questionnaire/data/repository/UsersQuestionnaireRepositoryImpl.kt create mode 100644 shared/src/commonMain/kotlin/org/hyperskill/app/users_questionnaire/data/source/UsersQuestionnaireCacheDataSource.kt create mode 100644 shared/src/commonMain/kotlin/org/hyperskill/app/users_questionnaire/domain/repository/UsersQuestionnaireRepository.kt create mode 100644 shared/src/commonMain/kotlin/org/hyperskill/app/users_questionnaire/injection/UsersQuestionnaireDataComponent.kt create mode 100644 shared/src/commonMain/kotlin/org/hyperskill/app/users_questionnaire/injection/UsersQuestionnaireDataComponentImpl.kt create mode 100644 shared/src/commonMain/kotlin/org/hyperskill/app/users_questionnaire/widget/domain/analytic/UsersQuestionnaireWidgetClickedCloseHyperskillAnalyticEvent.kt create mode 100644 shared/src/commonMain/kotlin/org/hyperskill/app/users_questionnaire/widget/domain/analytic/UsersQuestionnaireWidgetClickedHyperskillAnalyticEvent.kt create mode 100644 shared/src/commonMain/kotlin/org/hyperskill/app/users_questionnaire/widget/domain/analytic/UsersQuestionnaireWidgetViewedHyperskillAnalyticEvent.kt create mode 100644 shared/src/commonMain/kotlin/org/hyperskill/app/users_questionnaire/widget/injection/UsersQuestionnaireWidgetComponent.kt create mode 100644 shared/src/commonMain/kotlin/org/hyperskill/app/users_questionnaire/widget/injection/UsersQuestionnaireWidgetComponentImpl.kt create mode 100644 shared/src/commonMain/kotlin/org/hyperskill/app/users_questionnaire/widget/presentation/UsersQuestionnaireWidgetActionDispatcher.kt create mode 100644 shared/src/commonMain/kotlin/org/hyperskill/app/users_questionnaire/widget/presentation/UsersQuestionnaireWidgetFeature.kt create mode 100644 shared/src/commonMain/kotlin/org/hyperskill/app/users_questionnaire/widget/presentation/UsersQuestionnaireWidgetReducer.kt diff --git a/androidHyperskillApp/src/main/java/org/hyperskill/app/android/study_plan/fragment/StudyPlanFragment.kt b/androidHyperskillApp/src/main/java/org/hyperskill/app/android/study_plan/fragment/StudyPlanFragment.kt index 76380f63e8..4cb7408023 100644 --- a/androidHyperskillApp/src/main/java/org/hyperskill/app/android/study_plan/fragment/StudyPlanFragment.kt +++ b/androidHyperskillApp/src/main/java/org/hyperskill/app/android/study_plan/fragment/StudyPlanFragment.kt @@ -170,6 +170,7 @@ class StudyPlanFragment : } } } + is StudyPlanScreenFeature.Action.ViewAction.UsersQuestionnaireWidgetViewAction -> TODO() } } diff --git a/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj b/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj index d07267be49..6bfc63310f 100644 --- a/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj +++ b/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj @@ -376,6 +376,10 @@ 2CAA3C6A2AA9C7B6004F6CE6 /* LottieAnimations.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CAA3C692AA9C7B6004F6CE6 /* LottieAnimations.swift */; }; 2CAA3C6D2AA9CA9D004F6CE6 /* StepQuizProblemOnboardingModalViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CAA3C6C2AA9CA9D004F6CE6 /* StepQuizProblemOnboardingModalViewController.swift */; }; 2CAA3C6F2AA9CAB1004F6CE6 /* StepQuizProblemOnboardingModalView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CAA3C6E2AA9CAB1004F6CE6 /* StepQuizProblemOnboardingModalView.swift */; }; + 2CACBCBC2B7A12F1006D3AB2 /* UsersQuestionnaireWidgetView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CACBCBB2B7A12F1006D3AB2 /* UsersQuestionnaireWidgetView.swift */; }; + 2CACBCBE2B7A1365006D3AB2 /* UsersQuestionnaireWidgetViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CACBCBD2B7A1365006D3AB2 /* UsersQuestionnaireWidgetViewModel.swift */; }; + 2CACBCC02B7A137A006D3AB2 /* UsersQuestionnaireWidgetOutputProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CACBCBF2B7A137A006D3AB2 /* UsersQuestionnaireWidgetOutputProtocol.swift */; }; + 2CACBCC22B7A3E4E006D3AB2 /* UsersQuestionnaireWidgetAssembly.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CACBCC12B7A3E4E006D3AB2 /* UsersQuestionnaireWidgetAssembly.swift */; }; 2CAE8CF0280525BE00E6C83D /* StepViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CAE8CEF280525BE00E6C83D /* StepViewModel.swift */; }; 2CAE8CF2280525C900E6C83D /* StepView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CAE8CF1280525C900E6C83D /* StepView.swift */; }; 2CAE8CF4280525D400E6C83D /* StepAssembly.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CAE8CF3280525D400E6C83D /* StepAssembly.swift */; }; @@ -1067,6 +1071,10 @@ 2CAA3C692AA9C7B6004F6CE6 /* LottieAnimations.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LottieAnimations.swift; sourceTree = ""; }; 2CAA3C6C2AA9CA9D004F6CE6 /* StepQuizProblemOnboardingModalViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StepQuizProblemOnboardingModalViewController.swift; sourceTree = ""; }; 2CAA3C6E2AA9CAB1004F6CE6 /* StepQuizProblemOnboardingModalView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StepQuizProblemOnboardingModalView.swift; sourceTree = ""; }; + 2CACBCBB2B7A12F1006D3AB2 /* UsersQuestionnaireWidgetView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UsersQuestionnaireWidgetView.swift; sourceTree = ""; }; + 2CACBCBD2B7A1365006D3AB2 /* UsersQuestionnaireWidgetViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UsersQuestionnaireWidgetViewModel.swift; sourceTree = ""; }; + 2CACBCBF2B7A137A006D3AB2 /* UsersQuestionnaireWidgetOutputProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UsersQuestionnaireWidgetOutputProtocol.swift; sourceTree = ""; }; + 2CACBCC12B7A3E4E006D3AB2 /* UsersQuestionnaireWidgetAssembly.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UsersQuestionnaireWidgetAssembly.swift; sourceTree = ""; }; 2CAE8CEF280525BE00E6C83D /* StepViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StepViewModel.swift; sourceTree = ""; }; 2CAE8CF1280525C900E6C83D /* StepView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StepView.swift; sourceTree = ""; }; 2CAE8CF3280525D400E6C83D /* StepAssembly.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StepAssembly.swift; sourceTree = ""; }; @@ -1735,6 +1743,7 @@ E9F0A2A729D416AC00C4A61E /* StudyPlan */, E9A022AB291D0E1C004317DB /* TopicsRepetitions */, 2C2600862A2001E600BD3D39 /* TrackSelection */, + 2CACBCBA2B7A1292006D3AB2 /* UsersQuestionnaireWidget */, E9F923F428A2632800C065A7 /* Welcome */, ); path = Modules; @@ -3008,6 +3017,17 @@ path = ProblemOnboarding; sourceTree = ""; }; + 2CACBCBA2B7A1292006D3AB2 /* UsersQuestionnaireWidget */ = { + isa = PBXGroup; + children = ( + 2CACBCC12B7A3E4E006D3AB2 /* UsersQuestionnaireWidgetAssembly.swift */, + 2CACBCBF2B7A137A006D3AB2 /* UsersQuestionnaireWidgetOutputProtocol.swift */, + 2CACBCBB2B7A12F1006D3AB2 /* UsersQuestionnaireWidgetView.swift */, + 2CACBCBD2B7A1365006D3AB2 /* UsersQuestionnaireWidgetViewModel.swift */, + ); + path = UsersQuestionnaireWidget; + sourceTree = ""; + }; 2CAE8CEE280525A100E6C83D /* Step */ = { isa = PBXGroup; children = ( @@ -4599,6 +4619,7 @@ 2C688C052A4E97750061AFFD /* ProgressScreenProjectProgressView.swift in Sources */, E9523BF429DAA5690013A661 /* StudyPlanSkeletonView.swift in Sources */, 2C2B7DD22946EF2800FAB55D /* WebViewNavigationController.swift in Sources */, + 2CACBCC02B7A137A006D3AB2 /* UsersQuestionnaireWidgetOutputProtocol.swift in Sources */, 2C336D242865E38B00C91342 /* CodeInputAccessoryCollectionViewCell.swift in Sources */, 2C2FD62428192123004E7AF6 /* BundlePropertyListDeserializer.swift in Sources */, 2C8E66D7287877F500D3928D /* ProfileViewModel.swift in Sources */, @@ -4646,6 +4667,7 @@ 2C9674302888242D0091B6C9 /* StepQuizCodeViewData.swift in Sources */, 2C66720F2A529A7C0040EA2F /* ProgressScreenTrackProgressSkeletonView.swift in Sources */, 2C1F5888280D5D6200372A37 /* SocialAuthSDKProvider.swift in Sources */, + 2CACBCBE2B7A1365006D3AB2 /* UsersQuestionnaireWidgetViewModel.swift in Sources */, 2CAE8CF728052F9600E6C83D /* StepHeaderView.swift in Sources */, 2CF87DA229B718800092FF83 /* IntrospectViewController.swift in Sources */, 2CFD32462AAEFC6C00B9B6EA /* AppGraph+DefaultInstances.swift in Sources */, @@ -4740,6 +4762,7 @@ 2CA8E094281039EB00154088 /* RoundedRectangleButtonStyle.swift in Sources */, E9D537D02A71056100F21828 /* ProfileBadgesGridItemView.swift in Sources */, 2CB0ADEE2B04AD6D0089D557 /* ChallengeWidgetViewModel.swift in Sources */, + 2CACBCC22B7A3E4E006D3AB2 /* UsersQuestionnaireWidgetAssembly.swift in Sources */, E9CC6C0729893F2200D8D070 /* StepQuizInputProtocol.swift in Sources */, 2C96743728882A0C0091B6C9 /* StepQuizCodeDetailsView.swift in Sources */, 2C20FBC7284F6928006D879E /* ProgrammaticallyInitializableViewProtocol.swift in Sources */, @@ -4879,6 +4902,7 @@ 2C93AF2529B34FE6004639E0 /* StepQuizPyCharmAssembly.swift in Sources */, 2CDA98412944512D00ADE539 /* ProfileSkeletonView.swift in Sources */, 2CEDE70729965B4D0032D399 /* RestartApplicationLocalNotification.swift in Sources */, + 2CACBCBC2B7A12F1006D3AB2 /* UsersQuestionnaireWidgetView.swift in Sources */, 2CDA98432944524D00ADE539 /* HomeSkeletonView.swift in Sources */, 2C9D493D29F07015000599AB /* StudyPlanSectionErrorView.swift in Sources */, 2C336D262865E39D00C91342 /* CodeInputAccessoryView.swift in Sources */, diff --git a/iosHyperskillApp/iosHyperskillApp/Assets.xcassets/Gradients/Contents.json b/iosHyperskillApp/iosHyperskillApp/Assets.xcassets/Gradients/Contents.json new file mode 100644 index 0000000000..73c00596a7 --- /dev/null +++ b/iosHyperskillApp/iosHyperskillApp/Assets.xcassets/Gradients/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/iosHyperskillApp/iosHyperskillApp/Assets.xcassets/Gradients/brand-gradient-3.imageset/Contents.json b/iosHyperskillApp/iosHyperskillApp/Assets.xcassets/Gradients/brand-gradient-3.imageset/Contents.json new file mode 100644 index 0000000000..58bda5eef0 --- /dev/null +++ b/iosHyperskillApp/iosHyperskillApp/Assets.xcassets/Gradients/brand-gradient-3.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "filename" : "brand-gradient-3.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "preserves-vector-representation" : true + } +} diff --git a/iosHyperskillApp/iosHyperskillApp/Assets.xcassets/Gradients/brand-gradient-3.imageset/brand-gradient-3.pdf b/iosHyperskillApp/iosHyperskillApp/Assets.xcassets/Gradients/brand-gradient-3.imageset/brand-gradient-3.pdf new file mode 100644 index 0000000000000000000000000000000000000000..b334d4d0b0fd05b2a4b907561beb3c7cd83fdc9b GIT binary patch literal 2054 zcma)7O>f&U487}D=u)6PB(~&lpa`%g?J#V^vShbm2h~;`4~do8%8;$-e;*}TvR$}q zbx5culA_2*`eMDhn~`~f5Wzkc_dgNF*Vowl#OqS-Fa>Y}5G-%Q>XPH{*q{4)VPb{1 zAU`@UD{){|J?X}(+@0!NR-*p)=g4sgc8djSWy9G4Jz}^%q(MY8%+L7_c0roL-t1gN z`m8t|`(R6nAC1yc94Ca4a(lX0mQk4b6dz9e{!&&H#&|+Mpjk+)eizou`ZJZaAfv{> zMeYoqaP@%IE$-;GCqQPBGz^Fd(4QwHiNnP1vMlzK)a!QmoNUHuw)!Yyv z2oiO!(ZHdK9&08gZ)#1IHPL^cnQW>v6m~{}IZZPfQA~(Gr<6ocpV>(6HE28!0^g57 zBLa~`ge16Qg67@$$J_2$QPrisaw$Ypv_mfahlwxy9Nyq9#a=YqBg;AVR{VFB^UeDk z!`-3YgzQ<~V%17r*G%XQA@B8e7^+XmHX*uM=-UFlJxeGn2#xLQ;6m7z$&k+%qVJ zP{&XlKX*?Oh{$TmhE;psAeFWcE2!ROD-Qe$UslDHNu9S5LSmH`T?TJk{pIjRNLedc QBlV+6l7;Bv;`YnkKioXeLI3~& literal 0 HcmV?d00001 diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Models/Constants/Strings.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Models/Constants/Strings.swift index 9fb9a6721c..e5b21a76d6 100644 --- a/iosHyperskillApp/iosHyperskillApp/Sources/Models/Constants/Strings.swift +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Models/Constants/Strings.swift @@ -259,6 +259,12 @@ enum Strings { static let networkError = sharedStrings.challenge_widget_network_error_text.localized() } + // MARK: - Users questionnaire widget - + + enum UsersQuestionnaireWidget { + static let title = sharedStrings.users_questionnaire_widget_title.localized() + } + // MARK: - Interview Preparation - // MARK: Widget diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StudyPlan/StudyPlanViewModel.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StudyPlan/StudyPlanViewModel.swift index c80ee89608..dfac72e206 100644 --- a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StudyPlan/StudyPlanViewModel.swift +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StudyPlan/StudyPlanViewModel.swift @@ -12,6 +12,9 @@ final class StudyPlanViewModel: FeatureViewModel< var studyPlanWidgetStateKs: StudyPlanWidgetViewStateKs { .init(state.studyPlanWidgetViewState) } var gamificationToolbarViewStateKs: GamificationToolbarFeatureViewStateKs { .init(state.toolbarViewState) } var problemsLimitViewStateKs: ProblemsLimitFeatureViewStateKs { .init(state.problemsLimitViewState) } + var usersQuestionnaireWidgetFeatureStateKs: UsersQuestionnaireWidgetFeatureStateKs { + .init(state.usersQuestionnaireWidgetState) + } override func shouldNotifyStateDidChange( oldState: StudyPlanScreenFeature.ViewState, @@ -138,3 +141,31 @@ extension StudyPlanViewModel: StageImplementUnsupportedModalViewControllerDelega ) } } + +// MARK: - StudyPlanViewModel: UsersQuestionnaireWidgetOutputProtocol - + +extension StudyPlanViewModel: UsersQuestionnaireWidgetOutputProtocol { + func handleUsersQuestionnaireWidgetClicked() { + onNewMessage( + StudyPlanScreenFeatureMessageUsersQuestionnaireWidgetMessage( + message: UsersQuestionnaireWidgetFeatureMessageWidgetClicked() + ) + ) + } + + func handleUsersQuestionnaireWidgetCloseClicked() { + onNewMessage( + StudyPlanScreenFeatureMessageUsersQuestionnaireWidgetMessage( + message: UsersQuestionnaireWidgetFeatureMessageCloseClicked() + ) + ) + } + + func handleUsersQuestionnaireWidgetDidAppear() { + onNewMessage( + StudyPlanScreenFeatureMessageUsersQuestionnaireWidgetMessage( + message: UsersQuestionnaireWidgetFeatureMessageViewedEventMessage() + ) + ) + } +} diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StudyPlan/Views/StudyPlanView.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StudyPlan/Views/StudyPlanView.swift index dc4b7494c9..548f6283b6 100644 --- a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StudyPlan/Views/StudyPlanView.swift +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StudyPlan/Views/StudyPlanView.swift @@ -85,6 +85,15 @@ struct StudyPlanView: View { onReloadButtonTap: viewModel.doReloadProblemsLimit ) + let usersQuestionnaireWidgetFeatureStateKs = viewModel.usersQuestionnaireWidgetFeatureStateKs + if usersQuestionnaireWidgetFeatureStateKs != .hidden { + UsersQuestionnaireWidgetAssembly( + stateKs: usersQuestionnaireWidgetFeatureStateKs, + moduleOutput: viewModel + ) + .makeModule() + } + ForEach(data.sections, id: \.id) { section in StudyPlanSectionView( section: section, @@ -126,6 +135,10 @@ private extension StudyPlanView { handleStudyPlanWidgetViewAction( studyPlanWidgetViewAction.viewAction ) + case .usersQuestionnaireWidgetViewAction(let usersQuestionnaireWidgetViewAction): + handleUsersQuestionnaireWidgetViewAction( + usersQuestionnaireWidgetViewAction.viewAction + ) } } @@ -183,6 +196,18 @@ private extension StudyPlanView { } } } + + func handleUsersQuestionnaireWidgetViewAction( + _ viewAction: UsersQuestionnaireWidgetFeatureActionViewAction + ) { + switch UsersQuestionnaireWidgetFeatureActionViewActionKs(viewAction) { + case .showUsersQuestionnaire(let showUsersQuestionnaireViewAction): + WebControllerManager.shared.presentWebControllerWithURLString( + showUsersQuestionnaireViewAction.url, + controllerType: .inAppSafari + ) + } + } } // MARK: - StudyPlanView_Previews: PreviewProvider - diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/UsersQuestionnaireWidget/UsersQuestionnaireWidgetAssembly.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/UsersQuestionnaireWidget/UsersQuestionnaireWidgetAssembly.swift new file mode 100644 index 0000000000..30e2d98758 --- /dev/null +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/UsersQuestionnaireWidget/UsersQuestionnaireWidgetAssembly.swift @@ -0,0 +1,26 @@ +import shared +import SwiftUI + +final class UsersQuestionnaireWidgetAssembly: Assembly { + weak var moduleOutput: UsersQuestionnaireWidgetOutputProtocol? + + private let stateKs: UsersQuestionnaireWidgetFeatureStateKs + + init( + stateKs: UsersQuestionnaireWidgetFeatureStateKs, + moduleOutput: UsersQuestionnaireWidgetOutputProtocol? + ) { + self.stateKs = stateKs + self.moduleOutput = moduleOutput + } + + func makeModule() -> UsersQuestionnaireWidgetView { + let viewModel = UsersQuestionnaireWidgetViewModel() + viewModel.moduleOutput = moduleOutput + + return UsersQuestionnaireWidgetView( + stateKs: stateKs, + viewModel: viewModel + ) + } +} diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/UsersQuestionnaireWidget/UsersQuestionnaireWidgetOutputProtocol.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/UsersQuestionnaireWidget/UsersQuestionnaireWidgetOutputProtocol.swift new file mode 100644 index 0000000000..fba3fa4d6b --- /dev/null +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/UsersQuestionnaireWidget/UsersQuestionnaireWidgetOutputProtocol.swift @@ -0,0 +1,7 @@ +import Foundation + +protocol UsersQuestionnaireWidgetOutputProtocol: AnyObject { + func handleUsersQuestionnaireWidgetClicked() + func handleUsersQuestionnaireWidgetCloseClicked() + func handleUsersQuestionnaireWidgetDidAppear() +} diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/UsersQuestionnaireWidget/UsersQuestionnaireWidgetView.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/UsersQuestionnaireWidget/UsersQuestionnaireWidgetView.swift new file mode 100644 index 0000000000..62356477f4 --- /dev/null +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/UsersQuestionnaireWidget/UsersQuestionnaireWidgetView.swift @@ -0,0 +1,86 @@ +import SwiftUI + +extension UsersQuestionnaireWidgetView { + struct Appearance { + let skeletonHeight: CGFloat = 114 + + let spacing = LayoutInsets.defaultInset + } +} + +struct UsersQuestionnaireWidgetView: View { + private(set) var appearance = Appearance() + + let stateKs: UsersQuestionnaireWidgetFeatureStateKs + + let viewModel: UsersQuestionnaireWidgetViewModel + + var body: some View { + ZStack { + UIViewControllerEventsWrapper( + onViewDidAppear: viewModel.logViewedEvent + ) + buildBody() + } + } + + @ViewBuilder + private func buildBody() -> some View { + switch stateKs { + case .idle, .loading: + SkeletonRoundedView() + .frame(height: appearance.skeletonHeight) + case .hidden: + EmptyView() + case .visible: + Button( + action: { + withAnimation { + viewModel.doCallToAction() + } + }, + label: { + HStack(alignment: .center, spacing: 0) { + Text(Strings.UsersQuestionnaireWidget.title) + .font(.subheadline) + + Spacer() + + Button( + action: { + withAnimation { + viewModel.doCloseAction() + } + }, + label: { + Image(systemName: "xmark.circle.fill") + .padding(.all, appearance.spacing) + } + ) + .offset(x: appearance.spacing, y: 0) + } + .foregroundColor(.white) + .frame(maxWidth: .infinity) + .padding(.horizontal) + .background(backgroundGradient) + } + ) + .buttonStyle(BounceButtonStyle()) + } + } + + private var backgroundGradient: some View { + Image(.brandGradient3) + .renderingMode(.original) + .resizable() + .addBorder(color: .clear, width: 0) + } +} + +#Preview { + UsersQuestionnaireWidgetView( + stateKs: .visible, + viewModel: UsersQuestionnaireWidgetViewModel() + ) + .padding(.horizontal) +} diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/UsersQuestionnaireWidget/UsersQuestionnaireWidgetViewModel.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/UsersQuestionnaireWidget/UsersQuestionnaireWidgetViewModel.swift new file mode 100644 index 0000000000..be78facc8c --- /dev/null +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/UsersQuestionnaireWidget/UsersQuestionnaireWidgetViewModel.swift @@ -0,0 +1,17 @@ +import Foundation + +final class UsersQuestionnaireWidgetViewModel { + weak var moduleOutput: UsersQuestionnaireWidgetOutputProtocol? + + func doCallToAction() { + moduleOutput?.handleUsersQuestionnaireWidgetClicked() + } + + func doCloseAction() { + moduleOutput?.handleUsersQuestionnaireWidgetCloseClicked() + } + + func logViewedEvent() { + moduleOutput?.handleUsersQuestionnaireWidgetDidAppear() + } +} diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/analytic/domain/model/hyperskill/HyperskillAnalyticPart.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/analytic/domain/model/hyperskill/HyperskillAnalyticPart.kt index 598d4558d0..fadd617d2c 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/analytic/domain/model/hyperskill/HyperskillAnalyticPart.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/analytic/domain/model/hyperskill/HyperskillAnalyticPart.kt @@ -44,5 +44,6 @@ enum class HyperskillAnalyticPart(val partName: String) { DAILY_STUDY_REMINDERS_HOUR_INTERVAL_PICKER_MODAL("daily_study_reminders_hour_interval_picker_modal"), INTERVIEW_PREPARATION_WIDGET("interview_preparation_widget"), INTERVIEW_PREPARATION_COMPLETED_MODAL("interview_preparation_completed_modal"), - REQUEST_REVIEW_MODAL("request_review_modal") + REQUEST_REVIEW_MODAL("request_review_modal"), + USERS_QUESTIONNAIRE_WIDGET("users_questionnaire_widget") } \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/analytic/domain/model/hyperskill/HyperskillAnalyticRoute.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/analytic/domain/model/hyperskill/HyperskillAnalyticRoute.kt index 9222fefea8..08f60ebd88 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/analytic/domain/model/hyperskill/HyperskillAnalyticRoute.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/analytic/domain/model/hyperskill/HyperskillAnalyticRoute.kt @@ -105,8 +105,13 @@ sealed class HyperskillAnalyticRoute { override val path: String = "/debug" } - class StudyPlan : HyperskillAnalyticRoute() { + open class StudyPlan : HyperskillAnalyticRoute() { override val path: String = "/study-plan" + + class UsersQuestionnaireWidget : StudyPlan() { + override val path: String + get() = "${super.path}/users-questionnaire-widget" + } } class Leaderboard : HyperskillAnalyticRoute() { diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/core/injection/AppGraph.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/core/injection/AppGraph.kt index 32f9004546..d82b24f526 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/core/injection/AppGraph.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/core/injection/AppGraph.kt @@ -80,6 +80,8 @@ import org.hyperskill.app.track.injection.TrackDataComponent import org.hyperskill.app.track_selection.details.injection.TrackSelectionDetailsComponent import org.hyperskill.app.track_selection.list.injection.TrackSelectionListComponent import org.hyperskill.app.user_storage.injection.UserStorageComponent +import org.hyperskill.app.users_questionnaire.injection.UsersQuestionnaireDataComponent +import org.hyperskill.app.users_questionnaire.widget.injection.UsersQuestionnaireWidgetComponent import org.hyperskill.app.welcome.injection.WelcomeComponent import org.hyperskill.app.welcome.injection.WelcomeDataComponent import org.hyperskill.app.welcome_onboarding.injection.WelcomeOnboardingComponent @@ -179,4 +181,6 @@ interface AppGraph { fun buildInterviewPreparationOnboardingComponent(): InterviewPreparationOnboardingComponent fun buildRequestReviewDataComponent(): RequestReviewDataComponent fun buildRequestReviewModalComponent(stepRoute: StepRoute): RequestReviewModalComponent + fun buildUsersQuestionnaireDataComponent(): UsersQuestionnaireDataComponent + fun buildUsersQuestionnaireWidgetComponent(): UsersQuestionnaireWidgetComponent } \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/core/injection/BaseAppGraph.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/core/injection/BaseAppGraph.kt index 53d80e8742..4355ffc11f 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/core/injection/BaseAppGraph.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/core/injection/BaseAppGraph.kt @@ -151,6 +151,10 @@ import org.hyperskill.app.track_selection.list.injection.TrackSelectionListCompo import org.hyperskill.app.track_selection.list.injection.TrackSelectionListComponentImpl import org.hyperskill.app.user_storage.injection.UserStorageComponent import org.hyperskill.app.user_storage.injection.UserStorageComponentImpl +import org.hyperskill.app.users_questionnaire.injection.UsersQuestionnaireDataComponent +import org.hyperskill.app.users_questionnaire.injection.UsersQuestionnaireDataComponentImpl +import org.hyperskill.app.users_questionnaire.widget.injection.UsersQuestionnaireWidgetComponent +import org.hyperskill.app.users_questionnaire.widget.injection.UsersQuestionnaireWidgetComponentImpl import org.hyperskill.app.welcome.injection.WelcomeComponent import org.hyperskill.app.welcome.injection.WelcomeComponentImpl import org.hyperskill.app.welcome.injection.WelcomeDataComponent @@ -483,4 +487,10 @@ abstract class BaseAppGraph : AppGraph { override fun buildRequestReviewModalComponent(stepRoute: StepRoute): RequestReviewModalComponent = RequestReviewModalComponentImpl(appGraph = this, stepRoute = stepRoute) + + override fun buildUsersQuestionnaireDataComponent(): UsersQuestionnaireDataComponent = + UsersQuestionnaireDataComponentImpl(this) + + override fun buildUsersQuestionnaireWidgetComponent(): UsersQuestionnaireWidgetComponent = + UsersQuestionnaireWidgetComponentImpl(this) } \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/profile/domain/model/FeatureKeys.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/profile/domain/model/FeatureKeys.kt index 038a2af9af..9f170573e6 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/profile/domain/model/FeatureKeys.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/profile/domain/model/FeatureKeys.kt @@ -8,5 +8,6 @@ internal object FeatureKeys { const val LEARNING_PATH_DIVIDED_TRACK_TOPICS = "learning_path.divided_track_topics" const val MOBILE_LEADERBOARDS = "mobile_leaderboards" const val MOBILE_INTERVIEW_PREPARATION = "mobile.interview_preparation" + const val MOBILE_USERS_QUESTIONNAIRE = "mobile.users_questionnaire" const val MOBILE_SHORT_THEORY = "mobile.short_theory" } \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/profile/domain/model/FeaturesMap.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/profile/domain/model/FeaturesMap.kt index da22734c0b..67ce5136fd 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/profile/domain/model/FeaturesMap.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/profile/domain/model/FeaturesMap.kt @@ -23,5 +23,8 @@ val FeaturesMap.isMobileLeaderboardsEnabled: Boolean val FeaturesMap.isMobileInterviewPreparationEnabled: Boolean get() = get(FeatureKeys.MOBILE_INTERVIEW_PREPARATION) ?: false +val FeaturesMap.isMobileUsersQuestionnaireEnabled: Boolean + get() = get(FeatureKeys.MOBILE_USERS_QUESTIONNAIRE) ?: false + val FeaturesMap.isMobileShortTheoryEnabled: Boolean get() = get(FeatureKeys.MOBILE_SHORT_THEORY) ?: false \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/study_plan/screen/injection/StudyPlanScreenComponentImpl.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/study_plan/screen/injection/StudyPlanScreenComponentImpl.kt index 4bd3097f8c..b2a654a416 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/study_plan/screen/injection/StudyPlanScreenComponentImpl.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/study_plan/screen/injection/StudyPlanScreenComponentImpl.kt @@ -7,9 +7,10 @@ import org.hyperskill.app.problems_limit.domain.model.ProblemsLimitScreen import org.hyperskill.app.problems_limit.injection.ProblemsLimitComponent import org.hyperskill.app.study_plan.screen.presentation.StudyPlanScreenFeature import org.hyperskill.app.study_plan.widget.injection.StudyPlanWidgetComponent +import org.hyperskill.app.users_questionnaire.widget.injection.UsersQuestionnaireWidgetComponent import ru.nobird.app.presentation.redux.feature.Feature -class StudyPlanScreenComponentImpl(private val appGraph: AppGraph) : StudyPlanScreenComponent { +internal class StudyPlanScreenComponentImpl(private val appGraph: AppGraph) : StudyPlanScreenComponent { private val toolbarComponent: GamificationToolbarComponent = appGraph.buildGamificationToolbarComponent(GamificationToolbarScreen.STUDY_PLAN) @@ -17,6 +18,9 @@ class StudyPlanScreenComponentImpl(private val appGraph: AppGraph) : StudyPlanSc private val problemsLimitComponent: ProblemsLimitComponent = appGraph.buildProblemsLimitComponent(ProblemsLimitScreen.STUDY_PLAN) + private val usersQuestionnaireWidgetComponent: UsersQuestionnaireWidgetComponent = + appGraph.buildUsersQuestionnaireWidgetComponent() + private val studyPlanWidgetComponent: StudyPlanWidgetComponent = appGraph.buildStudyPlanWidgetComponent() @@ -29,6 +33,9 @@ class StudyPlanScreenComponentImpl(private val appGraph: AppGraph) : StudyPlanSc problemsLimitReducer = problemsLimitComponent.problemsLimitReducer, problemsLimitActionDispatcher = problemsLimitComponent.problemsLimitActionDispatcher, problemsLimitViewStateMapper = problemsLimitComponent.problemsLimitViewStateMapper, + usersQuestionnaireWidgetReducer = usersQuestionnaireWidgetComponent.usersQuestionnaireWidgetReducer, + usersQuestionnaireWidgetActionDispatcher = usersQuestionnaireWidgetComponent + .usersQuestionnaireWidgetActionDispatcher, studyPlanWidgetReducer = studyPlanWidgetComponent.studyPlanWidgetReducer, studyPlanWidgetDispatcher = studyPlanWidgetComponent.studyPlanWidgetDispatcher, studyPlanWidgetViewStateMapper = studyPlanWidgetComponent.studyPlanWidgetViewStateMapper, diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/study_plan/screen/injection/StudyPlanScreenFeatureBuilder.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/study_plan/screen/injection/StudyPlanScreenFeatureBuilder.kt index ddba9eb893..47c05e2446 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/study_plan/screen/injection/StudyPlanScreenFeatureBuilder.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/study_plan/screen/injection/StudyPlanScreenFeatureBuilder.kt @@ -22,6 +22,9 @@ import org.hyperskill.app.study_plan.widget.presentation.StudyPlanWidgetActionDi import org.hyperskill.app.study_plan.widget.presentation.StudyPlanWidgetFeature import org.hyperskill.app.study_plan.widget.presentation.StudyPlanWidgetReducer import org.hyperskill.app.study_plan.widget.view.mapper.StudyPlanWidgetViewStateMapper +import org.hyperskill.app.users_questionnaire.widget.presentation.UsersQuestionnaireWidgetActionDispatcher +import org.hyperskill.app.users_questionnaire.widget.presentation.UsersQuestionnaireWidgetFeature +import org.hyperskill.app.users_questionnaire.widget.presentation.UsersQuestionnaireWidgetReducer import ru.nobird.app.core.model.safeCast import ru.nobird.app.presentation.redux.dispatcher.transform import ru.nobird.app.presentation.redux.dispatcher.wrapWithActionDispatcher @@ -37,6 +40,8 @@ internal object StudyPlanScreenFeatureBuilder { toolbarActionDispatcher: GamificationToolbarActionDispatcher, problemsLimitReducer: ProblemsLimitReducer, problemsLimitActionDispatcher: ProblemsLimitActionDispatcher, + usersQuestionnaireWidgetReducer: UsersQuestionnaireWidgetReducer, + usersQuestionnaireWidgetActionDispatcher: UsersQuestionnaireWidgetActionDispatcher, studyPlanWidgetReducer: StudyPlanWidgetReducer, studyPlanWidgetDispatcher: StudyPlanWidgetActionDispatcher, problemsLimitViewStateMapper: ProblemsLimitViewStateMapper, @@ -48,6 +53,7 @@ internal object StudyPlanScreenFeatureBuilder { val studyPlanScreenReducer = StudyPlanScreenReducer( toolbarReducer = toolbarReducer, problemsLimitReducer = problemsLimitReducer, + usersQuestionnaireWidgetReducer = usersQuestionnaireWidgetReducer, studyPlanWidgetReducer = studyPlanWidgetReducer ).wrapWithLogger(buildVariant, logger, LOG_TAG) val studyPlanScreenActionDispatcher = StudyPlanScreenActionDispatcher( @@ -64,6 +70,7 @@ internal object StudyPlanScreenFeatureBuilder { StudyPlanScreenFeature.State( toolbarState = GamificationToolbarFeature.State.Idle, problemsLimitState = ProblemsLimitFeature.State.Idle, + usersQuestionnaireWidgetState = UsersQuestionnaireWidgetFeature.State.Idle, studyPlanWidgetState = StudyPlanWidgetFeature.State() ), reducer = studyPlanScreenReducer @@ -86,6 +93,14 @@ internal object StudyPlanScreenFeatureBuilder { transformMessage = StudyPlanScreenFeature.Message::ProblemsLimitMessage ) ) + .wrapWithActionDispatcher( + usersQuestionnaireWidgetActionDispatcher.transform( + transformAction = { + it.safeCast()?.action + }, + transformMessage = StudyPlanScreenFeature.Message::UsersQuestionnaireWidgetMessage + ) + ) .wrapWithActionDispatcher( studyPlanWidgetDispatcher.transform( transformAction = { diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/study_plan/screen/presentation/StudyPlanScreenFeature.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/study_plan/screen/presentation/StudyPlanScreenFeature.kt index 7e88745086..31f672844b 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/study_plan/screen/presentation/StudyPlanScreenFeature.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/study_plan/screen/presentation/StudyPlanScreenFeature.kt @@ -7,11 +7,13 @@ import org.hyperskill.app.problems_limit.presentation.ProblemsLimitFeature import org.hyperskill.app.problems_limit.presentation.ProblemsLimitFeature.isRefreshing import org.hyperskill.app.study_plan.widget.presentation.StudyPlanWidgetFeature import org.hyperskill.app.study_plan.widget.view.model.StudyPlanWidgetViewState +import org.hyperskill.app.users_questionnaire.widget.presentation.UsersQuestionnaireWidgetFeature object StudyPlanScreenFeature { internal data class State( val toolbarState: GamificationToolbarFeature.State, val problemsLimitState: ProblemsLimitFeature.State, + val usersQuestionnaireWidgetState: UsersQuestionnaireWidgetFeature.State, val studyPlanWidgetState: StudyPlanWidgetFeature.State ) { val isRefreshing: Boolean @@ -24,6 +26,7 @@ object StudyPlanScreenFeature { val trackTitle: String?, val toolbarViewState: GamificationToolbarFeature.ViewState, val problemsLimitViewState: ProblemsLimitFeature.ViewState, + val usersQuestionnaireWidgetState: UsersQuestionnaireWidgetFeature.State, val studyPlanWidgetViewState: StudyPlanWidgetViewState, val isRefreshing: Boolean ) @@ -47,6 +50,10 @@ object StudyPlanScreenFeature { val message: ProblemsLimitFeature.Message ) : Message + data class UsersQuestionnaireWidgetMessage( + val message: UsersQuestionnaireWidgetFeature.Message + ) : Message + data class StudyPlanWidgetMessage( val message: StudyPlanWidgetFeature.Message ) : Message @@ -62,6 +69,10 @@ object StudyPlanScreenFeature { val viewAction: ProblemsLimitFeature.Action.ViewAction ) : ViewAction + data class UsersQuestionnaireWidgetViewAction( + val viewAction: UsersQuestionnaireWidgetFeature.Action.ViewAction + ) : ViewAction + data class StudyPlanWidgetViewAction( val viewAction: StudyPlanWidgetFeature.Action.ViewAction ) : ViewAction @@ -75,12 +86,16 @@ object StudyPlanScreenFeature { val action: GamificationToolbarFeature.Action ) : InternalAction - data class StudyPlanWidgetAction( - val action: StudyPlanWidgetFeature.Action - ) : InternalAction - data class ProblemsLimitAction( val action: ProblemsLimitFeature.Action ) : InternalAction + + data class UsersQuestionnaireWidgetAction( + val action: UsersQuestionnaireWidgetFeature.Action + ) : InternalAction + + data class StudyPlanWidgetAction( + val action: StudyPlanWidgetFeature.Action + ) : InternalAction } } \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/study_plan/screen/presentation/StudyPlanScreenReducer.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/study_plan/screen/presentation/StudyPlanScreenReducer.kt index cadeb88003..1101a985df 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/study_plan/screen/presentation/StudyPlanScreenReducer.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/study_plan/screen/presentation/StudyPlanScreenReducer.kt @@ -9,6 +9,8 @@ import org.hyperskill.app.study_plan.domain.analytic.StudyPlanClickedRetryConten import org.hyperskill.app.study_plan.domain.analytic.StudyPlanViewedHyperskillAnalyticEvent import org.hyperskill.app.study_plan.widget.presentation.StudyPlanWidgetFeature import org.hyperskill.app.study_plan.widget.presentation.StudyPlanWidgetReducer +import org.hyperskill.app.users_questionnaire.widget.presentation.UsersQuestionnaireWidgetFeature +import org.hyperskill.app.users_questionnaire.widget.presentation.UsersQuestionnaireWidgetReducer import ru.nobird.app.presentation.redux.reducer.StateReducer internal typealias StudyPlanScreenReducerResult = Pair> @@ -16,6 +18,7 @@ internal typealias StudyPlanScreenReducerResult = Pair { override fun reduce( @@ -46,6 +49,13 @@ internal class StudyPlanScreenReducer( reduceProblemsLimitMessage(state.problemsLimitState, message.message) state.copy(problemsLimitState = problemsLimitState) to problemsLimitActions } + is StudyPlanScreenFeature.Message.UsersQuestionnaireWidgetMessage -> { + val (usersQuestionnaireWidgetState, usersQuestionnaireWidgetActions) = + reduceUsersQuestionnaireWidgetMessage(state.usersQuestionnaireWidgetState, message.message) + state.copy( + usersQuestionnaireWidgetState = usersQuestionnaireWidgetState + ) to usersQuestionnaireWidgetActions + } is StudyPlanScreenFeature.Message.StudyPlanWidgetMessage -> { val (widgetState, widgetActions) = reduceStudyPlanWidgetMessage(state.studyPlanWidgetState, message.message) @@ -74,6 +84,11 @@ internal class StudyPlanScreenReducer( state.problemsLimitState, ProblemsLimitFeature.InternalMessage.Initialize(forceUpdate = retryContentLoadingClicked) ) + val (usersQuestionnaireWidgetState, usersQuestionnaireWidgetActions) = + reduceUsersQuestionnaireWidgetMessage( + state.usersQuestionnaireWidgetState, + UsersQuestionnaireWidgetFeature.InternalMessage.Initialize + ) val (studyPlanState, studyPlanActions) = reduceStudyPlanWidgetMessage( state.studyPlanWidgetState, @@ -90,11 +105,18 @@ internal class StudyPlanScreenReducer( emptySet() } + val actions = toolbarActions + + problemsLimitActions + + usersQuestionnaireWidgetActions + + studyPlanActions + + analyticActions + return state.copy( toolbarState = toolbarState, problemsLimitState = problemsLimitState, + usersQuestionnaireWidgetState = usersQuestionnaireWidgetState, studyPlanWidgetState = studyPlanState - ) to (toolbarActions + problemsLimitActions + studyPlanActions + analyticActions) + ) to actions } private fun handlePullToRefreshMessage( @@ -163,6 +185,26 @@ internal class StudyPlanScreenReducer( return problemsLimitState to actions } + private fun reduceUsersQuestionnaireWidgetMessage( + state: UsersQuestionnaireWidgetFeature.State, + message: UsersQuestionnaireWidgetFeature.Message + ): Pair> { + val (usersQuestionnaireWidgetState, usersQuestionnaireWidgetActions) = + usersQuestionnaireWidgetReducer.reduce(state, message) + + val actions = usersQuestionnaireWidgetActions + .map { + if (it is UsersQuestionnaireWidgetFeature.Action.ViewAction) { + StudyPlanScreenFeature.Action.ViewAction.UsersQuestionnaireWidgetViewAction(it) + } else { + StudyPlanScreenFeature.InternalAction.UsersQuestionnaireWidgetAction(it) + } + } + .toSet() + + return usersQuestionnaireWidgetState to actions + } + private fun reduceStudyPlanWidgetMessage( state: StudyPlanWidgetFeature.State, message: StudyPlanWidgetFeature.Message diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/study_plan/screen/view/StudyPlanScreenViewStateMapper.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/study_plan/screen/view/StudyPlanScreenViewStateMapper.kt index 7726fa2751..81162a39dc 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/study_plan/screen/view/StudyPlanScreenViewStateMapper.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/study_plan/screen/view/StudyPlanScreenViewStateMapper.kt @@ -17,6 +17,7 @@ internal class StudyPlanScreenViewStateMapper( trackTitle = getTrackTitle(state), toolbarViewState = GamificationToolbarViewStateMapper.map(state.toolbarState), problemsLimitViewState = problemsLimitViewStateMapper.mapState(state.problemsLimitState), + usersQuestionnaireWidgetState = state.usersQuestionnaireWidgetState, studyPlanWidgetViewState = studyPlanWidgetViewStateMapper.map(state.studyPlanWidgetState), isRefreshing = state.isRefreshing ) diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/users_questionnaire/cache/UsersQuestionnaireCacheDataSourceImpl.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/users_questionnaire/cache/UsersQuestionnaireCacheDataSourceImpl.kt new file mode 100644 index 0000000000..8923ed2bda --- /dev/null +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/users_questionnaire/cache/UsersQuestionnaireCacheDataSourceImpl.kt @@ -0,0 +1,15 @@ +package org.hyperskill.app.users_questionnaire.cache + +import com.russhwolf.settings.Settings +import org.hyperskill.app.users_questionnaire.data.source.UsersQuestionnaireCacheDataSource + +internal class UsersQuestionnaireCacheDataSourceImpl( + private val settings: Settings +) : UsersQuestionnaireCacheDataSource { + override fun getIsUsersQuestionnaireWidgetHidden(): Boolean = + settings.getBoolean(UsersQuestionnaireCacheKeyValues.USERS_QUESTIONNAIRE_WIDGET_HIDDEN, false) + + override fun setIsUsersQuestionnaireWidgetHidden(isHidden: Boolean) { + settings.putBoolean(UsersQuestionnaireCacheKeyValues.USERS_QUESTIONNAIRE_WIDGET_HIDDEN, isHidden) + } +} \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/users_questionnaire/cache/UsersQuestionnaireCacheKeyValues.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/users_questionnaire/cache/UsersQuestionnaireCacheKeyValues.kt new file mode 100644 index 0000000000..7c89f915eb --- /dev/null +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/users_questionnaire/cache/UsersQuestionnaireCacheKeyValues.kt @@ -0,0 +1,5 @@ +package org.hyperskill.app.users_questionnaire.cache + +internal object UsersQuestionnaireCacheKeyValues { + const val USERS_QUESTIONNAIRE_WIDGET_HIDDEN = "users_questionnaire_widget_hidden" +} \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/users_questionnaire/data/repository/UsersQuestionnaireRepositoryImpl.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/users_questionnaire/data/repository/UsersQuestionnaireRepositoryImpl.kt new file mode 100644 index 0000000000..72dd17a349 --- /dev/null +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/users_questionnaire/data/repository/UsersQuestionnaireRepositoryImpl.kt @@ -0,0 +1,15 @@ +package org.hyperskill.app.users_questionnaire.data.repository + +import org.hyperskill.app.users_questionnaire.data.source.UsersQuestionnaireCacheDataSource +import org.hyperskill.app.users_questionnaire.domain.repository.UsersQuestionnaireRepository + +internal class UsersQuestionnaireRepositoryImpl( + private val usersQuestionnaireCacheDataSource: UsersQuestionnaireCacheDataSource +) : UsersQuestionnaireRepository { + override fun getIsUsersQuestionnaireWidgetHidden(): Boolean = + usersQuestionnaireCacheDataSource.getIsUsersQuestionnaireWidgetHidden() + + override fun setIsUsersQuestionnaireWidgetHidden(isHidden: Boolean) { + usersQuestionnaireCacheDataSource.setIsUsersQuestionnaireWidgetHidden(isHidden) + } +} \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/users_questionnaire/data/source/UsersQuestionnaireCacheDataSource.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/users_questionnaire/data/source/UsersQuestionnaireCacheDataSource.kt new file mode 100644 index 0000000000..f00a209beb --- /dev/null +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/users_questionnaire/data/source/UsersQuestionnaireCacheDataSource.kt @@ -0,0 +1,6 @@ +package org.hyperskill.app.users_questionnaire.data.source + +interface UsersQuestionnaireCacheDataSource { + fun getIsUsersQuestionnaireWidgetHidden(): Boolean + fun setIsUsersQuestionnaireWidgetHidden(isHidden: Boolean) +} \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/users_questionnaire/domain/repository/UsersQuestionnaireRepository.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/users_questionnaire/domain/repository/UsersQuestionnaireRepository.kt new file mode 100644 index 0000000000..34301dab72 --- /dev/null +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/users_questionnaire/domain/repository/UsersQuestionnaireRepository.kt @@ -0,0 +1,6 @@ +package org.hyperskill.app.users_questionnaire.domain.repository + +interface UsersQuestionnaireRepository { + fun getIsUsersQuestionnaireWidgetHidden(): Boolean + fun setIsUsersQuestionnaireWidgetHidden(isHidden: Boolean) +} \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/users_questionnaire/injection/UsersQuestionnaireDataComponent.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/users_questionnaire/injection/UsersQuestionnaireDataComponent.kt new file mode 100644 index 0000000000..5664e2229d --- /dev/null +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/users_questionnaire/injection/UsersQuestionnaireDataComponent.kt @@ -0,0 +1,7 @@ +package org.hyperskill.app.users_questionnaire.injection + +import org.hyperskill.app.users_questionnaire.domain.repository.UsersQuestionnaireRepository + +interface UsersQuestionnaireDataComponent { + val usersQuestionnaireRepository: UsersQuestionnaireRepository +} \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/users_questionnaire/injection/UsersQuestionnaireDataComponentImpl.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/users_questionnaire/injection/UsersQuestionnaireDataComponentImpl.kt new file mode 100644 index 0000000000..2f8ad533b5 --- /dev/null +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/users_questionnaire/injection/UsersQuestionnaireDataComponentImpl.kt @@ -0,0 +1,17 @@ +package org.hyperskill.app.users_questionnaire.injection + +import org.hyperskill.app.core.injection.AppGraph +import org.hyperskill.app.users_questionnaire.cache.UsersQuestionnaireCacheDataSourceImpl +import org.hyperskill.app.users_questionnaire.data.repository.UsersQuestionnaireRepositoryImpl +import org.hyperskill.app.users_questionnaire.data.source.UsersQuestionnaireCacheDataSource +import org.hyperskill.app.users_questionnaire.domain.repository.UsersQuestionnaireRepository + +internal class UsersQuestionnaireDataComponentImpl( + appGraph: AppGraph +) : UsersQuestionnaireDataComponent { + private val usersQuestionnaireCacheDataSource: UsersQuestionnaireCacheDataSource = + UsersQuestionnaireCacheDataSourceImpl(appGraph.commonComponent.settings) + + override val usersQuestionnaireRepository: UsersQuestionnaireRepository + get() = UsersQuestionnaireRepositoryImpl(usersQuestionnaireCacheDataSource) +} \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/users_questionnaire/widget/domain/analytic/UsersQuestionnaireWidgetClickedCloseHyperskillAnalyticEvent.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/users_questionnaire/widget/domain/analytic/UsersQuestionnaireWidgetClickedCloseHyperskillAnalyticEvent.kt new file mode 100644 index 0000000000..d3c8f4b2df --- /dev/null +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/users_questionnaire/widget/domain/analytic/UsersQuestionnaireWidgetClickedCloseHyperskillAnalyticEvent.kt @@ -0,0 +1,29 @@ +package org.hyperskill.app.users_questionnaire.widget.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.HyperskillAnalyticPart +import org.hyperskill.app.analytic.domain.model.hyperskill.HyperskillAnalyticRoute +import org.hyperskill.app.analytic.domain.model.hyperskill.HyperskillAnalyticTarget + +/** + * Represents an analytic event for clicking on a close button in the users questionnaire widget. + * + * JSON payload: + * ``` + * { + * "route": "/study-plan", + * "action": "click", + * "part": "users_questionnaire_widget", + * "target": "close" + * } + * ``` + * + * @see HyperskillAnalyticEvent + */ +object UsersQuestionnaireWidgetClickedCloseHyperskillAnalyticEvent : HyperskillAnalyticEvent( + HyperskillAnalyticRoute.StudyPlan(), + HyperskillAnalyticAction.CLICK, + HyperskillAnalyticPart.USERS_QUESTIONNAIRE_WIDGET, + HyperskillAnalyticTarget.CLOSE +) \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/users_questionnaire/widget/domain/analytic/UsersQuestionnaireWidgetClickedHyperskillAnalyticEvent.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/users_questionnaire/widget/domain/analytic/UsersQuestionnaireWidgetClickedHyperskillAnalyticEvent.kt new file mode 100644 index 0000000000..b234cb1036 --- /dev/null +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/users_questionnaire/widget/domain/analytic/UsersQuestionnaireWidgetClickedHyperskillAnalyticEvent.kt @@ -0,0 +1,26 @@ +package org.hyperskill.app.users_questionnaire.widget.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.HyperskillAnalyticPart +import org.hyperskill.app.analytic.domain.model.hyperskill.HyperskillAnalyticRoute + +/** + * Represents a click analytic event of the users questionnaire widget. + * + * JSON payload: + * ``` + * { + * "route": "/study-plan", + * "action": "click", + * "part": "users_questionnaire_widget" + * } + * ``` + * + * @see HyperskillAnalyticEvent + */ +object UsersQuestionnaireWidgetClickedHyperskillAnalyticEvent : HyperskillAnalyticEvent( + HyperskillAnalyticRoute.StudyPlan(), + HyperskillAnalyticAction.CLICK, + HyperskillAnalyticPart.USERS_QUESTIONNAIRE_WIDGET +) \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/users_questionnaire/widget/domain/analytic/UsersQuestionnaireWidgetViewedHyperskillAnalyticEvent.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/users_questionnaire/widget/domain/analytic/UsersQuestionnaireWidgetViewedHyperskillAnalyticEvent.kt new file mode 100644 index 0000000000..f3d7d7e5e7 --- /dev/null +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/users_questionnaire/widget/domain/analytic/UsersQuestionnaireWidgetViewedHyperskillAnalyticEvent.kt @@ -0,0 +1,23 @@ +package org.hyperskill.app.users_questionnaire.widget.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 a view analytic event of the users questionnaire widget. + * + * JSON payload: + * ``` + * { + * "route": "/study-plan/users-questionnaire-widget", + * "action": "view" + * } + * ``` + * + * @see HyperskillAnalyticEvent + */ +object UsersQuestionnaireWidgetViewedHyperskillAnalyticEvent : HyperskillAnalyticEvent( + HyperskillAnalyticRoute.StudyPlan.UsersQuestionnaireWidget(), + HyperskillAnalyticAction.VIEW +) \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/users_questionnaire/widget/injection/UsersQuestionnaireWidgetComponent.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/users_questionnaire/widget/injection/UsersQuestionnaireWidgetComponent.kt new file mode 100644 index 0000000000..ca037c75fc --- /dev/null +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/users_questionnaire/widget/injection/UsersQuestionnaireWidgetComponent.kt @@ -0,0 +1,9 @@ +package org.hyperskill.app.users_questionnaire.widget.injection + +import org.hyperskill.app.users_questionnaire.widget.presentation.UsersQuestionnaireWidgetActionDispatcher +import org.hyperskill.app.users_questionnaire.widget.presentation.UsersQuestionnaireWidgetReducer + +interface UsersQuestionnaireWidgetComponent { + val usersQuestionnaireWidgetReducer: UsersQuestionnaireWidgetReducer + val usersQuestionnaireWidgetActionDispatcher: UsersQuestionnaireWidgetActionDispatcher +} \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/users_questionnaire/widget/injection/UsersQuestionnaireWidgetComponentImpl.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/users_questionnaire/widget/injection/UsersQuestionnaireWidgetComponentImpl.kt new file mode 100644 index 0000000000..f2b2f4cff4 --- /dev/null +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/users_questionnaire/widget/injection/UsersQuestionnaireWidgetComponentImpl.kt @@ -0,0 +1,21 @@ +package org.hyperskill.app.users_questionnaire.widget.injection + +import org.hyperskill.app.core.injection.AppGraph +import org.hyperskill.app.core.presentation.ActionDispatcherOptions +import org.hyperskill.app.users_questionnaire.widget.presentation.UsersQuestionnaireWidgetActionDispatcher +import org.hyperskill.app.users_questionnaire.widget.presentation.UsersQuestionnaireWidgetReducer + +internal class UsersQuestionnaireWidgetComponentImpl( + private val appGraph: AppGraph +) : UsersQuestionnaireWidgetComponent { + override val usersQuestionnaireWidgetReducer: UsersQuestionnaireWidgetReducer + get() = UsersQuestionnaireWidgetReducer() + + override val usersQuestionnaireWidgetActionDispatcher: UsersQuestionnaireWidgetActionDispatcher + get() = UsersQuestionnaireWidgetActionDispatcher( + config = ActionDispatcherOptions(), + usersQuestionnaireRepository = appGraph.buildUsersQuestionnaireDataComponent().usersQuestionnaireRepository, + currentProfileStateRepository = appGraph.profileDataComponent.currentProfileStateRepository, + analyticInteractor = appGraph.analyticComponent.analyticInteractor + ) +} \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/users_questionnaire/widget/presentation/UsersQuestionnaireWidgetActionDispatcher.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/users_questionnaire/widget/presentation/UsersQuestionnaireWidgetActionDispatcher.kt new file mode 100644 index 0000000000..b1e0969564 --- /dev/null +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/users_questionnaire/widget/presentation/UsersQuestionnaireWidgetActionDispatcher.kt @@ -0,0 +1,80 @@ +package org.hyperskill.app.users_questionnaire.widget.presentation + +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.onEach +import org.hyperskill.app.analytic.domain.interactor.AnalyticInteractor +import org.hyperskill.app.core.presentation.ActionDispatcherOptions +import org.hyperskill.app.profile.domain.model.isMobileUsersQuestionnaireEnabled +import org.hyperskill.app.profile.domain.repository.CurrentProfileStateRepository +import org.hyperskill.app.users_questionnaire.domain.repository.UsersQuestionnaireRepository +import org.hyperskill.app.users_questionnaire.widget.presentation.UsersQuestionnaireWidgetFeature.Action +import org.hyperskill.app.users_questionnaire.widget.presentation.UsersQuestionnaireWidgetFeature.InternalAction +import org.hyperskill.app.users_questionnaire.widget.presentation.UsersQuestionnaireWidgetFeature.InternalMessage +import org.hyperskill.app.users_questionnaire.widget.presentation.UsersQuestionnaireWidgetFeature.Message +import ru.nobird.app.presentation.redux.dispatcher.CoroutineActionDispatcher + +class UsersQuestionnaireWidgetActionDispatcher( + config: ActionDispatcherOptions, + private val usersQuestionnaireRepository: UsersQuestionnaireRepository, + private val currentProfileStateRepository: CurrentProfileStateRepository, + private val analyticInteractor: AnalyticInteractor +) : CoroutineActionDispatcher(config.createConfig()) { + init { + currentProfileStateRepository.changes + .map { it.features.isMobileUsersQuestionnaireEnabled } + .distinctUntilChanged() + .onEach { isMobileUsersQuestionnaireEnabled -> + onNewMessage( + InternalMessage.UsersQuestionnaireFeatureFlagChanged( + isMobileUsersQuestionnaireEnabled + ) + ) + } + .launchIn(actionScope) + } + + override suspend fun doSuspendableAction(action: Action) { + when (action) { + InternalAction.FetchUsersQuestionnaireWidgetData -> + handleFetchUsersQuestionnaireWidgetDataAction(::onNewMessage) + InternalAction.FetchUsersQuestionnaireUrl -> + handleFetchUsersQuestionnaireUrlAction(::onNewMessage) + InternalAction.HideUsersQuestionnaireWidget -> + usersQuestionnaireRepository.setIsUsersQuestionnaireWidgetHidden(true) + is InternalAction.LogAnalyticEvent -> + analyticInteractor.logEvent(action.analyticEvent) + else -> { + // no op + } + } + } + + private suspend fun handleFetchUsersQuestionnaireWidgetDataAction(onNewMessage: (Message) -> Unit) { + val isMobileUsersQuestionnaireEnabled = currentProfileStateRepository + .getState(forceUpdate = false) + .map { it.features.isMobileUsersQuestionnaireEnabled } + .getOrDefault(defaultValue = false) + + onNewMessage( + InternalMessage.FetchUsersQuestionnaireWidgetDataResult( + isUsersQuestionnaireEnabled = isMobileUsersQuestionnaireEnabled, + isUsersQuestionnaireWidgetHidden = usersQuestionnaireRepository.getIsUsersQuestionnaireWidgetHidden() + ) + ) + } + + private suspend fun handleFetchUsersQuestionnaireUrlAction(onNewMessage: (Message) -> Unit) { + val currentUserId = currentProfileStateRepository + .getState(forceUpdate = false) + .map { it.id } + .getOrNull() ?: return + + onNewMessage( + InternalMessage.FetchUsersQuestionnaireUrlResult( + "https://docs.google.com/forms/d/e/1FAIpQLSf6k3woOqZr2zfmbBNvA71DyD04LN4v7l6k-vuyqdAmdMUnOA/viewform?usp=pp_url&entry.193481738=$currentUserId" // ktlint-disable + ) + ) + } +} \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/users_questionnaire/widget/presentation/UsersQuestionnaireWidgetFeature.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/users_questionnaire/widget/presentation/UsersQuestionnaireWidgetFeature.kt new file mode 100644 index 0000000000..35033ff5ef --- /dev/null +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/users_questionnaire/widget/presentation/UsersQuestionnaireWidgetFeature.kt @@ -0,0 +1,52 @@ +package org.hyperskill.app.users_questionnaire.widget.presentation + +import org.hyperskill.app.analytic.domain.model.AnalyticEvent + +object UsersQuestionnaireWidgetFeature { + sealed interface State { + object Idle : State + object Loading : State + object Hidden : State + object Visible : State + } + + sealed interface Message { + object CloseClicked : Message + object WidgetClicked : Message + + object ViewedEventMessage : Message + } + + internal sealed interface InternalMessage : Message { + object Initialize : InternalMessage + + data class FetchUsersQuestionnaireWidgetDataResult( + val isUsersQuestionnaireEnabled: Boolean, + val isUsersQuestionnaireWidgetHidden: Boolean + ) : InternalMessage + + data class UsersQuestionnaireFeatureFlagChanged( + val isUsersQuestionnaireEnabled: Boolean + ) : InternalMessage + + data class FetchUsersQuestionnaireUrlResult( + val url: String + ) : InternalMessage + } + + sealed interface Action { + sealed interface ViewAction : Action { + data class ShowUsersQuestionnaire(val url: String) : ViewAction + } + } + + internal sealed interface InternalAction : Action { + object FetchUsersQuestionnaireWidgetData : InternalAction + + object FetchUsersQuestionnaireUrl : InternalAction + + object HideUsersQuestionnaireWidget : InternalAction + + data class LogAnalyticEvent(val analyticEvent: AnalyticEvent) : InternalAction + } +} \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/users_questionnaire/widget/presentation/UsersQuestionnaireWidgetReducer.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/users_questionnaire/widget/presentation/UsersQuestionnaireWidgetReducer.kt new file mode 100644 index 0000000000..e6e5e29513 --- /dev/null +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/users_questionnaire/widget/presentation/UsersQuestionnaireWidgetReducer.kt @@ -0,0 +1,64 @@ +package org.hyperskill.app.users_questionnaire.widget.presentation + +import org.hyperskill.app.users_questionnaire.widget.domain.analytic.UsersQuestionnaireWidgetClickedCloseHyperskillAnalyticEvent +import org.hyperskill.app.users_questionnaire.widget.domain.analytic.UsersQuestionnaireWidgetClickedHyperskillAnalyticEvent +import org.hyperskill.app.users_questionnaire.widget.domain.analytic.UsersQuestionnaireWidgetViewedHyperskillAnalyticEvent +import org.hyperskill.app.users_questionnaire.widget.presentation.UsersQuestionnaireWidgetFeature.Action +import org.hyperskill.app.users_questionnaire.widget.presentation.UsersQuestionnaireWidgetFeature.InternalAction +import org.hyperskill.app.users_questionnaire.widget.presentation.UsersQuestionnaireWidgetFeature.InternalMessage +import org.hyperskill.app.users_questionnaire.widget.presentation.UsersQuestionnaireWidgetFeature.Message +import org.hyperskill.app.users_questionnaire.widget.presentation.UsersQuestionnaireWidgetFeature.State +import ru.nobird.app.presentation.redux.reducer.StateReducer + +class UsersQuestionnaireWidgetReducer : StateReducer { + override fun reduce(state: State, message: Message): Pair> = + when (message) { + InternalMessage.Initialize -> + if (state is State.Idle) { + State.Loading to setOf(InternalAction.FetchUsersQuestionnaireWidgetData) + } else { + null + } + is InternalMessage.FetchUsersQuestionnaireWidgetDataResult -> + if (message.isUsersQuestionnaireEnabled && !message.isUsersQuestionnaireWidgetHidden) { + State.Visible to emptySet() + } else { + State.Hidden to emptySet() + } + is InternalMessage.UsersQuestionnaireFeatureFlagChanged -> + if (state is State.Visible && !message.isUsersQuestionnaireEnabled) { + State.Hidden to emptySet() + } else { + null + } + Message.CloseClicked -> + if (state is State.Visible) { + State.Hidden to setOf( + InternalAction.HideUsersQuestionnaireWidget, + InternalAction.LogAnalyticEvent(UsersQuestionnaireWidgetClickedCloseHyperskillAnalyticEvent) + ) + } else { + null + } + Message.WidgetClicked -> + if (state is State.Visible) { + State.Hidden to setOf( + InternalAction.FetchUsersQuestionnaireUrl, + InternalAction.HideUsersQuestionnaireWidget, + InternalAction.LogAnalyticEvent(UsersQuestionnaireWidgetClickedHyperskillAnalyticEvent) + ) + } else { + null + } + is InternalMessage.FetchUsersQuestionnaireUrlResult -> + state to setOf(Action.ViewAction.ShowUsersQuestionnaire(url = message.url)) + Message.ViewedEventMessage -> + if (state is State.Visible) { + state to setOf( + InternalAction.LogAnalyticEvent(UsersQuestionnaireWidgetViewedHyperskillAnalyticEvent) + ) + } else { + null + } + } ?: (state to emptySet()) +} \ No newline at end of file diff --git a/shared/src/commonMain/resources/MR/base/strings.xml b/shared/src/commonMain/resources/MR/base/strings.xml index f3363cedad..37d6028e5f 100644 --- a/shared/src/commonMain/resources/MR/base/strings.xml +++ b/shared/src/commonMain/resources/MR/base/strings.xml @@ -532,6 +532,9 @@ Oops! We were unable to load the challenge. Reload + + Tell us about your time using our app + Interview preparation Oops! We were unable to load the interview widget diff --git a/shared/src/commonTest/kotlin/org/hyperskill/study_plan/screen/StudyPlanScreenTest.kt b/shared/src/commonTest/kotlin/org/hyperskill/study_plan/screen/StudyPlanScreenTest.kt index 93fb1ec329..7a574a43a3 100644 --- a/shared/src/commonTest/kotlin/org/hyperskill/study_plan/screen/StudyPlanScreenTest.kt +++ b/shared/src/commonTest/kotlin/org/hyperskill/study_plan/screen/StudyPlanScreenTest.kt @@ -17,11 +17,14 @@ import org.hyperskill.app.study_plan.screen.presentation.StudyPlanScreenFeature import org.hyperskill.app.study_plan.screen.presentation.StudyPlanScreenReducer import org.hyperskill.app.study_plan.widget.presentation.StudyPlanWidgetFeature import org.hyperskill.app.study_plan.widget.presentation.StudyPlanWidgetReducer +import org.hyperskill.app.users_questionnaire.widget.presentation.UsersQuestionnaireWidgetFeature +import org.hyperskill.app.users_questionnaire.widget.presentation.UsersQuestionnaireWidgetReducer class StudyPlanScreenTest { private val reducer = StudyPlanScreenReducer( GamificationToolbarReducer(GamificationToolbarScreen.STUDY_PLAN), ProblemsLimitReducer(ProblemsLimitScreen.STUDY_PLAN), + UsersQuestionnaireWidgetReducer(), StudyPlanWidgetReducer() ) @@ -60,6 +63,7 @@ class StudyPlanScreenTest { val expectedState = stubState( toolbarState = GamificationToolbarFeature.State.Loading, problemsLimitState = ProblemsLimitFeature.State.Loading, + questionnaireWidgetState = UsersQuestionnaireWidgetFeature.State.Loading, studyPlanWidgetState = StudyPlanWidgetFeature.State( sectionsStatus = StudyPlanWidgetFeature.ContentStatus.LOADING ) @@ -76,7 +80,13 @@ class StudyPlanScreenTest { private fun stubState( toolbarState: GamificationToolbarFeature.State = GamificationToolbarFeature.State.Idle, problemsLimitState: ProblemsLimitFeature.State = ProblemsLimitFeature.State.Idle, + questionnaireWidgetState: UsersQuestionnaireWidgetFeature.State = UsersQuestionnaireWidgetFeature.State.Idle, studyPlanWidgetState: StudyPlanWidgetFeature.State = StudyPlanWidgetFeature.State() ): StudyPlanScreenFeature.State = - StudyPlanScreenFeature.State(toolbarState, problemsLimitState, studyPlanWidgetState) + StudyPlanScreenFeature.State( + toolbarState, + problemsLimitState, + questionnaireWidgetState, + studyPlanWidgetState + ) } \ No newline at end of file From c59acc8758bc7fb2c9cd9f8fb31bf405e340d10d Mon Sep 17 00:00:00 2001 From: github-actions Date: Wed, 21 Feb 2024 02:17:16 +0000 Subject: [PATCH 078/104] Bump build number --- gradle/app.versions.toml | 2 +- .../NotificationServiceExtension/Info.plist | 2 +- .../iosHyperskillApp.xcodeproj/project.pbxproj | 16 ++++++++-------- iosHyperskillApp/iosHyperskillApp/Info.plist | 2 +- .../iosHyperskillAppTests/Info.plist | 2 +- .../iosHyperskillAppUITests/Info.plist | 2 +- 6 files changed, 13 insertions(+), 13 deletions(-) diff --git a/gradle/app.versions.toml b/gradle/app.versions.toml index 682ebe841f..6c083986f5 100644 --- a/gradle/app.versions.toml +++ b/gradle/app.versions.toml @@ -3,4 +3,4 @@ minSdk = '24' targetSdk = '33' compileSdk = '33' versionName = '1.50' -versionCode = '332' \ No newline at end of file +versionCode = '333' \ No newline at end of file diff --git a/iosHyperskillApp/NotificationServiceExtension/Info.plist b/iosHyperskillApp/NotificationServiceExtension/Info.plist index d7c0808032..6fccb404ab 100644 --- a/iosHyperskillApp/NotificationServiceExtension/Info.plist +++ b/iosHyperskillApp/NotificationServiceExtension/Info.plist @@ -9,7 +9,7 @@ CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleVersion - 337 + 338 CFBundleShortVersionString 1.50 CFBundlePackageType diff --git a/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj b/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj index 048ffd1c8c..5d9bfe0890 100644 --- a/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj +++ b/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj @@ -5061,7 +5061,7 @@ buildSettings = { CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 337; + CURRENT_PROJECT_VERSION = 338; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = 3DWS674B2M; GENERATE_INFOPLIST_FILE = NO; @@ -5082,7 +5082,7 @@ buildSettings = { CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 337; + CURRENT_PROJECT_VERSION = 338; DEVELOPMENT_TEAM = 3DWS674B2M; GENERATE_INFOPLIST_FILE = NO; INFOPLIST_FILE = iosHyperskillAppUITests/Info.plist; @@ -5103,7 +5103,7 @@ BUNDLE_LOADER = "$(TEST_HOST)"; CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 337; + CURRENT_PROJECT_VERSION = 338; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = 3DWS674B2M; INFOPLIST_FILE = iosHyperskillAppTests/Info.plist; @@ -5124,7 +5124,7 @@ BUNDLE_LOADER = "$(TEST_HOST)"; CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 337; + CURRENT_PROJECT_VERSION = 338; DEVELOPMENT_TEAM = 3DWS674B2M; INFOPLIST_FILE = iosHyperskillAppTests/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; @@ -5145,7 +5145,7 @@ CODE_SIGN_ENTITLEMENTS = NotificationServiceExtension/NotificationServiceExtension.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 337; + CURRENT_PROJECT_VERSION = 338; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = 3DWS674B2M; INFOPLIST_FILE = NotificationServiceExtension/Info.plist; @@ -5173,7 +5173,7 @@ CODE_SIGN_ENTITLEMENTS = NotificationServiceExtension/NotificationServiceExtension.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 337; + CURRENT_PROJECT_VERSION = 338; DEVELOPMENT_TEAM = 3DWS674B2M; INFOPLIST_FILE = NotificationServiceExtension/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; @@ -5318,7 +5318,7 @@ CODE_SIGN_ENTITLEMENTS = iosHyperskillApp/iosHyperskillApp.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 337; + CURRENT_PROJECT_VERSION = 338; DEVELOPMENT_ASSET_PATHS = "\"iosHyperskillApp/Preview Content\""; DEVELOPMENT_TEAM = 3DWS674B2M; ENABLE_PREVIEWS = YES; @@ -5354,7 +5354,7 @@ CODE_SIGN_ENTITLEMENTS = iosHyperskillApp/iosHyperskillApp.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 337; + CURRENT_PROJECT_VERSION = 338; DEVELOPMENT_ASSET_PATHS = "\"iosHyperskillApp/Preview Content\""; DEVELOPMENT_TEAM = 3DWS674B2M; ENABLE_PREVIEWS = YES; diff --git a/iosHyperskillApp/iosHyperskillApp/Info.plist b/iosHyperskillApp/iosHyperskillApp/Info.plist index f97a1cca24..8df22fc7ec 100644 --- a/iosHyperskillApp/iosHyperskillApp/Info.plist +++ b/iosHyperskillApp/iosHyperskillApp/Info.plist @@ -36,7 +36,7 @@ CFBundleVersion - 337 + 338 FirebaseAppDelegateProxyEnabled FirebaseMessagingAutoInitEnabled diff --git a/iosHyperskillApp/iosHyperskillAppTests/Info.plist b/iosHyperskillApp/iosHyperskillAppTests/Info.plist index 393ab32655..74a38df57c 100644 --- a/iosHyperskillApp/iosHyperskillAppTests/Info.plist +++ b/iosHyperskillApp/iosHyperskillAppTests/Info.plist @@ -15,6 +15,6 @@ CFBundleShortVersionString 1.50 CFBundleVersion - 337 + 338 diff --git a/iosHyperskillApp/iosHyperskillAppUITests/Info.plist b/iosHyperskillApp/iosHyperskillAppUITests/Info.plist index 0ee3e870ff..7ce190324d 100644 --- a/iosHyperskillApp/iosHyperskillAppUITests/Info.plist +++ b/iosHyperskillApp/iosHyperskillAppUITests/Info.plist @@ -15,6 +15,6 @@ CFBundleShortVersionString 1.50 CFBundleVersion - 337 + 338 From 95cc70a5892edb4818646526587e0fd9b862ca7e Mon Sep 17 00:00:00 2001 From: Aleksandr Zhukov Date: Wed, 21 Feb 2024 15:22:34 +0400 Subject: [PATCH 079/104] Android remove iframe element from content (#912) --- .../src/main/assets/scripts/remove_iframes.js | 6 ++++++ .../app/android/latex/view/mapper/LatexTextMapper.kt | 4 +++- .../view/model/block/RemoveIFrameElementsInjection.kt | 7 +++++++ 3 files changed, 16 insertions(+), 1 deletion(-) create mode 100644 androidHyperskillApp/src/main/assets/scripts/remove_iframes.js create mode 100644 androidHyperskillApp/src/main/java/org/hyperskill/app/android/latex/view/model/block/RemoveIFrameElementsInjection.kt diff --git a/androidHyperskillApp/src/main/assets/scripts/remove_iframes.js b/androidHyperskillApp/src/main/assets/scripts/remove_iframes.js new file mode 100644 index 0000000000..ce2e1906fd --- /dev/null +++ b/androidHyperskillApp/src/main/assets/scripts/remove_iframes.js @@ -0,0 +1,6 @@ +addEventListener('DOMContentLoaded', () => { + document.querySelectorAll('iframe') + .forEach(element => + element.parentNode.removeChild(element) + ); +}); \ No newline at end of file diff --git a/androidHyperskillApp/src/main/java/org/hyperskill/app/android/latex/view/mapper/LatexTextMapper.kt b/androidHyperskillApp/src/main/java/org/hyperskill/app/android/latex/view/mapper/LatexTextMapper.kt index d5752b8060..cbeaefe9b2 100644 --- a/androidHyperskillApp/src/main/java/org/hyperskill/app/android/latex/view/mapper/LatexTextMapper.kt +++ b/androidHyperskillApp/src/main/java/org/hyperskill/app/android/latex/view/mapper/LatexTextMapper.kt @@ -10,6 +10,7 @@ import org.hyperskill.app.android.latex.view.model.block.HorizontalScrollBlock import org.hyperskill.app.android.latex.view.model.block.KotlinRunnableSamplesScriptBlock import org.hyperskill.app.android.latex.view.model.block.LatexScriptBlock import org.hyperskill.app.android.latex.view.model.block.MetaBlock +import org.hyperskill.app.android.latex.view.model.block.RemoveIFrameElementsInjection import org.hyperskill.app.android.latex.view.model.block.WebScriptBlock import org.hyperskill.app.android.latex.view.model.rule.RelativePathContentRule import org.hyperskill.app.android.latex.view.resolvers.OlLiTagHandler @@ -22,7 +23,8 @@ class LatexTextMapper(networkEndpointConfigInfo: NetworkEndpointConfigInfo) { KotlinRunnableSamplesScriptBlock(), LatexScriptBlock(), WebScriptBlock(), - DataMobileHiddenBlock + DataMobileHiddenBlock, + RemoveIFrameElementsInjection ) private val regularBlocks = diff --git a/androidHyperskillApp/src/main/java/org/hyperskill/app/android/latex/view/model/block/RemoveIFrameElementsInjection.kt b/androidHyperskillApp/src/main/java/org/hyperskill/app/android/latex/view/model/block/RemoveIFrameElementsInjection.kt new file mode 100644 index 0000000000..0f3e8bcd16 --- /dev/null +++ b/androidHyperskillApp/src/main/java/org/hyperskill/app/android/latex/view/model/block/RemoveIFrameElementsInjection.kt @@ -0,0 +1,7 @@ +package org.hyperskill.app.android.latex.view.model.block + +object RemoveIFrameElementsInjection : ContentBlock { + override val header: String = """ + + """.trimIndent() +} \ No newline at end of file From bc26b8e27f919a97c7cecca5fa0fa031afa8bdf7 Mon Sep 17 00:00:00 2001 From: github-actions Date: Wed, 21 Feb 2024 11:23:06 +0000 Subject: [PATCH 080/104] Android: Bump build number --- gradle/app.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/app.versions.toml b/gradle/app.versions.toml index 6c083986f5..bc8098d7f3 100644 --- a/gradle/app.versions.toml +++ b/gradle/app.versions.toml @@ -3,4 +3,4 @@ minSdk = '24' targetSdk = '33' compileSdk = '33' versionName = '1.50' -versionCode = '333' \ No newline at end of file +versionCode = '334' \ No newline at end of file From ac87a460f3dabf4c530e35781826babf1aeff1f6 Mon Sep 17 00:00:00 2001 From: Ivan Magda Date: Thu, 22 Feb 2024 12:57:56 +0700 Subject: [PATCH 081/104] iOS: Add AppRouter (#915) --- .../project.pbxproj | 4 + .../Sources/Modules/App/AppAssembly.swift | 5 +- .../App/ViewControllers/AppRouter.swift | 151 +++++++++++++++++ .../ViewControllers/AppViewController.swift | 155 +++--------------- .../ProfileSettingsViewModel.swift | 2 +- 5 files changed, 181 insertions(+), 136 deletions(-) create mode 100644 iosHyperskillApp/iosHyperskillApp/Sources/Modules/App/ViewControllers/AppRouter.swift diff --git a/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj b/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj index 6bfc63310f..0b9de8eb04 100644 --- a/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj +++ b/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj @@ -338,6 +338,7 @@ 2C98C7AD2850B93100857783 /* icons.css in Resources */ = {isa = PBXBuildFile; fileRef = 2C98C7A52850B93100857783 /* icons.css */; }; 2C99B0FD2A141BF10018627B /* StudyPlanSectionItemBadgesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C99B0FC2A141BF10018627B /* StudyPlanSectionItemBadgesView.swift */; }; 2C99B1002A14255F0018627B /* StudyPlanWidgetViewStateSectionItemStateWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C99B0FF2A14255F0018627B /* StudyPlanWidgetViewStateSectionItemStateWrapper.swift */; }; + 2C9ACAA62B870E3D00FE63FA /* AppRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C9ACAA52B870E3D00FE63FA /* AppRouter.swift */; }; 2C9B66C427ECA73700569645 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C9B66C327ECA73700569645 /* AppDelegate.swift */; }; 2C9CC1BD280920B5006604D7 /* KeyboardManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C9CC1BC280920B5006604D7 /* KeyboardManager.swift */; }; 2C9CC1BF28092E06006604D7 /* AuthAdaptiveContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C9CC1BE28092E06006604D7 /* AuthAdaptiveContentView.swift */; }; @@ -1033,6 +1034,7 @@ 2C98C7A52850B93100857783 /* icons.css */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.css; path = icons.css; sourceTree = ""; }; 2C99B0FC2A141BF10018627B /* StudyPlanSectionItemBadgesView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StudyPlanSectionItemBadgesView.swift; sourceTree = ""; }; 2C99B0FF2A14255F0018627B /* StudyPlanWidgetViewStateSectionItemStateWrapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StudyPlanWidgetViewStateSectionItemStateWrapper.swift; sourceTree = ""; }; + 2C9ACAA52B870E3D00FE63FA /* AppRouter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppRouter.swift; sourceTree = ""; }; 2C9B66C327ECA73700569645 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 2C9CC1BC280920B5006604D7 /* KeyboardManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyboardManager.swift; sourceTree = ""; }; 2C9CC1BE28092E06006604D7 /* AuthAdaptiveContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthAdaptiveContentView.swift; sourceTree = ""; }; @@ -3357,6 +3359,7 @@ 2CDF14D328EEFA850060D972 /* ViewControllers */ = { isa = PBXGroup; children = ( + 2C9ACAA52B870E3D00FE63FA /* AppRouter.swift */, 2CA368E428EEAC39004F7FD8 /* AppViewController.swift */, 2CDF14D428EEFA9C0060D972 /* TabBar */, ); @@ -4574,6 +4577,7 @@ 2C005DCC27EF5B0300DC6503 /* GoogleServiceInfo.swift in Sources */, 2CBD191D291D3BF400F5FB0B /* UIKitRoundedRectangleButton.swift in Sources */, 2C078CE92AE29D0600D97E24 /* StepQuizFillBlanksViewDataMapperCache.swift in Sources */, + 2C9ACAA62B870E3D00FE63FA /* AppRouter.swift in Sources */, E9A6250F28ABAE83009423EE /* WelcomeViewModel.swift in Sources */, 2C0F3CFC2A80A47600947C35 /* BadgeDetailsModalView.swift in Sources */, E97BEA1E2977D26F00348EEC /* TopicCompletedModalViewController.swift in Sources */, diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/App/AppAssembly.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/App/AppAssembly.swift index 1f923214b6..abd7daa8a3 100644 --- a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/App/AppAssembly.swift +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/App/AppAssembly.swift @@ -17,8 +17,11 @@ final class AppAssembly: UIKitAssembly { feature: feature ) - let viewController = AppViewController(viewModel: viewModel) + let router = AppRouter() + + let viewController = AppViewController(viewModel: viewModel, router: router) viewModel.viewController = viewController + router.viewController = viewController return viewController } diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/App/ViewControllers/AppRouter.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/App/ViewControllers/AppRouter.swift new file mode 100644 index 0000000000..91d2cac7f0 --- /dev/null +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/App/ViewControllers/AppRouter.swift @@ -0,0 +1,151 @@ +import shared +import SwiftUI +import UIKit + +final class AppRouter { + weak var viewController: UIViewController? + + func route(_ route: Route) { + guard let viewController else { + return assertionFailure("AppRouter: viewController is nil") + } + + let viewControllerToPresent: UIViewController = { + switch route { + case .auth(let isInSignUpMode, let moduleOutput): + let assembly = AuthSocialAssembly( + isInSignUpMode: isInSignUpMode, + output: moduleOutput + ) + return UIHostingController(rootView: assembly.makeModule()) + case .studyPlan(let appTabBarControllerDelegate): + return AppTabBarController( + initialTab: .studyPlan, + availableTabs: AppTabItemsAvailabilityService.shared.getAvailableTabs(), + appTabBarControllerDelegate: appTabBarControllerDelegate + ) + case .studyPlanWithStep(let appTabBarControllerDelegate, let stepRoute): + let tabBarController = AppTabBarController( + initialTab: .studyPlan, + availableTabs: AppTabItemsAvailabilityService.shared.getAvailableTabs(), + appTabBarControllerDelegate: appTabBarControllerDelegate + ) + + if !tabBarController.isViewLoaded { + _ = tabBarController.view + } + + DispatchQueue.main.async { + let index = tabBarController.selectedIndex + + guard + let navigationController = tabBarController.children[index] as? UINavigationController + else { + return assertionFailure("AppRouter: Expected UINavigationController") + } + + let stepAssembly = StepAssembly(stepRoute: stepRoute) + navigationController.pushViewController(stepAssembly.makeModule(), animated: false) + } + + return tabBarController + case .trackSelection: + let assembly = TrackSelectionListAssembly(isNewUserMode: true) + let navigationController = UINavigationController( + rootViewController: assembly.makeModule() + ) + navigationController.navigationBar.prefersLargeTitles = true + return navigationController + case .onboarding(let moduleOutput): + let assembly = WelcomeAssembly(output: moduleOutput) + return UIHostingController(rootView: assembly.makeModule()) + case .firstProblemOnboarding(let isNewUserMode, let moduleOutput): + let assembly = FirstProblemOnboardingAssembly( + isNewUserMode: isNewUserMode, + output: moduleOutput + ) + return assembly.makeModule() + case .notificationOnboarding(let moduleOutput): + let assembly = NotificationsOnboardingAssembly(output: moduleOutput) + return assembly.makeModule() + } + }() + + let fromViewController = viewController.children.first { childrenViewController in + if childrenViewController is UIHostingController { + return false + } + return true + } + if let fromViewController, + type(of: fromViewController) == type(of: viewControllerToPresent) { + return + } + + assert(viewController.children.count <= 2) + + swapRootViewController( + for: viewController, + from: fromViewController, + to: viewControllerToPresent + ) + } + + // MARK: Private API + + private func swapRootViewController( + for viewController: UIViewController, + from oldViewController: UIViewController?, + to newViewController: UIViewController + ) { + oldViewController?.willMove(toParent: nil) + + viewController.addChild(newViewController) + + viewController.view.addSubview(newViewController.view) + newViewController.view.translatesAutoresizingMaskIntoConstraints = false + newViewController.view.snp.makeConstraints { make in + make.edges.equalToSuperview() + } + + newViewController.view.alpha = 0 + newViewController.view.setNeedsLayout() + newViewController.view.layoutIfNeeded() + + UIView.animate( + withDuration: Animation.swapRootViewControllerAnimationDuration, + delay: 0, + options: .transitionFlipFromLeft, + animations: { + newViewController.view.alpha = 1 + oldViewController?.view.alpha = 0 + }, + completion: { isFinished in + guard isFinished else { + return + } + + oldViewController?.view.removeFromSuperview() + oldViewController?.removeFromParent() + + newViewController.didMove(toParent: viewController) + } + ) + } + + // MARK: Inner Types + + enum Route { + case auth(isInSignUpMode: Bool, moduleOutput: AuthOutputProtocol?) + case studyPlan(appTabBarControllerDelegate: AppTabBarControllerDelegate?) + case studyPlanWithStep(appTabBarControllerDelegate: AppTabBarControllerDelegate?, stepRoute: StepRoute) + case trackSelection + case onboarding(moduleOutput: WelcomeOutputProtocol?) + case firstProblemOnboarding(isNewUserMode: Bool, moduleOutput: FirstProblemOnboardingOutputProtocol?) + case notificationOnboarding(moduleOutput: NotificationsOnboardingOutputProtocol?) + } + + enum Animation { + static let swapRootViewControllerAnimationDuration: TimeInterval = 0.3 + } +} diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/App/ViewControllers/AppViewController.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/App/ViewControllers/AppViewController.swift index f73a5c5f30..558f1ce854 100644 --- a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/App/ViewControllers/AppViewController.swift +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/App/ViewControllers/AppViewController.swift @@ -10,8 +10,6 @@ protocol AppViewControllerProtocol: AnyObject { extension AppViewController { enum Animation { - static let swapRootViewControllerAnimationDuration: TimeInterval = 0.3 - fileprivate static let clickedNotificationViewActionNavigateToHomeCompletionDelay: TimeInterval = 0.33 fileprivate static let clickedNotificationViewActionDismissProgressHUDDelay: TimeInterval = 0.33 } @@ -19,11 +17,13 @@ extension AppViewController { final class AppViewController: UIViewController { private let viewModel: AppViewModel + private let router: AppRouter var appView: AppView? { view as? AppView } - init(viewModel: AppViewModel) { + init(viewModel: AppViewModel, router: AppRouter) { self.viewModel = viewModel + self.router = router super.init(nibName: nil, bundle: nil) } @@ -102,44 +102,16 @@ extension AppViewController: AppViewControllerProtocol { } private func handleNavigateToViewAction(_ viewAction: AppFeatureActionViewActionNavigateToKs) { - #warning("TODO: Code dublication, see handleWelcomeOnboardingViewAction(_:)") - let viewControllerToPresent: UIViewController = { - switch viewAction { - case .onboardingScreen: - return UIHostingController(rootView: WelcomeAssembly(output: viewModel).makeModule()) - case .studyPlan: - return AppTabBarController( - initialTab: .studyPlan, - availableTabs: AppTabItemsAvailabilityService.shared.getAvailableTabs(), - appTabBarControllerDelegate: viewModel - ) - case .authScreen(let data): - let assembly = AuthSocialAssembly(isInSignUpMode: data.isInSignUpMode, output: viewModel) - return UIHostingController(rootView: assembly.makeModule()) - case .trackSelectionScreen: - let assembly = TrackSelectionListAssembly(isNewUserMode: true) - let navigationController = UINavigationController( - rootViewController: assembly.makeModule() - ) - navigationController.navigationBar.prefersLargeTitles = true - return navigationController - } - }() - - let fromViewController = children.first { viewController in - if viewController is UIHostingController { - return false - } - return true - } - if let fromViewController, - type(of: fromViewController) == type(of: viewControllerToPresent) { - return + switch viewAction { + case .authScreen(let data): + router.route(.auth(isInSignUpMode: data.isInSignUpMode, moduleOutput: viewModel)) + case .onboardingScreen: + router.route(.onboarding(moduleOutput: viewModel)) + case .studyPlan: + router.route(.studyPlan(appTabBarControllerDelegate: viewModel)) + case .trackSelectionScreen: + router.route(.trackSelection) } - - assert(children.count <= 2) - - swapRootViewController(from: fromViewController, to: viewControllerToPresent) } private func handleStreakRecoveryViewAction(_ viewAction: StreakRecoveryFeatureActionViewActionKs) { @@ -239,102 +211,17 @@ extension AppViewController: AppViewControllerProtocol { private func handleWelcomeOnboardingViewAction( _ viewAction: WelcomeOnboardingFeatureActionViewActionKs ) { - #warning("TODO: Code dublication, see handleNavigateToViewAction(_:)") - let viewControllerToPresent: UIViewController = { - switch viewAction { - case .navigateTo(let navigateToViewAction): - switch WelcomeOnboardingFeatureActionViewActionNavigateToKs(navigateToViewAction) { - case .firstProblemOnboardingScreen(let data): - let assembly = FirstProblemOnboardingAssembly( - isNewUserMode: data.isNewUserMode, - output: viewModel - ) - return assembly.makeModule() - case .notificationOnboardingScreen: - let assembly = NotificationsOnboardingAssembly(output: viewModel) - return assembly.makeModule() - case .studyPlanWithStep(let navigateToStudyPlanWithStepViewAction): - let tabBarController = AppTabBarController( - initialTab: .studyPlan, - availableTabs: AppTabItemsAvailabilityService.shared.getAvailableTabs(), - appTabBarControllerDelegate: viewModel - ) - - if !tabBarController.isViewLoaded { - _ = tabBarController.view - } - - DispatchQueue.main.async { - let index = tabBarController.selectedIndex - - guard - let navigationController = tabBarController.children[index] as? UINavigationController - else { - return assertionFailure("Expected UINavigationController") - } - - let stepAssembly = StepAssembly(stepRoute: navigateToStudyPlanWithStepViewAction.stepRoute) - navigationController.pushViewController(stepAssembly.makeModule(), animated: false) - } - - return tabBarController - } - } - }() - - let fromViewController = children.first { viewController in - if viewController is UIHostingController { - return false + switch viewAction { + case .navigateTo(let navigateToViewAction): + switch WelcomeOnboardingFeatureActionViewActionNavigateToKs(navigateToViewAction) { + case .firstProblemOnboardingScreen(let data): + router.route(.firstProblemOnboarding(isNewUserMode: data.isNewUserMode, moduleOutput: viewModel)) + case .notificationOnboardingScreen: + router.route(.notificationOnboarding(moduleOutput: viewModel)) + case .studyPlanWithStep(let data): + router.route(.studyPlanWithStep(appTabBarControllerDelegate: viewModel, stepRoute: data.stepRoute)) } - return true - } - if let fromViewController, - type(of: fromViewController) == type(of: viewControllerToPresent) { - return } - - assert(children.count <= 2) - - swapRootViewController(from: fromViewController, to: viewControllerToPresent) - } - - private func swapRootViewController( - from oldViewController: UIViewController?, - to newViewController: UIViewController - ) { - oldViewController?.willMove(toParent: nil) - - addChild(newViewController) - - view.addSubview(newViewController.view) - newViewController.view.translatesAutoresizingMaskIntoConstraints = false - newViewController.view.snp.makeConstraints { make in - make.edges.equalToSuperview() - } - - newViewController.view.alpha = 0 - newViewController.view.setNeedsLayout() - newViewController.view.layoutIfNeeded() - - UIView.animate( - withDuration: Animation.swapRootViewControllerAnimationDuration, - delay: 0, - options: .transitionFlipFromLeft, - animations: { - newViewController.view.alpha = 1 - oldViewController?.view.alpha = 0 - }, - completion: { isFinished in - guard isFinished else { - return - } - - oldViewController?.view.removeFromSuperview() - oldViewController?.removeFromParent() - - newViewController.didMove(toParent: self) - } - ) } } diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/ProfileSettings/ProfileSettingsViewModel.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/ProfileSettings/ProfileSettingsViewModel.swift index 3395565a0f..3e4ac8191b 100644 --- a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/ProfileSettings/ProfileSettingsViewModel.swift +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/ProfileSettings/ProfileSettingsViewModel.swift @@ -8,7 +8,7 @@ final class ProfileSettingsViewModel: FeatureViewModel< > { private static let applyNewThemeAnimationDelay: TimeInterval = 0.33 private static let dismissScreenAnimationDelay = - AppViewController.Animation.swapRootViewControllerAnimationDuration * 0.75 + AppRouter.Animation.swapRootViewControllerAnimationDuration * 0.75 private let applicationThemeService: ApplicationThemeServiceProtocol From e77a0b67357e208f0be22856f1d4cab9cc7d6a24 Mon Sep 17 00:00:00 2001 From: github-actions Date: Thu, 22 Feb 2024 05:58:34 +0000 Subject: [PATCH 082/104] iOS: Bump build number --- .../NotificationServiceExtension/Info.plist | 2 +- .../iosHyperskillApp.xcodeproj/project.pbxproj | 16 ++++++++-------- iosHyperskillApp/iosHyperskillApp/Info.plist | 2 +- .../iosHyperskillAppTests/Info.plist | 2 +- .../iosHyperskillAppUITests/Info.plist | 2 +- 5 files changed, 12 insertions(+), 12 deletions(-) diff --git a/iosHyperskillApp/NotificationServiceExtension/Info.plist b/iosHyperskillApp/NotificationServiceExtension/Info.plist index 6fccb404ab..e221245f3d 100644 --- a/iosHyperskillApp/NotificationServiceExtension/Info.plist +++ b/iosHyperskillApp/NotificationServiceExtension/Info.plist @@ -9,7 +9,7 @@ CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleVersion - 338 + 339 CFBundleShortVersionString 1.50 CFBundlePackageType diff --git a/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj b/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj index 38ef185050..c85c2d8b9a 100644 --- a/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj +++ b/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj @@ -5065,7 +5065,7 @@ buildSettings = { CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 338; + CURRENT_PROJECT_VERSION = 339; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = 3DWS674B2M; GENERATE_INFOPLIST_FILE = NO; @@ -5086,7 +5086,7 @@ buildSettings = { CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 338; + CURRENT_PROJECT_VERSION = 339; DEVELOPMENT_TEAM = 3DWS674B2M; GENERATE_INFOPLIST_FILE = NO; INFOPLIST_FILE = iosHyperskillAppUITests/Info.plist; @@ -5107,7 +5107,7 @@ BUNDLE_LOADER = "$(TEST_HOST)"; CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 338; + CURRENT_PROJECT_VERSION = 339; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = 3DWS674B2M; INFOPLIST_FILE = iosHyperskillAppTests/Info.plist; @@ -5128,7 +5128,7 @@ BUNDLE_LOADER = "$(TEST_HOST)"; CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 338; + CURRENT_PROJECT_VERSION = 339; DEVELOPMENT_TEAM = 3DWS674B2M; INFOPLIST_FILE = iosHyperskillAppTests/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; @@ -5149,7 +5149,7 @@ CODE_SIGN_ENTITLEMENTS = NotificationServiceExtension/NotificationServiceExtension.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 338; + CURRENT_PROJECT_VERSION = 339; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = 3DWS674B2M; INFOPLIST_FILE = NotificationServiceExtension/Info.plist; @@ -5177,7 +5177,7 @@ CODE_SIGN_ENTITLEMENTS = NotificationServiceExtension/NotificationServiceExtension.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 338; + CURRENT_PROJECT_VERSION = 339; DEVELOPMENT_TEAM = 3DWS674B2M; INFOPLIST_FILE = NotificationServiceExtension/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; @@ -5322,7 +5322,7 @@ CODE_SIGN_ENTITLEMENTS = iosHyperskillApp/iosHyperskillApp.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 338; + CURRENT_PROJECT_VERSION = 339; DEVELOPMENT_ASSET_PATHS = "\"iosHyperskillApp/Preview Content\""; DEVELOPMENT_TEAM = 3DWS674B2M; ENABLE_PREVIEWS = YES; @@ -5358,7 +5358,7 @@ CODE_SIGN_ENTITLEMENTS = iosHyperskillApp/iosHyperskillApp.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 338; + CURRENT_PROJECT_VERSION = 339; DEVELOPMENT_ASSET_PATHS = "\"iosHyperskillApp/Preview Content\""; DEVELOPMENT_TEAM = 3DWS674B2M; ENABLE_PREVIEWS = YES; diff --git a/iosHyperskillApp/iosHyperskillApp/Info.plist b/iosHyperskillApp/iosHyperskillApp/Info.plist index 8df22fc7ec..05bda6d2f3 100644 --- a/iosHyperskillApp/iosHyperskillApp/Info.plist +++ b/iosHyperskillApp/iosHyperskillApp/Info.plist @@ -36,7 +36,7 @@ CFBundleVersion - 338 + 339 FirebaseAppDelegateProxyEnabled FirebaseMessagingAutoInitEnabled diff --git a/iosHyperskillApp/iosHyperskillAppTests/Info.plist b/iosHyperskillApp/iosHyperskillAppTests/Info.plist index 74a38df57c..716ada6f16 100644 --- a/iosHyperskillApp/iosHyperskillAppTests/Info.plist +++ b/iosHyperskillApp/iosHyperskillAppTests/Info.plist @@ -15,6 +15,6 @@ CFBundleShortVersionString 1.50 CFBundleVersion - 338 + 339 diff --git a/iosHyperskillApp/iosHyperskillAppUITests/Info.plist b/iosHyperskillApp/iosHyperskillAppUITests/Info.plist index 7ce190324d..55b3b70838 100644 --- a/iosHyperskillApp/iosHyperskillAppUITests/Info.plist +++ b/iosHyperskillApp/iosHyperskillAppUITests/Info.plist @@ -15,6 +15,6 @@ CFBundleShortVersionString 1.50 CFBundleVersion - 338 + 339 From c858dd4b7e2bd0d04e0bf11b47b27e08b80d371e Mon Sep 17 00:00:00 2001 From: Ivan Magda Date: Thu, 22 Feb 2024 19:51:58 +0700 Subject: [PATCH 083/104] iOS: Update SwiftLint configuration (#916) ^ALTAPPS-473 --- iosHyperskillApp/.swiftlint.yml | 438 ++---------------- iosHyperskillApp/Podfile | 2 +- iosHyperskillApp/Podfile.lock | 8 +- .../Shared/Model/BlockOptionsExtensions.swift | 6 +- .../Shared/Model/ReplyExtensions.swift | 4 +- .../Extensions/UIKit/UIWindowExtensions.swift | 2 +- .../Model/Analyze/CodePlaygroundManager.swift | 4 + .../CodeEditor/View/SwiftUI/CodeEditor.swift | 4 +- ...gestionsPresentationContextProviding.swift | 7 +- .../CodeCompletionCellView.swift | 2 +- .../CodeCompletionTableViewController.swift | 2 +- .../UIKit/CodeEditorView/CodeEditorView.swift | 6 +- .../CodeInputAccessoryBuilder.swift | 1 + ...CodeInputAccessoryCollectionViewCell.swift | 2 +- .../Toolbar/CodeInputAccessoryView.swift | 6 +- .../UIKit/CodeTextView/CodeTextView.swift | 8 +- .../CodeTextViewLayoutManager.swift | 8 +- .../Frameworks/Collections/LinkedList.swift | 2 +- .../View/UIKit/ProcessedContentTextView.swift | 8 +- .../View/UIKit/ProcessedContentView.swift | 12 +- .../View/UIKit/ProcessedContentWebView.swift | 6 +- .../Frameworks/Core/FeatureViewModel.swift | 2 +- .../Local/LocalNotificationsService.swift | 2 +- .../Notifications/NotificationsService.swift | 4 +- .../NotificationsRegistrationService.swift | 2 +- .../WebController/WebViewController.swift | 1 + .../Sources/Helpers/MainBundleInfo.swift | 4 +- .../Models/Constants/Images/Images.swift | 2 +- .../Views/AuthCredentialsFormView.swift | 2 +- .../AuthCredentials/Views/AuthTextField.swift | 8 +- .../Views/AuthSocialControlsView.swift | 4 +- .../Views/About/ProfileAboutView.swift | 8 +- .../ProfileSettings/ProfileSettingsView.swift | 5 - .../StepQuizChildQuizAssembly.swift | 2 + .../StepQuizBottomControls.swift | 2 +- .../StepQuizDiscussionsButton.swift | 2 +- .../Modules/StepQuiz/Views/StepQuizView.swift | 2 + .../ViewData/StepQuizCodeViewDataMapper.swift | 8 +- .../FillBlanksTextCollectionViewCell.swift | 2 +- .../FillBlanksInputCollectionViewCell.swift | 9 +- .../FillBlanksSelectCollectionViewCell.swift | 7 +- .../Views/UIKit/FillBlanksQuizTitleView.swift | 4 +- .../Views/UIKit/FillBlanksQuizView.swift | 8 +- ...izFillBlanksSelectOptionsViewWrapper.swift | 1 + ...lanksSelectOptionsCollectionViewCell.swift | 7 +- .../StepQuizFillBlanksSelectOptionsView.swift | 8 +- .../StepQuizStringViewModel.swift | 2 +- .../StepQuizTableViewModel.swift | 4 +- .../Views/StepQuizTableRowView.swift | 2 +- .../SDKs/GoogleSocialAuthSDKProvider.swift | 2 +- .../Systems/AppPowerModeObserver.swift | 2 +- .../SwiftUI/Introspect/PullToRefresh.swift | 10 +- .../CollectionsTests/LinkedListTests.swift | 1 + .../ExtensionsTests/URLExtensionsTests.swift | 1 + .../IntrospectScrollViewTests.swift | 2 +- .../IntrospectViewControllerTests.swift | 4 +- 56 files changed, 163 insertions(+), 511 deletions(-) diff --git a/iosHyperskillApp/.swiftlint.yml b/iosHyperskillApp/.swiftlint.yml index 3097da7927..983ba93b7d 100644 --- a/iosHyperskillApp/.swiftlint.yml +++ b/iosHyperskillApp/.swiftlint.yml @@ -2,346 +2,127 @@ warning_threshold: 15 indentation: 4 -excluded: - - iosHyperskillApp/Sources/Frameworks/sharedSwift/Hyperskill-Mobile_shared.swift - -analyzer_rules: - - explicit_self - - unused_declaration - - unused_import - -only_rules: +disabled_rules: + - redundant_discardable_let + - function_body_length + - inclusive_language + - cyclomatic_complexity + - type_body_length + - superfluous_disable_command + +opt_in_rules: + - empty_count + - array_init - attributes - - block_based_kvo - - class_delegate_protocol - - closing_brace - closure_end_indentation - closure_spacing - collection_alignment - - colon - - comma - - compiler_protocol_init - - computed_accessors_order + - comma_inheritance - conditional_returns_on_newline - contains_over_filter_count - contains_over_filter_is_empty - contains_over_first_not_nil - contains_over_range_nil_comparison - - control_statement - convenience_type - - deployment_target - discarded_notification_center_observer - - discouraged_direct_init + - discouraged_assert - discouraged_none_name - discouraged_object_literal - - duplicate_enum_cases - - duplicate_imports - - duplicated_key_in_dictionary_literal - - dynamic_inline + - discouraged_optional_boolean - empty_collection_literal - empty_count - - empty_enum_arguments - - empty_parameters - - empty_parentheses_with_trailing_closure - empty_string - - empty_xctest_method - enum_case_associated_values_count - fallthrough - fatal_error_message - file_header - - file_length - file_name_no_space + - final_test_case - first_where - flatmap_over_map_reduce - - for_where - - force_cast - - force_try - force_unwrapping - - generic_type_name - identical_operands - - identifier_name - - implicit_getter - implicit_return - implicitly_unwrapped_optional - inert_defer - - is_disjoint - joined_default_parameter - - large_tuple - last_where - - leading_whitespace - - legacy_cggeometry_functions - - legacy_constant - - legacy_constructor - - legacy_hashing - legacy_multiple - - legacy_nsgeometry_functions - - legacy_random - - line_length + - let_var_whitespace - literal_expression_end_indentation + - local_doc_comment - lower_acl_than_parent - - mark - modifier_order - multiline_arguments - multiline_arguments_brackets - multiline_literal_brackets - multiline_parameters - multiline_parameters_brackets - - multiple_closures_with_trailing_closure - - nesting - - nimble_operator - - no_space_in_method_call - nslocalizedstring_key - - nsobject_prefer_isequal - - number_separator - - object_literal - - opening_brace - operator_usage_whitespace - - operator_whitespace - optional_enum_case_matching - overridden_super_call - override_in_extension - # - prohibited_nan_comparison - - prefer_self_type_over_type_of_self - - prefixed_toplevel_constant - - private_over_fileprivate - - private_unit_test + - prefer_zero_over_explicit_init - prohibited_interface_builder - prohibited_super_call - - protocol_property_accessors_order - - quick_discouraged_call - - quick_discouraged_focused_test - - quick_discouraged_pending_test - - reduce_boolean + - raw_value_for_camel_cased_codable_enum - reduce_into - # - redundant_discardable_let - redundant_nil_coalescing - - redundant_objc_attribute - - redundant_optional_initialization - - redundant_set_access_control - - redundant_string_enum_value + - redundant_self_in_closure - redundant_type_annotation - - redundant_void_return - # - return_value_from_void_function - - return_arrow_whitespace - - self_in_property_initialization - - shorthand_operator - - single_test_class + - shorthand_optional_binding - sorted_first_last - sorted_imports - - statement_position - static_operator - - strong_iboutlet - - switch_case_alignment - switch_case_on_newline - - syntactic_sugar - toggle_bool - - trailing_comma - - trailing_newline - - trailing_semicolon - - trailing_whitespace - # - tuple_pattern - - type_name - unavailable_function - - unneeded_break_in_switch + - unhandled_throwing_task - unowned_variable_capture - unused_capture_list - - unused_closure_parameter - - unused_control_flow_label - - unused_enumerated - - unused_optional_binding - - vertical_whitespace - vertical_whitespace_closing_braces - vertical_whitespace_opening_braces - # - void_function_in_ternary - - void_return - weak_delegate - yoda_condition +analyzer_rules: + - unused_declaration + - unused_import + +excluded: + - iosHyperskillApp/Sources/Frameworks/sharedSwift/Hyperskill-Mobile_shared.swift + - iosHyperskillApp/Sources/Frameworks/sharedSwift/Extensions/**/*.swift + # settings attributes: severity: error attributes_with_arguments_always_on_line_above: false -block_based_kvo: - severity: error - -class_delegate_protocol: - severity: error - -closing_brace: - severity: error - -closure_spacing: - severity: error - -colon: - severity: error - -comma: - severity: error - -compiler_protocol_init: - severity: error - -computed_accessors_order: - severity: error - order: get_set - -conditional_returns_on_newline: - severity: error - -contains_over_filter_count: - severity: error - -contains_over_filter_is_empty: - severity: error - -contains_over_first_not_nil: - severity: error - -contains_over_range_nil_comparison: - severity: error - -control_statement: - severity: error - -convenience_type: - severity: error - -deployment_target: - severity: error - -discarded_notification_center_observer: - severity: error - -discouraged_direct_init: - severity: error - -discouraged_none_name: - severity: warning - -discouraged_object_literal: - severity: error - -duplicate_enum_cases: - severity: error - -duplicate_imports: - severity: error - -duplicated_key_in_dictionary_literal: - severity: error - -dynamic_inline: - severity: error - -empty_collection_literal: - severity: error - -empty_count: - severity: error - -empty_enum_arguments: - severity: error - -empty_parameters: - severity: error - -empty_parentheses_with_trailing_closure: - severity: error - -empty_string: - severity: error - -empty_xctest_method: - severity: error - -enum_case_associated_values_count: - warning: 4 - error: 5 - -explicit_self: - severity: error - -fallthrough: - severity: error - -file_header: - forbidden_pattern: "^//[^/]|/\\*[^*]" - -file_length: - warning: 400 - error: 400 - ignore_comment_only_lines: true - -file_name_no_space: - severity: error - -first_where: - severity: error - -flatmap_over_map_reduce: - severity: error - -for_where: - severity: error - -force_cast: - severity: error - -force_try: - severity: error - -force_unwrapping: - severity: error - -identical_operands: - severity: error - -generic_type_name: - severity: error - identifier_name: severity: error - excluded: ["i", "j", "x", "y", "z", "id", "to", "vk", "h1", "h2", "h3", "r", "g", "b", "no", "ok", "c", "cs", "go", "js"] + excluded: + ["i", "j", "x", "y", "z", "id", "to", "vk", "h1", "h2", "h3", "r", "g", "b", "no", "ok", "c", "cs", "go", "js"] max_length: 64 -implicit_getter: - severity: error - -implicit_return: - severity: error - -implicitly_unwrapped_optional: - severity: error - -is_disjoint: - severity: error - large_tuple: warning: 3 error: 3 -last_where: - severity: error - -legacy_cggeometry_functions: - severity: error - -legacy_constant: - severity: error - -legacy_constructor: - severity: error - -legacy_hashing: - severity: error +type_name: + min_length: 3 + max_length: + warning: 64 + error: 64 + allowed_symbols: ["_"] -legacy_multiple: - severity: error +file_length: + warning: 400 + error: 400 + ignore_comment_only_lines: true -legacy_nsgeometry_functions: - severity: error +file_header: + forbidden_pattern: "^//[^/]|/\\*[^*]" line_length: warning: 120 @@ -350,125 +131,9 @@ line_length: ignores_comments: true ignores_interpolated_strings: true -lower_acl_than_parent: - severity: error - -mark: - severity: error - -modifier_order: - severity: error - -multiline_arguments: - severity: error - first_argument_location: next_line - -multiline_arguments_brackets: - severity: error - -multiline_literal_brackets: - severity: error - -multiline_parameters: - severity: error - -multiline_parameters_brackets: - severity: error - -multiple_closures_with_trailing_closure: - severity: error - nesting: - # for dataflow type_level: 3 -number_separator: - minimum_length: 6 - -object_literal: - severity: error - image_literal: false - color_literal: false - -opening_brace: - severity: error - -operator_usage_whitespace: - severity: error - -operator_whitespace: - severity: error - -optional_enum_case_matching: - severity: error - -overridden_super_call: - severity: error - -prefer_self_type_over_type_of_self: - severity: error - -prefixed_toplevel_constant: - severity: error - -prohibited_super_call: - severity: error - -prohibited_interface_builder: - severity: error - -reduce_boolean: - severity: error - -redundant_set_access_control: - severity: error - -redundant_type_annotation: - severity: error - -self_in_property_initialization: - severity: error - -statement_position: - severity: error - -static_operator: - severity: error - -switch_case_alignment: - severity: error - -switch_case_on_newline: - severity: error - -syntactic_sugar: - severity: error - -trailing_comma: - severity: error - -trailing_newline: - severity: error - -trailing_semicolon: - severity: error - -# tuple_pattern: -# severity: error - -type_name: - severity: error - max_length: 64 - -unneeded_break_in_switch: - severity: error - -unowned_variable_capture: - severity: error - -unused_enumerated: - severity: error - unused_import: require_explicit_imports: true allowed_transitive_imports: @@ -477,18 +142,3 @@ unused_import: - CoreFoundation - Darwin - ObjectiveC - -unused_optional_binding: - severity: error - -vertical_whitespace_closing_braces: - severity: error - -vertical_whitespace_opening_braces: - severity: error - -void_return: - severity: error - -weak_delegate: - severity: error diff --git a/iosHyperskillApp/Podfile b/iosHyperskillApp/Podfile index cc06ccbe88..656934d3e2 100644 --- a/iosHyperskillApp/Podfile +++ b/iosHyperskillApp/Podfile @@ -9,7 +9,7 @@ target "iosHyperskillApp" do pod "shared", :path => "../shared" - pod "SwiftLint", "0.53.0" + pod "SwiftLint", "0.54.0" pod "Sentry", "8.17.2" # Firebase diff --git a/iosHyperskillApp/Podfile.lock b/iosHyperskillApp/Podfile.lock index 206fa6cffc..59a5701ff4 100644 --- a/iosHyperskillApp/Podfile.lock +++ b/iosHyperskillApp/Podfile.lock @@ -96,7 +96,7 @@ PODS: - SVGKit (3.1.1): - CocoaLumberjack (~> 3.0) - SVProgressHUD (2.2.5) - - SwiftLint (0.53.0) + - SwiftLint (0.54.0) DEPENDENCIES: - AppsFlyerFramework (= 6.12.2) @@ -118,7 +118,7 @@ DEPENDENCIES: - STRegex (= 2.1.1) - SVGKit (from `https://github.com/SVGKit/SVGKit.git`, branch `3.x`) - SVProgressHUD (= 2.2.5) - - SwiftLint (= 0.53.0) + - SwiftLint (= 0.54.0) SPEC REPOS: trunk: @@ -221,8 +221,8 @@ SPEC CHECKSUMS: STRegex: d49e88d0fe58538d3175fdd989bc1243b9be2a07 SVGKit: 9bc0f0982df559e65b96f2150ff9c15a61e453ac SVProgressHUD: 1428aafac632c1f86f62aa4243ec12008d7a51d6 - SwiftLint: 5ce4d6a8ff83f1b5fd5ad5dbf30965d35af65e44 + SwiftLint: c1de071d9d08c8aba837545f6254315bc900e211 -PODFILE CHECKSUM: 1613f08afc0d23eb5c1805973d23b2f5c885203e +PODFILE CHECKSUM: 1ed5181374da1a28796a674e5e12e4fbb0aad2fa COCOAPODS: 1.15.2 diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Extensions/Shared/Model/BlockOptionsExtensions.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Extensions/Shared/Model/BlockOptionsExtensions.swift index 5c590c6b89..907a78a3a3 100644 --- a/iosHyperskillApp/iosHyperskillApp/Sources/Extensions/Shared/Model/BlockOptionsExtensions.swift +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Extensions/Shared/Model/BlockOptionsExtensions.swift @@ -2,6 +2,7 @@ import Foundation import shared extension Block.Options { + // swiftlint:disable discouraged_optional_boolean convenience init( isMultipleChoice: Bool? = nil, language: String? = nil, @@ -12,14 +13,14 @@ extension Block.Options { files: [Block.OptionsFile]? = nil ) { let isMultipleChoice: KotlinBoolean? = { - if let isMultipleChoice = isMultipleChoice { + if let isMultipleChoice { return KotlinBoolean(value: isMultipleChoice) } return nil }() let isCheckbox: KotlinBoolean? = { - if let isCheckbox = isCheckbox { + if let isCheckbox { return KotlinBoolean(value: isCheckbox) } return nil @@ -35,4 +36,5 @@ extension Block.Options { files: files ) } + // swiftlint:enable discouraged_optional_boolean } diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Extensions/Shared/Model/ReplyExtensions.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Extensions/Shared/Model/ReplyExtensions.swift index 46afa7b002..2f5be772c6 100644 --- a/iosHyperskillApp/iosHyperskillApp/Sources/Extensions/Shared/Model/ReplyExtensions.swift +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Extensions/Shared/Model/ReplyExtensions.swift @@ -21,9 +21,9 @@ extension Reply { lines: [ParsonsLine]? = nil ) { let choicesAnswer: [ChoiceAnswer]? = { - if let sortingChoices = sortingChoices { + if let sortingChoices { return sortingChoices.map(ChoiceAnswerChoice.init(boolValue:)) - } else if let tableChoices = tableChoices { + } else if let tableChoices { return tableChoices.map(ChoiceAnswerTable.init(tableChoice:)) } return nil diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Extensions/UIKit/UIWindowExtensions.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Extensions/UIKit/UIWindowExtensions.swift index 18a87b9ccc..4d092f544a 100644 --- a/iosHyperskillApp/iosHyperskillApp/Sources/Extensions/UIKit/UIWindowExtensions.swift +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Extensions/UIKit/UIWindowExtensions.swift @@ -34,7 +34,7 @@ extension UIWindow { /// - responder: The view object, `UIView`, `UIViewController`, or `UIWindow` instance. /// - level: The depth level in the view hierarchy. func traverseHierarchy(_ visitor: (_ responder: UIResponder, _ level: Int) -> Void) { - /// Stack used to accumulate objects to visit. + // Stack used to accumulate objects to visit. var stack: [(responder: UIResponder, level: Int)] = [(responder: self, level: 0)] while !stack.isEmpty { diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Frameworks/CodeEditor/Model/Analyze/CodePlaygroundManager.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Frameworks/CodeEditor/Model/Analyze/CodePlaygroundManager.swift index 60a79b4208..868fd1f19c 100644 --- a/iosHyperskillApp/iosHyperskillApp/Sources/Frameworks/CodeEditor/Model/Analyze/CodePlaygroundManager.swift +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Frameworks/CodeEditor/Model/Analyze/CodePlaygroundManager.swift @@ -139,6 +139,7 @@ final class CodePlaygroundManager { return beforeCursorString + afterCursorString } + // swiftlint:disable:next function_parameter_count private func checkNextLineInsertion( currentText: String, previousText: String, @@ -226,6 +227,7 @@ final class CodePlaygroundManager { } } + // swiftlint:disable:next function_parameter_count private func checkPaired( currentText: String, previousText: String, @@ -372,6 +374,7 @@ final class CodePlaygroundManager { currentCodeCompletionTableViewController = nil } + // swiftlint:disable:next function_parameter_count private func presentCodeCompletion( suggestions: [String], prefix: String, @@ -441,6 +444,7 @@ final class CodePlaygroundManager { textView.delegate?.textViewDidChange?(textView) } + // swiftlint:disable:next function_parameter_count func analyzeAndComplete( textView: UITextView, previousText: String, diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Frameworks/CodeEditor/View/SwiftUI/CodeEditor.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Frameworks/CodeEditor/View/SwiftUI/CodeEditor.swift index 046ecc5e1b..909991a315 100644 --- a/iosHyperskillApp/iosHyperskillApp/Sources/Frameworks/CodeEditor/View/SwiftUI/CodeEditor.swift +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Frameworks/CodeEditor/View/SwiftUI/CodeEditor.swift @@ -78,7 +78,7 @@ struct CodeEditor: UIViewRepresentable { self.code = newCode } context.coordinator.onDidBeginEditing = { [weak codeEditorView] in - guard let codeEditorView = codeEditorView else { + guard let codeEditorView else { return } @@ -87,7 +87,7 @@ struct CodeEditor: UIViewRepresentable { onDidBeginEditing?() } context.coordinator.onDidEndEditing = { [weak codeEditorView] in - guard let codeEditorView = codeEditorView else { + guard let codeEditorView else { return } diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Frameworks/CodeEditor/View/SwiftUI/CodeEditorSuggestionsPresentationContextProviding.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Frameworks/CodeEditor/View/SwiftUI/CodeEditorSuggestionsPresentationContextProviding.swift index 5a49b9c422..56c1d8948e 100644 --- a/iosHyperskillApp/iosHyperskillApp/Sources/Frameworks/CodeEditor/View/SwiftUI/CodeEditorSuggestionsPresentationContextProviding.swift +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Frameworks/CodeEditor/View/SwiftUI/CodeEditorSuggestionsPresentationContextProviding.swift @@ -4,15 +4,12 @@ protocol CodeEditorSuggestionsPresentationContextProviding: AnyObject { func presentationController(for codeEditorView: CodeEditorView) -> UIViewController? } -// swiftlint:disable all - final class ResponderChainCodeEditorSuggestionsPresentationContextProvider: - CodeEditorSuggestionsPresentationContextProviding -{ + CodeEditorSuggestionsPresentationContextProviding { private weak var presentationController: UIViewController? func presentationController(for codeEditorView: CodeEditorView) -> UIViewController? { - if let presentationController = presentationController { + if let presentationController { return presentationController } else { let responsibleViewController = codeEditorView.findViewController() diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Frameworks/CodeEditor/View/UIKit/CodeCompletion/CodeCompletionTableViewCell/CodeCompletionCellView.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Frameworks/CodeEditor/View/UIKit/CodeCompletion/CodeCompletionTableViewCell/CodeCompletionCellView.swift index f7e421bd11..9648df606f 100644 --- a/iosHyperskillApp/iosHyperskillApp/Sources/Frameworks/CodeEditor/View/UIKit/CodeCompletion/CodeCompletionTableViewCell/CodeCompletionCellView.swift +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Frameworks/CodeEditor/View/UIKit/CodeCompletion/CodeCompletionTableViewCell/CodeCompletionCellView.swift @@ -13,7 +13,7 @@ final class CodeCompletionCellView: UIView { private lazy var textLabel: UILabel = { let label = UILabel() - label.textColor = self.appearance.textColor + label.textColor = appearance.textColor label.numberOfLines = 1 return label }() diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Frameworks/CodeEditor/View/UIKit/CodeCompletion/CodeCompletionTableViewController.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Frameworks/CodeEditor/View/UIKit/CodeCompletion/CodeCompletionTableViewController.swift index 229b4320a4..c567662214 100644 --- a/iosHyperskillApp/iosHyperskillApp/Sources/Frameworks/CodeEditor/View/UIKit/CodeCompletion/CodeCompletionTableViewController.swift +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Frameworks/CodeEditor/View/UIKit/CodeCompletion/CodeCompletionTableViewController.swift @@ -59,7 +59,7 @@ final class CodeCompletionTableViewController: UITableViewController { clearsSelectionOnViewWillAppear = false tableView.rowHeight = suggestionRowHeight - //Adding tap gesture recognizer to catch selection to avoid resignFirstResponder call and keyboard disappearance + // Adding tap gesture recognizer to catch selection to avoid resignFirstResponder call and keyboard disappearance let tapGestureRecognizer = UITapGestureRecognizer( target: self, action: #selector(didTap(recognizer:)) diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Frameworks/CodeEditor/View/UIKit/CodeEditorView/CodeEditorView.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Frameworks/CodeEditor/View/UIKit/CodeEditorView/CodeEditorView.swift index ee1026deb4..89f7924d52 100644 --- a/iosHyperskillApp/iosHyperskillApp/Sources/Frameworks/CodeEditor/View/UIKit/CodeEditorView/CodeEditorView.swift +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Frameworks/CodeEditor/View/UIKit/CodeEditorView/CodeEditorView.swift @@ -13,7 +13,7 @@ final class CodeEditorView: UIView { weak var delegate: CodeEditorViewDelegate? private lazy var codeTextView: CodeTextView = { - let codeTextView = CodeTextView(appearance: self.appearance.textViewAppearance) + let codeTextView = CodeTextView(appearance: appearance.textViewAppearance) codeTextView.delegate = self // Disable features codeTextView.autocapitalizationType = .none @@ -62,7 +62,7 @@ final class CodeEditorView: UIView { var theme: CodeEditorTheme? { didSet { - if let theme = theme { + if let theme { codeTextView.updateTheme(name: theme.name, font: theme.font) } } @@ -126,7 +126,7 @@ final class CodeEditorView: UIView { codeTextView.reloadInputViews() } - guard let language = language, isEditable else { + guard let language, isEditable else { codeTextView.inputAccessoryView = nil return } diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Frameworks/CodeEditor/View/UIKit/CodeInputAccessory/CodeInputAccessoryBuilder.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Frameworks/CodeEditor/View/UIKit/CodeInputAccessory/CodeInputAccessoryBuilder.swift index ad5d6b69c5..4c4847f975 100644 --- a/iosHyperskillApp/iosHyperskillApp/Sources/Frameworks/CodeEditor/View/UIKit/CodeInputAccessory/CodeInputAccessoryBuilder.swift +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Frameworks/CodeEditor/View/UIKit/CodeInputAccessory/CodeInputAccessoryBuilder.swift @@ -1,6 +1,7 @@ import UIKit enum CodeInputAccessoryBuilder { + // swiftlint:disable:next function_parameter_count static func buildAccessoryView( size: CodeInputAccessorySize, language: CodeLanguage, diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Frameworks/CodeEditor/View/UIKit/CodeInputAccessory/Toolbar/CodeInputAccessoryCollectionViewCell.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Frameworks/CodeEditor/View/UIKit/CodeInputAccessory/Toolbar/CodeInputAccessoryCollectionViewCell.swift index 99d04cac31..39c369163a 100644 --- a/iosHyperskillApp/iosHyperskillApp/Sources/Frameworks/CodeEditor/View/UIKit/CodeInputAccessory/Toolbar/CodeInputAccessoryCollectionViewCell.swift +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Frameworks/CodeEditor/View/UIKit/CodeInputAccessory/Toolbar/CodeInputAccessoryCollectionViewCell.swift @@ -52,7 +52,7 @@ final class CodeInputAccessoryCollectionViewCell: UICollectionViewCell, Reusable label.numberOfLines = 1 label.textAlignment = .center - if let size = size { + if let size { label.font = makeFont(for: size) } diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Frameworks/CodeEditor/View/UIKit/CodeInputAccessory/Toolbar/CodeInputAccessoryView.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Frameworks/CodeEditor/View/UIKit/CodeInputAccessory/Toolbar/CodeInputAccessoryView.swift index c35f19a37b..7355c20146 100644 --- a/iosHyperskillApp/iosHyperskillApp/Sources/Frameworks/CodeEditor/View/UIKit/CodeInputAccessory/Toolbar/CodeInputAccessoryView.swift +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Frameworks/CodeEditor/View/UIKit/CodeInputAccessory/Toolbar/CodeInputAccessoryView.swift @@ -34,7 +34,7 @@ final class CodeInputAccessoryView: UIView { imageView.isUserInteractionEnabled = true let tapGestureRecognizer = UITapGestureRecognizer( target: self, - action: #selector(self.didTapHideKeyboardImageView(recognizer:)) + action: #selector(didTapHideKeyboardImageView(recognizer:)) ) imageView.addGestureRecognizer(tapGestureRecognizer) return imageView @@ -47,8 +47,8 @@ final class CodeInputAccessoryView: UIView { private lazy var collectionView: UICollectionView = { let collectionViewLayout = UICollectionViewFlowLayout() collectionViewLayout.scrollDirection = .horizontal - collectionViewLayout.sectionInset = self.appearance.collectionViewLayoutSectionInset - collectionViewLayout.minimumInteritemSpacing = self.appearance.collectionViewLayoutMinimumInteritemSpacing + collectionViewLayout.sectionInset = appearance.collectionViewLayoutSectionInset + collectionViewLayout.minimumInteritemSpacing = appearance.collectionViewLayoutMinimumInteritemSpacing let collectionView = UICollectionView(frame: .zero, collectionViewLayout: collectionViewLayout) collectionView.isPagingEnabled = false diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Frameworks/CodeEditor/View/UIKit/CodeTextView/CodeTextView.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Frameworks/CodeEditor/View/UIKit/CodeTextView/CodeTextView.swift index 33e951e877..967b5856cd 100644 --- a/iosHyperskillApp/iosHyperskillApp/Sources/Frameworks/CodeEditor/View/UIKit/CodeTextView/CodeTextView.swift +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Frameworks/CodeEditor/View/UIKit/CodeTextView/CodeTextView.swift @@ -50,7 +50,7 @@ final class CodeTextView: UITextView { var language: String? { didSet { guard language != oldValue, - let codeAttributedString = codeAttributedString + let codeAttributedString else { return } @@ -164,7 +164,7 @@ final class CodeTextView: UITextView { } func updateTheme(name: String, font: UIFont) { - guard let codeAttributedString = codeAttributedString else { + guard let codeAttributedString else { return } @@ -179,7 +179,7 @@ final class CodeTextView: UITextView { } private func invalidateDisplayOfCurrentLine() { - guard let codeTextViewLayoutManager = codeTextViewLayoutManager else { + guard let codeTextViewLayoutManager else { return } @@ -219,7 +219,7 @@ final class CodeTextView: UITextView { appearance.gutterBorderColor = invertedThemeBackgroundColor.withAlphaComponent(alphas.gutterBorderColorAlpha) - guard let codeTextViewLayoutManager = codeTextViewLayoutManager else { + guard let codeTextViewLayoutManager else { return } diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Frameworks/CodeEditor/View/UIKit/CodeTextView/CodeTextViewLayoutManager.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Frameworks/CodeEditor/View/UIKit/CodeTextView/CodeTextViewLayoutManager.swift index 71043eac14..09a48729c6 100644 --- a/iosHyperskillApp/iosHyperskillApp/Sources/Frameworks/CodeEditor/View/UIKit/CodeTextView/CodeTextViewLayoutManager.swift +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Frameworks/CodeEditor/View/UIKit/CodeTextView/CodeTextViewLayoutManager.swift @@ -104,7 +104,7 @@ final class CodeTextViewLayoutManager: NSLayoutManager { } } - guard let textStorage = textStorage else { + guard let textStorage else { return } @@ -128,7 +128,7 @@ final class CodeTextViewLayoutManager: NSLayoutManager { if characterRange.location == lastParagraphLocation { return lastParagraphNumber } else if characterRange.location < lastParagraphLocation { - guard let textStorage = textStorage else { + guard let textStorage else { return lastParagraphNumber } @@ -154,7 +154,7 @@ final class CodeTextViewLayoutManager: NSLayoutManager { return paragraphNumber } else { - guard let textStorage = textStorage else { + guard let textStorage else { return lastParagraphNumber } @@ -189,7 +189,7 @@ final class CodeTextViewLayoutManager: NSLayoutManager { } private func shouldHighlightParagraphRange(_ paragraphRange: NSRange) -> Bool { - guard shouldHighlightCurrentLine, let selectedRange = selectedRange else { + guard shouldHighlightCurrentLine, let selectedRange else { return false } return NSLocationInRange(selectedRange.location, paragraphRange) diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Frameworks/Collections/LinkedList.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Frameworks/Collections/LinkedList.swift index 666aea4cb0..d296d58c80 100644 --- a/iosHyperskillApp/iosHyperskillApp/Sources/Frameworks/Collections/LinkedList.swift +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Frameworks/Collections/LinkedList.swift @@ -34,7 +34,7 @@ final class LinkedList { let previous = node.previous let next = node.next - if let previous = previous { + if let previous { previous.next = next } else { self.head = next diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Frameworks/ContentProcessor/View/UIKit/ProcessedContentTextView.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Frameworks/ContentProcessor/View/UIKit/ProcessedContentTextView.swift index fbbd07017d..b2d2ba3599 100644 --- a/iosHyperskillApp/iosHyperskillApp/Sources/Frameworks/ContentProcessor/View/UIKit/ProcessedContentTextView.swift +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Frameworks/ContentProcessor/View/UIKit/ProcessedContentTextView.swift @@ -23,8 +23,8 @@ final class ProcessedContentTextView: UIView { private lazy var textView: UITextView = { let textView = UITextView() - textView.font = self.appearance.font - textView.textColor = self.appearance.textColor + textView.font = appearance.font + textView.textColor = appearance.textColor textView.textContainer.lineBreakMode = .byWordWrapping textView.textContainer.maximumNumberOfLines = 0 @@ -45,8 +45,8 @@ final class ProcessedContentTextView: UIView { private lazy var attributedLabel: AttributedLabel = { let attributedLabel = AttributedLabel() - attributedLabel.font = self.appearance.font - attributedLabel.textColor = self.appearance.textColor + attributedLabel.font = appearance.font + attributedLabel.textColor = appearance.textColor attributedLabel.numberOfLines = 0 attributedLabel.isSelectable = true attributedLabel.onClick = { [weak self] (label, detection) in diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Frameworks/ContentProcessor/View/UIKit/ProcessedContentView.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Frameworks/ContentProcessor/View/UIKit/ProcessedContentView.swift index 78b2d835c7..852a7d9aaf 100644 --- a/iosHyperskillApp/iosHyperskillApp/Sources/Frameworks/ContentProcessor/View/UIKit/ProcessedContentView.swift +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Frameworks/ContentProcessor/View/UIKit/ProcessedContentView.swift @@ -28,14 +28,14 @@ final class ProcessedContentView: UIView { private lazy var textView: ProcessedContentTextView = { let appearance = ProcessedContentTextView.Appearance( - font: self.appearance.labelFont, - textColor: self.appearance.labelTextColor + font: appearance.labelFont, + textColor: appearance.labelTextColor ) let view = ProcessedContentTextView( frame: .zero, appearance: appearance, - htmlToAttributedStringConverter: self.htmlToAttributedStringConverter + htmlToAttributedStringConverter: htmlToAttributedStringConverter ) view.delegate = self view.onLinkClick = { [weak self] link in @@ -63,10 +63,10 @@ final class ProcessedContentView: UIView { }() private lazy var activityIndicatorView: UIActivityIndicatorView = { - let activityIndicatorView = UIActivityIndicatorView(style: self.appearance.activityIndicatorViewStyle) + let activityIndicatorView = UIActivityIndicatorView(style: appearance.activityIndicatorViewStyle) activityIndicatorView.hidesWhenStopped = true - if let activityIndicatorViewColor = self.appearance.activityIndicatorViewColor { + if let activityIndicatorViewColor = appearance.activityIndicatorViewColor { activityIndicatorView.color = activityIndicatorViewColor } @@ -228,7 +228,7 @@ final class ProcessedContentView: UIView { } private func clearContent(oldProcessedContent: ProcessedContent?) { - guard let oldProcessedContent = oldProcessedContent else { + guard let oldProcessedContent else { return } diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Frameworks/ContentProcessor/View/UIKit/ProcessedContentWebView.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Frameworks/ContentProcessor/View/UIKit/ProcessedContentWebView.swift index e612a5bffa..fa78f00e19 100644 --- a/iosHyperskillApp/iosHyperskillApp/Sources/Frameworks/ContentProcessor/View/UIKit/ProcessedContentWebView.swift +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Frameworks/ContentProcessor/View/UIKit/ProcessedContentWebView.swift @@ -61,10 +61,10 @@ final class ProcessedContentWebView: UIView { }() private lazy var webView: WKWebView = { - let webView = WKWebView(frame: .zero, configuration: self.webViewConfiguration) + let webView = WKWebView(frame: .zero, configuration: webViewConfiguration) webView.isOpaque = false - webView.backgroundColor = self.appearance.backgroundColor - webView.scrollView.backgroundColor = self.appearance.backgroundColor + webView.backgroundColor = appearance.backgroundColor + webView.scrollView.backgroundColor = appearance.backgroundColor webView.navigationDelegate = self webView.scrollView.isScrollEnabled = false diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Frameworks/Core/FeatureViewModel.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Frameworks/Core/FeatureViewModel.swift index c53b733775..5ca921932d 100644 --- a/iosHyperskillApp/iosHyperskillApp/Sources/Frameworks/Core/FeatureViewModel.swift +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Frameworks/Core/FeatureViewModel.swift @@ -100,7 +100,7 @@ class FeatureViewModel: ObservableObject { } if isListeningForChanges, - let onViewAction = onViewAction { + let onViewAction { mainScheduler.schedule { onViewAction(viewAction) } } else { viewActionQueue.enqueue(value: viewAction) diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Frameworks/Notifications/Local/LocalNotificationsService.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Frameworks/Notifications/Local/LocalNotificationsService.swift index 91dc997e2e..d238d5ba5a 100644 --- a/iosHyperskillApp/iosHyperskillApp/Sources/Frameworks/Notifications/Local/LocalNotificationsService.swift +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Frameworks/Notifications/Local/LocalNotificationsService.swift @@ -109,7 +109,7 @@ final class LocalNotificationsService { /// - fireDate: The Date object to be checked. /// - Returns: `true` if the `fireDate` exists and it in the future, otherwise false. private func isFireDateValid(_ fireDate: Date?) -> Bool { - if let fireDate = fireDate { + if let fireDate { return fireDate.compare(Date()) == .orderedDescending } else { return false diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Frameworks/Notifications/NotificationsService.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Frameworks/Notifications/NotificationsService.swift index 09d5296abf..2eb81986c8 100644 --- a/iosHyperskillApp/iosHyperskillApp/Sources/Frameworks/Notifications/NotificationsService.swift +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Frameworks/Notifications/NotificationsService.swift @@ -89,7 +89,7 @@ extension NotificationsService { // MARK: Private Helpers private func reportReceivedLocalNotification(with userInfo: NotificationUserInfo?) { - guard let userInfo = userInfo, + guard let userInfo, let notificationName = userInfo[ LocalNotificationsService.PayloadKey.notificationName.rawValue ] as? String else { @@ -119,7 +119,7 @@ extension NotificationsService { } } - guard let userInfo = userInfo, + guard let userInfo, let notificationName = userInfo[ LocalNotificationsService.PayloadKey.notificationName.rawValue ] as? String else { diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Frameworks/Notifications/Registration/NotificationsRegistrationService.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Frameworks/Notifications/Registration/NotificationsRegistrationService.swift index 1b3595426d..7474d6a52b 100644 --- a/iosHyperskillApp/iosHyperskillApp/Sources/Frameworks/Notifications/Registration/NotificationsRegistrationService.swift +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Frameworks/Notifications/Registration/NotificationsRegistrationService.swift @@ -248,7 +248,7 @@ extension NotificationsRegistrationService { } private func postCurrentPermissionStatus(_ permissionStatus: NotificationPermissionStatus? = nil) { - if let permissionStatus = permissionStatus { + if let permissionStatus { NotificationCenter.default.post( name: .notificationsRegistrationServiceDidUpdatePermissionStatus, object: permissionStatus diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Frameworks/WebController/WebViewController.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Frameworks/WebController/WebViewController.swift index f07c0db33d..e61b13657d 100644 --- a/iosHyperskillApp/iosHyperskillApp/Sources/Frameworks/WebController/WebViewController.swift +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Frameworks/WebController/WebViewController.swift @@ -233,3 +233,4 @@ class WebViewController: UIViewController { progressBar.setProgress(completed ? 0.0 : Float(webView.estimatedProgress), animated: !completed) } } +// swiftlint:enable all diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Helpers/MainBundleInfo.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Helpers/MainBundleInfo.swift index 93eeec93ee..9ba93b1441 100644 --- a/iosHyperskillApp/iosHyperskillApp/Sources/Helpers/MainBundleInfo.swift +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Helpers/MainBundleInfo.swift @@ -18,8 +18,8 @@ enum MainBundleInfo { /// Returns formatted version with build number string -> "1.0 (1)". /// Otherwise returns `nil` if missing `shortVersionString` or `buildNumberString`. static var shortVersionWithBuildNumberString: String? { - guard let shortVersionString = shortVersionString, - let buildNumberString = buildNumberString else { + guard let shortVersionString, + let buildNumberString else { return nil } diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Models/Constants/Images/Images.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Models/Constants/Images/Images.swift index 9a5a368464..d1f6ef9f3b 100644 --- a/iosHyperskillApp/iosHyperskillApp/Sources/Models/Constants/Images/Images.swift +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Models/Constants/Images/Images.swift @@ -1,6 +1,6 @@ import Foundation -//@available(*, deprecated, message: "Use Xcode 15 image resources instead") +// @available(*, deprecated, message: "Use Xcode 15 image resources instead") enum Images { // MARK: - Common - diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/AuthCredentials/Views/AuthCredentialsFormView.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/AuthCredentials/Views/AuthCredentialsFormView.swift index fa5b00a223..53b1d53fba 100644 --- a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/AuthCredentials/Views/AuthCredentialsFormView.swift +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/AuthCredentials/Views/AuthCredentialsFormView.swift @@ -61,7 +61,7 @@ struct AuthCredentialsFormView: View { Divider() } - if let errorMessage = errorMessage { + if let errorMessage { AuthCredentialsErrorView(message: errorMessage) } diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/AuthCredentials/Views/AuthTextField.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/AuthCredentials/Views/AuthTextField.swift index 43d9a96a45..37475a10a5 100644 --- a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/AuthCredentials/Views/AuthTextField.swift +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/AuthCredentials/Views/AuthTextField.swift @@ -17,14 +17,14 @@ final class AuthTextField: UITextField { button.setImage(Appearance.imageEyeOpened, for: .normal) button.imageView?.contentMode = .scaleAspectFit button.tintColor = Appearance.tintColor - button.imageEdgeInsets = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0) + button.imageEdgeInsets = .zero button.frame = CGRect( - x: self.frame.size.width - Appearance.eyeButtonSize.width, - y: (self.frame.size.height - Appearance.eyeButtonSize.height) / 2, + x: frame.size.width - Appearance.eyeButtonSize.width, + y: (frame.size.height - Appearance.eyeButtonSize.height) / 2, width: Appearance.eyeButtonSize.width, height: Appearance.eyeButtonSize.height ) - button.addTarget(self, action: #selector(self.togglePasswordField), for: .touchUpInside) + button.addTarget(self, action: #selector(togglePasswordField), for: .touchUpInside) return button }() diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/AuthSocial/Views/AuthSocialControlsView.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/AuthSocial/Views/AuthSocialControlsView.swift index 87a6352684..e1f76e349c 100644 --- a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/AuthSocial/Views/AuthSocialControlsView.swift +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/AuthSocial/Views/AuthSocialControlsView.swift @@ -15,7 +15,7 @@ struct AuthSocialControlsView: View { text: provider.humanReadableName, imageName: provider.imageName, action: { - self.onSocialAuthProviderClick(provider) + onSocialAuthProviderClick(provider) } ) } @@ -23,7 +23,7 @@ struct AuthSocialControlsView: View { if isContinueWithEmailAvailable { Button( Strings.Auth.Social.emailText, - action: self.onContinueWithEmailClick + action: onContinueWithEmailClick ) .padding(.top) } diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/Profile/Views/About/ProfileAboutView.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/Profile/Views/About/ProfileAboutView.swift index 3c52c03c86..587ee230b0 100644 --- a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/Profile/Views/About/ProfileAboutView.swift +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/Profile/Views/About/ProfileAboutView.swift @@ -29,19 +29,19 @@ struct ProfileAboutView: View { .font(.title3) .foregroundColor(.primaryText) - if let livesInText = livesInText { + if let livesInText { Text(livesInText) .font(.body) .foregroundColor(.secondaryText) } - if let speaksText = speaksText { + if let speaksText { Text(speaksText) .font(.body) .foregroundColor(.secondaryText) } - if let bio = bio { + if let bio { VStack(alignment: .leading, spacing: LayoutInsets.smallInset) { Text(Strings.Profile.bio) .font(.headline) @@ -53,7 +53,7 @@ struct ProfileAboutView: View { } } - if let experience = experience { + if let experience { VStack(alignment: .leading, spacing: LayoutInsets.smallInset) { Text(Strings.Profile.experience) .font(.headline) diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/ProfileSettings/ProfileSettingsView.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/ProfileSettings/ProfileSettingsView.swift index 1503f5f416..0f71ffe6b9 100644 --- a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/ProfileSettings/ProfileSettingsView.swift +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/ProfileSettings/ProfileSettingsView.swift @@ -111,11 +111,6 @@ struct ProfileSettingsView: View { .foregroundColor(.secondaryText) } } - - // ALTAPPS-312 - //Button(Strings.Settings.rateApplication) { - //} - //.foregroundColor(Color(ColorPalette.primary)) } Section { diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuiz/ChildProtocols/StepQuizChildQuizAssembly.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuiz/ChildProtocols/StepQuizChildQuizAssembly.swift index c94ec0c7e6..05ed1d78fd 100644 --- a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuiz/ChildProtocols/StepQuizChildQuizAssembly.swift +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuiz/ChildProtocols/StepQuizChildQuizAssembly.swift @@ -2,6 +2,7 @@ import Foundation import shared import SwiftUI +// swiftlint:disable function_parameter_count protocol StepQuizChildQuizAssembly: Assembly { var moduleInput: StepQuizChildQuizInputProtocol? { get } var moduleOutput: StepQuizChildQuizOutputProtocol? { get set } @@ -123,3 +124,4 @@ enum StepQuizChildQuizViewFactory { } } } +// swiftlint:enable function_parameter_count diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuiz/Views/BottomControls/StepQuizBottomControls.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuiz/Views/BottomControls/StepQuizBottomControls.swift index 98e5365147..2b9fbd8444 100644 --- a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuiz/Views/BottomControls/StepQuizBottomControls.swift +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuiz/Views/BottomControls/StepQuizBottomControls.swift @@ -7,7 +7,7 @@ struct StepQuizBottomControls: View { VStack { Divider() - StepQuizDiscussionsButton(onClick: self.onShowDiscussionsClick).padding() + StepQuizDiscussionsButton(onClick: onShowDiscussionsClick).padding() } .background(BackgroundView()) } diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuiz/Views/BottomControls/StepQuizDiscussionsButton.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuiz/Views/BottomControls/StepQuizDiscussionsButton.swift index e07ac88821..f15970c479 100644 --- a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuiz/Views/BottomControls/StepQuizDiscussionsButton.swift +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuiz/Views/BottomControls/StepQuizDiscussionsButton.swift @@ -17,7 +17,7 @@ struct StepQuizDiscussionsButton: View { var body: some View { Button( - action: { self.onClick?() }, + action: { onClick?() }, label: { Text(Strings.StepQuiz.discussionsButton) .font(.body) diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuiz/Views/StepQuizView.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuiz/Views/StepQuizView.swift index 1dd82dc09b..d14c756c9b 100644 --- a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuiz/Views/StepQuizView.swift +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuiz/Views/StepQuizView.swift @@ -119,6 +119,7 @@ struct StepQuizView: View { } } + // swiftlint:disable function_parameter_count @ViewBuilder private func buildQuizContent( state: StepQuizFeatureState, @@ -151,6 +152,7 @@ struct StepQuizView: View { StepQuizSkeletonViewFactory.makeSkeleton(for: quizType) } } + // swiftlint:enable function_parameter_count @ViewBuilder private func buildChildQuiz( diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCode/ViewData/StepQuizCodeViewDataMapper.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCode/ViewData/StepQuizCodeViewDataMapper.swift index 219b8b9d25..450fc60068 100644 --- a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCode/ViewData/StepQuizCodeViewDataMapper.swift +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCode/ViewData/StepQuizCodeViewDataMapper.swift @@ -18,7 +18,7 @@ class StepQuizCodeViewDataMapper { let languageStringValue = reply?.language ?? blockOptions.limits?.first?.key let language: CodeLanguage? = { - if let languageStringValue = languageStringValue { + if let languageStringValue { return CodeLanguage(rawValue: languageStringValue) } return nil @@ -26,13 +26,13 @@ class StepQuizCodeViewDataMapper { let languageHumanReadableName = step.displayLanguage ?? language?.humanReadableName let codeTemplate: String? = { - guard let languageStringValue = languageStringValue else { + guard let languageStringValue else { return nil } if let codeTemplate = blockOptions.codeTemplates?[languageStringValue] { return codeTemplate - } else if let language = language { + } else if let language { return CodeLanguageSamples.sample(for: language) } @@ -55,7 +55,7 @@ class StepQuizCodeViewDataMapper { // MARK: Private API private func mapSamples(_ samples: [[String]]?) -> [StepQuizCodeViewData.Sample] { - guard let samples = samples else { + guard let samples else { return [] } diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizFillBlanks/Views/UIKit/Cells/FillBlanksTextCollectionViewCell.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizFillBlanks/Views/UIKit/Cells/FillBlanksTextCollectionViewCell.swift index 208c046e5b..dd6f3f1751 100644 --- a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizFillBlanks/Views/UIKit/Cells/FillBlanksTextCollectionViewCell.swift +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizFillBlanks/Views/UIKit/Cells/FillBlanksTextCollectionViewCell.swift @@ -12,7 +12,7 @@ final class FillBlanksTextCollectionViewCell: UICollectionViewCell, Reusable { private static var prototypeTextLabel: UILabel? private lazy var textLabel: UILabel = { - Self.makeTextLabel(appearance: self.appearance) + Self.makeTextLabel(appearance: appearance) }() var appearance = Appearance() diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizFillBlanks/Views/UIKit/Cells/Input/FillBlanksInputCollectionViewCell.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizFillBlanks/Views/UIKit/Cells/Input/FillBlanksInputCollectionViewCell.swift index fe5a67d76c..6da0a7fcd2 100644 --- a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizFillBlanks/Views/UIKit/Cells/Input/FillBlanksInputCollectionViewCell.swift +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizFillBlanks/Views/UIKit/Cells/Input/FillBlanksInputCollectionViewCell.swift @@ -18,19 +18,18 @@ final class FillBlanksInputCollectionViewCell: UICollectionViewCell, Reusable { var appearance = Appearance() private lazy var inputContainerView: FillBlanksInputContainerView = { - let view = FillBlanksInputContainerView( - appearance: .init(cornerRadius: self.appearance.cornerRadius) + FillBlanksInputContainerView( + appearance: .init(cornerRadius: appearance.cornerRadius) ) - return view }() private lazy var textField: UITextField = { let textField = UITextField() textField.font = Appearance.font - textField.textColor = self.appearance.textColor + textField.textColor = appearance.textColor textField.textAlignment = .center textField.delegate = self - textField.addTarget(self, action: #selector(self.textFieldDidChange(_:)), for: .editingChanged) + textField.addTarget(self, action: #selector(textFieldDidChange(_:)), for: .editingChanged) // Disable features textField.autocapitalizationType = .none textField.autocorrectionType = .no diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizFillBlanks/Views/UIKit/Cells/Select/FillBlanksSelectCollectionViewCell.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizFillBlanks/Views/UIKit/Cells/Select/FillBlanksSelectCollectionViewCell.swift index 73c35af33a..221c8684bd 100644 --- a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizFillBlanks/Views/UIKit/Cells/Select/FillBlanksSelectCollectionViewCell.swift +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizFillBlanks/Views/UIKit/Cells/Select/FillBlanksSelectCollectionViewCell.swift @@ -22,14 +22,13 @@ final class FillBlanksSelectCollectionViewCell: UICollectionViewCell, Reusable { var appearance = Appearance() private lazy var inputContainerView: FillBlanksSelectContainerView = { - let view = FillBlanksSelectContainerView( - appearance: .init(cornerRadius: self.appearance.cornerRadius) + FillBlanksSelectContainerView( + appearance: .init(cornerRadius: appearance.cornerRadius) ) - return view }() private lazy var textLabel: UILabel = { - Self.makeTextLabel(appearance: self.appearance) + Self.makeTextLabel(appearance: appearance) }() var text: String? { diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizFillBlanks/Views/UIKit/FillBlanksQuizTitleView.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizFillBlanks/Views/UIKit/FillBlanksQuizTitleView.swift index bc07e7742e..37b451d025 100644 --- a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizFillBlanks/Views/UIKit/FillBlanksQuizTitleView.swift +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizFillBlanks/Views/UIKit/FillBlanksQuizTitleView.swift @@ -17,8 +17,8 @@ final class FillBlanksQuizTitleView: UIView { private lazy var titleLabel: UILabel = { let label = UILabel() label.text = Strings.StepQuizFillBlanks.title - label.textColor = self.appearance.textColor - label.font = self.appearance.font + label.textColor = appearance.textColor + label.font = appearance.font label.numberOfLines = 1 return label }() diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizFillBlanks/Views/UIKit/FillBlanksQuizView.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizFillBlanks/Views/UIKit/FillBlanksQuizView.swift index 15f0de43a9..013e871236 100644 --- a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizFillBlanks/Views/UIKit/FillBlanksQuizView.swift +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizFillBlanks/Views/UIKit/FillBlanksQuizView.swift @@ -24,15 +24,15 @@ final class FillBlanksQuizView: UIView { private lazy var collectionView: UICollectionView = { let collectionViewLayout = LeftAlignedCollectionViewFlowLayout() collectionViewLayout.scrollDirection = .vertical - collectionViewLayout.minimumLineSpacing = self.appearance.collectionViewMinLineSpacing - collectionViewLayout.minimumInteritemSpacing = self.appearance.collectionViewMinInteritemSpacing - collectionViewLayout.sectionInset = self.appearance.collectionViewSectionInset + collectionViewLayout.minimumLineSpacing = appearance.collectionViewMinLineSpacing + collectionViewLayout.minimumInteritemSpacing = appearance.collectionViewMinInteritemSpacing + collectionViewLayout.sectionInset = appearance.collectionViewSectionInset let collectionView = UICollectionView( frame: .zero, collectionViewLayout: collectionViewLayout ) - collectionView.backgroundColor = self.appearance.backgroundColor + collectionView.backgroundColor = appearance.backgroundColor collectionView.isScrollEnabled = false collectionView.register(cellClass: FillBlanksInputCollectionViewCell.self) collectionView.register(cellClass: FillBlanksTextCollectionViewCell.self) diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizFillBlanksSelectOptions/Views/StepQuizFillBlanksSelectOptionsViewWrapper.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizFillBlanksSelectOptions/Views/StepQuizFillBlanksSelectOptionsViewWrapper.swift index 7ae7a5a8b0..4caec7b460 100644 --- a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizFillBlanksSelectOptions/Views/StepQuizFillBlanksSelectOptionsViewWrapper.swift +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizFillBlanksSelectOptions/Views/StepQuizFillBlanksSelectOptionsViewWrapper.swift @@ -41,6 +41,7 @@ struct StepQuizFillBlanksSelectOptionsViewWrapper: UIViewRepresentable { ) } context.coordinator.onDidSelectComponent = { + // swiftlint:disable:next closure_parameter_position [weak uiView, weak collectionViewAdapter, weak moduleOutput] indexPath in guard let uiView, let collectionViewAdapter else { return diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizFillBlanksSelectOptions/Views/UIKit/Cell/StepQuizFillBlanksSelectOptionsCollectionViewCell.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizFillBlanksSelectOptions/Views/UIKit/Cell/StepQuizFillBlanksSelectOptionsCollectionViewCell.swift index def7e55bb2..c2578600f9 100644 --- a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizFillBlanksSelectOptions/Views/UIKit/Cell/StepQuizFillBlanksSelectOptionsCollectionViewCell.swift +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizFillBlanksSelectOptions/Views/UIKit/Cell/StepQuizFillBlanksSelectOptionsCollectionViewCell.swift @@ -22,14 +22,13 @@ final class StepQuizFillBlanksSelectOptionsCollectionViewCell: UICollectionViewC var appearance = Appearance() private lazy var inputContainerView: StepQuizFillBlanksSelectOptionsCollectionViewCellContainerView = { - let view = StepQuizFillBlanksSelectOptionsCollectionViewCellContainerView( - appearance: .init(cornerRadius: self.appearance.cornerRadius) + StepQuizFillBlanksSelectOptionsCollectionViewCellContainerView( + appearance: .init(cornerRadius: appearance.cornerRadius) ) - return view }() private lazy var textLabel: UILabel = { - Self.makeTextLabel(appearance: self.appearance) + Self.makeTextLabel(appearance: appearance) }() var text: String? { diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizFillBlanksSelectOptions/Views/UIKit/StepQuizFillBlanksSelectOptionsView.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizFillBlanksSelectOptions/Views/UIKit/StepQuizFillBlanksSelectOptionsView.swift index dd992ce224..67336f6c56 100644 --- a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizFillBlanksSelectOptions/Views/UIKit/StepQuizFillBlanksSelectOptionsView.swift +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizFillBlanksSelectOptions/Views/UIKit/StepQuizFillBlanksSelectOptionsView.swift @@ -19,15 +19,15 @@ final class StepQuizFillBlanksSelectOptionsView: UIView { private lazy var collectionView: UICollectionView = { let collectionViewLayout = LeftAlignedCollectionViewFlowLayout() collectionViewLayout.scrollDirection = .vertical - collectionViewLayout.minimumLineSpacing = self.appearance.collectionViewMinLineSpacing - collectionViewLayout.minimumInteritemSpacing = self.appearance.collectionViewMinInteritemSpacing - collectionViewLayout.sectionInset = self.appearance.collectionViewSectionInset + collectionViewLayout.minimumLineSpacing = appearance.collectionViewMinLineSpacing + collectionViewLayout.minimumInteritemSpacing = appearance.collectionViewMinInteritemSpacing + collectionViewLayout.sectionInset = appearance.collectionViewSectionInset let collectionView = UICollectionView( frame: .zero, collectionViewLayout: collectionViewLayout ) - collectionView.backgroundColor = self.appearance.backgroundColor + collectionView.backgroundColor = appearance.backgroundColor collectionView.isScrollEnabled = false collectionView.automaticallyAdjustsScrollIndicatorInsets = false collectionView.register(cellClass: StepQuizFillBlanksSelectOptionsCollectionViewCell.self) diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizString/StepQuizStringViewModel.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizString/StepQuizStringViewModel.swift index 5539c57d8d..b21e28304b 100644 --- a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizString/StepQuizStringViewModel.swift +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizString/StepQuizStringViewModel.swift @@ -19,7 +19,7 @@ final class StepQuizStringViewModel: ObservableObject, StepQuizChildQuizInputPro self.reply = reply let text: String = { () -> String? in - guard let reply = reply else { + guard let reply else { return nil } diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizTable/StepQuizTableViewModel.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizTable/StepQuizTableViewModel.swift index 9a11b4f6c5..4ff40cf275 100644 --- a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizTable/StepQuizTableViewModel.swift +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizTable/StepQuizTableViewModel.swift @@ -39,7 +39,7 @@ final class StepQuizTableViewModel: ObservableObject, StepQuizChildQuizInputProt } func makeSelectColumnsViewController(for row: StepQuizTableViewData.Row) -> PanModalPresentableViewController { - let viewController = StepQuizTableSelectColumnsViewController( + StepQuizTableSelectColumnsViewController( row: row, columns: viewData.columns, selectedColumnsIDs: Set(row.answers.map(\.id)), @@ -59,8 +59,6 @@ final class StepQuizTableViewModel: ObservableObject, StepQuizChildQuizInputProt strongSelf.outputCurrentReply() } ) - - return viewController } func createReply() -> Reply { diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizTable/Views/StepQuizTableRowView.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizTable/Views/StepQuizTableRowView.swift index 2403a58d69..ee9e752e12 100644 --- a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizTable/Views/StepQuizTableRowView.swift +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizTable/Views/StepQuizTableRowView.swift @@ -38,7 +38,7 @@ struct StepQuizTableRowView: View { ) ) - if let subtitle = subtitle, !subtitle.isEmpty { + if let subtitle, !subtitle.isEmpty { LatexView( text: subtitle, configuration: .quizContent( diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Services/Auth/Social/SDKs/GoogleSocialAuthSDKProvider.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Services/Auth/Social/SDKs/GoogleSocialAuthSDKProvider.swift index 501bea2bc2..2eb64c3218 100644 --- a/iosHyperskillApp/iosHyperskillApp/Sources/Services/Auth/Social/SDKs/GoogleSocialAuthSDKProvider.swift +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Services/Auth/Social/SDKs/GoogleSocialAuthSDKProvider.swift @@ -44,7 +44,7 @@ final class GoogleSocialAuthSDKProvider: SocialAuthSDKProvider { ), presenting: currentPresentedViewController ) { user, error in - if let error = error { + if let error { #if DEBUG print("GoogleSocialAuthSDKProvider :: error = \(error.localizedDescription)") #endif diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Systems/AppPowerModeObserver.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Systems/AppPowerModeObserver.swift index 81b9e8467b..c4e9fd603d 100644 --- a/iosHyperskillApp/iosHyperskillApp/Sources/Systems/AppPowerModeObserver.swift +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Systems/AppPowerModeObserver.swift @@ -17,7 +17,7 @@ final class AppPowerModeObserver { private func updateProgressHUDHapticsEnabled() { // swiftlint:disable:next unowned_variable_capture DispatchQueue.main.async { [unowned self] in - ProgressHUD.setHapticsEnabled(!self.isLowPowerModeEnabled) + ProgressHUD.setHapticsEnabled(!isLowPowerModeEnabled) } } diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Views/SwiftUI/Introspect/PullToRefresh.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Views/SwiftUI/Introspect/PullToRefresh.swift index 13d80878c9..31ad07ea0f 100644 --- a/iosHyperskillApp/iosHyperskillApp/Sources/Views/SwiftUI/Introspect/PullToRefresh.swift +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Views/SwiftUI/Introspect/PullToRefresh.swift @@ -24,11 +24,11 @@ private struct PullToRefresh: UIViewRepresentable { } func updateUIView(_ uiView: UIKitIntrospectionView, context: Context) { - /// When `updateUiView` is called after creating the Introspection view, it is not yet in the UIKit hierarchy. - /// At this point, `introspectionView.superview.superview` is nil and we can't access the target UIKit view. - /// To workaround this, we wait until the runloop is done inserting the introspection view in the hierarchy, then run the selector. - /// Finding the target view fails silently if the selector yield no result. This happens when `updateUIView` - /// gets called when the introspection view gets removed from the hierarchy. + // When `updateUiView` is called after creating the Introspection view, it is not yet in the UIKit hierarchy. + // At this point, `introspectionView.superview.superview` is nil and we can't access the target UIKit view. + // To workaround this, we wait until the runloop is done inserting the introspection view in the hierarchy, then run the selector. + // Finding the target view fails silently if the selector yield no result. This happens when `updateUIView` + // gets called when the introspection view gets removed from the hierarchy. let coordinator = context.coordinator if context.coordinator.refreshControl == nil { uiView.moveToWindowHandler = { [weak uiView, weak coordinator] in diff --git a/iosHyperskillApp/iosHyperskillAppTests/CollectionsTests/LinkedListTests.swift b/iosHyperskillApp/iosHyperskillAppTests/CollectionsTests/LinkedListTests.swift index c5a9a25926..c07957708a 100644 --- a/iosHyperskillApp/iosHyperskillAppTests/CollectionsTests/LinkedListTests.swift +++ b/iosHyperskillApp/iosHyperskillAppTests/CollectionsTests/LinkedListTests.swift @@ -147,3 +147,4 @@ class LinkedListTest: XCTestCase { XCTAssertNil(list.tail) } } +// swiftlint:enable force_unwrapping diff --git a/iosHyperskillApp/iosHyperskillAppTests/ExtensionsTests/URLExtensionsTests.swift b/iosHyperskillApp/iosHyperskillAppTests/ExtensionsTests/URLExtensionsTests.swift index 2ba85ce929..3ef97a6cdc 100644 --- a/iosHyperskillApp/iosHyperskillAppTests/ExtensionsTests/URLExtensionsTests.swift +++ b/iosHyperskillApp/iosHyperskillAppTests/ExtensionsTests/URLExtensionsTests.swift @@ -29,3 +29,4 @@ class URLExtensionsTests: XCTestCase { XCTAssertNil(otherResult) } } +// swiftlint:enable force_unwrapping diff --git a/iosHyperskillApp/iosHyperskillAppTests/IntrospectTests/IntrospectScrollViewTests.swift b/iosHyperskillApp/iosHyperskillAppTests/IntrospectTests/IntrospectScrollViewTests.swift index 40e4b72934..678d3678e4 100644 --- a/iosHyperskillApp/iosHyperskillAppTests/IntrospectTests/IntrospectScrollViewTests.swift +++ b/iosHyperskillApp/iosHyperskillAppTests/IntrospectTests/IntrospectScrollViewTests.swift @@ -14,7 +14,7 @@ final class IntrospectScrollViewTests: XCTestCase { EmptyView() } .introspectScrollView { _ in - self.spy() + spy() } } } diff --git a/iosHyperskillApp/iosHyperskillAppTests/IntrospectTests/IntrospectViewControllerTests.swift b/iosHyperskillApp/iosHyperskillAppTests/IntrospectTests/IntrospectViewControllerTests.swift index 667f80fbb2..845c04e7de 100644 --- a/iosHyperskillApp/iosHyperskillAppTests/IntrospectTests/IntrospectViewControllerTests.swift +++ b/iosHyperskillApp/iosHyperskillAppTests/IntrospectTests/IntrospectViewControllerTests.swift @@ -14,7 +14,7 @@ final class IntrospectViewControllerTests: XCTestCase { } } .introspectViewController { _ in - self.spy() + spy() } } } @@ -29,7 +29,7 @@ final class IntrospectViewControllerTests: XCTestCase { } } .introspectHostingController { (_: UIHostingController) in - self.spy() + spy() } } } From 82160a539a667e6e40216eba9e7523c7747b9fb8 Mon Sep 17 00:00:00 2001 From: Ivan Magda Date: Fri, 23 Feb 2024 09:36:24 +0700 Subject: [PATCH 084/104] iOS: Fix code editor not replaces symbols in selection (#917) ^ALTAPPS-638 --- .../Model/Analyze/CodePlaygroundManager.swift | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Frameworks/CodeEditor/Model/Analyze/CodePlaygroundManager.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Frameworks/CodeEditor/Model/Analyze/CodePlaygroundManager.swift index 868fd1f19c..5aebb8c6ca 100644 --- a/iosHyperskillApp/iosHyperskillApp/Sources/Frameworks/CodeEditor/Model/Analyze/CodePlaygroundManager.swift +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Frameworks/CodeEditor/Model/Analyze/CodePlaygroundManager.swift @@ -433,12 +433,17 @@ final class CodePlaygroundManager { return } - let cursorPosition = textView.offset(from: textView.beginningOfDocument, to: selectedRange.start) - var text = textView.text ?? "" - text.insert(contentsOf: symbols, at: text.index(text.startIndex, offsetBy: cursorPosition)) - textView.text = text - // Import here to update selectedTextRange before calling textViewDidChange #APPS-2352 - textView.selectedTextRange = textRangeFrom(position: cursorPosition + symbols.count, textView: textView) + if selectedRange.isEmpty { + let cursorPosition = textView.offset(from: textView.beginningOfDocument, to: selectedRange.start) + var text = textView.text ?? "" + text.insert(contentsOf: symbols, at: text.index(text.startIndex, offsetBy: cursorPosition)) + textView.text = text + // Import here to update selectedTextRange before calling textViewDidChange #APPS-2352 + textView.selectedTextRange = textRangeFrom(position: cursorPosition + symbols.count, textView: textView) + } else { + textView.replace(selectedRange, withText: symbols) + } + // Manually call textViewDidChange, because when manually setting the text of a UITextView with code, // the textViewDidChange: method does not get called. textView.delegate?.textViewDidChange?(textView) From 8b6f9b75986f5092ed707210b873a627e36657e9 Mon Sep 17 00:00:00 2001 From: github-actions Date: Fri, 23 Feb 2024 02:37:03 +0000 Subject: [PATCH 085/104] iOS: Bump build number --- .../NotificationServiceExtension/Info.plist | 2 +- .../iosHyperskillApp.xcodeproj/project.pbxproj | 16 ++++++++-------- iosHyperskillApp/iosHyperskillApp/Info.plist | 2 +- .../iosHyperskillAppTests/Info.plist | 2 +- .../iosHyperskillAppUITests/Info.plist | 2 +- 5 files changed, 12 insertions(+), 12 deletions(-) diff --git a/iosHyperskillApp/NotificationServiceExtension/Info.plist b/iosHyperskillApp/NotificationServiceExtension/Info.plist index e221245f3d..7e9cfe8f64 100644 --- a/iosHyperskillApp/NotificationServiceExtension/Info.plist +++ b/iosHyperskillApp/NotificationServiceExtension/Info.plist @@ -9,7 +9,7 @@ CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleVersion - 339 + 340 CFBundleShortVersionString 1.50 CFBundlePackageType diff --git a/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj b/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj index c85c2d8b9a..88a4d166aa 100644 --- a/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj +++ b/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj @@ -5065,7 +5065,7 @@ buildSettings = { CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 339; + CURRENT_PROJECT_VERSION = 340; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = 3DWS674B2M; GENERATE_INFOPLIST_FILE = NO; @@ -5086,7 +5086,7 @@ buildSettings = { CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 339; + CURRENT_PROJECT_VERSION = 340; DEVELOPMENT_TEAM = 3DWS674B2M; GENERATE_INFOPLIST_FILE = NO; INFOPLIST_FILE = iosHyperskillAppUITests/Info.plist; @@ -5107,7 +5107,7 @@ BUNDLE_LOADER = "$(TEST_HOST)"; CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 339; + CURRENT_PROJECT_VERSION = 340; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = 3DWS674B2M; INFOPLIST_FILE = iosHyperskillAppTests/Info.plist; @@ -5128,7 +5128,7 @@ BUNDLE_LOADER = "$(TEST_HOST)"; CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 339; + CURRENT_PROJECT_VERSION = 340; DEVELOPMENT_TEAM = 3DWS674B2M; INFOPLIST_FILE = iosHyperskillAppTests/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; @@ -5149,7 +5149,7 @@ CODE_SIGN_ENTITLEMENTS = NotificationServiceExtension/NotificationServiceExtension.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 339; + CURRENT_PROJECT_VERSION = 340; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = 3DWS674B2M; INFOPLIST_FILE = NotificationServiceExtension/Info.plist; @@ -5177,7 +5177,7 @@ CODE_SIGN_ENTITLEMENTS = NotificationServiceExtension/NotificationServiceExtension.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 339; + CURRENT_PROJECT_VERSION = 340; DEVELOPMENT_TEAM = 3DWS674B2M; INFOPLIST_FILE = NotificationServiceExtension/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; @@ -5322,7 +5322,7 @@ CODE_SIGN_ENTITLEMENTS = iosHyperskillApp/iosHyperskillApp.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 339; + CURRENT_PROJECT_VERSION = 340; DEVELOPMENT_ASSET_PATHS = "\"iosHyperskillApp/Preview Content\""; DEVELOPMENT_TEAM = 3DWS674B2M; ENABLE_PREVIEWS = YES; @@ -5358,7 +5358,7 @@ CODE_SIGN_ENTITLEMENTS = iosHyperskillApp/iosHyperskillApp.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 339; + CURRENT_PROJECT_VERSION = 340; DEVELOPMENT_ASSET_PATHS = "\"iosHyperskillApp/Preview Content\""; DEVELOPMENT_TEAM = 3DWS674B2M; ENABLE_PREVIEWS = YES; diff --git a/iosHyperskillApp/iosHyperskillApp/Info.plist b/iosHyperskillApp/iosHyperskillApp/Info.plist index 05bda6d2f3..f7c86e54d8 100644 --- a/iosHyperskillApp/iosHyperskillApp/Info.plist +++ b/iosHyperskillApp/iosHyperskillApp/Info.plist @@ -36,7 +36,7 @@ CFBundleVersion - 339 + 340 FirebaseAppDelegateProxyEnabled FirebaseMessagingAutoInitEnabled diff --git a/iosHyperskillApp/iosHyperskillAppTests/Info.plist b/iosHyperskillApp/iosHyperskillAppTests/Info.plist index 716ada6f16..09cb717367 100644 --- a/iosHyperskillApp/iosHyperskillAppTests/Info.plist +++ b/iosHyperskillApp/iosHyperskillAppTests/Info.plist @@ -15,6 +15,6 @@ CFBundleShortVersionString 1.50 CFBundleVersion - 339 + 340 diff --git a/iosHyperskillApp/iosHyperskillAppUITests/Info.plist b/iosHyperskillApp/iosHyperskillAppUITests/Info.plist index 55b3b70838..dd582c52ac 100644 --- a/iosHyperskillApp/iosHyperskillAppUITests/Info.plist +++ b/iosHyperskillApp/iosHyperskillAppUITests/Info.plist @@ -15,6 +15,6 @@ CFBundleShortVersionString 1.50 CFBundleVersion - 339 + 340 From 10e01f99dddd2465f561d7b1efac2c7f767180ba Mon Sep 17 00:00:00 2001 From: Ivan Magda Date: Fri, 23 Feb 2024 11:01:21 +0700 Subject: [PATCH 086/104] Shared: Fix Home screen not updates problem of the day after topic is completed (#918) ^ALTAPPS-767 --- .../app/home/injection/HomeComponentImpl.kt | 1 + .../app/home/injection/HomeFeatureBuilder.kt | 5 ++- .../home/presentation/HomeActionDispatcher.kt | 31 +++++++++++++++++-- .../app/home/presentation/HomeFeature.kt | 14 +++++++-- .../app/home/presentation/HomeReducer.kt | 24 ++++++++++++-- 5 files changed, 66 insertions(+), 9 deletions(-) diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/home/injection/HomeComponentImpl.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/home/injection/HomeComponentImpl.kt index f83913f8a8..924cdb2afb 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/home/injection/HomeComponentImpl.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/home/injection/HomeComponentImpl.kt @@ -33,6 +33,7 @@ internal class HomeComponentImpl(private val appGraph: AppGraph) : HomeComponent appGraph.sentryComponent.sentryInteractor, appGraph.commonComponent.dateFormatter, appGraph.topicsRepetitionsFlowDataComponent.topicRepeatedFlow, + appGraph.stepCompletionFlowDataComponent.topicCompletedFlow, gamificationToolbarComponent.gamificationToolbarReducer, gamificationToolbarComponent.gamificationToolbarActionDispatcher, challengeWidgetComponent.challengeWidgetReducer, diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/home/injection/HomeFeatureBuilder.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/home/injection/HomeFeatureBuilder.kt index 589cfe479d..c25aeacc55 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/home/injection/HomeFeatureBuilder.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/home/injection/HomeFeatureBuilder.kt @@ -27,6 +27,7 @@ import org.hyperskill.app.logging.presentation.wrapWithLogger import org.hyperskill.app.profile.domain.repository.CurrentProfileStateRepository import org.hyperskill.app.sentry.domain.interactor.SentryInteractor import org.hyperskill.app.step.domain.interactor.StepInteractor +import org.hyperskill.app.step_completion.domain.flow.TopicCompletedFlow import org.hyperskill.app.topics_repetitions.domain.flow.TopicRepeatedFlow import org.hyperskill.app.topics_repetitions.domain.interactor.TopicsRepetitionsInteractor import ru.nobird.app.core.model.safeCast @@ -48,6 +49,7 @@ internal object HomeFeatureBuilder { sentryInteractor: SentryInteractor, dateFormatter: SharedDateFormatter, topicRepeatedFlow: TopicRepeatedFlow, + topicCompletedFlow: TopicCompletedFlow, gamificationToolbarReducer: GamificationToolbarReducer, gamificationToolbarActionDispatcher: GamificationToolbarActionDispatcher, challengeWidgetReducer: ChallengeWidgetReducer, @@ -74,7 +76,8 @@ internal object HomeFeatureBuilder { analyticInteractor, sentryInteractor, dateFormatter, - topicRepeatedFlow + topicRepeatedFlow, + topicCompletedFlow ) val homeViewStateMapper = HomeViewStateMapper( challengeWidgetViewStateMapper = challengeWidgetViewStateMapper, diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/home/presentation/HomeActionDispatcher.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/home/presentation/HomeActionDispatcher.kt index 6c90b5ed7c..f065cb4e4a 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/home/presentation/HomeActionDispatcher.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/home/presentation/HomeActionDispatcher.kt @@ -17,12 +17,14 @@ import org.hyperskill.app.freemium.domain.interactor.FreemiumInteractor import org.hyperskill.app.home.domain.interactor.HomeInteractor import org.hyperskill.app.home.presentation.HomeFeature.Action import org.hyperskill.app.home.presentation.HomeFeature.InternalAction +import org.hyperskill.app.home.presentation.HomeFeature.InternalMessage import org.hyperskill.app.home.presentation.HomeFeature.Message import org.hyperskill.app.profile.domain.repository.CurrentProfileStateRepository import org.hyperskill.app.sentry.domain.interactor.SentryInteractor import org.hyperskill.app.sentry.domain.model.transaction.HyperskillSentryTransactionBuilder import org.hyperskill.app.sentry.domain.withTransaction import org.hyperskill.app.step.domain.interactor.StepInteractor +import org.hyperskill.app.step_completion.domain.flow.TopicCompletedFlow import org.hyperskill.app.topics_repetitions.domain.flow.TopicRepeatedFlow import org.hyperskill.app.topics_repetitions.domain.interactor.TopicsRepetitionsInteractor import ru.nobird.app.presentation.redux.dispatcher.CoroutineActionDispatcher @@ -37,7 +39,8 @@ internal class HomeActionDispatcher( private val analyticInteractor: AnalyticInteractor, private val sentryInteractor: SentryInteractor, private val dateFormatter: SharedDateFormatter, - topicRepeatedFlow: TopicRepeatedFlow + topicRepeatedFlow: TopicRepeatedFlow, + topicCompletedFlow: TopicCompletedFlow ) : CoroutineActionDispatcher(config.createConfig()) { private var isTimerLaunched: Boolean = false @@ -47,11 +50,15 @@ internal class HomeActionDispatcher( init { homeInteractor.solvedStepsSharedFlow - .onEach { onNewMessage(Message.StepQuizSolved(it)) } + .onEach { onNewMessage(InternalMessage.StepQuizSolved(it)) } .launchIn(actionScope) topicRepeatedFlow.observe() - .onEach { onNewMessage(Message.TopicRepeated) } + .onEach { onNewMessage(InternalMessage.TopicRepeated) } + .launchIn(actionScope) + + topicCompletedFlow.observe() + .onEach { onNewMessage(InternalMessage.TopicCompleted) } .launchIn(actionScope) } @@ -59,6 +66,8 @@ internal class HomeActionDispatcher( when (action) { is InternalAction.FetchHomeScreenData -> handleFetchHomeScreenData(::onNewMessage) + is InternalAction.FetchProblemOfDayState -> + handleFetchProblemOfDayState(::onNewMessage) is InternalAction.LaunchTimer -> { if (isTimerLaunched) { return @@ -118,6 +127,22 @@ internal class HomeActionDispatcher( }.forEach(onNewMessage) } + private suspend fun handleFetchProblemOfDayState(onNewMessage: (Message) -> Unit) { + val currentProfile = currentProfileStateRepository + .getState(forceUpdate = true) + .getOrElse { return onNewMessage(InternalMessage.FetchProblemOfDayStateResultError) } + + getProblemOfDayState(currentProfile.dailyStep) + .fold( + onSuccess = { + onNewMessage(InternalMessage.FetchProblemOfDayStateResultSuccess(it)) + }, + onFailure = { + onNewMessage(InternalMessage.FetchProblemOfDayStateResultError) + } + ) + } + private suspend fun getProblemOfDayState(dailyStepId: Long?): Result = if (dailyStepId == null) { Result.success(HomeFeature.ProblemOfDayState.Empty) diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/home/presentation/HomeFeature.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/home/presentation/HomeFeature.kt index 57089bae12..23afa15fb7 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/home/presentation/HomeFeature.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/home/presentation/HomeFeature.kt @@ -121,9 +121,6 @@ object HomeFeature { object NextProblemInTimerStopped : Message data class HomeNextProblemInUpdate(val nextProblemIn: String) : Message - data class StepQuizSolved(val stepId: Long) : Message - object TopicRepeated : Message - object ClickedTopicsRepetitionsCard : Message object ClickedProblemOfDayCardReload : Message @@ -149,6 +146,15 @@ object HomeFeature { ) : Message } + internal sealed interface InternalMessage : Message { + data class StepQuizSolved(val stepId: Long) : InternalMessage + object TopicRepeated : InternalMessage + object TopicCompleted : InternalMessage + + object FetchProblemOfDayStateResultError : InternalMessage + data class FetchProblemOfDayStateResultSuccess(val problemOfDayState: ProblemOfDayState) : InternalMessage + } + sealed interface Action { sealed interface ViewAction : Action { sealed interface NavigateTo : ViewAction { @@ -177,6 +183,8 @@ object HomeFeature { object FetchHomeScreenData : InternalAction object LaunchTimer : InternalAction + object FetchProblemOfDayState : InternalAction + data class LogAnalyticEvent(val analyticEvent: AnalyticEvent) : InternalAction /** diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/home/presentation/HomeReducer.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/home/presentation/HomeReducer.kt index bec9f7b7b5..5783b3d559 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/home/presentation/HomeReducer.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/home/presentation/HomeReducer.kt @@ -13,6 +13,7 @@ import org.hyperskill.app.home.domain.analytic.HomeViewedHyperskillAnalyticEvent import org.hyperskill.app.home.presentation.HomeFeature.Action import org.hyperskill.app.home.presentation.HomeFeature.HomeState import org.hyperskill.app.home.presentation.HomeFeature.InternalAction +import org.hyperskill.app.home.presentation.HomeFeature.InternalMessage import org.hyperskill.app.home.presentation.HomeFeature.Message import org.hyperskill.app.home.presentation.HomeFeature.State import org.hyperskill.app.interview_preparation.presentation.InterviewPreparationWidgetFeature @@ -107,7 +108,7 @@ internal class HomeReducer( null } // Flow Messages - is Message.StepQuizSolved -> { + is InternalMessage.StepQuizSolved -> { if (state.homeState is HomeState.Content) { val problemOfDayState = if ( state.homeState.problemOfDayState is HomeFeature.ProblemOfDayState.NeedToSolve && @@ -126,7 +127,7 @@ internal class HomeReducer( null } } - is Message.TopicRepeated -> + is InternalMessage.TopicRepeated -> if ( state.homeState is HomeState.Content && state.homeState.repetitionsState is HomeFeature.RepetitionsState.Available @@ -141,6 +142,25 @@ internal class HomeReducer( } else { null } + InternalMessage.TopicCompleted -> + if (state.homeState is HomeState.Content && + state.homeState.problemOfDayState is HomeFeature.ProblemOfDayState.Empty + ) { + state to setOf(InternalAction.FetchProblemOfDayState) + } else { + null + } + InternalMessage.FetchProblemOfDayStateResultError -> null + is InternalMessage.FetchProblemOfDayStateResultSuccess -> + if (state.homeState is HomeState.Content) { + state.copy( + homeState = state.homeState.copy( + problemOfDayState = message.problemOfDayState + ) + ) to emptySet() + } else { + null + } is Message.ClickedTopicsRepetitionsCard -> if (state.homeState is HomeState.Content) { val isCompleted = state.homeState.repetitionsState is HomeFeature.RepetitionsState.Available && From 70577d166f968e3db9c0f3ca7c14859a174d27c4 Mon Sep 17 00:00:00 2001 From: github-actions Date: Fri, 23 Feb 2024 04:01:56 +0000 Subject: [PATCH 087/104] Bump build number --- gradle/app.versions.toml | 2 +- .../NotificationServiceExtension/Info.plist | 2 +- .../iosHyperskillApp.xcodeproj/project.pbxproj | 16 ++++++++-------- iosHyperskillApp/iosHyperskillApp/Info.plist | 2 +- .../iosHyperskillAppTests/Info.plist | 2 +- .../iosHyperskillAppUITests/Info.plist | 2 +- 6 files changed, 13 insertions(+), 13 deletions(-) diff --git a/gradle/app.versions.toml b/gradle/app.versions.toml index bc8098d7f3..2a1dd2ae21 100644 --- a/gradle/app.versions.toml +++ b/gradle/app.versions.toml @@ -3,4 +3,4 @@ minSdk = '24' targetSdk = '33' compileSdk = '33' versionName = '1.50' -versionCode = '334' \ No newline at end of file +versionCode = '335' \ No newline at end of file diff --git a/iosHyperskillApp/NotificationServiceExtension/Info.plist b/iosHyperskillApp/NotificationServiceExtension/Info.plist index 7e9cfe8f64..0259cd4085 100644 --- a/iosHyperskillApp/NotificationServiceExtension/Info.plist +++ b/iosHyperskillApp/NotificationServiceExtension/Info.plist @@ -9,7 +9,7 @@ CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleVersion - 340 + 341 CFBundleShortVersionString 1.50 CFBundlePackageType diff --git a/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj b/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj index 88a4d166aa..a3451aaed5 100644 --- a/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj +++ b/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj @@ -5065,7 +5065,7 @@ buildSettings = { CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 340; + CURRENT_PROJECT_VERSION = 341; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = 3DWS674B2M; GENERATE_INFOPLIST_FILE = NO; @@ -5086,7 +5086,7 @@ buildSettings = { CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 340; + CURRENT_PROJECT_VERSION = 341; DEVELOPMENT_TEAM = 3DWS674B2M; GENERATE_INFOPLIST_FILE = NO; INFOPLIST_FILE = iosHyperskillAppUITests/Info.plist; @@ -5107,7 +5107,7 @@ BUNDLE_LOADER = "$(TEST_HOST)"; CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 340; + CURRENT_PROJECT_VERSION = 341; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = 3DWS674B2M; INFOPLIST_FILE = iosHyperskillAppTests/Info.plist; @@ -5128,7 +5128,7 @@ BUNDLE_LOADER = "$(TEST_HOST)"; CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 340; + CURRENT_PROJECT_VERSION = 341; DEVELOPMENT_TEAM = 3DWS674B2M; INFOPLIST_FILE = iosHyperskillAppTests/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; @@ -5149,7 +5149,7 @@ CODE_SIGN_ENTITLEMENTS = NotificationServiceExtension/NotificationServiceExtension.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 340; + CURRENT_PROJECT_VERSION = 341; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = 3DWS674B2M; INFOPLIST_FILE = NotificationServiceExtension/Info.plist; @@ -5177,7 +5177,7 @@ CODE_SIGN_ENTITLEMENTS = NotificationServiceExtension/NotificationServiceExtension.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 340; + CURRENT_PROJECT_VERSION = 341; DEVELOPMENT_TEAM = 3DWS674B2M; INFOPLIST_FILE = NotificationServiceExtension/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; @@ -5322,7 +5322,7 @@ CODE_SIGN_ENTITLEMENTS = iosHyperskillApp/iosHyperskillApp.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 340; + CURRENT_PROJECT_VERSION = 341; DEVELOPMENT_ASSET_PATHS = "\"iosHyperskillApp/Preview Content\""; DEVELOPMENT_TEAM = 3DWS674B2M; ENABLE_PREVIEWS = YES; @@ -5358,7 +5358,7 @@ CODE_SIGN_ENTITLEMENTS = iosHyperskillApp/iosHyperskillApp.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 340; + CURRENT_PROJECT_VERSION = 341; DEVELOPMENT_ASSET_PATHS = "\"iosHyperskillApp/Preview Content\""; DEVELOPMENT_TEAM = 3DWS674B2M; ENABLE_PREVIEWS = YES; diff --git a/iosHyperskillApp/iosHyperskillApp/Info.plist b/iosHyperskillApp/iosHyperskillApp/Info.plist index f7c86e54d8..f654934c8c 100644 --- a/iosHyperskillApp/iosHyperskillApp/Info.plist +++ b/iosHyperskillApp/iosHyperskillApp/Info.plist @@ -36,7 +36,7 @@ CFBundleVersion - 340 + 341 FirebaseAppDelegateProxyEnabled FirebaseMessagingAutoInitEnabled diff --git a/iosHyperskillApp/iosHyperskillAppTests/Info.plist b/iosHyperskillApp/iosHyperskillAppTests/Info.plist index 09cb717367..71df844311 100644 --- a/iosHyperskillApp/iosHyperskillAppTests/Info.plist +++ b/iosHyperskillApp/iosHyperskillAppTests/Info.plist @@ -15,6 +15,6 @@ CFBundleShortVersionString 1.50 CFBundleVersion - 340 + 341 diff --git a/iosHyperskillApp/iosHyperskillAppUITests/Info.plist b/iosHyperskillApp/iosHyperskillAppUITests/Info.plist index dd582c52ac..f364282ef9 100644 --- a/iosHyperskillApp/iosHyperskillAppUITests/Info.plist +++ b/iosHyperskillApp/iosHyperskillAppUITests/Info.plist @@ -15,6 +15,6 @@ CFBundleShortVersionString 1.50 CFBundleVersion - 340 + 341 From 112cbba106f8dd3446a0e4e425071f0b4518ddc4 Mon Sep 17 00:00:00 2001 From: Ivan Magda Date: Fri, 23 Feb 2024 13:39:30 +0700 Subject: [PATCH 088/104] Shared, iOS: Update UI for unsupported steps (#919) ^ALTAPPS-806 --- .../view/fragment/DefaultStepQuizFragment.kt | 9 +++ .../project.pbxproj | 4 ++ .../Contents.json | 15 ++++ .../step-quiz-unsupported-illustration.pdf | Bin 0 -> 89135 bytes .../Sources/Models/Constants/Strings.swift | 4 +- .../Modules/StepQuiz/StepQuizViewModel.swift | 15 +++- .../StepQuiz/Views/StepQuizStatusView.swift | 10 +-- .../Views/StepQuizUnsupportedView.swift | 64 ++++++++++++++++++ .../Modules/StepQuiz/Views/StepQuizView.swift | 38 ++++++++--- .../hyperskill/HyperskillAnalyticPart.kt | 3 +- .../hyperskill/HyperskillAnalyticTarget.kt | 3 +- .../app/core/domain/url/HyperskillUrlPath.kt | 6 ++ .../domain/interactor/UrlPathProcessor.kt | 1 + ...kedGoToStudyPlanHyperskillAnalyticEvent.kt | 31 +++++++++ ...kedSolveOnTheWebHyperskillAnalyticEvent.kt | 31 +++++++++ .../injection/StepQuizComponentImpl.kt | 3 +- .../injection/StepQuizFeatureBuilder.kt | 5 +- .../injection/SubmissionDataComponentImpl.kt | 2 +- .../presentation/StepQuizActionDispatcher.kt | 15 +++- .../step_quiz/presentation/StepQuizFeature.kt | 22 +++++- .../step_quiz/presentation/StepQuizReducer.kt | 28 +++++++- .../commonMain/resources/MR/base/strings.xml | 6 +- .../hyperskill/HyperskillUrlBuilderTest.kt | 5 +- 23 files changed, 291 insertions(+), 29 deletions(-) create mode 100644 iosHyperskillApp/iosHyperskillApp/Assets.xcassets/StepQuiz/step-quiz-unsupported-illustration.imageset/Contents.json create mode 100644 iosHyperskillApp/iosHyperskillApp/Assets.xcassets/StepQuiz/step-quiz-unsupported-illustration.imageset/step-quiz-unsupported-illustration.pdf create mode 100644 iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuiz/Views/StepQuizUnsupportedView.swift create mode 100644 shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz/domain/analytic/StepQuizUnsupportedClickedGoToStudyPlanHyperskillAnalyticEvent.kt create mode 100644 shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz/domain/analytic/StepQuizUnsupportedClickedSolveOnTheWebHyperskillAnalyticEvent.kt diff --git a/androidHyperskillApp/src/main/java/org/hyperskill/app/android/step_quiz/view/fragment/DefaultStepQuizFragment.kt b/androidHyperskillApp/src/main/java/org/hyperskill/app/android/step_quiz/view/fragment/DefaultStepQuizFragment.kt index 52be7e8312..0087e804a8 100644 --- a/androidHyperskillApp/src/main/java/org/hyperskill/app/android/step_quiz/view/fragment/DefaultStepQuizFragment.kt +++ b/androidHyperskillApp/src/main/java/org/hyperskill/app/android/step_quiz/view/fragment/DefaultStepQuizFragment.kt @@ -295,6 +295,15 @@ abstract class DefaultStepQuizFragment : is StepQuizFeature.Action.ViewAction.StepQuizHintsViewAction -> { stepQuizHintsDelegate?.onAction(action.viewAction) } + StepQuizFeature.Action.ViewAction.NavigateTo.StudyPlan -> { + // TODO: ALTAPPS-807 + } + is StepQuizFeature.Action.ViewAction.CreateMagicLinkState -> { + // TODO: ALTAPPS-807 + } + is StepQuizFeature.Action.ViewAction.OpenUrl -> { + // TODO: ALTAPPS-807 + } } } diff --git a/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj b/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj index 0b9de8eb04..d1ddf8de99 100644 --- a/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj +++ b/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj @@ -273,6 +273,7 @@ 2C80D4FD288C4D0D00B2CD1E /* StepQuizCodeFullScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C80D4FC288C4D0D00B2CD1E /* StepQuizCodeFullScreenViewModel.swift */; }; 2C80D4FF288C4D4400B2CD1E /* StepQuizCodeFullScreenOutputProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C80D4FE288C4D4400B2CD1E /* StepQuizCodeFullScreenOutputProtocol.swift */; }; 2C80D503288C5EBB00B2CD1E /* StepQuizCodeNavigationState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C80D502288C5EBB00B2CD1E /* StepQuizCodeNavigationState.swift */; }; + 2C829B912B88583300765335 /* StepQuizUnsupportedView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C829B902B88583300765335 /* StepQuizUnsupportedView.swift */; }; 2C82BA322844B01D004C9013 /* PlaceholderView+Configurations.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C82BA312844B01D004C9013 /* PlaceholderView+Configurations.swift */; }; 2C83FBBE2B177633007AD7E2 /* LeaderboardTab.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C83FBBD2B177633007AD7E2 /* LeaderboardTab.swift */; }; 2C83FBC02B177F68007AD7E2 /* LeaderboardListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C83FBBF2B177F68007AD7E2 /* LeaderboardListView.swift */; }; @@ -968,6 +969,7 @@ 2C80D4FC288C4D0D00B2CD1E /* StepQuizCodeFullScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StepQuizCodeFullScreenViewModel.swift; sourceTree = ""; }; 2C80D4FE288C4D4400B2CD1E /* StepQuizCodeFullScreenOutputProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StepQuizCodeFullScreenOutputProtocol.swift; sourceTree = ""; }; 2C80D502288C5EBB00B2CD1E /* StepQuizCodeNavigationState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StepQuizCodeNavigationState.swift; sourceTree = ""; }; + 2C829B902B88583300765335 /* StepQuizUnsupportedView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StepQuizUnsupportedView.swift; sourceTree = ""; }; 2C82BA312844B01D004C9013 /* PlaceholderView+Configurations.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "PlaceholderView+Configurations.swift"; sourceTree = ""; }; 2C83FBBD2B177633007AD7E2 /* LeaderboardTab.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LeaderboardTab.swift; sourceTree = ""; }; 2C83FBBF2B177F68007AD7E2 /* LeaderboardListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LeaderboardListView.swift; sourceTree = ""; }; @@ -2133,6 +2135,7 @@ 2CA7614A2926272500987B66 /* StepQuizFeedbackView.swift */, E99B21802887E535006A6154 /* StepQuizSkeletonViewFactory.swift */, E99D4CEE2826B91100B49D52 /* StepQuizStatusView.swift */, + 2C829B902B88583300765335 /* StepQuizUnsupportedView.swift */, 2CD48D882858657100CFCC4A /* StepQuizView.swift */, 2C41127628376F50004948A3 /* BottomControls */, 2CCD49712838E40F004DC3CE /* Header */, @@ -5024,6 +5027,7 @@ 0C3BB55AA2B8FB7F5ED9CADB /* InterviewPreparationOnboardingView.swift in Sources */, 63FC2C36279DBA43CCEA1360 /* InterviewPreparationOnboardingViewModel.swift in Sources */, 0F98394636E12DEC98B7953A /* RequestReviewModalAssembly.swift in Sources */, + 2C829B912B88583300765335 /* StepQuizUnsupportedView.swift in Sources */, 91046416561EE431760D7D48 /* RequestReviewModalViewModel.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/iosHyperskillApp/iosHyperskillApp/Assets.xcassets/StepQuiz/step-quiz-unsupported-illustration.imageset/Contents.json b/iosHyperskillApp/iosHyperskillApp/Assets.xcassets/StepQuiz/step-quiz-unsupported-illustration.imageset/Contents.json new file mode 100644 index 0000000000..1cf4914ec5 --- /dev/null +++ b/iosHyperskillApp/iosHyperskillApp/Assets.xcassets/StepQuiz/step-quiz-unsupported-illustration.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "filename" : "step-quiz-unsupported-illustration.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "preserves-vector-representation" : true + } +} diff --git a/iosHyperskillApp/iosHyperskillApp/Assets.xcassets/StepQuiz/step-quiz-unsupported-illustration.imageset/step-quiz-unsupported-illustration.pdf b/iosHyperskillApp/iosHyperskillApp/Assets.xcassets/StepQuiz/step-quiz-unsupported-illustration.imageset/step-quiz-unsupported-illustration.pdf new file mode 100644 index 0000000000000000000000000000000000000000..094e0c7e4e01f05c5fd890d0e0b8d2419f4badc6 GIT binary patch literal 89135 zcmXuKcR1VM|2}Ti9(|#xRjm=VqEutnUJ;`;OJc{Wh=^EKt5#J}iBS@(R#8f-F3 zENYdgJ*ujx+0tL$-|O?sAGxmQIVb0w=Q)pgKki80Gcb}@1gfyIDqg>JJ;41T>-FoJ zn%5OQc!`@4C2{$Hb>FDB@oCt5$i9~FS~L}IS1 z{$GcgC(;|^b6tr#t=0cEnRxno`(UmsDgXat?0wAOg_VC^rx4+6JB> zz8;>&Xt(hH>ooF3U_8;+q1P3R5N;Sx15b|txaV~zYOj_SYY+zQ>E_QGM!Q2{!ow!B zNN)6fs{}V1@bx-_5rIq021c<(mrTT%|6?{#nz_ouK63Rrc;uV*`Onar$f{~C*Q;|Y zevgO0S6QDD&cC#dFH}sPjf7r|UW|TRIC4W?@c0-0b5(U!Jib_A&tf~gXuK-o1v++p z)H&P~)@P1bS{T}Idg}~F`ftZxz7RqP6Fl*LcR{tYB>#}4o$Z10iZk=s-;<<5nL%CH zKaurKwJ0Fc$>Z7-XbP(H=cmx?mgHtUB15-%$!`!vy&p<$3=w(Xl_EH+|^8= zBGb$lp4pUE;7hKiFCFYPNqCMc&_@4jvliLL*IL7@LCt&XAdSJYBWMyHeEjykx+z}& zxL1cyPG9*e>Era=OsLZX5}PIGfug90SL3WA6r-8+kKbXpC4HGY$m0roOscXNcZE{`)J1Q_`lwxz!P>cvu459h0mj| zX2On0ZOc--%mfXsp&z|J6V>z_M7~TVGzXacyYx<|HRY5sVg-OuBd#bbIjxN``3|Yl9)FDGH0toVYI>rTUj)5W zjd%-Q77IQ~KaZj3)Lhiz*ADg#+UCs`%Itnj;>f?doN}Z3?F6Vick9ONg@;{he;R@q zV4w6bX#S<{)#rZqcM3BE1BKE^ao<1nTH>YV+BjCOU+VkuW_UrQ_!pz2gHbp8j;OMR z+sE1;L$pVfx4(q3YEeUBFWw7O)e9zT57nb=UiYU((Btb+Xdy=Ob5j1>Qs0xe59-BJM#?&1?OgpM_Ukjt3&@*M-7* zH=cfmq2D{*>i!$xptbez@{zqZ%Wa|cmuY^Nh7A?qozJEP8tOd)eKj&gu%e+(u)(&9 zg{^ZY5gQk?5RasOn161`q;6!T;)?5?D#Y`HIcOxh^T&G*XZ8o8TCU%21GVd(?=QwF zJ+zl=77lsf0y}V>e{%S6$ew0gYRg-6$)jiRqPUZ-P`}ahR&Dq5-*a<4q_njabu zo?&vuH)f1t-|!x+O?3F=#?s%^`1@s&X&Y1{V4!uRuE9mOfUupTy{AQO~;Zm#Zo zfzs_p(&O>09kwCMewYL2+RG-r#FPXC9X;>h$z^RZZR0P8Y>b%<3ieMopM0tm7tx=7 zY`EDWpfgnT{AP|@-yeDQm&3PO@$6Gk9m2I^?BY@J>{pfIx-GH#ZH~Z1-E&3#bG~&R&-eVV$bUopzV*NC=FKfp zcp;Fx$5q__T%E>PB;4kQPtM1)`rx1|2008mbcvkTRQGL>zNBz<`|dyyJC8f70f>bK zvu3!Z+@JT+pHOcE@)Ubs&}t|@Sc?%*p$qwTuCCGLG&UF@eqdB=~mg<4I-_B^JKMqPVjSU8_0XX-!RDtk~D!)t%FXy}J( z$eY5I`Qx_ft2aK(c|R8anERumC*?6G&B#(7XZG$CX%Y7IWK|{Mz-xE7V*B7o|J}I<*C&v@IL_a-73)#d(^4t`GbDrWF<{Oqx1a`&U1Kk_zGe3NB>X0MTuc>>#tt+ z{$DT-1+V_Tzo|oi+QJ`?aOS=FEZiCR7)OwVCwSfL;Ou6QgMZOUU}Jp6W1*@z?RI$G z@)236O*ZCZD#M|klaLoH}8IlTD92PUmhly_t^UAfInQ($LP|B6PB+-)i)?wo&{+2-Hhu$Qhg{pR4A> zIT+AX^0v7nZ8@cBli^LJYu!J=knfej+6Qb#eJ8PyBW z%dYu+dOm*3QMq4ZJM7lJgGv#;X7b`ZIM>4jpsE}x<`qIOj_mjBI=0|x|5Lq z&QtsA7Gs6H7Ne*4LH6r$Gwu!^?~FBoV}J%9zJ{e#S~h`Bi{y=1b&ery86*dYS4qzX z#x=sFHFwnxmCyJnVD7=ahB*82VtQlM@ud>NjY}h;{tpD4QYQf7q37y;wO?sGLONUZ z6I1PlQ86W#hWS~|)o_B56MKDheoBuF0at&phpY>DkKnyWZW=uQwOpL=>CTv8TTlLL z*`|}VPNpOdLQ#?(>Q85ibxS7giQqwtX!d1AQDZyAN5v}LpsU6y53_`edxgLNGSizY zP%9X7jGRlHX7DItBAdpA=+WT(SWodJMZx71vxL2=dodOrN$?Gat3 zMZcl#1)c+zLW4w(tI7^9^{-*>3Ch7gzX<%q+EDCph2E>pTMl100{n9^`>k-OK(S0SS|VBd%Vuc#;xTVv;;3iM?zZbi91vc)5ojs z0Z)V`E>;h3#|?$Xvgduc#Nl8Q$2X=2;^AN%*ELU`*e3zLo2meHUfTJ<0oo=L zdlVD+CPR`uhSBHcokcN&Uz6*>yZu7m2#19V!3O%ckWI5Y`P2{+E@e+wJS*^u{c7il zXl)QZ<|4t9|9$fzjd=gT6O)fk-VGWhn%48@)6#)vm{K-EW|wS<@K(nSkKsP{mc-P> z>Y)_ip@rx#U4)JP(cN$MaQx@Ac)nrS;1p76SaZ zx}2iR=YDW!!NJ?SAQ?Cii?h<>U&8{K2sad6TDdGR&F&{MgDFklBZt>lY&3N0l0B*t zSYi+L`-J!T9v}P)4E4-$Ua!>AV;9vNllr}!3+c?aP($6X zE!DtcW~!birK0yBv>wm8!@Nb3> zB%7VcD{E)M+W~wE>$|+RKlt{klfb*+<9%6R``>*=cIPkpjjuK(%rO=DGMg5_Ex;OU z?Is*jQYDCd-uJMdk-`WMpr@?Y3{^ayI*SdEcD--jd#y^hM6lt+qj_b2O-XBb`*PYCTU>qX;6?b>B&JaKS9s+ zi+~F8t?QtyCj-dVpHm>6-BFkuT5^N!Yh^{%KXODIREw*BG} z;iR_tOs48t*XQ>k!tY8`&{evBm5|}=P2!!C>i1dfQJ*{Zi0tYfOf;Sf2YUEM_c+G1 z-@SktMnO`M$lFN5XZ7)?fRV73=g(oCElY_)0d0YksVWPrXxwmW^-J%!>Cb+>mTJ%R z8;}VcO4Mhv_$b#@;-x61*_CY?^ZX%3pXu^fdxsBs$5$LRxnUXdMXAbGRkp8rzaF=@ z>!I0aKeImwEY`ssn5(>-{INdyb6&W|_P?d2l>&R`jDC9KmP8GlOF;q9vqNOA=D@9N zUYE&KN)opD#Ot%1kZ=Uv`$J|9l;kc{C2~bMJl@ARbYHttc)uYw`$t%TPV0k~_P?yU zi9Z-!89m+S(qh+BhpYEpL6O<P>D|q82^cV1+n15c$?-xmUHMW^k_fHvv}*w zh`|=)EjOPFb=!9tI!@PdG-R7MFv!og+d}xHCuP12h(a1(IEC}NtR_GHkMD|EV=vM> zbUvz;$#3e-9)c#IMH=&D_j|QzOFShUEFGlVNRt!aC3~4uSiNleY9~39(W&JzFEm%B z-YaejVLMa)01~A6IlCZm-N8oRs38T&l6$i?O#$6AsX`CsDb@JdbdaVo>s0$AP2-tj zOk+oDpe^h`(0A>b1n>Qd`&*BjG4*I}(>7uz4;o8LtmDlq7XM=fg0b^W=J|6DHvp=1 zHL@RyPyJ5!9uPBFN#}Qgz3LTutDy8GV>9GlXnn-bkKV-A-J>RE=^2d5vo=39FED>o z5TFUHhG4MolFYxvnIC9@V((Z$ggH2~3#t2HWJyaIEFhozJEh9v!f1z*8?CoyGc=r2 zpQ~{8ZU%2qLcay=-aW4U5W4gA{WdI@=~(R2Rn70k(BPHB=^!(;wQ*4FaMS*U<>&1m!57TIw80DS{Vf$ zwsxQI{>_U1SlK1OTe-G-t4aZN595G7!+nxM^2Gb{#NcaVBK&#ET{nu!>7ScQbXvoM z!F;`_n(p^YAOHMpjqcQB1$1C{M>LSF@&(e~&e0-GvL|bX4AU%z(yDj5JY_g7`g!MX z%y6S;HWPcKp=}rhT9)>T^v(*x zL+?L#zW!PBS4SMeBUj_H)(MTC0`E2T*wAhHQk;zeMHtpAtlGs(xPbGaqO^d>Mfhoa_y##hI9hdnc!ITtT0R6-nlYB%Q;%Fxnpmj zE|v#%o7B-vmv?(q@$G;AJXhs8CQIp$h|{jRI{KmHZV^fNtdvxnl(q7fy%3D1N`WIkERUf1N9 zc2-+7ZKA(ylG;6m>ZXx@P=~J1xR4i5_X)C7?j*$+y|=b_fA+FZ+h;?+_0reLY3@{Q z?o^dN0cpnPcCW8Roh%u<&yi;9l0=OjJie@D(zb7g@k2TdE8yRGG;7FxlfAxL-Jo@qx2;M-7177s4rw?kv|ay&K@+< zy3aKJpo%_TcCBMf-nqH3Ui_xM^6hlZcbDrz!0-$X$FgW2xR8$sLK;!#$PKULYtZ;A zvfKJEHdRQ)n*G7+Ymsk$?7f`(bgjE7Kk@Rx=&;`I#hKExyU$_T^<%9R+iLu8p)G_Xt0WXsiCsIWq9T9s_g z@3-E$xT$8OJkoSn9J4dSRW4N!=DsnKZ+Vt{x1Pscx%Aq%dAVOd^^Mh|q)Qsm+ye<4 zg%LlW@p>z`EZpEiM-{nZU^G-&CdL{>aU zQ(X=$boh>X=938(sa2ci4s&ORtxI#dpX>I(;sH&IL(mQ}!gNOD^q=|+`4-8qE$NzS zSv6yJAx8?6$QOU6=Q&fg7hEn*?kPw72t=OP)D=V?%~Dzm=gwhHR}CIp7O&tnaURI8 zCM}1z*D{|}zhx!Br3*1P?sf5?hrRSVWWBp$*#wkPE^z&%%h1K5_mBaJB+#2p9F@UJ18PmKngYIM2K;)VTLyl8#ZW^SmG=VF&p^VR<_LF+dT20rG! zlfb&mrVA4tuB(xI0BYz{^p*>MrFLS7zs)aH; z>}F!k=S=^de@_tWvwQu@8Bcb~@c|#s(^{1s5&(CR-+&3fjA)HYHjaxPotEXegrBbE zxRBQ+pDO&gu@clUcc{E`ZLa>DOSf^WlZiWM@yed9N{{UAd>%Aests*0|7)Pm+#NK`ZxMY!^-ME3G7Hlx41(4zD$1fe*aFWskpGx#Yz8>F&22H1YYb2#;X>{wT9hw5JF)Ju5)wtGz3{nof0kkgS| zwQR9Z-~HxYGU@?KNfOGjsF4juV0$;oMJz=(Pr%M~Z`wTt`MHWln0NNkRJOsZS#E?N z_P1k(fAD_)-Lp46j>5#Tr=$wy628*uXiaaLy$@&KW#49c|BPA}V7ZM--|YC(91laU zRq-Li%AP`}xQv?~T6FLVRmdrKe@Z|o;y>*ToUGE} zi+Sr+^n6!`n3x-1n0r}(vuKYQNJib|x4Z3t4ih{5<+#-hL2$Bx=&Vo9p2@KGY*AU# ze1k(`=AXjHkEY+XuDy85r>sXCaWLGtAULhUl?eRWd)p-p$#~q~4F!d2HCxXSFzzk- zO=fFKHx@Iua>WVU6&`$nTtG7XlJE;dpBrpY@)W|3j0LA@j-QPN!Y51s-~u&x^|&38 z64E*)`n69UDmt63Ojq-lYt~O&j}Le+p#9texZ8Gc1Px%iEVAcJf852E8By+zjMyi~ zaar7G@(hlD7bUiA#UwUAK4H0*s9<#Xy2DkA%UZG!Fr{h5r`zL+-xknIDoZ z!_8r3TRBancmplC4OA^E*@g2f(=gmk%>1Oc+?>K8#Ic%Z(62RBbJh@SaD4X8ct7A@ zsMstjdV_12xq)K!Z}6=_!}K>DM)uLx-H*owY!Un}Us6H>VpekY%Mg`UG_&E&z8-B> z9<$~LUmo?!LYh>|wrDEMKwN=zwL}*GCy9(>Pk0?Wc?C)Rb_~}Yi10x{a46LJV%LBe zavu4NQtHSoXg6a5wzry&-h;Hv%aR3Y(Pm@>M+;k?;#UY8jWbzJMD-xOAcd3BgK z8JqAl#X3a52h<{I?Z~~1TLmZ3H}nq$h(Wigu|*S{Rb@8M9H15y-YAg+%a)MNxH+e3 zHM!8Xwq(qWAdBm!Jd_uF%J^sPZPe+Y%lW{1PJ4XP?uXFM4|f)w9|c-&@HCGTVo>AY zcxaioT}O2v*UzEbeV5u2wsS{1G1Xyys&Zk8W6#3{p%@;dEiK_|8bV3}XZGG)$^mR8 z?(48k(!l<+elI#}L@KKEbKglyIN}U2I7+#9erDf(5$vLGtc9Xxhi}`D@?@?x?VC2E zt>YoImQ^-P>b6yZxu5dww!=RiFYDC@4!Df31|N%U-<*@2Coj1j+O_t!))K&`5Ix)xHa8=I{at1{h!d0-#&-xeI|=azqgrk zN%q+&>BY*x`0A&F>;&s!O}1hcm)t0wOCy-w+FwUS$v{ht=1pXYPiymuKGncx zY$-r(%!DM41f1(3v|DoJed)UhL}Rf+g&51C83Fg&w59Ax<89?m$m*b#(j&TsiO+U z4@%)jj-Q{j&+acCX*S|f>7()1E6m>EZpCV+AI+LE%2^mcQWc5~<>Q*wJoswvh5tJC z9jdz=VnKTyPp3or0tyl$R{HNp3u3)P^m(YVLG_dqp{FYfMTSIFR&(6j&3KW<$H)1N z3(xI84#*}@ZBgn{@o;6JJOM1T1gt*GM_5S$5d)1RHdsAN6OgWVeSDo(wS^kSxRu~h zDGK< zo^7%~ljQM-YX0ut#Ba(3IQh$is6S#3O7{a#n#0A@&_ zKlYoo>aSdSTcNEec}sjI_i43KhqUpz-7>H~zIInV0&RMCt)60~o-9b}$g-z%?|+i` zd>A9N^uM4Qn2Vw=Cb5Ri0$b6@Nn|2k`P*ym^JbZ!eFDrxW!;NQ{NZs#_4jbLx=lLc zrb5}Efc8V2sBx1FTFiphr60frq?NKRh#oPmArDEXYosA7QTaFV9L(1lQ^S51L9#PV zpP5@*S*sAYBv-H&{<6pod{Y*rmS}2)$S}u#F)m)o`(i-pqtkKKJ;eJ|XC z?$h=NsUoapz-!r(WINuDp&h~jP}sq$DOIjHFa@XvJT-&uGnLQonQXb#Wj$;c7yR%d z@@Q+Lmg6bsJQEktSzu}0{v&tAyk{r6wQ?lxSmQnJw9&SB2%}oIA4zh6)d%xlrS;x7 zS6SBaQ-ya7Xo}-hxpY*5RER8%b*ZT8t{S>bh_gK~SFTbj+$5*%KsrU6ERjrFH9@p< z3MD<)VEplJRO_Gc{6@1%CyN>i?P)ykr){!f>ltIM751+|rgXQZ4}RUEl9jnhav=1b z7DDX4@1<_GpH`rR3kAz*B+Z)qPp4BoC4ZFQ?gRywd3HN$$4; zqp|*af7oY4FHVxQ&-W~&F$bSaR64;@wyP}%R*aY-aceJsTD<22DWTRoWEr^%1PO<| z*&Qd18TM70 z5_Yrwg*j9#OW3MCEHD$S6AY_R$i;ty36OmIB3;f_9jv)YO{FdJI9KtxPp{;CW-f$f z)w|AVp`C6kne8XdXEi;QbPDYcY_H$8;YI3?GIZHZwtlT$BUo{%o=P}PD^8hl+P7U! zL-qoK<7?y)&yjB1+^#hSTYZ46A$z3t&+6N29}R;CzsOfgL=w9wyfpab?2zra5VEBK zbOzOXu`JlaeC#kYAbkiRaL;WI0~RH44VhT7QR?_^;f8lXTX67|z#s=GqHW(#}=$blXQ4E^EDP4jnCnXCtBO7>*Dg{1bE%$xI-NEVfR3DONAz`!IU z{)G@ie6jBQ3e+4Uhc^M(e#k$14S!gIr~HmpNsxj_?Hat$kLOe%6?o214-_S4*H_Emv1 zQT!AyT7FQn)gkAc7s9W5id;G?VQL*)SGN6-^h^cO#UXr+kpu6?Vyx2j9+?I3^Q`R^%0pLUdHg1l$S?n75XRO-zL(atO}e;smw$noHn10l64&Zc_e?67K^S zNZpp%CMbKP+ek&Etgi=sPho{DIa75hMz$e_fkU_uL3-0_)R?K_tAwBgZe@0RIXfau zV{C{ATi(?xRlZx9rgi5R$0A6RXU`v=b{eT!B&S;2DG4B0R1qcHG96z{zvLKt~`8IT2q~xk8Je$S2hV56V?MYwWx9Rg981BHz$q9?lJoI<}CJrpPRKt@+DPwo_^V zAv_=+7e>&`xGt@E4>&TI@8LSsuzbxe=j zkXfd6zJL|%=eqj#bL{~JAnoy|PCLWJoV&dD7F(F(hovv4lFuT3w(!h7u`E!*cY3OA zJ`jMP1s6v>ptzl3xj;4W*~K5XY8x?}4jZfkmY0g0xkQkj@8P3d zX>y0`O<|}rOD|-{y_#cK!&z{63ycnFAneRIS>}%A?$Jp%xcgPW$Nc#M9uuLSwWF;ljs&cN5_xWhV<(0~HR> z?6|HzDIBD$JcI|CEwwRYFb{KiQUhJ%i4gn`)RL^y9B-L8EH+J#vsB-mLX6eSJUrq9 z0cTOKQO&myk8ofWEw!12$G2O`6qj(lcHUKWsXW&(Z1`lI86_?geTTioRHSYhSG4);5=C$YA-L9(o5+(FBSo%wSg$R?CpnL%;C{~P#;=(s?9h-s>>;fbuOR99nnQMSm_UxMB*;4hrQURCoVYfNL zFcpGz-aXKVI>Rl|e_|(S#>{~%LiI7d^KpQjzhq^mxi;h;dCGhu%Qhj8c|OZaO-rrb zu+dRbx{yH4uA%)nAc!l?u+eMqN)l2epqzG(?6{U!KdJ26gXc{!oS^Xs&%A;<)-qQ( zTKcdH&G+EN79X#?g6TE(%+-!a_VQ5(ch_+FQd{MWSD@S3VX~wVT)dcFeF6|QGj3^t z7)zzv6uk5Z9rO=;gL{vUbXQ?qo-J%fsW*4VR0V;d?_I`nA?qUZz!vW+DGxigQ)Wo< zHTH4HHr*X0Wki}XAhpE@xcH8$_f%v~J1GvjY}GACGUjYN@%jwlZG9|G(`F zNlmI~o#MaA4YkZk{d*$gCDc%N4sH|%Z6tLYH5PP~DF`5-Dit3Msax-vqW<*TEBx|Z z44Xg|=vediWFvh(M@Pu-*R(Zrayg~;ec5M5Dv`;sE-5K0@?kmEps@$Ip9bsdvhCu9 zZuLn)RLlXox8$8&PGQY;KvggG6$&+1 zKRen23(elX*$_nJOotmHLceFNVNWC|<3V?**Vl`GcRXq7bq1O#GE{@hbQIp^>i@e_ zLKsryx{BD!s;4T*quSq+>#Da&z1WE$_Tg&j?8KI3wA= zMX-zt8y7ST88-T7NflbQx#1@pd!X6P%QznBn4-_3BM7k%w`}7HsH9*b>^M3=hp;oU zZ7vh6-sjpqL>V=h{DVzXfyc@F^kyHDd#KMC4QF5{V1aLDbLxNwA#@4bxonM@4!4py-O2zAq>XyAUgpI!JwlWr^J)L4c1(A+%L=+&9AUAn z>iID3V98i?xITlE?^cec6{XJhEh5e1n(Kw#0aD9%;e4o8?xSN-+qX6i#BwTfU6tRa zK(5ec;nBqu&_PRp^jQ(^Cgg(#AH#A(KS@O(zhiXHOJdj*h~+O1x&2t*^If5k)*1n~ zRU&5{2yb#+|Mb3<9Jt&gL>hgFrI)uu*-T_LAPb}BTg(rBdX+%mO2d(YBxs2csKx2U zA=Z5b=U~k|xAYa9qWezM0o5<&{BClqaRArr^+CdL@ymp%%C=GPJnOMaXYH8$2lu1|pJG zTFm`)1IZ`wV^{)XO0ztuCDmB~Iwh4pdRCe^ekK9(d+V@a8rz*cp*llCzT?ZZ76Ai- z^};?fn`A!I2_6*Y60T#Bx1MO`zCPa|>Cio4p;fV#=hD5Mz5QL*u~7huxqT9Iz8*SRCMmk7Iwe*T4K2ssdEKpwl&h3?1;P#y9a}bUUoXKM^ z@!>%}9iLp7!01|&2bjXdIJkQa`0Y9Xpx>ZZuUuC zR{hU$UxH7k*NcZ~=j*U!{-;jB{g5{4g_2^-!~1)+<<+yV=DIZ`czT8KVmgh4{&Xak z>};XNJ|`M1S<=SRs3m%9VD2U@)ezOyRA$R=5A*`ekFGF5qms~s?dx-)5EZ%NXKue= zW46!i5P}0>mDFSd2gB=H4#WFCW+W{o?lm@r3g!nrII}9|$FVz>FB2J35$eDtJA@TE zV&+WK!c-@_IrFEvuhcHF^d2Paov%)LdBt&FI68Shv1YjjL$A{RpZlD5p;mz4(wWUg zPqx_g`!aOYP)LT(&H7SR*zVP2D=rhIrOXm$o5sJD1`fGOA2*X&8biPc(epIR#2Rbx zxrKeUbG?7J-5Liz#468*E^kN9S81&VTH9T{@q`rp2R_4?4_(i#0|QPEv8Qy?!{}!n zCpATN;LKwglu*5A?7jd({5~JvK#jU?o$t$7vY4We+Y0tJg&Ub~TDMcHTzM(pT&Dha z-URLYH$Mn_9b&3G2CDD>w`!R+hx7y0I@gK;5zUBG!yBXunAK?`3diq)`!F8j?j-D!QHlh^WP` ziat4-^#-U);<@i30$hV0D(0i90j=2lS&nyROeerxJ$2;@FQt^K0q0`Q4`WzGK*~i$ zu|c2owVJ<$c9%1MNfu64qD_)dCVHXbc8r|tg9pKY+FVrW;oN4h>% z-`=P}kBTTaH<#L2wYy-cYm|a3;e!BQqL=FUYVh=CrozXYvSDD!EWm9x)$-A`>EHh2 z92^qxC02)-M`DXGEs9Jo;q&dLI`icHf$!N9_gPgDUMRX+%P=-o>!v)`O*6RZ;9CN5 zNsnL1r|_WP0NPwSkZNwvodeqh@F1;{b1N;Hqqr<(z%2I`>cBYD#(k;~GjLF82&9hi z_*83mHepZ;81P=u27#Ff_GHY7naGvH3=*soaAp|50qt}bFKmo_lQeTu>8v!C=$`@c zdQyVkeJocgfw;qVRb;vhwm+XG(`aJ_r0TFW9R}z2=Vxg06=95GV}DmU<7W10DO`z~ zw{Uw(;@A|L+ca>{sRx|j;rsn*=z+UV3?ElRCzrE0KE(?vbF#=I^hs4TZ~xtEn%u2& zn+TN5GFuAquOR=-l+F4@=6RFdFIi4&#iB|yRDl5%Rc2PfOWAPj_sC7RBoVUI?E@`o z#8!5FYHffUpCBasr>nm0%aYtqP9@-S)Re>dM zbxZT0a-6no(tIr#2$i7>go&<+07i1AJb{}D=)FWp#E;E_=02%So-YOP{+tzdC3J_c zZ~d*xv$4YcY=U#8{GLHelIbgaD~=F0F)25 z-}Pm+&j+imO|%wN-@#S7q@mhR<=_6L>}nUo-kfNyq!mWyU7;4pe8i!wjdG`ABqvMl z3=}?yY}43O%3=XS*X2uO-K2G_wUBh1d;6KD*!^eoZe?8-k|}uNjwp&i)WAW$tL$;3 z-i6G5MLP}*WD;4HGF7``#pjK&Yo7}H6q7pTl!;*MXEC9i=ZL$Z(D|FyG`W{h;HC0Q zC~{fFu&GW0HNQe0nlp7Vh{N7xF_;7IFJh%?WZ;098V#Na!n=`>0lc-doptPAtGC(X zf@|wFtB-jD0re=fH>sS4k&B0FWv5dqlXP zXQjkaIo;6ss<6;(BC>;(1NKWZ!Ti;8)QrS{1>o**hO`*f~912P~!;$554zwyoU zj@%!sg&{hEd}D&Ch|k>q6kOX2XQC2#I6xT(vtno}|Htr0Zr+Nv3a@$gx<4DqjsM;! zy)kPFnnGY!GOat@G;ZV)3FzXoFL{~P1DRw%-~jV+TcVmQ+xKIQT`P;)Ov}&Yo10#Y z{(qt3#4-d#f-f@(@oeq^qhqwCAdnc`BnhcCSt|&o`o#og2S*Ol%cNeTrUV1Epn*3` z`GMKZp;TNcU@?=N>DT%=VDuq>6aVnNaL-PbW|#NSaz17a?;6+or&B)dFT`atUi)YG zUx|TZ<)nHfJ;~eqm-#)(%CJDPGLA<`S*y}I8^BTc@SZ8q2MJroEt(CSN{}uIFR7M& zmtRb@F2E;fc1eHJSIxCb_FJlo%CWDaUmTF{EdKN29Qm@Ul%@~>9>@6tC$l*(AD zz%T+aAz}#o;?R?88Wj*1vAf}(hDmPOAAb1<-JU0JScO2Znn+CjlmORA%*xD~`?zz!(eRxQ}5{F_#ROzg%xj~XsP##YnWkltos*&7*+53Q{QXW6H z(Y=9=EOgervvlcuqY2J((U|N1VD&aa;Rba(78g?Zi8AMR79 zz?vWs)A^?iA|M$}4GWjkb^ip)%*m27M)ytpSmv%-HCofi(bar(pnZx+2IU$k%(nkm zib@(*e}PIt29n+Ni7$seP|i=L5M^_yfdPQmw~5`{q3l-fz(Y2+y#Z$arU9wNSHkT> zE()i!qrY??iVK7q0QAu&()(FeU(u^PTN0l{y|-E@L@IRJAc=cn4geRLkxV56!7T#O z#XB7KEq9=uYlQ^#z!Ur<)7jE^NH>B{YMJm9iWlU6YX+5GJsUMthHb`EBEI-_GrS=; zh`ZN(Ud}Luw#X;L0eA^DoP4w@^|qVfGjMLAt&B8CvS=xOh7f*L6|vamRbV}-K_wh8 zH)Y0}F!$;D#x=0ZdgN{ibLE7hYOz(M)RYJuQ+wAv60yQ zxi33l668t@c32PGT8b0S0-J(-GR)idK4hxoFdVpY{-l}IK%^w#%((--)Z6y0P=asY z;9t|{TZEOda;Bjb+D^tVi&4^tY?l&t{Z0HkqV{orbEV8zbt3xSaOAnhh5UO&r2LeN z;W?4SOH?9p{*_T|f;l$xR!zkT<}w+Kwmv?JCbth>B$$JIm*iGstM|=S*YK#pv-pv4 zerMG?QswG)ZqpzF>~wSGlue#D2oS_zzuY+}D$?yk|Kn4~)9h7+RGpNEdnO{})r|GI zwTxs^KdB81pON_r043m&@Y^R?4wh22gG(S(d50zW|l68JLfFYJql+9Qam4NA)oR)z2SsK4j=9fjAR|(t@G{Z zmA7Us9kh22Kb+kAbV2uE9Vt=Wet7&!PVAIPzAB4KvPvQQHAtv@x#VgpF(Zaw4(ZyP zN#jPsOQ55)uY{f`c(CQ$vjP!;1903@rdQypt!bsw9Se1^h>7}A);QRrZ*O4u^4+#b zrnQiLh~eJ^erK5-!2mDqH`?+1!_Gr4iZ;{m{-V3epTCM5iv`v;Xn@D~W+liEC5y|X zmaya9?jXzoA81rXs8CL|MPzDr^`syQl9j9)yhs;J0}GaFQX!hJ%XAOD=Sc2;INtuy`PntRbt&(DheaJ)M40E=iAmN`p%ZAl&8zwIrnI_He$NA^=)QEf4o$b1EwLu*>Q;w z^`K7BtZ3}bh3VTCz6PeB?Ew_WZSBUl4*8WFvs7zufRoGkeJKXv$h|YgbyicCg$VK` z#^W37p3Eg7=xd{VIS31vp#NiHyk-c`bO8)hoRTP zfj*0ES@x_=%0r8FhN@f@Y68y9?rY(E?iG5s7P*6-os`Q%52kP8tPA=Jl`9q|Xr#J% zZ{^((7bHsb|AF_v&i>a(Pc<^2gZ_+vhC=D=d1mfTRtCiK?&TTa{E_uE+?jWu(p0$Ot|cNA$%8L z(>v)pw{M@dl1>&oemyH}H5Ov=T_%e|Fx|{pEJLT-2T$&kybPRG>0MWoDV%CXJ2 zp~Qe)sq3rpj%u_yFQA?BoxYZI(*u-1{&pL-YquOYoGt|Ke*7dFpff$gldfIxPlF~` z$8T)>+FPg6Jl6^1{eIrPezUpNf4E6f(Uycbz6_lJ6_chk!I}RS;cs7_V>O3}ke z1yl8%rFq{pQnD%KW+;^)p$;i?Jt=!lB_#{+L$zY$&mdC4b%MY8J zYpdP&=W=A2W0xoTVuPVztO$}yx`T1k#L@e9YOXjS7Mp-=$?_;k;U=E(e49~ri~cg- zk>%v~Gw8t%O7P{a0Y#NpYzyKvq5t_BN4d{6ZfL{8oBGfGi7llY;0z_AeXL5GNqpg# zgwiv`D?!}kjAW1x(c;VQ zyWcB<9)C-)FfQc(W9==2+UlaXUnpLzcyS9-ytou6kPxIeMGFLXhvLPxA%Wnuh2Tzc zEfklQ;7%#7En1++%k$dDd*{yFne*Yyo-_N*o-Jpsb=Lp4fEf=oJL_ss(dn{dOz#|c z?e(i1XCEGZnn22h zpp{$Vr!K))U@HpZ!$7gJ@~irR^kp!q0?}q9{YtIJ3XQ$lD1GW11yE=Q>q;EwDS3Ii zvLO?h3pqG&6UKin?=b&~mQRZ4m4mU_4srU9c{fq@Jc`1D!|dn2yS(iJVQwRoETn%;h-}P;|VRxk2t_u*KOjMv(R%;#Dm0PWIlLYJJo;m^+dxx>m7oUxtaQ(}C-c0ttG>Wahk8E$3eB9Q; zeBmRn+II5W={0g!>P<8&0Fw`OE0oR?lBi?L8XV1kma?Y|i?FJv80^H@qCq)E^F+jE za}hBIunOn`$17eUdp@J5+$GAX0^>WVG)cSOW29--@0HL&#tw?N06uS0>T3P{B{!hz z7lXM(QSfF0gkwn5WMYw(P}7z-_La0=PQWJ3ytPgh(Xa(>R*zsCOs9iQYbb)<>}QIm zTp1KB_BZ~7-{-o2|gIuVYa1hF?EJlVfu}ZORSZm%)^cVi@;&Fooc>V6kTi- zl0?cyS-eMF`I}wCzsd25Wafd4Oq!9?V!MsBt#lm!d8=71UHnRsGj)#=;+7WM_gXJs z?>m&h&c@<09^jX=Vu#{>GV1i(@g7xOw9m(@o$EK%?gK0qH~Jko#j%oIPC$X!IqQlC zQy48?wy!URwci!F{P%lUI2FN1;k6P(+@e?Vu-f+XS4O(7L#&n|+NPKHt0@SiV&wnI zB>7-F2e!f@rOwJq;dn9_XXr5FsQ8%S%FjgGq{=HUtLn4iH1^fzv0E=>pdKMj8+nRX zY&g4F+5ya2a={CJ%wXHGC(!=hum0(Y+s|BymCg6qnq>X2d*F0Z8;?@M_%SH)0*)f2 zG|OI145}~Fr3-~tyd}&|iE{EBraZ18ZJK|=K)B3cN-#GJH$BEN+VeVvES`+<8bp=S ze7&t1_HIf*SKODZRM3c0I4)LFS0Tdgv3_SMD|!FQS1WcRY*uw%_)4sNV!M6atM-^R z@Ed!TD8&n>Bud-EWR%S3mV6~}=VRFCB(O|%AjeweLpB2|DRE(?BJb;35A*0GDA11; z{XP9g?A~DZ9Hw9K5YYMHui?NTT#rsx<8hndgV6C>=Fq}B`CVQ*h)6!BPMOR<@rVBK zFCWWu@hvTq@ov?{J2n%EBot7W#a~VTQ=Os+#cUIwbN1+IT#POjwMbAeANwlXL1+9~DRnG1 z&BD{~4)oRHpvSc^NRDK+k$dqoDiqg3GSeFG?%~yUI_LO+HEva9} zw{cwOY?cMT3H_wbdW$kZH(NfwyR>BXyV-s~5;Yhu;AzQ#$deH8>soWIHxs zfoto2b~~n0!t9evZZ^ODvyGCd`_)#r?w)$vmc6G2EyB-CZ_`{w?gr=O+^AAoP-#NN zSGb}B=Y)z$;sA4%qvz0z5}x#{XuE~ij}Sc zvmF_6rGDnM8dHfPG)3c(w9T3Q;$)O|JF#bU3W;gX}7O~_D>~$kKGY| z3U33#Um3Hs2`Tmkkr`j@%+=IAsc61YfztPKm|9Z|-PWgXWS|JmODL^kNtT7*;H)leVm+6` z^_-Www%W8_`v7&kis+|*lxIiBBsoREK`bRHQMv>-d7nynR z3CkaQ>uHLQ*T|=hZ2tTNqj3CZ)nkq0Hn>lzj9z(1@U7zWU);284t~yl_Q64s7yT)k zI9Cx#>q#3`BP#e#jaIl<(;>+R@4Yf!^&WTZce7WEJy;UEY-?FW%t}ev2~{4X9;I)i z4~TS{d|--&Z-$;~mW~hAGWz^XBBl{$Poj0n`mB8AG&;J9%fJ0?KKr`>)}SEGPblT1 zQT+N7Ml`(Dz)%EKJn81 z1S-t3ll4-8xP_=^E>mKRi2h6AqvWCmAJ6T|7M+}{JDF}zy$a`m1llKw$M*;~Gv#^? z)W?thRS<^ezH~qzK0h8}R_08UG%m!1NYu-VI}lMp$m9DDx>h?JGe1P9+>SBTIqE!_ zyEEE#@TZiX#Q7mj))p75Z7p=n!OBNGio}gWC*bg!0Yow|RqH2--hB2{bHIs7vZJ7r zv=LEoK+xWp=hp$w9%kpHz%TaYn(a$+Z}7`_wt@E@3;_yl>Y>=osrAio1qV7=)pZ z2oAPh43maW{lY3nZw1!Ac6{MuKJ~QJ-u={C`zd0SP92R?z5Ay}2I($tafSb!LNk>N z;^sonW6b|tSckeke;i-*ttRd`W`quLmSDE6u`TX3Bc%HJnsa*v$Izj5F+=vINr|SW z@)L!31BKs!XTXiU;XYZ-wO!Q6w@*odV_FJSgfz8&s@Yb`oF_D^Tdee}jG*fRQ-i+mp@xqYY(GWZa_iCYA9x zkw?hk6*HZCk`svw1~7(yLs$+AD)v_Sp&jWTcrbRz?s6HQf_^+A>gRuDHdNqBcf|_= zKV$6C$l(?c!`k!Xf&6|x($CD)FcKxS-qxuWW<^?yq&!Uxr!wFZ$WjbrKT~!89=q?P zwqQwQ(jzrbRC5(Pg%bX_N2V07XQadC+r|)r}`G)swysmb#J&$A=iJ%W!T>McVY|T<7>P~i+PeX@Tr$Z z(l1Jn{RzIg`}+tEi7~yv#>!gmStzjoFx?WnGRe-$^pi;a^Sz|0fbR6`pF;oYM4;CZ z+rQp+jZKI#v`KaRX~lr5c4ut8LHBnIn`Wug)~bF{9!9U~Na>T{5oVI5(Hgjn?5t6L z=Q8%Qv8+3kUaC!(a+z93pv{P?)~$DYX|U?%Uu`)W<7|)D>wI1y{4xj=qZ7YQr83Kk z&_qVi9W{%tscQ_ixyO!mF@Bn__gtQ!#TNmr1>k**pmQDjO3Z{)ky9{CrBiLYo()MN zGES9J&-o_(E=OlP>vO2{=%CqP*K>?w(og_-+EJ`Bm^g+r$^%?)bu!nXe(nz@Ar{35HP6)5$kBns0VCPQ~WV) zq9!9y;%qq1&T`CxR!Ge;aSD6%c~Vhjj05U{_~3_taFF2ROnsvk!7nTme2l?h2-0lW zdg_+6z@p-2?1bGFwvObszxkyy7~qaZXO&BRB5wGV&79$R?>}Bb^Ty8-DJUG|d$s6uvB{GshSuY8%T_VtLIvBQhqlPk#1-dEDYqV7C1`8AD~t-h`_x(YJqUk4*zH zwtqvCtSgdPiLjoF^qv0T6!m!)YPMfZFYm@RDq|ip63ZppZtJO%(Inl*-ahci!V(WU z5wUOd9vu0cWz{mDq@x+fqAVfa1a_mWwK zY=nhlTJ(nrF6yo9+OFP;@yESLxaT!X( z@PmX09(D&hO$jM1LOckiD_1uKo(ajVtBd8gDD3VP=1(c0DSD)t3oGt=Bw3&#d-n~uF|AhN}z6Lr+UpPczAu$oL|DO1t?IQoT z{r^`?qUiqR?)6AxD)2*$aMC41o!ejwIr0GNJ)wHR5wOOm*@mQFwbJemzpY@6Pb#<^&7w=m7?~&ilm3IBx z{#)&*HKy6$5|k3ge~Dv><%_P-{>P5mrwvjD*x1Ehq~lwMAyoACZc@y*12f8syZ!Z9=5*P4@*1`S zA<1lelf$p>5u?_--h2z0PQ@y;XD3VkTYnABR^53*GD7ch6Czs1In z=${!Z6#0z0!%shSXIxvf64Q-3W-}VJX#DhG^R6%@jzdCO*WOG3e}FX?SYV6ja)D>FNe;!+>2~yar}ZY1l1? zabEWS-Ae$w6wmaA*(cl-xzEHag^g`b&AORTeX`o~7!_o=0g zzL=w&=^!!ocx7^~1=Rl8vhco&j~j$|bE}!r)$jIQ70-=d6JMK!p{-7KLWNqw&k^3P zg-SM^6Xv3TZ+c}_Wm@pc-e=~z@ufbwG*mp@JpN6E|0IppEK|Q5V3En(@CKf_Q=?Q1M$v56- z^OW6d&|eh`xf_J@TRxDfcP^x<2M7N9K`pa=Q4NLUU1@qztZ?dR9cP!SPS#%=SiKc^ z8^@2YIjNgl;b-R{{>FFt~b>dJHY|rsqI>S04L4~%UrmobnmC$-LJ^>2m@G|YLtwBv6v77M(fG4dv-USo^=uGS?LM+F6Ixcx4xbwz-jKCoDHonXi@#w1e_|Hidk?*V&4Vwtbk6&#BN&vr(vK!lKj{ z65&)J2Nxtm1BE!hfbtyttZ03|%tkLZrZEFp$3JniUKG%swlNOJ$6u1veMjVe0g>)FtX`-4xgVF(_JU@lH_Lht$o8QALMk^k4JZz$HF50P;VFQPNs%|bUALOJ}=wBWoVSGwG6r@r4QE;66-{7B0GioF7d+}Ie$RM0|yK20fNc~YwS0f zQrlI@#Vpg8WQRHFWbDpZq@qj~_$Kqw-WEeIsyI|FoL{L64{sY4g@srgb==6On-?zX zeCCEnbvV~>ka6XmneYT{$sq(m6 zPR?Ttp5PA6+xTLQ`EJN+>3ur$@v zSJ@}r+K&h5Ow}*UiGznbS^P1R3$nWu$^Rza7+Q2(Jzx)+=$X}41bDOqoj@RAnrf9l zpzU5J*)ZpeTQ8V;4&xC#&WivHIyKnqZrpa^{k(&Gm>Rx9!yrTLEnf%JBsm~C>5vXZ z_pFdj^=RGS2`NLIQ2Xrto+_qSyGWm0+iBLjcW>Wh2pNYMgle8t z7rxZ#18!AY6ns2rO9B{eEM68-n+k-Eu7I)7@Sfy~!0hal&3i4c!4uAf;H@xMw|hYy ztlS19kZhHkpc+|mlwq{=7x5A7Fb2G_5fdb>lK?U>RNNwbqB zG9IWNQ@R`n2;&11&a&(1~v)1Ylc<;K%-+Ul4LK%nbn!!(Eolu{NsDD06#Y9*TCOCtjpX-l+7cjrM~|UX zIR8|LUH;eUjnhuirp&+iADwAYo4*1wcBwjP$q-oEG+-mfs4VnS08XxVQ zTPOw4y1Rzd)P&LZXK`=*1)$FV?VIgs>Ll+zCDK#DHY~79K$v|k%^cgl`W)lDx^I?b zC%TyX8Z}J1a$I^hQMdUg^;<>)0@B74aK9NF-!9L$k}|z(sYE_f&0LMN(6>luG+q2# zTuQqK1Kg2|etm(=)#l|243o4^ZPp$U%d!Hm2GN&c2~Izzi1!LBAWhq1*gn^7Ov+SY zcG~KpiqmfFfNn+qjEWF1w1|A_uH8RLrlx7-cC%^au+uRT? zZ78_EpFa&(Jl|Sv&)CHWmnV? zw94^Ao6H6b1;kn@^}E;i>g5ZU$$}u}%Z&opa&9emHqw+!4+;GQ`m~d*vvWpkL;9Q~ zJE?$b?O&y4oz)+QfnBfXmizq)0k;!X@yH6Jeeo?js;F?vAkCL(&~cE>Cq%fSZ8$z9 zJ*$f5lX(=!k&$ldW%b8$35iPza1K)L#YH;It-q7`mV8I+ZJ^-GY}Wmq{)OX?^X|M> z2{))(VKaOE>lkoNCOvyqHI|h3X_rZg@DCr_*u*#B(XA1jkC-n=6R1ob^|Fr(e@}0T zzIpA=G4k-I|A2=a$mY8!DkvUQ0u|8E8u2Y1+4UTbzBq3;pzsyjT!aI7=uD5FMAYK+ zQ{$Vy_R(Gn97^_ElaUVAHLISd04vdi!Dyn94tftxeY2+*edBXRf*m&%<3{(2y6V<6 zV1^)7Qu6j@YQFBjk(O?U50g?b%~Vd|epu_r=AR0^0G8n;7uKmpCt+YLqC#|7$Kfy& z@(wvhr0@aahfZrS{UMpTT8<9Iqda@BcisbbE=`bH>{I z4uZY4jK*8-6Po%Nq0ZO&$I0h$#xafJ|_M>^W7#(@?1cD8^{#12E1s5r>XI;heT_h-_tMlZ(n4(W@@Q@tl%&XX? z(;B%bQ>Ur*{VaZPs@l3Oj&}INV<)WIt_WmLmr!;4_z&bV=w%al@taX=z*3ou?jtWH zm<#bWQZyCdb8S-4E*R779O>e=c6D(%+I>kbE~7>4;`CPUF}2*~WlKuJV8QuC$M|~@ zsH!pJmCyIE-ZPqE%>YdA~z3djbJN>4u{Z!?leo9&244AXruw8`?CAe2v z1&9i)1OK3mx{ZKzmcoqJ5ZqK+kdNhYUt6aq`YfD76^DS%=A{dT6^rE%S^NY^Pj(Q* zUoFadw3A&F?r1vPd2b7b51fD8Cn>;*_;tq3BZaI5>`R>teBw2tC_^+>bhEaCMm^`! zE4ou}U)YuM2>w=UW0RG+)>Apa9vWsrz~aI7|2lKJ&DyyhI5_vTRfX^RQkxZUeP}*Y z@j1g6ywF2peE>%dVyIgFi~I^1;T0Ti@^6P`E_|j5#%rksZBD-gAtcL*Yqg(OWehA>U_cX3L{=nvF3cB6PcDDW}^G<2l7Z-Jj zS&yO>Lsk_nSsC?%#aGr;*2?+R#jS*>q_pl~{fRtXKhj2uYU(7x`^*B*nl`JZ{<`ri z`{I~{R0-rZs&T`eb0Fp#PspWEFI#2!|3ZS$D70o}J8f$#iOV{SqV3=2GbMawZ=k#vlcJdlopsuU&kP4I!X1 zA<7GNpe5iznOZQw@n} zKpRMquz4SA7H`{U5`Z&>SG$<=`ZIz8Wd)^hR=&?56pe+td=+Fa-|S``VZNCm?#2nP z1{J8XjYzmV&@S|qm*SBXOhE#G@3;C7y2;|vcY8!y(1g12hIkmkXlHXT7iV58h%*a? ziK#2kHfdJz_8RCQx@>m!6zM2}kePg6MBnU_dSF=K3D2I@r{1h!+xo{`9#B_Dbe+}4 zqt&>@I6fn`y4Py0%O+64(JCph9X;}+=I#R_JIwV%92*U70(L)j*mJ`vF=OZ7TB*ty zKcDqlITw({nl!OQJR@V3^rQqUh=IAPAVwcG7IBpKCZB_qawK9pNEd=}vMN^Q&L;hO zyvPkZkrWu^&A&zQvuXiHrkuH%E{?dPNPqR!=+*=~Y{(?%D079^?JFWg>$IglG*j*k zx7gZY!4fOqHX=D>@#lv z1fYnnN-Je8Dir4UK{e|1%62`>cDxN%|3H(mPl&? z`Y?TmepcBd&uH}Sd+kf(RZwTbuxmM0xR$6}4_0yk ziD?)v4fl16vrMlGT*k9Z`wH9{&Fx)=nVG)1EN&S>;CEUTgNBe&p5nKln0Tq0GSF=F*zGV}IcYNBolAO^&Ki zJvQ)=m#GVjxh@SJ;x)i1X<~@!=sjZhR8=r<(t&kKL5U1TvYwo>Um~3{88Z`J9<6E} zk9vvmHrztrAzooJ>5uZRQ((N0@uJ7yJY6nH%Df!J-zlBI^9@m!>mt)=?rN8Pb&I^I zJ}%*5$0&_9lMUayUDh+4-#3kK!9&o@A24t==`}t^7Otlg-!SkS>SSx{9~mcJnP8Tw z^ff01@&d=+N^Wyf@U7?>z>>Fzk;P0+>=vA1RYcl5EECowhCt0%fu?c2;b!h%Z zzLV6aOkXwCP4Mf@wkL__!mO?+B>M7&;43O}@=b#TU$@%w=4@1j6_@Ih^F&T`2b8(taCCd7*8fR=>rGC_wcvB#NB&RK zcQbh#wko;c6S&v9_2A=)pE#WavL-a@3Y&=0{LI8bZXPyWbQ-aKLW~i5gp1uga(WO$w z6`w2)sa)zI2W&xg(MkBuCr zW8xaMI=CmjaD#-!hPPlkn5lTiKcJg{rp8FO39@V8{l_&m^p{^UMf&N2C8#+5lDQEA z`4o)6YCmv?+OV&v^{KU&mzB5T)1e*rJ53ot>pPi^-)F|?^8igbG`r1`YyQ|K4 z2*U;P1Oc1Q+&`}~Uf39Q6)jD^o`%tI00G`0u7zx^d^*S+YuzE5zPX@v|0uF49H2qh zh{rO@y7J~PBaBI&JiyE;NKCv=0{TrOXTtb$SA6`43`Q5gk3RbL*?g8y5e@O^$K z4kjNk=8lmNyg~e#^1^Rh_PMJxmtCG1uBce)V_oz3o(-P<2G&ibaN78)`)NGrrB%x? zgj9;@Ooc2$yylpLWhWTpL3P@LZHoJ_q)e6qvFU-(fC_7fjNw;Aa}XQPif9;<^9?t? z=U9a~V#gK{YSsF%-=I18#8~ENk=C#IT0j2_%ai$cei5iSD&VemeoQws8_nkZWby+~Dff6~UQItM8WgkVHDtEl zz{g@$oP2o)VDNg*DcU4!p|U}G1{$R3r?I%7T+grwO;CLrapU=yLrhd68J`lo!J>k} z``L56qT8u@9aE9nSRF>4PEz+t-TF?M8U3K{}-a$`E~B+qk3 zQ0TDgxs$YBh{~(gZ10r@+6jgY0lbCA?pkc^GCO&?$;LDU!SX|Ws6mxVawLh*3d1D3 zQfC~{Gnwqhe-Hx~{-DQDsY+@_(H1y#vVAqNsNT47R+C8*0P6=j9p*WQRtD3* z@;F648ss+lfdmJ67d0@cly z)`O)N4?$X?PwLsh7~pW!x(MCT3Zs1O1Y?$pjiA#aD`r^{Yy8J&EDMF59&E(;u$N#3 z>g4?wBou2w<({uAjR4q-OE#i__XVw(?+BHewU^HiK_ceqP1TI9R{s{!1J2;^X1_a! z3cz2zPt2LaVlbba&N(Af?Aj;7vdYz7r#xw7-LBqil;<8Rwf8scw&MvJXJd^y+Wu~G z#956`Ou7+js*YnqzhgC&rTB(f+=e%-ttX=k?i;O)RmGYCG(yHVV1K<4p_6Tax0C%@ znLcP0S57|k?)QZ7+|A=ew-7joKt8{@)Cu6)o+kE3Vs>EOvU944ryM**Y6sp|?BB-< zyvEVUrj;J?s*#iMVdS1u!BUWpvZP9$Rv@`FCJp|x{caKX>E>pE{p%;-=a=lCI1FM+2>k-e zM=iwV7~-)} z{{(@L(=Tk=VSfAL$^jo4szbYe!nL_4kbmiMI(a&#`PKNtw1E4Ub(QVeJfdCa*%!cV zp|me5#CFE-jG@oCpcaA8IBPnhhfS`MjO*3$vQ6IsM?KybUDjfw25uv=hZQemzPi08 zL>%+Ajv{W3x$=LQ=Vv&6`Pno5+hjm%@=NKM4NVF9(R7XC3M33ZdOST#9bxT2WqpqW<@wt~Z$l`r`;1ycz|hb*>T=2qgY+ImZjmD*~t z=_^E=QMOj#N3ZGSUcF6ANPXcykQ;ZM`p8|Cj8-^+V0`y^f_hP1&(v?-^G_e()dj~L ziMbQ=@a9~n(cvJPO-A#~mwV&&Y8p4Y&)#~o9Nl$igbB7eY{N3m-mx4iBTuz2qL>xn zbbtMyc#*=6<)WT~j0{A1v2wtLrEjLV7rqR5wjd=BODVAGuo~w`zm`W}+eJw$@y)9w zbGa_Z{Ub(Obg-6_qjjJSnp|2}17rpG@{1eZq%K1Yt|A)LSx#&ik+IAvP^D3AO+?w& zYTlEi5q^JCwj|F{0>~7^->TQ^`d;Y04crWT)LImT327dXJ$1lOPF?b>z3JXJgfH!H zYnjYyy0ZY`6HnL-Hhb(-cOi8r?dj*Amym2E@Mi6l5=E_g4*%2D-smhaTXKh`M%!zY zqWm>$BHg9rN96)C7e7V+Bd7b?qKIjQt)dF;rqJ^uH`zaE1rEAv0Ob->Y)>1tq60R( z)I$F4m&C~&BQ$xPT+N9|QZ+W9eGI$489D{^72v%An@W6;aC^OV*;{s6n;>yEW=h-c z3whRWPl!Y)IA}u82Lb&;zx#yzf7>N(XJtWGiIrshVOg}C-=a9U`ZC3-<%SOTwq775 zLPM_e8UokdkgsS)`SJLivAK?{_KM$5c78+sd(K(rZrNI7zAtmE{r4R%suTp;n}20^ zMPqfUaY8R^8g6XRAG3Tkc+`||#_!RfWa{pl~#`=072 zr$z$@M;T$RBm}6Pu+tnCeT99zYLb&H&rc~7U6gc;nVmO22UV2^u!sSr#p+USpot_O z-G+??=wH5S$}_j;vyp~$nH#L?MUn)`zeFa<15)b!@m!)0NCu`cNbA~8Ulzn=)oY5? z0-z?f^y6eyT!(?h=Bie%8gGaf0zy|}x3$NGFp+j&E+qMU@xNU3*6$#f$&nd^5VyV3 z0k{xYP*y`kJ9dcCrvygJ$vj{@6r!{CH2H&ef3^cjWW6!RXMu6V)epgx8e!X&);V*^ zgzr|4SJm@$;S9|8W-w+bjv5xMvEc-@Ag&W!atOu@-p(vKY+LA3rJkPRy-7aP}e_D+!A#siWQ-oDdmFDPm|FB!_jRc z)c<<1-nlbO-BiRosVf)AIuby1NV+d_22VfDXwbC-N0U=EE?bD{HKa$vE$p5*Ph-?w z_WpQ%fM9{3Fh?5}0yg$m6+KQfht5y?C8oIeN;tG@gYP6fM?-i|EF*{!s(UioX z;S=yd6FI~by7q{%g-Sk z3T|Dv@~LP>t|xNaFXUHSs7V20Rid?ojuIb~(->k|u$Q>EX&V%ZR^L`-d1lEXU6lm9 zDE)zjjsVFc>wNc3ES4WaV)Gq&{Q*{Q!n3@4Pb~y$Sn~}g(}T1pLQihot2-`ow(j2U z75{X-zH}#kTNMujHtyh&^v0#X37-Q|*PN9J*n~|7Q#;`Vy_R_o; z(g&XMCl1U6$8{klWfKuXSrh>bc@=a!+eDAGWfUw*vrzsj;pbC3dA(z1Z4m%s4R!+H z;`2b+1R|L4)KN4s^3VVa^hbY72#Q6X>}>SWy2g>=z@S*!LCP#-s>PI{eC+w%#w$k` zMub1G_n5_3Q)EE{cfsxre7ntl(L_~q7Pn3J9xccTL zgQALn#QTdvCobHUbLn<7xJb}yn{Qla<6TKDH01Uf;-x+DK5gm+37`L^)iph;pMNkG znK{;1KPN-_4d8E^ZlML~!hE&c7IR!CvrBHX+|FgjZZL`#?g+`jd4#8 z&MB~^38X4B0}WTC-2j2t0J#_MIQMb|OKBPFneU%*VuFcLyb6n=FiZ0}4-hkBS9lcJ zP@?<|T=}$4FII)!g~9jeSwME3+GN!TMhvrtw0d%9%8XnNrY^@ zfA{A;L@rePI*BrKz8k2!GGI**chd~|klYath)XzFq|BY@Rd~ksMO{Q0YW;!veL{2! zR2*4)2KZ99+?huj(sV3}tPpdHf9VZ^Fam$2hl_g9H==Ea2B4bkXBjaYYO&pWtV`Y?1-kmV&`jL$q}SLY z^O>r%SgK0wlMK!a*0+x#u{3{xE6 zXjhlQ;63yr@{iq+Ar_aOO3u}1>8CEP`PQ&+IpQWxc3*}78^TTf4XyP0Nxef+TW&Uv z;RE@OC7s6aZHa$*m~@{Nkp((GjIR9C_OlGD_xw%J9OYw^6^oP$rRGHw7O~gX;3p%IUM&Lu0ofn+W9;ii{ zTxFXD(BvHcn+?`x$9fz%fl&B#cs7`S#v6rOFJGop1ONOP|J6|4f$(!q7DnUFMp>ur zW4sQ}FGpy*?PT@>n9T=V|8};anOh2kehyZ$=^PTI?SlkTI?6j#JkcW+@#M{ezgxR_ZGC*|C|>66ULKC1<{Ec293_jlJ@ zi~JgPonM0f^t~PDIm)Z;12jV;UDI*-G)|u$FgpJsfDo6?{X9O5wuT>Ce{hqvA}0u7 z8Ag?1S10fQaopF6^_9neQ08&#ofq)8G83a;WNAo$D?U-uI1N*9z};NC@x;o>f{F~Y ziapnuB(BnVTm;H+`n%gO5}wP&{^fT=e(33rU5knxh+O5#9?@-eRW)VAYXZkO;d+`N zaWts2jH1@O&99Ov>W{cQ(Rzw{{`{nTR@bDTs;f;iU?{by&Y@GIQqmK-H1mG#vT8#! zX4tXH?DgPo(%jKU@t1FS$&T&YxC;a{vgbWs$q|TW#?#G7=Ht$Uk_OB?7Ii9-yK-yB zwy5gszWpN6)sSeqi1TQ+8&^{ynM|xF|7Z%ua8!uC|27$NuH=^7Zb$XFX~TSdSLb)A znE}0%RsL)IewTl-uy{;Hx5~C{3o6KK?=B1SgfB!m%w2T5*_wUm$oDA<59EK8P2!~c z&6JKabqZ#OpCdclaVKF_#4I*G7YD!m`pcY#Qo^Apx+Du9r{|-ougX}lFO`RarO(uz zHt|*R%oyg+$luL=FgN-Vr&<`Qf?c2^Kz7IX^YsIMpB21U!`Atm$x;M9Um0t>oi&8y z@nc}M=laL?!4h4`U)=2733c@rElNVydpuZcVi0_>_g0$$5>c zN+C!~85^aNCIfy#Wd-j@j0Y>gKagdi{%X-VV}QItl}?ev_cqFA3&86mvjdjM8ZF`2 zhKcVyLC@<6bv`~XkNBvWT*t+Ai9du<*xA|WxR3t|k`$quV}zhADir0GGVIGl;e4Nc z=en<^W%vlJeMy)9^Nf-B~zd5X6|HnKt{rsBU_|qPXqb z&DS!_>Iq-LO;O}B+@RNMXME8XJIvV@_>D(!B~0mpOnbRr&Ep_%OL6vRaDDoEdu4r= z&j-;+jtYRpB2`RL;bDQ$8`9`204G6i6^Gk*YJ9Z5$E9haodJBlpe&r(H#~`l%l-){ zo?>(l?8F(b`grx~YO@mrtXATN_mOW3$5vX#PZPl$pn86JTI^w&BhMNCjOCuN+X1^N#fq+TSI_`qP2Q}RW9eNu=pqK`g;|K2b?+6+Lt zew<;f3+~W~+Tx@`#A>_ijR|;@4$$*`U{9J!BFmy*G!9{>AlD!r<|9LFWl#Bd6)R|?hg@08e3{spu4ENkD_$p8Y z5dT@mhD4$@Ive;`g-k%yM1Aev`{c5pUliMg_NpV2uK+z>fBks! zGK18ztxi8IPu19*w$j2FKgWTc{2qPaz&*{OyV-&);ZSX(g!xl)eyKp;(?5f6QbqjX zQl|D>Y#Ncw$vN%u2@upa&xN!@58qGu-H9cu zr8-PY&A+$A1S>oNByHV3OQv*jqCkj~#SN5#_146^N^}aFls6qHXKD`c^yMUikNO0X ztVjc*v=>`EC&_|%OdA6W@YGA4DGevuJ8}5SHj=PXOesfKeul2ac z-r@&b?{kUx=s_b3)G(+uQIk9qGaR9&uCd8F(#IG~VD?ltiz(2)Lzx=&Xo zFN+ml#w)Doy3DSsX!~m61HC-|&BmLXO0eh9H}mu`{SmT@imuQEe>QD;Bl@2Bqxja! zWPt0@m#~h|oq(OylkNMHm_8!!2mH`d5#w*dkG~|Ct;rjrw>Sp7sQ8k9rtSb`>&_hm zl79xm+&VX2Am(`ZUhZwZEqhtvV~@wzYAaPv_jmg4=onYizr_c4yF)z^q4MC{ePxfx&}s%ZRve(=Dty3k(Qq(X{pj&01C-Y9^}aGA~ZF&FHAP;}jaQ2%e- zxQy&0<8U3$8BxcXWt^Gg?5zmLWs77dD|_B?ogL1KvO`ifXB1~-Q$`VGWR<@C?)U%w z^}f&hdB2|5cplDy;R}=`l zVD27)oA6zxv7zPKp6ZKC&?+-wmz|*hfN|l`<}nX$8YOgQ^XDf(x*4P4yg3%xaWmB&(Yy!t)FnXPMr? zfZYuU~`uC;KP`E}c2}J7pEozLI{>&KT)td=kfTzka;*8NiiJYOo zKH4Uj=^D3!wP^}Ai~dd-Sgr3D<)%pPl|JxF+HygCPa34E7N0IZ=9D`|>LG9KO_v*e z8TZOW`4>W&`ZrGYxNSUzp)X3UsB77ktS#*G7;Kh;6=T|C#Wi({eyb(Am6j!H?)w{kP8{L9=zvB^pesr4~$X?$NZhE?GYZs!J){=M zKz{s27K0xW)GF%>$H)BHqH)uN-o6BB%|yM+QR>_`7%zS0Bg&}VHNk*LrI(n4xZOC} z*AI*$8sp#=MZ+liw3wG~w8dU1LW*=-pW!^pVyCSJao)f@np6F2Hoep zh0*a9RhtYUxz3A`>gHY8QZB&@Gdj)V!%vxyyN!c=S5^IFjK$hqLj-zI;w(=qs&w{B z1VaQz`^Gy>?JMjzU*{h8SYCblCvvDPIRLO32NWw!Zwp1o85G_vV)cQh} zLq>(@+*rWf&nqj|q3)lJE@t&G74NrQq+T&Vd<}Jx8mD(l?a(PEtkTiDF^irUV3u37 z(u33bT{>LgJq7+JSH|aDo|PBddt)r<(Oz--O`b4M?!;1qh%BU0mq=%5^C*@S^Fes= z*h|_pLi%LblXUIAb*`(@Cew5`dHT9J+dfUQj-lUQ*GF+=LtWLxjYliaoG+or&Uo9g z{S*ae3D!o59XXT?mqP;&^=)G`6g3@X85NF7(#Px5E1aOYZp+MkykBre7G*M#UWOz- zc%Qb0w;sd_jtW_z7iLO!+nc%Zan^4ZHt`!yCHMLK-ZXYTW)`V{pQ~6YAcW0ZB#kfm ztX6uc=8ET9?>xkT6o4}I_c9ADu2m@`$W9Apk%4X$C;wSrI%)nT59A=!+cvtcDtq1G zR_^D1v+rIFT3OGT1UWB3c_!)1zj-l`u$d360ig2D592Z6KMou+Ec}=?`vO7=`U33a zU>9D58K(F=Chas-+FG^PH9ooA+%}6T?A+OvL?vrbWlyp)L;JxS>?5h0+Hy9i`RPIP z1QW?XJ-2X&425+?*d{Xes>}g~m@xGtZpMAB#*IkWa6Y+CF|E3Yo-IIN_SocX4;}cOUYfnnQzvY{X z`c3H~NmrwPuC@0XZIyvlL8UPKL)fGN8_AJMo1K7KALHiePurK+%MyHgNtl_e*5CrD z)+M|-?Ybmq0y2>24b*DSylNf*UD*|fR>f57B=XNs`aJ|#u$F{gufV$DVy{Mw2{kc1 zegMl?*Qce78;YyY3*uQrpNhjG%;}q7D$}{Sgt;Q0)^_+C@dmQ;K#?6Zeg?tWr(EbNIyC!X4aZ(rf=ZEDk?TxI}dtDyLPtn%z z1Z4`y;GR=37En?Lj8E`KgBbT(JvC6YP5KYeUjX1>`|ul$<`x}x;?$VZ<+&*DZS0po zi;#8$d2(v_BP0$lGJRZa{JiW0g#G`EsySpZH+bG!Tf?_P$HI<75z96_FwxZ6a8Cc_WI=$?a+uH;N zna@cQcs+=Wl$|K&qt?QM?a~1gCM#|x9joG#Iqx^s#m6&DKPEY461kSv4U24zy4>H` zy(cG-HyP;()l=^YzPX-#(kwg3j84Y;-&a_ zHE=+=E8Ek}DC)z$?{)JhLF;I$yypp9gfeHLDHw9AJ8}0O&IhIng)N5!GisvCvgzoZ ziBNtgW`f42lXxMu-&JOb;=GCK*N1*geCGr>jsASusG^!U-OQxf>y~9%o;RphSy&k7 zXmz#;aG>_g1iQ2v1@j!@Z)}>=%T*PT-3m&Mg-jdTIJ#ftl2Nij=z0``2Rfy$$=pXK z?>>`NTprh`?8hhC*v^XkThbV!w3unA^Wg9P!NYOq`G*D#mqtN7Aue?*z7+b`tE^-@ zHlNKHPx_9w+B@CPQn5D7Z%?OKeeyddi3*Y4-LlPX4Xjb+ra+ifu2nJYI*WvR(g5*n z(WsUCjSR|ve50>jE>Z21hCTi1)8E`ibZIdK6&MS3cNHwIjUF1dVQ(081{U z&ZiYE6)2}2e#R~*5QscmM@zLna|Xs_(;F4FJ!cvTg{ z_brK{Hh=F89Our514`nJTM|qVPdpN2Sj2IwVOtQ(wLE<&{wmZUss)p{U66*c% z%~Z8fv!?#H%CSOpM}VdVD;+J97M8$GJZK{Xw&xz_;r93p0H!N7xSOMVIQAEgQj_4;YOk<@IyZDI$8XO?wn@WZEpP)Q#6LmP5)x@vmpax!sjJ!>E+vR zrvBXTzVWz;n@D@^L`?H)EN9Gh$hT-3DYiaDowhal)B>Q5x@fGemdg2t>V*}XZ0fcA z%NtSDrc!!THS0K&d_7fvx8cUpYoYB*HbbX zI`iCiH2RmJ5^b`$E3PGM&)^T|@VpZ_=G?4v!?FNbEzm7Q2lq7us`rZ=By@c;nXNxh z;*9#j3{l*lGFPMprkh}s#L@xUmz~+1%*NV%9&+x$D;azqyzCNrn6|?*4C8L$g2{TN zEuXQbJP>Zw74G2(O|N*G24b_)e?CC^x)_N&PA)v6A>5u_i*`wf%nWlnm<&b?YX21e zv81`A8tV0PFs>@%lHdtExow#vaQIE%sPv5ByJyE=)AKvFd1_Tv+;PP{^1Hdchbx3=(uEnZjELf`HtT^xW;E|VvP7D^NB^h&x4u}m z?QzTH(QB*?|2~t#4Vjs92BAH0WmL`=Hs>=D*+$z=Xg0uh#zI>Sv3HFlWmgVeTqju1 zq8YZ|nLt+C6IrP)KJ^;oQ2ajiz^^iAD@8iQb=#RGh6v4k+?1iDz;O=w{(XPT7si|j z92JmTxB**LneTQP2V-)*rdL(T&c3u6UGh2$?;bWi{@2e0&O^&9MDpfE-jM0}8RM8I zNhRe3{ocKGHNv-qkHi~Sn)SC^G3@q7O_=$1_$M~|E*?TXDjn*HkcwbwSQ z@Toyx_uP%^n*Sa+Q zH(X-YD~cFk-vKn+zq+@qBG!Tromud$A^h#BNL@K~-{}aGci*qam1_K_k8z&hH_+4o zK(WJ=K@m9=douE^97;V)Nje6waC71_&8R}t8*PY6hkKqIt><|iqk>mu`m^Y}i|hPF zUO0J_5Re^zgf4#=$ zPq!=X=17V36qMyq7`=}5Na&z$$yoj~#Cz)w2j`uVxIT_N{{iV!_6&Gl{&^kcRY>!v zI!d!4`o*s|`-9Nhez#veUf*5*+9Y6Ge+Pee$-ATiQ+!~!1Qfgnh+|=-1@iMxQ1*S7 z1HiHso64?F!m<*`+r&D1oMn!=>zT`yo zgXJo+817>7{ps{~iTc>F{t@d#irj5X!$xBN56QF5WaUe0D@>C_7p5E{68lz31U*K7 z6>WIfJ;bPN&_^QBXC>zVVwU9548!BM0tS3l@jempw9i-w5_dC9^%o#VD{^Md&nzDL zsDI&%blpBIxD8*FfAHSYhFN5-%lnKS+fZ27bU)YAM|dMnq+~l1E`N>!3BDVbe)&%E zdoDYx$DgAl(^y`@zBBGqJ!58KBv3ljD1E}q?DBAm+`&~T5H-`q<0v~IicsgvlGR|= z)Z`&T6|uO7uzw_tPTFD+8r1HxrC-acw~jy+sL-;)L}JKAzZb8Kl!(SAK0jXIVnVSS z>}_IkflOQ~0|r1QO!EY;K;N$b^Miae!sxYlnq*3A^-G8puwz)i?xq6 zm;=3HhaGoCvL;67g!o`&Ak66vtnq6(ODuSgG?c3aSjJR7Rcz+~R%~^@qtMxY1B|zSYSlS-jcs$BV^QR|_^7b-|&HE2qMQeehMQ zCd0oMsK2zK3o7ZL&gQl{An;R+-2JBLyJ^{5mqJ&qcK77nj(t@%?w0)!irBc4t#8up zSuKM6uaUeVk9=_-*Of6lmYD2|`*G%s8}#u0>MH*vYnUb`5kTL@h|?TFpkSUqZ<@L2 zIMYLGw1h36zM_|-*StX+dYlAgQCgF;Uawb5@F~c!t~UnC&YV`pD=@hD8p``yg&nzF zMBkbozhsV!HqKdZaGfFQ&{@a3lip$Wjtv-Nl+xw*5nhYBTaI?xavB2rNiUZ*5&-z$1TTG)W~sG97;Q*P}{s?iSCXybnedr zuFLu+m+<$_>e>uw>DOW--F#b#x#uidAI8zRv(hRg4D3SjbUJ08I-YZVHnHxQgG}h}S={%5p_WVP2Q4OZc zjKLdL2<8}vk-rwrcE{Hy)O#Xy9NS;`}OhL zi+)8{45?@|U*`!J2EO!jO@?YcJ>W{`{bWX+@8+Y~?NtZV%ew}8chy&%MQx`rI3HQ;g?B09=Kw>GJcF(8sb*vc@7FMN$8fjPju7hx2LlI0|3rJ5pq^~ zVfM8|8ros@cY4T59pbg3J-|Hy`S~)sr@t z{)`{veB_p5hie|HZg7j6mB(iCD$ehIlc)Q6{X(=8d9CziXr-eBLjc;9re%!`8(WZO zk!WZ%O!ekzwcfH9hvY$z6#i4fPt3%revJr=(opAFPzim({jJH6<0;(J#`Ek$9i<`a z!OaQ)^iuB$Isf&xK$h)4Z<^sc3MS8gFiDwh{2ED6VZ2I%KnFIA|JB|t>5~`{oybF zg2UgB6VT{smn1qD@wCY-PVv;+4NOn%{ zTT2|2S$aOqY%E5$6h>#@__EAow&qEwk7ZNK@uozmEG?AOpTdUO@-^;SbebErA@!|$Ts z=%EJoVbfk{t(yCxxy|ZL#sa0ez72R}r^*17DtFK+(zAwn(2F?m>M0z|o=M@t%n>)F zlgw`tg7kcG8&8>hf=h>Qr_}%YSMoM*`Z+@oEScP1QiVKT#7WEL#Vo#*VGZ^dfsYTb zcs}ATJWTy|4$np{TrznBF!^5TeV)UKox`MQF<~>x47=xXiE`sfrVCHiCqHNGJle(O>1%U(t3`rGIZB9?CswP{&bSjDC?r-&9w>m0qm+u@U{4Icg`h zPo_E*8Fz+FUz5{ks|y0V(Xs>kLLS75W=#zD?4zRV40FOS=ll+_5%dSo1HfZ-K}suQ zIwBuGnva+(PYZ3V|9pA6BdMr=ALe<+t2u0JOP2n~x@sT^n{oTe=RyI}(>t4ObBW{Q zFPA1kE8MdQqUO+hvOAp~_r%p9ceOoy$n_B&zpc1r(ee^hWsum3wxDk5L`|g#vhu)Q zw)+oBOn#ZXjPHk*Y-9xtObsT27$x*URBBKT4I>LqT3e1>03H6by)xDuDgU-6KxISP z)0cnfGoxM(&UUCE-_Rf;HGfwRLbZ4oISs)DG~Lq7pmw~Wtj(fh(@AbJ`mmiQmOwpE zoU>;Qom#<~LWG)ddE<~Yz1yzbtE6sFovbgNOPaYhhY6ZVmbyoBklrSY)kp|SOyE}W zwdnDjVp~wij~l03f^v`f3;Vr)>`^g=_KGGO9p{-4FW_&_3nz~rUMs>L;&MNHooPz& zf9r{%{sISrI|BvHxtRDJc(s*K+Ua+0R~&GE%wYSYKDQ=@1HtpJK=^B3mAI=t zH^w0;$cTG*#t+ap+O|NJLj0qvGN7UK_uy!HYN(f-Y#heDi1bU+lH*FxF;v8rH3D7_ z3pK&g|L!N=y*z!%2>0AdG-2?$-H}eG{%Id+@v?!NC|uHE+O8LRA15&@pC;D?RaI$x zGoN!?1I>!x=@T@Pfw^GPu$g!K!Hx;zcl7tmk;AslWN8lSg}UP+e@*zmbC<#JLo+7- z0x*sbzgUun&d&yY@vc~x5{HoY_r^qA_)6jd5DsuvA}in3qC$d7o(URg#Gw=$Xh~JbzWSe__ONXY#y*QNw)CLi zc9d`TR+vn>0f;qg*UMgjdryvurVOM5eEzRK@G)eE^Oa(v8*Vs_XFeX>*NtSuRP9pXWl>N>DD#B47EYspW`iZO>O#b>}r&!ck+v zNvcry^K9r#>4(3YofK zT1&jEtS@y<58fANUX-gOd%R(Fo0^Z40zDhQat(Xpk-{|4z^FG}qwG{}0OvWzFa#*p|mI8)agIC-Of;q7yP_za$e!^zGorV`O#9i z4YNV!S#&x|L-jte(IJ66WWQA{Vrht0()$wcJ`x+rb}6N504vohmi5?Y$c9Kom3XHG zItkczimLd3nkKNL2)<3c%@QQ&)Y{=g_m~?Hh{h!ch@kUPTTj|<$#;04B#9sQ$jIlV z(OGBoy724Qb$Kbgn?xH@7*d?L9-)xc0qS`QlHPGhN_R@7u{7q2GFF}A ziLPXmF_%vomp_w&qi&4{w9K$H1Kx;>tA?7AX zc~vcjd#5glX7C^s1CJ9O!UVOpCzM&A6nhLuDVKy`X%s;?*488MS|xq0quI}34AW+t10C7>;^%_mrb-CJOo*O zZRw@I@W>Te3`xu~aR|J;cieIjJMN0JrOz~d6J1P*u*yK1#)LXYQiLzEJO6njds*?c zGs{0?7Pr!4P}*WJwk2Z!eV>tWt)v+KZl8HitEwo{q0I#9?{R}lGCZAW> zhw5N%)RwYnpXD8hnz5^~`F;K8E*%S}NT+K@S>thKbT5tg_8Sqw&R3Q7y12Rg{I zS7te8(Jje?cCNH!={}cafy2RkYit8H=s=&G42WMFGcacGEBLZFBnq2sJ^Ik7N>bz0 zo&*i;EIPSGMU#;Otiu2O4Y6JQ8gntVE_S1I#r#-Ta=7MHcliFQd%`&MgoX-cmGIM0jmXm++||GjSb0)_ZEk{p!}iy|XKtoa5oae3V5{CS`JpkFBlCox*hAAZ zDDYPtd8pPlN)9eH1f|VIDG{jUUC}W$HhoP}(xA{#>9O@);E8mT{a*uEY2zgXk|Kp~ z?KU@kFt7p7i#-1Xqu!#1$u*!+Jlhk5{yif^1FgjZLFayuiBvk7MEbh@T$c|FW*$vYF~`o=_6KHNtrFXH_j0XYK0mc9<-oZL1+z7no|w zi?x_@c%@^$ct)?j+&>M_u zz0}HBOxMMu<=4iiVugO8#p08X!lT%N*_OIP714pi7xcb*KL@=v)(KbXlAnsQAtWzn z-Q|(U5<||8cyujzJilKr$|cp1%tj3DbHkyEHx4owyr?%^_a7{ecY0uh=Dy0p2#q4Yp`ti9&nd;dwSHck9utN<5V{G6=Vm;UX|$Qw;WN zA2|2~XPdY2m2klZusG@yQ-I3d@4I6POCL5;p0<4c_hP)RE7G(=X%8Q*E9@t$a7H5h zouE7nO#Er*XZg3_o@8$+Lx3h5@{+b4V0H?Jao_JJ>x=!#My$IzdyPuRYa&g9P)@z~N zhc5Y}B*9~fpG{SWa0jG}r#al?E=Qa&J&N!O1n| zex>yJ^oK~T0-FBkpR1Ju1

TU#Rr(<#(s?Y)W{7Dq*EEhv-w%@++Z)q0*N)_NVKr z5dca@i{eN{QMt(@1ngO@t~+F+tv<)<4+SS=adWT7z!6<`psil$h9golhn?z$0BZ!Z z68g2N7g^#f9+gqWhM%s>`$b!LiM_fdz9Q47?pAPUtBV0Lmh5SISI1xFibK(H%6QcBEezRd;oxAuE#sX}k3C~YNy*7PZ{X)=f#&*MqV0y|JE>&H=%aU=RB zzU3$jCOo*}i*bR>gR_lcXi5M50&02DEviD&(C{m@f0u=YFf@*xdo4ju>L-KS z+MpmoHy0P=!Cm$N(CC#m&EHI&7o~uVYxWlUCZ2*)u=wf#6E*8fLX~hN^qDd0G3!nm zcy;qKp{OI?#=-RxVZCu^0`YCI{WKgkSltxumYYmK$Z#dHHUl#ldzp=FP!T_^G6q9T zV?`tSb;NG(uyjzpw1#D3M|5vIRZrz zu(r!@Qwge@c&AS*s#IK>?yF~}Fx^w>GQ&rt!GSjP-yw-+xM0a(Pr zr5KC`nY0ye{pz?;VQZNU!>x_t!m|1Q|b+%t5Kw_lySIJs*>ws zIZb@gg}J2>RS3Y}%p8G!nq`2H^-dI`D-`fy!}nN(WCMur3kTd}A7o+I{9vDBP%MRy za@F(tDaQ_LH1u#YE#V-xCDfrQCT=PQNs)<2e-DwFzh)7ft6d(|4<{SkuC+orJvB0# z7R+f*Cg&r|*z=^rFp#vCyg($7ozbP<8Q<-1rXI*js2Qu?6XAFyXxB6X!?S^6U6R$O z#TR5Co1H(cx?%(ibJw|}434yvbfr98aDQm2icWlw{qEX|>N85t;cIu~k4MC?8uz6cV*gInU zl&hky9_!gbWKtgDyNN?H34L0fowe28TnH_}Oq3kqMK zYtmrh^>GIJ5PL|32Ty|-ymO-gtXDh3NNm2#Y8lF25D7bNE8Fk{``d}#rezPa=&ecG z=JeUz7G~2Ogxs8VSBM*~lszkV4X=l{82$}NTN@xjCEP5M{x?HGa|ApSj zN0p}!dcpu>_E9pwY&4}enS&Dt)!AQ#m^=kuqQvNePFfkuC-m3?Q0v^9C8U9*Aa<$>6(K2k%$_P%%>kdL3s;NXtF*& zGqpg3?W-Uy*0Mk4;JTadihuFp+6D4(nhb0q^!^aQVBW6>^6uEe=}3uv(4D>lXf=7)y)-s5LuT2PPoXI0UALlBM2W!B-<9#bi0(g}7SNnHN}J zt@d%4PVzD9&otz+CH9wo{u5|D(l(;`x$W=D=jp9K1JD(_>n!Q>vnoG0(fJnGZC@?7 zyrb&w=I=L5@}P<}Rfi~Obq@#R*;Bj$k=yd}``#pr>_m7tWaTFbrU*HlyWS z1Ic;Lh3_#n9_SQEa!Jq4nbKAswQ*g-b_mLoLpowl(c+`du~*Je^T~e^Tp)&{xz=y zvObgZT=ZpAo!FQ^gXRd-a(5&pmMMie_I@LyPlEul{Zg3w^$EDbDV^i8y5ks+dDpY` zL@&dzf#*a(3^8aS?6{IELKK3}F*P*Q!wkA})lNxQHbRlW9m4=^u!_6@6PE{HL{9DP zN(SMGxB6IE7Hb3NK&D(ij&p9qIu5mFKU&>lRG~yrt;|UXRH|n1{3c)F1G~Y8_x#B{ zc$%rCsq*$PAUi=h`PB-WzgfRmap4mo7_IYDFzmbOAR*I6Gx|sQh$b6Nf|1=qKU9s5 zJPf@gf;l~>NvO_X8T-r@VMX?ARKEtgp%+`ho{FM8bov50w46Ze6GU3O13(Al1FZ>h z38knP!xD<{U<^=NuBE_*f=6}6#wb3;e0E71f?~BD)h`+1R&(ZFUHY)k(Ygqo*s)1u zpUwn?5`Xbmd2Qsm*d(sC@FjY(YNJ>PWgzAgeKr_b#8`NMCU019(j)qnF_6@&Xel$Re-_Z9x-imjW`Z)q8&!?>^ zrB5$3$P>FlT38Hn-aYJBI|V0@eI-8-mt1gHs}5EY?;Unp3*ex7^@%mshAf`22f&uD}U zpTr(+Qop_?@P}el%ocLyre3(Dm-FX8uLr%PWYM`<#f129rqTgQ+_jZqs(0-O z<%ptT)?=hfPeyJD(Ls<6D0_~Ip-d_+%f?-4`Ep?jl{P z^2OGWP3H6MIc`q?FonzLSAYnDgyje%>XyqnWRUBg4;gdH>l+n($YwTB)SB%6Tb%m{ zi{mA*v(+Lm_3GD&bFDfg;oB7m4oFhlfksDqBiFtJY$o!k`j29%;Sx zM|w~Ash4)dO&JK8M= zAphzMx8k0|lgUB0rf$_iA~xxAXp9nU+qy*o`SnUoks1A2YcHv5kC2CEcpq(>+@+Wo zp9clZ-dH8*5U3FcQfVZ7P`7r=j1PoKfaEwg1!faP@T}=Tu|PR8gB~XXb*iAO6EH$c zn7!jJ!qbC+l%2D@_x8z%n=7{@pOH=(I4ZoM_Q7#hiKH~% z*~HVl9y`~-UpzQlEL;wG*YCA}Ic!#|&^_ao9+6->m{s`&{8txKkhXkx^tNg=R1r}6 zkZpr$uI!7|gz^oqEq7JRn^Lf39gV(?;KwhjoAT03XIDQu1eFhf81cNze}|eoH}Rg9 zEQM4bcrS;Kp!0-;Rqum5=$jRlh0tZXul)XtZE+P;gmYVXTD+&TA>RHYb8FA%>y(zX z!zVMC+`t=*2KBM(d*fh{Duyg4VLU_uZ^aEEPmBSj*pmmbO*|*v0-#b!ECO(-!%Q`l zpcP}XCWs@h@=f*%;ji(>EF}PF%zh5pc`JZZC1fQvi($CBkkh1l-6!y&Ub^^Qx&Iif z3N}+bk3Ki%Q5#*ZlBEX90rPrdBHk3#*_cS~3Mu)~^^XVd{geOQkSV|Bs5~Qn^!?Zy z>h|T@^c!Cii9tLizoss0qMOX-CJIpn4@B?EK2A5M{am(g_r}61^3I1G@b7<4;(1h^ zmMn3ki8+8&6Z^d~mxYc}U3u#hX>V*LThx=g<;VCK2PiDWY5Dh@VF5-vbm7?c|sZEbuOTMu65vnaJdOQqnV*jaIL{rjWnk zZ~8-2kEn!QP^t*y@czkq3|VC^eaTWI8KvPF(pB2OydDr8&>$Qr~Xv? zilES;B*y zc4#1F_o?AQSd%UCOI6;|-+chVjAu$-(S^ba-*?`vabw!00oU>a=vM}Zil!=iuECR^N9da?QiG-A3c>oqvM#@T@RC$v-jrJOM6!u zM6Y+305YJ6gmqx$Upxup%^LIz5x~E_j`lkFJ-qR^F5s2g83>lXJif8%3N)FODdeQG z29A+HqqdCM6f!jfK8F~=yEuGZU{tDepmL6DHPaOSy}CM^wIPXu!IWaEl0vQC7L&w) zd4n!7QN%0igqydQkWE@_O%5?-OQGYea?F}SFWPcUERLnaAUyNVx6xnNQeN4~sp=~H z)*Y0H^G#-gBFp5~6!8+BSKVSPg}mqoc3*^(sMajsC!B4%80q;MC6WN$49(cqZDLEH zs(dhpk|Jmztr#prrHy!DEGDOKh^*Z^1*l9(Ip()A6wP~k0-fK>h0rJRL)RHyan5o2 z7W80?xN1FG)KSyEc(JTkWmDmHq)Q&V_yAM~n@rrGBKsSgreJu7%~csu0D{3!dAcmZ z)glgoe)sx~k4yqD%=_sI`jw+5WyN)9E}cAl^BWevo(`GTgpeCmbe|i*tlUxn32-v+iY<|Xv9fp0X7@pw9}=%;a0qhNxlRE=1Iu0dsJ$E^$CQnel(}`rE@0A#ff8$iHtb~JlSy%5p{Bq7OS-+xr)}qQ0zg-O6Xdq*^I8B@TRhyMZ`nB$P8EQSwO>cDtmIdN5vD zTa}BcjmG5&*w3feJ;K1~?$8EuCyL?q%Hws4Rn)msq>pM8HZJV4h2r%vELq@?R4#vX zTBMAmK2XwT0}s)4mI=5Bf}LH#UrArS()U@Cb5L;43ua8`yC7~Ft3I~-_m|t(DhVc^ zR|;{3QM-DM6BK=`^L;zEo!6T3!HII{-YaSm<*UPSuQ z`3~hw(3=Q#ZlSLEbNO{|vztLS61U&{?^8|K-1Rn4i-*g{PxG<*?q@dX65G1Azkj_c zvlT)Apf=J9B^!l5H0Wrz@i08<5XDyFR@Jdhe9H&>%!QS|&WDHiluSS8U&m*y;}iTZ zoCC#zP#KSdhX20y7xrI1d2rZ%ZJ^RTjYv8u66f`gW{o;wQ02s4DiTM}csqNTq<{2y z6BAnhOX#jz!@GPFTBMxS2O8S-h;;su1K<92j`6S4793QuhyBuyKh~ zjhlfgus&Iy(Z7Kzi9u^apV?v}DKG9KSTVp*2PM4cpwUpHV9dt|?0>0Dsw4)buPjE{ zbTie|S?9^kxbzpmoK5t$+b@(hgbn=;cV`x(Z02~!PM!*6A6sDPLl9xiF_(aT0?*6s zu7=9IIYz*#$zUw|gqy^=}l~3GEzS|a^gCHBeZAns> z-VwdQqdEcGw=0vhl>$RJ)7{hD6QZHWFPu@rGaS+~cb;xaa(+;dpVx-KFu7PiOJgha zYq_$FWD#TVmB3KMuwm9#cBn_4hjz8TI;`FUAF-Hm&YGV|P`ZpAUF@ep*-TPZ;w5|zf zxRCpXtQ5<=X1cx_xAGT&fi`!&#FE5&a@*@7(P7MIs_F3alROSyM+=Pd3Y`3x2|KG2 zXFB^P;sXggF#s)`oi6ZlSf6|QI3DE@cGJ!Fqr{{U6Lffc2v*iznVy79iB{ca&t$-IQxH`PT7I6xG-lE(RI~ zwOD9z5CCb-D#-j9lsmN96|uPd+|~O7bXy>Xm}Z)9>fn@T3L>U0e+tb0icv062&Sj3 zz&+FHt?s6yo!;4w(^OXUOjGk|Uc0kE?Tj~6h~hMB%Si`xhzxwFnPcdKJJEN-aqlD~ z-9#}*1QuED_Z3rh_?CAj;?(91kMaUhwc&39>m-K{5Y9Xui{9exvZO9f74prev^ewj zZf*exPk=HnX(5O{B%gim z-{3nyZ8#_7i{fPJQf~R)W9F2o&7j?ZgcVd3phowB1IpC#pk@M8EELWmR zCQQ3;xu-v8A?M_Ji^qX51ZQpk9XCtxhKv(ZlG&$0NI}h0R~XiDFBC8qYCzHHjFlxbE`Li8xzTx%k=i{K3^TvaG7x3O=!a0flR0AC4=*!{>xOO&$DDE+w7+=b+W=HtaJDdZ#srl)isGlkZgfpb_PA&~WsZWxUu6=Xj~B(% zjmWyWQb^`Nc5_B;$tFWDyp;fTKs~z)S>kW#h}K-M8xuNKAC0x#h}n3DAIAdyieQt4bNS6ykfBC{C{DEW}~vQB`$~cHc!+vSX}L==EkKz@)4B8MZ7BY zYFOB-s8vK;(XwApujrd$B2@%bAIYS_e$ZeE6r;MxJ#{}s0$D?jjJ60XcL;_ zIk7~EK3#3Oyhm*q>Iz<{?%sWIYSxU`7J9dW4K}lD-R3jZ?3a7;yG5L>#ee&*I%oZuF)l^i@ZW$uQ{6tgi*x! zf-{Q;K^ied(lvA?22u$UEG#Bop!rq@sD!{Cn|32%+$LBogkTBfI6wh<|AKQSe7t{^DwL3rVTuDQhH=^0dI^1hSFNZnhxhFB#@sE z3&wp}+{6c;cW?_}h$I?S*75QO{4Vv(R`w`>9>H#5Jw{3II?u%%=3)ZNIX)zpoK59r z_iwq&bEVA67dZo|K^=C`Ejk8~rH|KTCkG^Ej>y-D7cv?hf23fUYW{8oUe3I8RDBPo z>G*i{b2Q17LZz+_P~+1P%xFJ>2`T|_NLFJBMD1elE4c?`b1!f@iUYm zO|mjo7AEG*DY&J^w=E}1_%+@Y{gDLm5iyR-w6sSTH?$UIng(mu7^}*COxeQ|snY{S zO9oa`##|4}%i&~txnu>jC|gVvdg+TRg=09?=B9e^KObkD>AU?X;xziBfbt`zOow$E z2E2MO24@Bk;WBS88{q$XDaEoeZ!d*;ruf(MtE1v{q(;Fb;_m++j%RY5u)jp)t0BLo zlQ}knv5gS;18dzq*QWLX=&h&M=gvr9@maI{`1xzAnhsCaJ#^wCZWE!?0^_Z;VA#&y$$F|Vj*7RPuTG7B(Z?W=d)7+TtZV~J4gw`CFdk-pfYU2~JIEN;9R z4|7WChb^e&3ztk+2JhW~feh!-WwfrXR|B7a(4AEENJDK5F~@^IG$x^hl5{xb+lp>W z^svgp|BPYHsrryy&3~a^&8dWjeESm+^|?h<^5KIIP>DW8!kxmK{nN_$zMsz1h=)Fy zK>p65j{)+*nHo~1H0AGzE%Ulswa4h5HLIq)`u~G46bFf07*|Y-+QawFssFcHi=3g( z>gk12p~sL!Dr(nW%#=#6O2Cf$B>D=*J8$0!$(TLOoX#?w&L>>VN$$J48*iySbY2oVzG}mf#4K zP2~&VIWbJwvcR18klj5V+wIPe#_kWYil==LPON2cxzW}dhBpGnXgw9hyJgPz7h{Eh z%rsbBx0wLlwS^0n@1VpxkK0UtOg9htS{g=ZsxLH-osS(+5t|MbFr+^kmXPaUGrF-M zH8FIqel*os!2KalXy^x(*O^C~XbAXR7Adgny&`KknquB4@gX%oNAAx5D7wn9CL1&5+IwMmHkeASGSWB{3K=Qerenh@c=yH!30ZA~_6NL_$HoeZTf+*R?-; z?&m&npOd`+IL0>x<-r_yFs=nE50h#+R2mnwij3Z?KBPiNpD&+9Gr%w#&F}h#z_(nl z-ya4PxP@?>Zot-ub_Q|taw^XdpHGUP9K@^b(~%c~jW529+u^-tU99NbLW>c>djcBs z(0eow%UMx;q%%-2J>lv=*9F0OPh7^?%g)%R1zQWkRfd@vYBkc9&t`oY(AXJ1j;w8j zMoqmkgA7p%KUD%B(X$U)r99=~Z;6#)UW^ASupz;5B8KOqB61WkK53?rfGW}@-HGr; z`@8*|Y_W$>mglpPUjwe5wYwgL`t}U{+?+64tZ&@QoQkFE5{Nw!6j1TP>$RM46hWsT zo)%Jcpb!^FA3qX6jM@}vBh<#WS7DB`gbMJyiZN4VQt0$RO}?0*k^-;$mf z#M^lAv$+t4k8soRn88!4S-Yo1MME;$8#-JC!941E(Q|L;u3WmdJ9(7i2> ztj0+3ZD^w~?r6Gxp(XFJ`%5OfDxnX*_w%O)Eh7MhBGQVbeCw_hm?Q_E|Mu<;gAFc) zc2bq-;*G(ZLQP^SRo1}Rjm~1K)EYKNlDF63O&lRUdy+)%27iFmS6L95&N1tF?n+*E zxsLOFYq%?9oExh$H@p$&BLR7Ae;@y9$oiaUOy25#k06YBB7!<|=Cxaaw{t;sp2P792RUpE+_lv915rlO^vO>;0*c=k z=LF@Q(x(&z!<4_Eq56VDViSsBF6| zNE}b1FwwzMAaZS`{8Dw%k4@l;x{5fDbOUdKMjl#`5a;l_)e!`uf`Yv-3@NNCozQme zDqSo$=9=}{XoY9rpNDJrEp#3NJrcpXMR3g?eANE{p4F9-I;fLwh;}o&}f|PlnbA2L^f>jfpe%u`Ap_aWKyPC zjACR{#wCBr1VQBVB?DpnwN^Wp4Wdm4;pp@^suK$kvj}02Bi>^dJAez zR8a-`eB%pyuOb7al;`WfYNiBn)f=tynN8%p2lyX1YcwDrcH$^=1SCztI8pEy?4M*h zuWa4+4E8>MxW#=L0>6wM!WH%Ls0u@#pG}FX&Ma2B2elR7 zO2`H*ez21GZ%pH<$%s6hW*6;Ep^?;3sf|gpEdso=>8Nr~v-nL{f&nX!EKnBXK~egY z+duK+>s)z1NdU7-#C~h$=Qn$EU=Pk>ndhUQAR?JZc1!LC9ATF^a^6RE27BQcrE&10 zOXjx>c4r2cb>bGe@)QG@7@?PYnp)S-saOWXyawg=WO@47_aE6lBlR+L=IgGQRm}mD zlW=8dqdhmxK0hk`k#Gs)Int0wLu{xGM3q4@oIS=(Gx9LS9U7;XFuvRFCT2+tA2gPK z((>Erc)CuQ?=n;g`VNm~PzDAuI~M+G5fXyRkNuio6s7T*IZ z{bK2HyJ$?;Ea9d@of|%OMB>K5*ihyBh###h0m6XNcTlr3ff2#dJSy<*quf|T<8gzn z$TL02chq2Ek!J*;=^rf_WG|`b=RdB&|B7$GT02+WwA3Tm?{+KxAd)0_-@D$-P4Z?@ zGEXho+2;J_(EM^@&G8@IJ~BUV{G9?a?|Y!Oa@ycx)oQFK$IbTW2I$c9{sBG8?O7yo z0wuR-1!b2%4TAq8 zKX%ZPxU;}d8Cy>_gm78fn5)^6+I_3}YChR;Xmnk}5g%tZgU_|!^CfB7n+TY!4E_oq zLcMkK&+(cI2jbah>$|~GrYTP@^cCCnD5+A!M{r$OVm5ZmoEBEglS5DOIDL3w+x~9C5mRF;A>sZWj#EfRxeC=UI1l#6?&b#Qix)TYT6bpUL3LGOq zYyl29S&eUAiU1;y}{(@@Ps+q#V!_9K0Iv?bz(Ss?vDhF=8qNR*L zLyZg~gOkw5Rv4}?S#cKt(80Gw7utju7|lGR04|N^>K7DV93x(--k5Gmck6SJv;XpM z$GkQ1MU|2_yB`v{@T%j=-3NaBQuknEwKYC$T997jLJ*r`JcOG-yz|P!0;8 zC2p@(D;Dfp<6>G2weAkl=M3bvcW-5E_8Mer&ApKS2sgQLHC#D!r3j|hoSZp{geP`i ze3h(Y{>>?f;3rYTI$7?x(#zS72FaGx1?UvpNJR&?*D9H-0LI))VnIqj={fuhKB+hM zTu%X??lQ(HZN4Wo64>!|=iy&d9cO?1W}B?Jo&F0DQmqb?{erHd42)jG8 z-kMG^N4V6a8I5$=W$nKlb;mRcWHM)2({)Zmy;xs1kz}lgZ^Gwr1x7E4e#agt|AY;k zdu^CTwb*<0AYbz9+HBkX#V09hBO@LC;zzuCRA3vwFX&#Zd-@uWV~MN$jxGl`qIS?C zl5fULc-$5m2tip#amd7Tg!yW6s)F_Wss??TkXAumXQfBRx=lx8jL)|!%#!MDnh}Fo zOg9{Dbh2rG`~$%vuUtJ^@aHhzRdR~96!EKs7pQOZWn*SFONc-o9mU=^f}Qx%7$5mJ zd1yOzD$YFZK)pgs#`KUdo*vC*Cs|sJH_fO29C|1%tb0$;HIGFK(=u;UEhWkUh{9!n zr+y23=%0sAJ&0U#amsr|pm#{H|@I?W#vW>ZRkSfSUh zsuV!<;Lkxj&aZ=DA1U^#VLZU0m2m=8{dCK7P7?{SF_siE6eu-E07c7hT=QNcBU|5k zO0KD-D;D7Qn}Z)Ih%YQJu5s5U?*(|SmKI8lyj}DF;v@+Z{Ca4koW%C8fv7bXs&)_+ znj0f++TS=@RTKPT{PpvvCU>jut7bKf)M=7U@zo?9j$}@hC{MA+5lS>lB$gyj6r+Mf z7ES3q&Qg0Jo|zWAP|083K=`mzin~n80@X+N^xAEY>Qy9BTc}0#UlU}IV_t?1k&&8) z{+Nzrfh(h&onhAHd5dRLMaDx{MO1|I2RoTxJU^%S<7~Fs`aVT5PtYK3$$xBe{ka%> zCj|NBc#I}j|A8P5i#|^zxJgL<@D0tP06`PxaW9`5y~mg@{?npK`o>$BG0rk8RJ+Ys zSN*=b@>*xTCs-rW2aQt~E7ES>}^ zB<0modr?uv?)!yIjI~3Ky3gOem3o-MmnI(r=Hl}2PoCO~?wSKQc@Y6L_FioWreH{R z9)71o)n5uVIvx>`uGC)T6fqA&Eg_sG93rqNGCC{%tfH(kCI z(;r?h8vOywgbAk!H7c%B>0);tYyD1Ky3HhJ;hbJVqNGrJ&NnX8&3);@0C z^%=s+SV;`>xbtjLw}{^`@9di8cA8KS!he*GN>82RZ7y{uXUb){?DPsXQ#-JrglkT$ z&lu6X12bk|m`=3B9k=7Dv*hN(uUyOnte2)TU6-+()hn*m^x9*s3IL3|DNk#+51!e_ z$)o?PG-YrHzKy!bfQxE~I3!LX1BSgtM!_H_GPrtB$u=G6=L}jF6J3>v!~cak9mHPL z5SNZIhuB`j=RS+MEqZk6H`RDnc8sO&Hc>0^UCFs{Kn*W^18#7?`^+s!otzSRwjP>;bI)bXNQ6<2CfN~k}|+_x&p){ zpV`(u!aRqcMco>1DE)A%>UgMnr_>nYb5|U|kPF}T#zhBAjMnqb)NsU_tg53H3yAX; z>dH{;L{poqCX-CWgzTS(TtS(nw6hsHH*&o7X5I3J9TVzHvVGs6rK`_q^%K>C8%bd2 z{iZT~RZ$Z(L#~(8R>+7dqsL_;zL5i0&_D+CrxpJfDjUA<8FWCwZ6!MY{{YmVUJ#n5#PFmCZLg!6b{VHkk!W5wD$%%DSxn zr6KJ4uh(5Dri{L@32Lh@mWj(uGm%k4TG5&m;Ri`J^sk=Qc(eS-kiYQ_706gdoo6u) za3pEzD-$lr=n~GTM=#sUbczT=)Zv5-@jEa1utMJV7p;Nmp1xu=)M8;8F6|qIF4A;$ zIV$yJru_K)!b~QPPw!nhq!|sTy^54glRkATZI$WMj)mR0e^vJh0=tc-wh}zZkV@PloSi+PLAJ)tWB?_#_d(7JBU7W%KiMO zHo=|b>nxn~6wlsj4IqX5-UU`aFkCfn5pcL^`}(ivQ>n8HSF`eHd>H=IL^5C;A|jWi zQxMobLDYtsy{&DDc0Kt|H1+HM|5wd1-MhrgF87tz47A6abIy$%A zwiC8qyglC1cZX`;bH^e3DBQuzLphqEpLXQjG-j}+$=@n|>D1_1Z%Scifeg3M#xv;m z(6uq_VHjMx@l*#7J)L%XuGCkhx0eEqkPuUPk4M&jg`RM!WN68;n0HZl($f`#ncka9 zc?SQclMN7huSg^2L4Ub3m9+T6&2`_; z(1MhWIOqJcv=6|7F)VUvj1YVlK%xJ`c%UGTP zij%9mxzFhv^bI7+yEuOCV3|Yu)BXGbF)9IE zyJw$jszy||=jtiOS(eevvYD5;76WQ<7c(`Qnq1WGk3 z8cfX%W(5X@q_byJJ*-(aK_vAChNwiz2`0!q1A4b=|6XZy6e%Jm>}J%5`?XwHz_l2NkS3wl^x=wgCON)s?VQ` z-pw6GFR7Iw&ic#^U4#u83R8IZ9jK8933Gw%f`&9R>i)hjHaF}>m?8g+Xw-9^drzTn z*56iR2(Eua=S=Kzv%ifby%>Y)U>bY}AdbzXWnd{^)gz5&U1F!55F zh$@j~Oz9ft7VG$L;wW?#s-pBdoyH-v&~(x4^R?vW^ITybLm`IWOO78sr%bZF-hYak z(8;;4#;(2)Z-nVIVUPg3 zJ!M6Px~O$H|EFgN3`E?K4hpZ$Ub=c%+rPy`wut?BcTsDN{7p}Q<*oezu9~qm#@x<8 z*G9D40RNsrMuFkAc(A*P7Ll zpb~3c`C(~G=E8Kv8_%wGbZ#OMA9mVARa(-hJP zob8luT{f6ts8*ZoCK)fZ_Li1mxUHsQB?b$U0>Lit91%kM|7lP1npe_CMW1cltvb7y zk#i0zD1p|rQw0^?MxKs-skl>v`X(J2(8?H@p`nNOKV_NY`Pi}~9!T;i^bIg6i0sD-Awc!9)A;=&{rCAF8^X(xl$wbXube zy%@2dvlNkT%I3_HN_6I;W8kR5oLQI4PJEM8ehq4j*KzQmp5ZpFWAx`Z&F3!>fD(}v zuD%2`<@C3Xd)#;hGbou5imMo2(mq@DdYJ$Y&*a=sOZxuWX`G(BF`6`Z$arIdbJjJX zI0gl0o9F;iwYqXtE_m@VBokoK~#BrBAZdL#c$ihTTgS96!D`{$}cgB4A)HgH8U_3%^xE+PSU zW*AJseRo`-y*`FEz7d3wBGfwX2{OLUKoA)%QV23|-;}ecxD4;CVX;Tp%ZK>bi)wSf zNpDy~?2eKu4L|z)Un=juZ%N`*3bmK!RSbVsr^nVDcCClqpx2*(pE|@oU`s&?%f$V7 z*Wkko++~)_CtzH%-F2q1d?1BwFY}6N4gHBHt`*k(Gp&woMU*V>A_C}|GdEONmyJdfeXlC?p8VQy^D>Ey_fj&0Y z=aP+@R-oBcu~-U2F}_;u1qru>y3A;2=Z7>_fWW1ezoSmiOkSS+=XU#sZJ+d87SmZ` zB8l61jXAR9JLU^TI*W*;R|&s)NepGZL@B=OmN~qVsFX`{dZrl)GgdqtD0Qup>hF$} zqM-UPt%m|Oc^?yB!H+LZ-OBF(0&p9SwLz=q!n zs2rQf1f`3@mY()81y^9j-RY zsQW|-fKqtM1lj*8NE220OA|PG@LIM@&K+x8qh|r5 zoZuBo_6ag9Qw#tWXb7FrS!Y{G!%)f28Uhq(3Ftv5p%-J+&LXXO$eN9R(sO}I+bFED z;5S~h89hS?08~pj{IK*MQiLVFES?$#NN;cnq#_Aact`(yZ(b;D8igCtQj&E2Gyj%v1|`od0v zt7EsLAGFw_a-yi}p}Of@T-m!X-BMlvIO@WZKI=lmpm&I_RS%BY#lh+8L+(4BGMb$_ z*W;{BnbZ4|GzXpxnW9Fq=!8_)l1N6e_$r?h3%$P4Dc0myE5lf0VX}jP4^lT50{Xda zs-J%p)?;SN#_lun@G=#~y=8}Vd^VH@w10%(mI|Jk1^sD-rtA3Rok{=dZk%D^g?)bp zYY+uJkh7;w<@rkcD0P)h<(XptnPVWu3>><|S|sCqOdZw2ZP)Q}ES)-vPz*V(%ORC4 z920Y$RBI3%C9Z$JDbUQvluCY9>E+ufULQQa+sH-|ejF2BM24b9aaJl61W`QL&ws3z@I*xr2XeiZ zN{Q+WL`aKtPH6|fI!@~OfXDo}04Wz!UYOOWBsO^A>VW8$$*%3N(HJlpyj8O2o|5md zY-6=$aOwTK*bj}lZvRBAFi8bt zal2&Jtk;12l7$`=CIezJ0C}5aB{9SdWwMP^-n3$r8bfqw@Y8?gIc*te3avftE}idh z=Nhe;Fu9w1fX{VMHYRdT2)-fKceuu&b(Wk{gNRv|{rzgwc;YX%mPY zl5DItfGcpuOYzNW31Gka?1xW?@ePw|d7yM^rEjkZ)7j5ZU0@!Wg?EB5tspUuqSDpE z=_f$LF+@F!$kdo?`gtW}TcftYdNMsoL%@zxpVyBQzWbl_XL*Rkv-sWC_v>^f_+_{F zbA#*Qcwn!;ba@XRK8_nzGM+5h)W6q)%N)D{G=!2W&6_$hK^a9v4eLq`-<9Hj5+7ll|r2bTwVbJoACoF+MWs!YkKiD0mB#U|Xe@P>O1(zHa_*+NGB)wu+ z8470lHjk~K5=3fP!-iplQi@&VZBNd;CkydAvF&L7nBe87S~Ii3hNB3qaTR&%t{I+eDxN}=RqR?=bDz*(@}cAzD}AjMiMYKY)=23bGsB20! z3q?=yDm)J70^*g#CiAgqfP~9Bi`0bhU1o&|XHCC&2>#PPtySuswM?46CyEAcC~vAL zt-o+XBt4i$FmEp29K4vIOdqS^&A$9Ss--Yg58oQTSHT^3&S4n!9{`4uA zx3b|hqasYCnpLI{<1CQ^bI#yBSH7P=EQB)92~HGZZb0H0k`XO^-i|n5(n-_k>w=4w zZDh}%lZl&tEnX8ET}LMHuSDof-7H0h%_0q|gA@!kjiWV@Ndd~N*Y3IhSZ=R+CVTr5 z5h`q1{J0?AEVO_bEDql_Tk&lJ0p5Ic^LA;A?T&@bkve|rz-X15Jygjn=C+Bj&N#>( zei(aNjY%p~xpOKMP`)p(AbC??GEkD&_1EDRggkiVaMyu=r)KigP5eS`S1cM;{t;qLhRQ`l`UFUbau!)f7 zh;n!^j5}G$ag5l{;@=;^l}_N{x&M~Z``>SN92_+{&Y%&Ue=Mm%fqx259t2dIpf>JO zqWP!9Ql2<|um5jno}%ic?+!3Cn4$xF1%N%yHGEUZ7qXaSDxw#%+(F8%I+-4ys3xR( z^y66X0?o4;oS#r{uzcUGeMkhrMmk;1Y91#^{q^GhwVV4Kz%fqD@&|&(yu#%yUDNTf z=G0FS?(91QmrAW?gY@`&g!03`FwwNQA&>{JnU@2JO?RMP%<#lzl8MZRC~#mjd$THT!1f)+-f2TQr{_+pIZbZE%#WQ5w)Gq&hvcfa~OX; zYc{jUPw#Q}&>eCZKJ=Qo?>BT0F*?2V$?Y8vj$^6(&xFUl2FKn(J$0{eVE(CA1Z!n1XG{9Ul8a^gB z{4HsFI~Tk$L|dp_k@URQKR$4H0Y36klEpvKpiZSEc&POtn>=}hWi~@Gig%?Y!5}tl zEp%*ph)wh zS~=hG{+AGj9dROyF2sz#3l2yFtsz5qNTw2mkVnE?|e5yog)UyKBWDmLQ$_P>Uct!2U20R@GSyhB+P@d%YFJ0|a^WqJ}`Q zQys>*QT{{qM@|hl;Ni27ZyL4SO48-_Obp;ugaOy~@mm3g61(Sv{`wwS%0!#$_Ds`D ziK1lsRi*>OgdfgKKR$RS@Z12(#2xbSmwyGDasmof{KAH~`p8E7eMJ#c4{FTny96GX7JqW zzCALqm$O=En?VC=BmpbHnAL?L6plZZh9nC(Bv!sgBnMahb^2LWD_34rMvuY`JKAVY zKlK(1`*+}gTCCAS+TUz(Vsd{yvA*=bab`%(kS)~Mw@;pqIs9rLIwPolo;4ecqbR8C z^!1|xnbXwhsZ#nsw-(W@xh)S~o-$uR=L2RQv!HhvGvGRps`o8e%J>V1T`*d#I64Zs zcoUS&%llIMSNS$@F7R${zt}kZEU8uszBfsK=sQj;$4lq(ZJ77gH^_PbF0jSPC8O9> zUACGw68d(9#+=sB9*_jcNx)^Z%Ae0q4jZ9-d#x8eZZ}|^Z~kmg&Wx7-(JhQ~B%1{0 z8QJCi{aupB41NM9MIgU4*+D_7aFk3Q6F!jc)qn-afQKLx{9{mfC&|^BOkAj1d#4M8 zHGw@ZVKTelg!4gIh{H(t?fB8S$jM|`w1!*Q){Mo}@wb6C4--$@1{o}qh-5^>Z}Bbp z=b(T;bAtc*E6Fx>Sr$)ab8|&4XFE1Z#ERBT2WhxuC$Q`Nn%Tu(d%@FZfdyxafip)G z-y7?op7%UMC{_#k)D<*4`h4k4X|qM8S!3=)2&XcVwy6rQlXx7VEDO7|EVfhha-xP1 zK{^I01Ys`J%(s^ozK779z)i3+BwLaY)l`1+`Kh^%Bbl`B@1Cz*Nz=*GBQib9mmmX% zp@?ytQFZ?%;GlU&Md|k&6TzUW|I`^!iYKwpzY#?OCNm#Aook&5m9&&l)*K;LJ>ssp z{lPjjb-&bIKFf{5%Wye)&b=Kh9z9O-#>gN8F#vaFUFF__)_qZ7t?!7I-TrlI+ACMg zB-YB%!iRJ0?DPNVym07G3WX);cYdDH#4wfje6p zt{1X}5w>vw4I4xuQ(eg<%@&kHykU1A?9l)Rqm? z2=y6%g1o{DRB4Wgvl%zXAiTEMOH=SU=D%db81f0cL8hq-mNea#jG!#YHl3}e6U!3G z;w2py-8KRA;sdfBAsPb-qE$JH=G?x2;q_h+BEkJ1zQ2i8M-P^_5KaUmrWCS2ave%{ zmR}wdXNi*&;mag4(349n!!=ilKZLD`7^2MoFX zyvXB@-2K0vU+ec@!DD6cAx>3ha+59ifzx8of{t6du(cyx@ImXOx_xEDla5$-soTsv z78PHiXLrc|zqgwhWbnC6HrJkD&v`mQT?oK2O91k!Zzt7RrvS4QzfP$HM9VG?Vb|y& zW17|n18wXNH^YHYq`hQ0AzQ{<(RyG(uL~pBD?-{wD;L9nydl%N&Eh9(y&vU`x--RR z*gK9qi`QGu!iSET<#fBW_{Fs0=pu#fj^J59IQ=EcYh$&K0{#dl_+6B{U$sVYm5kiZd^}f7PXM__XORU} zNbh-*_-OC@bpg0W*lDOv9_;MyVm(b3vbOAIHPH&iK_C_ywq__29A(MErY@lrW7#_i zIUUd#A}|6^$9kK>r-puWH&-VL8?qF_YuxjOT{|)qkr`-wWb=-F@HB== z@R(Vo2gP4_5I`-3pIZlu4$@5&=f?SjW;dNg&CUaB zCu71Jse&zKeA=wZDj2S;Q5(JZH37;1j;dnFqM*vUE2$HF&!rp^4SSt|$813~!0;>| zu2Pd#QLo_4UoT2#Dqpl9ufJP7?hHT6ryvL&N>|l_?;Aztl3QIKR0i&=O|Hpgjus$8 zDJtw~671NYR9~ff&0qY%=`Ek4$=y@gbnZWZ6Lr$e`-Gu_S=9axo*kN*;Xu9@2c*i#JrmV=00P;kj z8Qztju87ol@sC4e;1h9Q?7Q}^B9!}OYbM~eaU=QGBW1a@AGQ;}-w355Vll2;GVG22 z*oRgVYwnwLlzmK_4IQ>_PjXx>+TD~3+`*K5=g#hc|z7c!cgDKJfqV%W*+gP@gd@;F9mJbg^)h2E$1OEaxF zgWxOCKX!=@p>qVH)Jcc0fZ8OTIo?IkhD`1aEzZIO+=yVDR!R25$BsUrF*UVbxyeRq zbj8TY)xx*wkC#__Xd=%g>EQ);cX?*8SHu}V(thdpOi0|CuX7CIV;-B^oK3!NUF4`{r#>|L0p)3Xk`LR6h@XXz;?MVhg<(!9ddeB1 z5AJ`rTBOvi3C$$&rCIZev@~N=1SyJ!b<)>3a>GDcM@7GZkE{pzOoDjM^ONw52QfF9 zbsnH9P&%NR9vc2%)LCG-C{5tPd-emM)!87Vn;hjvs+bx8!hM=0!F>YyC2aofLFXXk zRK?nnC|?MV11!1>$dimv6Y=lOk|zR(pqQ))g#QW=6GQ>fC7&Q`$p|IKJ$+_yjeAfX zHvXmD^9m-j?A+*A=Gt*!*YolsE$zSsHzcxm*m$LCVQfptYskG*L%@+KP5^>vkkM)} zE)J<`ZBvck-;{^mEV+`SHELgb8!*ILO&EK*dfF;G0z~4nvL$hw4+P(LUipawcRt>y zd3e*FRQ+Ar!E>KjYRr|RWb?g0;mwb#7?=)b*Ok($*X6VPB~(}>o8^&IF4@>QKf!;8 zLRz^EX=csW7PKIU^fa7O-*oF&ttxkYiDWM&t8SCa9!jBquL zAYKu;hPl(vBCcyOq-mndWhZq`AQ9Gc7TJ5Su zV_4~TN7DuwdKQt8EwH(B(A5_gba^8=s|axg^UN^Aa}BrlmZz$g<{Od!QBG9t4Y+et zzx{7$fwH_jtP_|t{`hlPfWd08T-~3nip3 z6<&&&Z|n%tS)(d!V9Xv>`xb;R4Bin1C22HVvSa}wFC&Yn^^=ygAWx`qYH2(^70$dA zg$10XlVVmv;reZ_s;0*=m=F1;o%zJhsd_ZP z$!zpckQ|n)OV9oOITU@b2#?MDv?S5@kkj+oHqByUJxza7_US|n?3>8nT~b#`S-TUx zXdn2~YD>EC;IM|7aSi3(0Dzava*WA?EONwNR|S4m>zYj$v$b5MDtsj>5J;)N-Dn`0 zZQy#)2ji8_hd7jVagpdV0BYi_zi;4qqg#(Vk;=#Itg${jL2%z`}_Kr`+;|{Xi0Z zN_E&-Iis}*I+E_;KBsCFeFK@QtlY6iU2%9gqe!3tUS9lC-jY>X`(D6|Es4iRg6JV% z9&v7p&Lp5BXrW&N4jJc8i@%K2XVW0U8eChXz0|KO3tqDv)@-BjvW@X=^DcN7`t*8& zqy_aruj6qp-Q}-kvg&C^?JVr8+TScSEOGUVH0yxu5> z2@VTow}6#gzKs1)Ws=c-Dk1J3SHedB!>vPY4RyRr3dm%ANM0;@Jmv8lN!JSwrjfkTIr< zVq_P*T)h6J-^*J9lL|H_7IeP5i@MN!!%BHNh@t9c3+rbOzZf1eyNuvM|85CXKva6*l2 zG&aWglQ5#&zdX?jBxpWCLG0QuNxfh5B(S$mukyJ@;37n3Y(e6;6v46&hm5|=OWpHP z=F*3V;f*>cct%?r1Ys7%jUvbuNl<*)`5IzEVU6WYf{nu+gOdKZS&4reF(U8`6o&>1 z*>)$scr4ft@_O+Z$@e(wHdR1;QCNu-K?IJu>3?d96nz(PwkYa_9r+drE!&P&(M@Owx3Z=>+yAvYbvtUWk%*ZHILkzoc$Mj2#$r_JY^j|r>V^}WUp;uOpg_ZQY4D; z7>ZLrIwJaDAVicjB`@VyutcDV%l1xhu3f2!$7z>q|Wb@qs#m9SMX ze%{>lcSYP$j=<)bDk)C)%Q`hyNF**iI01`)u;P|p$7#7pH`eBRL4K~kVEedhiyj6u zh`eMrl$(Fq2(M9iUXtIGCwVhUg7zDTuJnMpLEs?wlaDQx#m;g*+uhsdi@$w&DBMlMSw01uK`ud3nihG zrPHlyNw+u;W!+SJC&ny|TIci(X2Jd$ZtpmO1CskHViS_sSW7~+b3%61$vwiLclf%g zk;%t_=XYoBMfOpEml{1^*RzMgphx132LA!_Fu^a7@Ma%B5F@WHsHWf+{y+oo`%#i* zYxx@-kCBwsM~kmItLir;sAMak3uodHUo(sj;a#(?^~)bdz_<*Az;P32pl-c2M>VLm z<7kSe5HAmCTeVWgA8dEbeeU5o9Ktif$If?=nl{1x`|DZ9=WLu$yg*^EMFUcig#32&_e&$$QM`E$kFx@STZ z9Ys2{vB`XTk2Mnb$*q8u^kthq@~Fx@hnhQUmGQm@EjqS8B{PrS z`F!-D%)=X9R+R9jV%-~7E4u>FcOh|Sw(qNVQSYpKi>_i%vI%Ykw{`tV5lh0|F{eDw zrU)l#@^T~sHg91N1^mnyH?8n!q&|tsJ^WnM;l|oXG@$_Y&F40`GkBI^*k0Gk` zw6$0&t&S!t(bqd7Pe0Sb9xGD>Ij;;>L05~z+l!wXvzpWJPH>Y=23}wcNPvE4abdT! zHgZw!%a^+>@hse;C&F|fn*oZO)+~xfL$%AO$K|i=qpo5ORdYBe-6D@kro6msJ}II1 zd;Wf6zj>;N?D?1Opaztf!{taeR;ALJzY}LCSWYa-N=CgBzA3BIR&g118M4&eX3Ih3k7^o-LMUxVAt^GjdMEFsv_FHu=14Zf@`_m(;`}&E12LL6pbvFq;&yf_? zQYah@{qq%&$AcB|xJ91|Xt1EXI=lQ6#*O~o&Ku#i)gX(J$-+k~q*DvMaZKQ%+GlU| zZVeeN%05)&pUS)`n-{;-gUR|%e~|s4A%G9_#tr5rxZVA14K+ELpSkD4oWz9AS4~?C z+ap2$nxM~etJ_(>XZ}jj)e#{6tdLHYPIfC^U_5xJon$qsb`6C;82yh$Va{{5(|`XT zm_nO?Tx7X}OKnqS{$=8m3AK&{Y#H>)#CG>h@b;8Nz_1Q=nB=&7#X?nEIS!>FPgU*pvLYMz)8#yo?#y8v42HN>P+BrRWSq zCZQ?273rjT?c#*>ClK7e{~tX^5$)3*tz9*mKt%C6cEvJ}2zRw;E#b-Z0~cT{EvzJm zr($8K;Eq2N23u~&(IIDx&k5!5?HFTLo%~I{rO!fC~iJa~wcO~hb0>?DX*x~}URHNOI)~5E) zY2XPL{Ch@f%c_5G)wL%x!pBjv^W0D8HHySFmd{_;e58~Yo$ zzRtlopfJ<``PVCTnp_yYKP_oa27UCVo;p1-C#kP!>u*B`UXq!vu1yX$T7Vol78iyr zr3v_j?3ysgIlBO$gy^~#(N~W|V`1G>8~ri9f2-wOo92YK^Ivd1Cn_&avsW2rqrzX6 zupFwz1U?{+hiT>WD}8G_wrKsPYy6f>FxaAQ2A`Ivw&E|rp*_sy_TD+Wg}X{3PMGDz z$21x8rXe@%OiHayDB+vh8Nawc@=r;QAM)Y8h=xYA_kP#C30wG>?POYi{qZNqfhu9t z&>|U*_9vy+Iz0QV!N%qo?seiF=1oPzG0BJZ%4Jb0&)}E2Q$$dgucLRwa72(dd1l{- zd>#o|{XKyGfv9zZR+_&({qnU7@_>8n)P{p7CuKkxC2Tt!^ly$fH&N8O88^ry*O zgn_0lBMSRz7)Q(CBqMq!1$l^Z%>{K(uIASzH9Npy8$U9a91M9o<{|Zv71W%~)!cO0 zev#$W#6$goDH^eYsYg}~c9C1kI{G;j=#l6Nz2yG5BdZ<$!Xkf&IY$~FFr-ciSVL2| zem8ww-@ehdBOsR8Rls*?`1f7&K|NivQGy;i9Stm1U}^&z(%12O2r(#q=NO+fr6LOX zUwdC359R*$pGsvUkyMgKjWx#Xi;$9Ktl7$vO4gC3#!^aqCm~@l*(+PA6hf(!HbSKA zOWRR{lcj|gr21X=U`8F!^ZWju*Yn5o{9fOAomb~}-Jj*UuFv~g@0+>Rc>xnDPOu1r zqW|py3;(%*E>C*at!2k zy*3O7so*V2->6ym`sOulq8B;0 z;aeWUIljx?4;bMM=2f+_Wlu%7g>+}N9L>6)xxf!Xr_W3iy^3lzwS|Y$UCvcrY|B=5 zrQ{AS9C?_KSekHqpZ9`IEibEn8ZJ(}>1QZG^S`{Msq2MHNO8(8$(QT3r1OiG?0Efr zqjPH>8e<#d-a@N z&UTKIZLnupbggj9D0=k1r4+ntDq1-@ucW&x@lxI)jL%F+q9_^*V~=D8n~BC&t5%iR znHERurqYn-PRB08!EY9h`Dxr;UvTJHmZ-m%Zt zOs*6sUEwC@^HLedFVdWwFSaa`R6U%zpaT9F7Uw~=Ve^}Ec3|L+gaz=f5-ECQp`1(h z+U!FmUu*MapDI?!qeP=*cdXrwvh?0lH&-dL_q>S1-r(G;=6P*y>UD}+bt7V5`PrB{ z8^*{ujP$PAJAbXjQu&LX+=xiU#$`+Q70q@?*du{N zYfZF=9-Ds0iJW|B)v?|rg40#ElA{ghz5pj${jNgTw6aq_ik2~Z(}PC%U(M>CL~&+8)ljH;pN~@TGRCVze|XZhA(B)-l=D zx))~U-`qC1a@yh#f5&X?aAB{!^RiKS8t$H$_lJ$93STaY*(BD@ol$8MVUT24`Dg79 z-z^?4efklFpI1o-1dpy-s>`cIn+`C4BqK)hy(s zrI5(Nn}Kl$+eGB`-OGf|cdTD#l4oc>zv-01G@7*BnanM{a*Ye5HwMo(P^`Rhq(Kd5 z^)T|lk*61SuI&^#X0Yo?o3Q4k7=0J%kj)7fXnNohOxq{!AEHm5FSgoO#Y{FBwJa(SYHbT-CC;2NvR-YGj$1P3 zON)uq?4{i7vg->Y{XcB3(MFv)J3%GKIYA;In9aB(@NmLPc@Y=TI z_QL*UqaiYmr9)kzky*1(EqB>tb|}Taj$m1vJuGS{eyZx|`N~GpeVMFf?`4_mUBeSA z8|zkuMar5?&uMj&D|>P!;GHw$jq1bpayj2(-{tQ+TXw8mc1d<$|BBA*vikEbo9LHM zJD0+`+kDt^{W4E()Q+f)heu9_e0lt0q@}!f)Bb6RLYVM@O>++yJJ(LDS1C>YA=5On zL!#a(*>m3yPuVTaA$~4%8A1iwl;dAfd%Bns3%5iy*;T2bRU+YWH6@-g8ew}p3peeQ zNBwq15sN!4R-uf+CDksH@EuF+Lxq^?9VHH^lxs!CUl*g@kix*a@RO;kgKAfWbRuEv zNH6KOXGzGL?rB)HgE6PKqh=UXzkD&eD@Md5^u)sH*)yCOM$$wjHDOb0R^PpoBu|V@ z>Ap|Km0GBU!spu+X{jqzkIBkys0+MxfY|+G7f$rbF7Aeqh!oelrHMXG8}2WjXKQ{A zegQoB!;F_k8SQgS&efqC%AfDw+h05!_@l(AgDR;n-@}P8=oAXOvIdLRe)9B$?%F4j zdwzVrRDn7Kg%j**UoO^6k7OzwH4r+C=)%)y-)_jj)}r_T^cUbh)@ z;PuQk=rYMWT;usoj~I{J@-Ln=J9T6C`x{gxx7=ix(gzayP1!gpQA{@~JiFe2t)Iax z3ain5^C8H!+jQ)bx-}=toyF;@aFXDlJ!Q*zAl!x4(n!0l4=X|D=&C@2e-@J zk~KSIurXskxm;3&U5l!zBHcQQl)g-ImZYxCsdQ(TuHig>yO{s1j zmtCb z{A|`pGPPr8{JDVoX8_7h(Lr-V3U^L%`^OikguPamj~M3_1k5nnS^BMTOI-KQ$2a#~ z<+K&O$sKu2m6zhG>q;A)dU0!?%@>>d%UbOU>@O0cA25~~p>4!-mV_RYT{9 z4da;Arf+wxqFNIo-oILCn6b)RhHHH`MlQK1uq~0}Cbd1e==6PY)a4x55hzKI)g*(vyrb z$whCwZ&ed;f|69(=#|%a!tp&yBS${6SHHx5_?AWJTZ?kj??oka!U9U*tGv3)2^AM* z%5-OIO2|cTkoo>;XwQQg9`!eqZw;Q; zV!qC)j=#N6w_M$<1|SAExV3Uh8>R%~uwL|(2+$xo#PD8gS* zzB{pom+y{iu8Ue}ed(SgM&gR;VR~3%&OL&nn5#^# zibhb_o|P$aNhS_=gzIuw8}_Iu9mE#Hc|JY9u)oIK@)1bv%SM|MuD*Yz_HHr zfiw?=bz7ch7n-lSUKrTB*~rER@yb+<9fQ07u)z*(~CI``%X z142$m=6cM|@O2^E7Na!tJP(^;QXb_b1nHyIucW7)Y&?7Fb(Gam!Hi;@--POJR4F!Vm9v^9GY#ug;IBtQZm7zq%x4v zHOFFZ#pY^&q-1CGU1@~TIkxU5!%9vjzdM$ z^gbkW*ZXn;tu~j;QVz1g95qnRKL5k_os%~_-fPXs3wxNh`BsDa^}Y{h7YFx=LLINo zN1pm=*%rZX)Qp_mbiWweOunX0SHDiFdOXiT?1)0Jj=J3nGa);+XX5M#(+y2B5(_yq zt84boNgOD;`p|e2)}|+8`1(LEUVcIP3H_BiMK?a2IDEKfv>dCm%$!yiaydd`&BrA| zDsf$I#%IHqOAh>$!D7+}`uhiBk7`SbFmT@VW%SjZ#z_X{S~E#ECDq>MEdN89RLYg! z>@4(lMb6>To>%THGfyeG&^v>Kt{kDvn34xC%&S)p?0L~L6Qk0myw`pKef=~Qbk$2n zdt6}&WtsQe5LFxL6yy0LkxETL<)xTcN2!XNnv}8MlrB)(HdZi_WLYhPo^K7~6$yC8 zqiCf>`3b*kf8vLc&`1v>07IU;0j(*^5AN^{5l6ESI+Zn+W%{h~jAAfE2#kkfX+#6@s zy(`Zn^#Xj&@DDVMFUiy}71) z1F{P1G$rMpvEi@X%G$oE*MefF@h&dql>xk!Hcu=^dsE=Ww*|wwfo*sj3dgrZ*yO%z zc(4vZe6}+?F6Qd~ss#^SS4>Oy{SgrwRdf5<5}k#K+ufaqo4Zz_dRyQNI{I51mhYKS z@RyCn1IB_BRau+6d9mpB0>V+O2PS?H&j~55Dc0Ps*Zc_nsI#U^>h#d2wXvd=%j_5J zUo7==$dc>sbnQarO<~r}8n*$xuc9t*pVEGq>(|-^mhS03<2K6(zftJoY!l61ttWen zX_!QHQn41PXO7CxBfHBw&&H5^*F^?hoz>qs&+%2fw2p`5fdeLtPU|GUn*A-aj94Gl zs&-lX!ngby`#yi%zIvXF00|PMzYTBmdIEEY{#b~AZO4HEjfu{J`CKrpV4?Bt} zEl>P1P-o{VIZMXe=xNb|iWLhTY@SJ$waOilOrPTrccYB?EHCoZKz^3&7S8+&toqfl zgTeq&auoC(Qnx{T-R^$b0a24{$P@mQ}Ov8q2ieg3z5hJUp9ST zbb@^7QyfiArf6Prv%TS>X6Aa`he1buFPbp}q&riTFpi&x)s@aikKC%+wz>otrm*N$ z_(6P72{uwFR%<3ITHifybLYn|SH*X=y0ck*YTK;jerSq)-=|k|nNn{Txb5;|&Kx3} znNqbyd&nrm@70Kr_AGCCp&8_(29=#%nR#n1^EE5ObS!U+dN=zF#m?6`jf$LW9h@2w z7^Hl2?xV)OXEFN%iXyjHJfugyYfs%?aClVvXyP^9y=yaOl~-o0B+V+f_x^O(QSQJc zOYci}|H!FoZaS50=kVzIE{b}H!D@xOAv#qh@P+6({RNo(D{W7tdZlS?Z!r> zcXrHI9Gr109NWO0dLI^ZZv#7dNZL1mtt$Q8ja}7t!Z5T}~;PZ%~x|*}~Eo#2AO-WWGr;b%jQyKgt=Anl3jf1NzL609MZIH=l1w zu(pntJRcH1^97H*Q#u4U_{3J-=os+z3wHl3ZhPO1D{ zw9&g&$|!c^cI=)#D#SWvT*141LygP&B29aJ)56$3bw=;Bm3snu76qKn(DJa@#xAa0 z6Sei0!=gvU*dWoMWZ&28lZL$=hH50ub)MLC`#UJrcJ6BPXPnY%?!U39_`SLLj-`m{^tTNj7n5^*bev4;T%t_iAN0z3_tBAVxE7i&D>a@n)u0~bz*O?9d4P4WK z6I%HtR%!DNYN@U?tz+9J6mKyJ9OGWCg-qs6Hz?YL_9dPWbUOz zni_LuQVOR`Eu_dv&GjMET3Ypt$3-H?e(t6lAC|vKl;o^kwBy?4XMVkjg<1*& z&mDq_oUUlSJovYgXlQN;zE*E2*-a%Y&B&8lR`#Kkq1Mk=h0ySrr8{q85u!N$y} zPT7H-aT|`;)P;rH_E3{kidC$)UCXtx4U&?{SQ6UzVTYE9#%%k~#~!L3ioI2-X@O^a zj?jMaG-k`r)N)rjqZdwVR^3vb&-sjVM9p^i9wy0l$DzJ&d*1H87lu^4SCqaZC#@!F zqFv9M^<>z285Y@mUiw;d6SrO02#t{~z&5#E&vhK?HvL-_H?O%NL!!Lc_uioWl^%(< z)#(=&o1`!{kd9a#zlNPRe7QoWC~lTRWy_IF^@_p^HRhrY(eE>V6b}RUm&ziS>z%x` z-NfpdIrgPuuiOi@lHEF0T=C+Wl@Hn5-qr=Pb8Q!XD6D$m$2<`b(y3h2vL!RbMD)0Y zs!o-Sk)w6eRh+K-v2avcnZ&xs&mEOl#8=#3IPGuN7V|cPQ*(v;!>vP~?!2-5m_yau zYutb=HHjOC#On>;AJEvO|M;BF-n_s?@5=Wlwk2Bcn^(JZURC6x@aZnD_{aNIuE`u8 zwUiZCiYh$N!lxFsC-cR=O@mLveh#14)F_V`N!}Ig zeLm|+0{8m!x0{2d-o0@A5vb;LC@bOZxlW7d17&+N(=fq*rl|+FbE~cw8ksCr`Ld2N zSX3Iaa<-Rsq=^H3Ne(&13vE<5v>`FGJ^ODPvZ#A&GLqMu^gIF_j*Fe0AowjI;mnLwj!2BrvlB}a@~BF%)>HCk2ZgR}ni1xIB>aJkp?#=O{oN*4 z$Bduf`VR$b9n;P|@4kv#puUBERrJt`l7vcIl|G6|$u>8yvcI!Onk!Syt!z#5O)lA4 zgr!{S6p}o@T;)#cQLpLssQhHBq8O?3x34kl9Lj=TSA^i%tImZerG8hfFjKdGJ)}DO zKt!9~U)u<(8?;yTbQ^?7r?a%0=EOEFsbkqX80MMYvLVKFeS2>$`sgabtZTsFGi zar)Cew$H$Y6uWG?dOL5_dcVe@{Mt&z^KquNUylzwI83$n>5*L)Zhaaf_eJ=IWRU&O z?C(2MZ4W)4Zni4Ya&4;>+9B)s#&_4(rH2~y7n)Ul{*dx5@jBZ&x!NnI_YuQ zxy;CPy!YOSb9yaj=PMZA#u;bCe!Z`@P+Vf&`ireef4=Fq>3zIa(}dow5bJ*GflBFO zyHz%QnbE{YpXfI?9sKHi!>%R!cc^j|&{m#}gD;Yf>q zQ%*axSJ^5p^^%3q8uM!5!s#9NES!{_>)k>h?xh={Oj#{l&79@>)r0a;CnB$$-&w7= zy2{`D!gFqi_&}kd8MMM37;lW-ib6^;wTp0{|A==x?r1j>S7Z{t^{a_w@4mf?a&?(~ z4&sm8>CjvIbeAk-W=3RGNfVTqTPZ$LItP!KPV?ML&iov|DO(M`6_XO9_tAFW#%{tw z7q{Xe7R~IIrnTd6ibs5eN>gWfmr}LX-oi)G$qHXvo14saZ<3#6W^pxQyBV|Hw7;y? zadS+SR$%(BwslctHfwmZg4yq@PCs|rk=B(Q;%G@vlN5`(G+ptti>pYa&*12eXhxFW zt$-Q%nYWFqIxl*QL}%vS&K|A*d_W=Y=N$u=Y1K6C_zNnMYl=^$&LnOw7g{=}+LEm| zir$pGOZ6IcW$gtWr-=8{H_ttzcg26hxyr8WAJ`wG=53tNog<_;`YH>JHA9_+Su+l3 zchJ7<{XsNi#yoj?!6uusz2d5dFDv*8K4+Qx&PO`QBQLAV+P#A}baNS!O5%AT;;+!e z;!LMLH>iiJP7q1y85`Zm`NeW^yY*%sEjhVvwbkKcM-xj@)+yw^^fu`)YrY~|dv&js z$FS_{8ilzxPj39?bz-Ai#fOCaLMNg~nD3o(!|eOHf!g`fijE(b*@-7cYJJDI#XYha z9G0<`!JG9(q<+%32rSA^y^(AF=BpAW>h%H}@tv&72btW$=A)$bCQHrgkEo%;#7*kU ze3t23H#mB@`=l*!C{yN~olZ1+(p{*p_ECwvN4-xXwB6ZBDQDyN9`Da>Z5y!#G;Ur= z4Ex#xcGi3&+hnuOwF%wUPlGJ~vY;L9{*?o^P-ORsInFs-QFJyuKaK*QqC zZ$2;SFDd+>Vq#sN{WHh<#X9b@gZc5tHERD>f1^QjJI-CJpJFMO___^X;(*PTYh}e5PjO;^{U>Rx3}z?m3u8B>#3c&sJD-h!6@VO#=u6^1BKz- zXfsYb{r1UCE-xfHHFwxnWOYf8oN^y}{0H@sWp78mTCYl#ym+2Ml~PV_;fF*8v9+J+ z*G!1tTa4aylG-xudm`U<7=dhNM=p2@Urvzzj+ zee~QH{#2EnQ`_3!7s8>rZu%yx@aF2uc~#!eyv_u^{JXE*bW`O=YlD)Q-nVF*#Z6*c zE*P#A!vw3P*;JJvS7}{rLD*7m7WBcj5RoUep_oq3pEc{dE3=I?d>h-FIdGZ&j_Q|na ztsSW?hoWN}Y!~-cFJ?V@WO;enqM9i6Gpya6HyTRVcWOil7CY>V{T&7ae61a72n{P3 zZ(82#vXb>)KPgT#YcP-MU4S_lTTD|D!>g1xcV~q${0%!RxpSK>v$&@-aJ!6B%VP9o zy&if#Vg`yiGoSZO@56YU%q8SsIj81yVR0oh?3nqB0%i;5iSreyoo}+fEPC@`b1@Y3 z@6s`1K@yDi-a8?zWZ%Z~)*b=yUPNHeNn!PZ-72A}m(JW7HA_0a=6YSitH{>mkor%t z2hSZ7OG3-fOE>qqw4ZAlU~7KbxRx%Q|E>@7a%n1D#>CHt)xy zqQrgXS1Ou{hq&00Esfh^eHh*(`k(lZi%K85Ub9Z~C_M4Lq3X+91E24w6n1N8F*B}x zt5Xjd(uvpfHy(WarH%0-$h-OngZVVDPb2m=-Bo*z4JAx3_2|vuT1=u(PU>4ioU3E) zx<&4%^KUGEHnTQYv@6f@Mf;MrOQ!TE@I{a{R!W`U=(}J3sXp|?TFK(UpmI6p=&U!F zu04EgVVpLoRDC5;IzD3=dH1t}dZ?5JC3l@motni(0i|XsC5?lA)oa5UbAQ@gIjET9 zqn$=%JRI6)5$m&;qB5+d9h+TIZ*k&t`P{J8Ok*eAj1*({?!v5~R?Ae{i%(J>_BVpA zpE$N6bfw{TA9(ePt7EBR9a(p>;_((;cQkc4kEBmnr~(o3WrvQ^?BV%wT!4|_pPYKB)db70jECx*3Yf~S-Fga5oy46KycbeuIynKf}qmMq%wx$@v z!3?K%olpDrB>N>bK9R2TBSN!E{*_LA-2OD37VbHdQo}WrGamKK>~y`3!MCS7*CZ!h zf!82Ows+;}jq|>RAH*o#gd*T%hUq5@WQRYtY(bWeOieP#-_v5|+Q|RN#x)(4? zbJ%~H_g{~vS*`Y2{qc;Mb*WzuX}2fmxR@cMzb3bA_`3CJBgREiNUE6%4)bpC)ADGo z5%d0Tq6r~(Pdx25**c^$vtMV9D&qLVl=E)vb9tDzyuZ>cIlUO6w z?h3y3Ig7pq?@i0>R@SoETJtHTru(fZJ1t{KOWWc}!{8_0|K9tK{M*^oC za&z`UuKc!pp-?0=4vk!((PR>tPT|wZe7cqvTEixIryE+s&SJZln@a$Ug^z}*o3BTJ zCz=8St;R`)EWf~=Xu$;D&tvy^bH*lG!+I=dU;mxXEH_`5;9u_R=!`FA9k@M!x2b_2 z%V(Ts<>v1f$Z~P>=WQ|0&Db>_O+@%W>*&CL_z+n9`{vzQ(D1 zBBH=??b0S`*UH(~!)>xa__YXzPwW>{i2qA zXiy`8PNorwXtzBso@oA#yq|Db8kI;OFN6I9x5G?4 z4NoLe(LRCRf=!)WU8h(a84qVepb<%=u?c*>{^JR-0U&`!9N%D^bN=}KXr?gLz#P~6`{0yPvAq~u<`gQ0Ydnn2#|j}kg0v( zf0F~jBZoeL5P=85BO!8(JlTaH8NfsU{~r@#{_R4h43Yql{}vY_$ofFh{1AixHR&5q zLGY6^ynh1(0vkUD{Qnzc1R3ft0QogX{VfAW(1T>)ijFg zO$wGorV&U)G@S(F2~;W(J)Y3=65n6{(XjS(c4hi{@OIMk^JBUCPfm&XSt~#H<5T|Q zH7D<&q37rA$Fkn(>;iSudbc2^i<_0xne;DY^^=EB zaXq0BUd~VW*BFHphB6ZL7QhO08~f*9(9Q%vreb0o2LBf(#>>(1!Bg`()h-$vh`%CB z9Z$s4h-4fjk?I628Bd_oC}{r0q`PH#lO+@PZa*w z(MA3hT_ggQ2;jm|(ReBrPr=b}WVE{AQgEAq!(-`05|uziU`QZRVb=e4bn(iOf5Yp) z6JCU0;iV3qhz{u>f+`a98t^m{4b8upbQe#hL1%LkOIR2&(evUSouCZY(L*u7i>UfBR6cQc05`~V%Q^_P639UZqQgEM6!&0DGLM5X~ zL@b#~00;KJ9$&m{_TTuL3=!V-eQSi_IvQ!tPt+T*5CVLE7he=*EX9W+7*D`Mye1Hc z&?`idXjte%mW7aJf`E zjYg)U@i-FHC{!|miUxzi(Z~=a>F_9mMxnt-5?C9RKqjGSBxp<%2_!O12A&9HI-QE9 z60vxAJ_Frq^)cpfV3x28olF6<1QMA*ht4304m(h21Y|A|OM->Le2G*n4Ns**V;Z)F z5{W`Z6CfiYQ0R0#lADmJL<&^-P)dQ}(x8Fvf5E}Z~R3aT% zr9g#62A(0Z(BZie4YFn;o{T5qU@n;s*$ffb7;pZA8mW-r(5WO6k5d{Aq(jpPumc{4 zC!xt$Dl9@nSf&%PbP$}%+m=AZ!4zPCh&*v100+P;G6%MUf3P$fNCo*iNJpUINO+jf zi*GbMjzU6&rqD@rIswj&U%Y{OU@0Qpo-x5z=JCU4hYbUq0%Tg9*1NY3fcq_kh7o@dBK(hdZ$qc6f_mo31i_%c_1Q@ z0Ws()0q279M3Eqh(?Hb(G!Y&x(Ga8o5Md1h9UfrTlc zMIsc+V+sQdkSA{jO(evx^NxxLGD5Q+u!*RaH(5DI8*+LQ*p%OlmJSG)1;tb z77=n>Iy_B5kr0LAAr7F)kZwTB8xBlBqktho-y1lF40wDt$cBT)795rf3iJ-;J{3wW z0Ttw=f%$?0K@$Fa0Zp)vV77oG7nEQp0o5Bc4RCv7G@6P-luQ88z{COYfZIuIg3S@ZTEOsOBwr+N6cON0A(8+e2=fFQ9jZqG4Pl;6 z2ID~sW((LvFgdZ4fa(ogj<2-fX@L+*c2#B1U088L(2t+y_3?0sm zk2?X?8wHz=;{h5gG&~)!jV23l13rxiaH4>00tX5#BTfxRg7ch&M!{GCYz2!>#T3FU z4e>jmKQKwYPJK||KLJPJIC<7JjvjAN79Px!P64w7U4vSQL@)yZ5mW)j<}V=7 ztIMR_1XOPn4GfV;Awv`poR)wlIMu0mMP$I^#;lh@MBF=H<8TdqDjbbEi3*#6XTc*+ z64Brf$v7$zY#y)xD{~I;IzP_BNhZcj0^@2(Fv49B$G%4 zh$@KS1S)uUgclsRBZx@gtY~zwT37%FeK|Z5O%NZ3wAa9|1EeTe&=ryAkP#3=cmpR0 zMnItfpm|FnPU#o1c#Z*F5V%P&;z^wGJOk*E24F$l=eQK$siAR4gyD_yqAp zpf0e4F@S=L6HwvEk+UHYAg2&8Mu6OvibDd4Kmr0pavb;=gzZVfg3(ckkh6kG3lu{F zEQ9<6#JTfTNSaD0-s$rBn(VLzG6<;P3;5CkKM=qm)FaMuTrEhbf}|BwWH?l$Co%>%0bc@K3iua5c(SCE z(9YM%B(vb_ZYqfoN!?Mt@ao1clm_&zJs?M%c$s4tYKj~aGP3a;bJ9)76vl}l_xRO6 zGEV|8^8~+3M?R2Zfioo%XwZe^dE*Hx7NP@%N59k_oHAz8;EZOr#`_IMg^8({I z>&TMOrbDcMLPS0Z;yD2V86={Rj7*FJY=R)r0p_J=U|2**w7elxM_Mot8;KBeA(ID} z2#FR6jtCMqI@Acfs1IJ63M@h942BG)0u;);B%e;eL8=Qx@HxQXc;*aQD3Bm=rbBiM z6L_Do6CyymGB$Obh#V!<7UQQ25gkDR0o*+7&u5qi3@V+sE24TjlID#82vG{?9#2Bn zAR&Pk`JfTy)5x&BfCNy2s03LbWN`d(WF#3TK|v%~4fIci06h-J@m-Nx1ds^}LS+Vy z4t@aT38(@K_=O7L0M-Jx2tESh0vSg`$`R;n5y0624Mc)!0in@IBzWTlRD&oT2Pg*_ zz~#ar!+{|R#*s;o?I1fsItiyF0K9<4%NWN^LqHW+(4-alrZ#zRflUgy1loi>^QDPAaKFUJ7a!hj2(mq5+Gq}3LcSUU9dee0QN!3a{-@-5Kx0cKF*g0 zkpYN^O-x8VX&at$f769Gii{oS5|MyL2NU3ng4}{)1}WrWJ)X$Cg^9dcY6|uct_cVe zG~_iip4dDQfo)JK)YPPLMe~^CtqR~AlYl46uN(1128AOl@{WPOJNP6-FkplX<@5xX zpn0fmAPMJ-GInkx&^&Kt9%3LZn1V%~!uetf&KpU*VJ9A&laL`02f52b(4fohtA&^J_A%W-uto;LlJX|9DPXzIQ)r!Dj;RuiyL(U3`_1FRb!;DA* zGr}Q758?`uIX-|Kuo4P*HiSl|Lg0aB4B}ytoE=gtXcIy13?$@(m=!1jiZlQ`GJyvt z9ue|C3RniUwf`Q9FNM#6^L| zpml<%b&NZnrXkZp_#78u{Fsn(QlMb`KY2q~=5H)GTOS^g$DaTLyepm*$P6A*1S?M7 zW(*>*H(v!{DhL{QJR(FM2>5~skiwT&fT-VL#?$_P!Azi224_b`&Y)_}Z*=flguInt zS4gxFDujE)Sbz#i66A4c0v)K(UZV3tA=Gft>_i^v z{s?Gz8~Kb8d5rSJ02dCX%om6^1=_D;tn5OQ}6NP<;=*^37tgad&>fPFr%2z3l0n9~&R zb>juEa8n>t;Xe$4Cd1_SGS~Y0`USw-NE2`4^InF52NoV~V^5%jRn7r4gc!F3> zJo$rF17SLToNIv?{y>?$mf&qwmP-hLjxPTs?Eb4TIO1R`q8 z5h4FVdPzmveb{6d0&4N2?oB#1}9k?0WUev-$_U|``ajJNAgJlEN7;-8w-UTJYzk)Nv7=Y=i{b=R%W`Y rjNwSb%Fhol$bXGp!kbi(PD}aXp=$PQhC { @@ -42,7 +42,10 @@ final class StepQuizViewModel: FeatureViewModel< onNewMessage(StepQuizFeatureMessageInitWithStep(step: step, forceUpdate: false)) } - override func shouldNotifyStateDidChange(oldState: StepQuizFeatureState, newState: StepQuizFeatureState) -> Bool { + override func shouldNotifyStateDidChange( + oldState: StepQuizFeature.State, + newState: StepQuizFeature.State + ) -> Bool { if oldState.stepQuizState is StepQuizFeatureStepQuizStateAttemptLoading && newState.stepQuizState is StepQuizFeatureStepQuizStateAttemptLoaded { updateChildQuizSubscription = objectWillChange.sink { [weak self] in @@ -98,6 +101,14 @@ final class StepQuizViewModel: FeatureViewModel< moduleOutput?.stepQuizDidRequestContinue() } + func doUnsupportedQuizSolveOnTheWebAction() { + onNewMessage(StepQuizFeatureMessageUnsupportedQuizSolveOnTheWebClicked()) + } + + func doUnsupportedQuizGoToStudyPlanAction() { + onNewMessage(StepQuizFeatureMessageUnsupportedQuizGoToStudyPlanClicked()) + } + func makeViewData() -> StepQuizViewData { stepQuizViewDataMapper.mapStepDataToViewData(step: step, state: stepQuizStateKs) } diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuiz/Views/StepQuizStatusView.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuiz/Views/StepQuizStatusView.swift index 56563f0509..fc93d41868 100644 --- a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuiz/Views/StepQuizStatusView.swift +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuiz/Views/StepQuizStatusView.swift @@ -46,7 +46,6 @@ struct StepQuizStatusView: View { case wrong case evaluation case loading - case unsupportedQuiz case invalidReply(message: String) static var allCases: [StepQuizStatusView.State] { @@ -55,7 +54,6 @@ struct StepQuizStatusView: View { .wrong, .evaluation, .loading, - .unsupportedQuiz, .invalidReply(message: "Invalid reply") ] } @@ -64,7 +62,7 @@ struct StepQuizStatusView: View { switch self { case .correct: return Images.StepQuiz.checkmark - case .wrong, .unsupportedQuiz, .invalidReply: + case .wrong, .invalidReply: return Images.StepQuiz.info case .evaluation, .loading: return "" @@ -81,8 +79,6 @@ struct StepQuizStatusView: View { return Strings.StepQuiz.quizStatusEvaluation case .loading: return Strings.StepQuiz.quizStatusLoading - case .unsupportedQuiz: - return Strings.StepQuiz.unsupportedText case .invalidReply(let message): return message } @@ -92,7 +88,7 @@ struct StepQuizStatusView: View { switch self { case .correct: return Color(ColorPalette.secondary) - case .wrong, .evaluation, .loading, .unsupportedQuiz, .invalidReply: + case .wrong, .evaluation, .loading, .invalidReply: return Color(ColorPalette.primary) } } @@ -101,7 +97,7 @@ struct StepQuizStatusView: View { switch self { case .correct: return Color(ColorPalette.green200Alpha12) - case .evaluation, .loading, .unsupportedQuiz, .invalidReply, .wrong: + case .evaluation, .loading, .invalidReply, .wrong: return Color(ColorPalette.blue200Alpha12) } } diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuiz/Views/StepQuizUnsupportedView.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuiz/Views/StepQuizUnsupportedView.swift new file mode 100644 index 0000000000..79f8e92644 --- /dev/null +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuiz/Views/StepQuizUnsupportedView.swift @@ -0,0 +1,64 @@ +import SwiftUI + +extension StepQuizUnsupportedView { + enum Appearance { + static let illustrationMaxHeight: CGFloat = 130 + + static let rootSpacing = LayoutInsets.defaultInset * 2 + static let labelsSpacing = LayoutInsets.smallInset + static let buttonsSpacing = LayoutInsets.defaultInset + } +} + +struct StepQuizUnsupportedView: View { + let onSolveButtonTap: () -> Void + let onGoToStudyPlanButtonTap: () -> Void + + var body: some View { + VStack(alignment: .leading, spacing: Appearance.rootSpacing) { + Image(.stepQuizUnsupportedIllustration) + .renderingMode(.original) + .resizable() + .aspectRatio(contentMode: .fit) + .frame( + maxWidth: .infinity, + maxHeight: Appearance.illustrationMaxHeight, + alignment: .leading + ) + + VStack(alignment: .leading, spacing: Appearance.labelsSpacing) { + Text(Strings.StepQuiz.unsupportedTitle) + .font(.title2.bold()) + + Text(Strings.StepQuiz.unsupportedDescription) + .font(.body) + } + .foregroundColor(.newPrimaryText) + .multilineTextAlignment(.leading) + + VStack(spacing: Appearance.buttonsSpacing) { + Button( + Strings.StepQuiz.unsupportedButtonSolve, + action: onSolveButtonTap + ) + .buttonStyle(RoundedRectangleButtonStyle(style: .violet)) + + Button( + Strings.Common.goToStudyPlan, + action: onGoToStudyPlanButtonTap + ) + .buttonStyle(OutlineButtonStyle(style: .violet)) + } + } + } +} + +#if DEBUG +#Preview { + StepQuizUnsupportedView( + onSolveButtonTap: {}, + onGoToStudyPlanButtonTap: {} + ) + .padding() +} +#endif diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuiz/Views/StepQuizView.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuiz/Views/StepQuizView.swift index d14c756c9b..9f1d826e73 100644 --- a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuiz/Views/StepQuizView.swift +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuiz/Views/StepQuizView.swift @@ -58,11 +58,9 @@ struct StepQuizView: View { ScrollView { VStack(alignment: .leading, spacing: appearance.interItemSpacing) { if case .unsupported = viewData.quizType { - StepQuizStatusView(state: .unsupportedQuiz) - - LatexView( - text: viewData.stepText, - configuration: .stepText() + StepQuizUnsupportedView( + onSolveButtonTap: viewModel.doUnsupportedQuizSolveOnTheWebAction, + onGoToStudyPlanButtonTap: viewModel.doUnsupportedQuizGoToStudyPlanAction ) } else { StepExpandableStepTextView( @@ -122,7 +120,7 @@ struct StepQuizView: View { // swiftlint:disable function_parameter_count @ViewBuilder private func buildQuizContent( - state: StepQuizFeatureState, + state: StepQuizFeature.State, step: Step, quizName: String?, quizType: StepQuizChildQuizType, @@ -197,7 +195,7 @@ struct StepQuizView: View { @ViewBuilder private func buildQuizActionButtons( quizType: StepQuizChildQuizType, - state: StepQuizFeatureState, + state: StepQuizFeature.State, attemptLoadedState: StepQuizFeatureStepQuizStateAttemptLoaded ) -> some View { let submissionStatus: SubmissionStatus? = { @@ -307,17 +305,39 @@ struct StepQuizView: View { case .navigateTo(let viewActionNavigateTo): switch StepQuizFeatureActionViewActionNavigateToKs(viewActionNavigateTo) { case .home: - stackRouter.popViewController() - TabBarRouter(tab: .home).route() + stackRouter.popViewController( + animated: true, + completion: { + TabBarRouter(tab: .home).route() + } + ) case .stepScreen(let navigateToStepScreenViewAction): let assembly = StepAssembly(stepRoute: navigateToStepScreenViewAction.stepRoute) stackRouter.pushViewController(assembly.makeModule()) + case .studyPlan: + stackRouter.popViewController( + animated: true, + completion: { + TabBarRouter(tab: .studyPlan).route() + } + ) } case .stepQuizHintsViewAction(let stepQuizHintsViewAction): switch StepQuizHintsFeatureActionViewActionKs(stepQuizHintsViewAction.viewAction) { case .showNetworkError: ProgressHUD.showError(status: Strings.Common.connectionError) } + case .createMagicLinkState(let createMagicLinkStateViewAction): + switch StepQuizFeatureActionViewActionCreateMagicLinkStateKs(createMagicLinkStateViewAction) { + case .error: + ProgressHUD.showError() + case .loading: + ProgressHUD.show() + case .success: + ProgressHUD.showSuccess() + } + case .openUrl(let data): + WebControllerManager.shared.presentWebControllerWithURLString(data.url, controllerType: .inAppSafari) } } } diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/analytic/domain/model/hyperskill/HyperskillAnalyticPart.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/analytic/domain/model/hyperskill/HyperskillAnalyticPart.kt index fadd617d2c..891143a527 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/analytic/domain/model/hyperskill/HyperskillAnalyticPart.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/analytic/domain/model/hyperskill/HyperskillAnalyticPart.kt @@ -45,5 +45,6 @@ enum class HyperskillAnalyticPart(val partName: String) { INTERVIEW_PREPARATION_WIDGET("interview_preparation_widget"), INTERVIEW_PREPARATION_COMPLETED_MODAL("interview_preparation_completed_modal"), REQUEST_REVIEW_MODAL("request_review_modal"), - USERS_QUESTIONNAIRE_WIDGET("users_questionnaire_widget") + USERS_QUESTIONNAIRE_WIDGET("users_questionnaire_widget"), + UNSUPPORTED_QUIZ_PLACEHOLDER("unsupported_quiz_placeholder") } \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/analytic/domain/model/hyperskill/HyperskillAnalyticTarget.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/analytic/domain/model/hyperskill/HyperskillAnalyticTarget.kt index 13464938df..c3c58e399c 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/analytic/domain/model/hyperskill/HyperskillAnalyticTarget.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/analytic/domain/model/hyperskill/HyperskillAnalyticTarget.kt @@ -116,5 +116,6 @@ enum class HyperskillAnalyticTarget(val targetName: String) { HOME_SCREEN_QUICK_ACTION("home_screen_quick_action"), REQUEST_REVIEW_MODAL("request_review_modal"), WRITE_A_REQUEST("write_a_request"), - MAYBE_LATER("maybe_later") + MAYBE_LATER("maybe_later"), + SOLVE_ON_THE_WEB_VERSION("solve_on_the_web_version") } \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/core/domain/url/HyperskillUrlPath.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/core/domain/url/HyperskillUrlPath.kt index f124cf81ba..7f802e91a6 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/core/domain/url/HyperskillUrlPath.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/core/domain/url/HyperskillUrlPath.kt @@ -1,5 +1,7 @@ package org.hyperskill.app.core.domain.url +import org.hyperskill.app.step.domain.model.StepRoute + sealed class HyperskillUrlPath { abstract val path: String @@ -30,4 +32,8 @@ sealed class HyperskillUrlPath { class DeleteAccount : HyperskillUrlPath() { override val path: String = "/delete-account" } + + class Step(stepRoute: StepRoute) : HyperskillUrlPath() { + override val path: String = stepRoute.analyticRoute.path + } } \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/magic_links/domain/interactor/UrlPathProcessor.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/magic_links/domain/interactor/UrlPathProcessor.kt index 40627bd46d..099c0b29ed 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/magic_links/domain/interactor/UrlPathProcessor.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/magic_links/domain/interactor/UrlPathProcessor.kt @@ -43,5 +43,6 @@ class UrlPathProcessor( is HyperskillUrlPath.ResetPassword -> false is HyperskillUrlPath.StudyPlan -> true is HyperskillUrlPath.Track -> true + is HyperskillUrlPath.Step -> true } } \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz/domain/analytic/StepQuizUnsupportedClickedGoToStudyPlanHyperskillAnalyticEvent.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz/domain/analytic/StepQuizUnsupportedClickedGoToStudyPlanHyperskillAnalyticEvent.kt new file mode 100644 index 0000000000..d92747e0dc --- /dev/null +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz/domain/analytic/StepQuizUnsupportedClickedGoToStudyPlanHyperskillAnalyticEvent.kt @@ -0,0 +1,31 @@ +package org.hyperskill.app.step_quiz.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.HyperskillAnalyticPart +import org.hyperskill.app.analytic.domain.model.hyperskill.HyperskillAnalyticRoute +import org.hyperskill.app.analytic.domain.model.hyperskill.HyperskillAnalyticTarget + +/** + * Represents click on the "Go to Study Plan" button analytic event after unsupported quiz placeholder. + * + * JSON payload: + * ``` + * { + * "route": "/learn/step/1", + * "action": "click", + * "part": "unsupported_quiz_placeholder", + * "target": "go_to_study_plan" + * } + * ``` + * + * @see HyperskillAnalyticEvent + */ +class StepQuizUnsupportedClickedGoToStudyPlanHyperskillAnalyticEvent( + route: HyperskillAnalyticRoute +) : HyperskillAnalyticEvent( + route, + HyperskillAnalyticAction.CLICK, + HyperskillAnalyticPart.UNSUPPORTED_QUIZ_PLACEHOLDER, + HyperskillAnalyticTarget.GO_TO_STUDY_PLAN +) \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz/domain/analytic/StepQuizUnsupportedClickedSolveOnTheWebHyperskillAnalyticEvent.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz/domain/analytic/StepQuizUnsupportedClickedSolveOnTheWebHyperskillAnalyticEvent.kt new file mode 100644 index 0000000000..8ff46517bb --- /dev/null +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz/domain/analytic/StepQuizUnsupportedClickedSolveOnTheWebHyperskillAnalyticEvent.kt @@ -0,0 +1,31 @@ +package org.hyperskill.app.step_quiz.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.HyperskillAnalyticPart +import org.hyperskill.app.analytic.domain.model.hyperskill.HyperskillAnalyticRoute +import org.hyperskill.app.analytic.domain.model.hyperskill.HyperskillAnalyticTarget + +/** + * Represents click on the "Solve on the Web version" button analytic event after unsupported quiz placeholder. + * + * JSON payload: + * ``` + * { + * "route": "/learn/step/1", + * "action": "click", + * "part": "unsupported_quiz_placeholder", + * "target": "solve_on_the_web_version" + * } + * ``` + * + * @see HyperskillAnalyticEvent + */ +class StepQuizUnsupportedClickedSolveOnTheWebHyperskillAnalyticEvent( + route: HyperskillAnalyticRoute +) : HyperskillAnalyticEvent( + route, + HyperskillAnalyticAction.CLICK, + HyperskillAnalyticPart.UNSUPPORTED_QUIZ_PLACEHOLDER, + HyperskillAnalyticTarget.SOLVE_ON_THE_WEB_VERSION +) \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz/injection/StepQuizComponentImpl.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz/injection/StepQuizComponentImpl.kt index 7fb32c3b25..256f4b36ad 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz/injection/StepQuizComponentImpl.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz/injection/StepQuizComponentImpl.kt @@ -14,7 +14,7 @@ import org.hyperskill.app.step_quiz.view.mapper.StepQuizTitleMapper import org.hyperskill.app.step_quiz_hints.injection.StepQuizHintsComponent import ru.nobird.app.presentation.redux.feature.Feature -class StepQuizComponentImpl( +internal class StepQuizComponentImpl( private val appGraph: AppGraph, private val stepRoute: StepRoute ) : StepQuizComponent { @@ -52,6 +52,7 @@ class StepQuizComponentImpl( stepQuizReplyValidator, appGraph.profileDataComponent.currentProfileStateRepository, appGraph.buildFreemiumDataComponent().freemiumInteractor, + appGraph.buildMagicLinksDataComponent().urlPathProcessor, appGraph.analyticComponent.analyticInteractor, appGraph.sentryComponent.sentryInteractor, appGraph.buildOnboardingDataComponent().onboardingInteractor, diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz/injection/StepQuizFeatureBuilder.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz/injection/StepQuizFeatureBuilder.kt index aae15fe0b8..c19ff0955b 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz/injection/StepQuizFeatureBuilder.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz/injection/StepQuizFeatureBuilder.kt @@ -7,6 +7,7 @@ import org.hyperskill.app.core.presentation.ActionDispatcherOptions import org.hyperskill.app.core.view.mapper.ResourceProvider import org.hyperskill.app.freemium.domain.interactor.FreemiumInteractor import org.hyperskill.app.logging.presentation.wrapWithLogger +import org.hyperskill.app.magic_links.domain.interactor.UrlPathProcessor import org.hyperskill.app.onboarding.domain.interactor.OnboardingInteractor import org.hyperskill.app.profile.domain.repository.CurrentProfileStateRepository import org.hyperskill.app.sentry.domain.interactor.SentryInteractor @@ -25,7 +26,7 @@ import ru.nobird.app.presentation.redux.dispatcher.wrapWithActionDispatcher import ru.nobird.app.presentation.redux.feature.Feature import ru.nobird.app.presentation.redux.feature.ReduxFeature -object StepQuizFeatureBuilder { +internal object StepQuizFeatureBuilder { private const val LOG_TAG = "StepQuizFeature" fun build( @@ -34,6 +35,7 @@ object StepQuizFeatureBuilder { stepQuizReplyValidator: StepQuizReplyValidator, currentProfileStateRepository: CurrentProfileStateRepository, freemiumInteractor: FreemiumInteractor, + urlPathProcessor: UrlPathProcessor, analyticInteractor: AnalyticInteractor, sentryInteractor: SentryInteractor, onboardingInteractor: OnboardingInteractor, @@ -53,6 +55,7 @@ object StepQuizFeatureBuilder { stepQuizReplyValidator, currentProfileStateRepository, freemiumInteractor, + urlPathProcessor, analyticInteractor, sentryInteractor, onboardingInteractor, diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz/injection/SubmissionDataComponentImpl.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz/injection/SubmissionDataComponentImpl.kt index 6927df76eb..506bf96a40 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz/injection/SubmissionDataComponentImpl.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz/injection/SubmissionDataComponentImpl.kt @@ -6,7 +6,7 @@ import org.hyperskill.app.step_quiz.data.repository.SubmissionRepositoryImpl import org.hyperskill.app.step_quiz.domain.repository.SubmissionRepository import org.hyperskill.app.step_quiz.remote.SubmissionRemoteDataSourceImpl -class SubmissionDataComponentImpl( +internal class SubmissionDataComponentImpl( appGraph: AppGraph ) : SubmissionDataComponent { override val submissionRepository: SubmissionRepository = diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz/presentation/StepQuizActionDispatcher.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz/presentation/StepQuizActionDispatcher.kt index b42a05b1ca..fc3958af8d 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz/presentation/StepQuizActionDispatcher.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz/presentation/StepQuizActionDispatcher.kt @@ -2,9 +2,11 @@ package org.hyperskill.app.step_quiz.presentation import org.hyperskill.app.SharedResources import org.hyperskill.app.analytic.domain.interactor.AnalyticInteractor +import org.hyperskill.app.core.domain.url.HyperskillUrlPath import org.hyperskill.app.core.presentation.ActionDispatcherOptions import org.hyperskill.app.core.view.mapper.ResourceProvider import org.hyperskill.app.freemium.domain.interactor.FreemiumInteractor +import org.hyperskill.app.magic_links.domain.interactor.UrlPathProcessor import org.hyperskill.app.onboarding.domain.interactor.OnboardingInteractor import org.hyperskill.app.profile.domain.repository.CurrentProfileStateRepository import org.hyperskill.app.sentry.domain.interactor.SentryInteractor @@ -15,16 +17,19 @@ import org.hyperskill.app.step_quiz.domain.model.submissions.SubmissionStatus import org.hyperskill.app.step_quiz.domain.model.submissions.isWrongOrRejected import org.hyperskill.app.step_quiz.domain.validation.StepQuizReplyValidator import org.hyperskill.app.step_quiz.presentation.StepQuizFeature.Action +import org.hyperskill.app.step_quiz.presentation.StepQuizFeature.InternalAction +import org.hyperskill.app.step_quiz.presentation.StepQuizFeature.InternalMessage import org.hyperskill.app.step_quiz.presentation.StepQuizFeature.Message import org.hyperskill.app.step_quiz_fill_blanks.model.FillBlanksMode import ru.nobird.app.presentation.redux.dispatcher.CoroutineActionDispatcher -class StepQuizActionDispatcher( +internal class StepQuizActionDispatcher( config: ActionDispatcherOptions, private val stepQuizInteractor: StepQuizInteractor, private val stepQuizReplyValidator: StepQuizReplyValidator, private val currentProfileStateRepository: CurrentProfileStateRepository, private val freemiumInteractor: FreemiumInteractor, + private val urlPathProcessor: UrlPathProcessor, private val analyticInteractor: AnalyticInteractor, private val sentryInteractor: SentryInteractor, private val onboardingInteractor: OnboardingInteractor, @@ -208,6 +213,14 @@ class StepQuizActionDispatcher( } } } + is InternalAction.CreateMagicLinkForUnsupportedQuiz -> { + urlPathProcessor + .processUrlPath(HyperskillUrlPath.Step(action.stepRoute)) + .fold( + onSuccess = { onNewMessage(InternalMessage.CreateMagicLinkForUnsupportedQuizSuccess(it)) }, + onFailure = { onNewMessage(InternalMessage.CreateMagicLinkForUnsupportedQuizError) } + ) + } is Action.LogAnalyticEvent -> analyticInteractor.logEvent(action.analyticEvent) else -> {} diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz/presentation/StepQuizFeature.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz/presentation/StepQuizFeature.kt index 7416c2a980..e75406aca4 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz/presentation/StepQuizFeature.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz/presentation/StepQuizFeature.kt @@ -13,7 +13,7 @@ import org.hyperskill.app.step_quiz.domain.validation.ReplyValidationResult import org.hyperskill.app.step_quiz_fill_blanks.model.FillBlanksMode import org.hyperskill.app.step_quiz_hints.presentation.StepQuizHintsFeature -interface StepQuizFeature { +object StepQuizFeature { data class State( val stepQuizState: StepQuizState, val stepQuizHintsState: StepQuizHintsFeature.State @@ -112,6 +112,9 @@ interface StepQuizFeature { */ object TheoryToolbarItemClicked : Message + object UnsupportedQuizSolveOnTheWebClicked : Message + object UnsupportedQuizGoToStudyPlanClicked : Message + /** * Analytic */ @@ -136,6 +139,11 @@ interface StepQuizFeature { data class StepQuizHintsMessage(val message: StepQuizHintsFeature.Message) : Message } + internal sealed interface InternalMessage : Message { + object CreateMagicLinkForUnsupportedQuizError : InternalMessage + data class CreateMagicLinkForUnsupportedQuizSuccess(val url: String) : InternalMessage + } + sealed interface Action { data class FetchAttempt(val step: Step) : Action @@ -180,11 +188,23 @@ interface StepQuizFeature { val viewAction: StepQuizHintsFeature.Action.ViewAction ) : ViewAction + sealed interface CreateMagicLinkState : ViewAction { + object Loading : CreateMagicLinkState + object Error : CreateMagicLinkState + object Success : CreateMagicLinkState + } + data class OpenUrl(val url: String) : ViewAction + sealed interface NavigateTo : ViewAction { object Home : NavigateTo + object StudyPlan : NavigateTo data class StepScreen(val stepRoute: StepRoute) : NavigateTo } } } + + internal sealed interface InternalAction : Action { + data class CreateMagicLinkForUnsupportedQuiz(val stepRoute: StepRoute) : InternalAction + } } \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz/presentation/StepQuizReducer.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz/presentation/StepQuizReducer.kt index eb8660bca6..5bfe3fecd5 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz/presentation/StepQuizReducer.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz/presentation/StepQuizReducer.kt @@ -20,12 +20,16 @@ import org.hyperskill.app.step_quiz.domain.analytic.StepQuizClickedTheoryToolbar import org.hyperskill.app.step_quiz.domain.analytic.StepQuizCodeEditorClickedInputAccessoryButtonHyperskillAnalyticEvent import org.hyperskill.app.step_quiz.domain.analytic.StepQuizFullScreenCodeEditorClickedCodeDetailsHyperskillAnalyticEvent import org.hyperskill.app.step_quiz.domain.analytic.StepQuizFullScreenCodeEditorClickedStepTextDetailsHyperskillAnalyticEvent +import org.hyperskill.app.step_quiz.domain.analytic.StepQuizUnsupportedClickedGoToStudyPlanHyperskillAnalyticEvent +import org.hyperskill.app.step_quiz.domain.analytic.StepQuizUnsupportedClickedSolveOnTheWebHyperskillAnalyticEvent import org.hyperskill.app.step_quiz.domain.model.attempts.Attempt import org.hyperskill.app.step_quiz.domain.model.submissions.Reply import org.hyperskill.app.step_quiz.domain.model.submissions.Submission import org.hyperskill.app.step_quiz.domain.model.submissions.SubmissionStatus import org.hyperskill.app.step_quiz.domain.validation.ReplyValidationResult import org.hyperskill.app.step_quiz.presentation.StepQuizFeature.Action +import org.hyperskill.app.step_quiz.presentation.StepQuizFeature.InternalAction +import org.hyperskill.app.step_quiz.presentation.StepQuizFeature.InternalMessage import org.hyperskill.app.step_quiz.presentation.StepQuizFeature.Message import org.hyperskill.app.step_quiz.presentation.StepQuizFeature.State import org.hyperskill.app.step_quiz.presentation.StepQuizFeature.StepQuizState @@ -37,7 +41,7 @@ import ru.nobird.app.presentation.redux.reducer.StateReducer internal typealias StepQuizReducerResult = Pair> -class StepQuizReducer( +internal class StepQuizReducer( private val stepRoute: StepRoute, private val stepQuizHintsReducer: StepQuizHintsReducer ) : StateReducer { @@ -268,6 +272,28 @@ class StepQuizReducer( } is Message.TheoryToolbarItemClicked -> handleTheoryToolbarItemClicked(state) + Message.UnsupportedQuizGoToStudyPlanClicked -> + state to setOf( + Action.LogAnalyticEvent( + StepQuizUnsupportedClickedGoToStudyPlanHyperskillAnalyticEvent(stepRoute.analyticRoute) + ), + Action.ViewAction.NavigateTo.StudyPlan + ) + Message.UnsupportedQuizSolveOnTheWebClicked -> + state to setOf( + Action.ViewAction.CreateMagicLinkState.Loading, + InternalAction.CreateMagicLinkForUnsupportedQuiz(stepRoute), + Action.LogAnalyticEvent( + StepQuizUnsupportedClickedSolveOnTheWebHyperskillAnalyticEvent(stepRoute.analyticRoute) + ) + ) + InternalMessage.CreateMagicLinkForUnsupportedQuizError -> + state to setOf(Action.ViewAction.CreateMagicLinkState.Error) + is InternalMessage.CreateMagicLinkForUnsupportedQuizSuccess -> + state to setOf( + Action.ViewAction.CreateMagicLinkState.Success, + Action.ViewAction.OpenUrl(message.url) + ) is Message.ClickedRetryEventMessage -> if (state.stepQuizState is StepQuizState.AttemptLoaded) { val event = StepQuizClickedRetryHyperskillAnalyticEvent(stepRoute.analyticRoute) diff --git a/shared/src/commonMain/resources/MR/base/strings.xml b/shared/src/commonMain/resources/MR/base/strings.xml index 37d6028e5f..3b26bbb277 100644 --- a/shared/src/commonMain/resources/MR/base/strings.xml +++ b/shared/src/commonMain/resources/MR/base/strings.xml @@ -88,7 +88,6 @@ Send Checking Show discussions - Current quiz type is not supported in mobile app yet Problem of the day solved! Solve another one tomorrow to get more %s streak @@ -97,6 +96,11 @@ Continue with next topic Description + Current quiz type is not supported in mobile app yet + Web required + This problem is not supported in the mobile app. Please continue learning on the Web. + Solve on the Web version + See hint Report diff --git a/shared/src/commonTest/kotlin/org/hyperskill/HyperskillUrlBuilderTest.kt b/shared/src/commonTest/kotlin/org/hyperskill/HyperskillUrlBuilderTest.kt index ffcb20ab10..eb4a287b42 100644 --- a/shared/src/commonTest/kotlin/org/hyperskill/HyperskillUrlBuilderTest.kt +++ b/shared/src/commonTest/kotlin/org/hyperskill/HyperskillUrlBuilderTest.kt @@ -8,6 +8,7 @@ import org.hyperskill.app.core.domain.url.HyperskillUrlBuilder import org.hyperskill.app.core.domain.url.HyperskillUrlPath import org.hyperskill.app.debug.domain.model.EndpointConfigType import org.hyperskill.app.network.domain.model.NetworkEndpointConfigInfo +import org.hyperskill.app.step.domain.model.StepRoute class HyperskillUrlBuilderTest { @@ -34,7 +35,8 @@ class HyperskillUrlBuilderTest { HyperskillUrlPath.ResetPassword(), HyperskillUrlPath.StudyPlan(), HyperskillUrlPath.Track(1), - HyperskillUrlPath.DeleteAccount() + HyperskillUrlPath.DeleteAccount(), + HyperskillUrlPath.Step(StepRoute.Learn.Step(1)) ) for (path in paths) { @@ -48,6 +50,7 @@ class HyperskillUrlBuilderTest { is HyperskillUrlPath.StudyPlan -> "${networkEndpointConfigInfo.baseUrl}study-plan" is HyperskillUrlPath.Track -> "${networkEndpointConfigInfo.baseUrl}tracks/1" is HyperskillUrlPath.DeleteAccount -> "${networkEndpointConfigInfo.baseUrl}delete-account" + is HyperskillUrlPath.Step -> "${networkEndpointConfigInfo.baseUrl}learn/step/1" } assertEquals(expected, url.toString()) From 40fe2b999b52d2145218a82693d1c317565b44af Mon Sep 17 00:00:00 2001 From: github-actions Date: Fri, 23 Feb 2024 06:40:15 +0000 Subject: [PATCH 089/104] Bump build number --- gradle/app.versions.toml | 2 +- .../NotificationServiceExtension/Info.plist | 2 +- .../iosHyperskillApp.xcodeproj/project.pbxproj | 16 ++++++++-------- iosHyperskillApp/iosHyperskillApp/Info.plist | 2 +- .../iosHyperskillAppTests/Info.plist | 2 +- .../iosHyperskillAppUITests/Info.plist | 2 +- 6 files changed, 13 insertions(+), 13 deletions(-) diff --git a/gradle/app.versions.toml b/gradle/app.versions.toml index 2a1dd2ae21..d4a89f9f40 100644 --- a/gradle/app.versions.toml +++ b/gradle/app.versions.toml @@ -3,4 +3,4 @@ minSdk = '24' targetSdk = '33' compileSdk = '33' versionName = '1.50' -versionCode = '335' \ No newline at end of file +versionCode = '336' \ No newline at end of file diff --git a/iosHyperskillApp/NotificationServiceExtension/Info.plist b/iosHyperskillApp/NotificationServiceExtension/Info.plist index 0259cd4085..ebd9ba0102 100644 --- a/iosHyperskillApp/NotificationServiceExtension/Info.plist +++ b/iosHyperskillApp/NotificationServiceExtension/Info.plist @@ -9,7 +9,7 @@ CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleVersion - 341 + 342 CFBundleShortVersionString 1.50 CFBundlePackageType diff --git a/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj b/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj index 8d6423699d..4b304341ec 100644 --- a/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj +++ b/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj @@ -5069,7 +5069,7 @@ buildSettings = { CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 341; + CURRENT_PROJECT_VERSION = 342; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = 3DWS674B2M; GENERATE_INFOPLIST_FILE = NO; @@ -5090,7 +5090,7 @@ buildSettings = { CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 341; + CURRENT_PROJECT_VERSION = 342; DEVELOPMENT_TEAM = 3DWS674B2M; GENERATE_INFOPLIST_FILE = NO; INFOPLIST_FILE = iosHyperskillAppUITests/Info.plist; @@ -5111,7 +5111,7 @@ BUNDLE_LOADER = "$(TEST_HOST)"; CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 341; + CURRENT_PROJECT_VERSION = 342; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = 3DWS674B2M; INFOPLIST_FILE = iosHyperskillAppTests/Info.plist; @@ -5132,7 +5132,7 @@ BUNDLE_LOADER = "$(TEST_HOST)"; CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 341; + CURRENT_PROJECT_VERSION = 342; DEVELOPMENT_TEAM = 3DWS674B2M; INFOPLIST_FILE = iosHyperskillAppTests/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; @@ -5153,7 +5153,7 @@ CODE_SIGN_ENTITLEMENTS = NotificationServiceExtension/NotificationServiceExtension.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 341; + CURRENT_PROJECT_VERSION = 342; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = 3DWS674B2M; INFOPLIST_FILE = NotificationServiceExtension/Info.plist; @@ -5181,7 +5181,7 @@ CODE_SIGN_ENTITLEMENTS = NotificationServiceExtension/NotificationServiceExtension.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 341; + CURRENT_PROJECT_VERSION = 342; DEVELOPMENT_TEAM = 3DWS674B2M; INFOPLIST_FILE = NotificationServiceExtension/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; @@ -5326,7 +5326,7 @@ CODE_SIGN_ENTITLEMENTS = iosHyperskillApp/iosHyperskillApp.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 341; + CURRENT_PROJECT_VERSION = 342; DEVELOPMENT_ASSET_PATHS = "\"iosHyperskillApp/Preview Content\""; DEVELOPMENT_TEAM = 3DWS674B2M; ENABLE_PREVIEWS = YES; @@ -5362,7 +5362,7 @@ CODE_SIGN_ENTITLEMENTS = iosHyperskillApp/iosHyperskillApp.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 341; + CURRENT_PROJECT_VERSION = 342; DEVELOPMENT_ASSET_PATHS = "\"iosHyperskillApp/Preview Content\""; DEVELOPMENT_TEAM = 3DWS674B2M; ENABLE_PREVIEWS = YES; diff --git a/iosHyperskillApp/iosHyperskillApp/Info.plist b/iosHyperskillApp/iosHyperskillApp/Info.plist index f654934c8c..4bdca14f29 100644 --- a/iosHyperskillApp/iosHyperskillApp/Info.plist +++ b/iosHyperskillApp/iosHyperskillApp/Info.plist @@ -36,7 +36,7 @@ CFBundleVersion - 341 + 342 FirebaseAppDelegateProxyEnabled FirebaseMessagingAutoInitEnabled diff --git a/iosHyperskillApp/iosHyperskillAppTests/Info.plist b/iosHyperskillApp/iosHyperskillAppTests/Info.plist index 71df844311..8bb7eb0f8c 100644 --- a/iosHyperskillApp/iosHyperskillAppTests/Info.plist +++ b/iosHyperskillApp/iosHyperskillAppTests/Info.plist @@ -15,6 +15,6 @@ CFBundleShortVersionString 1.50 CFBundleVersion - 341 + 342 diff --git a/iosHyperskillApp/iosHyperskillAppUITests/Info.plist b/iosHyperskillApp/iosHyperskillAppUITests/Info.plist index f364282ef9..5e30db3120 100644 --- a/iosHyperskillApp/iosHyperskillAppUITests/Info.plist +++ b/iosHyperskillApp/iosHyperskillAppUITests/Info.plist @@ -15,6 +15,6 @@ CFBundleShortVersionString 1.50 CFBundleVersion - 341 + 342 From 083ac5c8d586970a9ea718cd37a395e6a19fbc6b Mon Sep 17 00:00:00 2001 From: Ivan Magda Date: Mon, 26 Feb 2024 08:47:36 +0700 Subject: [PATCH 090/104] Shared: Fix track selection details screen crashes (#913) ^ALTAPPS-1158 --- .../ProgressScreenActionDispatcher.kt | 10 ++-- .../ProjectSelectionDetailsInteractor.kt | 4 +- .../TrackSelectionDetailsActionDispatcher.kt | 59 ++++++++----------- 3 files changed, 31 insertions(+), 42 deletions(-) diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/progress_screen/presentation/ProgressScreenActionDispatcher.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/progress_screen/presentation/ProgressScreenActionDispatcher.kt index 86fabbc42d..3481e5383d 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/progress_screen/presentation/ProgressScreenActionDispatcher.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/progress_screen/presentation/ProgressScreenActionDispatcher.kt @@ -139,8 +139,8 @@ internal class ProgressScreenActionDispatcher( trackId: Long, forceLoadFromRemote: Boolean ): Result = - coroutineScope { - kotlin.runCatching { + kotlin.runCatching { + coroutineScope { val trackDeferred = async { trackInteractor.getTrack(trackId, forceLoadFromRemote) } @@ -149,7 +149,7 @@ internal class ProgressScreenActionDispatcher( } TrackWithProgress( track = trackDeferred.await().getOrThrow(), - trackProgress = trackProgressDeferred.await().getOrThrow() ?: return@runCatching null + trackProgress = trackProgressDeferred.await().getOrThrow() ?: return@coroutineScope null ) } } @@ -158,8 +158,8 @@ internal class ProgressScreenActionDispatcher( projectId: Long, forceLoadFromRemote: Boolean ): Result = - coroutineScope { - kotlin.runCatching { + kotlin.runCatching { + coroutineScope { val projectDeferred = async { projectsRepository.getProject(projectId, forceLoadFromRemote) } diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/project_selection/details/domain/interactor/ProjectSelectionDetailsInteractor.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/project_selection/details/domain/interactor/ProjectSelectionDetailsInteractor.kt index 4ef8b70ea4..8b16572b88 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/project_selection/details/domain/interactor/ProjectSelectionDetailsInteractor.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/project_selection/details/domain/interactor/ProjectSelectionDetailsInteractor.kt @@ -25,8 +25,8 @@ internal class ProjectSelectionDetailsInteractor( projectId: Long, forceLoadFromNetwork: Boolean ): Result = - coroutineScope { - kotlin.runCatching { + kotlin.runCatching { + coroutineScope { val trackDeferred = async { trackRepository.getTrack(trackId, forceLoadFromNetwork) } val projectDeferred = async { projectsRepository.getProject(projectId, forceLoadFromNetwork) } diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/track_selection/details/presentation/TrackSelectionDetailsActionDispatcher.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/track_selection/details/presentation/TrackSelectionDetailsActionDispatcher.kt index a412c7322a..9bcaf61dfe 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/track_selection/details/presentation/TrackSelectionDetailsActionDispatcher.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/track_selection/details/presentation/TrackSelectionDetailsActionDispatcher.kt @@ -9,9 +9,10 @@ import org.hyperskill.app.profile.domain.repository.ProfileRepository import org.hyperskill.app.providers.domain.repository.ProvidersRepository import org.hyperskill.app.sentry.domain.interactor.SentryInteractor import org.hyperskill.app.sentry.domain.model.transaction.HyperskillSentryTransactionBuilder +import org.hyperskill.app.sentry.domain.withTransaction import org.hyperskill.app.subscriptions.domain.repository.CurrentSubscriptionStateRepository import org.hyperskill.app.track_selection.details.presentation.TrackSelectionDetailsFeature.Action -import org.hyperskill.app.track_selection.details.presentation.TrackSelectionDetailsFeature.FetchAdditionalInfoResult +import org.hyperskill.app.track_selection.details.presentation.TrackSelectionDetailsFeature.InternalAction import org.hyperskill.app.track_selection.details.presentation.TrackSelectionDetailsFeature.Message import ru.nobird.app.presentation.redux.dispatcher.CoroutineActionDispatcher @@ -26,20 +27,9 @@ class TrackSelectionDetailsActionDispatcher( ) : CoroutineActionDispatcher(config.createConfig()) { override suspend fun doSuspendableAction(action: Action) { when (action) { - is TrackSelectionDetailsFeature.InternalAction.FetchAdditionalInfo -> { - val transaction = - HyperskillSentryTransactionBuilder.buildTrackSelectionDetailsScreenRemoteDataLoading() - sentryInteractor.startTransaction(transaction) - val message = fetchProvidersAndSubscription(action) - .getOrElse { - sentryInteractor.finishTransaction(transaction, it) - onNewMessage(TrackSelectionDetailsFeature.TrackSelectionResult.Error) - return - } - sentryInteractor.finishTransaction(transaction) - onNewMessage(message) - } - is TrackSelectionDetailsFeature.InternalAction.SelectTrack -> { + is InternalAction.FetchAdditionalInfo -> + handleFetchAdditionalInfoAction(action, ::onNewMessage) + is InternalAction.SelectTrack -> { val currentProfile = currentProfileStateRepository .getState() .getOrElse { @@ -56,7 +46,7 @@ class TrackSelectionDetailsActionDispatcher( onNewMessage(TrackSelectionDetailsFeature.TrackSelectionResult.Success) } - is TrackSelectionDetailsFeature.InternalAction.LogAnalyticEvent -> { + is InternalAction.LogAnalyticEvent -> { analyticInteractor.logEvent(action.event) } else -> { @@ -65,31 +55,30 @@ class TrackSelectionDetailsActionDispatcher( } } - private suspend fun fetchProvidersAndSubscription( - action: TrackSelectionDetailsFeature.InternalAction.FetchAdditionalInfo - ): Result = - coroutineScope { - runCatching { + private suspend fun handleFetchAdditionalInfoAction( + action: InternalAction.FetchAdditionalInfo, + onNewMessage: (Message) -> Unit + ) { + sentryInteractor.withTransaction( + HyperskillSentryTransactionBuilder.buildTrackSelectionDetailsScreenRemoteDataLoading(), + onError = { TrackSelectionDetailsFeature.FetchAdditionalInfoResult.Error } + ) { + coroutineScope { val providersDeferred = async { - providersRepository - .getProviders(action.providerIds, action.forceLoadFromNetwork) - .getOrThrow() + providersRepository.getProviders(action.providerIds, action.forceLoadFromNetwork) } val subscriptionDeferred = async { - currentSubscriptionStateRepository - .getState(action.forceLoadFromNetwork) - .getOrThrow() + currentSubscriptionStateRepository.getState(action.forceLoadFromNetwork) } val profileDeferred = async { - currentProfileStateRepository - .getState(forceUpdate = false) - .getOrThrow() + currentProfileStateRepository.getState(forceUpdate = false) } - FetchAdditionalInfoResult.Success( - subscriptionType = subscriptionDeferred.await().type, - profile = profileDeferred.await(), - providers = providersDeferred.await() + TrackSelectionDetailsFeature.FetchAdditionalInfoResult.Success( + subscriptionType = subscriptionDeferred.await().getOrThrow().type, + profile = profileDeferred.await().getOrThrow(), + providers = providersDeferred.await().getOrThrow() ) } - } + }.let(onNewMessage) + } } \ No newline at end of file From 16b623376ab557670c9806ac0354b28228f7c9f5 Mon Sep 17 00:00:00 2001 From: github-actions Date: Mon, 26 Feb 2024 01:48:25 +0000 Subject: [PATCH 091/104] Bump build number --- gradle/app.versions.toml | 2 +- .../NotificationServiceExtension/Info.plist | 2 +- .../iosHyperskillApp.xcodeproj/project.pbxproj | 16 ++++++++-------- iosHyperskillApp/iosHyperskillApp/Info.plist | 2 +- .../iosHyperskillAppTests/Info.plist | 2 +- .../iosHyperskillAppUITests/Info.plist | 2 +- 6 files changed, 13 insertions(+), 13 deletions(-) diff --git a/gradle/app.versions.toml b/gradle/app.versions.toml index d4a89f9f40..7334e17955 100644 --- a/gradle/app.versions.toml +++ b/gradle/app.versions.toml @@ -3,4 +3,4 @@ minSdk = '24' targetSdk = '33' compileSdk = '33' versionName = '1.50' -versionCode = '336' \ No newline at end of file +versionCode = '337' \ No newline at end of file diff --git a/iosHyperskillApp/NotificationServiceExtension/Info.plist b/iosHyperskillApp/NotificationServiceExtension/Info.plist index ebd9ba0102..5d6af58277 100644 --- a/iosHyperskillApp/NotificationServiceExtension/Info.plist +++ b/iosHyperskillApp/NotificationServiceExtension/Info.plist @@ -9,7 +9,7 @@ CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleVersion - 342 + 343 CFBundleShortVersionString 1.50 CFBundlePackageType diff --git a/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj b/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj index 4b304341ec..4ead9c19c4 100644 --- a/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj +++ b/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj @@ -5069,7 +5069,7 @@ buildSettings = { CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 342; + CURRENT_PROJECT_VERSION = 343; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = 3DWS674B2M; GENERATE_INFOPLIST_FILE = NO; @@ -5090,7 +5090,7 @@ buildSettings = { CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 342; + CURRENT_PROJECT_VERSION = 343; DEVELOPMENT_TEAM = 3DWS674B2M; GENERATE_INFOPLIST_FILE = NO; INFOPLIST_FILE = iosHyperskillAppUITests/Info.plist; @@ -5111,7 +5111,7 @@ BUNDLE_LOADER = "$(TEST_HOST)"; CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 342; + CURRENT_PROJECT_VERSION = 343; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = 3DWS674B2M; INFOPLIST_FILE = iosHyperskillAppTests/Info.plist; @@ -5132,7 +5132,7 @@ BUNDLE_LOADER = "$(TEST_HOST)"; CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 342; + CURRENT_PROJECT_VERSION = 343; DEVELOPMENT_TEAM = 3DWS674B2M; INFOPLIST_FILE = iosHyperskillAppTests/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; @@ -5153,7 +5153,7 @@ CODE_SIGN_ENTITLEMENTS = NotificationServiceExtension/NotificationServiceExtension.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 342; + CURRENT_PROJECT_VERSION = 343; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = 3DWS674B2M; INFOPLIST_FILE = NotificationServiceExtension/Info.plist; @@ -5181,7 +5181,7 @@ CODE_SIGN_ENTITLEMENTS = NotificationServiceExtension/NotificationServiceExtension.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 342; + CURRENT_PROJECT_VERSION = 343; DEVELOPMENT_TEAM = 3DWS674B2M; INFOPLIST_FILE = NotificationServiceExtension/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; @@ -5326,7 +5326,7 @@ CODE_SIGN_ENTITLEMENTS = iosHyperskillApp/iosHyperskillApp.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 342; + CURRENT_PROJECT_VERSION = 343; DEVELOPMENT_ASSET_PATHS = "\"iosHyperskillApp/Preview Content\""; DEVELOPMENT_TEAM = 3DWS674B2M; ENABLE_PREVIEWS = YES; @@ -5362,7 +5362,7 @@ CODE_SIGN_ENTITLEMENTS = iosHyperskillApp/iosHyperskillApp.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 342; + CURRENT_PROJECT_VERSION = 343; DEVELOPMENT_ASSET_PATHS = "\"iosHyperskillApp/Preview Content\""; DEVELOPMENT_TEAM = 3DWS674B2M; ENABLE_PREVIEWS = YES; diff --git a/iosHyperskillApp/iosHyperskillApp/Info.plist b/iosHyperskillApp/iosHyperskillApp/Info.plist index 4bdca14f29..411d79ee79 100644 --- a/iosHyperskillApp/iosHyperskillApp/Info.plist +++ b/iosHyperskillApp/iosHyperskillApp/Info.plist @@ -36,7 +36,7 @@ CFBundleVersion - 342 + 343 FirebaseAppDelegateProxyEnabled FirebaseMessagingAutoInitEnabled diff --git a/iosHyperskillApp/iosHyperskillAppTests/Info.plist b/iosHyperskillApp/iosHyperskillAppTests/Info.plist index 8bb7eb0f8c..a04acecd11 100644 --- a/iosHyperskillApp/iosHyperskillAppTests/Info.plist +++ b/iosHyperskillApp/iosHyperskillAppTests/Info.plist @@ -15,6 +15,6 @@ CFBundleShortVersionString 1.50 CFBundleVersion - 342 + 343 diff --git a/iosHyperskillApp/iosHyperskillAppUITests/Info.plist b/iosHyperskillApp/iosHyperskillAppUITests/Info.plist index 5e30db3120..794a120645 100644 --- a/iosHyperskillApp/iosHyperskillAppUITests/Info.plist +++ b/iosHyperskillApp/iosHyperskillAppUITests/Info.plist @@ -15,6 +15,6 @@ CFBundleShortVersionString 1.50 CFBundleVersion - 342 + 343 From 12f48487e59475a2691fc3881da500c8a87260e9 Mon Sep 17 00:00:00 2001 From: Ivan Magda Date: Mon, 26 Feb 2024 09:02:12 +0700 Subject: [PATCH 092/104] Shared, iOS: Ask about source of knowledge about Hyperskill (#914) ^ALTAPPS-1146 --- .../main/view/ui/activity/MainActivity.kt | 2 + .../project.pbxproj | 70 ++++++++- .../Publishers+KeyboardIsVisible.swift | 15 ++ .../Sources/Models/Constants/Strings.swift | 14 +- .../Sources/Modules/App/AppViewModel.swift | 12 ++ .../App/ViewControllers/AppRouter.swift | 4 + .../ViewControllers/AppViewController.swift | 2 + .../Views/StepQuizChoiceElementView.swift | 4 +- ...StepQuizTableSelectColumnsColumnView.swift | 39 +++-- .../StepQuizTableSelectColumnsView.swift | 2 +- ...UsersQuestionnaireOnboardingAssembly.swift | 31 ++++ ...uestionnaireOnboardingOutputProtocol.swift | 5 + ...sersQuestionnaireOnboardingViewModel.swift | 41 +++++ ...rsQuestionnaireOnboardingChoicesView.swift | 82 ++++++++++ ...rsQuestionnaireOnboardingContentView.swift | 140 ++++++++++++++++++ ...ersQuestionnaireOnboardingFooterView.swift | 70 +++++++++ .../UsersQuestionnaireOnboardingView.swift | 61 ++++++++ .../UsersQuestionnaireWidgetAssembly.swift | 0 ...ersQuestionnaireWidgetOutputProtocol.swift | 0 .../UsersQuestionnaireWidgetView.swift | 0 .../UsersQuestionnaireWidgetViewModel.swift | 0 .../Views/SwiftUI/CheckboxButton.swift | 21 ++- .../Sources/Views/SwiftUI/RadioButton.swift | 21 ++- .../hyperskill/HyperskillAnalyticRoute.kt | 5 + .../hyperskill/HyperskillAnalyticTarget.kt | 2 + .../hyperskill/app/core/injection/AppGraph.kt | 2 + .../app/core/injection/BaseAppGraph.kt | 5 + .../app/main/presentation/AppReducer.kt | 14 +- ...rsQuestionnaireOnboardingAnalyticParams.kt | 6 + ...ingClickedChoiceHyperskillAnalyticEvent.kt | 41 +++++ ...rdingClickedSendHyperskillAnalyticEvent.kt | 47 ++++++ ...rdingClickedSkipHyperskillAnalyticEvent.kt | 29 ++++ ...OnboardingViewedHyperskillAnalyticEvent.kt | 23 +++ .../UsersQuestionnaireOnboardingComponent.kt | 10 ++ ...ersQuestionnaireOnboardingComponentImpl.kt | 20 +++ ...rsQuestionnaireOnboardingFeatureBuilder.kt | 51 +++++++ ...QuestionnaireOnboardingActionDispatcher.kt | 23 +++ .../UsersQuestionnaireOnboardingFeature.kt | 43 ++++++ .../UsersQuestionnaireOnboardingReducer.kt | 76 ++++++++++ ...sQuestionnaireOnboardingViewStateMapper.kt | 53 +++++++ .../WelcomeOnboardingComponentImpl.kt | 5 +- .../presentation/WelcomeOnboardingFeature.kt | 13 +- .../presentation/WelcomeOnboardingReducer.kt | 91 ++++++------ .../commonMain/resources/MR/base/strings.xml | 19 +++ .../org/hyperskill/main/AppFeatureTest.kt | 2 +- 45 files changed, 1098 insertions(+), 118 deletions(-) create mode 100644 iosHyperskillApp/iosHyperskillApp/Sources/Extensions/Combine/Publishers+KeyboardIsVisible.swift create mode 100644 iosHyperskillApp/iosHyperskillApp/Sources/Modules/UsersQuestionnaire/Onboarding/UsersQuestionnaireOnboardingAssembly.swift create mode 100644 iosHyperskillApp/iosHyperskillApp/Sources/Modules/UsersQuestionnaire/Onboarding/UsersQuestionnaireOnboardingOutputProtocol.swift create mode 100644 iosHyperskillApp/iosHyperskillApp/Sources/Modules/UsersQuestionnaire/Onboarding/UsersQuestionnaireOnboardingViewModel.swift create mode 100644 iosHyperskillApp/iosHyperskillApp/Sources/Modules/UsersQuestionnaire/Onboarding/Views/UsersQuestionnaireOnboardingChoicesView.swift create mode 100644 iosHyperskillApp/iosHyperskillApp/Sources/Modules/UsersQuestionnaire/Onboarding/Views/UsersQuestionnaireOnboardingContentView.swift create mode 100644 iosHyperskillApp/iosHyperskillApp/Sources/Modules/UsersQuestionnaire/Onboarding/Views/UsersQuestionnaireOnboardingFooterView.swift create mode 100644 iosHyperskillApp/iosHyperskillApp/Sources/Modules/UsersQuestionnaire/Onboarding/Views/UsersQuestionnaireOnboardingView.swift rename iosHyperskillApp/iosHyperskillApp/Sources/Modules/{UsersQuestionnaireWidget => UsersQuestionnaire/Widget}/UsersQuestionnaireWidgetAssembly.swift (100%) rename iosHyperskillApp/iosHyperskillApp/Sources/Modules/{UsersQuestionnaireWidget => UsersQuestionnaire/Widget}/UsersQuestionnaireWidgetOutputProtocol.swift (100%) rename iosHyperskillApp/iosHyperskillApp/Sources/Modules/{UsersQuestionnaireWidget => UsersQuestionnaire/Widget}/UsersQuestionnaireWidgetView.swift (100%) rename iosHyperskillApp/iosHyperskillApp/Sources/Modules/{UsersQuestionnaireWidget => UsersQuestionnaire/Widget}/UsersQuestionnaireWidgetViewModel.swift (100%) create mode 100644 shared/src/commonMain/kotlin/org/hyperskill/app/users_questionnaire/onboarding/domain/analytic/UsersQuestionnaireOnboardingAnalyticParams.kt create mode 100644 shared/src/commonMain/kotlin/org/hyperskill/app/users_questionnaire/onboarding/domain/analytic/UsersQuestionnaireOnboardingClickedChoiceHyperskillAnalyticEvent.kt create mode 100644 shared/src/commonMain/kotlin/org/hyperskill/app/users_questionnaire/onboarding/domain/analytic/UsersQuestionnaireOnboardingClickedSendHyperskillAnalyticEvent.kt create mode 100644 shared/src/commonMain/kotlin/org/hyperskill/app/users_questionnaire/onboarding/domain/analytic/UsersQuestionnaireOnboardingClickedSkipHyperskillAnalyticEvent.kt create mode 100644 shared/src/commonMain/kotlin/org/hyperskill/app/users_questionnaire/onboarding/domain/analytic/UsersQuestionnaireOnboardingViewedHyperskillAnalyticEvent.kt create mode 100644 shared/src/commonMain/kotlin/org/hyperskill/app/users_questionnaire/onboarding/injection/UsersQuestionnaireOnboardingComponent.kt create mode 100644 shared/src/commonMain/kotlin/org/hyperskill/app/users_questionnaire/onboarding/injection/UsersQuestionnaireOnboardingComponentImpl.kt create mode 100644 shared/src/commonMain/kotlin/org/hyperskill/app/users_questionnaire/onboarding/injection/UsersQuestionnaireOnboardingFeatureBuilder.kt create mode 100644 shared/src/commonMain/kotlin/org/hyperskill/app/users_questionnaire/onboarding/presentation/UsersQuestionnaireOnboardingActionDispatcher.kt create mode 100644 shared/src/commonMain/kotlin/org/hyperskill/app/users_questionnaire/onboarding/presentation/UsersQuestionnaireOnboardingFeature.kt create mode 100644 shared/src/commonMain/kotlin/org/hyperskill/app/users_questionnaire/onboarding/presentation/UsersQuestionnaireOnboardingReducer.kt create mode 100644 shared/src/commonMain/kotlin/org/hyperskill/app/users_questionnaire/onboarding/view/mapper/UsersQuestionnaireOnboardingViewStateMapper.kt diff --git a/androidHyperskillApp/src/main/java/org/hyperskill/app/android/main/view/ui/activity/MainActivity.kt b/androidHyperskillApp/src/main/java/org/hyperskill/app/android/main/view/ui/activity/MainActivity.kt index 1888cce220..c923c9cd9f 100644 --- a/androidHyperskillApp/src/main/java/org/hyperskill/app/android/main/view/ui/activity/MainActivity.kt +++ b/androidHyperskillApp/src/main/java/org/hyperskill/app/android/main/view/ui/activity/MainActivity.kt @@ -251,6 +251,8 @@ class MainActivity : ) WelcomeOnboardingFeature.Action.ViewAction.NavigateTo.NotificationOnboardingScreen -> router.newRootScreen(NotificationsOnboardingScreen) + WelcomeOnboardingFeature.Action.ViewAction.NavigateTo.UsersQuestionnaireOnboardingScreen -> + TODO("ALTAPPS-1145: Implement QuestionnaireOnboardingScreen navigation") } is AppFeature.Action.ViewAction.StreakRecoveryViewAction -> StreakRecoveryViewActionDelegate.handleViewAction( diff --git a/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj b/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj index d1ddf8de99..89cae2adef 100644 --- a/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj +++ b/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj @@ -12,14 +12,18 @@ 058557BB273AAA24004C7B11 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 058557BA273AAA24004C7B11 /* Assets.xcassets */; }; 058557D9273AAEEB004C7B11 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 058557D8273AAEEB004C7B11 /* Preview Assets.xcassets */; }; 0809817CFCC9D4C45457B3C8 /* ProgressScreenAssembly.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9AACF19B25D42FD4AE322D5A /* ProgressScreenAssembly.swift */; }; + 09A3FA31C3ABC65467D36662 /* UsersQuestionnaireOnboardingAssembly.swift in Sources */ = {isa = PBXBuildFile; fileRef = D80660C909D729D12FEAB845 /* UsersQuestionnaireOnboardingAssembly.swift */; }; 0C3BB55AA2B8FB7F5ED9CADB /* InterviewPreparationOnboardingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A7D7125CEB88C2B8E29ABBB /* InterviewPreparationOnboardingView.swift */; }; 0F98394636E12DEC98B7953A /* RequestReviewModalAssembly.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4FF61AAE06DC019B8C49543C /* RequestReviewModalAssembly.swift */; }; + 1CAC118437C9C9910D39009E /* UsersQuestionnaireOnboardingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5774D657D505A3A80A7B60D /* UsersQuestionnaireOnboardingView.swift */; }; 2C005DCC27EF5B0300DC6503 /* GoogleServiceInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C005DCB27EF5B0300DC6503 /* GoogleServiceInfo.swift */; }; 2C0146AA28FDF2350083DA9C /* StepQuizCodeFullScreenInputProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C0146A928FDF2350083DA9C /* StepQuizCodeFullScreenInputProtocol.swift */; }; 2C023C86285D927A00D2D5A9 /* StepQuizTableAssembly.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C023C85285D927A00D2D5A9 /* StepQuizTableAssembly.swift */; }; 2C023C88285D928100D2D5A9 /* StepQuizTableViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C023C87285D928100D2D5A9 /* StepQuizTableViewModel.swift */; }; 2C023C8B285DCA2100D2D5A9 /* ReplyExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C023C8A285DCA2100D2D5A9 /* ReplyExtensions.swift */; }; 2C023C8D285DCA4300D2D5A9 /* DatasetExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C023C8C285DCA4300D2D5A9 /* DatasetExtensions.swift */; }; + 2C0409812B85FC3000E9CF41 /* UsersQuestionnaireOnboardingOutputProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C0409802B85FC3000E9CF41 /* UsersQuestionnaireOnboardingOutputProtocol.swift */; }; + 2C0409842B863EA600E9CF41 /* Publishers+KeyboardIsVisible.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C0409832B863EA600E9CF41 /* Publishers+KeyboardIsVisible.swift */; }; 2C05AC462A0E9EBC0039C7EF /* ProjectSelectionListFeatureViewStateKsExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C05AC452A0E9EBC0039C7EF /* ProjectSelectionListFeatureViewStateKsExtensions.swift */; }; 2C05AC482A0EA0180039C7EF /* ProjectSelectionListAssembly.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C05AC472A0EA0180039C7EF /* ProjectSelectionListAssembly.swift */; }; 2C05AC4A2A0EA0290039C7EF /* ProjectSelectionListViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C05AC492A0EA0290039C7EF /* ProjectSelectionListViewModel.swift */; }; @@ -82,6 +86,9 @@ 2C186ADB2B46989700DADB26 /* TopicsRepetitionsCountView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C186ADA2B46989700DADB26 /* TopicsRepetitionsCountView.swift */; }; 2C186ADD2B46A08E00DADB26 /* InterviewPreparationCompletedModalViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C186ADC2B46A08E00DADB26 /* InterviewPreparationCompletedModalViewController.swift */; }; 2C186ADF2B46A0A300DADB26 /* InterviewPreparationCompletedModalView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C186ADE2B46A0A300DADB26 /* InterviewPreparationCompletedModalView.swift */; }; + 2C195D042B8467EA0076B2C8 /* UsersQuestionnaireOnboardingContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C195D032B8467EA0076B2C8 /* UsersQuestionnaireOnboardingContentView.swift */; }; + 2C195D062B8482840076B2C8 /* UsersQuestionnaireOnboardingFooterView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C195D052B8482840076B2C8 /* UsersQuestionnaireOnboardingFooterView.swift */; }; + 2C195D092B84863F0076B2C8 /* UsersQuestionnaireOnboardingChoicesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C195D082B84863F0076B2C8 /* UsersQuestionnaireOnboardingChoicesView.swift */; }; 2C198DFE2AEA444100DCD35A /* FillBlanksSelectContainerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C198DFD2AEA444100DCD35A /* FillBlanksSelectContainerView.swift */; }; 2C198E012AEA835F00DCD35A /* StepQuizFillBlanksSelectOptionsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C198E002AEA835F00DCD35A /* StepQuizFillBlanksSelectOptionsView.swift */; }; 2C198E032AEA869300DCD35A /* StepQuizFillBlanksSelectOptionsCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C198E022AEA869300DCD35A /* StepQuizFillBlanksSelectOptionsCollectionViewCell.swift */; }; @@ -520,6 +527,7 @@ 2CFD7C6A2925447600902748 /* StepQuizFeatureStateKsExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CFD7C692925447600902748 /* StepQuizFeatureStateKsExtensions.swift */; }; 40D8E6EFE44EB7A6092C171B /* Pods_iosHyperskillApp.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9C0F8A86D62CB915A1E49CAA /* Pods_iosHyperskillApp.framework */; }; 4F5F2FD2F3BCAC06612FCAE8 /* InterviewPreparationOnboardingAssembly.swift in Sources */ = {isa = PBXBuildFile; fileRef = B431D493DED8999E3F3B6968 /* InterviewPreparationOnboardingAssembly.swift */; }; + 4FBE20D41C99246C44E068AA /* UsersQuestionnaireOnboardingViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 772BEE130815F1450D253FE3 /* UsersQuestionnaireOnboardingViewModel.swift */; }; 59B66CD4D1508049555D35AE /* ProgressScreenView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CCC18157582494D2909B214C /* ProgressScreenView.swift */; }; 60B4F143CF507F83C9581020 /* LeaderboardViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E205DEF27554501F7BE01AA /* LeaderboardViewModel.swift */; }; 63FC2C36279DBA43CCEA1360 /* InterviewPreparationOnboardingViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D9224BDAA50119E9135E1B74 /* InterviewPreparationOnboardingViewModel.swift */; }; @@ -709,6 +717,8 @@ 2C023C87285D928100D2D5A9 /* StepQuizTableViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StepQuizTableViewModel.swift; sourceTree = ""; }; 2C023C8A285DCA2100D2D5A9 /* ReplyExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReplyExtensions.swift; sourceTree = ""; }; 2C023C8C285DCA4300D2D5A9 /* DatasetExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DatasetExtensions.swift; sourceTree = ""; }; + 2C0409802B85FC3000E9CF41 /* UsersQuestionnaireOnboardingOutputProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UsersQuestionnaireOnboardingOutputProtocol.swift; sourceTree = ""; }; + 2C0409832B863EA600E9CF41 /* Publishers+KeyboardIsVisible.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Publishers+KeyboardIsVisible.swift"; sourceTree = ""; }; 2C05AC452A0E9EBC0039C7EF /* ProjectSelectionListFeatureViewStateKsExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProjectSelectionListFeatureViewStateKsExtensions.swift; sourceTree = ""; }; 2C05AC472A0EA0180039C7EF /* ProjectSelectionListAssembly.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProjectSelectionListAssembly.swift; sourceTree = ""; }; 2C05AC492A0EA0290039C7EF /* ProjectSelectionListViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProjectSelectionListViewModel.swift; sourceTree = ""; }; @@ -772,6 +782,9 @@ 2C186ADA2B46989700DADB26 /* TopicsRepetitionsCountView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TopicsRepetitionsCountView.swift; sourceTree = ""; }; 2C186ADC2B46A08E00DADB26 /* InterviewPreparationCompletedModalViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InterviewPreparationCompletedModalViewController.swift; sourceTree = ""; }; 2C186ADE2B46A0A300DADB26 /* InterviewPreparationCompletedModalView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InterviewPreparationCompletedModalView.swift; sourceTree = ""; }; + 2C195D032B8467EA0076B2C8 /* UsersQuestionnaireOnboardingContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UsersQuestionnaireOnboardingContentView.swift; sourceTree = ""; }; + 2C195D052B8482840076B2C8 /* UsersQuestionnaireOnboardingFooterView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UsersQuestionnaireOnboardingFooterView.swift; sourceTree = ""; }; + 2C195D082B84863F0076B2C8 /* UsersQuestionnaireOnboardingChoicesView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UsersQuestionnaireOnboardingChoicesView.swift; sourceTree = ""; }; 2C198DFD2AEA444100DCD35A /* FillBlanksSelectContainerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FillBlanksSelectContainerView.swift; sourceTree = ""; }; 2C198E002AEA835F00DCD35A /* StepQuizFillBlanksSelectOptionsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StepQuizFillBlanksSelectOptionsView.swift; sourceTree = ""; }; 2C198E022AEA869300DCD35A /* StepQuizFillBlanksSelectOptionsCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StepQuizFillBlanksSelectOptionsCollectionViewCell.swift; sourceTree = ""; }; @@ -1227,16 +1240,19 @@ 71D01125D308034C53D75DA6 /* ProjectSelectionDetailsView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = ProjectSelectionDetailsView.swift; sourceTree = ""; }; 7555FF7B242A565900829871 /* iosHyperskillApp.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = iosHyperskillApp.app; sourceTree = BUILT_PRODUCTS_DIR; }; 7555FF8C242A565B00829871 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 772BEE130815F1450D253FE3 /* UsersQuestionnaireOnboardingViewModel.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = UsersQuestionnaireOnboardingViewModel.swift; sourceTree = ""; }; 7A7D7125CEB88C2B8E29ABBB /* InterviewPreparationOnboardingView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = InterviewPreparationOnboardingView.swift; sourceTree = ""; }; 7F55BD539626D22DCF0E1344 /* SearchAssembly.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = SearchAssembly.swift; sourceTree = ""; }; 907B10B0F7D4970530A478A2 /* SearchView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = SearchView.swift; sourceTree = ""; }; 9AACF19B25D42FD4AE322D5A /* ProgressScreenAssembly.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = ProgressScreenAssembly.swift; sourceTree = ""; }; 9C0F8A86D62CB915A1E49CAA /* Pods_iosHyperskillApp.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_iosHyperskillApp.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + A5774D657D505A3A80A7B60D /* UsersQuestionnaireOnboardingView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = UsersQuestionnaireOnboardingView.swift; sourceTree = ""; }; B431D493DED8999E3F3B6968 /* InterviewPreparationOnboardingAssembly.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = InterviewPreparationOnboardingAssembly.swift; sourceTree = ""; }; C2065D585FD89A96C31C08BC /* TrackSelectionDetailsView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = TrackSelectionDetailsView.swift; sourceTree = ""; }; C2D19C8A442CF9C4F00370B8 /* NotificationsOnboardingAssembly.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = NotificationsOnboardingAssembly.swift; sourceTree = ""; }; CCC18157582494D2909B214C /* ProgressScreenView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = ProgressScreenView.swift; sourceTree = ""; }; CCFDFC2C226D2B79DE30D811 /* LeaderboardAssembly.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = LeaderboardAssembly.swift; sourceTree = ""; }; + D80660C909D729D12FEAB845 /* UsersQuestionnaireOnboardingAssembly.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = UsersQuestionnaireOnboardingAssembly.swift; sourceTree = ""; }; D9224BDAA50119E9135E1B74 /* InterviewPreparationOnboardingViewModel.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = InterviewPreparationOnboardingViewModel.swift; sourceTree = ""; }; E3570563AEEEEF2F5495BCA6 /* NotificationsOnboardingViewModel.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = NotificationsOnboardingViewModel.swift; sourceTree = ""; }; E900D10028434D0400A77BBC /* StepQuizSortingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StepQuizSortingView.swift; sourceTree = ""; }; @@ -1452,6 +1468,14 @@ path = Shared; sourceTree = ""; }; + 2C0409822B863E8A00E9CF41 /* Combine */ = { + isa = PBXGroup; + children = ( + 2C0409832B863EA600E9CF41 /* Publishers+KeyboardIsVisible.swift */, + ); + path = Combine; + sourceTree = ""; + }; 2C05AC442A0E9E910039C7EF /* List */ = { isa = PBXGroup; children = ( @@ -1674,6 +1698,17 @@ path = InterviewPreparationCompleted; sourceTree = ""; }; + 2C195D072B8483EA0076B2C8 /* Views */ = { + isa = PBXGroup; + children = ( + 2C195D082B84863F0076B2C8 /* UsersQuestionnaireOnboardingChoicesView.swift */, + 2C195D032B8467EA0076B2C8 /* UsersQuestionnaireOnboardingContentView.swift */, + 2C195D052B8482840076B2C8 /* UsersQuestionnaireOnboardingFooterView.swift */, + A5774D657D505A3A80A7B60D /* UsersQuestionnaireOnboardingView.swift */, + ); + path = Views; + sourceTree = ""; + }; 2C198DFC2AEA441E00DCD35A /* Select */ = { isa = PBXGroup; children = ( @@ -1747,7 +1782,7 @@ E9F0A2A729D416AC00C4A61E /* StudyPlan */, E9A022AB291D0E1C004317DB /* TopicsRepetitions */, 2C2600862A2001E600BD3D39 /* TrackSelection */, - 2CACBCBA2B7A1292006D3AB2 /* UsersQuestionnaireWidget */, + 2CB559302B87024000D949CB /* UsersQuestionnaire */, E9F923F428A2632800C065A7 /* Welcome */, ); path = Modules; @@ -2882,6 +2917,7 @@ 2C9B66E827ECAF0200569645 /* Extensions */ = { isa = PBXGroup; children = ( + 2C0409822B863E8A00E9CF41 /* Combine */, 2C1F586C280D09A100372A37 /* Foundation */, 2C023C89285DC9F800D2D5A9 /* Shared */, 2CF4340E28126886002893CD /* SwiftStdlib */, @@ -3022,7 +3058,7 @@ path = ProblemOnboarding; sourceTree = ""; }; - 2CACBCBA2B7A1292006D3AB2 /* UsersQuestionnaireWidget */ = { + 2CACBCBA2B7A1292006D3AB2 /* Widget */ = { isa = PBXGroup; children = ( 2CACBCC12B7A3E4E006D3AB2 /* UsersQuestionnaireWidgetAssembly.swift */, @@ -3030,7 +3066,7 @@ 2CACBCBB2B7A12F1006D3AB2 /* UsersQuestionnaireWidgetView.swift */, 2CACBCBD2B7A1365006D3AB2 /* UsersQuestionnaireWidgetViewModel.swift */, ); - path = UsersQuestionnaireWidget; + path = Widget; sourceTree = ""; }; 2CAE8CEE280525A100E6C83D /* Step */ = { @@ -3135,6 +3171,15 @@ path = Routers; sourceTree = ""; }; + 2CB559302B87024000D949CB /* UsersQuestionnaire */ = { + isa = PBXGroup; + children = ( + C054D95AE78230BD225678B6 /* Onboarding */, + 2CACBCBA2B7A1292006D3AB2 /* Widget */, + ); + path = UsersQuestionnaire; + sourceTree = ""; + }; 2CBC97C42A5543190078E445 /* StageCompleted */ = { isa = PBXGroup; children = ( @@ -3785,6 +3830,17 @@ name = Frameworks; sourceTree = ""; }; + C054D95AE78230BD225678B6 /* Onboarding */ = { + isa = PBXGroup; + children = ( + D80660C909D729D12FEAB845 /* UsersQuestionnaireOnboardingAssembly.swift */, + 2C0409802B85FC3000E9CF41 /* UsersQuestionnaireOnboardingOutputProtocol.swift */, + 772BEE130815F1450D253FE3 /* UsersQuestionnaireOnboardingViewModel.swift */, + 2C195D072B8483EA0076B2C8 /* Views */, + ); + path = Onboarding; + sourceTree = ""; + }; E6992F3BBF430924F32DC178 /* Leaderboard */ = { isa = PBXGroup; children = ( @@ -4527,6 +4583,7 @@ E9C3506F2886D0600080D277 /* OpenURLInsideAppButton.swift in Sources */, 2C23C0062879EA7D0083709F /* StreakDayState.swift in Sources */, 2C963BC72812D1BF0036DD53 /* HomeAssembly.swift in Sources */, + 2C0409842B863EA600E9CF41 /* Publishers+KeyboardIsVisible.swift in Sources */, 2C1860FC2923C540007D4EBF /* AppFeatureStateKsExtensions.swift in Sources */, 2C4FBD8E2876C94800ACA5C8 /* ProfileAboutSocialAccountsView.swift in Sources */, 2C05AC542A0EC5B00039C7EF /* ProjectSelectionListHeaderView.swift in Sources */, @@ -4678,6 +4735,7 @@ 2CAE8CF728052F9600E6C83D /* StepHeaderView.swift in Sources */, 2CF87DA229B718800092FF83 /* IntrospectViewController.swift in Sources */, 2CFD32462AAEFC6C00B9B6EA /* AppGraph+DefaultInstances.swift in Sources */, + 2C195D042B8467EA0076B2C8 /* UsersQuestionnaireOnboardingContentView.swift in Sources */, 2C96744228883A180091B6C9 /* StepQuizCodeViewDataMapper.swift in Sources */, 2C9E5E8629B215CA003AEC16 /* StageImplementViewModel.swift in Sources */, 2CAF254C2AB9C2E500595582 /* ShineEffect.swift in Sources */, @@ -4810,6 +4868,7 @@ E900D10328434E0D00A77BBC /* StepQuizSortingItemView.swift in Sources */, 2C54E42F2A1FAA88003406B9 /* TrackSelectionDetailsProvidersView.swift in Sources */, E996D41029221F0E00A47498 /* TopicsRepetitionsRepeatBlock.swift in Sources */, + 2C195D092B84863F0076B2C8 /* UsersQuestionnaireOnboardingChoicesView.swift in Sources */, E96E80F627EF57BA00AA6683 /* AuthSocialViewModel.swift in Sources */, E9A22BA0295081BD001700B7 /* StreakFreezeModalViewController.swift in Sources */, 2CBD1917291D392400F5FB0B /* UIView+Animations.swift in Sources */, @@ -4875,6 +4934,7 @@ 2C5837A32B2844E20096B89B /* SearchPlaceholderSuggestionsView.swift in Sources */, 2CD316C028A3B2040002B2B2 /* ApplicationTheme+SharedTheme.swift in Sources */, 2C1061A4285C34C900EBD614 /* StepQuizChildQuizOutputProtocol.swift in Sources */, + 2C0409812B85FC3000E9CF41 /* UsersQuestionnaireOnboardingOutputProtocol.swift in Sources */, 2C20FBA4284F165A006D879E /* ProcessedContent.swift in Sources */, 2C66720D2A5299C30040EA2F /* ProgressScreenCardSkeletonView.swift in Sources */, 2C7CB67E2ADFDA62006F78DA /* FillBlanksInputContainerView.swift in Sources */, @@ -5020,6 +5080,7 @@ 043790C380B462AFEB2B13BC /* LeaderboardAssembly.swift in Sources */, BAEC674E5161E8C7A10ADAAB /* LeaderboardView.swift in Sources */, 60B4F143CF507F83C9581020 /* LeaderboardViewModel.swift in Sources */, + 2C195D062B8482840076B2C8 /* UsersQuestionnaireOnboardingFooterView.swift in Sources */, 8E154CD6AF7D45A2CA013F85 /* SearchAssembly.swift in Sources */, 7A628C36D862C98ED2046D4F /* SearchView.swift in Sources */, ED49113F88FF32AAFE6AFFBC /* SearchViewModel.swift in Sources */, @@ -5029,6 +5090,9 @@ 0F98394636E12DEC98B7953A /* RequestReviewModalAssembly.swift in Sources */, 2C829B912B88583300765335 /* StepQuizUnsupportedView.swift in Sources */, 91046416561EE431760D7D48 /* RequestReviewModalViewModel.swift in Sources */, + 09A3FA31C3ABC65467D36662 /* UsersQuestionnaireOnboardingAssembly.swift in Sources */, + 1CAC118437C9C9910D39009E /* UsersQuestionnaireOnboardingView.swift in Sources */, + 4FBE20D41C99246C44E068AA /* UsersQuestionnaireOnboardingViewModel.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Extensions/Combine/Publishers+KeyboardIsVisible.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Extensions/Combine/Publishers+KeyboardIsVisible.swift new file mode 100644 index 0000000000..832728bc82 --- /dev/null +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Extensions/Combine/Publishers+KeyboardIsVisible.swift @@ -0,0 +1,15 @@ +import Combine +import UIKit + +extension Publishers { + static var keyboardIsVisible: AnyPublisher { + let willShow = NotificationCenter.default.publisher(for: UIApplication.keyboardWillShowNotification) + .map { _ in true } + + let willHide = NotificationCenter.default.publisher(for: UIApplication.keyboardWillHideNotification) + .map { _ in false } + + return MergeMany(willShow, willHide) + .eraseToAnyPublisher() + } +} diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Models/Constants/Strings.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Models/Constants/Strings.swift index a293841a6e..75feea9b89 100644 --- a/iosHyperskillApp/iosHyperskillApp/Sources/Models/Constants/Strings.swift +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Models/Constants/Strings.swift @@ -261,12 +261,24 @@ enum Strings { static let networkError = sharedStrings.challenge_widget_network_error_text.localized() } - // MARK: - Users questionnaire widget - + // MARK: - Users questionnaire - + + // MARK: Widget enum UsersQuestionnaireWidget { static let title = sharedStrings.users_questionnaire_widget_title.localized() } + // MARK: Onboarding + + enum UsersQuestionnaireOnboarding { + static let textInputPlaceholder = + sharedStrings.users_questionnaire_onboarding_text_input_placeholder.localized() + + static let sendButtot = sharedStrings.users_questionnaire_onboarding_send_button_text.localized() + static let skipButton = sharedStrings.users_questionnaire_onboarding_skip_button_text.localized() + } + // MARK: - Interview Preparation - // MARK: Widget diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/App/AppViewModel.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/App/AppViewModel.swift index 34b83a365e..77aa7aeb2e 100644 --- a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/App/AppViewModel.swift +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/App/AppViewModel.swift @@ -135,6 +135,18 @@ extension AppViewModel: NotificationsOnboardingOutputProtocol { } } +// MARK: - AppViewModel: UsersQuestionnaireOnboardingOutputProtocol - + +extension AppViewModel: UsersQuestionnaireOnboardingOutputProtocol { + func handleUsersQuestionnaireOnboardingCompleted() { + onNewMessage( + AppFeatureMessageWelcomeOnboardingMessage( + message: WelcomeOnboardingFeatureMessageUsersQuestionnaireOnboardingCompleted() + ) + ) + } +} + // MARK: - AppViewModel: FirstProblemOnboardingOutputProtocol - extension AppViewModel: FirstProblemOnboardingOutputProtocol { diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/App/ViewControllers/AppRouter.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/App/ViewControllers/AppRouter.swift index 91d2cac7f0..dd3ef6c8ed 100644 --- a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/App/ViewControllers/AppRouter.swift +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/App/ViewControllers/AppRouter.swift @@ -68,6 +68,9 @@ final class AppRouter { case .notificationOnboarding(let moduleOutput): let assembly = NotificationsOnboardingAssembly(output: moduleOutput) return assembly.makeModule() + case .usersQuestionnaireOnboarding(let moduleOutput): + let assembly = UsersQuestionnaireOnboardingAssembly(moduleOutput: moduleOutput) + return assembly.makeModule() } }() @@ -143,6 +146,7 @@ final class AppRouter { case onboarding(moduleOutput: WelcomeOutputProtocol?) case firstProblemOnboarding(isNewUserMode: Bool, moduleOutput: FirstProblemOnboardingOutputProtocol?) case notificationOnboarding(moduleOutput: NotificationsOnboardingOutputProtocol?) + case usersQuestionnaireOnboarding(moduleOutput: UsersQuestionnaireOnboardingOutputProtocol?) } enum Animation { diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/App/ViewControllers/AppViewController.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/App/ViewControllers/AppViewController.swift index 558f1ce854..79127c5021 100644 --- a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/App/ViewControllers/AppViewController.swift +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/App/ViewControllers/AppViewController.swift @@ -220,6 +220,8 @@ extension AppViewController: AppViewControllerProtocol { router.route(.notificationOnboarding(moduleOutput: viewModel)) case .studyPlanWithStep(let data): router.route(.studyPlanWithStep(appTabBarControllerDelegate: viewModel, stepRoute: data.stepRoute)) + case .usersQuestionnaireOnboardingScreen: + router.route(.usersQuestionnaireOnboarding(moduleOutput: viewModel)) } } } diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizChoice/Views/StepQuizChoiceElementView.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizChoice/Views/StepQuizChoiceElementView.swift index 06c8efaec7..aea59d7ac5 100644 --- a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizChoice/Views/StepQuizChoiceElementView.swift +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizChoice/Views/StepQuizChoiceElementView.swift @@ -25,10 +25,10 @@ struct StepQuizChoiceElementView: View { Button(action: onTap) { HStack(spacing: appearance.interItemSpacing) { if isMultipleChoice { - CheckboxButton(isSelected: .constant(isSelected), onClick: onTap) + CheckboxButton(isSelected: isSelected, onClick: onTap) .frame(widthHeight: appearance.checkboxIndicatorWidthHeight) } else { - RadioButton(isSelected: .constant(isSelected), onClick: onTap) + RadioButton(isSelected: isSelected, onClick: onTap) .frame(widthHeight: appearance.radioIndicatorWidthHeight) } diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizTableSelectColumns/Views/StepQuizTableSelectColumnsColumnView.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizTableSelectColumns/Views/StepQuizTableSelectColumnsColumnView.swift index 6375bb0d6a..1554ac4d2b 100644 --- a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizTableSelectColumns/Views/StepQuizTableSelectColumnsColumnView.swift +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizTableSelectColumns/Views/StepQuizTableSelectColumnsColumnView.swift @@ -14,7 +14,7 @@ extension StepQuizTableSelectColumnsColumnView { struct StepQuizTableSelectColumnsColumnView: View { private(set) var appearance = Appearance() - var isSelected: Binding + let isSelected: Bool let text: String @@ -37,7 +37,7 @@ struct StepQuizTableSelectColumnsColumnView: View { } @ViewBuilder - private func buildIndicator(isSelected: Binding, onTap: @escaping () -> Void) -> some View { + private func buildIndicator(isSelected: Bool, onTap: @escaping () -> Void) -> some View { if isMultipleChoice { CheckboxButton( appearance: .init(backgroundUnselectedColor: .clear), @@ -56,23 +56,22 @@ struct StepQuizTableSelectColumnsColumnView: View { } } -struct StepQuizTableSelectColumnsColumnView_Previews: PreviewProvider { - static var previews: some View { - Group { - StepQuizTableSelectColumnsColumnView( - isSelected: .constant(true), - text: "Some option", - isMultipleChoice: false, - onTap: {} - ) - StepQuizTableSelectColumnsColumnView( - isSelected: .constant(true), - text: "Some option", - isMultipleChoice: true, - onTap: {} - ) - } - .previewLayout(.sizeThatFits) - .padding() +#if DEBUG +#Preview { + VStack { + StepQuizTableSelectColumnsColumnView( + isSelected: true, + text: "Some option", + isMultipleChoice: false, + onTap: {} + ) + StepQuizTableSelectColumnsColumnView( + isSelected: true, + text: "Some option", + isMultipleChoice: true, + onTap: {} + ) } + .padding() } +#endif diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizTableSelectColumns/Views/StepQuizTableSelectColumnsView.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizTableSelectColumns/Views/StepQuizTableSelectColumnsView.swift index ccc52af19b..af318b2bde 100644 --- a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizTableSelectColumns/Views/StepQuizTableSelectColumnsView.swift +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizTableSelectColumns/Views/StepQuizTableSelectColumnsView.swift @@ -47,7 +47,7 @@ struct StepQuizTableSelectColumnsView: View { VStack(alignment: .leading, spacing: 0) { ForEach(columns) { column in StepQuizTableSelectColumnsColumnView( - isSelected: .constant(selectedColumnsIDs.contains(column.id)), + isSelected: selectedColumnsIDs.contains(column.id), text: column.text, isMultipleChoice: isMultipleChoice, onTap: { diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/UsersQuestionnaire/Onboarding/UsersQuestionnaireOnboardingAssembly.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/UsersQuestionnaire/Onboarding/UsersQuestionnaireOnboardingAssembly.swift new file mode 100644 index 0000000000..f1a60c0acb --- /dev/null +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/UsersQuestionnaire/Onboarding/UsersQuestionnaireOnboardingAssembly.swift @@ -0,0 +1,31 @@ +import shared +import SwiftUI + +final class UsersQuestionnaireOnboardingAssembly: UIKitAssembly { + private weak var moduleOutput: UsersQuestionnaireOnboardingOutputProtocol? + + init(moduleOutput: UsersQuestionnaireOnboardingOutputProtocol?) { + self.moduleOutput = moduleOutput + } + + func makeModule() -> UIViewController { + let usersQuestionnaireOnboardingComponent = + AppGraphBridge.sharedAppGraph.buildUsersQuestionnaireOnboardingComponent() + + let usersQuestionnaireOnboardingViewModel = UsersQuestionnaireOnboardingViewModel( + feature: usersQuestionnaireOnboardingComponent.usersQuestionnaireOnboardingFeature + ) + usersQuestionnaireOnboardingViewModel.moduleOutput = moduleOutput + + let usersQuestionnaireOnboardingView = UsersQuestionnaireOnboardingView( + viewModel: usersQuestionnaireOnboardingViewModel + ) + + let hostingController = StyledHostingController( + rootView: usersQuestionnaireOnboardingView + ) + hostingController.navigationItem.largeTitleDisplayMode = .never + + return hostingController + } +} diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/UsersQuestionnaire/Onboarding/UsersQuestionnaireOnboardingOutputProtocol.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/UsersQuestionnaire/Onboarding/UsersQuestionnaireOnboardingOutputProtocol.swift new file mode 100644 index 0000000000..a2822654fd --- /dev/null +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/UsersQuestionnaire/Onboarding/UsersQuestionnaireOnboardingOutputProtocol.swift @@ -0,0 +1,5 @@ +import Foundation + +protocol UsersQuestionnaireOnboardingOutputProtocol: AnyObject { + func handleUsersQuestionnaireOnboardingCompleted() +} diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/UsersQuestionnaire/Onboarding/UsersQuestionnaireOnboardingViewModel.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/UsersQuestionnaire/Onboarding/UsersQuestionnaireOnboardingViewModel.swift new file mode 100644 index 0000000000..1449dd1e69 --- /dev/null +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/UsersQuestionnaire/Onboarding/UsersQuestionnaireOnboardingViewModel.swift @@ -0,0 +1,41 @@ +import Foundation +import shared + +final class UsersQuestionnaireOnboardingViewModel: FeatureViewModel< + UsersQuestionnaireOnboardingFeature.ViewState, + UsersQuestionnaireOnboardingFeatureMessage, + UsersQuestionnaireOnboardingFeatureActionViewAction +> { + weak var moduleOutput: UsersQuestionnaireOnboardingOutputProtocol? + + override func shouldNotifyStateDidChange( + oldState: UsersQuestionnaireOnboardingFeature.ViewState, + newState: UsersQuestionnaireOnboardingFeature.ViewState + ) -> Bool { + !oldState.isEqual(newState) + } + + func selectChoice(_ choice: String) { + onNewMessage(UsersQuestionnaireOnboardingFeatureMessageClickedChoice(choice: choice)) + } + + func doTextInputValueChanged(_ value: String) { + onNewMessage(UsersQuestionnaireOnboardingFeatureMessageTextInputValueChanged(text: value)) + } + + func doSend() { + onNewMessage(UsersQuestionnaireOnboardingFeatureMessageSendButtonClicked()) + } + + func doSkip() { + onNewMessage(UsersQuestionnaireOnboardingFeatureMessageSkipButtonClicked()) + } + + func doCompleteOnboarding() { + moduleOutput?.handleUsersQuestionnaireOnboardingCompleted() + } + + func logViewedEvent() { + onNewMessage(UsersQuestionnaireOnboardingFeatureMessageViewedEventMessage()) + } +} diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/UsersQuestionnaire/Onboarding/Views/UsersQuestionnaireOnboardingChoicesView.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/UsersQuestionnaire/Onboarding/Views/UsersQuestionnaireOnboardingChoicesView.swift new file mode 100644 index 0000000000..0ef2787f17 --- /dev/null +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/UsersQuestionnaire/Onboarding/Views/UsersQuestionnaireOnboardingChoicesView.swift @@ -0,0 +1,82 @@ +import SwiftUI + +extension UsersQuestionnaireOnboardingChoicesView { + struct Appearance { + var spacing = LayoutInsets.smallInset + + let textFont = UIFont.preferredFont(forTextStyle: .body) + var radioButtonSize: CGFloat { + textFont.pointSize * 1.15 + } + + let selectedTextColor = Color.newPrimaryText + let unselectedTextColor = Color.newSecondaryText + } +} + +struct UsersQuestionnaireOnboardingChoicesView: View { + private(set) var appearance = Appearance() + + let choices: [String] + let selectedChoice: String? + + let onTap: (String) -> Void + + var body: some View { + VStack(spacing: appearance.spacing) { + ForEach(choices, id: \.self) { choice in + Button( + action: { + withAnimation { + handleTap(on: choice) + } + }, + label: { + let isSelected = choice == selectedChoice + + HStack(spacing: appearance.spacing) { + RadioButton( + appearance: .init( + borderUnselectedColor: appearance.unselectedTextColor + ), + isSelected: isSelected, + onClick: { + withAnimation { + handleTap(on: choice) + } + } + ) + .frame(widthHeight: appearance.radioButtonSize) + + Text(choice) + .font(Font(appearance.textFont)) + .foregroundColor( + isSelected ? appearance.selectedTextColor : appearance.unselectedTextColor + ) + .frame(maxWidth: .infinity, alignment: .leading) + } + .padding(.vertical, appearance.spacing) + .animation(.default, value: isSelected) + } + ) + } + } + } + + @MainActor + private func handleTap(on choice: String) { + FeedbackGenerator(feedbackType: .selection).triggerFeedback() + onTap(choice) + } +} + +#if DEBUG +#Preview { + UsersQuestionnaireOnboardingChoicesView( + choices: QuestionnaireOnboardingPreviewDefaults.choices, + selectedChoice: QuestionnaireOnboardingPreviewDefaults.choices.first, + onTap: { _ in } + ) + .padding() +} +#endif diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/UsersQuestionnaire/Onboarding/Views/UsersQuestionnaireOnboardingContentView.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/UsersQuestionnaire/Onboarding/Views/UsersQuestionnaireOnboardingContentView.swift new file mode 100644 index 0000000000..9b34e8b6b0 --- /dev/null +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/UsersQuestionnaire/Onboarding/Views/UsersQuestionnaireOnboardingContentView.swift @@ -0,0 +1,140 @@ +import Combine +import SwiftUI + +extension UsersQuestionnaireOnboardingContentView { + enum Appearance { + static let spacing = LayoutInsets.defaultInset + static let interitemSpacing = LayoutInsets.smallInset + + static let textInputHeight: CGFloat = 58 + static let textInputPlaceholderInsets = EdgeInsets(top: 16, leading: 13, bottom: 16, trailing: 16) + } +} + +struct UsersQuestionnaireOnboardingContentView: View { + let title: String + + let choices: [String] + let selectedChoice: String? + let onChoiceTap: (String) -> Void + + var textInputValue: Binding + let isTextInputVisible: Bool + + let isSendButtonEnabled: Bool + let onSendButtotTap: () -> Void + let onSkipButtotTap: () -> Void + + @State private var isKeyboardVisible = false + + var body: some View { + ScrollView { + VStack(spacing: Appearance.spacing) { + Text(title) + .font(.title).bold() + .foregroundColor(.newPrimaryText) + .multilineTextAlignment(.center) + + UsersQuestionnaireOnboardingChoicesView( + appearance: .init(spacing: Appearance.interitemSpacing), + choices: choices, + selectedChoice: selectedChoice, + onTap: onChoiceTap + ) + .padding(.vertical) + + if isTextInputVisible { + textInput + .animation(nil) + } + } + .introspectScrollView { scrollView in + scrollView.shouldIgnoreScrollingAdjustment = true + } + .padding() + } + .scrollBounceBehaviorBasedOnSize() + .safeAreaInsetBottomCompatibility(footerView) + .onReceive(Publishers.keyboardIsVisible) { isKeyboardVisible = $0 } + } + + private var textInput: some View { + TextEditor(text: textInputValue) + .foregroundColor(.newPrimaryText) + .font(.body) + .multilineTextAlignment(.leading) + .keyboardType(.asciiCapable) + .disableAutocorrection(true) + .frame(height: Appearance.textInputHeight) + .frame(maxWidth: .infinity) + .padding(LayoutInsets.small.edgeInsets) + .overlay( + Text(Strings.UsersQuestionnaireOnboarding.textInputPlaceholder) + .font(.body) + .foregroundColor(.newSecondaryText) + .allowsHitTesting(false) + .padding(Appearance.textInputPlaceholderInsets) + .opacity(textInputValue.wrappedValue.isEmpty ? 1 : 0) + , + alignment: .topLeading + ) + .addBorder() + } + + @ViewBuilder private var footerView: some View { + if isKeyboardVisible { + EmptyView() + } else { + UsersQuestionnaireOnboardingFooterView( + appearance: .init(spacing: Appearance.interitemSpacing), + isSendButtonEnabled: isSendButtonEnabled, + onSendButtotTap: onSendButtotTap, + onSkipButtotTap: onSkipButtotTap + ) + } + } +} + +#if DEBUG +enum QuestionnaireOnboardingPreviewDefaults { + static let title = "How did you hear about\nMy Hyperskill?" + static let choices = [ + "Google Play", + "Google Search", + "YouTube", + "Instagram", + "TikTok", + "News/article/blog", + "Friends/family", + "Other" + ] +} + +#Preview { + UsersQuestionnaireOnboardingContentView( + title: QuestionnaireOnboardingPreviewDefaults.title, + choices: QuestionnaireOnboardingPreviewDefaults.choices, + selectedChoice: nil, + onChoiceTap: { _ in }, + textInputValue: .constant(""), + isTextInputVisible: false, + isSendButtonEnabled: false, + onSendButtotTap: {}, + onSkipButtotTap: {} + ) +} + +#Preview { + UsersQuestionnaireOnboardingContentView( + title: QuestionnaireOnboardingPreviewDefaults.title, + choices: QuestionnaireOnboardingPreviewDefaults.choices, + selectedChoice: QuestionnaireOnboardingPreviewDefaults.choices.last, + onChoiceTap: { _ in }, + textInputValue: .constant(""), + isTextInputVisible: true, + isSendButtonEnabled: false, + onSendButtotTap: {}, + onSkipButtotTap: {} + ) +} +#endif diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/UsersQuestionnaire/Onboarding/Views/UsersQuestionnaireOnboardingFooterView.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/UsersQuestionnaire/Onboarding/Views/UsersQuestionnaireOnboardingFooterView.swift new file mode 100644 index 0000000000..a83a89b538 --- /dev/null +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/UsersQuestionnaire/Onboarding/Views/UsersQuestionnaireOnboardingFooterView.swift @@ -0,0 +1,70 @@ +import SwiftUI + +extension UsersQuestionnaireOnboardingFooterView { + struct Appearance { + var spacing = LayoutInsets.smallInset + } +} + +struct UsersQuestionnaireOnboardingFooterView: View { + private(set) var appearance = Appearance() + + private let feedbackGenerator = FeedbackGenerator(feedbackType: .selection) + + let isSendButtonEnabled: Bool + + let onSendButtotTap: () -> Void + let onSkipButtotTap: () -> Void + + var body: some View { + actionButtons + .padding() + .background( + TransparentBlurView() + .edgesIgnoringSafeArea(.all) + ) + .fixedSize(horizontal: false, vertical: true) + } + + @MainActor private var actionButtons: some View { + VStack(alignment: .center, spacing: appearance.spacing) { + Button( + Strings.UsersQuestionnaireOnboarding.sendButtot, + action: { + feedbackGenerator.triggerFeedback() + onSendButtotTap() + } + ) + .buttonStyle(.primary) + .shineEffect(isActive: isSendButtonEnabled) + .disabled(!isSendButtonEnabled) + + Button( + Strings.UsersQuestionnaireOnboarding.skipButton, + action: { + feedbackGenerator.triggerFeedback() + onSkipButtotTap() + } + ) + .buttonStyle(GhostButtonStyle()) + } + } +} + +#if DEBUG +#Preview { + VStack { + UsersQuestionnaireOnboardingFooterView( + isSendButtonEnabled: true, + onSendButtotTap: {}, + onSkipButtotTap: {} + ) + + UsersQuestionnaireOnboardingFooterView( + isSendButtonEnabled: false, + onSendButtotTap: {}, + onSkipButtotTap: {} + ) + } +} +#endif diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/UsersQuestionnaire/Onboarding/Views/UsersQuestionnaireOnboardingView.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/UsersQuestionnaire/Onboarding/Views/UsersQuestionnaireOnboardingView.swift new file mode 100644 index 0000000000..e30714f0d2 --- /dev/null +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/UsersQuestionnaire/Onboarding/Views/UsersQuestionnaireOnboardingView.swift @@ -0,0 +1,61 @@ +import shared +import SwiftUI + +extension UsersQuestionnaireOnboardingView { + struct Appearance { + let backgroundColor = Color(ColorPalette.newLayer1) + } +} + +struct UsersQuestionnaireOnboardingView: View { + private(set) var appearance = Appearance() + + @StateObject var viewModel: UsersQuestionnaireOnboardingViewModel + + var body: some View { + ZStack { + UIViewControllerEventsWrapper(onViewDidAppear: viewModel.logViewedEvent) + + BackgroundView(color: appearance.backgroundColor) + + UsersQuestionnaireOnboardingContentView( + title: viewModel.state.title, + choices: viewModel.state.choices, + selectedChoice: viewModel.state.selectedChoice, + onChoiceTap: viewModel.selectChoice(_:), + textInputValue: Binding( + get: { viewModel.state.textInputValue ?? "" }, + set: { viewModel.doTextInputValueChanged($0) } + ), + isTextInputVisible: viewModel.state.isTextInputVisible, + isSendButtonEnabled: viewModel.state.isSendButtonEnabled, + onSendButtotTap: viewModel.doSend, + onSkipButtotTap: viewModel.doSkip + ) + .animation(.default, value: viewModel.state) + } + .onAppear { + viewModel.startListening() + viewModel.onViewAction = handleViewAction(_:) + } + .onDisappear { + viewModel.stopListening() + viewModel.onViewAction = nil + } + } +} + +// MARK: - UsersQuestionnaireOnboardingView (ViewAction) - + +private extension UsersQuestionnaireOnboardingView { + func handleViewAction( + _ viewAction: UsersQuestionnaireOnboardingFeatureActionViewAction + ) { + switch UsersQuestionnaireOnboardingFeatureActionViewActionKs(viewAction) { + case .showSendSuccessMessage(let showSendSuccessMessageViewAction): + ProgressHUD.showSuccess(status: showSendSuccessMessageViewAction.message) + case .completeUsersQuestionnaireOnboarding: + viewModel.doCompleteOnboarding() + } + } +} diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/UsersQuestionnaireWidget/UsersQuestionnaireWidgetAssembly.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/UsersQuestionnaire/Widget/UsersQuestionnaireWidgetAssembly.swift similarity index 100% rename from iosHyperskillApp/iosHyperskillApp/Sources/Modules/UsersQuestionnaireWidget/UsersQuestionnaireWidgetAssembly.swift rename to iosHyperskillApp/iosHyperskillApp/Sources/Modules/UsersQuestionnaire/Widget/UsersQuestionnaireWidgetAssembly.swift diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/UsersQuestionnaireWidget/UsersQuestionnaireWidgetOutputProtocol.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/UsersQuestionnaire/Widget/UsersQuestionnaireWidgetOutputProtocol.swift similarity index 100% rename from iosHyperskillApp/iosHyperskillApp/Sources/Modules/UsersQuestionnaireWidget/UsersQuestionnaireWidgetOutputProtocol.swift rename to iosHyperskillApp/iosHyperskillApp/Sources/Modules/UsersQuestionnaire/Widget/UsersQuestionnaireWidgetOutputProtocol.swift diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/UsersQuestionnaireWidget/UsersQuestionnaireWidgetView.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/UsersQuestionnaire/Widget/UsersQuestionnaireWidgetView.swift similarity index 100% rename from iosHyperskillApp/iosHyperskillApp/Sources/Modules/UsersQuestionnaireWidget/UsersQuestionnaireWidgetView.swift rename to iosHyperskillApp/iosHyperskillApp/Sources/Modules/UsersQuestionnaire/Widget/UsersQuestionnaireWidgetView.swift diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/UsersQuestionnaireWidget/UsersQuestionnaireWidgetViewModel.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/UsersQuestionnaire/Widget/UsersQuestionnaireWidgetViewModel.swift similarity index 100% rename from iosHyperskillApp/iosHyperskillApp/Sources/Modules/UsersQuestionnaireWidget/UsersQuestionnaireWidgetViewModel.swift rename to iosHyperskillApp/iosHyperskillApp/Sources/Modules/UsersQuestionnaire/Widget/UsersQuestionnaireWidgetViewModel.swift diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Views/SwiftUI/CheckboxButton.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Views/SwiftUI/CheckboxButton.swift index de5c1e572e..9429baad89 100644 --- a/iosHyperskillApp/iosHyperskillApp/Sources/Views/SwiftUI/CheckboxButton.swift +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Views/SwiftUI/CheckboxButton.swift @@ -18,7 +18,7 @@ extension CheckboxButton { struct CheckboxButton: View { private(set) var appearance = Appearance() - @Binding var isSelected: Bool + let isSelected: Bool var onClick: (() -> Void)? @@ -57,16 +57,15 @@ struct CheckboxButton: View { } } -struct CheckboxButton_Previews: PreviewProvider { - static var previews: some View { - Group { - CheckboxButton(isSelected: .constant(true)) - .frame(width: 50, height: 50) +#if DEBUG +#Preview { + VStack { + CheckboxButton(isSelected: true) + .frame(width: 50, height: 50) - CheckboxButton(isSelected: .constant(false)) - .frame(width: 50, height: 50) - } - .previewLayout(.sizeThatFits) - .padding() + CheckboxButton(isSelected: false) + .frame(width: 50, height: 50) } + .padding() } +#endif diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Views/SwiftUI/RadioButton.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Views/SwiftUI/RadioButton.swift index 7546311f2e..473520ecba 100644 --- a/iosHyperskillApp/iosHyperskillApp/Sources/Views/SwiftUI/RadioButton.swift +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Views/SwiftUI/RadioButton.swift @@ -16,7 +16,7 @@ extension RadioButton { struct RadioButton: View { private(set) var appearance = Appearance() - @Binding var isSelected: Bool + let isSelected: Bool var onClick: (() -> Void)? @@ -48,16 +48,15 @@ struct RadioButton: View { } } -struct RadioButton_Previews: PreviewProvider { - static var previews: some View { - Group { - RadioButton(isSelected: .constant(true)) - .frame(width: 24, height: 24) +#if DEBUG +#Preview { + VStack { + RadioButton(isSelected: true) + .frame(width: 24, height: 24) - RadioButton(isSelected: .constant(false)) - .frame(width: 24, height: 24) - } - .previewLayout(.sizeThatFits) - .padding() + RadioButton(isSelected: false) + .frame(width: 24, height: 24) } + .padding() } +#endif diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/analytic/domain/model/hyperskill/HyperskillAnalyticRoute.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/analytic/domain/model/hyperskill/HyperskillAnalyticRoute.kt index 08f60ebd88..a884601792 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/analytic/domain/model/hyperskill/HyperskillAnalyticRoute.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/analytic/domain/model/hyperskill/HyperskillAnalyticRoute.kt @@ -20,6 +20,11 @@ sealed class HyperskillAnalyticRoute { override val path: String get() = "${super.path}/interview-preparation" } + + object UsersQuestionnaire : Onboarding() { + override val path: String + get() = "${super.path}/questionnaire" + } } open class Login : HyperskillAnalyticRoute() { diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/analytic/domain/model/hyperskill/HyperskillAnalyticTarget.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/analytic/domain/model/hyperskill/HyperskillAnalyticTarget.kt index c3c58e399c..151d101006 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/analytic/domain/model/hyperskill/HyperskillAnalyticTarget.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/analytic/domain/model/hyperskill/HyperskillAnalyticTarget.kt @@ -8,6 +8,7 @@ enum class HyperskillAnalyticTarget(val targetName: String) { PROFILE("profile"), DEBUG("debug"), SEND("send"), + SKIP("skip"), INPUT_OUTPUT_INFO("input_output_info"), STEP_TEXT_DESCRIPTION("step_text_description"), RESET("reset"), @@ -117,5 +118,6 @@ enum class HyperskillAnalyticTarget(val targetName: String) { REQUEST_REVIEW_MODAL("request_review_modal"), WRITE_A_REQUEST("write_a_request"), MAYBE_LATER("maybe_later"), + CHOICE("choice"), SOLVE_ON_THE_WEB_VERSION("solve_on_the_web_version") } \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/core/injection/AppGraph.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/core/injection/AppGraph.kt index d82b24f526..1e7a136d0d 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/core/injection/AppGraph.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/core/injection/AppGraph.kt @@ -81,6 +81,7 @@ import org.hyperskill.app.track_selection.details.injection.TrackSelectionDetail import org.hyperskill.app.track_selection.list.injection.TrackSelectionListComponent import org.hyperskill.app.user_storage.injection.UserStorageComponent import org.hyperskill.app.users_questionnaire.injection.UsersQuestionnaireDataComponent +import org.hyperskill.app.users_questionnaire.onboarding.injection.UsersQuestionnaireOnboardingComponent import org.hyperskill.app.users_questionnaire.widget.injection.UsersQuestionnaireWidgetComponent import org.hyperskill.app.welcome.injection.WelcomeComponent import org.hyperskill.app.welcome.injection.WelcomeDataComponent @@ -183,4 +184,5 @@ interface AppGraph { fun buildRequestReviewModalComponent(stepRoute: StepRoute): RequestReviewModalComponent fun buildUsersQuestionnaireDataComponent(): UsersQuestionnaireDataComponent fun buildUsersQuestionnaireWidgetComponent(): UsersQuestionnaireWidgetComponent + fun buildUsersQuestionnaireOnboardingComponent(): UsersQuestionnaireOnboardingComponent } \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/core/injection/BaseAppGraph.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/core/injection/BaseAppGraph.kt index 4355ffc11f..40616c7913 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/core/injection/BaseAppGraph.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/core/injection/BaseAppGraph.kt @@ -153,6 +153,8 @@ import org.hyperskill.app.user_storage.injection.UserStorageComponent import org.hyperskill.app.user_storage.injection.UserStorageComponentImpl import org.hyperskill.app.users_questionnaire.injection.UsersQuestionnaireDataComponent import org.hyperskill.app.users_questionnaire.injection.UsersQuestionnaireDataComponentImpl +import org.hyperskill.app.users_questionnaire.onboarding.injection.UsersQuestionnaireOnboardingComponent +import org.hyperskill.app.users_questionnaire.onboarding.injection.UsersQuestionnaireOnboardingComponentImpl import org.hyperskill.app.users_questionnaire.widget.injection.UsersQuestionnaireWidgetComponent import org.hyperskill.app.users_questionnaire.widget.injection.UsersQuestionnaireWidgetComponentImpl import org.hyperskill.app.welcome.injection.WelcomeComponent @@ -493,4 +495,7 @@ abstract class BaseAppGraph : AppGraph { override fun buildUsersQuestionnaireWidgetComponent(): UsersQuestionnaireWidgetComponent = UsersQuestionnaireWidgetComponentImpl(this) + + override fun buildUsersQuestionnaireOnboardingComponent(): UsersQuestionnaireOnboardingComponent = + UsersQuestionnaireOnboardingComponentImpl(this) } \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/main/presentation/AppReducer.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/main/presentation/AppReducer.kt index b3df28e921..904ec407ee 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/main/presentation/AppReducer.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/main/presentation/AppReducer.kt @@ -13,7 +13,6 @@ import org.hyperskill.app.profile.domain.model.isNewUser import org.hyperskill.app.streak_recovery.presentation.StreakRecoveryFeature import org.hyperskill.app.streak_recovery.presentation.StreakRecoveryReducer import org.hyperskill.app.welcome_onboarding.presentation.WelcomeOnboardingFeature -import org.hyperskill.app.welcome_onboarding.presentation.WelcomeOnboardingFeature.OnboardingFlowFinishReason import org.hyperskill.app.welcome_onboarding.presentation.WelcomeOnboardingReducer import org.hyperskill.app.welcome_onboarding.presentation.getFinishAction import ru.nobird.app.presentation.redux.reducer.StateReducer @@ -258,15 +257,10 @@ internal class AppReducer( finishAction: WelcomeOnboardingFeature.Action.OnboardingFlowFinished ): Set = setOf( - when (val reason = finishAction.reason) { - is OnboardingFlowFinishReason.NotificationOnboardingFinished -> - if (reason.profile?.isNewUser == true) { - Action.ViewAction.NavigateTo.TrackSelectionScreen - } else { - Action.ViewAction.NavigateTo.StudyPlan - } - OnboardingFlowFinishReason.FirstProblemOnboardingFinished -> - Action.ViewAction.NavigateTo.StudyPlan + if (finishAction.profile?.isNewUser == true) { + Action.ViewAction.NavigateTo.TrackSelectionScreen + } else { + Action.ViewAction.NavigateTo.StudyPlan } ) diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/users_questionnaire/onboarding/domain/analytic/UsersQuestionnaireOnboardingAnalyticParams.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/users_questionnaire/onboarding/domain/analytic/UsersQuestionnaireOnboardingAnalyticParams.kt new file mode 100644 index 0000000000..dae5130b66 --- /dev/null +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/users_questionnaire/onboarding/domain/analytic/UsersQuestionnaireOnboardingAnalyticParams.kt @@ -0,0 +1,6 @@ +package org.hyperskill.app.users_questionnaire.onboarding.domain.analytic + +internal object UsersQuestionnaireOnboardingAnalyticParams { + const val PARAM_SOURCE = "source" + const val PARAM_INPUT = "input" +} \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/users_questionnaire/onboarding/domain/analytic/UsersQuestionnaireOnboardingClickedChoiceHyperskillAnalyticEvent.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/users_questionnaire/onboarding/domain/analytic/UsersQuestionnaireOnboardingClickedChoiceHyperskillAnalyticEvent.kt new file mode 100644 index 0000000000..d4e6f7f580 --- /dev/null +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/users_questionnaire/onboarding/domain/analytic/UsersQuestionnaireOnboardingClickedChoiceHyperskillAnalyticEvent.kt @@ -0,0 +1,41 @@ +package org.hyperskill.app.users_questionnaire.onboarding.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.HyperskillAnalyticPart +import org.hyperskill.app.analytic.domain.model.hyperskill.HyperskillAnalyticRoute +import org.hyperskill.app.analytic.domain.model.hyperskill.HyperskillAnalyticTarget + +/** + * Represents click on the choice analytic event. + * + * JSON payload: + * ``` + * { + * "route": "/onboarding/questionnaire", + * "action": "click", + * "part": "main", + * "target": "choice", + * "context": + * { + * "source": "Google Play" + * } + * } + * ``` + * + * @see HyperskillAnalyticEvent + */ +class UsersQuestionnaireOnboardingClickedChoiceHyperskillAnalyticEvent( + private val selectedChoice: String +) : HyperskillAnalyticEvent( + HyperskillAnalyticRoute.Onboarding.UsersQuestionnaire, + HyperskillAnalyticAction.CLICK, + HyperskillAnalyticPart.MAIN, + HyperskillAnalyticTarget.CHOICE +) { + override val params: Map + get() = super.params + + mapOf( + PARAM_CONTEXT to mapOf(UsersQuestionnaireOnboardingAnalyticParams.PARAM_SOURCE to selectedChoice) + ) +} \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/users_questionnaire/onboarding/domain/analytic/UsersQuestionnaireOnboardingClickedSendHyperskillAnalyticEvent.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/users_questionnaire/onboarding/domain/analytic/UsersQuestionnaireOnboardingClickedSendHyperskillAnalyticEvent.kt new file mode 100644 index 0000000000..63156b440c --- /dev/null +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/users_questionnaire/onboarding/domain/analytic/UsersQuestionnaireOnboardingClickedSendHyperskillAnalyticEvent.kt @@ -0,0 +1,47 @@ +package org.hyperskill.app.users_questionnaire.onboarding.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.HyperskillAnalyticPart +import org.hyperskill.app.analytic.domain.model.hyperskill.HyperskillAnalyticRoute +import org.hyperskill.app.analytic.domain.model.hyperskill.HyperskillAnalyticTarget +import ru.nobird.app.core.model.mapOfNotNull + +/** + * Represents click on the "Send" button analytic event. + * + * JSON payload: + * ``` + * { + * "route": "/onboarding/questionnaire", + * "action": "click", + * "part": "main", + * "target": "send", + * "context": + * { + * "source": "Google Play", + * "input": "Some text" + * } + * } + * ``` + * + * @see HyperskillAnalyticEvent + */ +class UsersQuestionnaireOnboardingClickedSendHyperskillAnalyticEvent( + private val selectedChoice: String, + private val textInputValue: String? +) : HyperskillAnalyticEvent( + HyperskillAnalyticRoute.Onboarding.UsersQuestionnaire, + HyperskillAnalyticAction.CLICK, + HyperskillAnalyticPart.MAIN, + HyperskillAnalyticTarget.SEND +) { + override val params: Map + get() = super.params + + mapOf( + PARAM_CONTEXT to mapOfNotNull( + UsersQuestionnaireOnboardingAnalyticParams.PARAM_SOURCE to selectedChoice, + UsersQuestionnaireOnboardingAnalyticParams.PARAM_INPUT to textInputValue + ) + ) +} \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/users_questionnaire/onboarding/domain/analytic/UsersQuestionnaireOnboardingClickedSkipHyperskillAnalyticEvent.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/users_questionnaire/onboarding/domain/analytic/UsersQuestionnaireOnboardingClickedSkipHyperskillAnalyticEvent.kt new file mode 100644 index 0000000000..50a51046f2 --- /dev/null +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/users_questionnaire/onboarding/domain/analytic/UsersQuestionnaireOnboardingClickedSkipHyperskillAnalyticEvent.kt @@ -0,0 +1,29 @@ +package org.hyperskill.app.users_questionnaire.onboarding.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.HyperskillAnalyticPart +import org.hyperskill.app.analytic.domain.model.hyperskill.HyperskillAnalyticRoute +import org.hyperskill.app.analytic.domain.model.hyperskill.HyperskillAnalyticTarget + +/** + * Represents click on the "Skip" button analytic event. + * + * JSON payload: + * ``` + * { + * "route": "/onboarding/questionnaire", + * "action": "click", + * "part": "main", + * "target": "skip" + * } + * ``` + * + * @see HyperskillAnalyticEvent + */ +object UsersQuestionnaireOnboardingClickedSkipHyperskillAnalyticEvent : HyperskillAnalyticEvent( + HyperskillAnalyticRoute.Onboarding.UsersQuestionnaire, + HyperskillAnalyticAction.CLICK, + HyperskillAnalyticPart.MAIN, + HyperskillAnalyticTarget.SKIP +) \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/users_questionnaire/onboarding/domain/analytic/UsersQuestionnaireOnboardingViewedHyperskillAnalyticEvent.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/users_questionnaire/onboarding/domain/analytic/UsersQuestionnaireOnboardingViewedHyperskillAnalyticEvent.kt new file mode 100644 index 0000000000..1d5ab8ebf5 --- /dev/null +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/users_questionnaire/onboarding/domain/analytic/UsersQuestionnaireOnboardingViewedHyperskillAnalyticEvent.kt @@ -0,0 +1,23 @@ +package org.hyperskill.app.users_questionnaire.onboarding.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 a view analytic event. + * + * JSON payload: + * ``` + * { + * "route": "/onboarding/questionnaire", + * "action": "view" + * } + * ``` + * + * @see HyperskillAnalyticEvent + */ +object UsersQuestionnaireOnboardingViewedHyperskillAnalyticEvent : HyperskillAnalyticEvent( + HyperskillAnalyticRoute.Onboarding.UsersQuestionnaire, + HyperskillAnalyticAction.VIEW +) \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/users_questionnaire/onboarding/injection/UsersQuestionnaireOnboardingComponent.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/users_questionnaire/onboarding/injection/UsersQuestionnaireOnboardingComponent.kt new file mode 100644 index 0000000000..358b880471 --- /dev/null +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/users_questionnaire/onboarding/injection/UsersQuestionnaireOnboardingComponent.kt @@ -0,0 +1,10 @@ +package org.hyperskill.app.users_questionnaire.onboarding.injection + +import org.hyperskill.app.users_questionnaire.onboarding.presentation.UsersQuestionnaireOnboardingFeature.Action +import org.hyperskill.app.users_questionnaire.onboarding.presentation.UsersQuestionnaireOnboardingFeature.Message +import org.hyperskill.app.users_questionnaire.onboarding.presentation.UsersQuestionnaireOnboardingFeature.ViewState +import ru.nobird.app.presentation.redux.feature.Feature + +interface UsersQuestionnaireOnboardingComponent { + val usersQuestionnaireOnboardingFeature: Feature +} \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/users_questionnaire/onboarding/injection/UsersQuestionnaireOnboardingComponentImpl.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/users_questionnaire/onboarding/injection/UsersQuestionnaireOnboardingComponentImpl.kt new file mode 100644 index 0000000000..cc493fa324 --- /dev/null +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/users_questionnaire/onboarding/injection/UsersQuestionnaireOnboardingComponentImpl.kt @@ -0,0 +1,20 @@ +package org.hyperskill.app.users_questionnaire.onboarding.injection + +import org.hyperskill.app.core.injection.AppGraph +import org.hyperskill.app.users_questionnaire.onboarding.presentation.UsersQuestionnaireOnboardingFeature.Action +import org.hyperskill.app.users_questionnaire.onboarding.presentation.UsersQuestionnaireOnboardingFeature.Message +import org.hyperskill.app.users_questionnaire.onboarding.presentation.UsersQuestionnaireOnboardingFeature.ViewState +import ru.nobird.app.presentation.redux.feature.Feature + +internal class UsersQuestionnaireOnboardingComponentImpl( + private val appGraph: AppGraph +) : UsersQuestionnaireOnboardingComponent { + override val usersQuestionnaireOnboardingFeature: Feature + get() = UsersQuestionnaireOnboardingFeatureBuilder.build( + analyticInteractor = appGraph.analyticComponent.analyticInteractor, + buildVariant = appGraph.commonComponent.buildKonfig.buildVariant, + logger = appGraph.loggerComponent.logger, + platform = appGraph.commonComponent.platform, + resourceProvider = appGraph.commonComponent.resourceProvider + ) +} \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/users_questionnaire/onboarding/injection/UsersQuestionnaireOnboardingFeatureBuilder.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/users_questionnaire/onboarding/injection/UsersQuestionnaireOnboardingFeatureBuilder.kt new file mode 100644 index 0000000000..7776497a74 --- /dev/null +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/users_questionnaire/onboarding/injection/UsersQuestionnaireOnboardingFeatureBuilder.kt @@ -0,0 +1,51 @@ +package org.hyperskill.app.users_questionnaire.onboarding.injection + +import co.touchlab.kermit.Logger +import org.hyperskill.app.analytic.domain.interactor.AnalyticInteractor +import org.hyperskill.app.core.domain.BuildVariant +import org.hyperskill.app.core.domain.platform.Platform +import org.hyperskill.app.core.presentation.ActionDispatcherOptions +import org.hyperskill.app.core.presentation.transformState +import org.hyperskill.app.core.view.mapper.ResourceProvider +import org.hyperskill.app.logging.presentation.wrapWithLogger +import org.hyperskill.app.users_questionnaire.onboarding.presentation.UsersQuestionnaireOnboardingActionDispatcher +import org.hyperskill.app.users_questionnaire.onboarding.presentation.UsersQuestionnaireOnboardingFeature +import org.hyperskill.app.users_questionnaire.onboarding.presentation.UsersQuestionnaireOnboardingFeature.Action +import org.hyperskill.app.users_questionnaire.onboarding.presentation.UsersQuestionnaireOnboardingFeature.Message +import org.hyperskill.app.users_questionnaire.onboarding.presentation.UsersQuestionnaireOnboardingFeature.ViewState +import org.hyperskill.app.users_questionnaire.onboarding.presentation.UsersQuestionnaireOnboardingReducer +import org.hyperskill.app.users_questionnaire.onboarding.view.mapper.UsersQuestionnaireOnboardingViewStateMapper +import ru.nobird.app.presentation.redux.dispatcher.wrapWithActionDispatcher +import ru.nobird.app.presentation.redux.feature.Feature +import ru.nobird.app.presentation.redux.feature.ReduxFeature + +internal object UsersQuestionnaireOnboardingFeatureBuilder { + private const val LOG_TAG = "UsersQuestionnaireOnboardingFeature" + + fun build( + analyticInteractor: AnalyticInteractor, + buildVariant: BuildVariant, + logger: Logger, + platform: Platform, + resourceProvider: ResourceProvider + ): Feature { + val reducer = UsersQuestionnaireOnboardingReducer(resourceProvider) + .wrapWithLogger(buildVariant, logger, LOG_TAG) + val actionDispatcher = UsersQuestionnaireOnboardingActionDispatcher( + config = ActionDispatcherOptions(), + analyticInteractor = analyticInteractor + ) + + val viewStateMapper = UsersQuestionnaireOnboardingViewStateMapper( + platform = platform, + resourceProvider = resourceProvider + ) + + return ReduxFeature( + initialState = UsersQuestionnaireOnboardingFeature.State(), + reducer = reducer + ) + .wrapWithActionDispatcher(actionDispatcher) + .transformState(viewStateMapper::mapState) + } +} \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/users_questionnaire/onboarding/presentation/UsersQuestionnaireOnboardingActionDispatcher.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/users_questionnaire/onboarding/presentation/UsersQuestionnaireOnboardingActionDispatcher.kt new file mode 100644 index 0000000000..cfafdeba1a --- /dev/null +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/users_questionnaire/onboarding/presentation/UsersQuestionnaireOnboardingActionDispatcher.kt @@ -0,0 +1,23 @@ +package org.hyperskill.app.users_questionnaire.onboarding.presentation + +import org.hyperskill.app.analytic.domain.interactor.AnalyticInteractor +import org.hyperskill.app.core.presentation.ActionDispatcherOptions +import org.hyperskill.app.users_questionnaire.onboarding.presentation.UsersQuestionnaireOnboardingFeature.Action +import org.hyperskill.app.users_questionnaire.onboarding.presentation.UsersQuestionnaireOnboardingFeature.InternalAction +import org.hyperskill.app.users_questionnaire.onboarding.presentation.UsersQuestionnaireOnboardingFeature.Message +import ru.nobird.app.presentation.redux.dispatcher.CoroutineActionDispatcher + +internal class UsersQuestionnaireOnboardingActionDispatcher( + config: ActionDispatcherOptions, + private val analyticInteractor: AnalyticInteractor +) : CoroutineActionDispatcher(config.createConfig()) { + override suspend fun doSuspendableAction(action: Action) { + when (action) { + is InternalAction.LogAnalyticEvent -> + analyticInteractor.logEvent(action.event, action.forceLogEvent) + else -> { + // no op + } + } + } +} \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/users_questionnaire/onboarding/presentation/UsersQuestionnaireOnboardingFeature.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/users_questionnaire/onboarding/presentation/UsersQuestionnaireOnboardingFeature.kt new file mode 100644 index 0000000000..1679fcb99a --- /dev/null +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/users_questionnaire/onboarding/presentation/UsersQuestionnaireOnboardingFeature.kt @@ -0,0 +1,43 @@ +package org.hyperskill.app.users_questionnaire.onboarding.presentation + +import org.hyperskill.app.analytic.domain.model.AnalyticEvent + +object UsersQuestionnaireOnboardingFeature { + internal data class State( + val selectedChoice: String? = null, + val textInputValue: String? = null + ) + + data class ViewState( + val title: String, + val choices: List, + val selectedChoice: String?, + val textInputValue: String?, + val isTextInputVisible: Boolean, + val isSendButtonEnabled: Boolean + ) + + sealed interface Message { + data class ClickedChoice(val choice: String) : Message + data class TextInputValueChanged(val text: String) : Message + + object SendButtonClicked : Message + object SkipButtonClicked : Message + + object ViewedEventMessage : Message + } + + sealed interface Action { + sealed interface ViewAction : Action { + object CompleteUsersQuestionnaireOnboarding : ViewAction + data class ShowSendSuccessMessage(val message: String) : ViewAction + } + } + + internal sealed interface InternalAction : Action { + data class LogAnalyticEvent( + val event: AnalyticEvent, + val forceLogEvent: Boolean = false + ) : InternalAction + } +} \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/users_questionnaire/onboarding/presentation/UsersQuestionnaireOnboardingReducer.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/users_questionnaire/onboarding/presentation/UsersQuestionnaireOnboardingReducer.kt new file mode 100644 index 0000000000..e512f49983 --- /dev/null +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/users_questionnaire/onboarding/presentation/UsersQuestionnaireOnboardingReducer.kt @@ -0,0 +1,76 @@ +package org.hyperskill.app.users_questionnaire.onboarding.presentation + +import org.hyperskill.app.SharedResources +import org.hyperskill.app.core.view.mapper.ResourceProvider +import org.hyperskill.app.users_questionnaire.onboarding.domain.analytic.UsersQuestionnaireOnboardingClickedChoiceHyperskillAnalyticEvent +import org.hyperskill.app.users_questionnaire.onboarding.domain.analytic.UsersQuestionnaireOnboardingClickedSendHyperskillAnalyticEvent +import org.hyperskill.app.users_questionnaire.onboarding.domain.analytic.UsersQuestionnaireOnboardingClickedSkipHyperskillAnalyticEvent +import org.hyperskill.app.users_questionnaire.onboarding.domain.analytic.UsersQuestionnaireOnboardingViewedHyperskillAnalyticEvent +import org.hyperskill.app.users_questionnaire.onboarding.presentation.UsersQuestionnaireOnboardingFeature.Action +import org.hyperskill.app.users_questionnaire.onboarding.presentation.UsersQuestionnaireOnboardingFeature.InternalAction +import org.hyperskill.app.users_questionnaire.onboarding.presentation.UsersQuestionnaireOnboardingFeature.Message +import org.hyperskill.app.users_questionnaire.onboarding.presentation.UsersQuestionnaireOnboardingFeature.State +import ru.nobird.app.presentation.redux.reducer.StateReducer + +private typealias ReducerResult = Pair> + +internal class UsersQuestionnaireOnboardingReducer( + private val resourceProvider: ResourceProvider +) : StateReducer { + override fun reduce(state: State, message: Message): ReducerResult = + when (message) { + is Message.ClickedChoice -> + state.copy(selectedChoice = message.choice) to setOf( + InternalAction.LogAnalyticEvent( + UsersQuestionnaireOnboardingClickedChoiceHyperskillAnalyticEvent(message.choice) + ) + ) + is Message.TextInputValueChanged -> + handleTextInputValueChangedMessage(state, message) + Message.SendButtonClicked -> + handleSendButtonClickedMessage(state) + Message.SkipButtonClicked -> + handleSkipButtonClickedMessage(state) + Message.ViewedEventMessage -> + state to setOf( + InternalAction.LogAnalyticEvent(UsersQuestionnaireOnboardingViewedHyperskillAnalyticEvent) + ) + } ?: (state to emptySet()) + + private fun handleTextInputValueChangedMessage( + state: State, + message: Message.TextInputValueChanged + ): ReducerResult? = + if (state.selectedChoice != null) { + state.copy(textInputValue = message.text) to emptySet() + } else { + null + } + + private fun handleSendButtonClickedMessage(state: State): ReducerResult? = + if (state.selectedChoice != null) { + state to setOf( + InternalAction.LogAnalyticEvent( + UsersQuestionnaireOnboardingClickedSendHyperskillAnalyticEvent( + selectedChoice = state.selectedChoice, + textInputValue = state.textInputValue + ), + forceLogEvent = true + ), + Action.ViewAction.ShowSendSuccessMessage( + resourceProvider.getString( + SharedResources.strings.users_questionnaire_onboarding_send_answer_success_message + ) + ), + Action.ViewAction.CompleteUsersQuestionnaireOnboarding + ) + } else { + null + } + + private fun handleSkipButtonClickedMessage(state: State): ReducerResult = + state to setOf( + InternalAction.LogAnalyticEvent(UsersQuestionnaireOnboardingClickedSkipHyperskillAnalyticEvent), + Action.ViewAction.CompleteUsersQuestionnaireOnboarding + ) +} \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/users_questionnaire/onboarding/view/mapper/UsersQuestionnaireOnboardingViewStateMapper.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/users_questionnaire/onboarding/view/mapper/UsersQuestionnaireOnboardingViewStateMapper.kt new file mode 100644 index 0000000000..96818d5774 --- /dev/null +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/users_questionnaire/onboarding/view/mapper/UsersQuestionnaireOnboardingViewStateMapper.kt @@ -0,0 +1,53 @@ +package org.hyperskill.app.users_questionnaire.onboarding.view.mapper + +import org.hyperskill.app.SharedResources +import org.hyperskill.app.core.domain.platform.Platform +import org.hyperskill.app.core.domain.platform.PlatformType +import org.hyperskill.app.core.view.mapper.ResourceProvider +import org.hyperskill.app.users_questionnaire.onboarding.presentation.UsersQuestionnaireOnboardingFeature.State +import org.hyperskill.app.users_questionnaire.onboarding.presentation.UsersQuestionnaireOnboardingFeature.ViewState + +internal class UsersQuestionnaireOnboardingViewStateMapper( + platform: Platform, + resourceProvider: ResourceProvider +) { + private val title = resourceProvider.getString( + SharedResources.strings.users_questionnaire_onboarding_title_template, + resourceProvider.getString(platform.appNameResource) + ) + + private val choices = listOf( + when (platform.platformType) { + PlatformType.IOS -> + resourceProvider.getString(SharedResources.strings.users_questionnaire_onboarding_source_app_store) + PlatformType.ANDROID -> + resourceProvider.getString(SharedResources.strings.users_questionnaire_onboarding_source_google_play) + }, + resourceProvider.getString(SharedResources.strings.users_questionnaire_onboarding_source_google_search), + resourceProvider.getString(SharedResources.strings.users_questionnaire_onboarding_source_youtube), + resourceProvider.getString(SharedResources.strings.users_questionnaire_onboarding_source_instagram), + resourceProvider.getString(SharedResources.strings.users_questionnaire_onboarding_source_tiktok), + resourceProvider.getString(SharedResources.strings.users_questionnaire_onboarding_source_news), + resourceProvider.getString(SharedResources.strings.users_questionnaire_onboarding_source_friends), + resourceProvider.getString(SharedResources.strings.users_questionnaire_onboarding_source_other) + ) + + fun mapState(state: State): ViewState { + val isTextInputVisible = state.selectedChoice == choices.last() + + val isSendButtonEnabled = if (isTextInputVisible) { + state.textInputValue?.isNotBlank() == true + } else { + state.selectedChoice != null + } + + return ViewState( + title = title, + choices = choices, + selectedChoice = state.selectedChoice, + textInputValue = state.textInputValue.takeIf { isTextInputVisible }, + isTextInputVisible = isTextInputVisible, + isSendButtonEnabled = isSendButtonEnabled + ) + } +} \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/welcome_onboarding/injection/WelcomeOnboardingComponentImpl.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/welcome_onboarding/injection/WelcomeOnboardingComponentImpl.kt index 8059f25790..622a014690 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/welcome_onboarding/injection/WelcomeOnboardingComponentImpl.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/welcome_onboarding/injection/WelcomeOnboardingComponentImpl.kt @@ -1,5 +1,6 @@ package org.hyperskill.app.welcome_onboarding.injection +import org.hyperskill.app.core.domain.platform.PlatformType import org.hyperskill.app.core.injection.AppGraph import org.hyperskill.app.core.presentation.ActionDispatcherOptions import org.hyperskill.app.welcome_onboarding.presentation.WelcomeOnboardingActionDispatcher @@ -9,7 +10,9 @@ internal class WelcomeOnboardingComponentImpl( private val appGraph: AppGraph ) : WelcomeOnboardingComponent { override val welcomeOnboardingReducer: WelcomeOnboardingReducer - get() = WelcomeOnboardingReducer() + get() = WelcomeOnboardingReducer( + isUsersQuestionnaireOnboardingEnabled = appGraph.commonComponent.platform.platformType == PlatformType.IOS + ) override val welcomeOnboardingActionDispatcher: WelcomeOnboardingActionDispatcher get() = WelcomeOnboardingActionDispatcher( diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/welcome_onboarding/presentation/WelcomeOnboardingFeature.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/welcome_onboarding/presentation/WelcomeOnboardingFeature.kt index 4b990b6e65..61c980a988 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/welcome_onboarding/presentation/WelcomeOnboardingFeature.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/welcome_onboarding/presentation/WelcomeOnboardingFeature.kt @@ -12,6 +12,8 @@ object WelcomeOnboardingFeature { sealed interface Message { object NotificationOnboardingCompleted : Message + object UsersQuestionnaireOnboardingCompleted : Message + data class FirstProblemOnboardingCompleted(val firstProblemStepRoute: StepRoute?) : Message } @@ -26,20 +28,15 @@ object WelcomeOnboardingFeature { ) : InternalMessage } - sealed interface OnboardingFlowFinishReason { - data class NotificationOnboardingFinished(val profile: Profile?) : OnboardingFlowFinishReason - object FirstProblemOnboardingFinished : OnboardingFlowFinishReason - } - sealed interface Action { - data class OnboardingFlowFinished( - val reason: OnboardingFlowFinishReason - ) : Action + data class OnboardingFlowFinished(val profile: Profile?) : Action sealed interface ViewAction : Action { sealed interface NavigateTo : ViewAction { object NotificationOnboardingScreen : NavigateTo + object UsersQuestionnaireOnboardingScreen : NavigateTo + data class FirstProblemOnboardingScreen(val isNewUserMode: Boolean) : NavigateTo data class StudyPlanWithStep(val stepRoute: StepRoute) : NavigateTo diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/welcome_onboarding/presentation/WelcomeOnboardingReducer.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/welcome_onboarding/presentation/WelcomeOnboardingReducer.kt index 913e63fc9a..137c4572f5 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/welcome_onboarding/presentation/WelcomeOnboardingReducer.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/welcome_onboarding/presentation/WelcomeOnboardingReducer.kt @@ -1,19 +1,19 @@ package org.hyperskill.app.welcome_onboarding.presentation -import org.hyperskill.app.profile.domain.model.Profile import org.hyperskill.app.profile.domain.model.isNewUser import org.hyperskill.app.welcome_onboarding.presentation.WelcomeOnboardingFeature.Action import org.hyperskill.app.welcome_onboarding.presentation.WelcomeOnboardingFeature.Action.ViewAction import org.hyperskill.app.welcome_onboarding.presentation.WelcomeOnboardingFeature.InternalAction import org.hyperskill.app.welcome_onboarding.presentation.WelcomeOnboardingFeature.InternalMessage import org.hyperskill.app.welcome_onboarding.presentation.WelcomeOnboardingFeature.Message -import org.hyperskill.app.welcome_onboarding.presentation.WelcomeOnboardingFeature.OnboardingFlowFinishReason import org.hyperskill.app.welcome_onboarding.presentation.WelcomeOnboardingFeature.State import ru.nobird.app.presentation.redux.reducer.StateReducer private typealias ReducerResult = Pair> -class WelcomeOnboardingReducer : StateReducer { +class WelcomeOnboardingReducer( + private val isUsersQuestionnaireOnboardingEnabled: Boolean +) : StateReducer { override fun reduce(state: State, message: Message): ReducerResult = when (message) { is InternalMessage.OnboardingFlowRequested -> @@ -22,78 +22,71 @@ class WelcomeOnboardingReducer : StateReducer { Message.NotificationOnboardingCompleted -> handleNotificationOnboardingCompleted(state) + Message.UsersQuestionnaireOnboardingCompleted -> + handleUsersQuestionnaireOnboardingCompleted(state) + is InternalMessage.FirstProblemOnboardingDataFetched -> handleFirstProblemOnboardingDataFetched(state, message) is Message.FirstProblemOnboardingCompleted -> - handleFirstProblemOnboardingCompleted(message) + handleFirstProblemOnboardingCompleted(state, message) } private fun handleOnboardingFlowRequested( message: InternalMessage.OnboardingFlowRequested - ): ReducerResult = - if (!message.isNotificationPermissionGranted) { - State(message.profile) to - setOf(ViewAction.NavigateTo.NotificationOnboardingScreen) + ): ReducerResult { + val state = State(message.profile) + return if (!message.isNotificationPermissionGranted) { + state to setOf(ViewAction.NavigateTo.NotificationOnboardingScreen) } else { - onNotificationOnboardingCompleted(message.profile) + handleNotificationOnboardingCompleted(state) } + } private fun handleNotificationOnboardingCompleted( state: State ): ReducerResult = - if (state.profile != null) { - onNotificationOnboardingCompleted(state.profile) + if (isUsersQuestionnaireOnboardingEnabled && state.profile?.isNewUser == true) { + state to setOf(ViewAction.NavigateTo.UsersQuestionnaireOnboardingScreen) } else { - state to setOf( - Action.OnboardingFlowFinished( - OnboardingFlowFinishReason.NotificationOnboardingFinished(state.profile) - ) - ) + handleUsersQuestionnaireOnboardingCompleted(state) + } + + private fun handleUsersQuestionnaireOnboardingCompleted( + state: State + ): ReducerResult = + if (state.profile?.isNewUser == false) { + state to setOf(InternalAction.FetchFirstProblemOnboardingData) + } else { + completeOnboardingFlow(state) } private fun handleFirstProblemOnboardingDataFetched( state: State, message: InternalMessage.FirstProblemOnboardingDataFetched ): ReducerResult = - if (state.profile?.isNewUser == false) { + if (state.profile?.isNewUser == false && !message.wasFirstProblemOnboardingShown) { state to setOf( - if (!message.wasFirstProblemOnboardingShown) { - ViewAction.NavigateTo.FirstProblemOnboardingScreen(isNewUserMode = false) - } else { - Action.OnboardingFlowFinished( - OnboardingFlowFinishReason.FirstProblemOnboardingFinished - ) - } + ViewAction.NavigateTo.FirstProblemOnboardingScreen(isNewUserMode = false) ) } else { - state to setOf( - Action.OnboardingFlowFinished( - OnboardingFlowFinishReason.FirstProblemOnboardingFinished - ) - ) + completeOnboardingFlow(state) } private fun handleFirstProblemOnboardingCompleted( + state: State, message: Message.FirstProblemOnboardingCompleted - ): ReducerResult = - State(profile = null) to setOf( - message.firstProblemStepRoute?.let { stepRoute -> - ViewAction.NavigateTo.StudyPlanWithStep(stepRoute) - } ?: Action.OnboardingFlowFinished( - OnboardingFlowFinishReason.FirstProblemOnboardingFinished - ) - ) + ): ReducerResult { + val (newState, newActions) = completeOnboardingFlow(state) - private fun onNotificationOnboardingCompleted( - profile: Profile - ): ReducerResult = - State(profile = profile.takeIf { !it.isNewUser }) to setOf( - if (profile.isNewUser) { - Action.OnboardingFlowFinished( - OnboardingFlowFinishReason.NotificationOnboardingFinished(profile) - ) - } else { - InternalAction.FetchFirstProblemOnboardingData - } - ) + val finalActions = if (message.firstProblemStepRoute != null) { + setOf(ViewAction.NavigateTo.StudyPlanWithStep(message.firstProblemStepRoute)) + } else { + newActions + } + + return newState to finalActions + } + + private fun completeOnboardingFlow(state: State): ReducerResult = + State(profile = null) to setOf(Action.OnboardingFlowFinished(state.profile)) } \ No newline at end of file diff --git a/shared/src/commonMain/resources/MR/base/strings.xml b/shared/src/commonMain/resources/MR/base/strings.xml index 3b26bbb277..e276768cf2 100644 --- a/shared/src/commonMain/resources/MR/base/strings.xml +++ b/shared/src/commonMain/resources/MR/base/strings.xml @@ -539,6 +539,25 @@ Tell us about your time using our app + + How did you hear about\n%s? + + App Store + Google Play + Google Search + YouTube + Instagram + TikTok + News/article/blog + Friends/family + Other + + Type your answer here... + Send + Skip + + Answer sent! Enjoy 10 extra problems on the first day. + Interview preparation Oops! We were unable to load the interview widget diff --git a/shared/src/commonTest/kotlin/org/hyperskill/main/AppFeatureTest.kt b/shared/src/commonTest/kotlin/org/hyperskill/main/AppFeatureTest.kt index 125ebae269..741003a7dd 100644 --- a/shared/src/commonTest/kotlin/org/hyperskill/main/AppFeatureTest.kt +++ b/shared/src/commonTest/kotlin/org/hyperskill/main/AppFeatureTest.kt @@ -20,7 +20,7 @@ class AppFeatureTest { private val appReducer = AppReducer( StreakRecoveryReducer(resourceProvider = ResourceProviderStub()), NotificationClickHandlingReducer(), - WelcomeOnboardingReducer(), + WelcomeOnboardingReducer(isUsersQuestionnaireOnboardingEnabled = true), PlatformType.ANDROID ) From 5a263d1f77270236ed061b66e80c8e8cadae9b02 Mon Sep 17 00:00:00 2001 From: github-actions Date: Mon, 26 Feb 2024 02:02:51 +0000 Subject: [PATCH 093/104] Bump build number --- gradle/app.versions.toml | 2 +- .../NotificationServiceExtension/Info.plist | 2 +- .../iosHyperskillApp.xcodeproj/project.pbxproj | 16 ++++++++-------- iosHyperskillApp/iosHyperskillApp/Info.plist | 2 +- .../iosHyperskillAppTests/Info.plist | 2 +- .../iosHyperskillAppUITests/Info.plist | 2 +- 6 files changed, 13 insertions(+), 13 deletions(-) diff --git a/gradle/app.versions.toml b/gradle/app.versions.toml index 7334e17955..745edf3f1c 100644 --- a/gradle/app.versions.toml +++ b/gradle/app.versions.toml @@ -3,4 +3,4 @@ minSdk = '24' targetSdk = '33' compileSdk = '33' versionName = '1.50' -versionCode = '337' \ No newline at end of file +versionCode = '338' \ No newline at end of file diff --git a/iosHyperskillApp/NotificationServiceExtension/Info.plist b/iosHyperskillApp/NotificationServiceExtension/Info.plist index 5d6af58277..624d66524a 100644 --- a/iosHyperskillApp/NotificationServiceExtension/Info.plist +++ b/iosHyperskillApp/NotificationServiceExtension/Info.plist @@ -9,7 +9,7 @@ CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleVersion - 343 + 344 CFBundleShortVersionString 1.50 CFBundlePackageType diff --git a/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj b/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj index 09774956fb..f7ee743b58 100644 --- a/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj +++ b/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj @@ -5133,7 +5133,7 @@ buildSettings = { CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 343; + CURRENT_PROJECT_VERSION = 344; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = 3DWS674B2M; GENERATE_INFOPLIST_FILE = NO; @@ -5154,7 +5154,7 @@ buildSettings = { CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 343; + CURRENT_PROJECT_VERSION = 344; DEVELOPMENT_TEAM = 3DWS674B2M; GENERATE_INFOPLIST_FILE = NO; INFOPLIST_FILE = iosHyperskillAppUITests/Info.plist; @@ -5175,7 +5175,7 @@ BUNDLE_LOADER = "$(TEST_HOST)"; CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 343; + CURRENT_PROJECT_VERSION = 344; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = 3DWS674B2M; INFOPLIST_FILE = iosHyperskillAppTests/Info.plist; @@ -5196,7 +5196,7 @@ BUNDLE_LOADER = "$(TEST_HOST)"; CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 343; + CURRENT_PROJECT_VERSION = 344; DEVELOPMENT_TEAM = 3DWS674B2M; INFOPLIST_FILE = iosHyperskillAppTests/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; @@ -5217,7 +5217,7 @@ CODE_SIGN_ENTITLEMENTS = NotificationServiceExtension/NotificationServiceExtension.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 343; + CURRENT_PROJECT_VERSION = 344; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = 3DWS674B2M; INFOPLIST_FILE = NotificationServiceExtension/Info.plist; @@ -5245,7 +5245,7 @@ CODE_SIGN_ENTITLEMENTS = NotificationServiceExtension/NotificationServiceExtension.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 343; + CURRENT_PROJECT_VERSION = 344; DEVELOPMENT_TEAM = 3DWS674B2M; INFOPLIST_FILE = NotificationServiceExtension/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; @@ -5390,7 +5390,7 @@ CODE_SIGN_ENTITLEMENTS = iosHyperskillApp/iosHyperskillApp.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 343; + CURRENT_PROJECT_VERSION = 344; DEVELOPMENT_ASSET_PATHS = "\"iosHyperskillApp/Preview Content\""; DEVELOPMENT_TEAM = 3DWS674B2M; ENABLE_PREVIEWS = YES; @@ -5426,7 +5426,7 @@ CODE_SIGN_ENTITLEMENTS = iosHyperskillApp/iosHyperskillApp.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 343; + CURRENT_PROJECT_VERSION = 344; DEVELOPMENT_ASSET_PATHS = "\"iosHyperskillApp/Preview Content\""; DEVELOPMENT_TEAM = 3DWS674B2M; ENABLE_PREVIEWS = YES; diff --git a/iosHyperskillApp/iosHyperskillApp/Info.plist b/iosHyperskillApp/iosHyperskillApp/Info.plist index 411d79ee79..9c008a58fc 100644 --- a/iosHyperskillApp/iosHyperskillApp/Info.plist +++ b/iosHyperskillApp/iosHyperskillApp/Info.plist @@ -36,7 +36,7 @@ CFBundleVersion - 343 + 344 FirebaseAppDelegateProxyEnabled FirebaseMessagingAutoInitEnabled diff --git a/iosHyperskillApp/iosHyperskillAppTests/Info.plist b/iosHyperskillApp/iosHyperskillAppTests/Info.plist index a04acecd11..20623d0fc0 100644 --- a/iosHyperskillApp/iosHyperskillAppTests/Info.plist +++ b/iosHyperskillApp/iosHyperskillAppTests/Info.plist @@ -15,6 +15,6 @@ CFBundleShortVersionString 1.50 CFBundleVersion - 343 + 344 diff --git a/iosHyperskillApp/iosHyperskillAppUITests/Info.plist b/iosHyperskillApp/iosHyperskillAppUITests/Info.plist index 794a120645..2bea676cd6 100644 --- a/iosHyperskillApp/iosHyperskillAppUITests/Info.plist +++ b/iosHyperskillApp/iosHyperskillAppUITests/Info.plist @@ -15,6 +15,6 @@ CFBundleShortVersionString 1.50 CFBundleVersion - 343 + 344 From f6d7c4644251f277a580f66c90fdceca19eca097 Mon Sep 17 00:00:00 2001 From: Aleksandr Zhukov Date: Wed, 28 Feb 2024 14:07:42 +0400 Subject: [PATCH 094/104] Android widget for users questionnaire (#923) --- .../view/ui/delegate/ProblemsLimitDelegate.kt | 3 + .../study_plan/fragment/StudyPlanFragment.kt | 25 ++- .../UsersQuestionnaireCardDelegate.kt | 78 ++++++++++ .../ui/UsersQuestionnaireWidget.kt | 145 ++++++++++++++++++ .../drawable/ic_user_questionnaire_close.xml | 9 ++ .../main/res/layout/fragment_study_plan.xml | 19 ++- .../presentation/StudyPlanScreenViewModel.kt | 6 + 7 files changed, 278 insertions(+), 7 deletions(-) create mode 100644 androidHyperskillApp/src/main/java/org/hyperskill/app/android/users_questionnaire/delegate/UsersQuestionnaireCardDelegate.kt create mode 100644 androidHyperskillApp/src/main/java/org/hyperskill/app/android/users_questionnaire/ui/UsersQuestionnaireWidget.kt create mode 100644 androidHyperskillApp/src/main/res/drawable/ic_user_questionnaire_close.xml diff --git a/androidHyperskillApp/src/main/java/org/hyperskill/app/android/problems_limit/view/ui/delegate/ProblemsLimitDelegate.kt b/androidHyperskillApp/src/main/java/org/hyperskill/app/android/problems_limit/view/ui/delegate/ProblemsLimitDelegate.kt index 303f513f86..cbb16ada5c 100644 --- a/androidHyperskillApp/src/main/java/org/hyperskill/app/android/problems_limit/view/ui/delegate/ProblemsLimitDelegate.kt +++ b/androidHyperskillApp/src/main/java/org/hyperskill/app/android/problems_limit/view/ui/delegate/ProblemsLimitDelegate.kt @@ -20,14 +20,17 @@ class ProblemsLimitDelegate( addState() addState( + viewBinding.root, viewBinding.problemsLimitSkeleton ) addState( + viewBinding.root, viewBinding.problemsLimitsContent ) addState( + viewBinding.root, viewBinding.problemsLimitRetryButton ) } diff --git a/androidHyperskillApp/src/main/java/org/hyperskill/app/android/study_plan/fragment/StudyPlanFragment.kt b/androidHyperskillApp/src/main/java/org/hyperskill/app/android/study_plan/fragment/StudyPlanFragment.kt index 4cb7408023..765af0245a 100644 --- a/androidHyperskillApp/src/main/java/org/hyperskill/app/android/study_plan/fragment/StudyPlanFragment.kt +++ b/androidHyperskillApp/src/main/java/org/hyperskill/app/android/study_plan/fragment/StudyPlanFragment.kt @@ -19,6 +19,7 @@ import org.hyperskill.app.android.problems_limit.view.ui.delegate.ProblemsLimitD import org.hyperskill.app.android.stage_implementation.view.dialog.UnsupportedStageBottomSheet import org.hyperskill.app.android.study_plan.delegate.LearningActivityTargetViewActionHandler import org.hyperskill.app.android.study_plan.delegate.StudyPlanWidgetDelegate +import org.hyperskill.app.android.users_questionnaire.delegate.UsersQuestionnaireCardDelegate import org.hyperskill.app.core.injection.ReduxViewModelFactory import org.hyperskill.app.study_plan.presentation.StudyPlanScreenViewModel import org.hyperskill.app.study_plan.screen.presentation.StudyPlanScreenFeature @@ -49,6 +50,7 @@ class StudyPlanFragment : private var gamificationToolbarDelegate: GamificationToolbarDelegate? = null private var problemsLimitDelegate: ProblemsLimitDelegate? = null private var studyPlanWidgetDelegate: StudyPlanWidgetDelegate? = null + private var usersQuestionnaireCardDelegate: UsersQuestionnaireCardDelegate? = null private var fragmentWasResumed = false @@ -84,6 +86,7 @@ class StudyPlanFragment : override fun onViewCreated(view: View, savedInstanceState: Bundle?) { initGamificationToolbarDelegate() initProblemsLimitDelegate() + initUserQuestionnaireCardDelegate() initSwipeRefresh() studyPlanWidgetDelegate?.setup(viewBinding.studyPlanRecycler, viewBinding.studyPlanError) } @@ -110,6 +113,15 @@ class StudyPlanFragment : problemsLimitDelegate?.setup() } + private fun initUserQuestionnaireCardDelegate() { + usersQuestionnaireCardDelegate = UsersQuestionnaireCardDelegate() + usersQuestionnaireCardDelegate?.setup( + viewBinding.studyPlanUserQuestionnaire, + viewLifecycleOwner, + onNewMessage = studyPlanViewModel::onNewMessage + ) + } + private fun initSwipeRefresh() { with(viewBinding.studyPlanSwipeRefresh) { setHyperskillColors() @@ -125,6 +137,7 @@ class StudyPlanFragment : gamificationToolbarDelegate = null problemsLimitDelegate?.cleanup() problemsLimitDelegate = null + usersQuestionnaireCardDelegate = null } override fun onDestroy() { @@ -138,6 +151,10 @@ class StudyPlanFragment : gamificationToolbarDelegate?.setSubtitle(state.trackTitle) problemsLimitDelegate?.render(state.problemsLimitViewState) studyPlanWidgetDelegate?.render(state.studyPlanWidgetViewState) + usersQuestionnaireCardDelegate?.render( + state.usersQuestionnaireWidgetState, + viewBinding.studyPlanUserQuestionnaire + ) } private fun renderSwipeRefresh(state: StudyPlanScreenFeature.ViewState) { @@ -170,7 +187,13 @@ class StudyPlanFragment : } } } - is StudyPlanScreenFeature.Action.ViewAction.UsersQuestionnaireWidgetViewAction -> TODO() + is StudyPlanScreenFeature.Action.ViewAction.UsersQuestionnaireWidgetViewAction -> { + usersQuestionnaireCardDelegate?.handleActions( + context = requireContext(), + activity = requireActivity(), + action = action.viewAction + ) + } } } diff --git a/androidHyperskillApp/src/main/java/org/hyperskill/app/android/users_questionnaire/delegate/UsersQuestionnaireCardDelegate.kt b/androidHyperskillApp/src/main/java/org/hyperskill/app/android/users_questionnaire/delegate/UsersQuestionnaireCardDelegate.kt new file mode 100644 index 0000000000..35daee6281 --- /dev/null +++ b/androidHyperskillApp/src/main/java/org/hyperskill/app/android/users_questionnaire/delegate/UsersQuestionnaireCardDelegate.kt @@ -0,0 +1,78 @@ +package org.hyperskill.app.android.users_questionnaire.delegate + +import android.app.Activity +import android.content.Context +import android.net.Uri +import androidx.browser.customtabs.CustomTabsIntent +import androidx.compose.runtime.DisposableEffect +import androidx.compose.runtime.getValue +import androidx.compose.ui.platform.ComposeView +import androidx.compose.ui.platform.ViewCompositionStrategy +import androidx.core.view.isVisible +import androidx.lifecycle.LifecycleOwner +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import kotlinx.coroutines.flow.MutableStateFlow +import org.hyperskill.app.android.core.extensions.setHyperskillColors +import org.hyperskill.app.android.core.view.ui.widget.compose.HyperskillTheme +import org.hyperskill.app.android.users_questionnaire.ui.UsersQuestionnaireWidget +import org.hyperskill.app.users_questionnaire.widget.presentation.UsersQuestionnaireWidgetFeature + +class UsersQuestionnaireCardDelegate { + + private val stateFlow: MutableStateFlow = MutableStateFlow(null) + + fun setup( + composeView: ComposeView, + viewLifecycleOwner: LifecycleOwner, + onNewMessage: (UsersQuestionnaireWidgetFeature.Message) -> Unit + ) { + composeView.apply { + setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnLifecycleDestroyed(viewLifecycleOwner)) + setContent { + HyperskillTheme { + val viewState by stateFlow.collectAsStateWithLifecycle() + DisposableEffect(viewLifecycleOwner) { + onNewMessage(UsersQuestionnaireWidgetFeature.Message.ViewedEventMessage) + onDispose { + // no op + } + } + viewState?.let { actualViewState -> + UsersQuestionnaireWidget( + actualViewState, + onNewMessage = onNewMessage + ) + } + } + } + } + } + + fun render( + state: UsersQuestionnaireWidgetFeature.State, + composeView: ComposeView + ) { + composeView.isVisible = when (state) { + UsersQuestionnaireWidgetFeature.State.Idle, + UsersQuestionnaireWidgetFeature.State.Hidden -> false + UsersQuestionnaireWidgetFeature.State.Loading, + UsersQuestionnaireWidgetFeature.State.Visible -> true + } + stateFlow.value = state + } + + fun handleActions( + context: Context, + activity: Activity, + action: UsersQuestionnaireWidgetFeature.Action.ViewAction + ) { + when (action) { + is UsersQuestionnaireWidgetFeature.Action.ViewAction.ShowUsersQuestionnaire -> { + val intent = CustomTabsIntent.Builder() + .setHyperskillColors(context) + .build() + intent.launchUrl(activity, Uri.parse(action.url)) + } + } + } +} \ No newline at end of file diff --git a/androidHyperskillApp/src/main/java/org/hyperskill/app/android/users_questionnaire/ui/UsersQuestionnaireWidget.kt b/androidHyperskillApp/src/main/java/org/hyperskill/app/android/users_questionnaire/ui/UsersQuestionnaireWidget.kt new file mode 100644 index 0000000000..fd28735d0d --- /dev/null +++ b/androidHyperskillApp/src/main/java/org/hyperskill/app/android/users_questionnaire/ui/UsersQuestionnaireWidget.kt @@ -0,0 +1,145 @@ +package org.hyperskill.app.android.users_questionnaire.ui + +import androidx.compose.foundation.Image +import androidx.compose.foundation.background +import androidx.compose.foundation.border +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.requiredSize +import androidx.compose.foundation.layout.requiredWidth +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Brush +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.Shape +import androidx.compose.ui.res.colorResource +import androidx.compose.ui.res.dimensionResource +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.tooling.preview.PreviewParameter +import androidx.compose.ui.tooling.preview.PreviewParameterProvider +import androidx.compose.ui.unit.dp +import org.hyperskill.app.android.R +import org.hyperskill.app.android.core.view.ui.widget.compose.HyperskillTheme +import org.hyperskill.app.android.core.view.ui.widget.compose.ShimmerLoading +import org.hyperskill.app.users_questionnaire.widget.presentation.UsersQuestionnaireWidgetFeature +import org.hyperskill.app.R as SharedR + +object UsersQuestionnaireWidgetDefaults { + + val shape: Shape + @Composable + get() = RoundedCornerShape(dimensionResource(id = R.dimen.corner_radius)) +} + +@Composable +fun UsersQuestionnaireWidget( + viewState: UsersQuestionnaireWidgetFeature.State, + onNewMessage: (UsersQuestionnaireWidgetFeature.Message) -> Unit, + modifier: Modifier = Modifier +) { + when (viewState) { + UsersQuestionnaireWidgetFeature.State.Idle, + UsersQuestionnaireWidgetFeature.State.Hidden -> { + // no op + } + UsersQuestionnaireWidgetFeature.State.Loading -> { + ShimmerLoading( + modifier = modifier + .fillMaxWidth() + .height(64.dp) + ) + } + UsersQuestionnaireWidgetFeature.State.Visible -> { + UsersQuestionnaireWidgetContent(onNewMessage, modifier) + } + } +} + +@Composable +private fun UsersQuestionnaireWidgetContent( + onNewMessage: (UsersQuestionnaireWidgetFeature.Message) -> Unit, + modifier: Modifier = Modifier +) { + Row( + horizontalArrangement = Arrangement.spacedBy(16.dp), + modifier = modifier + .clip(UsersQuestionnaireWidgetDefaults.shape) + .border( + width = 1.dp, + color = colorResource(id = SharedR.color.color_on_surface_alpha_9), + shape = UsersQuestionnaireWidgetDefaults.shape + ) + .background( + Brush.verticalGradient( + listOf( + Color(0xFF7AB7FE), + Color(0xFF6C63FF) + ) + ) + ) + .clickable { + onNewMessage(UsersQuestionnaireWidgetFeature.Message.WidgetClicked) + } + .padding( + horizontal = 20.dp, + vertical = 24.dp + ) + ) { + Text( + text = stringResource(id = SharedR.string.users_questionnaire_widget_title), + color = colorResource(id = SharedR.color.text_on_color), + modifier = Modifier + .weight(1f) + .align(Alignment.CenterVertically) + ) + Box( + modifier = Modifier + .requiredSize(24.dp) + .align(Alignment.CenterVertically) + .clickable { + onNewMessage(UsersQuestionnaireWidgetFeature.Message.CloseClicked) + } + ) { + Image( + painter = painterResource(id = R.drawable.ic_user_questionnaire_close), + contentDescription = null, + modifier = Modifier.align(Alignment.Center) + ) + } + } +} + +private class UsersQuestionnairePreviewParameterProvider : + PreviewParameterProvider { + override val values: Sequence + get() = sequenceOf( + UsersQuestionnaireWidgetFeature.State.Loading, + UsersQuestionnaireWidgetFeature.State.Visible + ) +} + +@Preview +@Composable +private fun UsersQuestionnaireWidgetPreview( + @PreviewParameter(UsersQuestionnairePreviewParameterProvider::class) + state: UsersQuestionnaireWidgetFeature.State +) { + HyperskillTheme { + UsersQuestionnaireWidget( + viewState = state, + onNewMessage = {}, + modifier = Modifier.requiredWidth(320.dp) + ) + } +} \ No newline at end of file diff --git a/androidHyperskillApp/src/main/res/drawable/ic_user_questionnaire_close.xml b/androidHyperskillApp/src/main/res/drawable/ic_user_questionnaire_close.xml new file mode 100644 index 0000000000..b6683b2f3b --- /dev/null +++ b/androidHyperskillApp/src/main/res/drawable/ic_user_questionnaire_close.xml @@ -0,0 +1,9 @@ + + + diff --git a/androidHyperskillApp/src/main/res/layout/fragment_study_plan.xml b/androidHyperskillApp/src/main/res/layout/fragment_study_plan.xml index 9ab2df38c2..b35c3e3529 100644 --- a/androidHyperskillApp/src/main/res/layout/fragment_study_plan.xml +++ b/androidHyperskillApp/src/main/res/layout/fragment_study_plan.xml @@ -25,12 +25,19 @@ android:orientation="vertical"> + android:id="@+id/studyPlanProblemsLimit" + layout="@layout/layout_problems_limit" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginTop="28dp" + android:layout_marginHorizontal="20dp"/> + + (reduxViewContainer) { + init { onNewMessage(StudyPlanScreenFeature.Message.Initialize) onNewMessage(StudyPlanScreenFeature.Message.ViewedEventMessage) } + + fun onNewMessage(message: UsersQuestionnaireWidgetFeature.Message) { + onNewMessage(StudyPlanScreenFeature.Message.UsersQuestionnaireWidgetMessage(message)) + } } \ No newline at end of file From 28ecbba48a73109b266a66f90cea9d7db2da303a Mon Sep 17 00:00:00 2001 From: github-actions Date: Wed, 28 Feb 2024 10:08:31 +0000 Subject: [PATCH 095/104] Bump build number --- gradle/app.versions.toml | 2 +- .../NotificationServiceExtension/Info.plist | 2 +- .../iosHyperskillApp.xcodeproj/project.pbxproj | 16 ++++++++-------- iosHyperskillApp/iosHyperskillApp/Info.plist | 2 +- .../iosHyperskillAppTests/Info.plist | 2 +- .../iosHyperskillAppUITests/Info.plist | 2 +- 6 files changed, 13 insertions(+), 13 deletions(-) diff --git a/gradle/app.versions.toml b/gradle/app.versions.toml index 745edf3f1c..3918425d88 100644 --- a/gradle/app.versions.toml +++ b/gradle/app.versions.toml @@ -3,4 +3,4 @@ minSdk = '24' targetSdk = '33' compileSdk = '33' versionName = '1.50' -versionCode = '338' \ No newline at end of file +versionCode = '339' \ No newline at end of file diff --git a/iosHyperskillApp/NotificationServiceExtension/Info.plist b/iosHyperskillApp/NotificationServiceExtension/Info.plist index 624d66524a..dfcaadc66c 100644 --- a/iosHyperskillApp/NotificationServiceExtension/Info.plist +++ b/iosHyperskillApp/NotificationServiceExtension/Info.plist @@ -9,7 +9,7 @@ CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleVersion - 344 + 345 CFBundleShortVersionString 1.50 CFBundlePackageType diff --git a/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj b/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj index f7ee743b58..d34112365e 100644 --- a/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj +++ b/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj @@ -5133,7 +5133,7 @@ buildSettings = { CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 344; + CURRENT_PROJECT_VERSION = 345; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = 3DWS674B2M; GENERATE_INFOPLIST_FILE = NO; @@ -5154,7 +5154,7 @@ buildSettings = { CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 344; + CURRENT_PROJECT_VERSION = 345; DEVELOPMENT_TEAM = 3DWS674B2M; GENERATE_INFOPLIST_FILE = NO; INFOPLIST_FILE = iosHyperskillAppUITests/Info.plist; @@ -5175,7 +5175,7 @@ BUNDLE_LOADER = "$(TEST_HOST)"; CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 344; + CURRENT_PROJECT_VERSION = 345; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = 3DWS674B2M; INFOPLIST_FILE = iosHyperskillAppTests/Info.plist; @@ -5196,7 +5196,7 @@ BUNDLE_LOADER = "$(TEST_HOST)"; CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 344; + CURRENT_PROJECT_VERSION = 345; DEVELOPMENT_TEAM = 3DWS674B2M; INFOPLIST_FILE = iosHyperskillAppTests/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; @@ -5217,7 +5217,7 @@ CODE_SIGN_ENTITLEMENTS = NotificationServiceExtension/NotificationServiceExtension.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 344; + CURRENT_PROJECT_VERSION = 345; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = 3DWS674B2M; INFOPLIST_FILE = NotificationServiceExtension/Info.plist; @@ -5245,7 +5245,7 @@ CODE_SIGN_ENTITLEMENTS = NotificationServiceExtension/NotificationServiceExtension.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 344; + CURRENT_PROJECT_VERSION = 345; DEVELOPMENT_TEAM = 3DWS674B2M; INFOPLIST_FILE = NotificationServiceExtension/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; @@ -5390,7 +5390,7 @@ CODE_SIGN_ENTITLEMENTS = iosHyperskillApp/iosHyperskillApp.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 344; + CURRENT_PROJECT_VERSION = 345; DEVELOPMENT_ASSET_PATHS = "\"iosHyperskillApp/Preview Content\""; DEVELOPMENT_TEAM = 3DWS674B2M; ENABLE_PREVIEWS = YES; @@ -5426,7 +5426,7 @@ CODE_SIGN_ENTITLEMENTS = iosHyperskillApp/iosHyperskillApp.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 344; + CURRENT_PROJECT_VERSION = 345; DEVELOPMENT_ASSET_PATHS = "\"iosHyperskillApp/Preview Content\""; DEVELOPMENT_TEAM = 3DWS674B2M; ENABLE_PREVIEWS = YES; diff --git a/iosHyperskillApp/iosHyperskillApp/Info.plist b/iosHyperskillApp/iosHyperskillApp/Info.plist index 9c008a58fc..0ea38d11af 100644 --- a/iosHyperskillApp/iosHyperskillApp/Info.plist +++ b/iosHyperskillApp/iosHyperskillApp/Info.plist @@ -36,7 +36,7 @@ CFBundleVersion - 344 + 345 FirebaseAppDelegateProxyEnabled FirebaseMessagingAutoInitEnabled diff --git a/iosHyperskillApp/iosHyperskillAppTests/Info.plist b/iosHyperskillApp/iosHyperskillAppTests/Info.plist index 20623d0fc0..ad17a555d0 100644 --- a/iosHyperskillApp/iosHyperskillAppTests/Info.plist +++ b/iosHyperskillApp/iosHyperskillAppTests/Info.plist @@ -15,6 +15,6 @@ CFBundleShortVersionString 1.50 CFBundleVersion - 344 + 345 diff --git a/iosHyperskillApp/iosHyperskillAppUITests/Info.plist b/iosHyperskillApp/iosHyperskillAppUITests/Info.plist index 2bea676cd6..164949e93c 100644 --- a/iosHyperskillApp/iosHyperskillAppUITests/Info.plist +++ b/iosHyperskillApp/iosHyperskillAppUITests/Info.plist @@ -15,6 +15,6 @@ CFBundleShortVersionString 1.50 CFBundleVersion - 344 + 345 From 75552cf5c53d33f9525985b31bca2d845193d68e Mon Sep 17 00:00:00 2001 From: Aleksandr Zhukov Date: Thu, 29 Feb 2024 05:08:41 +0400 Subject: [PATCH 096/104] Mobile only subscription (#852) * Support Mobile-only subscription type (#854) * Android, Shared integrate RevenueCat SDK (#855) * Shared paywall feature (#859) * Android paywall feature (#860) * Update android version code * iOS: Paywall screen UI (#867) * Shared, Android subscription in profile settings (#868) * iOS: Subscription info UI profile settings (#875) * iOS: Manage subscription UI (#878) * Shared paywall subscription purchase (#876) * Fix profile settings ViewState (#877) * Shared, Android add paywall link to limits bottomsheet (#888) * Shared, Android manage subscription (#887) * Shared, Android show paywall every third session (#892) * Shared: Purchase feature flag (#897) * Shared: Setup RevenueCat only when user is authorized (#899) * iOS: Show paywall every third time (#901) * Shared, iOS: Add terms link to Paywall (#904) * Android add terms of service to the paywall (#911) * Shared update subscription state after expiration (#920) --------- Co-authored-by: Ivan Magda --- .gitattributes | 1 + .../src/main/AndroidManifest.xml | 3 +- .../hyperskill/app/android/HyperskillApp.kt | 4 +- .../core/extensions/ContextExtensions.kt | 14 +- .../android/core/extensions/ThemeExtension.kt | 12 +- .../compose/PaddingValuesExtensions.kt | 20 ++ .../core/injection/AndroidAppComponentImpl.kt | 15 +- .../compose/HyperskillProgressIndicator.kt | 16 + .../ui/widget/compose/HyperskillTopAppBar.kt | 4 +- .../compose/OnComposableShownFirstTime.kt | 17 + .../home/view/ui/fragment/HomeFragment.kt | 12 +- .../main/view/ui/activity/MainActivity.kt | 98 ++++-- .../fragment/ManageSubscriptionFragment.kt | 74 ++++ .../navigation/ManageSubscriptionScreen.kt | 11 + .../ui/ManageSubscriptionContent.kt | 149 ++++++++ .../ui/ManageSubscriptionDefaults.kt | 13 + .../ui/ManageSubscriptionPreviewDefaults.kt | 19 + .../ui/ManageSubscriptionScreen.kt | 111 ++++++ .../paywall/fragment/PaywallFragment.kt | 106 ++++++ .../paywall/navigation/PaywallScreen.kt | 21 ++ .../app/android/paywall/ui/PaywallContent.kt | 115 ++++++ .../paywall/ui/PaywallPreviewDefaults.kt | 5 + .../app/android/paywall/ui/PaywallScreen.kt | 175 ++++++++++ .../android/paywall/ui/SubscriptionDetails.kt | 63 ++++ .../paywall/ui/SubscriptionSyncLoading.kt | 57 +++ .../delegate/ProblemOfDayCardFormDelegate.kt | 10 +- .../dialog/ProblemsLimitReachedBottomSheet.kt | 16 +- .../dialog/ProfileSettingsDialogFragment.kt | 132 ++++--- .../view/fragment/DefaultStepQuizFragment.kt | 10 +- .../TopicsRepetitionCardFormDelegate.kt | 5 +- .../main/res/drawable-hdpi/img_paywall.webp | Bin 0 -> 80048 bytes .../main/res/drawable-mdpi/img_paywall.webp | Bin 0 -> 39372 bytes .../main/res/drawable-xhdpi/img_paywall.webp | Bin 0 -> 133350 bytes .../main/res/drawable-xxhdpi/img_paywall.webp | Bin 0 -> 277248 bytes .../res/drawable-xxxhdpi/img_paywall.webp | Bin 0 -> 459888 bytes .../main/res/drawable/ic_paywall_option.xml | 9 + .../fragment_problems_limit_reached.xml | 27 +- .../res/layout/fragment_profile_settings.xml | 299 ++-------------- .../layout/layout_problem_of_the_day_card.xml | 2 +- .../layout/layout_topics_repetition_card.xml | 4 +- .../layout/view_profile_settings_content.xml | 328 ++++++++++++++++++ .../util/AppFeatureStateSerializationTest.kt | 6 +- buildSrc/build.gradle.kts | 2 +- gradle/app.versions.toml | 2 +- gradle/libs.versions.toml | 3 + .../project.pbxproj | 62 +++- .../Assets.xcassets/Paywall/Contents.json | 6 + .../Contents.json | 23 ++ .../paywall-premium-mobile.png | Bin 0 -> 10440 bytes .../paywall-premium-mobile@2x.png | Bin 0 -> 29671 bytes .../paywall-premium-mobile@3x.png | Bin 0 -> 56799 bytes .../Extensions/SwiftUI/View/View+Task.swift | 37 ++ ...ettingsFeatureViewStateKsExtensions.swift} | 18 +- .../Sources/Models/Constants/Strings.swift | 2 + .../Sources/Modules/App/AppViewModel.swift | 13 + .../ViewControllers/AppViewController.swift | 11 +- .../Sources/Modules/Home/HomeViewModel.swift | 23 +- .../Sources/Modules/Home/Views/HomeView.swift | 4 +- .../Modules/Paywall/PaywallFeaturesView.swift | 91 +++++ .../Modules/Paywall/PaywallFooterView.swift | 63 ++++ .../Sources/Modules/Paywall/PaywallView.swift | 58 ++++ .../ProfileSettingsViewModel.swift | 16 +- ...ofileSettingsSubscriptionSectionView.swift | 41 +++ .../{ => Views}/ProfileSettingsView.swift | 32 +- .../Modules/StepQuiz/Views/StepQuizView.swift | 2 + .../SubscriptionDetailsView.swift | 81 +++++ .../Views/SwiftUI/BadgeView/BadgeView.swift | 7 +- .../SwiftUI/OffsetObservingScrollView.swift | 166 +++++++++ shared/build.gradle.kts | 23 ++ .../androidMain/keys/revenuecat.properties | Bin 0 -> 83 bytes .../app/core/domain/platform/Platform.kt | 2 + .../core/injection/CommonAndroidAppGraph.kt | 11 +- .../injection/CommonAndroidAppGraphImpl.kt | 31 +- .../PlatformManageSubscriptionComponent.kt | 7 + ...PlatformManageSubscriptionComponentImpl.kt | 20 ++ .../ManageSubscriptionViewModel.kt | 24 ++ .../injection/PlatformPaywallComponent.kt | 7 + .../injection/PlatformPaywallComponentImpl.kt | 20 ++ .../paywall/presentation/PaywallViewModel.kt | 38 ++ .../presentation/ProfileSettingsViewModel.kt | 14 +- .../domain/AndroidPurchaseManager.kt | 114 ++++++ .../domain/model/AndroidPurchaseParams.kt | 7 + .../step_quiz/AndroidStepQuizTest.kt | 1 + .../hyperskill/HyperskillAnalyticRoute.kt | 12 +- .../hyperskill/HyperskillAnalyticTarget.kt | 10 +- .../app/core/domain/platform/Platform.kt | 5 + .../hyperskill/app/core/injection/AppGraph.kt | 12 +- .../app/core/injection/BaseAppGraph.kt | 24 +- .../StateRepositoriesComponentImpl.kt | 11 +- .../core/view/mapper/date/MonthFormatter.kt | 17 + .../view/mapper/date/SharedDateFormatter.kt | 15 + .../domain/interactor/FreemiumInteractor.kt | 60 ---- .../injection/FreemiumDataComponent.kt | 7 - .../injection/FreemiumDataComponentImpl.kt | 19 - .../app/home/injection/HomeComponentImpl.kt | 2 +- .../app/home/injection/HomeFeatureBuilder.kt | 6 +- .../home/presentation/HomeActionDispatcher.kt | 12 +- .../app/home/presentation/HomeFeature.kt | 6 +- .../app/home/presentation/HomeReducer.kt | 2 +- .../main/domain/interactor/AppInteractor.kt | 25 ++ .../app/main/injection/AppFeatureBuilder.kt | 33 +- .../app/main/injection/MainComponentImpl.kt | 38 +- .../main/presentation/AppActionDispatcher.kt | 189 +++++++--- .../app/main/presentation/AppFeature.kt | 46 ++- .../app/main/presentation/AppReducer.kt | 131 +++++-- ...ionClickedManageHyperskillAnalyticEvent.kt | 29 ++ ...bscriptionViewedHyperskillAnalyticEvent.kt | 23 ++ ...ionClickedManageHyperskillAnalyticEvent.kt | 29 ++ .../injection/ManageSubscriptionComponent.kt | 10 + .../ManageSubscriptionComponentImpl.kt | 23 ++ .../ManageSubscriptionFeatureBuilder.kt | 60 ++++ .../ManageSubscriptionActionDispatcher.kt | 86 +++++ .../presentation/ManageSubscriptionFeature.kt | 71 ++++ .../presentation/ManageSubscriptionReducer.kt | 86 +++++ .../ManageSubscriptionStateExtensions.kt | 6 + .../ManageSubscriptionViewStateMapper.kt | 43 +++ .../domain/analytic/PaywallAnalyticParams.kt | 5 + ...dBuySubscriptionHyperskillAnalyticEvent.kt | 44 +++ ...ntinueWithLimitsHyperskillAnalyticEvent.kt | 44 +++ ...ryContentLoadingHyperskillAnalyticEvent.kt | 44 +++ ...AndPrivacyPolicyHyperskillAnalyticEvent.kt | 44 +++ .../PaywallViewedHyperskillAnalyticEvent.kt | 38 ++ .../domain/model/PaywallTransitionSource.kt | 9 + .../app/paywall/injection/PaywallComponent.kt | 10 + .../paywall/injection/PaywallComponentImpl.kt | 26 ++ .../injection/PaywallFeatureBuilder.kt | 67 ++++ .../presentation/PaywallActionDispatcher.kt | 109 ++++++ .../paywall/presentation/PaywallFeature.kt | 112 ++++++ .../paywall/presentation/PaywallReducer.kt | 201 +++++++++++ .../paywall/view/PaywallViewStateMapper.kt | 52 +++ .../injection/ProblemsLimitComponentImpl.kt | 1 - .../ProblemsLimitActionDispatcher.kt | 39 +-- .../presentation/ProblemsLimitFeature.kt | 4 +- .../presentation/ProblemsLimitReducer.kt | 3 +- .../mapper/ProblemsLimitViewStateMapper.kt | 3 +- .../app/profile/domain/model/FeatureKeys.kt | 1 + .../app/profile/domain/model/FeaturesMap.kt | 3 + ...eSettingsClickedHyperskillAnalyticEvent.kt | 4 +- .../injection/ProfileSettingsComponent.kt | 7 +- .../injection/ProfileSettingsComponentImpl.kt | 40 ++- .../ProfileSettingsFeatureBuilder.kt | 38 +- .../ProfileSettingsActionDispatcher.kt | 95 ++++- .../presentation/ProfileSettingsFeature.kt | 42 ++- .../presentation/ProfileSettingsReducer.kt | 69 +++- .../view/ProfileSettingsViewStateMapper.kt | 54 +++ .../view/ProgressScreenViewStateMapper.kt | 5 +- .../domain/interactor/PurchaseInteractor.kt | 37 ++ .../domain/model/PlatformPurchaseParams.kt | 3 + .../purchases/domain/model/PurchaseManager.kt | 35 ++ .../purchases/domain/model/PurchaseResult.kt | 94 +++++ .../purchases/injection/PurchaseComponent.kt | 7 + .../injection/PurchaseComponentImpl.kt | 12 + .../HyperskillSentryTransactionBuilder.kt | 36 ++ .../injection/StepCompletionComponentImpl.kt | 2 +- .../StepCompletionActionDispatcher.kt | 6 +- ...dUnlockUnlimitedProblemsHSAnalyticEvent.kt | 30 ++ .../injection/StepQuizComponentImpl.kt | 29 +- .../injection/StepQuizFeatureBuilder.kt | 29 +- .../presentation/StepQuizActionDispatcher.kt | 146 ++++---- .../step_quiz/presentation/StepQuizFeature.kt | 11 +- .../step_quiz/presentation/StepQuizReducer.kt | 14 +- .../injection/StepQuizHintsComponentImpl.kt | 2 +- .../StepQuizHintsActionDispatcher.kt | 18 +- .../presentation/StepQuizHintsFeature.kt | 24 +- .../presentation/StepQuizHintsReducer.kt | 10 +- .../mapper/StepQuizHintsViewStateMapper.kt | 2 +- .../CurrentSubscriptionStateHolderImpl.kt | 33 ++ .../cache/SubscriptionCacheKeys.kt | 5 + .../CurrentSubscriptionStateRepositoryImpl.kt | 6 +- .../repository/SubscriptionsRepositoryImpl.kt | 12 + .../source/CurrentSubscriptionStateHolder.kt | 6 + .../source/SubscriptionsRemoteDataSource.kt | 2 + .../interactor/SubscriptionsInteractor.kt | 118 +++++++ .../domain/model/Subscription.kt | 53 ++- .../domain/model/SubscriptionStatus.kt | 16 + .../domain/model/SubscriptionType.kt | 29 +- .../repository/SubscriptionsRepository.kt | 7 + .../injection/SubscriptionsDataComponent.kt | 9 + .../SubscriptionsDataComponentImpl.kt | 43 +++ .../SubscriptionsRemoteDataSourceImpl.kt | 10 +- .../TrackSelectionDetailsFeature.kt | 5 +- .../TrackSelectionDetailsReducer.kt | 16 +- .../TrackSelectionDetailsViewStateMapper.kt | 12 +- .../WelcomeOnboardingComponentImpl.kt | 4 +- .../WelcomeOnboardingActionDispatcher.kt | 12 +- .../presentation/WelcomeOnboardingFeature.kt | 11 + .../presentation/WelcomeOnboardingReducer.kt | 31 ++ .../commonMain/resources/MR/base/strings.xml | 37 +- .../StepQuizHintsViewStateMapperTest.kt | 2 +- .../mapper/date/SharedDateFormatterTest.kt | 14 + .../org/hyperskill/main/AppFeatureTest.kt | 135 ++++++- .../org/hyperskill/profile/ProfileStub.kt | 5 +- .../progress_screen/ProgressScreenTest.kt | 44 +-- .../org/hyperskill/step_quiz/StepQuizTest.kt | 4 + .../SubscriptionSerializationTest.kt | 40 +++ .../subscriptions/SubscriptionStub.kt | 8 +- .../details/TrackSelectionDetailsTest.kt | 39 ++- .../app/core/domain/platform/Platform.kt | 2 + .../app/core/injection/IosAppComponentImpl.kt | 6 + .../domain/model/IOSPurchaseManager.kt | 27 ++ .../domain/model/IOSPurchaseParams.kt | 5 + 201 files changed, 5764 insertions(+), 1017 deletions(-) create mode 100644 androidHyperskillApp/src/main/java/org/hyperskill/app/android/core/extensions/compose/PaddingValuesExtensions.kt create mode 100644 androidHyperskillApp/src/main/java/org/hyperskill/app/android/core/view/ui/widget/compose/HyperskillProgressIndicator.kt create mode 100644 androidHyperskillApp/src/main/java/org/hyperskill/app/android/core/view/ui/widget/compose/OnComposableShownFirstTime.kt create mode 100644 androidHyperskillApp/src/main/java/org/hyperskill/app/android/manage_subscription/fragment/ManageSubscriptionFragment.kt create mode 100644 androidHyperskillApp/src/main/java/org/hyperskill/app/android/manage_subscription/navigation/ManageSubscriptionScreen.kt create mode 100644 androidHyperskillApp/src/main/java/org/hyperskill/app/android/manage_subscription/ui/ManageSubscriptionContent.kt create mode 100644 androidHyperskillApp/src/main/java/org/hyperskill/app/android/manage_subscription/ui/ManageSubscriptionDefaults.kt create mode 100644 androidHyperskillApp/src/main/java/org/hyperskill/app/android/manage_subscription/ui/ManageSubscriptionPreviewDefaults.kt create mode 100644 androidHyperskillApp/src/main/java/org/hyperskill/app/android/manage_subscription/ui/ManageSubscriptionScreen.kt create mode 100644 androidHyperskillApp/src/main/java/org/hyperskill/app/android/paywall/fragment/PaywallFragment.kt create mode 100644 androidHyperskillApp/src/main/java/org/hyperskill/app/android/paywall/navigation/PaywallScreen.kt create mode 100644 androidHyperskillApp/src/main/java/org/hyperskill/app/android/paywall/ui/PaywallContent.kt create mode 100644 androidHyperskillApp/src/main/java/org/hyperskill/app/android/paywall/ui/PaywallPreviewDefaults.kt create mode 100644 androidHyperskillApp/src/main/java/org/hyperskill/app/android/paywall/ui/PaywallScreen.kt create mode 100644 androidHyperskillApp/src/main/java/org/hyperskill/app/android/paywall/ui/SubscriptionDetails.kt create mode 100644 androidHyperskillApp/src/main/java/org/hyperskill/app/android/paywall/ui/SubscriptionSyncLoading.kt create mode 100644 androidHyperskillApp/src/main/res/drawable-hdpi/img_paywall.webp create mode 100644 androidHyperskillApp/src/main/res/drawable-mdpi/img_paywall.webp create mode 100644 androidHyperskillApp/src/main/res/drawable-xhdpi/img_paywall.webp create mode 100644 androidHyperskillApp/src/main/res/drawable-xxhdpi/img_paywall.webp create mode 100644 androidHyperskillApp/src/main/res/drawable-xxxhdpi/img_paywall.webp create mode 100644 androidHyperskillApp/src/main/res/drawable/ic_paywall_option.xml create mode 100644 androidHyperskillApp/src/main/res/layout/view_profile_settings_content.xml create mode 100644 iosHyperskillApp/iosHyperskillApp/Assets.xcassets/Paywall/Contents.json create mode 100644 iosHyperskillApp/iosHyperskillApp/Assets.xcassets/Paywall/paywall-premium-mobile.imageset/Contents.json create mode 100644 iosHyperskillApp/iosHyperskillApp/Assets.xcassets/Paywall/paywall-premium-mobile.imageset/paywall-premium-mobile.png create mode 100644 iosHyperskillApp/iosHyperskillApp/Assets.xcassets/Paywall/paywall-premium-mobile.imageset/paywall-premium-mobile@2x.png create mode 100644 iosHyperskillApp/iosHyperskillApp/Assets.xcassets/Paywall/paywall-premium-mobile.imageset/paywall-premium-mobile@3x.png create mode 100644 iosHyperskillApp/iosHyperskillApp/Sources/Extensions/SwiftUI/View/View+Task.swift rename iosHyperskillApp/iosHyperskillApp/Sources/Frameworks/sharedSwift/Extensions/{ProfileSettingsFeatureStateKsExtensions.swift => ProfileSettingsFeatureViewStateKsExtensions.swift} (53%) create mode 100644 iosHyperskillApp/iosHyperskillApp/Sources/Modules/Paywall/PaywallFeaturesView.swift create mode 100644 iosHyperskillApp/iosHyperskillApp/Sources/Modules/Paywall/PaywallFooterView.swift create mode 100644 iosHyperskillApp/iosHyperskillApp/Sources/Modules/Paywall/PaywallView.swift create mode 100644 iosHyperskillApp/iosHyperskillApp/Sources/Modules/ProfileSettings/Views/ProfileSettingsSubscriptionSectionView.swift rename iosHyperskillApp/iosHyperskillApp/Sources/Modules/ProfileSettings/{ => Views}/ProfileSettingsView.swift (90%) create mode 100644 iosHyperskillApp/iosHyperskillApp/Sources/Modules/SubscriptionDetails/SubscriptionDetailsView.swift create mode 100644 iosHyperskillApp/iosHyperskillApp/Sources/Views/SwiftUI/OffsetObservingScrollView.swift create mode 100644 shared/src/androidMain/keys/revenuecat.properties create mode 100644 shared/src/androidMain/kotlin/org/hyperskill/app/manage_subscription/injection/PlatformManageSubscriptionComponent.kt create mode 100644 shared/src/androidMain/kotlin/org/hyperskill/app/manage_subscription/injection/PlatformManageSubscriptionComponentImpl.kt create mode 100644 shared/src/androidMain/kotlin/org/hyperskill/app/manage_subscription/presentation/ManageSubscriptionViewModel.kt create mode 100644 shared/src/androidMain/kotlin/org/hyperskill/app/paywall/injection/PlatformPaywallComponent.kt create mode 100644 shared/src/androidMain/kotlin/org/hyperskill/app/paywall/injection/PlatformPaywallComponentImpl.kt create mode 100644 shared/src/androidMain/kotlin/org/hyperskill/app/paywall/presentation/PaywallViewModel.kt create mode 100644 shared/src/androidMain/kotlin/org/hyperskill/app/purchases/domain/AndroidPurchaseManager.kt create mode 100644 shared/src/androidMain/kotlin/org/hyperskill/app/purchases/domain/model/AndroidPurchaseParams.kt delete mode 100644 shared/src/commonMain/kotlin/org/hyperskill/app/freemium/domain/interactor/FreemiumInteractor.kt delete mode 100644 shared/src/commonMain/kotlin/org/hyperskill/app/freemium/injection/FreemiumDataComponent.kt delete mode 100644 shared/src/commonMain/kotlin/org/hyperskill/app/freemium/injection/FreemiumDataComponentImpl.kt create mode 100644 shared/src/commonMain/kotlin/org/hyperskill/app/manage_subscription/domain/analytic/ManageSubscriptionClickedManageHyperskillAnalyticEvent.kt create mode 100644 shared/src/commonMain/kotlin/org/hyperskill/app/manage_subscription/domain/analytic/ManageSubscriptionViewedHyperskillAnalyticEvent.kt create mode 100644 shared/src/commonMain/kotlin/org/hyperskill/app/manage_subscription/domain/analytic/RenewSubscriptionClickedManageHyperskillAnalyticEvent.kt create mode 100644 shared/src/commonMain/kotlin/org/hyperskill/app/manage_subscription/injection/ManageSubscriptionComponent.kt create mode 100644 shared/src/commonMain/kotlin/org/hyperskill/app/manage_subscription/injection/ManageSubscriptionComponentImpl.kt create mode 100644 shared/src/commonMain/kotlin/org/hyperskill/app/manage_subscription/injection/ManageSubscriptionFeatureBuilder.kt create mode 100644 shared/src/commonMain/kotlin/org/hyperskill/app/manage_subscription/presentation/ManageSubscriptionActionDispatcher.kt create mode 100644 shared/src/commonMain/kotlin/org/hyperskill/app/manage_subscription/presentation/ManageSubscriptionFeature.kt create mode 100644 shared/src/commonMain/kotlin/org/hyperskill/app/manage_subscription/presentation/ManageSubscriptionReducer.kt create mode 100644 shared/src/commonMain/kotlin/org/hyperskill/app/manage_subscription/presentation/ManageSubscriptionStateExtensions.kt create mode 100644 shared/src/commonMain/kotlin/org/hyperskill/app/manage_subscription/view/mapper/ManageSubscriptionViewStateMapper.kt create mode 100644 shared/src/commonMain/kotlin/org/hyperskill/app/paywall/domain/analytic/PaywallAnalyticParams.kt create mode 100644 shared/src/commonMain/kotlin/org/hyperskill/app/paywall/domain/analytic/PaywallClickedBuySubscriptionHyperskillAnalyticEvent.kt create mode 100644 shared/src/commonMain/kotlin/org/hyperskill/app/paywall/domain/analytic/PaywallClickedContinueWithLimitsHyperskillAnalyticEvent.kt create mode 100644 shared/src/commonMain/kotlin/org/hyperskill/app/paywall/domain/analytic/PaywallClickedRetryContentLoadingHyperskillAnalyticEvent.kt create mode 100644 shared/src/commonMain/kotlin/org/hyperskill/app/paywall/domain/analytic/PaywallClickedTermsOfServiceAndPrivacyPolicyHyperskillAnalyticEvent.kt create mode 100644 shared/src/commonMain/kotlin/org/hyperskill/app/paywall/domain/analytic/PaywallViewedHyperskillAnalyticEvent.kt create mode 100644 shared/src/commonMain/kotlin/org/hyperskill/app/paywall/domain/model/PaywallTransitionSource.kt create mode 100644 shared/src/commonMain/kotlin/org/hyperskill/app/paywall/injection/PaywallComponent.kt create mode 100644 shared/src/commonMain/kotlin/org/hyperskill/app/paywall/injection/PaywallComponentImpl.kt create mode 100644 shared/src/commonMain/kotlin/org/hyperskill/app/paywall/injection/PaywallFeatureBuilder.kt create mode 100644 shared/src/commonMain/kotlin/org/hyperskill/app/paywall/presentation/PaywallActionDispatcher.kt create mode 100644 shared/src/commonMain/kotlin/org/hyperskill/app/paywall/presentation/PaywallFeature.kt create mode 100644 shared/src/commonMain/kotlin/org/hyperskill/app/paywall/presentation/PaywallReducer.kt create mode 100644 shared/src/commonMain/kotlin/org/hyperskill/app/paywall/view/PaywallViewStateMapper.kt create mode 100644 shared/src/commonMain/kotlin/org/hyperskill/app/profile_settings/view/ProfileSettingsViewStateMapper.kt create mode 100644 shared/src/commonMain/kotlin/org/hyperskill/app/purchases/domain/interactor/PurchaseInteractor.kt create mode 100644 shared/src/commonMain/kotlin/org/hyperskill/app/purchases/domain/model/PlatformPurchaseParams.kt create mode 100644 shared/src/commonMain/kotlin/org/hyperskill/app/purchases/domain/model/PurchaseManager.kt create mode 100644 shared/src/commonMain/kotlin/org/hyperskill/app/purchases/domain/model/PurchaseResult.kt create mode 100644 shared/src/commonMain/kotlin/org/hyperskill/app/purchases/injection/PurchaseComponent.kt create mode 100644 shared/src/commonMain/kotlin/org/hyperskill/app/purchases/injection/PurchaseComponentImpl.kt create mode 100644 shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz/domain/analytic/ProblemsLimitReachedModalClickedUnlockUnlimitedProblemsHSAnalyticEvent.kt create mode 100644 shared/src/commonMain/kotlin/org/hyperskill/app/subscriptions/cache/CurrentSubscriptionStateHolderImpl.kt create mode 100644 shared/src/commonMain/kotlin/org/hyperskill/app/subscriptions/cache/SubscriptionCacheKeys.kt create mode 100644 shared/src/commonMain/kotlin/org/hyperskill/app/subscriptions/data/repository/SubscriptionsRepositoryImpl.kt create mode 100644 shared/src/commonMain/kotlin/org/hyperskill/app/subscriptions/data/source/CurrentSubscriptionStateHolder.kt create mode 100644 shared/src/commonMain/kotlin/org/hyperskill/app/subscriptions/domain/interactor/SubscriptionsInteractor.kt create mode 100644 shared/src/commonMain/kotlin/org/hyperskill/app/subscriptions/domain/model/SubscriptionStatus.kt create mode 100644 shared/src/commonMain/kotlin/org/hyperskill/app/subscriptions/domain/repository/SubscriptionsRepository.kt create mode 100644 shared/src/commonMain/kotlin/org/hyperskill/app/subscriptions/injection/SubscriptionsDataComponent.kt create mode 100644 shared/src/commonMain/kotlin/org/hyperskill/app/subscriptions/injection/SubscriptionsDataComponentImpl.kt create mode 100644 shared/src/commonTest/kotlin/org/hyperskill/subscriptions/SubscriptionSerializationTest.kt create mode 100644 shared/src/iosMain/kotlin/org/hyperskill/app/purchase/domain/model/IOSPurchaseManager.kt create mode 100644 shared/src/iosMain/kotlin/org/hyperskill/app/purchase/domain/model/IOSPurchaseParams.kt diff --git a/.gitattributes b/.gitattributes index 6e2360ef21..86ff548d59 100644 --- a/.gitattributes +++ b/.gitattributes @@ -9,6 +9,7 @@ androidHyperskillApp/keys/debug.properties filter=git-crypt diff=git-crypt androidHyperskillApp/keys/release.properties filter=git-crypt diff=git-crypt androidHyperskillApp/fastlane/Appfile filter=git-crypt diff=git-crypt androidHyperskillApp/google-services.json filter=git-crypt diff=git-crypt +shared/src/androidMain/keys/revenuecat.properties filter=git-crypt diff=git-crypt buildsystem/certs/** filter=git-crypt diff=git-crypt shared/keys/** filter=git-crypt diff=git-crypt sentry.properties filter=git-crypt diff=git-crypt diff --git a/androidHyperskillApp/src/main/AndroidManifest.xml b/androidHyperskillApp/src/main/AndroidManifest.xml index 3231dc6630..f05819b206 100644 --- a/androidHyperskillApp/src/main/AndroidManifest.xml +++ b/androidHyperskillApp/src/main/AndroidManifest.xml @@ -1,6 +1,5 @@ - + diff --git a/androidHyperskillApp/src/main/java/org/hyperskill/app/android/HyperskillApp.kt b/androidHyperskillApp/src/main/java/org/hyperskill/app/android/HyperskillApp.kt index 0d9114e02f..80ccb87552 100644 --- a/androidHyperskillApp/src/main/java/org/hyperskill/app/android/HyperskillApp.kt +++ b/androidHyperskillApp/src/main/java/org/hyperskill/app/android/HyperskillApp.kt @@ -55,14 +55,14 @@ class HyperskillApp : Application(), ImageLoaderFactory { ) setNightMode(appGraph) - initSentry() + initSentry(appGraph) initChannels() } override fun newImageLoader(): ImageLoader = graph().imageLoadingComponent.imageLoader - private fun initSentry() { + private fun initSentry(appGraph: AppGraph) { appGraph.sentryComponent.sentryInteractor.setup() } diff --git a/androidHyperskillApp/src/main/java/org/hyperskill/app/android/core/extensions/ContextExtensions.kt b/androidHyperskillApp/src/main/java/org/hyperskill/app/android/core/extensions/ContextExtensions.kt index 796234eee8..c1f6f0edce 100644 --- a/androidHyperskillApp/src/main/java/org/hyperskill/app/android/core/extensions/ContextExtensions.kt +++ b/androidHyperskillApp/src/main/java/org/hyperskill/app/android/core/extensions/ContextExtensions.kt @@ -1,7 +1,9 @@ package org.hyperskill.app.android.core.extensions +import android.app.Activity import android.content.ActivityNotFoundException import android.content.Context +import android.content.ContextWrapper import android.content.Intent import android.net.Uri import android.widget.Toast @@ -22,4 +24,14 @@ fun Context.openUrl(uri: Uri) { fun Context.openUrl(url: String) { openUrl(Uri.parse(url)) -} \ No newline at end of file +} + +/** + * Find the closest Activity in a given Context. + */ +tailrec fun Context.findActivity(): Activity = + when (this) { + is Activity -> this + is ContextWrapper -> baseContext.findActivity() + else -> throw IllegalStateException("Can't find Activity in a given context") + } \ No newline at end of file diff --git a/androidHyperskillApp/src/main/java/org/hyperskill/app/android/core/extensions/ThemeExtension.kt b/androidHyperskillApp/src/main/java/org/hyperskill/app/android/core/extensions/ThemeExtension.kt index 40ce6f5060..9f1df25be7 100644 --- a/androidHyperskillApp/src/main/java/org/hyperskill/app/android/core/extensions/ThemeExtension.kt +++ b/androidHyperskillApp/src/main/java/org/hyperskill/app/android/core/extensions/ThemeExtension.kt @@ -1,14 +1,14 @@ package org.hyperskill.app.android.core.extensions -import org.hyperskill.app.android.HyperskillApp +import android.content.Context import org.hyperskill.app.profile_settings.domain.model.Theme -val Theme.representation - get() = when (this) { +fun Theme.getStringRepresentation(context: Context): String = + when (this) { Theme.DARK -> - HyperskillApp.graph().context.resources.getString(org.hyperskill.app.R.string.settings_theme_dark) + context.resources.getString(org.hyperskill.app.R.string.settings_theme_dark) Theme.LIGHT -> - HyperskillApp.graph().context.resources.getString(org.hyperskill.app.R.string.settings_theme_light) + context.resources.getString(org.hyperskill.app.R.string.settings_theme_light) Theme.SYSTEM -> - HyperskillApp.graph().context.resources.getString(org.hyperskill.app.R.string.settings_theme_system) + context.resources.getString(org.hyperskill.app.R.string.settings_theme_system) } \ No newline at end of file diff --git a/androidHyperskillApp/src/main/java/org/hyperskill/app/android/core/extensions/compose/PaddingValuesExtensions.kt b/androidHyperskillApp/src/main/java/org/hyperskill/app/android/core/extensions/compose/PaddingValuesExtensions.kt new file mode 100644 index 0000000000..06391f8a80 --- /dev/null +++ b/androidHyperskillApp/src/main/java/org/hyperskill/app/android/core/extensions/compose/PaddingValuesExtensions.kt @@ -0,0 +1,20 @@ +package org.hyperskill.app.android.core.extensions.compose + +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.calculateEndPadding +import androidx.compose.foundation.layout.calculateStartPadding +import androidx.compose.runtime.Composable +import androidx.compose.ui.platform.LocalLayoutDirection + +@Composable +operator fun PaddingValues.plus(other: PaddingValues): PaddingValues { + val layoutDirection = LocalLayoutDirection.current + return PaddingValues( + start = this.calculateStartPadding(layoutDirection) + + other.calculateStartPadding(layoutDirection), + top = this.calculateTopPadding() + other.calculateTopPadding(), + end = this.calculateEndPadding(layoutDirection) + + other.calculateEndPadding(layoutDirection), + bottom = this.calculateBottomPadding() + other.calculateBottomPadding(), + ) +} \ No newline at end of file diff --git a/androidHyperskillApp/src/main/java/org/hyperskill/app/android/core/injection/AndroidAppComponentImpl.kt b/androidHyperskillApp/src/main/java/org/hyperskill/app/android/core/injection/AndroidAppComponentImpl.kt index 1f48ac1048..52d3ba9d39 100644 --- a/androidHyperskillApp/src/main/java/org/hyperskill/app/android/core/injection/AndroidAppComponentImpl.kt +++ b/androidHyperskillApp/src/main/java/org/hyperskill/app/android/core/injection/AndroidAppComponentImpl.kt @@ -1,7 +1,6 @@ package org.hyperskill.app.android.core.injection import android.app.Application -import android.content.Context import org.hyperskill.app.analytic.domain.model.AnalyticEngine import org.hyperskill.app.analytic.injection.AnalyticComponent import org.hyperskill.app.analytic.injection.AnalyticComponentImpl @@ -29,20 +28,18 @@ import org.hyperskill.app.sentry.injection.SentryComponent import org.hyperskill.app.sentry.injection.SentryComponentImpl class AndroidAppComponentImpl( - private val application: Application, + override val application: Application, userAgentInfo: UserAgentInfo, buildVariant: BuildVariant, analyticEngines: List = emptyList() ) : AndroidAppComponent, CommonAndroidAppGraphImpl() { - override val context: Context - get() = application override val commonComponent: CommonComponent by lazy { - CommonComponentImpl(application, buildVariant, userAgentInfo) + CommonComponentImpl(this.application, buildVariant, userAgentInfo) } override val imageLoadingComponent: ImageLoadingComponent by lazy { - ImageLoadingComponentImpl(context) + ImageLoadingComponentImpl(this.application) } override val navigationComponent: NavigationComponent by lazy { @@ -61,7 +58,7 @@ class AndroidAppComponentImpl( } override val platformLocalNotificationComponent: PlatformLocalNotificationComponent by lazy { - PlatformLocalNotificationComponentImpl(application, this) + PlatformLocalNotificationComponentImpl(this.application, this) } override fun buildPlatformPushNotificationsComponent(): AndroidPlatformPushNotificationComponent = @@ -80,11 +77,11 @@ class AndroidAppComponentImpl( * Latex component */ override fun buildPlatformLatexComponent(): PlatformLatexComponent = - PlatformLatexComponentImpl(application, networkComponent.endpointConfigInfo) + PlatformLatexComponentImpl(this.application, networkComponent.endpointConfigInfo) /** * CodeEditor component */ override fun buildPlatformCodeEditorComponent(): PlatformCodeEditorComponent = - PlatformCodeEditorComponentImpl(application) + PlatformCodeEditorComponentImpl(this.application) } \ No newline at end of file diff --git a/androidHyperskillApp/src/main/java/org/hyperskill/app/android/core/view/ui/widget/compose/HyperskillProgressIndicator.kt b/androidHyperskillApp/src/main/java/org/hyperskill/app/android/core/view/ui/widget/compose/HyperskillProgressIndicator.kt new file mode 100644 index 0000000000..5fcc32ec89 --- /dev/null +++ b/androidHyperskillApp/src/main/java/org/hyperskill/app/android/core/view/ui/widget/compose/HyperskillProgressIndicator.kt @@ -0,0 +1,16 @@ +package org.hyperskill.app.android.core.view.ui.widget.compose + +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.viewinterop.AndroidView +import me.zhanghai.android.materialprogressbar.MaterialProgressBar + +@Composable +fun HyperskillProgressBar(modifier: Modifier = Modifier) { + AndroidView( + factory = { context -> + MaterialProgressBar(context) + }, + modifier = modifier + ) +} \ No newline at end of file diff --git a/androidHyperskillApp/src/main/java/org/hyperskill/app/android/core/view/ui/widget/compose/HyperskillTopAppBar.kt b/androidHyperskillApp/src/main/java/org/hyperskill/app/android/core/view/ui/widget/compose/HyperskillTopAppBar.kt index 7b5a5dcc27..6a11d34f4b 100644 --- a/androidHyperskillApp/src/main/java/org/hyperskill/app/android/core/view/ui/widget/compose/HyperskillTopAppBar.kt +++ b/androidHyperskillApp/src/main/java/org/hyperskill/app/android/core/view/ui/widget/compose/HyperskillTopAppBar.kt @@ -9,6 +9,7 @@ import androidx.compose.material.Text import androidx.compose.material.TopAppBar import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.colorResource import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource @@ -20,6 +21,7 @@ fun HyperskillTopAppBar( title: String, modifier: Modifier = Modifier, onNavigationIconClick: (() -> Unit)? = null, + backgroundColor: Color = MaterialTheme.colors.background, actions: @Composable RowScope.() -> Unit = {} ) { TopAppBar( @@ -40,7 +42,7 @@ fun HyperskillTopAppBar( modifier = TopAppBarTitleModifier ) }, - backgroundColor = MaterialTheme.colors.background, + backgroundColor = backgroundColor, actions = actions, modifier = modifier ) diff --git a/androidHyperskillApp/src/main/java/org/hyperskill/app/android/core/view/ui/widget/compose/OnComposableShownFirstTime.kt b/androidHyperskillApp/src/main/java/org/hyperskill/app/android/core/view/ui/widget/compose/OnComposableShownFirstTime.kt new file mode 100644 index 0000000000..6096c2abc3 --- /dev/null +++ b/androidHyperskillApp/src/main/java/org/hyperskill/app/android/core/view/ui/widget/compose/OnComposableShownFirstTime.kt @@ -0,0 +1,17 @@ +package org.hyperskill.app.android.core.view.ui.widget.compose + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.DisposableEffect + +@Composable +fun OnComposableShownFirstTime( + key: Any?, + block: () -> Unit +) { + DisposableEffect(key) { + block() + onDispose { + // no op + } + } +} \ No newline at end of file diff --git a/androidHyperskillApp/src/main/java/org/hyperskill/app/android/home/view/ui/fragment/HomeFragment.kt b/androidHyperskillApp/src/main/java/org/hyperskill/app/android/home/view/ui/fragment/HomeFragment.kt index e462743e3c..bc0102176e 100644 --- a/androidHyperskillApp/src/main/java/org/hyperskill/app/android/home/view/ui/fragment/HomeFragment.kt +++ b/androidHyperskillApp/src/main/java/org/hyperskill/app/android/home/view/ui/fragment/HomeFragment.kt @@ -226,8 +226,8 @@ class HomeFragment : val homeState = state.homeState if (homeState is HomeFeature.HomeState.Content) { - renderProblemOfDay(viewBinding, homeState.problemOfDayState, homeState.isFreemiumEnabled) - renderTopicsRepetition(homeState.repetitionsState, homeState.isFreemiumEnabled) + renderProblemOfDay(viewBinding, homeState.problemOfDayState, homeState.areProblemsLimited) + renderTopicsRepetition(homeState.repetitionsState, homeState.areProblemsLimited) } renderChallengeCard(state.challengeWidgetViewState) @@ -281,19 +281,19 @@ class HomeFragment : private fun renderProblemOfDay( viewBinding: FragmentHomeBinding, state: HomeFeature.ProblemOfDayState, - isFreemiumEnabled: Boolean + areProblemsLimited: Boolean ) { problemOfDayCardFormDelegate.render( dateFormatter = dateFormatter, binding = viewBinding.homeScreenProblemOfDayCard, state = state, - isFreemiumEnabled = isFreemiumEnabled + areProblemsLimited = areProblemsLimited ) } private fun renderTopicsRepetition( repetitionsState: HomeFeature.RepetitionsState, - isFreemiumEnabled: Boolean + areProblemsLimited: Boolean ) { viewBinding.homeScreenTopicsRepetitionCard.root.isVisible = repetitionsState is HomeFeature.RepetitionsState.Available @@ -302,7 +302,7 @@ class HomeFragment : context = requireContext(), binding = viewBinding.homeScreenTopicsRepetitionCard, recommendedRepetitionsCount = repetitionsState.recommendedRepetitionsCount, - isFreemiumEnabled = isFreemiumEnabled + areProblemsLimited = areProblemsLimited ) } } diff --git a/androidHyperskillApp/src/main/java/org/hyperskill/app/android/main/view/ui/activity/MainActivity.kt b/androidHyperskillApp/src/main/java/org/hyperskill/app/android/main/view/ui/activity/MainActivity.kt index c923c9cd9f..3545de9089 100644 --- a/androidHyperskillApp/src/main/java/org/hyperskill/app/android/main/view/ui/activity/MainActivity.kt +++ b/androidHyperskillApp/src/main/java/org/hyperskill/app/android/main/view/ui/activity/MainActivity.kt @@ -4,13 +4,16 @@ import android.annotation.SuppressLint import android.content.Intent import android.content.pm.PackageManager import android.os.Bundle +import android.util.Log import androidx.activity.viewModels import androidx.appcompat.app.AppCompatActivity import androidx.core.app.NotificationManagerCompat import androidx.core.content.ContextCompat import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen import androidx.core.view.isVisible +import androidx.lifecycle.DefaultLifecycleObserver import androidx.lifecycle.Lifecycle +import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.flowWithLifecycle @@ -40,6 +43,8 @@ import org.hyperskill.app.android.notification.model.DefaultNotificationClickedD import org.hyperskill.app.android.notification.model.PushNotificationClickedData import org.hyperskill.app.android.notification_onboarding.fragment.NotificationsOnboardingFragment import org.hyperskill.app.android.notification_onboarding.navigation.NotificationsOnboardingScreen +import org.hyperskill.app.android.paywall.fragment.PaywallFragment +import org.hyperskill.app.android.paywall.navigation.PaywallScreen import org.hyperskill.app.android.step.view.screen.StepScreen import org.hyperskill.app.android.streak_recovery.view.delegate.StreakRecoveryViewActionDelegate import org.hyperskill.app.android.track_selection.list.navigation.TrackSelectionListScreen @@ -93,6 +98,13 @@ class MainActivity : ) } + private val onForegroundObserver = + object : DefaultLifecycleObserver { + override fun onResume(owner: LifecycleOwner) { + mainViewModel.onNewMessage(AppFeature.Message.AppBecomesActive) + } + } + @SuppressLint("InlinedApi") override fun onCreate(savedInstanceState: Bundle?) { val splashScreen = installSplashScreen() @@ -111,6 +123,7 @@ class MainActivity : viewBinding = ActivityMainBinding.inflate(layoutInflater) setContentView(viewBinding.root) initViewStateDelegate() + lifecycle.addObserver(onForegroundObserver) viewBinding.mainError.tryAgain.setOnClickListener { mainViewModel.onNewMessage( @@ -126,6 +139,7 @@ class MainActivity : observeAuthFlowSuccess() observeNotificationsOnboardingFlowFinished() observeFirstProblemOnboardingFlowFinished() + observePaywallCompleted() mainViewModel.logScreenOrientation(screenOrientation = resources.configuration.screenOrientation) logNotificationAvailability() @@ -157,47 +171,59 @@ class MainActivity : @SuppressLint("InlinedApi") private fun observeAuthFlowSuccess() { - lifecycleScope.launch { - router - .observeResult(AuthFragment.AUTH_SUCCESS) - .flowWithLifecycle(lifecycle, Lifecycle.State.STARTED) - .collectLatest { - val profile = (it as? Profile) ?: return@collectLatest - mainViewModel.onNewMessage( - AppFeature.Message.UserAuthorized( - profile = profile, - isNotificationPermissionGranted = ContextCompat.checkSelfPermission( - this@MainActivity, - android.Manifest.permission.POST_NOTIFICATIONS - ) == PackageManager.PERMISSION_GRANTED - ) - ) - } + observeResult(AuthFragment.AUTH_SUCCESS) { profile -> + mainViewModel.onNewMessage( + AppFeature.Message.UserAuthorized( + profile = profile, + isNotificationPermissionGranted = ContextCompat.checkSelfPermission( + this@MainActivity, + android.Manifest.permission.POST_NOTIFICATIONS + ) == PackageManager.PERMISSION_GRANTED + ) + ) } } private fun observeNotificationsOnboardingFlowFinished() { - lifecycleScope.launch { - router - .observeResult(NotificationsOnboardingFragment.NOTIFICATIONS_ONBOARDING_FINISHED) - .flowWithLifecycle(lifecycle, Lifecycle.State.STARTED) - .collectLatest { - mainViewModel.onNewMessage(WelcomeOnboardingFeature.Message.NotificationOnboardingCompleted) - } + observeResult(NotificationsOnboardingFragment.NOTIFICATIONS_ONBOARDING_FINISHED) { + mainViewModel.onNewMessage(WelcomeOnboardingFeature.Message.NotificationOnboardingCompleted) } } private fun observeFirstProblemOnboardingFlowFinished() { + observeResult(FirstProblemOnboardingFragment.FIRST_PROBLEM_ONBOARDING_FINISHED) { + mainViewModel.onNewMessage( + WelcomeOnboardingFeature.Message.FirstProblemOnboardingCompleted( + firstProblemStepRoute = it.safeCast() + ) + ) + } + } + + private fun observePaywallCompleted() { + observeResult(PaywallFragment.PAYWALL_COMPLETED) { + mainViewModel.onNewMessage( + WelcomeOnboardingFeature.Message.PaywallCompleted + ) + } + } + + private inline fun observeResult( + key: String, + router: Router = this.router, + crossinline onResult: (T) -> Unit + ) { lifecycleScope.launch { router - .observeResult(FirstProblemOnboardingFragment.FIRST_PROBLEM_ONBOARDING_FINISHED) + .observeResult(key) .flowWithLifecycle(lifecycle, Lifecycle.State.STARTED) .collectLatest { - mainViewModel.onNewMessage( - WelcomeOnboardingFeature.Message.FirstProblemOnboardingCompleted( - firstProblemStepRoute = it.safeCast() - ) - ) + val result = it.safeCast() + if (result == null) { + Log.e("MainActivity", "Can't cast result by key=$key.") + } else { + onResult(result) + } } } } @@ -227,7 +253,7 @@ class MainActivity : override fun onAction(action: AppFeature.Action.ViewAction) { when (action) { - is AppFeature.Action.ViewAction.NavigateTo.OnboardingScreen -> + is AppFeature.Action.ViewAction.NavigateTo.WelcomeScreen -> router.newRootScreen(WelcomeScreen) is AppFeature.Action.ViewAction.NavigateTo.AuthScreen -> router.newRootScreen(AuthScreen()) @@ -251,6 +277,8 @@ class MainActivity : ) WelcomeOnboardingFeature.Action.ViewAction.NavigateTo.NotificationOnboardingScreen -> router.newRootScreen(NotificationsOnboardingScreen) + is WelcomeOnboardingFeature.Action.ViewAction.NavigateTo.Paywall -> + router.newRootScreen(PaywallScreen(viewAction.paywallTransitionSource)) WelcomeOnboardingFeature.Action.ViewAction.NavigateTo.UsersQuestionnaireOnboardingScreen -> TODO("ALTAPPS-1145: Implement QuestionnaireOnboardingScreen navigation") } @@ -279,6 +307,16 @@ class MainActivity : } AppFeature.Action.ViewAction.NavigateTo.StudyPlan -> router.newRootScreen(MainScreen(Tabs.STUDY_PLAN)) + is AppFeature.Action.ViewAction.NavigateTo.Paywall -> { + if (supportFragmentManager.findFragmentByTag(PaywallScreen.TAG) == null) { + router.navigateTo(PaywallScreen(action.paywallTransitionSource)) + } + } + is AppFeature.Action.ViewAction.NavigateTo.StudyPlanWithPaywall -> + router.newRootChain( + MainScreen(initialTab = Tabs.STUDY_PLAN), + PaywallScreen(action.paywallTransitionSource) + ) } } diff --git a/androidHyperskillApp/src/main/java/org/hyperskill/app/android/manage_subscription/fragment/ManageSubscriptionFragment.kt b/androidHyperskillApp/src/main/java/org/hyperskill/app/android/manage_subscription/fragment/ManageSubscriptionFragment.kt new file mode 100644 index 0000000000..c562a9e9be --- /dev/null +++ b/androidHyperskillApp/src/main/java/org/hyperskill/app/android/manage_subscription/fragment/ManageSubscriptionFragment.kt @@ -0,0 +1,74 @@ +package org.hyperskill.app.android.manage_subscription.fragment + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.compose.ui.platform.ComposeView +import androidx.compose.ui.platform.ViewCompositionStrategy +import androidx.fragment.app.Fragment +import androidx.fragment.app.viewModels +import androidx.lifecycle.ViewModelProvider +import org.hyperskill.app.android.HyperskillApp +import org.hyperskill.app.android.core.extensions.openUrl +import org.hyperskill.app.android.core.view.ui.navigation.requireRouter +import org.hyperskill.app.android.core.view.ui.widget.compose.HyperskillTheme +import org.hyperskill.app.android.manage_subscription.ui.ManageSubscriptionScreen +import org.hyperskill.app.android.paywall.navigation.PaywallScreen +import org.hyperskill.app.core.view.handleActions +import org.hyperskill.app.manage_subscription.presentation.ManageSubscriptionFeature.Action.ViewAction +import org.hyperskill.app.manage_subscription.presentation.ManageSubscriptionViewModel + +class ManageSubscriptionFragment : Fragment() { + companion object { + fun newInstance(): ManageSubscriptionFragment = + ManageSubscriptionFragment() + } + + private var viewModelFactory: ViewModelProvider.Factory? = null + private val manageSubscriptionViewModel: ManageSubscriptionViewModel by viewModels { + requireNotNull(viewModelFactory) + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + injectComponent() + manageSubscriptionViewModel.handleActions(this, onAction = ::onAction) + } + + private fun injectComponent() { + val platformManageSubscriptionComponent = + HyperskillApp.graph().buildPlatformManageSubscriptionComponent() + viewModelFactory = platformManageSubscriptionComponent.reduxViewModelFactory + } + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View = + ComposeView(requireContext()).apply { + setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnLifecycleDestroyed(viewLifecycleOwner)) + setContent { + HyperskillTheme { + ManageSubscriptionScreen( + viewModel = manageSubscriptionViewModel, + onBackClick = ::onBackClick + ) + } + } + } + + private fun onAction(action: ViewAction) { + when (action) { + is ViewAction.OpenUrl -> + requireContext().openUrl(action.url) + is ViewAction.NavigateTo.Paywall -> + requireRouter().navigateTo(PaywallScreen(action.paywallTransitionSource)) + } + } + + private fun onBackClick() { + requireRouter().exit() + } +} \ No newline at end of file diff --git a/androidHyperskillApp/src/main/java/org/hyperskill/app/android/manage_subscription/navigation/ManageSubscriptionScreen.kt b/androidHyperskillApp/src/main/java/org/hyperskill/app/android/manage_subscription/navigation/ManageSubscriptionScreen.kt new file mode 100644 index 0000000000..e01551233c --- /dev/null +++ b/androidHyperskillApp/src/main/java/org/hyperskill/app/android/manage_subscription/navigation/ManageSubscriptionScreen.kt @@ -0,0 +1,11 @@ +package org.hyperskill.app.android.manage_subscription.navigation + +import androidx.fragment.app.Fragment +import androidx.fragment.app.FragmentFactory +import com.github.terrakok.cicerone.androidx.FragmentScreen +import org.hyperskill.app.android.manage_subscription.fragment.ManageSubscriptionFragment + +object ManageSubscriptionScreen : FragmentScreen { + override fun createFragment(factory: FragmentFactory): Fragment = + ManageSubscriptionFragment.newInstance() +} \ No newline at end of file diff --git a/androidHyperskillApp/src/main/java/org/hyperskill/app/android/manage_subscription/ui/ManageSubscriptionContent.kt b/androidHyperskillApp/src/main/java/org/hyperskill/app/android/manage_subscription/ui/ManageSubscriptionContent.kt new file mode 100644 index 0000000000..a9e2180389 --- /dev/null +++ b/androidHyperskillApp/src/main/java/org/hyperskill/app/android/manage_subscription/ui/ManageSubscriptionContent.kt @@ -0,0 +1,149 @@ +package org.hyperskill.app.android.manage_subscription.ui + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.verticalScroll +import androidx.compose.material.MaterialTheme +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.res.colorResource +import androidx.compose.ui.res.dimensionResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import org.hyperskill.app.R +import org.hyperskill.app.android.core.extensions.compose.plus +import org.hyperskill.app.android.core.view.ui.widget.compose.HyperskillButton +import org.hyperskill.app.android.core.view.ui.widget.compose.HyperskillTheme +import org.hyperskill.app.android.paywall.ui.SubscriptionDetails +import org.hyperskill.app.manage_subscription.presentation.ManageSubscriptionFeature.ViewState + +@Composable +fun ManageSubscriptionContent( + state: ViewState.Content, + onActionButtonClick: () -> Unit, + modifier: Modifier = Modifier, + padding: PaddingValues = PaddingValues() +) { + Column( + modifier = modifier + .fillMaxSize() + .verticalScroll(rememberScrollState()) + .background(colorResource(id = R.color.layer_1)) + .padding(ManageSubscriptionDefaults.ContentPadding + padding) + ) { + Column( + verticalArrangement = Arrangement.spacedBy(24.dp), + modifier = Modifier.weight(1f) + ) { + SubscriptionHeader(validUntilFormatted = state.validUntilFormatted) + PlanDetails() + MobileOnlyWarning() + Spacer(modifier = Modifier.height(24.dp)) + } + + val buttonText = state.buttonText + if (buttonText != null) { + HyperskillButton( + onClick = onActionButtonClick, + modifier = Modifier.fillMaxWidth() + ) { + Text(text = buttonText) + } + } + } +} + +@Composable +private fun SubscriptionHeader( + validUntilFormatted: String?, + modifier: Modifier = Modifier +) { + Column( + verticalArrangement = Arrangement.spacedBy(8.dp), + modifier = modifier + ) { + Text( + text = stringResource(id = R.string.manage_subscription_plan_title), + style = MaterialTheme.typography.body2 + ) + Text( + text = stringResource(id = R.string.manage_subscription_mobile_only), + style = MaterialTheme.typography.h5 + ) + if (validUntilFormatted != null) { + Text( + text = validUntilFormatted, + style = MaterialTheme.typography.body2 + ) + } + } +} + +@Composable +private fun PlanDetails(modifier: Modifier = Modifier) { + Column(modifier = modifier) { + Text( + text = stringResource(id = R.string.manage_subscription_plan_details_title), + style = MaterialTheme.typography.h6, + fontSize = 16.sp + ) + Spacer(modifier = Modifier.height(8.dp)) + SubscriptionDetails() + } +} + +@Composable +private fun MobileOnlyWarning( + modifier: Modifier = Modifier +) { + Text( + text = stringResource(id = R.string.manage_subscription_mobile_only_warning), + color = colorResource(id = R.color.color_primary), + modifier = modifier + .clip(RoundedCornerShape(dimensionResource(id = org.hyperskill.app.android.R.dimen.corner_radius))) + .background(colorResource(id = R.color.color_overlay_blue_alpha_12)) + .padding(horizontal = 16.dp, vertical = 14.dp) + ) +} + +@Preview +@Composable +private fun SubscriptionHeaderPreview() { + HyperskillTheme { + SubscriptionHeader( + validUntilFormatted = ManageSubscriptionPreviewDefaults.VALID_UNTIL_FORMATTED + ) + } +} + +@Preview +@Composable +fun MobileOnlyWarningPreview() { + HyperskillTheme { + MobileOnlyWarning() + } +} + +@Preview +@Composable +private fun ManageSubscriptionContentPreview() { + HyperskillTheme { + ManageSubscriptionContent( + state = ManageSubscriptionPreviewDefaults.ActiveSubscriptionContent, + onActionButtonClick = {} + ) + } +} \ No newline at end of file diff --git a/androidHyperskillApp/src/main/java/org/hyperskill/app/android/manage_subscription/ui/ManageSubscriptionDefaults.kt b/androidHyperskillApp/src/main/java/org/hyperskill/app/android/manage_subscription/ui/ManageSubscriptionDefaults.kt new file mode 100644 index 0000000000..9107ebed9e --- /dev/null +++ b/androidHyperskillApp/src/main/java/org/hyperskill/app/android/manage_subscription/ui/ManageSubscriptionDefaults.kt @@ -0,0 +1,13 @@ +package org.hyperskill.app.android.manage_subscription.ui + +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.ui.unit.dp + +object ManageSubscriptionDefaults { + val ContentPadding = PaddingValues( + start = 24.dp, + end = 20.dp, + top = 24.dp, + bottom = 32.dp + ) +} \ No newline at end of file diff --git a/androidHyperskillApp/src/main/java/org/hyperskill/app/android/manage_subscription/ui/ManageSubscriptionPreviewDefaults.kt b/androidHyperskillApp/src/main/java/org/hyperskill/app/android/manage_subscription/ui/ManageSubscriptionPreviewDefaults.kt new file mode 100644 index 0000000000..fd8bb77d82 --- /dev/null +++ b/androidHyperskillApp/src/main/java/org/hyperskill/app/android/manage_subscription/ui/ManageSubscriptionPreviewDefaults.kt @@ -0,0 +1,19 @@ +package org.hyperskill.app.android.manage_subscription.ui + +import org.hyperskill.app.manage_subscription.presentation.ManageSubscriptionFeature + +object ManageSubscriptionPreviewDefaults { + const val VALID_UNTIL_FORMATTED = "Valid until January 27, 2024, 02:00" + + val ActiveSubscriptionContent: ManageSubscriptionFeature.ViewState.Content + get() = ManageSubscriptionFeature.ViewState.Content( + validUntilFormatted = VALID_UNTIL_FORMATTED, + buttonText = "Manage subscription" + ) + + val ExpiredSubscriptionContent: ManageSubscriptionFeature.ViewState.Content + get() = ManageSubscriptionFeature.ViewState.Content( + validUntilFormatted = VALID_UNTIL_FORMATTED, + buttonText = "Renew subscription" + ) +} \ No newline at end of file diff --git a/androidHyperskillApp/src/main/java/org/hyperskill/app/android/manage_subscription/ui/ManageSubscriptionScreen.kt b/androidHyperskillApp/src/main/java/org/hyperskill/app/android/manage_subscription/ui/ManageSubscriptionScreen.kt new file mode 100644 index 0000000000..6e1f184182 --- /dev/null +++ b/androidHyperskillApp/src/main/java/org/hyperskill/app/android/manage_subscription/ui/ManageSubscriptionScreen.kt @@ -0,0 +1,111 @@ +package org.hyperskill.app.android.manage_subscription.ui + +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.material.Scaffold +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.colorResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.tooling.preview.PreviewParameter +import androidx.compose.ui.tooling.preview.PreviewParameterProvider +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import org.hyperskill.app.R +import org.hyperskill.app.android.core.view.ui.widget.compose.HyperskillProgressBar +import org.hyperskill.app.android.core.view.ui.widget.compose.HyperskillTheme +import org.hyperskill.app.android.core.view.ui.widget.compose.HyperskillTopAppBar +import org.hyperskill.app.android.core.view.ui.widget.compose.OnComposableShownFirstTime +import org.hyperskill.app.android.core.view.ui.widget.compose.ScreenDataLoadingError +import org.hyperskill.app.manage_subscription.presentation.ManageSubscriptionFeature.Message +import org.hyperskill.app.manage_subscription.presentation.ManageSubscriptionFeature.ViewState +import org.hyperskill.app.manage_subscription.presentation.ManageSubscriptionViewModel + +@Composable +fun ManageSubscriptionScreen( + viewModel: ManageSubscriptionViewModel, + onBackClick: () -> Unit +) { + OnComposableShownFirstTime(viewModel) { + viewModel.onNewMessage(Message.ViewedEventMessage) + } + val viewState by viewModel.state.collectAsStateWithLifecycle() + ManageSubscriptionScreen( + viewState = viewState, + onBackClick = onBackClick, + onRetryLoadingClick = viewModel::onRetryClick, + onActionButtonClick = viewModel::onActionButtonClick + ) +} + +@Composable +fun ManageSubscriptionScreen( + viewState: ViewState, + onBackClick: () -> Unit, + onRetryLoadingClick: () -> Unit, + onActionButtonClick: () -> Unit +) { + Scaffold( + topBar = { + HyperskillTopAppBar( + title = stringResource(id = R.string.manage_subscription_screen_title), + onNavigationIconClick = onBackClick, + backgroundColor = colorResource(id = R.color.layer_1) + ) + } + ) { padding -> + when (viewState) { + ViewState.Idle -> { + // no op + } + ViewState.Loading -> { + Box(modifier = Modifier.fillMaxSize()) { + HyperskillProgressBar( + modifier = Modifier.align(Alignment.Center) + ) + } + } + ViewState.Error -> { + ScreenDataLoadingError( + errorMessage = stringResource(id = R.string.paywall_placeholder_error_description) + ) { + onRetryLoadingClick() + } + } + is ViewState.Content -> { + ManageSubscriptionContent( + state = viewState, + onActionButtonClick = onActionButtonClick, + padding = padding + ) + } + } + } +} + +private class ManageSubscriptionPreviewProvider : PreviewParameterProvider { + override val values: Sequence + get() = sequenceOf( + ManageSubscriptionPreviewDefaults.ActiveSubscriptionContent, + ManageSubscriptionPreviewDefaults.ExpiredSubscriptionContent, + ViewState.Loading, + ViewState.Error + ) +} + +@Preview(showBackground = true) +@Composable +fun ManageSubscriptionScreenPreview( + @PreviewParameter(ManageSubscriptionPreviewProvider::class) viewState: ViewState +) { + HyperskillTheme { + ManageSubscriptionScreen( + viewState = viewState, + onBackClick = {}, + onActionButtonClick = {}, + onRetryLoadingClick = {} + ) + } +} \ No newline at end of file diff --git a/androidHyperskillApp/src/main/java/org/hyperskill/app/android/paywall/fragment/PaywallFragment.kt b/androidHyperskillApp/src/main/java/org/hyperskill/app/android/paywall/fragment/PaywallFragment.kt new file mode 100644 index 0000000000..4e43f5a1bf --- /dev/null +++ b/androidHyperskillApp/src/main/java/org/hyperskill/app/android/paywall/fragment/PaywallFragment.kt @@ -0,0 +1,106 @@ +package org.hyperskill.app.android.paywall.fragment + +import android.net.Uri +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.Toast +import androidx.browser.customtabs.CustomTabsIntent +import androidx.compose.ui.platform.ComposeView +import androidx.compose.ui.platform.ViewCompositionStrategy +import androidx.fragment.app.Fragment +import androidx.fragment.app.viewModels +import androidx.lifecycle.ViewModelProvider +import org.hyperskill.app.android.HyperskillApp +import org.hyperskill.app.android.core.extensions.setHyperskillColors +import org.hyperskill.app.android.core.view.ui.navigation.requireAppRouter +import org.hyperskill.app.android.core.view.ui.navigation.requireRouter +import org.hyperskill.app.android.core.view.ui.widget.compose.HyperskillTheme +import org.hyperskill.app.android.main.view.ui.navigation.MainScreen +import org.hyperskill.app.android.main.view.ui.navigation.Tabs +import org.hyperskill.app.android.paywall.ui.PaywallScreen +import org.hyperskill.app.core.view.handleActions +import org.hyperskill.app.paywall.domain.model.PaywallTransitionSource +import org.hyperskill.app.paywall.presentation.PaywallFeature.Action.ViewAction +import org.hyperskill.app.paywall.presentation.PaywallViewModel +import ru.nobird.android.view.base.ui.extension.argument + +class PaywallFragment : Fragment() { + companion object { + const val PAYWALL_COMPLETED = "PAYWALL_COMPLETED" + + fun newInstance(paywallTransitionSource: PaywallTransitionSource): PaywallFragment = + PaywallFragment().apply { + this.paywallTransitionSource = paywallTransitionSource + } + } + + private var paywallTransitionSource: PaywallTransitionSource by argument() + + private var viewModelFactory: ViewModelProvider.Factory? = null + private val paywallViewModel: PaywallViewModel by viewModels { + requireNotNull(viewModelFactory) + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + injectComponent() + paywallViewModel.handleActions(this, onAction = ::onAction) + } + + private fun injectComponent() { + val platformPaywallComponent = + HyperskillApp.graph().buildPlatformPaywallComponent(paywallTransitionSource) + viewModelFactory = platformPaywallComponent.reduxViewModelFactory + } + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View = + ComposeView(requireContext()).apply { + setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnLifecycleDestroyed(viewLifecycleOwner)) + setContent { + HyperskillTheme { + PaywallScreen( + viewModel = paywallViewModel, + onBackClick = ::onBackClick + ) + } + } + } + + private fun onAction(action: ViewAction) { + when (action) { + ViewAction.CompletePaywall -> { + requireAppRouter().sendResult(PAYWALL_COMPLETED, Any()) + } + ViewAction.StudyPlan -> { + requireRouter().backTo(MainScreen(initialTab = Tabs.STUDY_PLAN)) + } + is ViewAction.ShowMessage -> { + Toast.makeText( + requireContext(), + getString(action.messageKind.stringRes.resourceId), + Toast.LENGTH_SHORT + ).show() + } + ViewAction.NavigateTo.BackToProfileSettings -> + requireRouter().backTo(MainScreen(Tabs.PROFILE)) + ViewAction.ClosePaywall -> + requireRouter().exit() + is ViewAction.OpenUrl -> { + val intent = CustomTabsIntent.Builder() + .setHyperskillColors(requireContext()) + .build() + intent.launchUrl(requireContext(), Uri.parse(action.url)) + } + } + } + + private fun onBackClick() { + requireRouter().exit() + } +} \ No newline at end of file diff --git a/androidHyperskillApp/src/main/java/org/hyperskill/app/android/paywall/navigation/PaywallScreen.kt b/androidHyperskillApp/src/main/java/org/hyperskill/app/android/paywall/navigation/PaywallScreen.kt new file mode 100644 index 0000000000..86e8864791 --- /dev/null +++ b/androidHyperskillApp/src/main/java/org/hyperskill/app/android/paywall/navigation/PaywallScreen.kt @@ -0,0 +1,21 @@ +package org.hyperskill.app.android.paywall.navigation + +import androidx.fragment.app.Fragment +import androidx.fragment.app.FragmentFactory +import com.github.terrakok.cicerone.androidx.FragmentScreen +import org.hyperskill.app.android.paywall.fragment.PaywallFragment +import org.hyperskill.app.paywall.domain.model.PaywallTransitionSource + +class PaywallScreen( + private val paywallTransitionSource: PaywallTransitionSource +) : FragmentScreen { + companion object { + const val TAG: String = "PaywallScreen" + } + + override val screenKey: String + get() = TAG + + override fun createFragment(factory: FragmentFactory): Fragment = + PaywallFragment.newInstance(paywallTransitionSource) +} \ No newline at end of file diff --git a/androidHyperskillApp/src/main/java/org/hyperskill/app/android/paywall/ui/PaywallContent.kt b/androidHyperskillApp/src/main/java/org/hyperskill/app/android/paywall/ui/PaywallContent.kt new file mode 100644 index 0000000000..ed16ce5011 --- /dev/null +++ b/androidHyperskillApp/src/main/java/org/hyperskill/app/android/paywall/ui/PaywallContent.kt @@ -0,0 +1,115 @@ +package org.hyperskill.app.android.paywall.ui + +import androidx.compose.foundation.BorderStroke +import androidx.compose.foundation.Image +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.material.MaterialTheme +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.colorResource +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import org.hyperskill.app.R +import org.hyperskill.app.android.core.extensions.compose.plus +import org.hyperskill.app.android.core.view.ui.widget.compose.HyperskillButton +import org.hyperskill.app.android.core.view.ui.widget.compose.HyperskillButtonDefaults +import org.hyperskill.app.android.core.view.ui.widget.compose.HyperskillTheme + +@Composable +fun PaywallContent( + buyButtonText: String, + isContinueWithLimitsButtonVisible: Boolean, + onTermsOfServiceClick: () -> Unit, + onBuySubscriptionClick: () -> Unit, + onContinueWithLimitsClick: () -> Unit, + modifier: Modifier = Modifier, + padding: PaddingValues = PaddingValues() +) { + Column( + modifier = modifier + .fillMaxSize() + .background(PaywallDefaults.BackgroundColor) + .padding(PaywallDefaults.ContentPadding + padding) + ) { + Column( + verticalArrangement = Arrangement.spacedBy(24.dp), + modifier = Modifier.weight(1f) + ) { + Image( + painter = painterResource(id = org.hyperskill.app.android.R.drawable.img_paywall), + contentDescription = null, + modifier = Modifier + .align(Alignment.CenterHorizontally) + ) + Text( + text = stringResource(id = R.string.paywall_mobile_only_title), + style = MaterialTheme.typography.h5, + fontWeight = FontWeight.Medium + ) + SubscriptionDetails() + } + Column { + HyperskillButton( + onClick = onBuySubscriptionClick, + modifier = Modifier.fillMaxWidth() + ) { + Text(text = buyButtonText) + } + Spacer(modifier = Modifier.height(8.dp)) + if (isContinueWithLimitsButtonVisible) { + HyperskillButton( + onClick = onContinueWithLimitsClick, + colors = HyperskillButtonDefaults.buttonColors(colorResource(id = R.color.layer_1)), + border = BorderStroke(1.dp, colorResource(id = R.color.button_tertiary)), + modifier = Modifier.fillMaxWidth() + ) { + Text( + text = stringResource(id = R.string.paywall_mobile_only_continue_btn), + color = colorResource(id = R.color.button_tertiary) + ) + } + } + Spacer(modifier = Modifier.height(20.dp)) + + Text( + text = stringResource(id = R.string.paywall_tos_and_privacy_bth), + fontSize = 12.sp, + color = colorResource(id = R.color.color_on_surface_alpha_60), + textAlign = TextAlign.Center, + modifier = Modifier + .align(Alignment.CenterHorizontally) + .clickable(onClick = onTermsOfServiceClick) + ) + } + } +} + +@Preview +@Composable +fun PaywallContentPreview() { + HyperskillTheme { + PaywallContent( + buyButtonText = PaywallPreviewDefaults.BUY_BUTTON_TEXT, + isContinueWithLimitsButtonVisible = true, + onTermsOfServiceClick = {}, + onBuySubscriptionClick = {}, + onContinueWithLimitsClick = {} + ) + } +} \ No newline at end of file diff --git a/androidHyperskillApp/src/main/java/org/hyperskill/app/android/paywall/ui/PaywallPreviewDefaults.kt b/androidHyperskillApp/src/main/java/org/hyperskill/app/android/paywall/ui/PaywallPreviewDefaults.kt new file mode 100644 index 0000000000..3586120773 --- /dev/null +++ b/androidHyperskillApp/src/main/java/org/hyperskill/app/android/paywall/ui/PaywallPreviewDefaults.kt @@ -0,0 +1,5 @@ +package org.hyperskill.app.android.paywall.ui + +object PaywallPreviewDefaults { + const val BUY_BUTTON_TEXT = "Subscribe for $12.00/month" +} \ No newline at end of file diff --git a/androidHyperskillApp/src/main/java/org/hyperskill/app/android/paywall/ui/PaywallScreen.kt b/androidHyperskillApp/src/main/java/org/hyperskill/app/android/paywall/ui/PaywallScreen.kt new file mode 100644 index 0000000000..43f541853c --- /dev/null +++ b/androidHyperskillApp/src/main/java/org/hyperskill/app/android/paywall/ui/PaywallScreen.kt @@ -0,0 +1,175 @@ +package org.hyperskill.app.android.paywall.ui + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.material.Scaffold +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.colorResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.tooling.preview.PreviewParameter +import androidx.compose.ui.tooling.preview.PreviewParameterProvider +import androidx.compose.ui.unit.dp +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import org.hyperskill.app.R +import org.hyperskill.app.android.core.extensions.findActivity +import org.hyperskill.app.android.core.view.ui.widget.compose.HyperskillProgressBar +import org.hyperskill.app.android.core.view.ui.widget.compose.HyperskillTheme +import org.hyperskill.app.android.core.view.ui.widget.compose.HyperskillTopAppBar +import org.hyperskill.app.android.core.view.ui.widget.compose.OnComposableShownFirstTime +import org.hyperskill.app.android.core.view.ui.widget.compose.ScreenDataLoadingError +import org.hyperskill.app.paywall.presentation.PaywallFeature +import org.hyperskill.app.paywall.presentation.PaywallFeature.ViewState +import org.hyperskill.app.paywall.presentation.PaywallFeature.ViewStateContent +import org.hyperskill.app.paywall.presentation.PaywallViewModel + +object PaywallDefaults { + val ContentPadding = PaddingValues( + start = 24.dp, + end = 20.dp, + top = 24.dp, + bottom = 32.dp + ) + + val BackgroundColor: Color + @Composable + get() = colorResource(id = R.color.layer_1) +} + +@Composable +fun PaywallScreen( + viewModel: PaywallViewModel, + onBackClick: () -> Unit +) { + OnComposableShownFirstTime(viewModel) { + viewModel.onNewMessage(PaywallFeature.Message.ViewedEventMessage) + } + val state by viewModel.state.collectAsStateWithLifecycle() + val activity = LocalContext.current.findActivity() + PaywallScreen( + viewState = state, + onBackClick = onBackClick, + onBuySubscriptionClick = remember(activity) { + { + viewModel.onBuySubscriptionClick(activity) + } + }, + onContinueWithLimitsClick = viewModel::onContinueWithLimitsClick, + onRetryLoadingClick = viewModel::onRetryLoadingClicked, + onTermsOfServiceClick = viewModel::onTermsOfServiceClick + ) +} + +@Composable +fun PaywallScreen( + viewState: ViewState, + onBackClick: () -> Unit, + onBuySubscriptionClick: () -> Unit, + onContinueWithLimitsClick: () -> Unit, + onRetryLoadingClick: () -> Unit, + onTermsOfServiceClick: () -> Unit +) { + Scaffold( + topBar = { + if (viewState.isToolbarVisible) { + HyperskillTopAppBar( + title = stringResource(id = R.string.paywall_screen_title), + onNavigationIconClick = onBackClick, + backgroundColor = PaywallDefaults.BackgroundColor + ) + } + } + ) { padding -> + when (val contentState = viewState.contentState) { + ViewStateContent.Idle -> { + // no op + } + ViewStateContent.Loading -> { + Box( + modifier = Modifier + .fillMaxSize() + .background(PaywallDefaults.BackgroundColor) + ) { + HyperskillProgressBar( + modifier = Modifier.align(Alignment.Center) + ) + } + } + ViewStateContent.Error -> + ScreenDataLoadingError( + errorMessage = stringResource(id = R.string.paywall_placeholder_error_description), + modifier = Modifier.background(PaywallDefaults.BackgroundColor) + ) { + onRetryLoadingClick() + } + is ViewStateContent.Content -> + PaywallContent( + buyButtonText = contentState.buyButtonText, + isContinueWithLimitsButtonVisible = contentState.isContinueWithLimitsButtonVisible, + onBuySubscriptionClick = onBuySubscriptionClick, + onContinueWithLimitsClick = onContinueWithLimitsClick, + onTermsOfServiceClick = onTermsOfServiceClick, + padding = padding + ) + ViewStateContent.SubscriptionSyncLoading -> + SubscriptionSyncLoading() + } + } +} + +private class PaywallPreviewProvider : PreviewParameterProvider { + override val values: Sequence + get() = sequenceOf( + ViewState( + isToolbarVisible = true, + contentState = ViewStateContent.Content( + buyButtonText = PaywallPreviewDefaults.BUY_BUTTON_TEXT, + isContinueWithLimitsButtonVisible = false + ) + ), + ViewState( + isToolbarVisible = false, + contentState = ViewStateContent.Content( + buyButtonText = PaywallPreviewDefaults.BUY_BUTTON_TEXT, + isContinueWithLimitsButtonVisible = true + ) + ), + ViewState( + isToolbarVisible = true, + contentState = ViewStateContent.Error + ), + ViewState( + isToolbarVisible = true, + contentState = ViewStateContent.Loading + ), + ViewState( + isToolbarVisible = true, + contentState = ViewStateContent.SubscriptionSyncLoading + ) + ) +} + +@Preview(showBackground = true) +@Composable +fun PaywallScreenPreview( + @PreviewParameter(provider = PaywallPreviewProvider::class) viewState: ViewState +) { + HyperskillTheme { + PaywallScreen( + viewState = viewState, + onBackClick = {}, + onBuySubscriptionClick = {}, + onContinueWithLimitsClick = {}, + onRetryLoadingClick = {}, + onTermsOfServiceClick = {} + ) + } +} \ No newline at end of file diff --git a/androidHyperskillApp/src/main/java/org/hyperskill/app/android/paywall/ui/SubscriptionDetails.kt b/androidHyperskillApp/src/main/java/org/hyperskill/app/android/paywall/ui/SubscriptionDetails.kt new file mode 100644 index 0000000000..0e4265ef6a --- /dev/null +++ b/androidHyperskillApp/src/main/java/org/hyperskill/app/android/paywall/ui/SubscriptionDetails.kt @@ -0,0 +1,63 @@ +package org.hyperskill.app.android.paywall.ui + +import androidx.compose.foundation.Image +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.material.MaterialTheme +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import org.hyperskill.app.android.R +import org.hyperskill.app.android.core.view.ui.widget.compose.HyperskillTheme +import org.hyperskill.app.R as SharedR + +@Composable +fun SubscriptionDetails(modifier: Modifier = Modifier) { + Column( + modifier = modifier, + verticalArrangement = Arrangement.spacedBy(6.dp) + ) { + SubscriptionOption(text = stringResource(id = SharedR.string.mobile_only_subscription_feature_1)) + SubscriptionOption(text = stringResource(id = SharedR.string.mobile_only_subscription_feature_2)) + SubscriptionOption(text = stringResource(id = SharedR.string.mobile_only_subscription_feature_3)) + } +} + +@Composable +fun SubscriptionOption( + text: String, + modifier: Modifier = Modifier +) { + Row( + modifier = modifier, + horizontalArrangement = Arrangement.spacedBy(10.dp) + ) { + Image( + painter = painterResource(id = R.drawable.ic_paywall_option), + contentDescription = null, + modifier = Modifier.align(Alignment.CenterVertically) + ) + Text( + text = text, + style = MaterialTheme.typography.body2, + fontWeight = FontWeight.Normal, + fontSize = 16.sp + ) + } +} + +@Preview +@Composable +private fun PaywallSubscriptionDetailsPreview() { + HyperskillTheme { + SubscriptionDetails() + } +} \ No newline at end of file diff --git a/androidHyperskillApp/src/main/java/org/hyperskill/app/android/paywall/ui/SubscriptionSyncLoading.kt b/androidHyperskillApp/src/main/java/org/hyperskill/app/android/paywall/ui/SubscriptionSyncLoading.kt new file mode 100644 index 0000000000..3b3f5e682b --- /dev/null +++ b/androidHyperskillApp/src/main/java/org/hyperskill/app/android/paywall/ui/SubscriptionSyncLoading.kt @@ -0,0 +1,57 @@ +package org.hyperskill.app.android.paywall.ui + +import androidx.compose.foundation.Image +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.height +import androidx.compose.material.MaterialTheme +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import org.hyperskill.app.android.R +import org.hyperskill.app.android.core.view.ui.widget.compose.HyperskillTheme +import org.hyperskill.app.android.core.view.ui.widget.compose.centerWithVerticalBias +import org.hyperskill.app.R as SharedR + +@Composable +fun SubscriptionSyncLoading( + modifier: Modifier = Modifier +) { + Box( + modifier = modifier + .fillMaxSize() + .background(PaywallDefaults.BackgroundColor) + ) { + Column(modifier = Modifier.align(Alignment.centerWithVerticalBias(-0.5f))) { + Image( + painter = painterResource(id = R.drawable.img_reload), + contentDescription = null, + modifier = Modifier.align(Alignment.CenterHorizontally) + ) + Spacer(modifier = Modifier.height(16.dp)) + Text( + text = stringResource(id = SharedR.string.paywall_subscription_sync_description), + style = MaterialTheme.typography.h6, + textAlign = TextAlign.Center, + modifier = Modifier.align(Alignment.CenterHorizontally) + ) + } + } +} + +@Preview +@Composable +private fun SubscriptionSyncLoadingPreview() { + HyperskillTheme { + SubscriptionSyncLoading() + } +} \ No newline at end of file diff --git a/androidHyperskillApp/src/main/java/org/hyperskill/app/android/problem_of_day/view/delegate/ProblemOfDayCardFormDelegate.kt b/androidHyperskillApp/src/main/java/org/hyperskill/app/android/problem_of_day/view/delegate/ProblemOfDayCardFormDelegate.kt index 166bdfd3c5..4166c38838 100644 --- a/androidHyperskillApp/src/main/java/org/hyperskill/app/android/problem_of_day/view/delegate/ProblemOfDayCardFormDelegate.kt +++ b/androidHyperskillApp/src/main/java/org/hyperskill/app/android/problem_of_day/view/delegate/ProblemOfDayCardFormDelegate.kt @@ -25,7 +25,7 @@ class ProblemOfDayCardFormDelegate( dateFormatter: SharedDateFormatter, binding: LayoutProblemOfTheDayCardBinding, state: HomeFeature.ProblemOfDayState, - isFreemiumEnabled: Boolean + areProblemsLimited: Boolean ) { with(binding) { when (state) { @@ -112,13 +112,13 @@ class ProblemOfDayCardFormDelegate( } } } - renderFooter(binding, state, isFreemiumEnabled) + renderFooter(binding, state, areProblemsLimited) } private fun renderFooter( binding: LayoutProblemOfTheDayCardBinding, state: HomeFeature.ProblemOfDayState, - isFreemiumEnabled: Boolean + areProblemsLimited: Boolean ) { val needToRefresh = when (state) { HomeFeature.ProblemOfDayState.Empty -> false @@ -142,8 +142,8 @@ class ProblemOfDayCardFormDelegate( problemOfDayNextProblemInCounterView.text = nextProblemIn } } - binding.problemOfDayFreemiumBadge.isVisible = when (state) { - is HomeFeature.ProblemOfDayState.NeedToSolve -> isFreemiumEnabled + binding.problemOfDayUnlimitedBadge.isVisible = when (state) { + is HomeFeature.ProblemOfDayState.NeedToSolve -> areProblemsLimited HomeFeature.ProblemOfDayState.Empty, is HomeFeature.ProblemOfDayState.Solved -> false } diff --git a/androidHyperskillApp/src/main/java/org/hyperskill/app/android/problems_limit/dialog/ProblemsLimitReachedBottomSheet.kt b/androidHyperskillApp/src/main/java/org/hyperskill/app/android/problems_limit/dialog/ProblemsLimitReachedBottomSheet.kt index e92c7c3883..566dac62a5 100644 --- a/androidHyperskillApp/src/main/java/org/hyperskill/app/android/problems_limit/dialog/ProblemsLimitReachedBottomSheet.kt +++ b/androidHyperskillApp/src/main/java/org/hyperskill/app/android/problems_limit/dialog/ProblemsLimitReachedBottomSheet.kt @@ -6,6 +6,7 @@ import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import androidx.core.view.isVisible import androidx.fragment.app.viewModels import by.kirich1409.viewbindingdelegate.viewBinding import com.google.android.material.bottomsheet.BottomSheetBehavior @@ -24,13 +25,18 @@ class ProblemsLimitReachedBottomSheet : BottomSheetDialogFragment() { const val TAG = "ProblemsLimitReachedBottomSheet" - fun newInstance(modalText: String): ProblemsLimitReachedBottomSheet = + fun newInstance( + modalText: String, + isUnlockUnlimitedProblemsButtonVisible: Boolean + ): ProblemsLimitReachedBottomSheet = ProblemsLimitReachedBottomSheet().apply { this.modalText = modalText + this.isUnlockUnlimitedProblemsButtonVisible = isUnlockUnlimitedProblemsButtonVisible } } private var modalText: String by argument() + private var isUnlockUnlimitedProblemsButtonVisible: Boolean by argument() private val viewBinding: FragmentProblemsLimitReachedBinding by viewBinding( FragmentProblemsLimitReachedBinding::bind @@ -71,6 +77,14 @@ class ProblemsLimitReachedBottomSheet : BottomSheetDialogFragment() { problemsLimitReachedHomeButton.setOnClickListener { viewModel.onNewMessage(StepQuizFeature.Message.ProblemsLimitReachedModalGoToHomeScreenClicked) } + problemsLimitReachedUnlimitedProblemsButton.isVisible = isUnlockUnlimitedProblemsButtonVisible + if (isUnlockUnlimitedProblemsButtonVisible) { + problemsLimitReachedUnlimitedProblemsButton.setOnClickListener { + viewModel.onNewMessage( + StepQuizFeature.Message.ProblemsLimitReachedModalUnlockUnlimitedProblemsClicked + ) + } + } problemsLimitReachedDescription.text = modalText } } diff --git a/androidHyperskillApp/src/main/java/org/hyperskill/app/android/profile_settings/view/dialog/ProfileSettingsDialogFragment.kt b/androidHyperskillApp/src/main/java/org/hyperskill/app/android/profile_settings/view/dialog/ProfileSettingsDialogFragment.kt index 0e4bdcae5f..fdaed8722b 100644 --- a/androidHyperskillApp/src/main/java/org/hyperskill/app/android/profile_settings/view/dialog/ProfileSettingsDialogFragment.kt +++ b/androidHyperskillApp/src/main/java/org/hyperskill/app/android/profile_settings/view/dialog/ProfileSettingsDialogFragment.kt @@ -1,5 +1,6 @@ package org.hyperskill.app.android.profile_settings.view.dialog +import android.annotation.SuppressLint import android.content.ActivityNotFoundException import android.content.Intent import android.net.Uri @@ -7,6 +8,7 @@ import android.os.Bundle import android.util.TypedValue import android.view.View import androidx.appcompat.app.AppCompatDelegate +import androidx.core.view.isVisible import androidx.fragment.app.DialogFragment import androidx.lifecycle.ViewModelProvider import by.kirich1409.viewbindingdelegate.viewBinding @@ -14,17 +16,22 @@ import com.google.android.material.dialog.MaterialAlertDialogBuilder import org.hyperskill.app.SharedResources import org.hyperskill.app.android.HyperskillApp import org.hyperskill.app.android.R +import org.hyperskill.app.android.core.extensions.getStringRepresentation import org.hyperskill.app.android.core.extensions.openUrl -import org.hyperskill.app.android.core.extensions.representation import org.hyperskill.app.android.core.view.ui.dialog.LoadingProgressDialogFragment import org.hyperskill.app.android.core.view.ui.dialog.dismissDialogFragmentIfExists +import org.hyperskill.app.android.core.view.ui.navigation.requireRouter import org.hyperskill.app.android.databinding.FragmentProfileSettingsBinding +import org.hyperskill.app.android.manage_subscription.navigation.ManageSubscriptionScreen +import org.hyperskill.app.android.paywall.navigation.PaywallScreen import org.hyperskill.app.android.profile_settings.view.mapper.asNightMode import org.hyperskill.app.android.view.base.ui.extension.snackbar import org.hyperskill.app.profile.presentation.ProfileSettingsViewModel import org.hyperskill.app.profile_settings.domain.model.FeedbackEmailData import org.hyperskill.app.profile_settings.domain.model.Theme -import org.hyperskill.app.profile_settings.presentation.ProfileSettingsFeature +import org.hyperskill.app.profile_settings.presentation.ProfileSettingsFeature.Action +import org.hyperskill.app.profile_settings.presentation.ProfileSettingsFeature.Message +import org.hyperskill.app.profile_settings.presentation.ProfileSettingsFeature.ViewState import ru.nobird.android.view.base.ui.delegate.ViewStateDelegate import ru.nobird.android.view.base.ui.extension.showIfNotExists import ru.nobird.android.view.redux.ui.extension.reduxViewModel @@ -32,7 +39,7 @@ import ru.nobird.app.presentation.redux.container.ReduxView class ProfileSettingsDialogFragment : DialogFragment(R.layout.fragment_profile_settings), - ReduxView { + ReduxView { companion object { const val TAG = "ProfileSettingsDialogFragment" @@ -44,7 +51,7 @@ class ProfileSettingsDialogFragment : private lateinit var viewModelFactory: ViewModelProvider.Factory private val profileSettingsViewModel: ProfileSettingsViewModel by reduxViewModel(this) { viewModelFactory } - private val viewStateDelegate: ViewStateDelegate = ViewStateDelegate() + private var viewStateDelegate: ViewStateDelegate? = null private var currentThemePosition: Int = -1 @@ -64,30 +71,32 @@ class ProfileSettingsDialogFragment : override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) + initViewStateDelegate(viewBinding) + with(viewBinding.settingsCenteredToolbar) { centeredToolbarTitle.setText(org.hyperskill.app.R.string.settings_title) centeredToolbarTitle.setTextAppearance(androidx.appcompat.R.style.TextAppearance_AppCompat_Body2) centeredToolbarTitle.setTextSize(TypedValue.COMPLEX_UNIT_SP, 18F) centeredToolbar.setNavigationOnClickListener { - profileSettingsViewModel.onNewMessage(ProfileSettingsFeature.Message.ClickedDoneEventMessage) + profileSettingsViewModel.onNewMessage(Message.ClickedDoneEventMessage) dismiss() } centeredToolbar.setNavigationIcon(R.drawable.ic_close_thin) } - - viewBinding.settingsThemeButton.setOnClickListener { - profileSettingsViewModel.onNewMessage(ProfileSettingsFeature.Message.ClickedThemeEventMessage) + viewBinding.settingsContent.settingsThemeButton.setOnClickListener { + profileSettingsViewModel.onNewMessage(Message.ClickedThemeEventMessage) MaterialAlertDialogBuilder(requireContext(), R.style.ThemeOverlay_App_MaterialAlertDialog) .setTitle(org.hyperskill.app.R.string.settings_theme) .setSingleChoiceItems( - Theme.values().map { theme -> theme.representation }.toTypedArray(), + Theme.values().map { theme -> theme.getStringRepresentation(requireContext()) }.toTypedArray(), currentThemePosition ) { _, which -> val newTheme = Theme.values()[which] - profileSettingsViewModel.onNewMessage(ProfileSettingsFeature.Message.ThemeChanged(theme = newTheme)) - viewBinding.settingsThemeChosenTextView.text = newTheme.representation + profileSettingsViewModel.onNewMessage(Message.ThemeChanged(theme = newTheme)) + viewBinding.settingsContent.settingsThemeChosenTextView.text = + newTheme.getStringRepresentation(requireContext()) AppCompatDelegate.setDefaultNightMode(newTheme.asNightMode()) } .setNegativeButton(org.hyperskill.app.R.string.cancel) { dialog, _ -> @@ -96,37 +105,43 @@ class ProfileSettingsDialogFragment : .show() } - viewBinding.settingsTermsOfServiceButton.setOnClickListener { - profileSettingsViewModel.onNewMessage(ProfileSettingsFeature.Message.ClickedTermsOfServiceEventMessage) + viewBinding.settingsContent.settingsTermsOfServiceButton.setOnClickListener { + profileSettingsViewModel.onNewMessage(Message.ClickedTermsOfServiceEventMessage) openLinkInBrowser(resources.getString(org.hyperskill.app.R.string.settings_terms_of_service_url)) } - viewBinding.settingsPrivacyPolicyButton.setOnClickListener { - profileSettingsViewModel.onNewMessage(ProfileSettingsFeature.Message.ClickedPrivacyPolicyEventMessage) + viewBinding.settingsContent.settingsPrivacyPolicyButton.setOnClickListener { + profileSettingsViewModel.onNewMessage(Message.ClickedPrivacyPolicyEventMessage) openLinkInBrowser(resources.getString(org.hyperskill.app.R.string.settings_privacy_policy_url)) } - viewBinding.settingsReportProblemButton.setOnClickListener { - profileSettingsViewModel.onNewMessage(ProfileSettingsFeature.Message.ClickedReportProblemEventMessage) + viewBinding.settingsContent.settingsReportProblemButton.setOnClickListener { + profileSettingsViewModel.onNewMessage(Message.ClickedReportProblemEventMessage) openLinkInBrowser(resources.getString(org.hyperskill.app.R.string.settings_report_problem_url)) } - viewBinding.settingsSendFeedbackButton.setOnClickListener { - profileSettingsViewModel.onNewMessage(ProfileSettingsFeature.Message.ClickedSendFeedback) + viewBinding.settingsContent.settingsSendFeedbackButton.setOnClickListener { + profileSettingsViewModel.onNewMessage(Message.ClickedSendFeedback) + } + + viewBinding.settingsContent.settingsSubscriptionFrameLayout.setOnClickListener { + profileSettingsViewModel.onNewMessage(Message.SubscriptionDetailsClicked) } - viewBinding.settingsRateAppButton.setOnClickListener { - profileSettingsViewModel.onNewMessage(ProfileSettingsFeature.Message.ClickedRateUsInPlayStoreEventMessage) + viewBinding.settingsContent.settingsRateAppButton.setOnClickListener { + profileSettingsViewModel.onNewMessage(Message.ClickedRateUsInPlayStoreEventMessage) requireContext().openUrl( getString(org.hyperskill.app.R.string.settings_rate_in_google_play_url) ) } val userAgentInfo = HyperskillApp.graph().commonComponent.userAgentInfo - viewBinding.settingsVersionTextView.text = "${userAgentInfo.versionName} (${userAgentInfo.versionCode})" + @SuppressLint("SetTextI18n") + viewBinding.settingsContent.settingsVersionTextView.text = + "${userAgentInfo.versionName} (${userAgentInfo.versionCode})" - viewBinding.settingsLogoutButton.setOnClickListener { - profileSettingsViewModel.onNewMessage(ProfileSettingsFeature.Message.ClickedSignOutEventMessage) + viewBinding.settingsContent.settingsLogoutButton.setOnClickListener { + profileSettingsViewModel.onNewMessage(Message.ClickedSignOutEventMessage) MaterialAlertDialogBuilder( requireContext(), @@ -136,15 +151,15 @@ class ProfileSettingsDialogFragment : .setMessage(org.hyperskill.app.R.string.settings_sign_out_dialog_explanation) .setPositiveButton(org.hyperskill.app.R.string.yes) { _, _ -> profileSettingsViewModel.onNewMessage( - ProfileSettingsFeature.Message.SignOutNoticeHiddenEventMessage( + Message.SignOutNoticeHiddenEventMessage( isConfirmed = true ) ) - profileSettingsViewModel.onNewMessage(ProfileSettingsFeature.Message.SignOutConfirmed) + profileSettingsViewModel.onNewMessage(Message.SignOutConfirmed) } .setNegativeButton(org.hyperskill.app.R.string.no) { dialog, _ -> profileSettingsViewModel.onNewMessage( - ProfileSettingsFeature.Message.SignOutNoticeHiddenEventMessage( + Message.SignOutNoticeHiddenEventMessage( isConfirmed = false ) ) @@ -152,11 +167,11 @@ class ProfileSettingsDialogFragment : } .show() - profileSettingsViewModel.onNewMessage(ProfileSettingsFeature.Message.SignOutNoticeShownEventMessage) + profileSettingsViewModel.onNewMessage(Message.SignOutNoticeShownEventMessage) } - viewBinding.settingsDeleteAccountButton.setOnClickListener { - profileSettingsViewModel.onNewMessage(ProfileSettingsFeature.Message.ClickedDeleteAccountEventMessage) + viewBinding.settingsContent.settingsDeleteAccountButton.setOnClickListener { + profileSettingsViewModel.onNewMessage(Message.ClickedDeleteAccountEventMessage) MaterialAlertDialogBuilder( requireContext(), @@ -168,57 +183,88 @@ class ProfileSettingsDialogFragment : org.hyperskill.app.R.string.settings_account_deletion_dialog_delete_button_text ) { _, _ -> profileSettingsViewModel.onNewMessage( - ProfileSettingsFeature.Message.DeleteAccountNoticeHidden(true) + Message.DeleteAccountNoticeHidden(true) ) } .setNegativeButton(org.hyperskill.app.R.string.cancel) { dialog, _ -> profileSettingsViewModel.onNewMessage( - ProfileSettingsFeature.Message.DeleteAccountNoticeHidden(false) + Message.DeleteAccountNoticeHidden(false) ) dialog.dismiss() } .show() - profileSettingsViewModel.onNewMessage(ProfileSettingsFeature.Message.DeleteAccountNoticeShownEventMessage) + profileSettingsViewModel.onNewMessage(Message.DeleteAccountNoticeShownEventMessage) } - profileSettingsViewModel.onNewMessage(ProfileSettingsFeature.Message.InitMessage()) - profileSettingsViewModel.onNewMessage(ProfileSettingsFeature.Message.ViewedEventMessage) + profileSettingsViewModel.onNewMessage(Message.InitMessage) + profileSettingsViewModel.onNewMessage(Message.ViewedEventMessage) + } + + private fun initViewStateDelegate(viewBinding: FragmentProfileSettingsBinding) { + viewStateDelegate = ViewStateDelegate().apply { + addState() + addState(viewBinding.settingsProgress) + addState(viewBinding.settingsContent.root) + } + } + + override fun onDestroyView() { + super.onDestroyView() + viewStateDelegate = null } private fun openLinkInBrowser(link: String) { requireContext().openUrl(link) } - override fun onAction(action: ProfileSettingsFeature.Action.ViewAction) { + override fun onAction(action: Action.ViewAction) { when (action) { - is ProfileSettingsFeature.Action.ViewAction.SendFeedback -> + is Action.ViewAction.SendFeedback -> sendEmailFeedback(action.feedbackEmailData) - is ProfileSettingsFeature.Action.ViewAction.OpenUrl -> + is Action.ViewAction.OpenUrl -> openLinkInBrowser(action.url) - is ProfileSettingsFeature.Action.ViewAction.ShowGetMagicLinkError -> + is Action.ViewAction.ShowGetMagicLinkError -> viewBinding.root.snackbar(SharedResources.strings.common_error.resourceId) + is Action.ViewAction.NavigateTo.Paywall -> { + requireRouter() + .navigateTo(PaywallScreen(action.paywallTransitionSource)) + } + is Action.ViewAction.NavigateTo.SubscriptionManagement -> { + requireRouter().navigateTo(ManageSubscriptionScreen) + } else -> { // no op } } } - override fun render(state: ProfileSettingsFeature.State) { - viewStateDelegate.switchState(state) + override fun render(state: ViewState) { + viewStateDelegate?.switchState(state) - if (state is ProfileSettingsFeature.State.Content) { + if (state is ViewState.Content) { if (state.isLoadingMagicLink) { LoadingProgressDialogFragment.newInstance() .showIfNotExists(childFragmentManager, LoadingProgressDialogFragment.TAG) } else { childFragmentManager.dismissDialogFragmentIfExists(LoadingProgressDialogFragment.TAG) } - viewBinding.settingsThemeChosenTextView.text = state.profileSettings.theme.representation + viewBinding.settingsContent.settingsThemeChosenTextView.text = + state.profileSettings.theme.getStringRepresentation(requireContext()) currentThemePosition = state.profileSettings.theme.ordinal + renderSubscription(state) } } + private fun renderSubscription( + state: ViewState.Content + ) { + state.subscriptionState?.description + ?.let(viewBinding.settingsContent.settingsSubscriptionHeader::setText) + viewBinding.settingsContent.settingsSubscriptionLinearLayout.isVisible = + state.subscriptionState != null + } + private fun sendEmailFeedback(feedbackEmailData: FeedbackEmailData) { val intent = Intent(Intent.ACTION_SENDTO) .setData(Uri.parse("mailto:")) diff --git a/androidHyperskillApp/src/main/java/org/hyperskill/app/android/step_quiz/view/fragment/DefaultStepQuizFragment.kt b/androidHyperskillApp/src/main/java/org/hyperskill/app/android/step_quiz/view/fragment/DefaultStepQuizFragment.kt index 0087e804a8..641257920e 100644 --- a/androidHyperskillApp/src/main/java/org/hyperskill/app/android/step_quiz/view/fragment/DefaultStepQuizFragment.kt +++ b/androidHyperskillApp/src/main/java/org/hyperskill/app/android/step_quiz/view/fragment/DefaultStepQuizFragment.kt @@ -31,6 +31,7 @@ import org.hyperskill.app.android.main.view.ui.navigation.MainScreen import org.hyperskill.app.android.main.view.ui.navigation.MainScreenRouter import org.hyperskill.app.android.main.view.ui.navigation.Tabs import org.hyperskill.app.android.main.view.ui.navigation.switch +import org.hyperskill.app.android.paywall.navigation.PaywallScreen import org.hyperskill.app.android.problems_limit.dialog.ProblemsLimitReachedBottomSheet import org.hyperskill.app.android.step.view.model.StepCompletionHost import org.hyperskill.app.android.step.view.model.StepCompletionView @@ -278,11 +279,18 @@ abstract class DefaultStepQuizFragment : is StepQuizFeature.Action.ViewAction.NavigateTo.StepScreen -> { requireRouter().navigateTo(StepScreen(action.stepRoute)) } + is StepQuizFeature.Action.ViewAction.NavigateTo.Paywall -> { + requireRouter().navigateTo(PaywallScreen(action.paywallTransitionSource)) + } is StepQuizFeature.Action.ViewAction.RequestResetCode -> { requestResetCodeActionPermission() } is StepQuizFeature.Action.ViewAction.ShowProblemsLimitReachedModal -> { - ProblemsLimitReachedBottomSheet.newInstance(action.modalText) + ProblemsLimitReachedBottomSheet + .newInstance( + modalText = action.modalText, + isUnlockUnlimitedProblemsButtonVisible = action.isUnlockUnlimitedProblemsButtonVisible + ) .showIfNotExists(childFragmentManager, ProblemsLimitReachedBottomSheet.TAG) } is StepQuizFeature.Action.ViewAction.ShowProblemOnboardingModal -> { diff --git a/androidHyperskillApp/src/main/java/org/hyperskill/app/android/topics_repetitions/view/delegate/TopicsRepetitionCardFormDelegate.kt b/androidHyperskillApp/src/main/java/org/hyperskill/app/android/topics_repetitions/view/delegate/TopicsRepetitionCardFormDelegate.kt index fc75533fee..267c2eb413 100644 --- a/androidHyperskillApp/src/main/java/org/hyperskill/app/android/topics_repetitions/view/delegate/TopicsRepetitionCardFormDelegate.kt +++ b/androidHyperskillApp/src/main/java/org/hyperskill/app/android/topics_repetitions/view/delegate/TopicsRepetitionCardFormDelegate.kt @@ -10,7 +10,7 @@ class TopicsRepetitionCardFormDelegate { context: Context, binding: LayoutTopicsRepetitionCardBinding, recommendedRepetitionsCount: Int, - isFreemiumEnabled: Boolean + areProblemsLimited: Boolean ) { with(binding) { topicsRepetitionBackgroundImageView.setImageResource( @@ -48,7 +48,8 @@ class TopicsRepetitionCardFormDelegate { org.hyperskill.app.R.string.good_job } ) - topicsRepetitionFreemiumBadge.isVisible = isFreemiumEnabled && recommendedRepetitionsCount > 0 + topicsRepetitionUnlimitedBadge.isVisible = + areProblemsLimited && recommendedRepetitionsCount > 0 } } } \ No newline at end of file diff --git a/androidHyperskillApp/src/main/res/drawable-hdpi/img_paywall.webp b/androidHyperskillApp/src/main/res/drawable-hdpi/img_paywall.webp new file mode 100644 index 0000000000000000000000000000000000000000..1482d2f89f2cb84bd85c2657260546915e64bf33 GIT binary patch literal 80048 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 zI*d%;CJBMUHnM?l?B|46iJ+hY=~GZOfLoXl000000000000000000000000000000 J00000002x1-PHg9 literal 0 HcmV?d00001 diff --git a/androidHyperskillApp/src/main/res/drawable-mdpi/img_paywall.webp b/androidHyperskillApp/src/main/res/drawable-mdpi/img_paywall.webp new file mode 100644 index 0000000000000000000000000000000000000000..802ff4e40622f88abaa712b494a2af790e6142bd GIT binary patch literal 39372 zcmaI7dt6d^{y*;QbRBc7oWZ2j%A(x~yddPNscV+ajFP)My9UQA2nwCd2`^=M+0oQo zyku^uWRf}2wt)hI4qivol)($ebWprashERkX<{nM@62a^kH_cx$M^R;a1L+Ic|YFg zeSN)N&*$s){_9tPfn60A78lQ*`E%%>|497A!otE8KI1;J&{|l06%g>}-jCqF79aKg zcHIU)iocS?{OkEMhlwQM@XlW>K7v30bC6>azx{9O|9XXU8KM8?Rb&3IIsX4|?xxZb zW8f@r;qw?1emPvH&*1cDasN&K{SuI_i_J6 zn(}|7W4>knmcIbbb2|QV@^A0@?f9*AduUhw{tf)Q3qB87BwGB{;=IL~-`WrVhmVAZ z78ZY8v9S2~_W$0az&J+QE_zG-36F!;as{ancL_g+SAcm|xMIsze$Li^E%_z=g@)%1|daB8&S{?EVto;bc~ zG1}07jhB-=Zd`7u)&FJqDCKuQyfU|MC5-<1&UE*8cfyN~=Rv)vW52lU-S^wkSYEOA zyE;GH4qJA_FzDQAQi9kv^&cL3xF5~(h(026- zHpwCC2~J2^19YPQSM`aFXKaX0r%&R@YOW~lSDOl7D+&+>7N$u3w$Tfp1H9+R8rcpz z3iVA5V}TGh5vErQ$jAzQ$!5nB`*fhem!1xwl=KSTy|q`yZdvyUCH2j-Xsu%!at#-6 zwbrFV#ux6e0|X4a_3sKqHTYFN11Ibj^gDi5@1nhkY4DA=Lv!j5;s8)MQopCd$3@MB zRAS%3B3<6-i?q=UN2mxAA;1EZH$uvhH$VVL$7vuSdxw>{Oi;bnFz8+ZsUqp=1ss`f zb-WOQ&WGtsQRSyPixv-7K(ifGa>e8&C0#hPJZ~lT<*y87WPZlSzh|J;bb1~t7oq@Q zytP{OU@hN3Zz@C!6q2yz8UiRBS9JzDwrhG+bm7nyLURP%Zk!vpe(hC3AT&!-njVtM zMe_)?3R@^xdD1GKngBrDVt-5HWDP=Soi4Bz;z_^NRb~1XY7!*d^@GdqF%bxU$Fe^d zfw1&;C4i)0e)`q;{#7B3n&EQg{S!O^e80P2zAn{Wj&yf>Wn*}uojI{8an(L5(d5Wd z7&vLlbb4WMO?n=BzTvrRdWH+5u^Wlg@EO`68~_20ie8neZ~jd~Q7euv4qr6}c|9&Bjo-${#jGg|^qadA6A1d0sG9Kw;mS9hQe3tY(!M!age~DxBf4CO+!!UXnk(qV0@<+0+a^8-Dx<8 z1bQA14^c@1Spnp%X}KWqbjN7yg>sL=VGoRvW6dAR2qQR_yX&+#I?0Jgt#B+}Y1mV! z+oQ+&iD{jk+9AoO!D`jsfXQI>^;3YY?P`&%$ltQ2HDXjbveaSXjB2IfJx#5r2ugZe z9}c6WyAasjO?|6&7`+6=pZk`V8;D@%v|6y; z&=Wq=L| z3Xkv}hk$K{@<(pe_7dp!nnY}(=jQHk6GPgo2@?KjmxQy?#=z)zz9~(lmKAL<8+;=; zyzvtn{Pg9%3WgOo*N5d@7Pw>iX+m!vXRU$O(Z=ZPO{Y%WL!;Fa;sFBavQ7~)(mjw| zv<6RD0O__8XLYAa>TIPvYzb1ZnqV-BAp+Fex+f&{2~uLQ?3_1_pbi(6u@Wr%muBd6 zpe<5Kkr$^hrkjTA@6qgpGkF?{gF6N-z@K#_V+44rBwKemA>E71DPF@5CgUbAV3uhd znq+jO3kX2$(T0`kC47aKfPtHCaz5EiwnC;xfSbJ(YS+>PdQS%sfC@l+Q+X&NP@0Pe+lvcg>`}2(W=5L&RSdL4gXUNKS@gned3^H zAV)#RA+=|`=iTB?^|^QgI@yW>Jb;Vy0B_UCz4|1w6s39YMFelov_YeuZzxL!%KGmN z2Vbj!$>6rj19PXy{}!WdlvHdN7gWDdbA2;10v!Fc6KMKgr))$iUOM0F>9zI7ufLu% zE{bbp@#rp4kzi)|kT;iCo-TvV#9$1Wo0*rQ=EcSaXU+DSJ{+NjAuM5t(wbH4AG$}E z=JrElVIZZ8UMP^clIXRi`uDc$&F17-LgaE2^d%&5hQ#~VPH|T5?p6ZFG<%IFHtIG{ znq$m)jSXoPWO4F^z%=iipC<(c!v z=bw3Ty1(vx;Sd%3f>MF9G^)J#=YmU!&_2s0+qLFRnptN4$-E5LB&MrMTxo+QilQYt z?bt?=bUr#maU={k=|LR`R^Qr(V$;?-W>JdLsU`EPjV5JVK~Af649iy zmRT}1b26r*%CBS6T(>f4zSdru?nQa8kkiq@1+Ex1wju>*_~Ym!nx7%uPEy9H_H;*5 zJGjwV`@FL@3%9P`&NLspjG6q3OEzH%;tNW{l&Vzms zj!2w1h>ioTB*gOaV38=U{6o!Fd}6~+Z9VZQwit&YR-A7O?>I~UiGn#+qZ_FqRVP=H zUx%1O%yH(&BkVrCL>d%UE+P_38HIun+~ld=zK}}yTq9Ij2vGQq!2Z%S?%p zyRZa@<3xg%g{!qh9BDY9>(W=sGkZI#@_lNRXWl;kV7sNucagrlDK&c##*A}K^{-AQjw14qWM#uJulHZ`rmzHRZ+KAEg67}lfMLRUS7J$a`#m*42Qb}c+HIDbd(FGMRK1l3dT1C?1WDFa z@L1VNWtE2Bo>f@=j2-N+U`$s=Yu!s{MrHI5L7R6O(AT24lZ)&e8MkXct1`$*j00r} z504X_f|j^wqo|YjZqXD{nF~vp2Xf~)9&rFa$j4hr_N<+5Zt>9Gn~Q<9a2(gYbO_nm zg(RU$^L=98J^s)Gu#N>hlshjUp5I4RNaAvC>zN5aLR^87K2&v1|HpvP+wBWH(|*|{(2JC(`8oS`xI z3KR=;jn9bX#Z4T%S)$+~KmdWBynyU?G`sk)=;v-i6xqCgt$|#OI6|k0!!?~0YvuyE zWE*Clqy)5g+)Y=_T62{vcNK0oj@x~P2VpBXLv#_fM;^Nnj|m2?C`d_Qpjv{0r?7Qw zr1=JVGM2;|4w$ZdAnQ2;x_!bz)$L~O;;8QEySk2!I8lrBy^~v$E$C7OR+t)rlhVY| zVqyTOqDJ0>=sjujv~d-VTMkLl=->=W^XA`s;T3S_4|S!{U)#)=q_!-GXCVoqnVLr= zk|@X7LS&v9Z!Nh z*t+7)vgBHy{hf3{OgC1cb?-a_iFb;j(H-WPf2SUtZ)@@ zU$W4~VVW5FiTT_Cx+9oEdIPUq-bNJ+Q3*BG)z#x18ipg|FhunJknl*2Sn8x!(N(?E z;Rq)|SabEzhx#MK?uL^(6geR3dn&3lIU=UVwq78Z?-)a z9vT_325&{+aT7^!v5GF!_1sgqk#{x+2~d}mFC%KRQo={SLuL9}X8CCAbtL+BhCwth zJ`a-53GRj;1m(3K8@{_4X#A6YKW6o|--vn3T#*k03+WC!w3gyoMzbRD@xmd=RFf_} z{UyDYpoB{m5z(3_)u{<@XUFQxZgLu;XPWNR=iH8U080@1+sAE|91v2bbEpU*cjj!0 zxBAQkHmngr49Mn-(I}+^1s1}pYBVgd|FQ90fD;%Q13yG4<#go;&Z^MQ!JY^**z9OG z`9jH%qzi7eHM3|Z3yoGX_n7zY0GmUG)Po*Am2wCM`BGQ z&m4suSIw8OBoHWN-0py}1G}3GrHzWTk2(a!d-}m#{t>5AS&K7535L-LO0Hk*Eh0FQ z{kbHrn18(Yp>RhxfY#)s9$Su9tRD=0J|K>41`802o; zOf{;yetk5PJjmq`(Vj7+f$655tOqoV;c5esA_)yPFeLEO24G}`j?0!6pp-aGEI#xZ zbKq=g1UHwz)&WVNPOW6_XDuX z-M#xv7gr#IfaRu2gM>(ghv>YzSA|>u`Ihfm1A~s_!dO?xz=VRF2D+V<5KqXQzN+&9 zq;cPY_Jo|R0gsc0$r=Y zZXG8bmC(5dkQhX%%f*1i1b8ZNJj+(Q_lu*88#vt#ZiG9jw;%X4Lfe9$CPKGZ3 zROx`egyG=hoU$84aV}7=CpTCbJ&2>1X?){T2eAY-lC(#{O(ZBAt%UyS-dQ>c6-Vr? zNJUfpgAOQ|4PAh7o@~9AA>IiJKbPD_pG6qo1`GVzc)CjU*aP6P(WTYZp7$O_AnK9& ztQq}*E(u!sB5i~V3p+KIIKbHe7QpI`GiVlSxz7O>)Gr)Z!!c#F;=dq5St=kjY@o7q z_?biDBe>15iEHfVI;-gtRE?q+!EuQ>UtA zl#oV3LscYc4GbHFWQby&{fJg#e+2Tv7Aug&8cs_ddQs?eMg(Q~97O>AnjwiGHG!)u zAoZN7hq&b+h%3yEb+fl(XKiS9VHJi3u>|P=YlWD?3tvT7rJRyHIx-3DgW}l#52=_OlN&w(nzy z1`Qyv@>(BOC^+b#8B;p|1e-9zb4gDTlqaB(8;ObW6Ql2fVi6KP9*aSNBr8h}nApIV zAq3VzELUdKNlLMl>%&HCaaE}zkqFJET9ep!WjJC9TzB?N-)AH>7Kx3Ddn&IRmAE1a z>PjKruoJ=AV=0scqKE_eGneUfn6=UljXk@=x+w#8?GNqC9{$*}6DVN~+#IgI{e)Jb zol^Sj0vVm1%-Wd^1*NkW$=y)_BQofPbPwS(pk@UDF7E1QSERtZnz%$iO8UlWqbFZwL;<+ z(>tryQaU=hf*}gdsvq>0IHo43BoMumd0)!jQMiYo2A<8299b104DXsyl;Ykm&&C4m zXwNeK-E%~Y@xkXtfzLK^4B4NnOU843vRAshc7d=~bmGe!MvEJ2?1dvS1CMBw>2N_Z zJj;DRuAqA5{MWFJQhdv&$Dtoj1klhYtYcBJgQ3JMSzhli;?N-FPuw1PXziMWp-i@) zbVlXoLfrc5A-Xg+g410E+pK{QwlHvbXlcbEfQKZ(> zon{f2+Vb*!lxX2_jP;%TUf?W?M2I1$yHm(+;ys-wkkpLWQh6+noy*Us36o9Vj=JNx z$3Y=}_9#HH=4@OlNvtLbySt7GKDRW8Z!<`t-=H7*)W{wxDiZgD*7_Gjj;5PpsHi8P zK>|Aj7k6sNIbKXeOMMx1I;m74SrXuZ-QVrJNkvgJP9nm zd(KGwGDf!ZsSkY+W)x&`w|zkWnhu!VTCRqrequvzV789C)=|_7ho{3i#@bXxBd0t;5O@hoLJ%j{a+{n6q736+%=dcS_O0 zAZ*~Z)P{i>H)tNy$7U%delv3-Y%o?soDSe2p zc+U2xqYljRdgF(b!J_9?Art4zgO!!S(XmlXvse`=iNPQViSdQe9e|&!3@-W+nI*U_ z5am^hd$aC-zDxwgTzBE;;(|PmPZQ>8&fXFCP>51&FZ5x(ISEQ&NpBNe>f1|MU&{_i?vEFMNMM6h_KZ92U7^Z=szxD@bIZ`-G=ddW;U7kQp# z6b#qr`Ai230%1B>)_U?`*JKIgC|0&YQ<2KSTbupn5&3O+ZGdMmUjbZ z$F(IGK2YyKLtX#m82^Ok${+_8TKIg|5O?*z(%>0jcM`)9AkHjlB^(+HvGE0;W`_h4 z6U4f>S{G>Ow!a!pVkZ|wyD<$m+8g)KalE@Zh?BDv0|{Lhq;=99KUyz17bQBc{>kgV z;s7o00T2i-zoZ-4)l{y-heeLQn?Pb%V3agQ{D3@&1rlT3C_wF0#aG4`l|rkN!`bL< zM~pu@;6epHwd`}T*31a%;Afm@18F3N+XG-Yck#uQPxA!|svB8)P?B<3iS-++41&Rh zVKguRoCpJPRLWpM^yx4gORYHk9^??rVUWdn$4hqwg(9BL6cGPkQD6)nagTQp5{-vR zw{{WWWi^B}^wPNk#=?k)Qbs~^G`T4zI=BC889k0a4W zG;mUolXEt*3}3vDO~;6DYbw1_8vamABk_P8PP1}m<6*TJS$HY}6{#?A|mIJIFHj! z)gE64Bg)D(K4Iw4=MKpJ)*))GIC>X|6(1lauzX1(T;^(~i3f~4yR(CR4!RYt0gS{7 zfqmB}L9Da$;x89ix6EAdCf!iV+vKcVzWfwhceyR0H+`C5SR$_c@;mici@L~e;fQHaPu$l-;JcX+W47OCt1v0dfD3-=lZ{KZM z(UUxq&55p>;Sz6Evgk+resC4XbyxY!lRRSvJF75KcFs(jDw>4goa50WH8fe6H@|x5 zHlDi&!#<+M5(aYcgj`tPa}FD$kGK&|BD76ZYm~R~axq%)5-ScJFm?GHy90gwGWECe-(qUtd;H&Un#|tg&36hOZ7es#?!W+a+-e}3tB()2@uVektSj=)imp~ z0_B-{!6Fi76P=Y!>8nN z@#oPwcZt1cYD;`XJ259AhuQCG)||Uhvks#%#jy4hqFG%MiA==r#!ApIm}{8B+TGCG zSR$k_FsZJFr-E~TvB%_doCXF#zb33?h6BUd3M>I8vWy4LDIG{7&n|7>r@vU@QK;p2Sa@-uwDz7W zrH+?z{U9`UQ0m9|gN_u$LS+R{`7h*$2W(RuYae*z1Iu)Q4=cA@f-Yp(VIZu$IJX?F z|G`<)z5aSbh6vpeR8K#j0#7h5znWB$aF0`Ms9-_=lz!hq!DC7jG+u~OS+?m$fav!3 zJ_{rD=y^9MkbqDZ5nY8eF#mkmHqmM)oUq(rf8j#lbV1u5EVWgQ5-{d zW&rO`#2ld*E=tjs+{AyTRvJp&Yv6TYQ_8TnfpCBg8Wmb!X$cnwov=b6(1HZ6i~FJ- z*X1BvmoAQ;(K>8Xg510Ata;H+MP1{|(?KT%A_(Wm&1fBLY7>zNJ46Kj*1 z1-d>e!4gv4#*@@tOYXJ^JxzflIO)|e*H#^Y;5bGl`1wH&u+-o)V82n2FyD3TL;k7HQh>L@k2PLF* zFIpO=CJ@MeiFUn&h7r^ujlJ?paKyuexP^nf( ziXBY(qxd=a3Qn>hce@fEryF67-AhxJp*+#pL1ZaS_^A6B0%vU$pn56&?v^Lp`)5XT z!ZpVkq$5=6OH(ut=SIbfa|JTf&-p2p%Eox%R5*@K6ddF0g3!|s#1)^i!n9S*}&$o-@eT5+`EJmk#I^-WC>4$*}&!4LqF0W^`wO^o0? ze^KNEqFKI!;bp5d<^`}rLL8Xu-Eau5C7_o@oWEo&3uV(itHfK;yPs3=uyCXA1YkT2 zlb@E##{ROPvErN(v zJ`7RK+V!)_0|j{Ig(yLpZbOEibbh0*T4A^mO7&Q=sN9uQ)7^P#uuD7TKpp5=yrXnz z@61u$>3C7uFOT~z=NyMTNClLh7!HFX>;@WjGMS4A1v5R3pd?KWQ7EvxU-husX8{xg zj zqooD_2r%skSFUH|{(|5^dk9m5xcs_VfAPAwJh%FSuL# zn%Xl~oD(k>iJ>km$p+RYw4pX4TF@ZCV?eP;1iu8QxfiIu`RLs6IZO78Ocqgj;b;$$ z;A(qPfO>4lPNuzHn6h5O7?@Nk(=-ZE=27y{Y07hDWSz#M!Or`y^4_WfPTvi2Fms$=GuVfjB3+nNRbFhj=W2PX0m ztP~usj=h6@1d_O7IEt2r>|DM9`{6BbeJ0^BHSf0Omk9p$W}+||F|;c*=!%{B%>m^Z zbTd)ug>Eq`0&1RdT~qG|2Xg~?aYSNJ7*Zd-x_-9uz^ zH1o&Nk4rc?vQP;-#=-M!7qFKh8}42YsY-kO?MAMEK@!Buo!{@~Bx2B-bJ+~BYXz8? zl1S3)B=t1e%nxUp(MwwUAQ5|NzrVKo*tD%r$;|Mj{IadmX6tduJ{w4ifTYnFV%wDk zIG_cp5-Oy@0(=~(0ur_K6Niq~%_-M9zZ|eVn<3WReOfV`bN$`ZHp6C!Z;~uIkxT^! z@XUtnlxU%^Sj0&rcK{*?SqSY-R9<F=LB zu1b;_B)PDq&Ws2|Te5u#*^lttL~$k9+X=w@zCCBO?%CIo%cWo4$uZse)c;DmB;);P z+f;`-#HS(o989FJyD6y=3x(u|Ap!+f?#$l~isAs@)QG$(2~>?qFx5xBk5g6ZLY_sC zNqM&%H;d|StXDiyT(>@H}i1Yxr{Je!ezsZqxn|CxZ$5AZg_ zCSDCzuBp8=PP44J%*`V2L0?vJPA-Im{Td96a>)y`N{iII2*0Q4<`UDg0+*YVSHx)L z!j`;!#$KnMrjf~UE*UG=TFvrV*dcssm{w#(P)i}IkTGODz&^ET1&Sd6Rf8A4yDVJm zNRIDlS{~hMamn?K_$Sf-`aAD7UG|{gYURxOE!}XJ6jm!lA~F4D0}g`SHEEt=4z194 z@Udg+w0Fo>d_w0EV@10i;kSr=jq$aoLGY;KmprjxXh)WUz8$|JPBT4gfBVWdX@=eLU!A4hMxLQ<9VlzCBmhA`_Iz(8KO^mb#6ZbT=UbfN4qNNS1RX*WAsr z$Hnt)n_$(A1$i@}sHR~o1y(l53Mjv121b_1bg6Nb2enYrzXgddM0 z6fbX`QyLQ2JI_a6e>LTpx;Ah>Z*}th+bw%cfbzziwO{_cV7Hj4N6%W?7+yo35n?a0 z?PJHy|J2=Bt{XsV3*@wjR50 zdn!w@MEGB6j=b^8$tCl{qvDot&962$iv7~2d~Cp70eahoKR&10TBi6j^$}Es8}TAX zMwv;=X?_24a%(7#m%0}vpuxl$0*)N)MM{aGY7~;S!;&K&E9=fu$fHS4fxKCpEzDjU zNyUUH`WHmPb~UvO;25m=&y`bDRyO+sGsBf9j=NGKaHq#R)5Y>Phav| z%FB2^_n7C<3itQZ4c=zm=2^~63qiS(@&aHF(Z7dHoTa8Bj##kU5@JRP7~CDo&AMcA z-0q-V!ZKfDb(!D$CHRGJf1X-h^h=$+f=I~`1(vG?sc&t=*td7s=n+R|M{>HIy8z;a zvik;jURe9A{QS)-if8sPdtKKJZiM*BVaEHZoE9d;BMDBHr3qykAMaRVfUYV01nlII zplk5lqKGc(p~Q7@nePX&t?zp`>#DZppT{3Bct6J_VeOip`17k}?Zs4yWit^8XdLh< zB=$KbGHBc4hS9vHIKgZ2c-?4vHjvpJ(sBrP>5k_%99G|=VS-`S!ppb^dK5y8EQ90P zG}YBZB2$?;ZT{+Ips&aGt+=@DZAaO{1Z%w``|;wWd*P?gMpF4lV04bme6qZk%bqC@ zbJip8M3agL%&pgsriL;H-u1=2b{)*9+Vq>go*UEY zYfKEZ%eNb=G1jevn|57Uwtnj!AuZ-d*6Hs%>O)Sy`(=)G?J)y8;;;6yv!YxG3jkBU zjrS~$Nbwx=Vvg?nrZ=^=?ZZx3u`OQj@!s<;w|!aj!;Hpn49idgk=;8m0bqqI2_3ay z*XLYM%uC)w63~!B441#szQHoZY^9k@L5rJf#ck%8s_WMm6|9if3j8iLl}^%~MuABO z2!dCR(e_#ScTcY~m466AH-_igPOkDyrm{3e*&esH_l_x}i`F~zMbn2?su{%c`@pnWbn0IKwu zC9v(0FtoK}`Z~MYT0#b9vOzLW8R>44SZ2HlYhUSapD!D)k9OV&GN)-)#pZX3ZSyN0 zyhEJiM{rmi_ii^#1Y%%M>r#{IvF`wb=)z7$3t$HW>@C<@H8=ac8_$_ZgB2#0g2M<( z@kreCAE)!%`#J&U9`W?wujC_+_8j+nA7FkqI=N+J1w+(?4{_mUbKA_u)<2_`J?lf9 z3H7d?r??!L0wSnkbE|CQCJm-HH8bI*vPzH+)Z;8UVa`v5G%gB`<-0OMAs__%bdE>o zkM=}}Znu5{Vmt}8wGk~JCe61STMZM3iErm0*QPCvw=a&gFRlE=qfUfI6VxhIuauty zb6b+QGDY%w%XH~#EEx{>A`#UmjpEh-s4ER|Qum>PvD32RW@W%Nn{$2QB#!~30g3KdVwmu11nT0Tse-_A}faT@y= zob5Hdjf%;XSKmaL12PB7B8m92dsiqOOfJ6v<(`UY>{`d%i|3KrY306PC1CTLlfBaN zDeTJYlAz!@!q0)pC1qCi_Kou;VR2O3S^d5X*Y=&fnE5uWZLU8dW4U#%&pEO3?N5I1 z4sHoF?HidNUS@gOCa_v&tGun8 zzZ)8BV7T?TK2BJxFPpnuktO1crQ8?eY%+ey+ZsJlb^Udf;w7G>oO9fQ7r>*(881g8 zoR81Ep!JJM*xuf~mfni1Zf^1mjWc=(3FYi|f&e%|5devT6bVs@s|b(aoWHNWAV@C1 zmM}q=%@9<5wE?}Tiuk-q+m!KYhVvi1XE4u(kHOT z6!ryJ0na+KzKS8=OeSh-iz+FWvO6$AE3Lzu$F8 zBN;wfH+J@i?+1z0q|(?SoaY68ch{L1rt@O@gVwq18KojwegSsq)3J*nsULGMeerT5<6xWs=04JxdJV9H1QLztM5%D4N{~7S3JGsO%3Ch8A4^;EmH4ZHT6UV zdX_DluW*ctX|sollGB>u;0`#v0jI?TgyrEYh{0+k>|eLD7nT0PJ6zmU88>nc)wAo> zM*G}q+B@6KcRuv->GdQ`;u!4&#v^is6XaYb#u1U+Jrtx}ArV~3$J;C4OqGR~(bkwi zpl(7%4+^NqHHH)WUouFXuv(ibOs18Mjb-zXQqG}!vdY@l4^f#@F;VZ&t%lzpYp{n@ z`4K6u+OZ_H^KqnAMfn~#E)m_6)1Vp~bse$B32HE_IgBjDn^Sh&DW(=}WoZdfJR`zM z0aw;`9^U3(pp>$g?;a{^eP5e!V-Pk2ZJT_{JZoPu)zvcHuU`3Xb;EH#;4Zr39tQ(37>1HTw=7JSToH@1-{zqu zokx3Sq+}UlaCiH{Wg@D?p7w6rs{6t0nD#~P9zD9xE!$NZd;rE66w(z>41agO%ju-lMH?HGQ|YAHD0}bGRePT~d!))VT*MY>{!A(7(6tgp#=`xm z)YXY`mM$p|s+A6;{Fu2iUpA>*$y`bnoJ<^>;RitEX;kmkyxeGt+>kZn)nAu1p<(if zYzEoQtrzJwYlT3LJbeiq$P5T_0-bAjtaW*}zlvL!Xl0e^32H)~?vEoh!`mgl%@4d) z&~M|X)wH1#vq?XHiXkRW^j8Q0cZ2rM6Qz0YCX5>{1Sw?I1 zI)F*oJa{vh>gApBT0gOtIWAA!S%FBzbM9Th3K<{dlbi9G^U4f$na`G8Wa{L#$;=rY znxF02Ft1`Bvfn|j2BOZ#rxjZ`c`5i0&Id4wf)_1pYS%e9L?gM%xBrf=`ePTs_lEK?a&@cod4E~)TEfB zl-&!)aiccUyvI?22S|N(iB;A2db#JX0LERjnKrnusLtrg4O!IMbC0%ng@Tmro)LV{ z!SML3^ra

ujF+?dW96s{49Dz2JT_8!y!jE3Ot6K+N!XlJaVQf|%6U;0!M{y9nRy zHAg`1a>`R0ArHE;+|b?uj7ZQu*E0Jvw{ksNURf29@)-RuVd^O$4jhQGD;sZlIJi&% zDL#F6$t{UF_LZg5aK6?>GWlbl7Ye4?%U#_n;+ziHVSCD-Vl=jT9rKiF`TOHf&ux6O zXwrtj1ty4s6Y(CxlO(i2PV=y}xe_VAY+xqJ$2tLi_0^HX7az}UbJFBRN6e{v5^yQu zvm@PS6YmrEj2}CIecYcKD+H~Y7=_nI%OtMbH=kU~7&ou2YuX)<5T5B}hoh6@xO>K1 z{yP7#JQphbln|UZ^(pB>-dIy=S2wVMd4B}TNsa+v5+F;KvMbvaD38|EHgKlGhQ59l zY4S7IUu$1Tu1i!#W0aL~Sm;&DNln?sz!Z7G*fb8Ue0e|l$AP6=H86;YhcZ4^2gj@p z<;$WaHF!GC4Od*5bAQ~}-GgRBt7+q!HnFtjk|b) zPs9P3HWxl%cpz|?6oZYj#t6hb*V01aM5qI%}hpo!;_F?a5*&*TQLZ6VQj zHgT+JH;5FY&_poKpi&{JS9I4e;wsv`klXHv_Bnr>-}i?$2b4qkxCT#y+7Z;Mb#gG3NadIf@% z_jBEui+(G-_4{x!X}@{=fF3w{{*7@(TE<~`+Cr42Qd=}Mt$%rfJp|2OvqEs|M*EvY zaA;g@jV!u?Q^K&5nBD-ad|C%LC24IaLW|LNR~lZZmYylO1%VS9Tg&H_F&T~aH%MT_ z{C+Sg`n+12(wj_%_tUv*ZH0p|6^@tJQVhM?AsR-BL@udqAsp9rZ3sXA?-j-31W0Q$ zsFTL>T(Mppyihg|sQ{(a3F-{L$9I(1GU)cO^kbZ z2Ub{c!t9mQC^ZtuxK0W{sf$FxqCm9@H$Cg3yloGY%xgF7m8zdnO3(J4Au;Q!(R459 zT~iQ3kRx+as&JUJpQsf&Z6#)QDN%F9@B+K000}Je&pX$DB*tb8Mmo|V8exF3Wb=3q z-e1>W6@p>QI6n7K?BuXA#|VDRd2k$p-Q8DUA5yW~;VL&1m;=JK(N1DDf2fW@Vj5d1GXVOT_uLwAtCY^X+P^cPwu#d(P@t#;uM1AR!ob zB?|F{f*AYBy3r(3KUe29IWW@ah}-Cb_YKCwo<<;$0~7BQl2%)yUw_@i!8~)-%?>)A z-XJ=H@mBgbrMz}i*UYG*v+Y3$y29_@)`_QLYAtKZR$%%Py$JS?tkclp(DF-VikVfv1eR-#%ylHS_kC`mNvc>_+>5;{!*vdC5G|-e&GIW6b7xp2>PR zB*}5qZ_GSq{MfX*Ubb#FM7{N!GeMdEHp{l|jSa3Yu9zj6Gi`tJ`#$rsS!hT#TMvJ; zVO~$zboeDx+1`>lZ2YI0ZBE$CG+A#(1^Q{))tR&ABO7z?_n9>7lcvEzp0WK;j_)`B zOw;1Z;QHX}o^12a?Z?d5H!qqd^-=v97tFbv_t%b@6I**?*2+wiiypJ> z-5HZxmo{g%2DjpLwiWGvXn$_{*N5$&U9iebItFUtrHo~wmfuEa=%M{uhR$9|6#^%V&})D138`{&6h9xVGppZKbz<{I@Wgm-|n}3 z2tS@;|FbFY*44jO>;KjM(Up-Aj{VTeA?XwHyOUC(Y`n2*$4^)5r-?uO1aFB=GCgR) zW_E$k|6K9Y#$THn{<14j^|y=J?w20&{GR_S7c{>roDQo9IrZieg>?7qo6zG`mt9*f zVh_IbzOcI--{XkFBeyf1@9p-LNiY8vg|Pe+a3<}VbFu#IA1*M* zBT8?Ga(!QMd4G!+%v9UHDt4z7XJ5ko@FqFzo@>Or-#6srYoA3VZ5sCTAAS1$xy+tk zVpGJw!~!T5!iHY<6=qkzP z%If`}t^-{oJH9%do;-I;Q(nm4zRU6E*yW}p`)&NTUY%RZl&zOe|6})JneqGsZA)4A zH9_1ZRn&KJU;QWIgoji1w*2rfFFdT?;Zb_+#>Z6Af8KO2H$9TR-2AHif$*hOz;lah z!N&q?Grf}E=^GHaQMt=s=#BS8zT`x^Ws1vVet5^z1G}I4q%kYMqa5n~XmIZXo6kl+ zM}85|5*YXsBQ5#U2N(W~GW^csTp{k0_n94C#Mzy4yX~X(E;Ky)_0;xD-y~`?Fxh8^ zd1TCs==`C`e_gbf9Chvt{LzK`cgDcqDnB~;G>7v405L$$zyFQNr#d)byW^7+A`8^y z=Dll1f5jj5p9=!c$yw>i*Wg3k9K#3%as>~EDF{UXK6eg5GzWuyl%#1y@E$a8C(8rI zwEWj9Coq-Xmh%tjo8^5WQ^g|WaDK?EhuYpCIHVBY!-jR0bz?-IT;n#xFzAEs+R%J< zD@pmdpqpuye;(9_gH*WxiIiz=P=-zNC*!e&=RP&*UJOp>E)QI;hDSnb68q;{V}HUX8p3C2lgJ-)WB=L$*HV>RhLD@i~;@~tE}sK`V%|{9r=^0LG9>n1h(Q zWItpO;zfP?^&bw=g5f%qA~)$ zUnF$}0092@iGTnIqlq&mjnp7!aJNsjPig{Z!rfo5EWla=+S$JnVtN|lfrj2#^DWN~ zT5~}6uRNHAkiGUJdBelmi6Zy7Dxs$hVou9n5Nq<0^8ctV)@wCWrs|mWlC|+xQ+hW44BkqZDu_10w} zIFXQ*O1>KD%{(J5T8PIyKq9F^kE)M3k3VO>vSQaq17VNtCdjsn93r0^}kUF62WbeEOzPfd(jXuR&PZ*WT`G4bbY5Yiv9k zkYJl$IisGGX5F_sEAgAmw`^Q>AV`+Da}cSyWP)|;nbi@MerYF_9%r!*KcZjC74{OrK1~B-5rkB%b@UJHwPXF z6#+E`+J1T2(sSAo>+7QDaLgZpV&_Q*iMz4oqxPtMUY>Y|ZSdd!f|)wu;O(N?*#*9! zMswSoXa-tzfIZ*I0VdB;-?W+|)U7d87_zsay`s{o%F$)L@ClbUB!;1x+;udJ ziN1&X;18wrW~`H{>lH;GYbScKdZ^6?RjWU{A-u5FOR~^M+0u??R=`3PkpzQtSAzq$ z6Lyz;(D=875U7^F?KE61(g=WCz@j9eh}Tbv(?6O2$gMtjH5+`*{h()+VS!Ntt*fpk zm=5%7@;@)+ZGemE_;vgx8_<%m%?csvD`C?lzV=T5e$b9I9i#MM{|FQ|v<1Mwv^#(t z-9k7TTg+l7=Pu<%eE$&US;sxz!m#uc{srDVmk65S?z5h!bkvv^8*w6QZ$)kF{Y~@m zN^I6?(1qX5k2e{R{{(3OfZ`x?uHQDWEIL3!)UcS?2m0y}np`hLpug1l5?yeUn?HYt zpAKS+OgCR)A(>$y|F9cW<)W{z^7+eji;0+Oi;0!iqP<;96TKlTfHn&#UTQY#(~j7~uW6eR;Ft<5O`~);1*9 zOa;8HkO|;n@~o7sX*>K$?5)B&>5@A)X!uHDPcuEo?o}UFO?~ zG)+{*+sQTR-r9-Wp?H2FD&C~owS#(WuUIkt-w^isLkF4V)(dg}i<|2Q^REZQUjm%*D``0?er-}6A-(F&lhK%2# zf-cGX+mT|``0jKZNBr+5LVp^c_ox|>Tn5eFgBVAHU9Q2JY|u6nOVp3b6(yFE>THQO zFP-l5XG^X3BA07G(hQB7xd7s2MZuwUth5KPpm&JO*XgR@vM<~gZ#@*%RqqgWJ%o?K zd^|!&1-i87`BMJB-{;kj6QD!Bw+O6vi+@fsD;Md6mxhYT2l*Ef2ChKL@`1 zXAS~TY{ueCE|>?F4hQpPflVt@)E=spA*2t;iTb;@+`mlMp73%nhyOk;xVFXOXBf7w z{N#Fwrj8He^&V_@RtxQ8!J!n7POm0=>nB4wV~FWT<;MqQQ4;Yr*-V@+q)6eyxN1)FE+fGbY2!I~|mydluGk z?oOaW!lw)%S$p?6((TKLGD5mA$#T8ZFik7+T5ScKL(PcyRx*+_=(bwI*HWTe4Ik#a zguvSs`-1MdUPo|A({DdxYcB*aOI>o^_2bc$-b2=P8IoUIC-nb3<_B%LgFQ@0%77;; ze(cF!^3@HI0rXdU&x~7Qom-NvDNc6u$yN^|vGZ3?QhGC`L$NszdaP*x`CSqCM((G0 zAf(+g;jLVjoZwzg%U^d|q!|S^)8_a z88Fawal(x!X`3uPnu6nzxyxGt{^&vJ43&QozJDGx_sX8pc|YhSfhAO;4@{i;@+LXb z0A01Uair^c2&(4$rL~%d)z+H%2Cp-ceB3sr81V)XLLS9Qcq@_fGlS+hF6y-v5Wk42 zjaHIRFy^yik_S#tAH~X@Nhg#h2S^rqf}Lz!WklwCVhUnScbi847bnz}$pN8oMEY(4 zR|F^h)_u*P&q;}}E>=VM`SAJVY(##A$tgJo^=yZ+TfC{JcZTsXCY?!{+PHm*d~B7X zN<@)YjRo6Yf40~3;qZTU#;P3%P$=O&xe{BKxDwsrbS`YwmwemlLo2wqh!(%5b(!RK zTy)!&E6HpGNWqP)AF!~@h^QKx_0}J+8VnY=NZ?Pr;28&>PRGchtm@P}^D*>->cnQmt-*n~UOSAc1Li;>p^0tCjxzOga8hG{%(k zY&`X6!vh+(faS_flT3r|@A!dN*Xf|#CI5)$T!o>hO5#UK?O!*xMEI@Q)-#E z#f1k`q&GZX8%v>pzbs?_#7=!P5X$cEL-6c_Yxk$d*|qjU)JXgd{Y{$r6nE+?m>z1I z4vWV>NSQm?OEk9~de!k!x9hfJ2!-u5DdXUjiCa1v58s z@qAJudz^K@7^R8HHXVa`nfJ~QlmE35!|{9iGaMl6VD;gr?Q@G-mS z5!p{hL|#P-errrib{kqKe$W zF}jA5XJ;hlW)b)Xd=Q>rHi+a;AQV;~LU2+dsDZG2KhOhM3RgxWaCTSD|+7<);5b|<{LwwA05F1#|l!5i{@0-chwkJxn(1H z_10z({@-GoF7Z$KQwG^UZ0Ec^v&!~0p!#(@ZODH$5|D>%7(TrVjCBHM+Qd>phe2Z0 zEJ;?q)b2l$;oqXO+KlXWstR%N>R%)^-@NVgE6-j>ulV#xO(Li*XTs>RSu*-d1%mxv zc>PKAyoOJ2l!acN1o8H55>eO5@ZL+~QjP_%-+|%HpB>O1xjmxT~ph90gW8wNtaB4*t6GMNPm z9`J`P4)DO$`!W30Hn4TwC1h;*;N4i7_6c=1q`Ln^0iDFE6g1~mKGs|dURJL(r3J&D zX96R$CP#G}vZcG>OCe%&Kf4;+O8Xw3(WYFE4fc*!i6W;K|2>G^3552Kup zIYBH=BmY=W*O32jtqt`tgd~-xZ%2J(i=r>h^h+;|QUVckrKr6<%}R$i!;hNH`qRXb z$0u^*Wm{H#=VQu>AH-H6OMxYD38r2fYAoy(CUtVqgBX{cCBE>sEIa+;EYf>rc9^m# zuxk=yY-deqV<|IdJKB7|+(TDci$0qB`k{;EP5va>L_jHMLdIiCyp{5aP+&kId z$T4yzrc+JQy&3u2%WB!)oU4f&8U_-EccOlbVx1A-pNcDnY_uSgEA7n`>8~rtyAfs+ z^@5hW8~G5(wG^KHK1I2{T3#Q#q6ZNELQTGh?F=j5rUKZo@q$ee1&4)XSJc*;1A0 zDl%?1&zVwH=!~wHXw5$_Ks|+kGJ#!He{#yn54=#_0P2Qlby_e9DtZc18Njfn)Z)4FkBUSo=tgt|r~K46ElOhg1{zcca-=@IDom zimt3>77m$*Eq^y!;b9ivx_T&H1MCEU0j(Y45qaEZ{5$`fB`e3?6z`bW3^VOWMzakC zKvr**i`*EObMNCB^`SXuYc+LfH2@qLAWwEjwD0`?4c70tU+b2TyuUSx(}2EM-t+My zy!T{w6!L?~P+EMUd{}5}G~`6LZX?*^Aj8F&gzz{XnJGx2nr{&+$-QL^SOx&jX`sN4 z3cZBHTGCDuyJ@Upqh(W$bPN$1Me16YF=n8;OxBLfBSoDy*K1ATP(9{WoDO?vGLGDZ z`++8TCyW!QhKWfU&X_RROFA*3726%?iYit%3B>2tkP)bPz@kQMhXd%HgNYJ8dj z#piPNb<$qXz+0b8Zv`xZVY-&gHHM^@MPPnKANizOWUmH#{>^Az$(&)$`LgBf6{U?| z?cZO+28-IdMzp$BM}A8x6ET&%_RHzFcTOSTRZeM+_ueC z;j=k}CStw69Ff4~f@q|aC0`Gj=vl#M=fjfbOI!u_#9m=j@mT&Vp9GFVu- zzT+tM#%TS%u=-6^j=~U=dol0JAk}P;tbSqoDIDhs=a5WgT;H*lts;;q-iH=Z9jDa= zzhy-!I_>$Pp+tXY_N9lfjvloB_iAd!2Am&|D29p8s z)~e`kVs{byUOIAebm7SiQK8-UjONd`^Vj*RgJonx0%2qymYF5tn%E$^rbz@7Oj~uP z!Y{)3YrYwd* zb}C5V?{LH|8U?)~iy;1sr$1)gwuPi?o^5Lmi_tVgXs5Mj?zbcEwHZNbwtPdYiw0U8e z-WEHFo9Lak2mmY!b!o@@HAy6}0*LZ}XmXK}F??%!CX^@1zDD>(JWIwgkjrUs?H+K~p+JUOC$`_6Nc?wD*;JaL zghjUMILv0D7Woh_-gFK2WbAUtK5>iZPy=CIb^tk<&xW8 zNZ$MH{i0x^LF?=BT}by%mbB+J9I^jJ)d6vmi$8(bQ0efCVv;GqE)~9%EEz~j#j3&g z@Dmu|*y|?}Yetym`|-|beFC*4dD44m6JlL@* z%L&4nAUGCvr*S>`vFQ(-9)pPT#{Y*FDg*-0I(PHSu{#J83l%r}02Xog0C95062@oJ z2FELYq#$^CwWY!E2Z z;U3P=W0nG9f5|kx!WstDN*8zvvt&VLNmcTgRE;H+XPrVT7Vtbv`2Cf3$1xB% zG{@s1MoEsKm?HwIK0{Ul6d}&&R?cFW#je{N`Wi~bhEP8SVZh)QOmQfEHu6l5yXutK zMnQx8aXYeJZ#JxVyX9O=tj2Iti@Z?7S2M~6As+QBL#?@(Ow3}ALC|_%so3Hp0gurw zfFi;(8oXc#0g1V_yJMtZ2|k|p{Y$h6yv07xv;C4AS;kQOgLkyQD*_)A97F+6)V!)} znH!nYW6E}i;ja*aj|lg(5HIu-udkpeQ?kJ1h*1_C<n7{cNOoqjFjLT?@yeH*VD-63T+- z1+Ksf<|21l&d-m2{&N1K_X}h;WOQ$Bw2F*N*mR-UAJ zNkV9Nf=gH;D$91c=7gMAs!@|Olz}GsovxLbHbTmjFW>M$K%xlq9iJ9aGuX#mG!SeQ zz%m*W=nu`fjMRMFy8#7RWiG#DJ0*jmg9BUvcs~&M`gVI!JGnXjWjjbS!_Hx<0pjVh zdo}8B)nS0P1fAwRw9y-k{29b(QRH=8y-tl~2YCF~`5h?54UcX`G~LuvbHCebR2fS5 z8aE1UmCD5z3R-D@kw_^0QhtbEYptcASF$7K8qQrTOYz|996VzRahGvCqa2ZEs-$VP za*lQ%-uRodW$nOn9)h(b87ExVFwR;vid!pwi4lER$iY?1US+@L<5l`~gd6y?94j&K zzOzNOg#Tme3d_v`>ldiymRN$H5fTe9z>muQ1qsBvmK7jo(2M0yWO?yvEJ*0bU&J@w zFpII;VjL)HOA?W93)X47Q@)ShPDmx;LBLBC2vB>i>dvccab6E6gZgx-X{^8SIEaJB zvpPHfz=Jl~%oIixsnh#&edq<@vUhWymNc}-l!W!dB1zL8=0(3U4slCKW+NgoAp)p? z2mfJIY11=&ItG4>RFM;FZfl*{q+R&~&kmIt)0UxtHME_5qZtO6@V0Khg zVvr&`!J5s7!?x-p)!Ldj7GiND|7!5UkPCO`STHTsWUxH~DGIkiM|2ykiOYR?J#~fs zcC5vQVQlJ}o3UY_-NMsy!;QIV`~VjuqdsGc#uG2zI@GqLQDoZ>y+0hBer1bN%P z*g*w1rJ94K&6T7wq{PS!IRZqi?oezBO`fKg7GC={!tSeUnJ||sWA=mPktPZN*Sql2 z#UF5-(1j@Ma#qA2l$yb*rLwKYLbtRdzh9MYR>Zbc1>-Id+lwmm;1I~Ly1BZV0br>f%jEY1X*u8BIINy8OrLgk7A@t+Np@*|L)1HZk9;$VV?Sv zgFwKDA7PrrINvgb#(G-5BeGaVZL87K^5ZdMJ*L4$3F#DDV3EEC82>U6W5dmWWAZ)b zmmx`1u&`NXJ|#(yULh6!woKDMYT?DwULlg70@XBzvO6^|SUmW1Z;M$ z{f(4Gx&`d{1P9UUEm*h!06|+W32PS^AlR-bY*ALJRE-W-2-Sj7du~6Nz#QNTW3Lj+Y|g<>RtIOpVXL~sCJ8@31^ z_?mB*j@540FxyE2ryZlzb1N6+Ov4$^C0QlMuwhdZ5SsH%$R=v6Ez)v&--w@!?@y5R2ONuU`9(u2jrPkjqy!Q z6U|L+Zsu?|OP>k{^d3HG4I5Q{UhAxK#7=a|aWuPX!AeRNU&%H(3)GDSXUV}rcnJE( z&X+`=t*EKWp2xSBftW>M6-|$QTX0>r11M5@%F6LB8ut}UmTY;W!r-}Nldoy+?RNLr zmJeAzy4kE_+ro`J!~zZ_IiPM*phZPWb-Ge0^}JeC6dKnQcx9q%OhIup;nX zP=hK`cj;;ymmOlC!kc3Mxaq&mJ#`T-K}t$>sX9gMOpeg!cCq*Q&mX3hz)F|2~d4{K{Wo8gsaN) zV<2dexaQa~043eKR z?q_?Vj!%Bsf_OLT`srWYH~pCEDWk8GIOsWaEg+{AzJG;Cn0>@u}q#7f@oRqnZMvR$g%?(Jg*D9(9(CoQ_^|4@S# zVxlikf7e~3#x5Pz&N_0~N5|w>*fILd;%2pKseSnZSR^rhLZBflQPmUZH~^~hd~p!& zdh!A9LKMZCQX)=#=1BL7Onw-~R=y5=N)}b0G_kI#wz_;5-2;Bdvap;Q8@N_9N_B$2 z3oZaFU;u@F7tTkk39`Q^)`jiYBzOR%;}sKXZF;iN44}`t@`MTrn&^rZ8?*T_MkwRX z4zis@Y2BWVVuJKw4M16&V&t_`BmmM+AF}ph6IgmSrdF5_dn@1?dJ^!ATV^?0XLsbd zM*K??{1}F3k7CJ7J>a?#|B){z7}V=XQDr)W1{oG7psMouYNmXC1plqD%RUa5w5!IE z%pVL7<}?&jf&h+C-u13umv|@yd?g=h}Wau-YhYNI0One|4R^b*<3Lhi;2r6DHM8O znJN7ao`6lUgB_W|>H}@~SLK#_^p-$&P{EG6CGN!703HUjpM;BOm{SP*%S#*4c^X+B zapa_lZKZ~91}W>p2#o@4;+k21dmmf>@}UkM&XZRVO@as>hSEBEx?-6MTHQ8B`xY9? zgZ_9Jzyd1@Gag2JArt9%&dq(wFMYRWP9#Tj zKWT2F%(H5^!vd^4-A1Up@)*w22r??@M}T>oS#>nMykxTWZa0b&b&~QTB?fUGNfW-6 z!-8x*>CdbzmjnLxV?FA^#R`)w10kE?mBG*ZSED;rHj^j*1yJc~4z;nPjc2FtOdih{ zm3HjQsKl8xmo5E0Nr4|&2O@+29y2!T?E$9$(X zQGKiqMcDUP$zEL7yTK1#Hrjhbg>>)OFXoO#ZE;JAea^aX;#|(lsn7{JBP#I6ZWNMl z8uaqJ@zkkZa02s4mtyiYi16F`?)1%llDaGaG1}dUUf=XwzorfS73oOamMKXZTJ(Ue z`D--gDvtV+FVTv&hi%Va^YB+BygxL2wqU>r@O*rg@yxhC_SX0Ti1O;xW99FOe&p6t z`sFiPn}oeq-YDQCqkM4b7$@OQuW2?hRQF-ViC{m=D-R7pQEd;y0mz9Mk}=h`tsAp3 zYg}A{J6fy0C1-HTnLNdpah3j5wGLR_fJ{(+rv^6g5<*{UOpayy)!#zXP>%pF#}!Rh zL%(m}2wh!|=h50(KdDr{$n}0E4T7t*)I%Qy-is&%V8`cJ5G~$Gl%X?|2G4?gLpxPa z(UFq(&y!N=G{kK5RDOtJ$M{w;5p_bdaTPrJzyn{q^9JcwfSlr@0Na?8tBAw*pnZl- zrx>_!imK*8|Bc{RH3#t##B$g>HZtO$$xa@IvhGTjp14K9j`b!7oqf>eeO&ZMQc32J z;Vw<#KTb?(l60jxPllGr7zn~<>CPA81<#`nAb4!K4|+CH@4r^89q!t)F|}ZtI-KB! zm(XoK2zsbC;#Dere5L9nnR#Yv0jL~%0s*3#v18Kb@>3jWK5k1!v-N+2yzQ{=k$2tn zb$WV8&pu6ZxKwYbctiJGpxoI5KTIH`Q;*KTEszYTG{$FUHOm`N+&bn~l>b8mLjfU# z1nTjWl!h5P6D4k~%|MAK0uIuC)UY1sH`*kk_B@7xq3DAY13(hl(Tt_6_F*yxX=f^A zrb$1?3mpHpYrq}k{3zb{w#L}55>zrh*LOS8)VgT{A2GH_zaVzC2L`6x^;9bMjd4pS zgp(FRNN`>Ty5X{nlJ#=~(iEKCYxb`UVYvzB)`q85QLwS#4d5(X&2=BBdKeD&PsV&X&dO*lhd{z`M`V0sDlP z(@$L;fWCXrD0tE7n|5DgU_Viby|4!9OZGC5hOk0Jm4>e;4coOCOIBdOf&IX=U&tOa zOXM4Nk%mfO*;L1J!1|M%Fq9uzDb_e$ni%UOl|hlK=t(#$`2{9@h`ce7*+hW^Z~I-y zkt))+($j2IeRkU`Z0$9>$e4mqfe45#j(79cMU0JluOY>LjwRi$8^dx_0~zoiqSmms zGegSA68myL8mC5M>WXoQ1CJx&`FSG>GR)LVtAy#yqdM9H-P&-)$hHQnzfbqgVJwoT zoO2duT=c12n;P)S^HQ_E&X3_JC%WaZg`Om+GaL#LV5hQx$#(2snsTSSt00e}4hy>d zyzFE}X4s=!u($~hKVwzsH4*OK$h51p6$}jM@dWmp!Y2|2(g8&@2zi2EMtS$U9lP%Q zau^XJ&fbt`f}6zvdv;1)eg-%4^rj{FwnC#;2*KGwoRvaJ(zO`juW*6qY%gQv{}4%Z z8BhWc0sI)9WQ24E?TffLwA4^qh+sKcZys0lwUWj{5*&L_nZCmW;?4{}u`qJm%yDgx%>XySEh%9B`B9_l3-Cfn?L-g@*`5u4`D;5fk{F6v z*f+CMdB9$3quf;&r@}#&L+J^NVBZ#%@V_>l+iE=@X9q5!m_DH0BreZFFdEFAAU0LA zLmn9Wm5FN3Zm04`UyYPixx||fv~&FxLQXoptyx!DJ~7IwK?P9X$QEFmVMZNTKw&Z- z)}e+W42bKtR;vI6k2)$#fiFN3V8&Y6lU)yyAYXdeqQ;u0p_unl4u5kOs* zg&3_h06Wyxtsa5OBVt29xgzmQ`6IPM!Gj0i4(#)yqv7ErBod+v(P`6d$sFL7K%8Z4GDX%3YDxb!>4>Mo1?#+;m}mT`&0uiP6OaE`*{dg)hinuR@yPko z(x&;N;)t<>S~CF){*FZy9O1)DmBydE1WwqQs}O3VZrOVuv()KYj1)ShKQi#lc?^lm zWE1|C0D7*C-Y<&{0hA@HJ?MBs*C9fj;I@vY#metWlL{tAHGoh$&QOCA%0>>Z+Edo+ zMD6l-h3Ki%s_f-SRNL*!!jyZlYLN#;4N3#M&)v+v8IrdDYDJ{fnL}UJd4t1&=)2h#?C*yca^`&n1ia|ew?c~pGd_up$eLTR*8 zFu*RwaU4_F71W&o%TQ2gWi!9;_WhiM(LpP_&Y?mwftLOKpyo+{iD7UOxW~ei$h)u# z<@#AguC~3(JqZ~OqM{90&nVz*ts{OcXlk%zvl{g>+Z{HOiEers3fzD+*~t+CtBIdq z-(~hK@GHLAW@=t=>HD|O7~rj?R{BrOinQ1ZUO$#*(X5tl#8qjLd;&gjn@V-S%22|w zNCB@jTa{7uv3h{%;4B9qDD+Qbd$KL{8#c?_*bZ4{?_pKxL}U8os@3 zhP18BmHJ+zo}xS5N~{P62&j+7a~<3B3EHyd2*GvBg7}bC;fO>4t|H-GYgwI#&e(~1$|W3kV?OZtw|H|N-@+8NM6 zsy`j}eO9|eHN~C7V28qw7tHfJf5XlupiGI~)`5A`FJ?c?SVJ)Mh87!}vfs`N&$x6A z3%j&ST($!Gw2U3prexH05{|i7xL7LHhibmU0a`~UZH;@L*#R)A zh&^ambyC(xjeeHfH@d^X`TE`v8A*ho7J?+fQdX*437)>FmdYcLT1)F#_Mb#q zP4fU%|Mf*X<11qsmyRt)DGgFAQI^&@v7o+WSv>4%(|EOsy4hFKJkY8uPZ$VgIJbt1 zacp_tj7Kh0B!jsLvkhR>bQW^k`C2llp&1Y*AW$nOE7V@>mt=VW!Ge7$9=T}20=J3L z2+!^0icqC??3%OP?WjsplmJPDYv2OW_V0saLU#%#!Wf%u#F9{uq#Bj`vu2tjM4KrhYC9v4~TEylOBh{*&wBfIXJo zPk@wG3vZ2a2~;dnbiTdGdJdye1deS<%TFp^J9AkL!lbAAp(If4iQtUAYu@q-q1C2} z-~-)b%+-^&gU>1poRxF2>f6mF;@01%zdqFK^nB_^F*z1tXO+MzE-plOYHgRn~lCV@U|gMY^sTL8Nz30FLm?HGTD^t!%8I z_o~#a@r;8DO$!#&c$eHs1h0`2zwVlO<`@i^=~z1^$OMupU!MGCCx#R033(Nfj~4J1e+=lnnT5bQ)b$2 zNDJT`n`GJoC1|r4nZ;{XuV6d!rkMN(>P}3MLsRnp-HaA_DTe>i>@`#wO%Q96YEnjmGJN*7q9et=w zUjhLBKhsXI2@B^{SR9+Ut}|~XVQNC;ti<+5c4EwmB*n6_^sv`xmZ!Rm`Zd$k@T)X4 zHb?|+OS4YWYMN*b0x{OFCsu0uo$ENWc;@8OvqGXCdg<+JYYcW*QLwyQ5q>0jJ@6^N zVZ|{jLn_@WyBTiE{^UT!#lEP2b*=onjt%s`Rsh%%r=MT^-wVKkoALZfq^}_xR1v^T z&K%M&xV#u?7h_0u_QDItbH{jrB+AM#c#?Ht43bK-dKr>6g)0~ol%(gNviAvr2c36P zFh|{-@V`ex;4E$q<-Qtk^3#7(H+pn;qtIz zxoWI2rT{#LRKN(pl-91*{o`jJ0Apmpt+e6kl(qnjl>R{04sLxN97Kf|`-SzU_1}qp z;d8ZWP4LGdwqgec&H3YEYCU*@WNOfamgUfyq;7Br6=+^*bt~g$iSy!|H}qf!MY)8o zd#9Wl{gz|O)i>n<5~)v?0PNSNiXH$9ZsRX3+q@RXu>ciQMJWK-XJ9d{@@#^_%@p<_ z(NWOZF^UAta>a8pq#INM$7A`A(Bn2^+ki~0T_(&IGpvJ<1 zs3AUB|LUBI{j0>k?W0=Li61UkOY&fZpf;x~&KrhsI6o7|v_2DI47me~b6o=q5d4=0 zl8Vbl`u{GLsc$`NuK^SuK=fW?N}>s$tBbsc8Jo)E2yr+WZcsV58MNfV>rY+Viqg$A1kEe<8;jA0wYHfGQx<#b+o!zny&Wv1OO|(NL75nvEqw11_|C zRR*zeC4r|PN(2C7464+jzhKoP8CknN6XK2{hn+}9_L38)dz5v*^MDm)a~^dJVBVW) z#24T0^=tJ`+Hg~Fdpj8e^>qlQpeg;|LBxbrS}Q*VXrbu9rh$O)yP>#T8mKKx8l)5! zm4`+0g}xLRG>TZ`w&hAi!R3lBKIJl&9B`<^52<^hEsj3$I%U0LH#F`S}H zhNFzmj}snC8#q*>IfTu9sw}?Tir+nXrm-KS`(`jJv0+6Hhs&E^mk@5M*2@yph!G=V z?0^aA_uNT)6hG`lwur~s3@g`57T=FUrAfUnZ=uhUOJdXp`{gCFwV^)6^R=YFI3%~e z9Z;e38k$p(ia6a9%X8zgUq;n80jP5glu}JKG@5F^4FvB9KI&Zx?YB`@#>f_a06x-O zwIRqhP)F!!P8iAv4O}$l`TY5X_)n3&cuzN0qL4~fQ20Wn8I!;xd_zEyL>acCi4lDP zcsf?xE8S$eEULO}W5~3HFl|}jfu7nv(h_5mO`)#q4RV+dzx$k+Xy=%nRJ`8W4P2S2 zvuTy7740LJE^7NtOYDyC{p4QkhA1LUBD5(?3zwj!*(WtF`I50PuUxZgEEVU2PtV$< zk?mJD8$(QuBEws!h#E44*1U~pGhc&!X}lo&!&_|6w`cMY?oR5?_Oe+?_mdnxN37X~ld%EPtRM%*;Xzy95zH0hvWv zV|UCi_Vo##F?V!j_?aw-5yX&3ARYh%bFJdwuTo3Am570&@oxWp4iFla>X<3U@Dv%k zWu`GuEgZH;Qfab?FUfPO_G3Sq1hMmV!nzW4jy9Hrtp^c+2|rCqDStvIhp-L{fCip1 z1l*$0*SYb-^w(}sLapU08Cb1q%Y`U#VLJimh-^AT_i-l2Hu^U(^BT z0*@s@+U2QwT>}f7lgy;+o;f)+T40^MDg1q#Cvyr1D7X86nI3O=3)+~A{iU_1%Rq`7 zoe^HB%!glQObMCAqCKCQ>R|NBc2}FouU3&O$aHaR(`@)CuuDn`&i;iq3VSG*f_#Xk z7~WQ0V=L;g5!As%O{qO0=p#FSIz1t$Id#u{#4sCU zj1_R$Z&HS+G?3yFac=JBlB*wc>m>OTi#F5vqn@~Q(U=;0+8QR60@VPTCmr)CEW>2& z-|T|)D`6ciS^8S~X1&yz*iUQ%Xn_(oNyV~VQW4yfVk6a8l)!NyQ-0sIaMil%rJMm_ zu@6#}bzt>OyK{iS8=ZJNCy z6;v?T9UVM79?jq&-&Bdx4)HNhfiX1O%#TeJyt_gB$PPD56r#<{6H~qQ==5h;n1H<( z^>oT5!6p7;iebR0phS>5vgRGWuYL~<6%tFc4FS-i6mg(r-xZ6J}C%@?YIyHMR?FaYJ6&?eIMj>4i2O zb4WA$bj@mI7DlEi>kgl|prlg$G(RtVnw=l@d7134h)o?U`JPWr%Zmx?kn(JaQ*~^6VfmqFWP1OQNq#_`Y1*0l%Z+0loFj%CJgF=2xRBlTO@} z!iJ~Uw!7s_X3B=N@}KR~+N73IK9dL{&Y+T+8KN|obHFuLK0e&*t$l<2dRs8fnYx(O zexq+I3t(;3A}d=KiIaoj@H4uuo^EnK&Q%x}qx`Y>Q_Cb|aNb)EpO0L4bo30J=Z)?K zT`_ewzOFslA5oq)KZ1SIiRjYd@JZnw!YL^-OoGW~O?xhi0GgH5kV}=#YB8hL4_zRy zD<=W0@#%sDSJJVF_T=&RU}*Y94bsJp=)MSA5EbDS=;L7p2RkBuq*dR$EDisdUU^b< zBgrkNpt<{M_-IlsnB#0TZ|5{7VhYK8>e`Qkq}%HMKblsdEFZ4(Cw;GNJ0uP)`Nm_` z6K63V^)10gJCi8jZjPbzVL#CRc`bzYI8IlTdNO%-@7F9Rj-F?~bE-}8buh_g8tak; z;ET-=$T^c?0UFhQxXuZRk|2+lk9mBg?~<3@2rJbVgqXp=%$f_3Q2HAX1G+S=xsy3 zz|!WN@ZXK0oCNi4fhw0R1!$rEV#GVF@a;BK{+4~vD2kMGWHM`b0-ZZ`KFQVcLMv4I znEE<)X}2}H;L2Q)sDh|k5%=RJHi4vZ4yoXk-t*T(`e!(XmJpUDS1Id1)vBvLLYSNk z+CbmKe&(2Lq2cS<24hk8O z7(APXV*^pTT`&J^k4z4p;HM3o6Id3+xapN~`4-ewA5n4$jy219^ zR8FwUL~~%FA63+?EoH>cSprR$lNhA;e>8nC8023Gf|BuOj^SwK;__e*1aU@5>dVBb z3d8cOZvjPm--PX6GE47QOUp@c7Ps5##ND-*7EZW~z8j;^yBFGV+dDh`XT-=e^!`9f8;4SLVp81Tq^N`DD|bCH z!}?yyzs%I*RMkYtEJ5%6zZ zZl~C8`#t9}{0lejJe0J8D|*N{@1C;U&;+{d%;Vw#L-44n`M$th(^Z>I;BTcBQdGXs zu%+GquO4|dpv#c37Vz&DAbXq2$=w%4LVo_z{Qn^ND z99ELe?7^>>=%@=CcHO{BS4NdY2i$~vl5iXBFaUuV*pn-~2F9nHa`^m*J1(4&-9y4Q z9j^pH2Mq=1Bg@#oP9=Fyu!6Gn0RX!~yRgSPmZ(}D!8&ac9)cI1_rj7C*(GwfG4e>J zl%joL#E!Ith|(t7w*wo)73e3AUg4SpV#%uH=BWt#hd@s>c}37AG*NDJC~R?rNO@N; zlOX^}w65D%x$wL6$znOdyji|;+_Kh)_; zZcW^k*k))6RIf$ge>qV-h8-WpIvxRbAQ%#PFKyiCzn~I#M||bI;jqisn$z(B)ehkV zv{swY|vEDmxQ>oi`V^a%??%Xzx;X!6g+crS; zH|hxQ6{y(Fg30CsCQX*(Kk4ZSSUnY_fKX+lDTnm}H{QL%K@We14v5A}FZj-weB)y7 znxqh5Rfr8}8%hLWrbu>xt7dv_T;+@5yTm1quoo<`HIe$+Ujf}ZygCA%uDQR|{tA?N z&0-pVyUcI^E}!W*QfoIB57>LpmCDRG5-6A2{k-h*JTBC6s#e~96*(-?7$1S$R)X>G z_NBubVHB=b*sBl`r@~Q_?Ny@%bC)^5@Bbf)GszC2B`uu76+V~oozX~ujq>ltoCEUQ zN-XUry`XL6gbK}(@scu-bZJs;P%^*%@F?e;e9R!isv3SX4}rQ;HnZ_+8+OF|3>M z!%HmNTO0KkWF0pQV3|6AzGlN40mli!vo91}i-c!%7JqYUPc@1dSPI!ZVtrpHp(H^IXfflU&YGDM-|Ek-%t{yrUS zE)5Swk*7;W+hx?axAfAMV2BwsfZBK*8UCW0`fz78b5tb z(RBJs&*m<35jL#Z{Y-!f#XN9TiEM8bz^`6J2~66l zNQD@`$pgAvle0p98V_X0j*sI6w;QV=m7Qjl*gJ5~ajA2?$aj^TzI4U%mIH&1x|4p6 zdRM!=&9e=bpC2DHt|?C??TQe4wNLi`OI3T(FFs;gm7fAL0YV>+E zB>5khKldg>S8`&}w_qH#3Pf>K32x$i!at164v7IPA_DNhyiL`OyB%P5R)OEtP4-~j zLdBCx;gd1nug9pN8F{V;hL8cC)@Y;J?Opn#acFiYY5POyrZ7NT4U!l;^(U8>3n_>p z>{C^UE@UH36Ih6K5?-jCsf(!|8Uc0fu?gI*QPx?^5}c@E+w;=tPa!W;aD|Uj0vr2M z8UqO4y|4==0fdw*qKju%WZ>D?Sy8h_j+5$hI0y*@@-p)9NrA#f?F;Ic1rct*bmSz#%$j@AIgyG! zjPx-zWE!i&PaQgamZ1TT=@x*`KL(3uC$%y&s<|~XVBQ~)b5P0Z z#NCo-INE6x`~U(tt^v4vWv2IUA6*iM%~L8s+6&yqod`@ucf#1*ZR~ZiC+QOD?IpbQ zZFiwqCE4eB`8}hP%Q7-chiTIW1uWj#n~;CpRcny@tYhqZb0)4aVJoEiD%e6I?Ij`^n0pb6DUEXg%8mwSKN4aAydx;%A;)9k^II|b^S9i zb;*YkXbwM5Y@bPBoTz>6IID#HL-bJYK0lzFlhjlNDEs^=`8!^@W)~`8UFfF%q}ET~ z+P;R|Nk#(o8}t;E)7EMfkWK`QINY`2=*s5<6p#Q?+%tv zVH;TZnKj6AU9b%ur8+8W{$4m_;rs+v6W%_)qA^h6 z%))M}`<5D=x?W>j7?Td28^-)UZx2vkR~q?XuT(i|KH%@|XTpw>6$S45V}A#LX&aPW z;>Vgf?KI%J8ewFLmWaI-Ro$ZZr_PuUL(i*#p|n8^IDZI~^nLdpEDglOT%B*aB{?L( zl^;)E4!bf;TVnG}&)@HhNoK8fMC^0V;%N%rj64bY*+sYvsH~mn6!X4sxycZZyBrv_ z_z6VVJ$r2B_p2@@pz%5?0IH*e%xxLgX8;I=P8Bm+!C>Vrtt#&$(2@-O0HmgRN15ZA zdfium%j+g#@oIWFStXk(F0qY47Il4^YXtdq)D`x}2V>`7b0^RebE?2T6Tdxu`R_ZH zuvHY822B}ZeaCwS!9gb~QZ>5qk(QwvV5fYkUU<4h7r1s%@aU^NW>vY`;bLt_FZDxR zqgA|a-_hgwFDcb;>Q=ndjq~oWE!k5y6CZGr(4oU=smm)Gmh4ey5MwAX)E8&!sE3H&7&cKE^CGJ=vSCC_HeOva#$0a)QL00QJDH}&^uwiPB_sG z!?-iA>{mc~l~eqZeLmR-$7KXp-MzEg`q?fhW}SC|T1|GtD_o!kFQ%F2{A3ftv6CPj z5t+3eQ#$L#N?;;@*j^%qT4UyWEXHrxl9Z?A(x*O9&->{UoP)OLLd77x3n8(z_i@t| zJSFxh`u7>-6X5TPlrDnh?%!9JTzN5^PMuSnh}Uz@nfQ}J1}hDP1mjD#q&3Ya1&#k^ zJ=y5mdfcurKIitg0HVpVlyH0;M1eTlgXE2S<@7flnsExo}Ek5xawC6r>`nZ8$TjseYPACoT;|e$N6}n z%ui81!>$_)0Y_~b0emg_K+heZ!R5fQL<7}6*&(wIq37SMyc#{|Ti-)~Bnw8g z$+skb57lVVrD;-aHsa_A)}UA83(LPkZ}0$vC!9 zfmFrKyz;-ry=s<2Vs;JO8S9%0wN=`Vh86HO`&X4xI z*MfY_8v1uz7Plkxh%f*xdMZmol~{9ZiH*MEo4Y>wFOVW_I$U>Q0mY zrE4_6em@awgU={GQgBs7M`W+#Hk?A2jtj?YojGG~|Eh2vLLfX!cF&UP$>eoqB{h2^ zSI&CHQt<5H^(+7Uy+8S9|Nm6etQ@Q+E~VP1dEEuv)c8~3PQUMOj#vj}{=8 z=cZ5T03#*;%ez<8<$FM~(T%>9TZye^1%)(-id6Uu_?r4+5GTSK-y6*Xm)O?@+&L~= z(2*NrzJ^_JH*RCP$HS6LT6iM8!qbHM@?^67=13H8ce*5lF@Dbr%@l?Xt{-Anbg7}wI@X*Si#Q$ zgF&mlk{rZkICfAcdy^2WJ-ZV$*~4>1q^^%OFz25$9*{jE0}Iq#l&sln2WLVmnN-B? z9BU`1C^9jodB1X$ybQG$f+f;A5WS^wX@zZ5eZ+Jgva=D(C!v7`iv#pr-vHO-Eo*J| zU+5{->h}qbQ!r=+rH@LD^y5zbjjSwG`=wX)5gLKHQ_{xFEV-azQ*9Zoe-vIQAua`mz1QD+kR_`P2 zg*0!rwA!DgT++C_;j5bWVSr%46?Np%;=-iqhnn@z73?`nHt<;b0j-c|VzLZrnIzR*YTQ>K@CP?r)OQ^xzwg^I@Yab7(q zlxzidg6_vtgd9iUc$!3Q)AZM8J`z$*vQ~DQai=kM-;2(uit&>WGLOwf;1daa6g1(Q zu=tI!qI&OaHb>6nEZFZQ>5vI3O}4k}>b%!c@1Hwx-+lJpuN8|fEaki)Se|<7@?>yR0;o04 z&@jYy@s#SiG!4# zYU9ldvA_4$t}r|P^t*OKOz`PmDC}{Ydx+#-<2Jr?WH-lb25zvFPOx9C*`gRQ%+jpC zJdZFMT~a|KZVpdicwm_1I{x8NJ3C^yx9Cl_M$ucC31NvGo19~{elkU z+7Mt*W{uA5S0-kqS3J!XCeOUnl^T}xj9b&Skr9dUTo7ZlbVyUmxUkatq`aI_qU`Tn zRmbDwY}GY0-mxC`gHM0=`0KUT79%ad24vJuMFtOd?=tmPe*y zoO=zywGw<23ZH2g?1=#aPxD@?%mAmHPKSlv737N{Xzu36%2~<0!#o5vn2uhees_;U zbt&O+jc45iFQ#tNA+RG=mOefL?2_#wNv6a0BV`ec68>1*oQ>E|a5$ZKM~mEDtlzBZ zv5ZJOi&c8CgJk<(WPzry{BV-j5H-y*9LL-j?>L}iZj+B!t_2IHWrDOL1@2@jd+Ii@ zrOd~@|JRSAgS$E-7l{b8V$E55WEcmA0PCg|Ikt)=;^Ei|k$uC_=li`R~-sd%52e&QXnLtx)_W>doidJJ!R zrVy7M#tQ&i?ga4kT84tO8{|LkEQ333d`)$nB|c9nk(7-Je{WPw?_u};C++ZT1s%cY z>A4As!;(Y4KbN2~hcTVF$k2x<6HeDgxqXUG#*2tyd~w&64*QnNyD=a`S7EPQh#3Zq zBKSN^nre2ju6p-Z%XjV*Sz>WC$e5hT_Xy8| zd-zx+VGq-8Nqj(#a>4i>fE{xcEZ%XlNxA|q@eeoB1uEJRFXqq?0tZP3OyVH5i)@G! z9UUz?>c?a@;~0m7#q&mT(ER!5X4pB(4q~?%%3v8#XeY*?onKGuV6hwzV85Hh_#|p_ z0-A^XniePJdijnsu+gGoHwFoG%%Lj;kLpWku3ag0nX) zk|WCS;g%~{ty#eD^lO;~1Mf@axrz(`G_?>1w+>b;p$n`p3ksw@6?pG-9WaqR*{;MiN{?#qb3ii-1SVCggsYU+ zaL$6iNu^08+3Gm72JM zN4^xZDT)cXf+}8A33mx|>uY#$dvCaBlWP3B$3Al) z%g`~2U;R0@OsYu{Xa=k3wr|b&boIHuohdndyj@qI6FB!?0iIMu7^@@!sc3%IZ{{{r zk4HMvJXvcU77_3+Wm-^P;9P` z%fKu)Z8-k$!BMKTD|QDFhmdxa)d%E2L4b&AckJ#rJ!d8y(Quo)6;(rj9HdRpX%g~Z zATd$W+)8(U-QoZBdVliI|Ng0~@|s7RM?rrdEg|TM{Gwyb8Z;TD&5QBc0000003QEV zV3MK$N$-U6XHu>5+w5%9ZMO<;h)S?^&wLj(!^TGcvqU6>NgzssD2MBUAu<4ffOv(R z8-oM_K-6`+<4TWS9>MBkp}{!5eq)ZdC-h}7UADm*3Q@zY9(f)n(vYyl?`8}_Y)4@X zNkj0n+sZ|hShKCgNnz&-j;=ml=oB!)H@TPyMQ+cPx|N7-*sQoS$>jQrf-Nfed&EVQ zQio)YT5yGHmUP}qZJ!*=#=66qEIj9&&Y^R{eh4Y^2yF$%C;p``&3da+t<-Ztd08VV zWz;T*%$^~Gb_ib^u#M)+Cn`mS(mCb0_a(_HvVP!oK4Z4pAPR(hv1tBGzni91^SI~& z^7slAa;@iK&Zm(r?_UrgRI2Nsc_+AppDflS`1eAhdr8mCf<6tiwC>cN_4MD5Nh^hB z!F7@{fi$Zeo8V?AsFp-|^Hd`6`Lpq>2MNF!#efO(cs1^X{=Mcr@eHm2#w6){rEZ%6 z8;bgf;pwX`|Lw;oD=hWiWS*vr^g6`Iq1lf<21`tfzEa|J8*34`bW>H>Ud!Hvexd^dIcy?(F`L1Njej)l`@Khb{kMI-CCkoBto! z!rApd{P=$yAqRVp|K$3Q{HHr)D@QGje^>Z_N(^uVr~_mH694J{-~69Al>h*Ij{pF4 z^8fhEa{z#j2mkSrcb7SF``R9q6BhvbF{QepLbhD0%<@ z);s`!Xz*XLf9C&D8~9&{_+MR4|Aq~~9$*Ck1LOgY01E)iKg99xp|S&b#FZd&=8OND zB8PP#l2^AD_?9WUCy0viUKpHs$jLeWd-INS+)|6%0`|1V7nB_)D=iDWageg4=@BFn zH=l1$DB`+$3F{9+#Yj0cz`ELSB-~3BoX><#((ahutk}5Zo+T@pYsv~hPsK0EC3>G-? zyr2NayLX_2N3LJ%dy##Z;=zUykr{_Z*zE);Mjq-Ton>8>^XDc7JcWui;MQD^ z>!3Znq%T5OfA&GVo_Ut35l|5@g&=Z`R333+Cgj?4l-Sb&g$F{3ea|k2&%@rkbJ0a~ zlkHpWAr@o7O%MDxAzvP03aI2#EjLrCGVV+wSO@TSFP^1;&%i{($BY=eonV-$rP?nz z5Ox1DA3zhkDYz$Hc2EA@K7-E+^*ba~2)s zFUpEgzd}ggiys9rv44ne@f?yP@M5>AeiUvItzgIn*ydV8B#TinzqF|YRsgM+zyrGG zHV(n>!L1)~zn;U^pcFb`VqBA~y)6=FqF69sXrbNn1Qu%*_z`eZq(XK#z36z}`hrH- z3r$m$Q^8Z`a7{3_Gf~S(sy@7_=ATj>)=UMXZUuaB!oxQxk@}PNmAq@OItITUI=wip z;*j#nY^#NX`I4@sYV5HT>B$HW*&4C-$cLzUY#ML%atd~2TMDJ@int#;-Z0?QziKwC zwT35eq(qyG4OPF#s}bl-VLUpW;u)9%lQLr((tSi;-4(zE-%i{(y-r~(RE^BUkWC)t zzSo-}kdp^&@0k2m@kzl5!i&wY{%rG#IWVn8y_#mi_1AnYX|<8M8)+V~Z(BWT4S$ip zgD>Q$rAcHKiQ3TY*-3sOB=*>JJpp3*}yRhq54`jm& zBcgKgl2_ALm=*evw815CMUwmqVr?$4mdbN25L#mIH_B`w5mAQ-KbSI!d-(x86>}Gd|oeCPE z8K1sCyCp{Ou<^i75Bc?bHPnxbdSoq?ZNoSqa zOT7W61rJr->mEHGl~3#EhR;`uNzKarZ1{p8ywa;@M&SeXS?1=p0(l96Y<6`YIzqAF zU2HVnf~lAXd8YMJTf4G?6H)vw1rc`M{^x>+cV90=o6#6+FH!J|=*NerxzK=uI^29} zG?*WzNDO%qyKOujYg3&JyPZz%GSvT~MISHehF6;SQ`osDXI>(aHQ!A8mdUZrDlnP} z{)cELX~a7C2mgRAHW#+KyGFTso3AJK;o>tTo$^zEpPg90;e|sqZWYU5@p+~|#K)8| zG!weh=Qx(`XBCTJSgL@sJZjNFYL7h-yC^#1tvNZ+-Tx2uO_=e<>qeW%Vzk+UzPbhf zgkNFmM_~Y#sg{!FslRISIN5U(5AUt^hd7>$s*!YLh>pi+DpXSSHSpCptl; z%IM##Y)Y==4hTW6m+;6`!mUiEpd;Po84@G9WHo5yPKBK_RG%^repKc>eC6(JX{Yqb zAQ@gGDs*MLUFW)`tDAh(ED8`iluDc0I+%SjObV;}`WCD{-)7%P44GFwyXR#W7$cmX9g!^e36XBK~GV;XCqn=@3K6 zCp<)Zt{DJ>EWTB+n3nymu&?OKVVb1^TDyPtC>J7OaXS9<3?j`po1Gp8gH@CF4O2k+ zM-z3x?2`lEP)er1F$|$RUJB*|=gu~*Oo-h)LORy)C&VjSSS3Uy0Mvqp7t11id3_X9 z8miQObDl7YlzJSQ9UNcEFxG+R_hxJ5f?8X2`ru$8vq%*)bJ2z>{}3c+Nf`&`NF8+g zcYakSh_1;B^5ZF#L4llPcSh!7vu>wq-1cW8uDpoR54Iphfq2*`|XDTg|6RC{ouZraiMPDDi*WD*Q>SZ{a0b+m+7!zBc+iP_=7F$_BSp#fAl9ekYRvzUP#$f)!waTyUqPrI~W;#ns)duhN8}o33*@`McjYZoptkJ#EMIPW= z#C<9`bA=Dtqq3PGrfHpPN!}(b1fXDT<1f4*c&9S*bn7A8@08%w>|QB!c}_Nz zUy9>s&+lbQMAw_nWtv11Ff)1HoC4o?uxU;diJ8^75vd9fNX=J93&p2k_+1uR!x^{b zUMH+*ci{vhQW@?>=E)Q@jZhCbQ`CJ4L}EUfQ~V5t?1*0nCL2%zp5>9@r8@s23|tjf zn0KadfNE-1CmI)Z^DMRs} z)A{IeV)#|U^aFK^ME)_InBD_uq(TcV14q6Z->A5B8cEhC=%Is^r=wS1CdBIwUQ1H^ z;|QECd~lxq(ePOhzO`H73jJWufMQ3QhUpCq4!WXZO8snYwC7EK)U>ILgu-Y#hwn&BK5qnoDQ-Y&IYBuJbo^@nhf}O7t+%J&={L_w+~je4JJKbt zf1k&my^YBRVGFkM-am(4jXpm3H^#$70M@C zJZhksO3A#~{=E0t$)!)<`LVqWmrLt3Rbzi51uhQcsaToY1Z$R{Ie|>zLR{6^LB&g{-x_?3ZAqe!<`sif2{6)N0fHOJqTZ?dr zm=LFp6dimCDBi}YU6&V$CY2!(OQ}r097p=~9D+pJi4F|{Iy#wODO7Zt*q;{{Won(I?QM2QygB-CF}_C>fkSYGi8)nT`V_b z1n*aJA(rUF2Bx8|g9ed=j&Cvyo|ktNB+c2XiQ1Os9=tnI`UHxlsx>mKrNNi+^p_J@?$jOms(8 zYG$c0cM)C0#`U;apM`B~=e>%G>8qGOY;7QWl1TrHL%VnNJ0MFnYDK07jhEp$RV#G$ zXg=lw&Xv!Hd!yj;40K7H`HX;(-Eh#K!_HJ9TP9yM6t*brv!h7?SFH3TAN)zw(Q5-Z zNx*~D5!Q>rHxWr#oYDZHBrh5z>I0&$oq5uKrxp01z9-YSO%NjcQ%kz3%S4H|@|&o* z^IO2PH(3!r=?ESTtCt0Z;EC{B5$tndqZek+Rn}1^0;f0y z)c}kI!0$9Zygv=5uu*s|jJ<;YjLrZj_ED%@O7x&iWn(7RCvD8yWFADOoa_(y`5Z_A zXxY1%^*ntaQ`|lkjG6rOno@3>B%OIw%=DR!_96XH!T5?OP26*Md*d%I^{pd45`Q@J zU|Kwmis^R1uM;|4p$oQgg^k{UUOHt^L^Bg)wo1Fevf%kF^(9#j<}H(u+qcq95#5_6DlEeY-|m1xgOR^edZu z&dP^A$ZnMV^Ji)t6$oh2qf6q7Rhb_gMtXk+5i#U@uysaqb?A{0aH^Tq;!kG$VaoNh zv=6R_3$7MMb@@J>^?N$a8I$U_n;sQi)1RDw_;_WGH6;1$_KYBM1lykyl#1%>-TJ;6 zo9JvcNO|-7genQRea*!Y8l2S?j2H|a0JEYVb}%4*gBZukwz6=kKqY7A{=tA3_GI;opW1&Qp$6Q->qVho z2eznYR9%@At_4%RC~04^B~IY4X7zIltj)uZbb1A8HpjO`)ZOXd*B;3rHRLIn^&G{n zN_vi`iPE5!~oI1veT6hs`r~g-0XmRMnqr{mj{P;`Z> z1n(wDS>7#xND+dQUYU{KRbH79%uoF~=PzeW=Me#n-1ZJY13V_WKQ>{l zFA6bjr-VQ`&!Ex{PG!Nmx?y|h>?6bRMi@9zM3mN9-3UxbOW-kXbu?xpnF7Iuomkl5 z1sj(aTQv;@Z@4|^K}D!@HNv#M^{0GJS+Yu=uLMlU<@Xn-+-J;Ak)E#f3#c(f9G6W( zP(g(?*;1NON{ud$rJLbw6gvL_%#F(QGrV0TNj>4~t91YA>v-SsxRp9g8YQXPw*8UM z_VZk!j|g8BdqS#lY5u*#h+{TkNLL7>YP?4~WQ6g`4e_CIjYNyi9O-x1q|7`@{79vH zP&0oK2Wrk~f&@R}m*)3f_T~^HTlVNw{3QzsxmUuFcfN2j+9*SstsfgV+TeAp{R#Mo z27iPskWcykO}Z!?oHdu>7qeRa&3{$wu+!7;z+faXrq zufOALQcrKH8knU>c0Y@sJoQW{$f9_lU$iO@UM=%Y>p~O&tl&1AqmeLI(R;dftFU^vB1)JsE1<~N{I%CaD>tm+i_9)+ zGlWX&D~(>86&|c|RIX-W4`?R)*)kBP0J)?tuNu-Rsa|4s6j?7|4cnmE<+|>3yF42r zV-CW4V=Vms?mxow96_U;c%tl)+}OVqO#HP)YW)m0#lsOldau8$FUn#e^S}>y@!A-? zK5I~%gcQ)eH?gBfqrv9YTZa#e1uYjDTZpp0M`8xvJm)R5&yLUH7#|8LoPECei;*bm z-9(jN?f&feu%r9~i7kvN2=|DCdo5*1P31P~5At1CVb&|!p#hH0!Riby7D~W09W`$h{{y){d(Kl6_5BFYEgyTUlr3Y|3+KhAt=2}y4CB|;&9fB@oUuzP zkX>hMbmeO0XC|ri5%#k=h$${RRYpm_zVCdF-0_UHApElE7>aYM|98=$<)Zt*y^O5hgG{z`rfdo?0e$4Qf!&+mwBiI z?UqRZzv?EaEguJd4X#-(xu>8@r&))`%fQS@hSpjLOmFaZJF2JJ6VYnJ??z)>D@>LH zE;?Fd_fVgnEz#&DQbRnAMWgY7gPL~gxrP02Ig}P?xcKxkas5gM<9oMKhCgpfrOed# ze}2kUr&rC0XtGg0LV9sAsPKTlt7tVyVU|VMq2jW~+TC&aO4{Xzc}{L(N=Bk!9G5$C zqpq1~e2!?bGc>V8*8+kYm#+Zian z!^}}j^<)KZt?iW|H(aiN(2U6*_=hn2M{MP#&muhK19!tL!}xMj60>ju02sh(Uq z-VwEw*e{q%!&s51=p6dap-4(=E?dyZXxWT4bxd$VQQM{79hRvqnhER)A0(pP1b?mX zgFA?M9+k1^Zc?e6*QR0GG_e_y7nLU^=4;Yp3I8qP9Ps+hG%Ns>s?*i5TnMjeD@l=ulWCautiU-=}TXdUvQ`<@nt z)ZcjhCq8ubvaE8blD0d%B17JJuvoN?)E`Ji1#OQHuk>h$ZO*9a`~ps$RBx!W4CBfp zG6F4R_pkM8M}&*5`7Nrg`%r7c%U>_ZxDC}iu!TR#4}>DwB3kYdZDlU zMqLZ?6_|ZBD6sLd{y3X>a(fsPXloQz7q#9PIqCp}%Z{2Fgxq*AaBn~>%pxJk#LgN; z(KgE(1>G^9CN0{Qy1CDAI~0$8dl5FoBM&vKD+waSyl~_5)1g0@_gcj6xPE9yv+T6ytp4lbHjZ#m-hHsft8J5q!&`KW(l7-{+ZfYfH@rm zFAt9FH)49}@Px>yBl?={NTT@+BX+TWG`ab?8tn*&1zy!`aM_a4E{Eb}Ef{?1naSEl z0lbC`5}dJFhVmW+=M!x0J`7f8(0m}?S-!?ajp~cKHdIuJrUs_Vx~Qd8y4!R|Z+>`R zIHP81!edWK)oDrD47H-O9~aR+`%L0!P+lr>&6zedWSnY^Od*`Df`@0`$-Y@$NZ2D; zibkH1Gi4?%n1L{#%&&_o+pO?wf*nh7)smLd#I|qL8hp>|MRaPRez5AP7{YDRjdV#b zNswzaNaDyh0pMh}?eu`TuC&><%ZarFSh63+V&y4hG7)&=dlnvGTh~znoy6bfz{5b2 zf{-41upPLBkmLC(hRx8Q6OErRI)g(#FJIX~dk{^vd8GvxK=CH7xFr>J`CVZH;(3;yAWMDc|}i5MXD5L9EO=6qMND00X^BM{acfw`%bL3tn6|c$V4Y< z`hD}o|6Awc7cf&Wv9=z*82vThdry#q!cx%!t_4jYSDQHI3zT=BwsPQxs(2JQl1!GZ zX71^$&zSfD5xOlkiF4XjQ+r3yDI3h~_8*0g7n8C5M*B%U$B()^odzPD`->U1XwKh< z8lK*p7oM!AZY`p*<-U*-Y^4e3n(`MJl;ZZEn85g_JtA|KNd2?Lfw2%h=BaGUnfCNx z1Q2GjBpzF}^WXO+o2*=s4mgLTV-?t7WC^-qNdrC!TVk*+lM+X!R-j&Ay@+w)&PCtL ziiMK(T}O>W+}4ib@WU=o-7cKqIGve5 ztvi&rU>bVg>JHTFnBqc95&bEqV1ntm`Hyy=ql?3A-SfD>VG;VilA-#t;8)h59o$ql zj22!8jMyTZ(LE#(Jo`9P@%TuIr@4T-h~k=l6X)np%-a7cqXKD!@%$Fv+ ze$*R1E)x~AYZUXi^D7)>4%@8#ON(d*zqPbvWGm8SXtk(!qf{bV!9B_9@XIPI*^SEN%6H%v&Vf8D{~%Rv5WzfHOIPss^QIPpGz zvI)(kbt947mr^fy_5aXg$Z6SKKzF90gkA8+zRh7q-&j=r6eU?8HbU&OAiI5LLg`IB zom#{mLq|@QTZ7Ox9sbEu3s`i=`P&7r&(H8M##&-boY?x`p1gHLvFWBcbbHNt&lW|( z<$Czl2a0@&`WlA277aB6|U<4+o zk{G|Q2Dxv+R?lEkARa6S@}2Yb)KU_`6_%s(un+brEr=A0&P0>|ZZZ-OLn-y~c1Cm} zQLH#mLtS^Pf4I_Bl*3ET_B%JI+X(?{JwpY)Djcx0GiOGICi6QBBa)=_4Fr@FO#PP7 zP{(5HhTZjWOaD{7Ff5lbzs>W)=KHPU?>Ip>385J>g()GP87VLo9ls0|mcX9zr7&*7 z(xl#c{w)!OLnli_B*dTp)#>qo2YQ*`Hsa-O_*uCPP#uvSC5P3<0TwMU)h}0S*^z#R zLZtC5p{Gmq830k4>X$9wf*D|b`MQS5W)Gle%b;uyDXrY8pIg7=k6}^^1El(4b_l|7-;C@vF}>;X#7FHBrai08gs)}~ z{UfLgK6C=9wMdFeD}JpMhkVW>Xf?2She`u13rV0OcT zV+Gk8D)F~8Q!cJtaYf3*&EWQ6eue8mC%86u7o_(bE7dG_`l0f0B+;+DDUtB|fl1hb zG8@(@iuw-fA%{N|d3H16@JP^f2a%YL<86lVX^ei*R?b2aO-l-uGI%(agvced8FJk1 zI0Uhl+oHdMLIQG4#POr!oK-AuNjc&&)jU(q9p>&m23~FcHLGrExHcqHM3%#ZVca#L~M#P8T1yhSsIJ9poN$TA0>hc$YcaB<&s5ZDO1by zTQWcuMGic*>=Qd$EnDaB5+~W^`#*eax9S$Xq&+p>#!^OE@z*jZkoqV8ki#$;fkJ2d zDWNd_uo3^`?Lu^o$05uDl1F4z#86r(j`t@pnp|x_=`9bhYLl`$+=XAL2AV-WZI~`| zVW!cMlF4P`?vKPOVemV$v?eOL;lRH1D9W~vqwCmUU`Y7)a3h z(G=&Bb-Smh?i0?Dieia$%Xg3D&6{P(+b&8NBd_8D3KF)k(Pc09Tq)(Xs~x z8_SXl7D)+oL{7TtI9UPJj85L$_z0(f>(gzcHU55nMN_z3JU`(Dui4q5hekcj=I^(# zH>jx++^1U!a0R?hxMm)lU{`E{JMZuI_q5C0LRqByiVCaT)jYPF#Dqc$)Njuw{bxMa z6_IKB)bIrLV!-))rNIK>CB$F9LHJb_1agym*n_b*jcQE_SXi6TD#W6%k14Cb%wJmt zKX(@pS7cvC9m#S|>x6F77m{xled*FPma?9M8nvV)VY5?Ok5xnyt=wtoj!%FvuIQ5& z`c53xrVz2^bl1Zrd@)Qx{V{k9=!c);)?liHM+?mygjoqzgiBwy4&+x?( zX;TDz?x}zZkp0n2N;(Z^*#Jku7zpK9|D{Y`Ixc&83oU6!GJ){TBD0+l(3Q&o-B^=aOUOc)dX_Spsa`XBphP z$-c%)X9*xi4hlz2g8aak_7L*A3hCz>$5rcyUIu? zM{_U1>IIO%;h|%50hcS*q%K=BC3_B1H-1Q`xGGE&Dz&o*!y)Q)cr*L^%D+FfwrK>2 z&xh!!@jxK!y@m9LcR){LH9Rv*msOP$QDW@=7sDzBVxY!e;4+^nXgy-+@mKciI#1z` z)#P6=#*{zem$8!`e7vezEc*HviSMTJdlmY1Dr=s1dTZgsM`Uek_)`X7F`$8S7FhDH z`~$|p_aTeCCsb46Y@^l%7W?iyu}$vckK8un&iKbz?{?irR_W|5PsMcjVaK3TGn+^1 zdL^yCr3N6ZiGV?jyC$ScOm?-3rYfjGQ^RObA6{ZR7Ix=ctk{9(0>W2fGUOeXa}M)54uE4OqK9Xt9fw{6Xc;FYdQTEsY|dsCeUHx z$|<9ziA6=Fj+{4Sk)MD?aCU9-ZS{GBSk|Jfx}sno-X)wJx-qz;=5=oexoPekO)1#K zl6dDK0l-vjNUo?3grbgV#0_(%@I91tpan;=)euvpcw2njA)YIC%pf4sKK7i5TL?30 z{7Q7583DEZAJC9o%Lo_OUakMJqgO7C@Sa=H>Q{+Nv3hQV6#NToy!0GVkd`gBGO#4?uZ7qxiyd!y& zbXNKZT5D3a=Nv*zB$mo*<5s~tyhc9k>YXqIGXiA&2qVqgLOe zKR110AznO0Lp#VEC3cC{+OqWVBJW*wFObd}pddQ@wSeN%D3`{RL> z&1SH`)kYFQ^M#E$K$#L3K$KH;nu3bCoTxb#x1$F?EnN*r4S}W71a%oUW9u~&YY z58^)7!N6z_6-|BCjm=OjK7`Xc@)n?{gs+|3&1_&8U#W*BW7S#@8C3i8a2^1K|N_NAfOqgmfb4Ue6_P2HAb1m>I7h%bL}t2db{H;$eGsb!S<@!>|G zia;hj%lSfYFU@)TSoo!IY`evwF@3@}egHOPH# zc!IvEjXP<*ErHWN-~=KiSrFtSFkQ&i$AD6n75I*Wjkdnmi4Bb%?I^bHBERdvK^AV1Xh;`^Q<4)|>MS8in(VtxBjLIN1Ela;s zZ#h>~ z`XDBIDRv3Z3Wa*!B)1HOzLMRS=j;~9X4^M>E^3s!;+?%KiXGq(cidDQWxR|vW;;9^ z1)^U!QRzF>es(?p2WK6|zwp8nD$JmX`x3~^!+#iGgGhl7!NDqlLvSZpdWf_%(qv1q zG)z8-VlP&Jzt;)%$zmacY@Rm65T{ghKL_>SiZXMRV=m;}cFF+&0$3cRU-I||d0Gl5 zpVBC22RO=*(h6CV1N0Cs=;TrH8mepR(RWkkcUU(3Sv5b$2_?5}m%p+ZJ@=`xd6=^m z;;&vPbO`UY;wp}wi6g{UAUr~``Gpq4E6d5adTS@r(Qx!w>FTZig8PVw-a6}>)7>l? z38pX)NvYZ6>YpbOSm{IC4#WXr3-kgQ;l&BJzvDCb^L!z&oL^&S8!MsVFG=QaFLFTY z__%0;*a$Z1S?N0viR*KX&W%ffgo0|nBETV%c4iJFLXWMzW8P+pAjmy~qy}(n(Rz(k zk#-zEQw38T@T{vjf7>DRILwzzVw(_*w-i0f@cL1F7FhOL5Gz%a=KMvD%jM^TjqW7x zV_%R{Ki3`1EngP8ly39^Xo^jEqRA$8(7KE+-OFTmIHB{Zl2cx&f7*k0C2zoBvjO`1 zPCDyLJSNO28lGasq0xC2tEm9y02073V2uez+Np$|Jt^wlqu zmz=z78myS7LFBiFyvk(^kQ4w-BaKaE5~oQxAA1!>d+6J8aSORaqPKFuO_qNV9TgN{ zf^buX82T3GRLT60(muy)nE9bEF+4M$0^ULebJ-lMAryoNcvHaphYisrMFTJWaX_6D zCO>46f#A$#HpD4O(iSBZ6F5VWq)hLd@4_@Hob~;4t#USz8p2i!K#KIrxj}Bp?d202 z?$TI3wgOKaxGLPIKnq_PGd>(95oWQL(XCAFSF%Ax|8v{;_9_I_89dJ)3S$|IT>t#L z1t8)mj3Evs`W-?_$?Y35S6G;S1% z6?9zW$~Sz@C1BXLYp~jELvI3o#!s!tVnuZ=63Zd(hQ!IivlqC^q>7^TC>|lk39LPuD@XI|O`95h)}fyb9Ia&KNy;Ho-<#(%1Pzy3|QREjsDa zbIHDWPUGA>ok?8r;bC-Se4Ky|Lyc_yU<=Z@sD=s_?yvm{4#ie_Uud~TN_xr$Ikftw zNlZLTk=T&3j#S#|aKw`(awnUUJXOY)FEegj$bw#50nM^=Zk9YJr>nl-*iQ@aHE2 z)8U@|a#0|tx^k6fbj#|{#IJU-*EY>LVZ+C`^-|rRWOMkVez6oirnxmDkvDt8t68V# zdh^$_n2NK87sSvL6bM^IxZ0e)TPPw?;6uYE{5z2`&Mh^t{D|

r;jwNgmJCHIeW9 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℘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


h3O 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
YaOTpKSZ1&&K%9+zKftq7vw_2VW1 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�Ws(NDnxX#|pcbB=-LqO9Jbc!4=&*`oCx^x}Z5wD2 zxtRyTsAZa1!C1zbP-gg}sBZzO*a@=lA@?2nlSHIV_#MmVCK8V=7w>;2vL3FMmQWLYBpaumli zx(Mm-aN+eWb^7lYugstLOtX!>{db>DdY-aT?H>VXFOddTr8RY)eOkk6<^EJLl1nCF zZ!H*e2@F&Lo-)MpGEt4$SrM~F zuKH|^cwb(3OV02bhpwN4m;eE73DKqj!?ur1y{7aI3(8NcHIA%1q1Zz!R{pkU;L>B} z;;sdWSTjw9ThY`a2fmM=QdOW!hj8qqL0z>)oQy%|NnV$2)53Z&1C1LZ|Et`_Jy&{> zV4mn+j}w6MGU^R7D@L|wXx!+Je3GKvZi%r8&d0#5xo@KEw^d2}87(GOS<^DN%w%vK z13iKuQ~99#$xOWzS5z@CvZFCn%C^(hk#FF$&ej65>dzDG4!0=)b-a967XYH?Lo`1^ zPEwTo#FdiyossX2E{V`;)F0?xP3w*o7YVaknOqBIz5M9(L(h5uH$o$FQh|bW8(*=U z%UeLGdiUf_3t&nMJ{WP$Ss2+fq7TlP@7d2<@w`CCx%&?27%)P82c=KR_Hvu`b1yo? zlkTkYBH13F%JaeX{*!!r=~aJ%$eCJNI(P^q?W5Z52>8!aI^nX@{)!LVo!*r`OCiKA z0X{$Mp~&uHf%Z=9odA9|7ea_mhW?)*K66F4r}t+I+1&vPmDG;3b&a{k2H zM@G(8c+HI3gKx=c*$$(5FwO8EbP4569y{UMJT=4aJ643F0t3=DTSWf?brBd<@i0ty zikh`&=>+wG(nqrVv*JkG3{NzE$v%bNNs#$k)1GwJY|Hc;E>dDY7+Zk-`6$hac*QtR z7t^jZN>}K{QYE=f8~t7b%VB&-0ijWAoVWM=FavX#!k;C-b%9 zBfnBy9$*}T^iVij2#V_Zx(n8JrIg3=Qs53YG=_OrnPlMbGPsp4fxV(lj_Yo)IvBrz zzOa<=L^a!JxR8{+4?Op?WXp;<^EA+B!%d}Y$VFYFhwv{#ip4be8f z*Z$%~fzQ7J<3i7~-=)Opja#m=O*2p3ZtHb`3n9gZ0m12D>zliJUR?!U99Y>e5mbt7 zVYdY7>S#|dC>s>r{!4|8c$ z^oT1UmtFYH7Bxpr``1%-kZL}&>lx??}Chk+E^3c*EPIOZQ z;DD1=03^4?Mj=n^riA8l3o$VQ_5mJ9VGEp-UqxpmcT`HtH2Yt3)Vp(~9FmZp#ibfhI^a&;X(*4jAaA_@W|0m-~dKqr~Eu1l+fbD;{Dm9qj6 z=G}U$dSf6XWs}v0Nkf@u=Im9%n5P$F*v!!!j0lfHaP|-ZqrCSeY`+rAM#F7Egg7e}=lkG6!N!GoKZ`VdxXrdQ6)&f_pzVcz+)zH+9iTZ!d}6KhRK2Xvls$^4*55?Pf8yfx6>NcU z6)NGLiVGIp>BE(gUxv8rUKB-qK^FNX4zVE@?IGm_lU@wk3OTYr!sVSbvpX55D~)b_ z5;h=4Gyw)s#IUd#`)#`;?hF<^ot5K^_3H+iR?`dHkg}|YgX{ur(Z!SmZG};B!#@sbnaMp%%)^68l0Qw#76dU zby*@S#5TTmQmrO3_+gJz)a}=3ctvtzsl}@4C8?~HFm~4M0)Uz=2l7{~ok$M&^QjTM z>0mE9&f|5Tn_bXuZXpJsg9Dn%rdrhUx*1n>YmmRSLXhzfWfOZ<^yk|@Hvg?J(-x`4 zTGBm{V?5}*z&=agaEk{3vIW(*aNz&BVYWSv5p~Ls*vZfXkv{{=+K89IYgy zT4!hBZcr_#w-G%2EKark$L+ok60AYt)vi zqe#+pL8U(GZo)~kHKT>D-EJ*yO7xIy!DNs32HMe@iX>9b|9lSCSI z+L_p%)`m=dgVpSO=O!cq6X$$j{tMni$bgpvBNN*&oT5xy)BmbLxdmN@II?$P+i!O^ zUzieA?jS7tcrdwd#Hw%nA4(B3&!2zbY6v&sYt8TFc|A3i-QFh|p)G}M*YYCxu^byS z*nV?-0n~BIWiP)AT%18MA$t{pbW68Ww2wioVQ`vjSdv%s=?iD@ZxD!~ZDt73GyWnf z3fo2B>8EEk`|W2Fx;WcNJ%_2LVd#@%qPjWB$v+4yjD0)7Q4pD4G~qfN!uWonI9hf`>EDY) zt@thBAc?!ww#oj^O&*)k;I>&owuX7*d%H&o8p&;(6gJogPYX9rB@5T-%4+!PiG`~ zn7KL$(rymCVlR{a&Ft}*eG~pr6bu6kV7uUm?l@9hA-F?q6K0Z@VJ?aNx0t}f?bDM4 zB5a@rC>c=uY$^eWdz6gL-pA^Eu)w9%u=K4+p}!;4QpX;Xt|f(OFL%+NM!3+cXIUwF zcOq$y(A-rd`h+7dWA#bz5pwyYg(8O|g!)eWgAyE!L3E&ZGq>!N{44*Z77?eKdy1U7 zmg|VVum{>-jFd~x#BdJs^1ase{BJw&TS!5_-A;n{003OAxwxs~uhimQ!)qs)0vic- z5AHx_Iw<}(cL{pp1h;WNn7nJvB-qxOn59I5yH6N;33*h%C!^1R?h{iQ;A7$mN&SNZP zuNhSqW4~v%7Cx*04eJXQw$Z~W9axcbo9Ky|{k0d6?lc@9T@~lL!Srd*2eUC@c^wNz-HD1W zgUGlvbjcCN+VolG36(B8;92BEL<4mNW?=v^$SW-$3b%_GEjqXfT%%OPtQG0~Zx}Sb zqs6Dc7IbxW5}h5Wv00itPqr+!l;`+@&SU>i-9flsS!mbE%FlYKa0!z*GJ}9D^R_*<8`O~x%|Rw_v&qw+Az+H{)KqX>&l>+ zE_0-4$oCXmCuoJY^ZDjZca%<~!wg&yPGDTaE|BJH$rrwY1@d-A$>wnjuB#rYc6Af* z^Sn$iYW8~7Y!7jF|$vPa<;Tq(8@;g5dmYPRo@jC(`<{2`O zI8jEUv5HF{<5k&|oDD0pDl#F9l2144S2%_Rp|+ zf@OMNOHaKkF~U0laWmnN2v?pS2&zCKr?WFfPWruX&65!gS{7s>?#OTVbO-t}dn^P< zPiQ$5+K5}9EW&8C(BxjfZ9+m2BLTH}ewHiMrrT;J#@e#gcKq*!7i|c#mrRtwik8UEVmlQc7#Byw#9Y7 znt1=GHkPxhP-7w=J3y=OfB<1D)b-f&Ycmfcm*32~J2A!5VJ+n00+}=?Zf_k8al2el zs`Jc&h zN-aOl#~g-NUfvfR>m`2aTEC8i89fc6ut(K-1m?7+57h2btbKZ#Ifsr!pPUfnqD#^` zyUe^6_NL@ks`-tFrE8WMebzb2IpmgGR^uREwBj;yk5=>7E+w$daV?*d<6wrH0)q%KoWQwA@GE{}#1>W7M=}$>|O}qp6RbDTO5n-6M*Q%lg-RKIkr_9)*W)S>P$!sPEt6 zp4y6p1i}JNDN15^2^}N>DI86r&Fp;p1Qa+s;$eFp=XT_;ZBn9l(=r|YZ;Ye3Y+Pm+ z?O9~0;M5&m?At?ORM!>qop`?RQ`7w>JzOTvTW0#m$N1)PIfLEWYo92MV@pgp+9%1; zd8+sLc#4G$zta}gsa#`6Sd#9mgws*!#L4)Ob9z6hm00LVXD?<%mw5}_6B<=a7WHSV zS3pF@NuBczO)0t>$G3|Ah$Pgbc~39Y3)k>RZR;}j79St1lB~3x*NRuC=f7!J+jN7= zLJDN1j?Ke~4Y9k90l~v8mFJjEQWdoSRRw_Iz-xIGrK zU)wL2Mh{ty&%xi2>Yk|$nms#UVy*CmQket6EFuW##6MP>sqL#j-L8e7WFs&fCcT0~ z|0f_EYT3roG!|@ipQ_dwHdEl2LB-F?Gd3D!ac*x@2mbFAAqbRXW5ENlK9U72krG*Q z-6BYcbvKqt5!M1o{N)TVtk%@601Cy3-8rRoa4Bw)VTKNt0{x1Nw!M3-F0w(tjeQ?r zKKr{m5gi24H{30QhLC+}6F7YQi(3LTTu=ytPx5lZ{61!ahQ0LOit*2y0YP(?13YFv zcd6kxzUVf%$P7=gPh0I3T!#r+Q@ngwTDkG>k+;Mw`5IXtX(yG$ zu~WM;sryeJMsG^KX;k5XVl=h?2&6Q%X8O|G?3@g(TCxf=)fKDE10z;D_wVB1Dq^vQu z?-N>7GV^#b`wHJ~`oxa*g)hF)aOZ+c&OeWT?%wCCIoxwjdH|=^S#(M|o&F;t0{HCB z^D!1DjG&+iubVQLsfn7?fQ8)HE->y?$|V^!ll$?$)fH*j)};48G^vQ}jT^@OnT`j0 zL=c^d+1{_yTvWPRml(;4p?=D(DycsZeEIE!dMgSqDBZqkABaPCkG!?DSKXr}fsr|Z zE5#$(hH$6m@*KUxBrJKSh4AX2Z!{?OBxgQPVRyoleR5RoT zxU8gBqHZZ{*B|Ho4^`QYOD-X#_@}BU?R~325k( zqKCYfBe^PHfO9}B`r+F%74d-Mz6fv#cl5260x0qyx+K-uSXU%PEHN;fnwrlGcWC(6 zxOR)6_fqrmy5dIjZ-VH+hMerGfKY}N=GIt|ZG5vA+%&ajM%3I8u9reFxs?}j(L_Xz-Hy#o340Z{2daW zih!aV>dmWqfOp-eOj1m8+`g9g0N`pvA7PL_;~g>5{@;|~Cd1XSH=Ctw*na$IN`OO6 zUL?Ofy~E45jtRhoM|aq$zy2-5UVa=uF`PLt8Uzgoa0*(QZ4M`$` zxx_tDrb(XU^~)hOR=zIIJc1TA+<5)|I(<(G6vs+v~Zatgg!e{+BV0IIQ$`Rt=}U^*f(P ziWn1-Hg~zz9#_u-1~k+JT~(J*>q?piklp-Np3-^^W2^o&c{PITMrQ7}WXq-@$>wge zq0tSNYuYA>W^A4f_8mfb-wSETjtyiSRWgSHm@GlGyoGRzBWR){vj}Cy1!P~rnLkv47?Ya7I6&c=U4$S<&_XckZG_;8!BCr#)ucd&ZFXVI$RQ!!>mKSC`#rvChb2eB_$MiF}cDdnw^!yuvC#S?YP z4|>SP98Kqsibm`659w{d?$tdgk0L2%l3Zg`U?H@PDxh#RiF{o@HhlGU+^AE7B z=ALh3t93v6QPVgAJSl5JW6fx?Gg^_H1`G2IDK>~2m^~$BEHS}FW05?I8fy&Jl9FZg z9Wuuxsh;SOQDNL6yEQ;-sO)iVmi|5f6}8A$*`%cm);au%Bkh+03Iz;(kBQ~;PjiBS zd4%(ot}~2NACsW)p&J{E_->*l9KpzZkl*MS8Ct za$E?)4RT{G!Ee1SL{GMH6|GuwQbTS21fw*XD-TqKEnVn%w-*1&wnVF7o&eu!&PYVH z$Ay|+W~}A>9^W{T*b*HzXLJ%io~_6bWN)+LXln;1tiJEmeYo#poY4pJCM!6yK}I$BxT?{Lo=uz-uRW(2~n9v z!IwQGf~j+om7Z$Z?!04=BFl}`Fwa4gWRS0EhVHCGXm{loC_F%`-&x^8jR>%OgCT?k4&!8-w zBizbkR@}^BI>0WtR3)SrbP9W3(Bc5At&4O;SW!jE zji_ZEo$Mzp$6S80#G`eRYaKVAA4M?3M{v=@L*saM~dK9m#QoA<$)V5SU*~Sjq5M=j7>cUEb zR6NhW^uy*C%D(TJq?L1s&AqbZ&b>lJ(=mOoyw`5qTo>ft@;1I8>(aRrMB{4a0S!(% zw^arho&-R%Jz`DNfz;K#O2ZoL&@CLuNDJlY)^6DVv$)zLS+6LE?R1SvQXsutOPJt~ z9fL_8?izVHg;}MMX%DmNQqqU=uWW35{-YEEy^IZj&X-lL=!qLf-r5SPGz1#Xcm>P& zM4h-bVaWr*N^ku?%tj4TCEvM{)S6!IjdvQ$=h&u%@J+dePQ7C!XAmtWNN%zXr99eKU6_AyrcPX3?Eanr)#v*yA^ zINjV0fK29}(*;|*<$l;-3H>RukG}Ho7ckWY0pnyoKiZc8h|et~w~7z(tMX)k5LdD0ewC!0#>4R7%m>O~wT_DmbIjz0p3xJxj4 zH!{S?(~;Y~gP>gkqz>-n30I=p6MI=z$Qa|f$6s5>?nvuJ#M=PTFw+(w=`VddIMh2D zTw@Qlu4+quF))>!hyEN5R)37qi}$qP@q-;oL6v?#-b^gOl9@2+dYY28c|^99{X~G| zctgT-d}aFJ+LyCtuSQZjFmEYtNFL<7Tl@5kAY`aiU0aRCjI=5DL2f>!8h zCv`_WK5DDeuG3zGQaDizr(c_*@V7LIu(6?%#p!h2>B^Nx`&Q$w+*R2GTD260T0QyG zUzE>CE`0M$)ukEvD59n)0{fok{Mo5ul&77mz?^@SpObC_GxKr6S@F;NtV1>eX*R%w z?ylR)%BUr8N*7y^jtG(3w;4gxtyL*IU@+d7H#XclgK(>9fESh(&czQph>|J2|4vE| z^}L6-%^f*9^02Glw$IDPl3>&L-TLKpwH=y1Z0exX>-s;45hpcWZ&sqEK<&1ERw&rs z<_Ab4e)6%X*hiIG4AJ>BtOy|L^O(-pZo3$d2(Y9*HEt*p=ihgn^El}fY|T;-scxS% zMnB=;bCtY9z(Arw2wh;zV)d&MSLVU^OaN$gBcp2|Ka>pFhvESFAuhaC^^tp&-Qy0B zSvU6RWc=kOIM*LK&H0Yu65nm9!IRPnR(Z|RYDil;<6#YKZZDCORpog~~?Up?Sr=|ZSgp)0xQT$JWfF(1F} z!$hOmp6j*sO>+;DJ?G+9#3Q#9>C_83$LJrVjT@#IZ5!4T5OSyRb{L!&^_Oz< z721NVPcB+W;HmLBRhncnG4sl~)Lr`rEU(^ca7f-W-E=d2e}=q~IKJFdCg7ST%vv1T z|I;tP-zbUM0$p@%n&*HK=!Gi~KHEx1qDAjmD7e*A5l}DbbILSElBzq)!T+5zv7cZ1&x19 z6|q^lAv)_Btg;7o>+;oaLhnfcsMr&y*Sw0<^|Q+$90*M52fx}>Zz za&sQIkB(_FVCRzzx9Qm&+sQJH{yrx~Af&3gj5FY;4R>=P4o;x&<{}RgnuVe;!0Jyp zj|O+~_8=o`X)|t|%#s<3)C`t=xJYA)9m!jppc==ZZrdR)O(f7EG7aVZwp_m8ZaR-; zv!42WcMsxow_Qv~5cf9Z;tpf~_ObyM9>Tkbfp6tEr><=b3M;9>7Jy*o+M#glEdyZO z*M+Nb;Z3x%S>$nUfz7yVAP+-u5F><1?S#y5Afn*M(k_wY`SoxH<5cPnAh-u7&D35O z6?J%IrDyk_GQbn8&2Q`%4!e=_v>C}IM5ur=M6u}rBja`Dlw1!qV~f{P9^vlu&dy~OgjP!B<$n<-r_LaYjOM_pq-DZP{b7l zLpYA`hL>)h#(|x3moE_GWwy9tH1J)Z-u5WINiIxU_uc<@tm+ADZMOA}uACLuCG9j) z!7hz@AKr|fttq=)Tu%b63vw~*Gh3a|KR(tFvxfqtfz~XuWnJY+Z0j??Dzp(eRy(;? zAMyO@eE>9Ss;BilS_Ju-0~^##WA2lxd-4vW+QQT$+yFs9zP}+=6>WfHz&`1cSCZ+@ z&3tK+aSldo-1MsqV8Km#o=co%LU>01#8))^f4$&i-JmP3?Wzu97poay0<=f4_69D6 zM=U(JcNHBUW~^Z;p~IP&m{%U$2XheV;`DnuP_ z^>P=l5Lo|(yqNfoQvkwPVN9M)htmxDf;-eZ+}FqP@@g<{l*DB?)EUltXvuicT2{iX zn5oK5d`a69th1r;>)TCdU%r?83V+4VC%+JIP8>tm?`#Xo8spbv3)!cB-=S8-B^L(`(gP)IJeAAykQKv4IL0Q=VgcSN}&mDQLG|C zGS$o<%X|GqOX;}&m<*`115J&FakM+N4xj9hXi*%I^E6)@$}Pg3_^4kajL;LBH`tvH zsn1~4{XC8Z+dFX-=FbXkh9|ct{QuBkdIaTdjn6ulVy&~5hbDc+UN5+v>g#IMwMJ6haDM2>O9aG^Qf#-|NbEdo(X(_s-IOOn6)6K^O|VI> z(bzQ~`12c`UJeyCK;{}4K|^+ajL?lUe03#ynXXBbN{~f-6DY-~%qrdP;RKVbIA)}) zz#+)VjDxu`e@m7_)cZqGXQhmszf=~LUi&@bHNeVcgLR*($$9LGRPg~&K;ReeAa0|L)O;pf-|g%j|_;*${sJS_L5 zPOlZ^+}CKP6j+pH`(gD^a0kfxb~%?l$8#Y2^M99QXi)N80mPPyYC2hy%SlJf$R7*n z{KRv^QH+o9HRJ)glY^ds>&lqxL+XiN?nCVv#mqfbY&$w(Bv5;`U7_7TFA;l928xF_ z*iaexRGG-Up0*GtxW!?O@AYdE4^Gk1Vv<&VFV$ZYQC{VW40leMWbO-Y9 zhz1i#_p5~^f8!(6-+`W8)zB zMSo;#!GVu&9{RyW-Ot}K1el731QqAeIPb>f6LNem0V;BtDv`F6Cg^9)tjjzq1oD%3 zvlq|Gfht|#<+-4-;oF*&_EGmqr3Q)|p_H^-y9An7ZQ`)AE({`jE)Wp@+z&Ao=0*#1 zBupMRrdb^@r!d5qeN}jX?3{le3ulOZcNtE9#}+DS>Xe%2Cken#gdzAH5o z=??dMF{|)%)%i(c9;$B}`lX@TJ$G<^FWn^FC()MLzEepd!F0X*)aX@>NP?ZbnES~; zr-$6;Z#N|tZEXh*aR3I9LA-tnu`LRPG01S^MU>PhFLKE)OU0!5=rEBNXL7pZ524{u z=ICcDYvII47n|PwUZ7BZ0TfA%h2PPdVt5Jl$AJ~MnD?>}>Yjl)yoWHiCte^5{=VMb zl48uh*Ul7(Fr^voqsA~q5^!kt3Z5KVKjGY{u1b=!OqBYNw4_U@VA%5M<~*IGoygH- z>logA)kSP=wZ1q}7`YjPgxg`~FbqG*oYQDmVxBkJ`~=-wQTxzUK&rhxwA)kYQ){bsa zXC9{~I6=xh`|}ePH(zH}0!Nfikn42QE?jOxC*lU@kBX*(JMN@3P{7S|O&LujJRq*kml%C!fs-2+$91SR%5aoLeKoj zg35ETS}|I@lAdU!-8djE^_dQ8v)9541t+G zmp%Fieu43 zW)Ey{aoZUlQs^}GCinEa#?Zalkz%2nmUCKlQbAgbvvcO0uFArmq2o+%@2CgLbzMW9 z>)c1sQ%<+0(;PXda04CdDo4=VB20y^_7PK!wNp zTn>;-82}q5iB6U^RDGjS+U5V8RY74@iX9eW$vx7Vy_H<)Lqih?IMCIphLf~NpjNDfUVnG2}~Q%4Z1k^jCMF{7BLy1 zQ<}irYRW8R?5{BV4#rp3h=$5pwVs`~tSbMXl}3_U$Z!nRoQ5&Zg^y7|17VH|V_>fm zqE5pf*fdk;7%kUS-kfFgm*l+$_qpCdNEU-mg>F5daXVsJnaIQQr1*AN3IFUZlW|Kd=mx;{mi=h?&-|}E({Q2cmN2i2X!}VE`=GFR0km< zP`=)Ij?5+72|yT;NRd#-H)zwo&`r6Od=|j04?!~(Awk}=$|#Dh1@OlA`p8&w=8%B~ z^((;~Ip36YO1LN4jCXq{lg97zxuS!HbS#RY`j8yU-DDrB$4=`bs9q7+H#iwiHP?oV zKm#6tgE6VLg%QR3SJIVXW}eap*W0J-$zu`Dm(BM^S%`6Dy0)sQ2cwpkZYOuROZPra z#U><~^2nX}UJ@Wt9%(Lg1m--IA}`t$Dl$fmoW zfP`{QC2Tp4JG3q2jUPGlnfFVKPNdQJ(KtOcyE897idBLSCi!hgq8W!m0V@;yBFfqk zI$|uUiRx_l_EMzt{T`jACN^uBNlSRPiAG zO{~@URzOH-7+F3)+3zbYvZakf=nJB*7<}}CIvTJu`_wjrqKM9CvD^@0kho*AsAM0e zzqmYd#m_=^88E&YIOO-{P<3&kc~6jpKD~FOxaXsItvb)| z3)*5I&S=$LcO4_UjseQ8%Z1d9S{uw!4)eQ z!eMhj$Lg#3W%`EPf4KR+aWjD~n<}`j`)>|S0Gd?8ednPr3cbGGT39-nZbj(G9~vNw zIH@=S#N_SmrdyXrlgo%pK3D#k8EXc(&(rvGGGl?r@m~;1(mJyQX(Q7v?WdFlqejrG z)t!Qfb3pu%(ZGcu+q?79%?giAFbibqh|MLimt=0r;4ys=y0;{L2`i_@3L2M(R684? znFT55CAA5nG(_+^O+mA@>D5CM1kW6=ivWH~QVvX&I<`!h!^pG1fW`6e`V8C@cgZUJ zwW5zo5KndBH5u*$T{C)K@YwS2;Ia;9#lQ@8FofCkM`vTtH+8?M7MAHjeQ!;u3sjMD5>Gy@Bt5?w3$aInX_CIH5})2 z&fSOCKB^66H^kccpJ?7doCA!}@+E5f4C}`pWO%?s2mDKHe0&(Hp zZIFi^KltUh1^s19z(gaWZ;RLJ@ycUI>y{36&%t~?S)OfZY-OsR1)~>?m76+NW|kw> zXQ?713}eqE=s<*8&K?WP>$r=~8|!*i$La;#@(XIhuJ0!RA#MMQ@=YAz$BmX}T;<1( zF?l;|%&hVRJB?WxBC|@P^694&01z@&%9u_m4Ste6U9T!s-}D@GzX)qUL*6!dbCYsoDDNAHN4MMrpSkz{9r8+IX}N zwiO^Ao@k?Wrw*FY~ypvx_4kHJ8F~biaxq$-;pr`G z3E~J)6KHcfz|b1tMqz+7Hhjq#LxwpcH=kcrg#r1caK8{S!KV=Qi!Ri#^)DB3uva*r zVB)ktI4q0x>HNw_SNys2i*3zL??k4UwFNfTvS>zL;%hQe&=t>0Cxnn2Eyv1#a2ZE# z*(?Go3h)f_L?1hx-jz*7OliiAd(Eg1rT@Y(QQ!5nu@GpKzRS}Qv^X|gD4_&}qz(#Y zyTSXh%GL_1b}L_&>rWOspT3a*AUm8;K`}kFKOg+V(&>){z#?amm=@oMHZ&y%0Vr1% zKq$MF!xbcFrPPcm96QE&`U*~55GS%=0W{d%gF(22KT<=LkCO)4h zAo%4KpB_@M0cf>Y-pIofD=xF3Li%y>HvRS#d`ds((g&pYP2y#4$%kE>N!aGp2(t@u!svOqoCAzQTkg6yEvSJ~oCMn@erup;)_Ka_1 zbBd=or`xg^|0OQ zOAcIi6mh$BX^5RSjsNP#Z{rE-MEX1Yt#shrKs)%BI8ZyKGsk?t0&O`)7eVxvrZunk zK0W5Hgt#YOiC2WY1RpG79o=`U$dg-zrF6@CA=Zc0jw40xPJ^f)uTq2I@lpLH$QAGLKCAt? z5N<*faLA#=K$!RnkMSSy$p&peH`15ZBxm?5nR6kqhBAlW#KNIRb5}JVQ~#bc=`VPTAT7Io-fXrqv0bpf^Bg3|wGVU5KMID)0b5oD`} z2rkEgIG>!plDQaC1*VmwhISND5Sq-ZMMT4~Hlr^Y8@m-3M37sDu2Y>z0fT_ML82(7 z@Hp~m_v`)sCpglE$g4`;*(7bW*KWQyj%rnEHNyXsoTj_?TK(-n&>k`<3msg#5a38B z%lD`nDKVxmd4mz2sd7)NK>3}w0d7INyTcmPt<~il05-({DdB;E*ZSM{8}jlqKMH{k zjH43nNinEZUL6m}iY8bf+!&8Bp?-h@#lf7S%it_7BNwfa>PKmd0Q^VyL%YssYlx;< zv=O?e`G+c-&S{pG+B^W{3-p%6%$vq45J4-{ZlC+W8&DI48uL;hYelyXov*?lCxMb^ z)9;!z#B)DkAr#*u)WIo$=ZAi_vaP%dYPDROLhu{{%CJgbAvs0o;OQ3-uYIlorI6nE z->oem>*VLW{U1-_FGow-B9I{ij{u6jJXtG=y>UWVq{*#-!30t7#I05*h|`FY2~0vi zzbBc{3ZDf|X4>tnE+HB1c_OIEo+tE8$hiU04QOihIXXU%yOGcI5NlH$c)SezVg`Qg z7bu{%%dERDDCIiPSL7e}lW5JtpSr4_T8)D-Yn@lh&1D*IJ1eYs$vUlqLXJLb3(3i! zBImQd9HW9mhHTmq3DSJaO{u^#&`>oa#kLnba-zXGZY3HXtKESX zXv*Nf1CSBg0(Wq$IzD=H3NuwFd2_g2d|qCN(^@1<7;`BihU-)z|Vb!G^BSQ zC;q2BHR+(L*XbOlkA^XT24Sw6vjGN~DY8+U$j5n{bMaPbrbHNMdc0%vN1CK^DzS0P z3jhFz1;@j0V3au6{joAp8Q{OxNC*8_RpwkaO7($JGFSEY5j*}uicPh{U{ao;`QrY; z{^{w^Wl6>ab{GpmmKLT&(?iM;1np{=J@Ax=j{+nX?Mo3eu3KsGL zRBl*UD-Z5ikN#$c!Udw>JLvQ+`6*cn?@P;at(s|6>m4xBS82~#+}=i{IZXl zVV)*c=+LI$_yLd?{HPiJXX2X=Gvonc1nfNzBJdyj z8gxW~k_SF!gjzN~>8OMTYWe5&*;N)6N{}0#XnV1}DknwpnEdufe z&lxEGtf_yEAa+3pFThYHT32|atCdrP`4M4eRmi3zB|rShq)KO65l+C3EP}_OSrP7= z9SPhvMD)FS$-J2Xw@u%NrK_+vyVB<$vQM9zQ%ArSpZD3pKl|kjN%y?wNq}5TMEO5= z6P$~F9*%zHx5Ou5dl@^Z-o`F6y|Cm!#@Z$6E6Zks80EMQL%scbASY`TC|BEvbL=zJwr! z#BKgk`YLNsI=ja@rv$^krDgNG9_~_g94{2Jn^`zh)Ecr;{8eh7CJ3on+jdNzck;%* zZ>Ds6xc?wMU=O@~W}5Y$BCDXvL)17WZvB~yUY4fi*1;HR%9;*fp51c52xmuNmn*9M zDdWGQ`YieB(12f632rqY!YEZV-0#L1ptVK_@wUuigX;ON0hv%d@>@JW?%s*eO2Y06vu(@ja#PHZQDkeLOb?+gG?;Q>wk(h? zCgw|w0A@r@f?xGRdV!R0b)c(CzGyggPn_VnAk-gkgub3a-F+v(VW~R4qv}QIvv{Iu zC3l2+Jq+Bc*(2MZ`S?oX#l{_jOIeCj_D?TqZxpS8c3p*v8Ab+r9o#sAjQd_sI-vKK z`)(^J_|LoT{?Y-g7))9U24*nlmH&W`Bn(e4)yl}DG)j24o@zZ5Czt};$K#bIuP()K zQs(`!_!}K7WtTLjaXJFCs>#YwmEK4GS{DKBSBj~qO=veuj9{(RXHhHrlAT9-E&N5f z%V(eOdlztLCv5GW27D4i)+83_a^O&BtMA(woSSc=xLRpf>|2!>(08!AnEZW0-V1*k^M1G^IU}ZGTnrO z*w%qW_8zX1Kn!$DM&}OgjwUeF9`~3fvp>laRIT$IXj93>j+U{z&LNjmCj?FEJN4#| zBH%$-n`H|9)T8x%Xr_+XoT78YJwI}1IFpxY9y0H#M1hbhKV=hEM>>H3uF3+YvQZF@ zgqv45oJ1(3{`2Or%{)`5hz$YMOe#Aw;yX$GJTp>j;ise!(@UK#mudZ&8l<9+))qZK2%sW>NP)UvmS+uv*eFR#cBw?xw zJS43J){JPnaK4J+yJyBf|2gcI(Qvl2AyZ{_kn8_4doj5nSHK*MHgr#1n1kPJ_F8BFUusoj(K+0Yd> zc&Epsq!fVXZagnB(jSNyTWDdOGTn<+4~0xUao6jPMd323SqT5rYThuy_VFubQQB+p zaD6i5bCd0?9}H57{}(;i>eHKKYS;4+O_qf8K+}-c2=ftt}yyslusC#vVd( z*g91``lkd&3aAY2zRjQ;=lrRQ?w8M#Pz?$PZ$5>mXK>7}lLv zbIceiU`7A%Eu7jIEpWl>J<7!D^!Z;k;<(ff^VHYV52w#lrQWpsj)I(GJJ&K^pHtFz zS)!L?C8(tH8wIn`^se|!X^R&8Xs`RQK*Z-T*BHS0z+nOF}P4!0zjpN8meyywbG~;xwFbSN3FK!dE8-vjgzB8rtn#u9rx{W zou!aL4T%zRKNBh@f?p9d&Q91Pmmeu##{io}G`mhC6DAUKF7#=iCw8Mho@yPX|0B@M zMzl%?pwCttb`RDhDpqsJD#ITSk5oYz%{O;37+bi(Z3H|}Wna!9hIksXX|QJqA<6GQ zN14Nm!BgzOB5cP3D ziH$pZ0p*QU;PldxCY=PsMm+G-t3OZD*GUcAtfNb#NWk$i@Y;V}Ic#N-;ktq%k!yJZ ziMWy-L9{6~@w{B#hD!1ALwOPRGa%?vO*gDpT~JKZ-=n6@IH?e*kY-2XEV7hf)|2RA z>FFG43&NQ~Q8)}XrpuG@P?ARaBJnSzJWGla2YI;DbE76bic8BNm~fT|SjHDBCZIDP z8fAxEC1^nYH(4_U(Na2KIR%?Z(&<%YH22{V>VNuhcI_QXdDquCwX10XV*W0 zMj%4J5Zj#@rtS$uu2k@9W0kQp(XW8gV((I25}wj|+uuvYFXWp`*{Hl)G8H|;qA{%j z;5toA5bFQNW~ay*#d1~6k~ex4UT_gV9<(9J#E2;-%a75Vm6U1dn%P`bK7OA@wCM6B z>kePYhC{VRx`;$9XUix`iOQ!?>Tg$ z>W`8!?k?-wn`I{BK;`V@U}zGmSl#s#_W0#o1;dy<2W}2S0XxvUU-IbO(}N2yc=}K>ZkfHC|Ddlm zX?i4}qM14ooK50Kb+^DT8VxIM&&fo2@!8~FF2*symWaM(Yd#$ZL#^ZD$3geC({bTv zSJD!?P4Z3qIaL@r9Ir6_Vj34V975@oHANHnn^z>EO?>Rb#EjfDx(Zr14NQjq-MkIJ z2Seq+$~*{E1STqs=4HRgpkn>-Vq6GI2d_`U-J~reP?>VdKx-6i>OSB*#|By>IbFk?+~!{5&-^2v@2Vc9<0(Z5C`KSRjyfTflsJ znNjTjRIF(&0s+`%9D1|JUjWoMV|Js$3$;=?j`Jqjmq3NAOojW|tvPw2cei7~Bdzva zV|lMMsk?8oWoaHpnT(bar9dUK@b;}kL`i4M8Y)?YB5$UjPVfyna z7b$e*qx_WXM8e|;r}xx>?(lKCeLYH*HyQ=3pEl#y&%gjiYfc*f^wOtgn9TEw07$d} zCD|E5VtKg{--Tb68(nI?&iz3V;P(h6)|=6YB05P@EP@lWQ&X_{dee$lMO;)Hk$-a_5N)Dk^2nP190e|LxXk_5CwY$xj ztTqq}wSg!|Ur_X#Zb^6%yAVLXJgeCtXN~M_0(nmvP;Px3={i$iWSIslK3wc!MFuF_ib#-x!iQZnN4pq2%>VYk(a!XkH-G3nn9s zwljizH%*$0SLJ(3qo`ccT_u0N$mSTUDUN7JgUX`@S0_XK#Df%?3kT%t<*Rd4U&Z%> z?V?46uW5O?>JA-erG8X(u)+S5uR5QnTW#e#e*BA93`W?mWp|~LQS{3nfC5WLK*~EO z&q7!^+vA{QTb8{@@{}VFmDBS}U8HZg+Xjk3^l`jJvh92$Vb z6|e!65C&Wv9>z)?b^(uj(RK}U&{nN9`v~JcQ;J{!C2H$|NFNmATJLnulJb`(#)04} zv_bY!Q3jfn9rHGgDfjq$v}o5Rqc0wTjN``kUVr7cL2iZvy>p}fz_}=FegN_|;cbD@ z*o3bZKnfc9*WZnu3!H=ml!)>k=PkY-R4VnrQ z{@wI$yh6Qewc6We&I$dFD*ckor2#)aZ^x3AzM6zeuQl#lVV|W73((?!Gbz(n8BOLw zr_Y1So*^0~F~O9eV8_MGR>3^4NQQH}u1gUq$l<-HKc#H`=}7fJk9@=L&R#T*nVrmq zyD4>)m$0O0hx(LMvE_bqy1+=(q6K=q-MUNA;7mj?)2k{sH%ojV#!*2|b`gJFHeHsA z&+h*H>Qodq4I=dcC;_x2CzBXGu$zZ9#Pm(0OM6d@9|MC?7MSaU113Uh!2VMJo!W&W=_3NuVWdJxj7HE^-#kaz>ypmj@_?-WYiGNt z-Igv1ZyMICKUtp1H$dn#3l7yQrJ52A0j=#Ikh-3eZXL|fGxkOXM}=Q@Ze`m78i_$P z;IT5NhSzh*3)(F|itVbnElE)H;t@M~n0aEVLdK=N%=Y*&PUA{BDnqhG;y}BJJfs$F zY!|lAabCj>1qS(gKAptY<7bWG|4c+H!SL<29ZDN{Ys#GEQBrOG)V5Zh?xm-YTA1=C zJ{vA~GmeE&7G!LfK$D3zYc?z#9Zv@=&)x&XQ@koe69e)omaT-s_DG@^A1?RV&W+>E z`rw;)A2{!T1o5&6K;G7>Pg5(=t$L(A)ra*K-U6=hAkk-tI9B#C{D!Z$k=3e(Kh+$k z6~}C2JR$eLy-MyBClE`5QHEoeKn&b?v8gGbAhi(ZV_0{|zE{wTZ2TwEYZwaJA*v&FCo~0WI~i zw{yuf7@hD8?%)JNPy@r0Lao=Tgw~e;;Tqc%twm@Fbz<}btW-U9QBH^6%hKlH&OMG< zYC#6wWEtEn%cXeiSr0GceM+8Ffd5zEv(w$P`(qqdpRHR2fcrt2m+Q$nH|gAPG|cV=!tjJGZ@#BIY}J#GFWowYuBj@uND4 zSq>6p#&zZHuCOvt$?**GZ|GL-JXcJ$5QRR7T4gTb_vqBre>+`9cwJM3^3V9~)vJ1l8AqtNf? z_8JXZ7^}X2)g7I;sD5cILAc*TsuOL34ZVlLdqG6v=i>7pIpEHGNRbyRN;e+jP5i+W z<%ds~pG0h04jvKD$X(vB3nb0aSYc&~DGhEyG<7nbHaFL`U%h*|)8A=pG%Hv~nJ54n z$D{(pKZ8h~=hTqwat_5q8+zn3cnyGtFY)Sk+K3~s_j;^!0(+NNosvdCcbqhE#1!P# zZ^y=w5+CS8+J z?>Dt0_~}20!wAIpnJTgSFfg_4o!s8%7AQt#ZkrD2gkdfUf2+q4E zbqJ)H*hRKYIawk#2Tj~nhbRAgXoh{m&mLyxy=y7W&s5z=$9o|B{Y2JK$3r! zy9A9@g-|xwuW-3dAtZ9r(TpCZF5DZ#^B1C=F_dsG9-C+dAA+TV@6X;tX?O_W!&m;P zy!szEh$L?{T2bJYwfO3POFs8flz&XwW<<)g9t`hfFBkk8K$*S23!{lTquUVh`Q{9v zCq1l-D-nE@B+6i4^27tAg)5)vo~?#`*TY_j1I)sTnioksbjUBNS*JwIe`gKznkP@W08p5p^am;3P zn54>4_**iH;n>i>LAeu208ZWz_JZ^fXM!6KM8Ow@#Ai@&x^3H@!#&JUK!8wc^h1eS z@S)4(%Krc{C^W2db)R#1zxa&mK<}vta<^MLxW!&W4TUd^nWPya8ThEK=6NydY<%W_ZgKYP&84`)C+ z=T|OTdf)E#jN)$`p<6@IPR{#olQa0Bg$J>A8BT2yJ z7x-mX0o{K`_BmYD?&y_~$<-C%`T`9Mwc!2rpST}DB(~O0#_AUA#y+MLbie3DYp6j~ zNHB{pEf=x2$O z^c&btJV8u7!Kmy*+CgkXSv4>E=XXzaMcj*{DDdhkSZOFLoMZw9TvSTCl5ND7qnj1!(!CCyo1RKf?9?#g}VM&-)# zL?SY^p&Zxjfng@!Uq6+1}GpsPV}RY(3itT|JwQ%NV#%qHACZNVH>QdF`?&WF zIRZspLAH7w^JB&Z{Da?Df&s_miFVk=Di9T!THw^pzw1u};T98v201^@<+qwlCS)q) zo6=%#CR)2`nVF8;-^vIq!nlvvu~9=Eo@Pj=>CFyD%FECBkVw}|taW*>a4@hcET^Uu zjLmMcs|il8b0*BFqE23QAFl-OhepX*3745$-=NE#ZuWgL^)^nw6GH#Ec{1Du?ZMq2 zVjI2FXeN~}x5Dw!E$2*2^R71cwb0%bxbbWS!deNZnd^fBrmwn)fK4V#Q0}#_*7yf2!dc0tL`D>YhBrX#TON9s$hT}iNe)odC*~XC$ zqi<5aDS*uO9VbL?;b2T%F8_b|&pHwgLWnTAA4buw65ELBc3#1WM13u8?{^2EOb6=? zZoe;G3rr;lQR5;OJd!o%3#f_D75(Ra0|&!j?Rh%5K-HlRN5lFafyqWw-??e$IH{l0 zuU0(w>paUwpDPy)IsmX$o=|hR=|*Jnm=?9-Vsl8@YWg9U0{W)be|N$btXA=k*3Fx_?5~>cKHO$W)M_4t|$`0Ov&iOXA$fI>};QCW1i%T?$#(3m4o@9*VS}|wObWb+~eiW zWxS`Tmp7*stl?Zco&ZL6O*AuA6sCo$rXIYiI(`R6K!Jjp-)EOw;E38?OaH3AQtC8- zTGWfhu}gO^)7-ef`eW;g_{Zz);O0A8^7p!c#;{Rh+e0I**p=&q*Z`VbdKM+ zYfV{~{q4-V4VF=NH9aBQf33CX%d6O&ZC=t-Uoo0F2-2Vtuj{IkBq8=oNQ6q-yxKM8 zOB67d$&%;{e`mCUe+Un>K3SdRXLOp*3dvX1WGzQ%5#Lzc8}5$HiG}IRpyM7-=RIcw zSj;o?&2iSa-1d3Y+Hh?W5Z=BfSb!u0>Pfaf7tx71MLOce8~bD7+f}zGOQ3$=k$-=w z4jDKiWEgPZ%)N?P-*v@Hd!9rkVmr^QLQUIsoMf0~4xDTC!;9!sPg|rn#1QXN&)Yt` zz>t$_J6`hBtXGSz@-jvi(Yje2f#;DD&8>D=57$1eK_kb=596L9d}l{R^>KXXN6hnNl5t}lEs5p%oYDF~xF7pj+HR^5 z0at<9V?())KSkBsg)Mq#+ZSKn<5?~OW%@ahy zaL4`r-Y0|_O8c|QgqTp1fNmwdgr}UEPe}l(F4%40s7)z%q<702v+8a_&(MFcz%gHO zrLgs}Q(#y{9U6dP%F!8JdbNa;>AEIqsJ>PhYyy6mpAr8l+l9vjq`7bcYK=}w3UQ2LHA*NW`NIA5d&#e@#1QwV4Xq34l&ItiO^o;hiOe)#hz7i za^F6>#dh`$vMr=oGsXFZBePjp8!f~;)PpjQ9T5b0zcnWtI(QE0wF!P+RRG2@tH)8r zFd~l1-8>NdA=?lL{+x51x26pfGnlkXdq_#I+|ISTY;CdyKj{Nk@EgnC#WcjVsQ$&H z$(x?p2)f*;^bvPIIo#6xvp`42X1-S6DO`?x7Ey!B++R}=jnj&D?}^-yVYC}YpFFPl z206l2%3PDRXX+1*AMBn1+di@^i2gVL*8#(LgP;MbgO6=w)!b#VO3aUaFuD4?_S-5&k1O|#*>^h<;kHUAlCI`N|#PDI|UBYN$3IxzI=#aJ(o9_#Gx@7u$n9YlnQhxJ5M6mVfPkMoNAj-*S>a+2;LR2+AC0Xau;Xw~k6h2N?;)A6o*4b9&ba7gBL z`ZKpi2z(82^p^)@FR|I>t9Z(vSeK9^d=X?F=JYsAV>HNC~zx(o##a{_s``4lP6A;ax)R zsEV-xZ#9t0AXfZjxV?0aIq!Xaym0vU=xZTc$;M#7nqSE~VCar{#q*>tf0sOQyxCcg z4&dSY);V5XD*@NWY&(73=QnagpkgXAM|RoAn5NIVOqB3XruMBL*_mk%V(aRQ!hK8A z&Y?U3F{*gk?MC94kxG&H1cM4I6z70?F$bJOqa(CSVDZ{Q*Tg2_^ch}WY|4^EwM6V!sol3wE(*OXWt@# zDVRSC&~5pfyt;=ctRZTYMKZ=z!}pjgJ{L0f-f)mzXhii1eWEKolknx;A>;rLM$r0KTv{DxFNC5>$p`{R#-2_IBmP>S z@EzYaANDE}Cp%gx5+>2b4s?B`OMtsqWMF$(3-r}>ONc0~y>7e^+SG~(+VgExSl;{Y zt$fHbVTk>Ib-@KFYEIpG2d~7K3Q2A24RnO!k!24!dWy{2@82aRQEgIH`U^5?lkY<{ zy#Sy<&ZK%}dywGi4?UtL!rmcvax84odS>;3X5V zv`A`*CbofS8dx%piL|5W;x)davON@}RGgYoD12T`BeElx{_l8pN{^!fqh zhs+Et`b9*i0}gx6!?wuKuZdbGDr2X+!ku@5G0+NypEDo74rG_vMPsy0In{S1^RYt6 z2rB}8dYnyMKF=_=we!(bVyzfkbC$b)1{l3$ea^qQWLnRs+?SV~qfoGsO z2ARib=w%lM5o)NMQkFx;qeTSKK|eL5xWn^KM%e^G{pyYH)wmp^|G{EfClbG=rdPKt2wBfsHGp*Zn;GziVk)vxJ$THZZn=hApo5E@Frvr?{-HloL0Zr!4 zYQKzi(PGp5LvyXFvFMZT0Lb38_cE*KM-QQuVN%_+3=1< zcs2S;ant5kO^S_WYPp&tFGqfIM6s>H-6Ou;bBN?;XC~|7ycZGAglKIX5}ir}AzV)@ za~+en9;ckha@&BnpUqKO9{H**-j*O62Gr5VLj|Ss+trOU@u3g$S%N4Qg!yru08l1M z9ojH01xM^rp#*NqDCDD@AF2hoZs?GB4YUiN4HLm$sJmf|AD{|>Bal@I36scDs~`p~ z6}GB}@-*J3DHa=~mW%x$A$RsK6lYs5=3FLI_S$v1Sa6gv^eUL&lvA^jDlo_;;u?|t zdp{odSMHzkc+G4j@SVJjxxt8E{@`8JHBD%l)-vK2PSLOK1LUmAf5<4*!q{opLo5Z` zH{BD|L{?=*iO=JA;aD#&$rgV~LSR3ZyZz5NDNCXQ1aiqRw`_JukmeS7Dm#`LgK>~D z4)?3>IXzPDPqy8@)ng zwPwMPD{s*H@kU#!&8H^aZ81%2O}$jis%Npay?-83!_ar&8n1qARfN;bGCv$8&DH|O zf_!nE9=C<~$Bx?ccy7AbU+|AOR3WXR8&948=vTzgp1LAWG~d&mc0D@3opCBahN3U` z%ZFC(bAAlTaw$&#YY45V>mB^AQdVM9vt+hG316^Tu4wJzjUUbF z7qPk?d3bFOoY0ayiYUz~QCAaO=-xaZ;SZV^3(;TIyV?Mu+!|62PX!E?AeW@#bs4e| z1NPE=%YaAvz)fC8GS&U!{$fy*3>z*oUw!}B6`0f|0n|zV zF|54(BLa`z7RQ=V><3NNgVeF;}|LrPl>%caAKj!1PcL3|XdBDU7@1;Q)ST3%m_#fQ4;ilu ze{P6OL+m4tt3rlXN+01Pp^++BFgGX$LjZ=g04}3DWmDYWswChw)AXAZQ%9xM{p6tK z#dqL{U8X;6ry|%W(U=-f@$i^9?$LC*B_RpTDK@Hpc#v4_u ze!SY4%7_DIeAxklV&`G`V4_~O=q40VZET9MS47Mf3>qV|VN`pPl+rwdu~!^wYz?ZA zT2gdy`$qRiwuK3@)xf}g$Niw?L`lQv!#p8xA}}bn^$WRf;2l^W7|%pSm)&IwF<|D)h zoKYSFW@DAxjR9(HpD|slUdfRAxDgU4F`4M`#PqLzCXgrH(lyTq-VTi9Q68d~W2RRj z!R!Gt-NzR;K>L^Or_y!f9PG6NHTyE|CD%1>QqOP;BKuEGU`#2tl<7FAkK-@Pp*RN+ z+j=yARWub0UNh0iM6@!^BT`b`aoUSCE$fp#yM0PpA(s({`w|Eu{(sR33zo=KGp;rc{K-w7IE_1ykzF(r{A8_CW7G1$gPDoqP!*uKi}HN$~q`hXxM5xtM2ME39t9 zk9~m%>v^XkB2Oi1f)rZ`?t+b|y2^_+)GwUH#Qrn1E!(hb&cD&ti<-1y^OgoZMoEI7 zFq@IzeND`%-cDrWOB`D0;v-43OYRzTLXLsqL$ds1b%NJnz^G0=(;vnDLV%q0tk!OFQ5zF5buZ8FG)D zCMwr8^jh@c*7=pevql=bW6br*Q&VaOWPdGPFA<~in0%CPbG(8r{OB1Y@6SjBia3*x z9DKmXLO;8msT2$PF<3CI>8leKrL@JD@Dr9>dHoRzWRsk*V$L&bq&W!`_uC3P?a2#Y z(nIFt%2nI0prbt8!g0d)syHB zTOW<}26t>IyQi^NWln{F5A>->nJ3`})9az|tA@}M7C~I>M`>tdJs03;Qz0$6sS&Lp1t;T3>3sgfQAdfTLHD%%CCif$#gd)HFbw0>b^NP~V1vW{=`sl# zGln9YFX*0pK9Apy(OCP=?9z_MQ}nA6@)x;MWwIu5nzGh^ppzbVEezglCjBnsR#R4_ zCH2LpgXLi1&tqE9`VF_Lwpygxb-w{8@eq0vx6tOFe&j%Fw8R|#)w)u|rC3c>p8H~y zB>Ro~X0wp6F3--Ia08$u>7C`l7IH4jKjVw5O^E|nF!+J$hN-*%R_bR?y%(($XpK@A zizP88j4B<7#p)cddwD{R=1vJqr432p-`)+%A9sWU_+B>dD6n269xgZcF9YWI_q9yC z-;PF0rYM64avbu+pNEHvR-wW%CCqwfupI1{QLHRyL0Mt70LyYjNG4yq9-Xq-xC2_A zu?WNbUdjuK*nzMd@%AZ2>OX7jfgEyl_*e4v$yn>8QdM`|@&?`jR&oh&JH8Ry0+ri= zG!8WmB}w}k`3OCEWST;eK?W+=cT28SSBIRK5Ph_Vg`gSn?PH}U9;5Qh*5JWJ_-9{? zQcr3zUe3}+js1u_HC_%zQac5+$H^^g+$219)a48Lsx4h^%)?^;U+A)PS?GBxN;=~Tj+ zCG&*rpvfay=ZOhkIRi{lbh0=O7^2%BnYG#7<8v&Y)VgOR?qLgAGBma0K!KEktjc>q zLNa&OLRr}d$Y4`-MwEEkV~fu(i=Jin1r%e!VQR67r%Rdov339v>781ZC{^fsW(*6G zmuV-{fhzt0(9g9_-~WC%OAqs+fH4eCOG?QJQNUh36l5y6*POzva_glE4=4#4Jp}mh zo^r!zh@IU?Lf}$fLyNwoRm(I3WYm9jJk~oPr1prSL5DbRfUN5Nd?OCyDv3p56|bpk zM;U*|wL8QN)Qa(Aa(1Z4l$r*XU9M8H$SqNFa@AO7gVMT_-r6GN2X+i0gNTJVpQo79 z`H>Rt`UxxL!P`b)123jU&Tq>bAOkiN43T>zhlqd%enh~9OmDf48@_(Y-;I{kMh zUDc%za-42da&F4{4Sq|=kgoD5xNq5#^5DMCrDYLL;eNaZ_!)_fePxrdNFJi`69>gF z9{*itT=gI`UcX!Jl9VVIH zBo~Ct#UIp=d!ItdJ{T_Z&`Tn7+|ejVwV2_udC7I4mhHQRNSIA_3U=dncj_j&??Z@< zw+Mz8Mu;@|V%tcv@N;sq59+`B=fH*Y94ivzT#EgDgNQJwicN)AGBs@#7_`5VlcMOj-A$Bs zxziSo@moabtA;whfIh+d9<5a0OFcic!{_wx(B`iGw$Ydq;;1H+qGK^zyM_$k0c`@t zwmv_nw_tzR;wiZ=J3kvNlgH~PviMO1jVFo!>(C2xnryZ)64Q5EZP=sdFGt56*d|FH zQ1&%n90)4qNCQ&OF+8J9QI*SdnuU#^6Xw}-Wwd?CWMEp>-lDuv$2Dfs?ZdsVmD!}e z>?k1rwp%ld&X)56VRO{ckrydr$`(*&tgUk&@M;(15JaoO$dgw9ix603m9{o~^~vvF zi$#na;3>Px?AuVlS2OYhJj&AH-1CwSsk87_*5e)Y2=vCy<`d|+!Unec3Gc-DiQBq# zJm%uOy90?J``v|A&R|iwdI|$&z%@a~Et~_SyyC_1iIvCMQb6SZ8GaYv8lfFg8<g zyxX}#+44o8;nPA2cc%)hb~iZSl^zhj%BNvzooQnhzEZBO*6>uaA!OtNYGL6RyE#&>Dx&|i&<7RkE{w7d|kiOBKm_4vC_&jMBY^?$chd?u^ zruR1x&$}?( zyfpr^Q!_*tX%nHC+Av7Z-$c;lc{XBy4nJiT`JBq?M^ZYGqPJ3OUXC6{kIW| z=%p~<;{$OUbV1ZXYoOgU4ewd2>Vxj5?kuJggTb9NBEf#oIO=lbE9RT(twNo(Qma4| zT(9U;z%_EhU64bJ%&;=FvV9(bPWW+g;iam;!}w2)E}Xl?Dq~YaF_FDP{R#Q2pweb? z9xd4(xrNcvW2ga$QR%A{zAxxJa-sJM z2|)GTOC9PWo%2g1^>Qz1XIZ^C7o+;cc&TOC7B;t!YnA2|JSqxYLI_QJ``>IOMm62O| zFZe}Hl7nq#ec6oAg)rlFNh@1|-z{cU8>hMpQ`XWkh(4AJiJ`l&VBRDwq+yUDH~~#r zn(($BTnNYgIlV<^Z>=BQT$K|R(g&O9=dZG|0Y03zZ$KQSzRwIC(*fO_-aogM#d`*x zT#?WGM>C|?72T~t)!hH)H#p%;@c8O+C=_0IRcH-+O=1HNFrNf`=EZu{KD5<5J|Ie* zg(0{gV{C}7=og3BzX&eibJHCzqP7*dBqs-^zq|xH>@&7t)Govn_&BKImtxUnArq8` zfhwL9Rw56_RnmE}nXIYjBQrb;hK0MzelyKLI0dY~eY^f1$_O^rj2FOahjd(*MDF#< z3W(;K&A+8z0mY8guH9A{F4uc{rDQZOo_RKee#_s@>85j=y;w{pjn<$v}t zDn`04Wj;}wX2eO>^~wow;nqhne+!yAcf zAI^RoH`Bxso-rFCERyt+!R5UBTLC{5DW7F(Ukslv1TW3I#g6D_Q)k=@>DOwKHceR` zu^M@oP1J#3DfJ9Rs?B#^LV_!qleA7mx(t^cd8zhyzWh%YP7v(~w(rHgak70TJN28?K( z7H=_2M>5#hAR#L=RAFbzuy{7YjkDEtr#t)*nQq26XOisBJmOK|ZCG%Hy#xOg~~WtQ3Ts2k+Lt?)YWY z%F-fHjzz0!_WAeZpV!3l-@wmFse}bY4yHv6ZZf^>o4FhOAQfmS?tqyt#kl(twu+b- zm-@KAb=;^WbiS~+pZiQV!xbBPwg}m^&UD5d?1N;91*EC(anUR2$^7dF{v5H4c-y5qra{#)of4kFv%I0#9=Zg4lFIf=?I;p)fN((ZVkpwKiF_90%7SY|%+TW3cTqp*+C*@Cvz!mGqn;@VeR9|Hz^DS-Bb zW$;q~{l6?!6TF}$@{DpHusP#Yv16kT#(=`61J=GHdyq`vWLO@_u;Da;zu~K_I_w?T z>wCtm6!$Bb5{_VBg7x_WAG>=PIE)b^&2EoV?LKQ3tYBLIAI~~>-AD1Na0hD$jjH3> zJ*$^QM|F07wvE~3CP9VOGzjypj4DB+t4-HioAB=@{f6gOAy>3~t+tJuEAS^3ZfFLvzR9tZt$5d_MGCbg~gZOj0^KVl$&0OZb zy!B{@A~|xgacn4IPc1K=$@(53!;muIZN>th)SU&z3qKG8GFunG+r3NX%A|Z|=8~Sl zg;;3pKfHienC=!Pgb3u}DUu+2m`wp5K^{3)(QI^W?WSWSyPqTT2utS=B%MC--M^dq z@Db2Uf%#%VcWX0w3h^yl^xwhM;un#Fq5U4DTFRb36w{Tz=qolBT@c%$n+v{ssMum{ zverZXGQJNxzl}N0-*BExq}hPmt?Y}}FsOR1_rh1tLEx241s)IfvWM$(Et<2`v1IJZ z-U%uNU>hJ4^Lpg(&dir%B0iF_`vm$=cV=yy3~1{;iSe73E+r2Z?=K_zG)R=#|}oEd)SHmqJeF*y8+YBx35kZf(h z_kW;##Y5t^T+Bm5II1L7SRae(AtAY|4V+H!8#b^uh1Yuh0tPADb0l`9;6@ z(P%;@9rilB0;}=Wd&;#}l3$olX9P-E;H|#cZ_r4JIbnoGD*7q2qcIsV%ab2ghKodk zK;+bnV*E33Qv*%|^G+>acv!QbD-B4W5k5^}@2Gu%H4&8k(qv=E@)wRNDR-NbqbB*%7mI2m*%KPR$~hE0Nasj5G3+ zuBuK{US8z0g@?>-l{!&;hd&Wlf1eDu;2r}WqR)B?4|x6(#FnB$TNH%3I;=}>#6!@x zvoYT4^I{Ea^3qF(`7@N5Ds*FYL498Wk@~mT1<0z@Ng@l_`miS_F=8Yc=ByB>m(Osc zb;YrqrR9p@ErLs{;P4k;GTuC)r_2_nhs$dK9w?TXEyO|Q%DajBorwF>@Z>9;XN7F> z_>$sLVKqiK1JSURwsiqi`-rDrJv=f0XI4uYN#Aj4nG5)GKU}jPfhvbYx-_M?GS&fz zo9IvWit}ADjdFFNb}+7a;=|O~e-%`B%oX6$!-;{td*dctlOUbWNz$b7lux&s8+z8HBeeEit=TR>F<%w~CF+4u1*gIOr5?!5nrq ze{hly1`z^X<y4a}R~(;1xgc3e?s7 zLo;rP>=i1pZg1+quSTy97-rqktYZ#_K-g>#CpZNkf(coc`?A%YXK9{UT$%)$p6dgg z5#Ne0EA4{WW{I9*-LdKdVCsnj=_W)NAbX7D}c5Jh>M;f`u?OVIt zH-%wfpkv>S<2emXaR3FQSSFyK)b&N9qBsl0I z%SERVG&C&9VN@TW=8ljSwy6K3_&rxx5yeH#oFUdsOP5yYi1DRNnGNAg$Lf ztud=^K`vr{GS4ha_a`z@s00gi6>A@nhUlC@w_uOmVq(>_*w)PT-3M`ca)+;Nu^(Us zE`169O%p*hue;AOXRTP~95mWHL1v60v4TvS$-Dnl7@TWuKJ7xUXTzvkL4lRuZhgX9D2%^6@n@Cbr?tc3nM}gZy?7= zxQdE*XL@k(9=kZ>QCjdo#yzN|SrMRdCka}Q$B#debd#iNNxAgo=Lz}x0_d?7=ec_uo+>jpC??=VSka@YfZAUoCQ<6 z1_zYw%qW$1HmLmlQNGwn1G98f(b+;c2I8Z9vE&NvF3*_RLm^+Plc-a<$5q18U6+&Gq`1KP7=f9d2r7e zEZ9NfAM$=7g1E#3FX<1I_86xB|7hY)nBZAiG*16;k2c4}+#pX-2DO8%A~?ABVr|;uOKD^@VlcD}Gx0_cK5jE3) z$^Lj+^tM1jWwdtt=2I#Ms}Pi=JzF06SO_SpcXF)@)uq`mJ)+kZQZ57`{b0=xF!H~2_@H`^rF5gW5{-P~& zdRmJ2z@oz7^X|d&<(z&8bq$|zxl}WEqB=YgNQ+vsvE@^P( z96quE1SGEK9@DVm!SoF{jpm;^54S|a zXM`>t+cK3%ADYm(0hD{vWMRKdu%^~jt>APWl zaDut}@N#eGu=CM!GJDfFesVirlH;{(@|=>9Jil{F%exf`{ zi%d)afeW|BR`+3#XnX<~l~&O)1y0);BuMAu@Ct76!r$LllR>K*JB^UXN@R@xdlh+p zH80Z&-JK_okGWIx<6Yl>-+7eP=%e@Fm8sFzGZ=yL4){uWZO61YnB1_X8>=PDVpbLD z4C#4(n6^K3CK?Jgqf;%j*xk9w;hpTP9$(vA8O_RdD!I|xYd{f9 zQVVYRH=a5YGs&-(GI|{oZTj|%W}Pita(QMgkj>9aS?UbRjlh;$hs)JZez}(28KfQT zMhI_L^aNZOL%$CiWV!+U$*4`g`BDA@Ur-Wx%!$GcmnYxxEZ&FqG<^{k4(a@?>!Wwz z@BI|hu<`ay&@*khUsVJf)-nmK+x$@OC|i;J!YLrO00000000000000000005#vuU; zSozYhU!^_uCC~ySLel*}En5I#J*05A-zQi~eO^puBgcVijWbz7XoU}$0RlkyE)Hjj zus_vs*7jLf1g?uv)$(8;?IMeyCN|D~K?cfm74yKTDUUX;p9P8p zJgFGxOpeqm{S342tqE5lHn(e;z3X|X*u(jt$T%fDAg2PEu*Ggt0y#5Tj}fWP5X)I1 zxA)&OetttHEzyI4|3++WT;xr%qH?X*K%4v@my|Bl(#ZpgE zd0DT@N>3qVWkuK?KJ$+x%<{`5s=LPNJ(=J~_q}~FW z66W}nLWOw*8+Ki9fqTBp+tz|8{Z>PR=o>zClt2i&jbN{2k+M#Zh{N$;O7C_9Q%~?7(jXqw&=IFgz8~+>B zyrAO~K{B}pojj)RvPhW@tdwy!fj)zo=5<2$qcx~^X-)cy`6M$;)ev)ADLIwiR9h&{ zEqDF}n3kni@XCU2hXyF;ysmkeE4lJ2q9IXpbULuSk%*pqxz@C{T*4w)+KZ&+8QpBk zwU%wB=k))6!5kjLw)9^K6quSeb|QHE){?%Rm2i9=8;wm_CKFfFR*g}WhLVF?r^@<+ zJ|!&FC(>&0FYcM0Of`iW0OK!JrRv=bClcZ6?=9u_12&)=2;Ai|J+O`v5`YIl0gTfW z@0Rv58`l54At&`Szz9J#RwI`*uS^iJjDS}^qmjK#ePd*Y;RtE!!ns20lFjC?iZfB1b?Gd{Z|84DfEW>U2 zK+Zjx&5snRW@KWc_rhynsv$b6@;zFObD@@zKKLAx(Dk;d@vsud9v?Q!i>A?P8JW{!65; zwHZ=0uz4hen!KC}HMVoJUXeN^eimc$5v2FIHdaGL`|2~sX{)Bq`g6hyrm75i7(Pn% zA3qkOJLz@Zxmwx4pi6z8LN2gGnqJa`1ADHjab_r8Iloo=7SwO$*YJg4PlDOZC(xqh z0_d@XBY*>Kmh}%JAB){OB)!BQkhFyv1D)TBor;7AyH>*5mPV0izmv zBdHSW9}nOuyg^5hkpgNJdmK=pM`NM5?q$xk-@ohH^k-&tjtTNJjG1sHJ>Ba5%HmyD zM&J??dWl@-XV*Os`QRO#(s|^mz&?^jqLqMS>3JCT*pdYI$Z6=?D`47jd{12cOL39X z=AYa<5kbdjh)h`q=29s%p=`h{FPioq(?)=nW_*d=u0F!_3-0I8-_h?~mRCO0>uztrWRo?)8Pj->HwtNjlSl^fXHK{kswSRpJtOU-ytzw)pG-HVL8@e5tSok za_`1hz|sfXH{8eetvaFdIzcKS_Nc^C;r*9!(m$5)q~-;+*WFJesBP{4RWViKR4eiE z-~R76$cK>1Slkl^aeJ~tku1L6T0f!`z?X(5k3sDE-dEVC(s_0ELDz{b^I_h(r5b)o zF;ocDKL>8PF<$w!`!*2lFy0Bt1nkf-{P||zw7B>{lzJuEg18iSwxmICd{W-npcRuD zJz0VUeI{Qy4AqDIzx#Yx5z}Y^uZYl)00000000000000000000000000000000000 O00000000000001JX;nP{ literal 0 HcmV?d00001 diff --git a/androidHyperskillApp/src/main/res/drawable-xxxhdpi/img_paywall.webp b/androidHyperskillApp/src/main/res/drawable-xxxhdpi/img_paywall.webp new file mode 100644 index 0000000000000000000000000000000000000000..28dc6cb8fbcbb52b494c999576527ddb3e93855c GIT binary patch literal 459888 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�DTn=O!k0|62CSZLXX&fF0xO$8M!&-FR*I4#a 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
I|M2r?WJkRle^?4_ z>wtYKM*Qdt{c9B?BZr}9R80-YP`JmuCMfK>CyLuIQl=D-BJ3Rq~@*@M=>?$ zc6~OmXSJOQrPTqRsYMART@YQX4-yV1n+o|<91jLV7rmE|YONr@*L}RCO&5Z`aA96G zWirVvw>EW888+B_GasGZ>Cp#o3Z{#2E@u3~Y!#qblyFpN^b7j0m9x0tNS0;DLRv)f z$lZGg zmd1dBM4vR;jm8@B4DJhF$}u8ZOfjYzv;GlE__;Cn$ z`jN?w)ri;52%ARVoDO5@I5xt!_I8N=R}VJh`7!qs)IljhY{6X_j?i6*#O5_-M;Ir$ zQ-3Ec9E2*Pb5w8kAE!NaRh_b zO;B)v4%Yi5g6Qg12bPNCg5G)jB!1BH;ID-tpJRpc$#-M-_uyZ)k#fL{(OeKz>UsXy zM)UiC;B|DKtByZ2JCN{;=wDs7QCgJ(rEa1iDS*R+T1@Z5Ec2@;0#~gW7b$LTG5E00 zwpJuXKZtvGM97~auldkXg()M8^BWaW7=GwNMoDA&mR{DpD!*S-UaP0nL5H6B>!ab%2|TQ` zMat%t%dx5bkwnZ-RLs{6SF;;mde^E9L%t!Q<~XDr`>;zb2y%E%IF4W zDYPl=H2hbd8sf`XauQ;4W0f{naj$hedbnuKTY?gxF*=~<+-{MUw6!(@t#_k}f@`Fb zv|wFzX%rP2i)$?DbuefI(4oaZYSsREtl-xD%@lX>swNfh%CGPDI(Eu_2F-G^)ytaI z4y$Z3(^v$B3t5N&Xk!k1d!teegh#KHwM)?lU)jriwKS`_2|ff3-W)`}<99%20}0HA zenh!GzX5%>jjlZ8zLmbDA<*FzkRFGe85VK|rb(3`0B;9uO=xAL_XFZ*nSACR~8bVw3Bu7O&ptxrMFQmMea;fbzfFH`(6nyV~Hf&Ts2Y>LwyC zRes zW3guqYrRW|+bbKy-KWD4zFf)BuSWJ23^c#1rC;T-#7dQ!2W7BWaQ(FS$Lz$~Huea1 z2e|CUMYN#jqj|`ugJfHb1d6eLVq@pv?7`!11GGu8sn1cMAVk6yUKAy;PUM%^{)26@ z!ed#mRAKWzN{-_d>`H0K4(+DY?zEz%LGDb9YGh2U4voWXb;iT|tx6v* z$HIW2AN@vd>UcG5!EZ|bLQZ4So$uvx<)K#L{$a?9JAV(dLLvoN2+^orTyr=?gW8&_ z?Z(yNMcB3x2O}h7ji#k?rM|ziKAX$aYbe@zgC3j(FqmcB&dz4v2SY{S0%XEBZ7)Rx zkF%{ZoDjKVUflrosfEw@f%m^x@W9scu3JpvxMxN*b#NE)l}D_0NM!u z-`LyuwQ3F+zGNcIebdb`ml$z}chz!k0}jV zcgkgueNXn5L|P*{Kb(8�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 z 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�uo2ft9*C>2@5{g7NotN;w86=I8+ZsWBJbUUc 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%0AKFlSEue?jqiF@w85a)*lR zrUJOe>(9){f++(0y}xh=NUG)e}~N3RX=yA3J-+}X!x_FHL)u*euON4)wzv zA_3=VY`B-)?nE#uzKqiMRzu?g5D;uw>|ymFqr;~!je+`K@b z_UCFv;VSRIh*M&G*?Apt~fv=_z+!JdvsVTj__Btwz=vn#c;fCQl0Rm2!h%S;2O}jtB8s{ z46@-vqn`C7Z2Y!r?wPFQyozDdacc~r?I&9Xp-P(LVm9!2024Qr}7CD9-V>!}5dW;K%uC10>Vz z=M)}Q+*3sL?(WNc~nS~;o;ujtyFkz8(`AtWdC{Od>I`JE)u(CX4xEs)|6caMe=Nb`!RED`{-9__)ON{oGowL6

d&+LzLDcT@gKqVz;n#n-@#{AeOsQz>9 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%0Diw~cdNqd1B=E5y#3 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 Sv6q%D()IfPd$RxU{=Wbc`B$6( literal 0 HcmV?d00001 diff --git a/androidHyperskillApp/src/main/res/drawable/ic_paywall_option.xml b/androidHyperskillApp/src/main/res/drawable/ic_paywall_option.xml new file mode 100644 index 0000000000..f1b0946aad --- /dev/null +++ b/androidHyperskillApp/src/main/res/drawable/ic_paywall_option.xml @@ -0,0 +1,9 @@ + + + diff --git a/androidHyperskillApp/src/main/res/layout/fragment_problems_limit_reached.xml b/androidHyperskillApp/src/main/res/layout/fragment_problems_limit_reached.xml index 11be520190..e01ee2577c 100644 --- a/androidHyperskillApp/src/main/res/layout/fragment_problems_limit_reached.xml +++ b/androidHyperskillApp/src/main/res/layout/fragment_problems_limit_reached.xml @@ -1,10 +1,11 @@ - @@ -13,6 +14,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:src="@drawable/ic_bottom_sheet_swipe_indicator" + android:layout_gravity="center_horizontal" app:layout_constraintTop_toTopOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toEndOf="parent" @@ -33,16 +35,13 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/androidHyperskillApp/src/main/res/layout/fragment_profile_settings.xml b/androidHyperskillApp/src/main/res/layout/fragment_profile_settings.xml index 74e58223da..d57251c433 100644 --- a/androidHyperskillApp/src/main/res/layout/fragment_profile_settings.xml +++ b/androidHyperskillApp/src/main/res/layout/fragment_profile_settings.xml @@ -1,288 +1,37 @@ - + android:layout_height="match_parent" + android:orientation="vertical"> - + android:layout_height="wrap_content" + android:elevation="?appBarElevation"> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + - + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + - - - - + android:layout_gravity="center" + android:visibility="gone"/> - + - \ No newline at end of file + \ No newline at end of file diff --git a/androidHyperskillApp/src/main/res/layout/layout_problem_of_the_day_card.xml b/androidHyperskillApp/src/main/res/layout/layout_problem_of_the_day_card.xml index 4fdc1c9a83..f632ad2763 100644 --- a/androidHyperskillApp/src/main/res/layout/layout_problem_of_the_day_card.xml +++ b/androidHyperskillApp/src/main/res/layout/layout_problem_of_the_day_card.xml @@ -104,7 +104,7 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/androidHyperskillApp/src/test/java/org/hyperskill/app/android/util/AppFeatureStateSerializationTest.kt b/androidHyperskillApp/src/test/java/org/hyperskill/app/android/util/AppFeatureStateSerializationTest.kt index 6fa6d486a0..a3d18f5a01 100644 --- a/androidHyperskillApp/src/test/java/org/hyperskill/app/android/util/AppFeatureStateSerializationTest.kt +++ b/androidHyperskillApp/src/test/java/org/hyperskill/app/android/util/AppFeatureStateSerializationTest.kt @@ -15,7 +15,11 @@ class AppFeatureStateSerializationTest { AppFeature.State.Loading::class -> AppFeature.State.Loading AppFeature.State.NetworkError::class -> AppFeature.State.NetworkError AppFeature.State.Ready::class -> - AppFeature.State.Ready(isAuthorized = true, isMobileLeaderboardsEnabled = true) + AppFeature.State.Ready( + isAuthorized = true, + isMobileLeaderboardsEnabled = true, + isMobileOnlySubscriptionEnabled = true + ) else -> throw IllegalStateException("Unknown state class: $stateClass. Please add it to the test.") } val json = Json.encodeToString(AppFeature.State.serializer(), state) diff --git a/buildSrc/build.gradle.kts b/buildSrc/build.gradle.kts index 876c922b22..b22ed732fd 100644 --- a/buildSrc/build.gradle.kts +++ b/buildSrc/build.gradle.kts @@ -4,4 +4,4 @@ plugins { repositories { mavenCentral() -} +} \ No newline at end of file diff --git a/gradle/app.versions.toml b/gradle/app.versions.toml index b97545928e..ba5762485c 100644 --- a/gradle/app.versions.toml +++ b/gradle/app.versions.toml @@ -3,4 +3,4 @@ minSdk = '24' targetSdk = '33' compileSdk = '33' versionName = '1.49.1' -versionCode = '312' \ No newline at end of file +versionCode = '321' \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index e7033fcba4..74234d85fc 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -17,6 +17,7 @@ coil = '2.2.0' lottie = '6.1.0' kermit = '2.0.0-RC4' androidxBrowser = "1.5.0" +revenueCat = "7.4.0" googlePlayReview = "2.0.1" @@ -86,6 +87,8 @@ gms-play-login = { module = "com.google.android.gms:play-services-auth", version google-play-review = { module = "com.google.android.play:review-ktx", version.ref = "googlePlayReview"} +revenuecat = { module = "com.revenuecat.purchases:purchases", version.ref = "revenueCat" } + firebase-bom = { module = "com.google.firebase:firebase-bom", version = "31.2.2" } firebase-messaging = { module = "com.google.firebase:firebase-messaging-ktx" } diff --git a/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj b/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj index 89cae2adef..a133a27b49 100644 --- a/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj +++ b/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj @@ -64,7 +64,7 @@ 2C0F3CFA2A80A42D00947C35 /* BadgeDetailsModalViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C0F3CF92A80A42D00947C35 /* BadgeDetailsModalViewController.swift */; }; 2C0F3CFC2A80A47600947C35 /* BadgeDetailsModalView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C0F3CFB2A80A47600947C35 /* BadgeDetailsModalView.swift */; }; 2C0F3CFF2A80AAA100947C35 /* BadgeLevelView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C0F3CFE2A80AAA100947C35 /* BadgeLevelView.swift */; }; - 2C0FA879292FD73400A37636 /* ProfileSettingsFeatureStateKsExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C0FA878292FD73400A37636 /* ProfileSettingsFeatureStateKsExtensions.swift */; }; + 2C0FA879292FD73400A37636 /* ProfileSettingsFeatureViewStateKsExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C0FA878292FD73400A37636 /* ProfileSettingsFeatureViewStateKsExtensions.swift */; }; 2C1061A2285C349400EBD614 /* StepQuizChildQuizAssembly.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C1061A1285C349400EBD614 /* StepQuizChildQuizAssembly.swift */; }; 2C1061A4285C34C900EBD614 /* StepQuizChildQuizOutputProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C1061A3285C34C900EBD614 /* StepQuizChildQuizOutputProtocol.swift */; }; 2C1061A8285C3A2D00EBD614 /* StepQuizChildQuizType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C1061A7285C3A2D00EBD614 /* StepQuizChildQuizType.swift */; }; @@ -95,6 +95,7 @@ 2C198E052AEA86DF00DCD35A /* StepQuizFillBlanksSelectOptionsCollectionViewCellContainerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C198E042AEA86DF00DCD35A /* StepQuizFillBlanksSelectOptionsCollectionViewCellContainerView.swift */; }; 2C198E082AEA8BB900DCD35A /* StepQuizFillBlanksSelectOptionsCollectionViewAdapter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C198E072AEA8BB900DCD35A /* StepQuizFillBlanksSelectOptionsCollectionViewAdapter.swift */; }; 2C198E0A2AEA904800DCD35A /* StepQuizFillBlanksSelectOptionsViewWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C198E092AEA904800DCD35A /* StepQuizFillBlanksSelectOptionsViewWrapper.swift */; }; + 2C1B71052B6CB7D9003FD4A1 /* OffsetObservingScrollView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C1B71042B6CB7D9003FD4A1 /* OffsetObservingScrollView.swift */; }; 2C1F5869280D063800372A37 /* WebViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C1F5868280D063800372A37 /* WebViewController.swift */; }; 2C1F586B280D094A00372A37 /* WebControllerManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C1F586A280D094A00372A37 /* WebControllerManager.swift */; }; 2C1F5870280D0CB700372A37 /* WebCacheCleaner.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C1F586F280D0CB700372A37 /* WebCacheCleaner.swift */; }; @@ -255,6 +256,8 @@ 2C725B5E28090D1F00A49043 /* View+Border.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C725B5D28090D1F00A49043 /* View+Border.swift */; }; 2C725B612809125700A49043 /* LayoutInsets.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C725B602809125700A49043 /* LayoutInsets.swift */; }; 2C725B632809198000A49043 /* BackgroundView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C725B622809198000A49043 /* BackgroundView.swift */; }; + 2C7271242B6B634F005628B0 /* View+Task.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C7271232B6B634F005628B0 /* View+Task.swift */; }; + 2C7271282B6B92AD005628B0 /* PaywallFooterView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C7271272B6B92AD005628B0 /* PaywallFooterView.swift */; }; 2C772E7D28ABB4E500A58758 /* AppleIDSocialAuthSDKProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C772E7C28ABB4E500A58758 /* AppleIDSocialAuthSDKProvider.swift */; }; 2C7802C5285C93F900082547 /* StepQuizActionButtonState+SubmissionStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C7802C4285C93F900082547 /* StepQuizActionButtonState+SubmissionStatus.swift */; }; 2C7822612942F9CF0067200F /* StreakBarButtonItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C7822602942F9CF0067200F /* StreakBarButtonItem.swift */; }; @@ -265,6 +268,7 @@ 2C7994AF2A1299B800874C16 /* TrackSelectionListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C7994AE2A1299B800874C16 /* TrackSelectionListView.swift */; }; 2C7994B12A129D6100874C16 /* TrackSelectionListSkeletonView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C7994B02A129D6100874C16 /* TrackSelectionListSkeletonView.swift */; }; 2C7A1B1F2922EB070018D72C /* Hyperskill-Mobile_shared.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C7A1B1E2922EB070018D72C /* Hyperskill-Mobile_shared.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; }; + 2C7C0D632B6B45A20093609D /* PaywallFeaturesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C7C0D622B6B45A20093609D /* PaywallFeaturesView.swift */; }; 2C7CB66B2ADFB947006F78DA /* StepQuizFillBlanksAssembly.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C7CB66A2ADFB947006F78DA /* StepQuizFillBlanksAssembly.swift */; }; 2C7CB66D2ADFB951006F78DA /* StepQuizFillBlanksViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C7CB66C2ADFB951006F78DA /* StepQuizFillBlanksViewModel.swift */; }; 2C7CB66F2ADFB96F006F78DA /* StepQuizFillBlanksView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C7CB66E2ADFB96F006F78DA /* StepQuizFillBlanksView.swift */; }; @@ -312,6 +316,7 @@ 2C919E3327EEF92F0022A2F2 /* LinkedListTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C919E3227EEF92F0022A2F2 /* LinkedListTests.swift */; }; 2C919E3527EEFF110022A2F2 /* Queue.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C919E3427EEFF110022A2F2 /* Queue.swift */; }; 2C919E3727EF00950022A2F2 /* QueueTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C919E3627EF00950022A2F2 /* QueueTests.swift */; }; + 2C9320F52B68F14100999992 /* PaywallView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C9320F42B68F14100999992 /* PaywallView.swift */; }; 2C93AF1F29B34A88004639E0 /* StepQuizPyCharmViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C93AF1E29B34A88004639E0 /* StepQuizPyCharmViewModel.swift */; }; 2C93AF2129B34C5A004639E0 /* StepQuizPyCharmViewDataMapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C93AF2029B34C5A004639E0 /* StepQuizPyCharmViewDataMapper.swift */; }; 2C93AF2329B34F66004639E0 /* StepQuizPyCharmView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C93AF2229B34F66004639E0 /* StepQuizPyCharmView.swift */; }; @@ -430,6 +435,7 @@ 2CBFB94A28897DBB0044D1BA /* StepQuizCodeFullScreenView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CBFB94928897DBB0044D1BA /* StepQuizCodeFullScreenView.swift */; }; 2CBFB94C28897DD70044D1BA /* StepQuizCodeFullScreenAssembly.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CBFB94B28897DD70044D1BA /* StepQuizCodeFullScreenAssembly.swift */; }; 2CC4AAF1280DB513002276A0 /* WebOAuthService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CC4AAF0280DB513002276A0 /* WebOAuthService.swift */; }; + 2CC63AEC2B70B25200407810 /* ProfileSettingsSubscriptionSectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CC63AEB2B70B25200407810 /* ProfileSettingsSubscriptionSectionView.swift */; }; 2CC7833E295DAE3E00A867CD /* WelcomeFeatureStateKsExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CC7833D295DAE3E00A867CD /* WelcomeFeatureStateKsExtensions.swift */; }; 2CC78D0928C74E7D0006EF91 /* UIViewControllerEventsWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CC78D0828C74E7D0006EF91 /* UIViewControllerEventsWrapper.swift */; }; 2CC78D0C28C74EF90006EF91 /* ViewRelatedEventsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CC78D0B28C74EF90006EF91 /* ViewRelatedEventsViewController.swift */; }; @@ -476,6 +482,7 @@ 2CE31F4827F1BB79008EEE66 /* AuthSocialAssembly.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CE31F4727F1BB79008EEE66 /* AuthSocialAssembly.swift */; }; 2CE31F4B27F1E070008EEE66 /* AppViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CE31F4A27F1E070008EEE66 /* AppViewModel.swift */; }; 2CE31F4D27F1E0C8008EEE66 /* AppAssembly.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CE31F4C27F1E0C8008EEE66 /* AppAssembly.swift */; }; + 2CE4F0732B71D358001FD376 /* SubscriptionDetailsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CE4F0722B71D358001FD376 /* SubscriptionDetailsView.swift */; }; 2CE58C5A2B07662300E5EBBE /* ChallengeWidgetContentStateProgressGridView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CE58C592B07662300E5EBBE /* ChallengeWidgetContentStateProgressGridView.swift */; }; 2CE58C5C2B0768F300E5EBBE /* ChallengeWidgetContentStateProgressGridItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CE58C5B2B0768F300E5EBBE /* ChallengeWidgetContentStateProgressGridItemView.swift */; }; 2CE601362B3345DD00E9CC46 /* ColorResource+UIColor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CE601352B3345DD00E9CC46 /* ColorResource+UIColor.swift */; }; @@ -759,7 +766,7 @@ 2C0F3CF92A80A42D00947C35 /* BadgeDetailsModalViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BadgeDetailsModalViewController.swift; sourceTree = ""; }; 2C0F3CFB2A80A47600947C35 /* BadgeDetailsModalView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BadgeDetailsModalView.swift; sourceTree = ""; }; 2C0F3CFE2A80AAA100947C35 /* BadgeLevelView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BadgeLevelView.swift; sourceTree = ""; }; - 2C0FA878292FD73400A37636 /* ProfileSettingsFeatureStateKsExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileSettingsFeatureStateKsExtensions.swift; sourceTree = ""; }; + 2C0FA878292FD73400A37636 /* ProfileSettingsFeatureViewStateKsExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileSettingsFeatureViewStateKsExtensions.swift; sourceTree = ""; }; 2C1061A1285C349400EBD614 /* StepQuizChildQuizAssembly.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StepQuizChildQuizAssembly.swift; sourceTree = ""; }; 2C1061A3285C34C900EBD614 /* StepQuizChildQuizOutputProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StepQuizChildQuizOutputProtocol.swift; sourceTree = ""; }; 2C1061A7285C3A2D00EBD614 /* StepQuizChildQuizType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StepQuizChildQuizType.swift; sourceTree = ""; }; @@ -791,6 +798,7 @@ 2C198E042AEA86DF00DCD35A /* StepQuizFillBlanksSelectOptionsCollectionViewCellContainerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StepQuizFillBlanksSelectOptionsCollectionViewCellContainerView.swift; sourceTree = ""; }; 2C198E072AEA8BB900DCD35A /* StepQuizFillBlanksSelectOptionsCollectionViewAdapter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StepQuizFillBlanksSelectOptionsCollectionViewAdapter.swift; sourceTree = ""; }; 2C198E092AEA904800DCD35A /* StepQuizFillBlanksSelectOptionsViewWrapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StepQuizFillBlanksSelectOptionsViewWrapper.swift; sourceTree = ""; }; + 2C1B71042B6CB7D9003FD4A1 /* OffsetObservingScrollView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OffsetObservingScrollView.swift; sourceTree = ""; }; 2C1F5868280D063800372A37 /* WebViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebViewController.swift; sourceTree = ""; }; 2C1F586A280D094A00372A37 /* WebControllerManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebControllerManager.swift; sourceTree = ""; }; 2C1F586F280D0CB700372A37 /* WebCacheCleaner.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebCacheCleaner.swift; sourceTree = ""; }; @@ -957,6 +965,8 @@ 2C725B5D28090D1F00A49043 /* View+Border.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "View+Border.swift"; sourceTree = ""; }; 2C725B602809125700A49043 /* LayoutInsets.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LayoutInsets.swift; sourceTree = ""; }; 2C725B622809198000A49043 /* BackgroundView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BackgroundView.swift; sourceTree = ""; }; + 2C7271232B6B634F005628B0 /* View+Task.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "View+Task.swift"; sourceTree = ""; }; + 2C7271272B6B92AD005628B0 /* PaywallFooterView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PaywallFooterView.swift; sourceTree = ""; }; 2C772E7C28ABB4E500A58758 /* AppleIDSocialAuthSDKProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppleIDSocialAuthSDKProvider.swift; sourceTree = ""; }; 2C7802C4285C93F900082547 /* StepQuizActionButtonState+SubmissionStatus.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "StepQuizActionButtonState+SubmissionStatus.swift"; sourceTree = ""; }; 2C7822602942F9CF0067200F /* StreakBarButtonItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StreakBarButtonItem.swift; sourceTree = ""; }; @@ -967,6 +977,7 @@ 2C7994AE2A1299B800874C16 /* TrackSelectionListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TrackSelectionListView.swift; sourceTree = ""; }; 2C7994B02A129D6100874C16 /* TrackSelectionListSkeletonView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TrackSelectionListSkeletonView.swift; sourceTree = ""; }; 2C7A1B1E2922EB070018D72C /* Hyperskill-Mobile_shared.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Hyperskill-Mobile_shared.swift"; sourceTree = ""; }; + 2C7C0D622B6B45A20093609D /* PaywallFeaturesView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PaywallFeaturesView.swift; sourceTree = ""; }; 2C7CB66A2ADFB947006F78DA /* StepQuizFillBlanksAssembly.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StepQuizFillBlanksAssembly.swift; sourceTree = ""; }; 2C7CB66C2ADFB951006F78DA /* StepQuizFillBlanksViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StepQuizFillBlanksViewModel.swift; sourceTree = ""; }; 2C7CB66E2ADFB96F006F78DA /* StepQuizFillBlanksView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StepQuizFillBlanksView.swift; sourceTree = ""; }; @@ -1015,6 +1026,7 @@ 2C919E3227EEF92F0022A2F2 /* LinkedListTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LinkedListTests.swift; sourceTree = ""; }; 2C919E3427EEFF110022A2F2 /* Queue.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Queue.swift; sourceTree = ""; }; 2C919E3627EF00950022A2F2 /* QueueTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QueueTests.swift; sourceTree = ""; }; + 2C9320F42B68F14100999992 /* PaywallView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PaywallView.swift; sourceTree = ""; }; 2C93AF1E29B34A88004639E0 /* StepQuizPyCharmViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StepQuizPyCharmViewModel.swift; sourceTree = ""; }; 2C93AF2029B34C5A004639E0 /* StepQuizPyCharmViewDataMapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StepQuizPyCharmViewDataMapper.swift; sourceTree = ""; }; 2C93AF2229B34F66004639E0 /* StepQuizPyCharmView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StepQuizPyCharmView.swift; sourceTree = ""; }; @@ -1133,6 +1145,7 @@ 2CBFB94928897DBB0044D1BA /* StepQuizCodeFullScreenView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StepQuizCodeFullScreenView.swift; sourceTree = ""; }; 2CBFB94B28897DD70044D1BA /* StepQuizCodeFullScreenAssembly.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StepQuizCodeFullScreenAssembly.swift; sourceTree = ""; }; 2CC4AAF0280DB513002276A0 /* WebOAuthService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebOAuthService.swift; sourceTree = ""; }; + 2CC63AEB2B70B25200407810 /* ProfileSettingsSubscriptionSectionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileSettingsSubscriptionSectionView.swift; sourceTree = ""; }; 2CC7833D295DAE3E00A867CD /* WelcomeFeatureStateKsExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WelcomeFeatureStateKsExtensions.swift; sourceTree = ""; }; 2CC78D0828C74E7D0006EF91 /* UIViewControllerEventsWrapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIViewControllerEventsWrapper.swift; sourceTree = ""; }; 2CC78D0B28C74EF90006EF91 /* ViewRelatedEventsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewRelatedEventsViewController.swift; sourceTree = ""; }; @@ -1179,6 +1192,7 @@ 2CE31F4727F1BB79008EEE66 /* AuthSocialAssembly.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthSocialAssembly.swift; sourceTree = ""; }; 2CE31F4A27F1E070008EEE66 /* AppViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppViewModel.swift; sourceTree = ""; }; 2CE31F4C27F1E0C8008EEE66 /* AppAssembly.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppAssembly.swift; sourceTree = ""; }; + 2CE4F0722B71D358001FD376 /* SubscriptionDetailsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubscriptionDetailsView.swift; sourceTree = ""; }; 2CE58C592B07662300E5EBBE /* ChallengeWidgetContentStateProgressGridView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChallengeWidgetContentStateProgressGridView.swift; sourceTree = ""; }; 2CE58C5B2B0768F300E5EBBE /* ChallengeWidgetContentStateProgressGridItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChallengeWidgetContentStateProgressGridItemView.swift; sourceTree = ""; }; 2CE601352B3345DD00E9CC46 /* ColorResource+UIColor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ColorResource+UIColor.swift"; sourceTree = ""; }; @@ -1644,7 +1658,7 @@ 2C186AD22B468DBE00DADB26 /* InterviewPreparationWidgetViewStateKsExtensions.swift */, E9B55A5429C8A03E0066900E /* ProblemsLimitFeatureViewStateKsExtensions.swift */, 2C0DFB992923BB4300D30921 /* ProfileFeatureStateKsExtensions.swift */, - 2C0FA878292FD73400A37636 /* ProfileSettingsFeatureStateKsExtensions.swift */, + 2C0FA878292FD73400A37636 /* ProfileSettingsFeatureViewStateKsExtensions.swift */, 2C5CA2362A20185300DBF2F9 /* ProjectSelectionDetailsFeatureViewStateKsExtensions.swift */, 2C05AC452A0E9EBC0039C7EF /* ProjectSelectionListFeatureViewStateKsExtensions.swift */, 2C306A0D29B4590C0068FF4F /* StageImplementFeatureViewStateKsExtensions.swift */, @@ -1767,6 +1781,7 @@ 58E45951DA331A5F7A54B0BA /* InterviewPreparationOnboarding */, E6992F3BBF430924F32DC178 /* Leaderboard */, B58361EACE24BF4B761F10BA /* NotificationsOnboarding */, + 2C9320F32B68F13000999992 /* Paywall */, E9B55A5329C89FFF0066900E /* ProblemsLimit */, 2C9EB95B2861BAAE007DDE44 /* Profile */, 2C963BC82812D3410036DD53 /* ProfileSettings */, @@ -1780,6 +1795,7 @@ 2CE7B4852AB05AA300DCBE4D /* StepQuizSubmodules */, E9F655CE2875B30B00291143 /* Streak */, E9F0A2A729D416AC00C4A61E /* StudyPlan */, + 2CE4F0712B71D347001FD376 /* SubscriptionDetails */, E9A022AB291D0E1C004317DB /* TopicsRepetitions */, 2C2600862A2001E600BD3D39 /* TrackSelection */, 2CB559302B87024000D949CB /* UsersQuestionnaire */, @@ -1998,6 +2014,7 @@ E9FAF38E299F61AE001FC596 /* View+MeasureSize.swift */, 2CD4EDF82B79D51E0091F0B2 /* View+SafeAreaInset.swift */, 2C4605B02ABD75FC003C17E9 /* View+ScrollBounceBehavior.swift */, + 2C7271232B6B634F005628B0 /* View+Task.swift */, ); path = View; sourceTree = ""; @@ -2755,6 +2772,16 @@ path = iosHyperskillAppTests; sourceTree = ""; }; + 2C9320F32B68F13000999992 /* Paywall */ = { + isa = PBXGroup; + children = ( + 2C7C0D622B6B45A20093609D /* PaywallFeaturesView.swift */, + 2C7271272B6B92AD005628B0 /* PaywallFooterView.swift */, + 2C9320F42B68F14100999992 /* PaywallView.swift */, + ); + path = Paywall; + sourceTree = ""; + }; 2C93AF1D29B349AC004639E0 /* StepQuizPyCharm */ = { isa = PBXGroup; children = ( @@ -2811,9 +2838,9 @@ isa = PBXGroup; children = ( 2C963BCB2812D9330036DD53 /* ProfileSettingsAssembly.swift */, - 2C963BC92812D3550036DD53 /* ProfileSettingsView.swift */, E9F59B8F289FE053001CEA02 /* ProfileSettingsViewModel.swift */, 2C106D9A28C1CFA1004FA584 /* Controllers */, + 2CC63AED2B70B25800407810 /* Views */, ); path = ProfileSettings; sourceTree = ""; @@ -3230,6 +3257,7 @@ 2C43CDF828B55CC600E74762 /* HyperskillLogoView.swift */, E9D537D12A71330A00F21828 /* LinearGradientProgressView.swift */, 2C32375228380C340062CAF6 /* NavigationToolbarInfoItem.swift */, + 2C1B71042B6CB7D9003FD4A1 /* OffsetObservingScrollView.swift */, E9C3506E2886D0600080D277 /* OpenURLInsideAppButton.swift */, E9101712283296F3002E70F5 /* RadioButton.swift */, 2CAF254B2AB9C2E500595582 /* ShineEffect.swift */, @@ -3271,6 +3299,15 @@ path = Web; sourceTree = ""; }; + 2CC63AED2B70B25800407810 /* Views */ = { + isa = PBXGroup; + children = ( + 2CC63AEB2B70B25200407810 /* ProfileSettingsSubscriptionSectionView.swift */, + 2C963BC92812D3550036DD53 /* ProfileSettingsView.swift */, + ); + path = Views; + sourceTree = ""; + }; 2CC78D0A28C74EAF0006EF91 /* UIViewControllerEvents */ = { isa = PBXGroup; children = ( @@ -3467,6 +3504,14 @@ path = App; sourceTree = ""; }; + 2CE4F0712B71D347001FD376 /* SubscriptionDetails */ = { + isa = PBXGroup; + children = ( + 2CE4F0722B71D358001FD376 /* SubscriptionDetailsView.swift */, + ); + path = SubscriptionDetails; + sourceTree = ""; + }; 2CE58C5D2B07690700E5EBBE /* ProgressGrid */ = { isa = PBXGroup; children = ( @@ -4531,6 +4576,7 @@ E94BB04C2A9DFCCF00736B7C /* StepQuizParsonsViewData.swift in Sources */, 2C3100532AB194A200C09BFB /* StepQuizParsonsViewDataMapperCodeContentCache.swift in Sources */, 2CE8EE712B066D05004EB545 /* ChallengeWidgetViewStateContentCollectRewardButtonStateKsExtensions.swift in Sources */, + 2CE4F0732B71D358001FD376 /* SubscriptionDetailsView.swift in Sources */, 2CDA984929445C0A00ADE539 /* ProfileStatisticsItemView.swift in Sources */, E9F655D12875B32700291143 /* ProblemOfDayCardView.swift in Sources */, E99CD0BC292B9A2D00620259 /* TopicsRepetitionsViewModel.swift in Sources */, @@ -4582,6 +4628,7 @@ 2C0DFB9A2923BB4300D30921 /* ProfileFeatureStateKsExtensions.swift in Sources */, E9C3506F2886D0600080D277 /* OpenURLInsideAppButton.swift in Sources */, 2C23C0062879EA7D0083709F /* StreakDayState.swift in Sources */, + 2C7271242B6B634F005628B0 /* View+Task.swift in Sources */, 2C963BC72812D1BF0036DD53 /* HomeAssembly.swift in Sources */, 2C0409842B863EA600E9CF41 /* Publishers+KeyboardIsVisible.swift in Sources */, 2C1860FC2923C540007D4EBF /* AppFeatureStateKsExtensions.swift in Sources */, @@ -4629,6 +4676,7 @@ 2C11D5CA2A11311900C59238 /* FeedbackGeneratorPreviewView.swift in Sources */, 2C971B852AC2F5DC00868FCE /* StepExpandableStepTextView.swift in Sources */, 2CB279AF28C72AA400EDDCC8 /* DeepLinkRouterProtocol.swift in Sources */, + 2C7271282B6B92AD005628B0 /* PaywallFooterView.swift in Sources */, 2C023C86285D927A00D2D5A9 /* StepQuizTableAssembly.swift in Sources */, 2C20FBC4284F67F3006D879E /* ProcessedContentWebView.swift in Sources */, 2C4F639B2A101DCE00D4EE39 /* ProjectSelectionListGridView.swift in Sources */, @@ -4767,6 +4815,7 @@ 2C3796122877001700C197E2 /* ProfileHeaderView.swift in Sources */, 2C725B5E28090D1F00A49043 /* View+Border.swift in Sources */, 2C6672092A5297250040EA2F /* ProgressScreenProjectProgressSkeletonView.swift in Sources */, + 2C1B71052B6CB7D9003FD4A1 /* OffsetObservingScrollView.swift in Sources */, 2C8E4F9C2848A1550011ADFA /* PanModalViewModifier.swift in Sources */, 2CA7B88F2893295A00A789EF /* CodeEditorSuggestionsPresentationContextProviding.swift in Sources */, 2C8E4F9A284897360011ADFA /* PanModalSwiftUIViewController.swift in Sources */, @@ -4907,6 +4956,7 @@ 2CD48D8B2858684100CFCC4A /* StepQuizViewData.swift in Sources */, 2CB0ADF52B04BC8E0089D557 /* ChallengeWidgetAssembly.swift in Sources */, 2C05AC572A0EC9E50039C7EF /* ProjectSelectionListHeaderSkeletonView.swift in Sources */, + 2CC63AEC2B70B25200407810 /* ProfileSettingsSubscriptionSectionView.swift in Sources */, E9FB89AC2893EA580011EFFB /* NotificationPermissionStatus.swift in Sources */, 2C4FBD8C2876C39C00ACA5C8 /* ProfileAboutView.swift in Sources */, 2C023C88285D928100D2D5A9 /* StepQuizTableViewModel.swift in Sources */, @@ -4942,7 +4992,7 @@ E94BB0482A9DF9DD00736B7C /* StepQuizParsonsView.swift in Sources */, E99CCB0B287E945300898BBF /* HomeViewModel.swift in Sources */, 2C7CB6782ADFD0E8006F78DA /* StepQuizFillBlanksViewDataMapper.swift in Sources */, - 2C0FA879292FD73400A37636 /* ProfileSettingsFeatureStateKsExtensions.swift in Sources */, + 2C0FA879292FD73400A37636 /* ProfileSettingsFeatureViewStateKsExtensions.swift in Sources */, 2C1061AA285C3C3300EBD614 /* StepQuizChoiceAssembly.swift in Sources */, 2CF72AA828477E0600E1C192 /* StepQuizTableRowView.swift in Sources */, 2C80D503288C5EBB00B2CD1E /* StepQuizCodeNavigationState.swift in Sources */, @@ -5052,6 +5102,7 @@ E91017152832975C002E70F5 /* CheckboxButton.swift in Sources */, 2C99B1002A14255F0018627B /* StudyPlanWidgetViewStateSectionItemStateWrapper.swift in Sources */, E9E964872A0B8D8200841DF6 /* StepQuizProblemsLimitView.swift in Sources */, + 2C9320F52B68F14100999992 /* PaywallView.swift in Sources */, 2C8CD9AE2994EFC5008DC09D /* DebugFeatureViewStateKsExtensions.swift in Sources */, 2C83FBC02B177F68007AD7E2 /* LeaderboardListView.swift in Sources */, 2C967432288824370091B6C9 /* StepQuizCodeViewModel.swift in Sources */, @@ -5073,6 +5124,7 @@ 59B66CD4D1508049555D35AE /* ProgressScreenView.swift in Sources */, 2CE7B4842AB0593F00DCBE4D /* AttributedTextLabelWrapper.swift in Sources */, 2CE8EE6D2B065F00004EB545 /* ChallengeWidgetContentStateDeadlineView.swift in Sources */, + 2C7C0D632B6B45A20093609D /* PaywallFeaturesView.swift in Sources */, F759010A5FC990E99AAF0D76 /* ProgressScreenViewModel.swift in Sources */, DA48146596C2AB3F4E68208E /* NotificationsOnboardingAssembly.swift in Sources */, B2B30D0486FC13DCC80F4263 /* NotificationsOnboardingView.swift in Sources */, diff --git a/iosHyperskillApp/iosHyperskillApp/Assets.xcassets/Paywall/Contents.json b/iosHyperskillApp/iosHyperskillApp/Assets.xcassets/Paywall/Contents.json new file mode 100644 index 0000000000..73c00596a7 --- /dev/null +++ b/iosHyperskillApp/iosHyperskillApp/Assets.xcassets/Paywall/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/iosHyperskillApp/iosHyperskillApp/Assets.xcassets/Paywall/paywall-premium-mobile.imageset/Contents.json b/iosHyperskillApp/iosHyperskillApp/Assets.xcassets/Paywall/paywall-premium-mobile.imageset/Contents.json new file mode 100644 index 0000000000..6bcbd4bb0e --- /dev/null +++ b/iosHyperskillApp/iosHyperskillApp/Assets.xcassets/Paywall/paywall-premium-mobile.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "paywall-premium-mobile.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "paywall-premium-mobile@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "paywall-premium-mobile@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/iosHyperskillApp/iosHyperskillApp/Assets.xcassets/Paywall/paywall-premium-mobile.imageset/paywall-premium-mobile.png b/iosHyperskillApp/iosHyperskillApp/Assets.xcassets/Paywall/paywall-premium-mobile.imageset/paywall-premium-mobile.png new file mode 100644 index 0000000000000000000000000000000000000000..c8cc5f58efe403270e61428ffbd0af80442018e3 GIT binary patch literal 10440 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 XYAyA+#<^5!00000NkvXXu0mjfDZKfw literal 0 HcmV?d00001 diff --git a/iosHyperskillApp/iosHyperskillApp/Assets.xcassets/Paywall/paywall-premium-mobile.imageset/paywall-premium-mobile@3x.png b/iosHyperskillApp/iosHyperskillApp/Assets.xcassets/Paywall/paywall-premium-mobile.imageset/paywall-premium-mobile@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..9a6184b873de0be022847429b449c21ebfe15339 GIT binary patch literal 56799 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%0DXf0jEC!`+b0C*BESr~4+;aci_KlR*wi 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 Void + + @State var task: Task? + + func body(content: Content) -> some View { + content + .onAppear { + if task != nil { + task?.cancel() + } + task = Task(priority: priority, operation: action) + } + .onDisappear { + task?.cancel() + } + } +} + +extension View { + @ViewBuilder + @available(iOS, deprecated: 15.0) + func taskCompatibility( + priority: TaskPriority = .userInitiated, + _ action: @escaping @Sendable () async -> Void + ) -> some View { + if #available(iOS 15.0, *) { + self.task(priority: priority, action) + } else { + self.modifier(TaskCompatibilityModifier(priority: priority, action: action)) + } + } +} diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Frameworks/sharedSwift/Extensions/ProfileSettingsFeatureStateKsExtensions.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Frameworks/sharedSwift/Extensions/ProfileSettingsFeatureViewStateKsExtensions.swift similarity index 53% rename from iosHyperskillApp/iosHyperskillApp/Sources/Frameworks/sharedSwift/Extensions/ProfileSettingsFeatureStateKsExtensions.swift rename to iosHyperskillApp/iosHyperskillApp/Sources/Frameworks/sharedSwift/Extensions/ProfileSettingsFeatureViewStateKsExtensions.swift index ae22a8b685..7b085e223b 100644 --- a/iosHyperskillApp/iosHyperskillApp/Sources/Frameworks/sharedSwift/Extensions/ProfileSettingsFeatureStateKsExtensions.swift +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Frameworks/sharedSwift/Extensions/ProfileSettingsFeatureViewStateKsExtensions.swift @@ -1,41 +1,27 @@ import Foundation import shared -extension ProfileSettingsFeatureStateKs: Equatable { - public static func == (lhs: ProfileSettingsFeatureStateKs, rhs: ProfileSettingsFeatureStateKs) -> Bool { +extension ProfileSettingsFeatureViewStateKs: Equatable { + public static func == (lhs: ProfileSettingsFeatureViewStateKs, rhs: ProfileSettingsFeatureViewStateKs) -> Bool { switch (lhs, rhs) { case (.idle, .idle): return true case (.loading, .loading): return true - case (.error, .error): - return true case (.content(let lhsData), .content(let rhsData)): return lhsData.isEqual(rhsData) case (.content, .idle): return false case (.content, .loading): return false - case (.content, .error): - return false - case (.error, .idle): - return false - case (.error, .loading): - return false - case (.error, .content): - return false case (.loading, .idle): return false case (.loading, .content): return false - case (.loading, .error): - return false case (.idle, .loading): return false case (.idle, .content): return false - case (.idle, .error): - return false } } } diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Models/Constants/Strings.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Models/Constants/Strings.swift index 75feea9b89..60a0a9ae22 100644 --- a/iosHyperskillApp/iosHyperskillApp/Sources/Models/Constants/Strings.swift +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Models/Constants/Strings.swift @@ -385,6 +385,8 @@ enum Strings { static let rateInAppStore = sharedStrings.settings_rate_in_app_store.localized() static let rateInAppStoreURL = sharedStrings.settings_rate_in_app_store_url.localized() + static let subscription = sharedStrings.settings_subscription.localized() + enum Theme { static let title = sharedStrings.settings_theme.localized() diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/App/AppViewModel.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/App/AppViewModel.swift index 77aa7aeb2e..e70d3d9895 100644 --- a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/App/AppViewModel.swift +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/App/AppViewModel.swift @@ -212,6 +212,13 @@ private extension AppViewModel { name: .attAuthorizationStatusDidChange, object: nil ) + + notificationCenter.addObserver( + self, + selector: #selector(handleApplicationWillEnterForeground), + name: UIApplication.willEnterForegroundNotification, + object: UIApplication.shared + ) } @objc @@ -272,6 +279,12 @@ AppViewModel: \(#function) PushNotificationData not found in userInfo = \(String let isAuthorized = authorizationStatus == .authorized analytic.setAppTrackingTransparencyAuthorizationStatus(isAuthorized: isAuthorized) } + + @objc + private func handleApplicationWillEnterForeground() { + #warning("Enable when subscription purchase is implemented") + //onNewMessage(AppFeatureMessageAppBecomesActive()) + } } // MARK: - AppViewModel: StreakRecoveryModalDelegate - diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/App/ViewControllers/AppViewController.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/App/ViewControllers/AppViewController.swift index 79127c5021..e5b7e2bdd8 100644 --- a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/App/ViewControllers/AppViewController.swift +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/App/ViewControllers/AppViewController.swift @@ -105,12 +105,18 @@ extension AppViewController: AppViewControllerProtocol { switch viewAction { case .authScreen(let data): router.route(.auth(isInSignUpMode: data.isInSignUpMode, moduleOutput: viewModel)) - case .onboardingScreen: + case .welcomeScreen: router.route(.onboarding(moduleOutput: viewModel)) case .studyPlan: router.route(.studyPlan(appTabBarControllerDelegate: viewModel)) case .trackSelectionScreen: router.route(.trackSelection) + case .paywall: + #warning("TODO: ALTAPPS-1116; implement paywall Route") + router.route(.studyPlan(appTabBarControllerDelegate: viewModel)) + case .studyPlanWithPaywall: + #warning("TODO: ALTAPPS-1116; implent studyPlanWithPaywall Route") + router.route(.studyPlan(appTabBarControllerDelegate: viewModel)) } } @@ -222,6 +228,9 @@ extension AppViewController: AppViewControllerProtocol { router.route(.studyPlanWithStep(appTabBarControllerDelegate: viewModel, stepRoute: data.stepRoute)) case .usersQuestionnaireOnboardingScreen: router.route(.usersQuestionnaireOnboarding(moduleOutput: viewModel)) + case .paywall: + #warning("TODO: ALTAPPS-1116") + router.route(.studyPlan(appTabBarControllerDelegate: viewModel)) } } } diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/Home/HomeViewModel.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/Home/HomeViewModel.swift index ee106cc42c..65f6c8217f 100644 --- a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/Home/HomeViewModel.swift +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/Home/HomeViewModel.swift @@ -2,7 +2,6 @@ import shared import UIKit final class HomeViewModel: FeatureViewModel { - private var applicationWasInBackground = false private var shouldReloadContent = false var homeStateKs: HomeFeatureHomeStateKs { .init(state.homeState) } @@ -20,14 +19,8 @@ final class HomeViewModel: FeatureViewModel { @@ -17,7 +17,7 @@ final class ProfileSettingsViewModel: FeatureViewModel< // It's impossible to handle onTap on `Picker`, so using `onAppear` callback with debouncer. private let analyticLogClickedThemeEventDebouncer: DebouncerProtocol = Debouncer() - var stateKs: ProfileSettingsFeatureStateKs { .init(state) } + var stateKs: ProfileSettingsFeatureViewStateKs { .init(state) } init( applicationThemeService: ApplicationThemeServiceProtocol, @@ -26,18 +26,14 @@ final class ProfileSettingsViewModel: FeatureViewModel< self.applicationThemeService = applicationThemeService super.init(feature: feature) - onNewMessage(ProfileSettingsFeatureMessageInitMessage(forceUpdate: false)) + onNewMessage(ProfileSettingsFeatureMessageInitMessage()) } override func shouldNotifyStateDidChange( - oldState: ProfileSettingsFeatureState, - newState: ProfileSettingsFeatureState + oldState: ProfileSettingsFeatureViewState, + newState: ProfileSettingsFeatureViewState ) -> Bool { - ProfileSettingsFeatureStateKs(oldState) != ProfileSettingsFeatureStateKs(newState) - } - - func doRetryLoadProfileSettings() { - onNewMessage(ProfileSettingsFeatureMessageInitMessage(forceUpdate: true)) + ProfileSettingsFeatureViewStateKs(oldState) != ProfileSettingsFeatureViewStateKs(newState) } func doThemeChange(newTheme: ApplicationTheme) { diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/ProfileSettings/Views/ProfileSettingsSubscriptionSectionView.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/ProfileSettings/Views/ProfileSettingsSubscriptionSectionView.swift new file mode 100644 index 0000000000..7da4f2183a --- /dev/null +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/ProfileSettings/Views/ProfileSettingsSubscriptionSectionView.swift @@ -0,0 +1,41 @@ +import SwiftUI + +struct ProfileSettingsSubscriptionSectionView: View { + let description: String + + let action: () -> Void + + var body: some View { + Section(header: Text(Strings.Settings.subscription)) { + Button( + action: { action() }, + label: { + HStack(spacing: 0) { + Text(description) + .frame(maxWidth: .infinity, alignment: .leading) + .layoutPriority(1) + Spacer() + NavigationLink.empty + } + } + ) + .accentColor(.primaryText) + } + } +} + +#Preview { + NavigationView { + Form { + ProfileSettingsSubscriptionSectionView( + description: "Try Mobile only plan for $12.00", + action: {} + ) + + ProfileSettingsSubscriptionSectionView( + description: "Mobile only", + action: {} + ) + } + } +} diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/ProfileSettings/ProfileSettingsView.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/ProfileSettings/Views/ProfileSettingsView.swift similarity index 90% rename from iosHyperskillApp/iosHyperskillApp/Sources/Modules/ProfileSettings/ProfileSettingsView.swift rename to iosHyperskillApp/iosHyperskillApp/Sources/Modules/ProfileSettings/Views/ProfileSettingsView.swift index 0f71ffe6b9..b380734da5 100644 --- a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/ProfileSettings/ProfileSettingsView.swift +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/ProfileSettings/Views/ProfileSettingsView.swift @@ -49,20 +49,33 @@ struct ProfileSettingsView: View { switch viewModel.stateKs { case .idle, .loading: ProgressView() - case .error: - PlaceholderView(configuration: .networkError(action: viewModel.doRetryLoadProfileSettings)) case .content(let content): if content.isLoadingMagicLink { let _ = ProgressHUD.show() } - buildContent(profileSettings: content.profileSettings) + buildContent( + profileSettings: content.profileSettings, + subscriptionState: content.subscriptionState + ) } } @ViewBuilder - private func buildContent(profileSettings: ProfileSettings) -> some View { + private func buildContent( + profileSettings: ProfileSettings, + subscriptionState: ProfileSettingsFeatureViewStateContent.SubscriptionState? + ) -> some View { Form { + if let subscriptionState { + ProfileSettingsSubscriptionSectionView( + description: subscriptionState.description_, + action: { + #warning("TODO: ALTAPPS-1138") + } + ) + } + Section(header: Text(Strings.Settings.appearance)) { Picker( Strings.Settings.Theme.title, @@ -207,13 +220,16 @@ struct ProfileSettingsView: View { switch ProfileSettingsFeatureActionViewActionNavigateToKs(navigateToViewAction) { case .parentScreen: presentationMode.wrappedValue.dismiss() + case .paywall: + #warning("TODO: ALTAPPS-1126") + case .subscriptionManagement: + #warning("TODO: ALTAPPS-1132") } } } } -struct ProfileSettingsView_Previews: PreviewProvider { - static var previews: some View { - ProfileSettingsAssembly().makeModule() - } +@available(iOS 15.0, *) +#Preview { + ProfileSettingsAssembly().makeModule() } diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuiz/Views/StepQuizView.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuiz/Views/StepQuizView.swift index 9f1d826e73..f359746b66 100644 --- a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuiz/Views/StepQuizView.swift +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuiz/Views/StepQuizView.swift @@ -321,6 +321,8 @@ struct StepQuizView: View { TabBarRouter(tab: .studyPlan).route() } ) + case .paywall: + #warning("TODO: ALTAPPS-1121") } case .stepQuizHintsViewAction(let stepQuizHintsViewAction): switch StepQuizHintsFeatureActionViewActionKs(stepQuizHintsViewAction.viewAction) { diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/SubscriptionDetails/SubscriptionDetailsView.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/SubscriptionDetails/SubscriptionDetailsView.swift new file mode 100644 index 0000000000..c642c4ca1f --- /dev/null +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/SubscriptionDetails/SubscriptionDetailsView.swift @@ -0,0 +1,81 @@ +import SwiftUI + +extension SubscriptionDetailsView { + enum Appearance { + static let spacing = LayoutInsets.defaultInset + LayoutInsets.smallInset + static let interItemSpacing = LayoutInsets.smallInset + + static let noticeBadgeAppearance = BadgeView.Appearance( + cornerRadius: LayoutInsets.defaultInset / 2, + insets: .default, + font: .body + ) + } +} + +struct SubscriptionDetailsView: View { + var body: some View { + ScrollView(.vertical, showsIndicators: false) { + VStack(spacing: Appearance.spacing) { + header + details + notice + } + .padding() + } + .scrollBounceBehaviorBasedOnSize() + .safeAreaInsetBottomCompatibility(footer) + } + + private var header: some View { + VStack(alignment: .leading, spacing: Appearance.interItemSpacing) { + Text("Your current plan:") + .font(.callout) + Text("Hyperskill Mobile only") + .font(.title.bold()) + Text("Valid until January 27, 2024, 02:00") + .font(.body) + } + .foregroundColor(.newPrimaryText) + .frame(maxWidth: .infinity, alignment: .leading) + } + + private var details: some View { + VStack(alignment: .leading, spacing: Appearance.interItemSpacing) { + Text("Plan details:") + .font(.callout.bold()) + PaywallFeaturesView() + } + } + + private var notice: some View { + BadgeView( + appearance: Appearance.noticeBadgeAppearance, + text: """ +Please be aware that with the Mobile оnly plan on hyperskill.org, \ +there is a limit on the number of problems you can solve per day. +""", + style: .blue + ) + } + + @MainActor private var footer: some View { + Button( + "Manage subscription", + action: { + FeedbackGenerator(feedbackType: .selection).triggerFeedback() + } + ) + .buttonStyle(RoundedRectangleButtonStyle(style: .violet)) + .padding() + .background( + TransparentBlurView() + .edgesIgnoringSafeArea(.all) + ) + .fixedSize(horizontal: false, vertical: true) + } +} + +#Preview { + SubscriptionDetailsView() +} diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Views/SwiftUI/BadgeView/BadgeView.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Views/SwiftUI/BadgeView/BadgeView.swift index a62d44f77d..2672743ba4 100644 --- a/iosHyperskillApp/iosHyperskillApp/Sources/Views/SwiftUI/BadgeView/BadgeView.swift +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Views/SwiftUI/BadgeView/BadgeView.swift @@ -2,9 +2,10 @@ import SwiftUI extension BadgeView { struct Appearance { - let cornerRadius: CGFloat = 4 + var cornerRadius: CGFloat = 4 + var insets = LayoutInsets(horizontal: 8, vertical: 4) - let insets = LayoutInsets(horizontal: 8, vertical: 4) + var font = Font.caption } } @@ -17,7 +18,7 @@ struct BadgeView: View { var body: some View { Text(text) - .font(.caption) + .font(appearance.font) .foregroundColor(style.foregroundColor) .padding(appearance.insets.edgeInsets) .background(style.backgroundColor) diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Views/SwiftUI/OffsetObservingScrollView.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Views/SwiftUI/OffsetObservingScrollView.swift new file mode 100644 index 0000000000..e4bf932d39 --- /dev/null +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Views/SwiftUI/OffsetObservingScrollView.swift @@ -0,0 +1,166 @@ +import SwiftUI + +// https://www.swiftbysundell.com/articles/observing-swiftui-scrollview-content-offset/ + +// MARK: - PositionObservingView - + +/// View that observes its position within a given coordinate space, and assigns that position to the specified Binding. +private struct PositionObservingView: View { + var coordinateSpace: CoordinateSpace + + @Binding var position: CGPoint + + @ViewBuilder var content: () -> Content + + var body: some View { + content() + .background( + GeometryReader { geometry in + Color.clear.preference( + key: PreferenceKey.self, + value: geometry.frame(in: coordinateSpace).origin + ) + } + ) + .onPreferenceChange(PreferenceKey.self) { position in + self.position = position + } + } +} + +private extension PositionObservingView { + enum PreferenceKey: SwiftUI.PreferenceKey { + static var defaultValue: CGPoint { .zero } + + static func reduce(value: inout CGPoint, nextValue: () -> CGPoint) { + // No-op + } + } +} + +// MARK: - OffsetObservingScrollView - + +/// Specialized scroll view that observes its content offset (scroll position) and assigns it to the specified Binding. +struct OffsetObservingScrollView: View { + var axes: Axis.Set = [.vertical] + + var showsIndicators = true + + @Binding var offset: CGPoint + + @ViewBuilder var content: () -> Content + // The name of our coordinate space doesn't have to be + // stable between view updates (it just needs to be consistent within this view), + // so we'll simply use a plain UUID for it: + private let coordinateSpaceName = UUID() + + var body: some View { + ScrollView(axes, showsIndicators: showsIndicators) { + PositionObservingView( + coordinateSpace: .named(coordinateSpaceName), + position: Binding( + get: { offset }, + set: { newOffset in + offset = CGPoint( + x: -newOffset.x, + y: -newOffset.y + ) + } + ), + content: content + ) + } + .coordinateSpace(name: coordinateSpaceName) + } +} + +// MARK: - Preview - + +#if DEBUG +/// View that renders scrollable content beneath a header that +/// automatically collapses when the user scrolls down. +@available(iOS 15.0, *) +struct ContentView: View { + var title: String + var headerGradient: Gradient + @ViewBuilder var content: () -> Content + + private let headerHeight = (collapsed: 50.0, expanded: 150.0) + @State private var scrollOffset = CGPoint() + + var body: some View { + GeometryReader { geometry in + OffsetObservingScrollView(offset: $scrollOffset) { + VStack(spacing: 0) { + makeHeaderText(collapsed: false) + content() + } + } + .overlay(alignment: .top) { + makeHeaderText(collapsed: true) + .background(alignment: .top) { + headerLinearGradient.ignoresSafeArea() + } + .opacity(collapsedHeaderOpacity) + } + .background(alignment: .top) { + // We attach the expanded header's background to the scroll + // view itself, so that we can make it expand into both the + // safe area, as well as any negative scroll offset area: + headerLinearGradient + .frame(height: max(0, headerHeight.expanded - scrollOffset.y) + geometry.safeAreaInsets.top) + .ignoresSafeArea() + } + } + } +} + +@available(iOS 15.0, *) +private extension ContentView { + var collapsedHeaderOpacity: CGFloat { + let minOpacityOffset = headerHeight.expanded / 2 + let maxOpacityOffset = headerHeight.expanded - headerHeight.collapsed + + guard scrollOffset.y > minOpacityOffset else { + return 0 + } + guard scrollOffset.y < maxOpacityOffset else { + return 1 + } + + let opacityOffsetRange = maxOpacityOffset - minOpacityOffset + return (scrollOffset.y - minOpacityOffset) / opacityOffsetRange + } + + var headerLinearGradient: LinearGradient { + LinearGradient( + gradient: headerGradient, + startPoint: .top, + endPoint: .bottom + ) + } + + func makeHeaderText(collapsed: Bool) -> some View { + Text(title) + .font(collapsed ? .body : .title) + .lineLimit(1) + .padding() + .frame(height: collapsed ? headerHeight.collapsed : headerHeight.expanded) + .frame(maxWidth: .infinity) + .foregroundColor(.white) + .accessibilityHeading(.h1) + .accessibilityHidden(collapsed) + } +} + +@available(iOS 15.0, *) +#Preview { + ContentView( + title: "Test content offset", + headerGradient: Gradient(colors: [.yellow, .indigo]), + content: { + Text("Test") + } + ) +} +#endif diff --git a/shared/build.gradle.kts b/shared/build.gradle.kts index ec64ad9c79..29a40158c8 100644 --- a/shared/build.gradle.kts +++ b/shared/build.gradle.kts @@ -1,3 +1,4 @@ +import com.android.build.api.dsl.LibraryBuildType import com.codingfeline.buildkonfig.compiler.FieldSpec.Type.BOOLEAN import com.codingfeline.buildkonfig.compiler.FieldSpec.Type.STRING import java.time.Year @@ -7,6 +8,7 @@ import org.jetbrains.dokka.base.DokkaBaseConfiguration import org.jetbrains.dokka.gradle.DokkaTask import org.jetbrains.kotlin.gradle.plugin.mpp.Framework import org.jetbrains.kotlin.gradle.tasks.KotlinNativeLink +import org.jetbrains.kotlin.konan.properties.Properties import org.jetbrains.kotlin.konan.properties.loadProperties import org.jetbrains.kotlin.konan.properties.propertyString @@ -90,6 +92,7 @@ kotlin { implementation(libs.android.parcelable) implementation(project.dependencies.platform(libs.firebase.bom)) implementation(libs.firebase.messaging) + implementation(libs.revenuecat) implementation(libs.kermit) } } @@ -154,6 +157,26 @@ android { sourceSets { getByName("main").java.srcDirs("build/generated/moko/androidMain/src") } + + buildTypes { + fun applyFlavorConfigsFromFile(libraryBuildType: LibraryBuildType) { + if (SystemProperties.isCI() && !SystemProperties.isGitCryptUnlocked()) return + val properties: Properties = + loadProperties("${project.rootDir}/shared/src/androidMain/keys/revenuecat.properties") + properties.keys.forEach { name -> + name as String + libraryBuildType.buildConfigField( + type = "String", + name = name, + value = requireNotNull(properties.propertyString(name)) + ) + } + } + + all { + applyFlavorConfigsFromFile(this) + } + } } buildkonfig { diff --git a/shared/src/androidMain/keys/revenuecat.properties b/shared/src/androidMain/keys/revenuecat.properties new file mode 100644 index 0000000000000000000000000000000000000000..697732a372cb1db78f34b3894c638a09b71bd643 GIT binary patch literal 83 zcmV-Z0IdH2M@dveQdv+`0O2)Y%m+w#FQeqIG6^{jW!AuiNap;+kCH?bkod>C4ZcXI p@#q^aYtRW`1x0e$B+C^5-nd8cj#^kA^M`Z~m%|H8_C&F^q@>)yCNcm3 literal 0 HcmV?d00001 diff --git a/shared/src/androidMain/kotlin/org/hyperskill/app/core/domain/platform/Platform.kt b/shared/src/androidMain/kotlin/org/hyperskill/app/core/domain/platform/Platform.kt index 51c02f56aa..5994210bca 100644 --- a/shared/src/androidMain/kotlin/org/hyperskill/app/core/domain/platform/Platform.kt +++ b/shared/src/androidMain/kotlin/org/hyperskill/app/core/domain/platform/Platform.kt @@ -12,4 +12,6 @@ actual class Platform actual constructor() { actual val feedbackName: String = "Android" actual val appNameResource: StringResource = SharedResources.strings.android_app_name + + actual val isSubscriptionPurchaseEnabled: Boolean = true } \ No newline at end of file diff --git a/shared/src/androidMain/kotlin/org/hyperskill/app/core/injection/CommonAndroidAppGraph.kt b/shared/src/androidMain/kotlin/org/hyperskill/app/core/injection/CommonAndroidAppGraph.kt index 1c423b6717..15ad60ff8a 100644 --- a/shared/src/androidMain/kotlin/org/hyperskill/app/core/injection/CommonAndroidAppGraph.kt +++ b/shared/src/androidMain/kotlin/org/hyperskill/app/core/injection/CommonAndroidAppGraph.kt @@ -1,6 +1,6 @@ package org.hyperskill.app.core.injection -import android.content.Context +import android.app.Application import org.hyperskill.app.auth.injection.AuthCredentialsComponent import org.hyperskill.app.auth.injection.AuthSocialComponent import org.hyperskill.app.auth.injection.PlatformAuthCredentialsComponent @@ -14,7 +14,10 @@ import org.hyperskill.app.home.injection.PlatformHomeComponent import org.hyperskill.app.interview_preparation_onboarding.injection.PlatformInterviewPreparationOnboardingComponent import org.hyperskill.app.leaderboard.injection.PlatformLeaderboardComponent import org.hyperskill.app.main.injection.PlatformMainComponent +import org.hyperskill.app.manage_subscription.injection.PlatformManageSubscriptionComponent import org.hyperskill.app.notifications_onboarding.injection.PlatformNotificationsOnboardingComponent +import org.hyperskill.app.paywall.domain.model.PaywallTransitionSource +import org.hyperskill.app.paywall.injection.PlatformPaywallComponent import org.hyperskill.app.play_services.injection.PlayServicesCheckerComponent import org.hyperskill.app.profile.injection.PlatformProfileComponent import org.hyperskill.app.profile.injection.ProfileComponent @@ -43,7 +46,7 @@ import org.hyperskill.app.welcome.injection.PlatformWelcomeComponent import org.hyperskill.app.welcome.injection.WelcomeComponent interface CommonAndroidAppGraph : AppGraph { - val context: Context + val application: Application val platformMainComponent: PlatformMainComponent @@ -110,4 +113,8 @@ interface CommonAndroidAppGraph : AppGraph { fun buildPlatformRequestReviewComponent( stepRoute: StepRoute ): PlatformRequestReviewComponent + + fun buildPlatformPaywallComponent(paywallTransitionSource: PaywallTransitionSource): PlatformPaywallComponent + + fun buildPlatformManageSubscriptionComponent(): PlatformManageSubscriptionComponent } \ No newline at end of file diff --git a/shared/src/androidMain/kotlin/org/hyperskill/app/core/injection/CommonAndroidAppGraphImpl.kt b/shared/src/androidMain/kotlin/org/hyperskill/app/core/injection/CommonAndroidAppGraphImpl.kt index 31c4d71af7..46e63158ec 100644 --- a/shared/src/androidMain/kotlin/org/hyperskill/app/core/injection/CommonAndroidAppGraphImpl.kt +++ b/shared/src/androidMain/kotlin/org/hyperskill/app/core/injection/CommonAndroidAppGraphImpl.kt @@ -1,5 +1,6 @@ package org.hyperskill.app.core.injection +import org.hyperskill.app.BuildConfig import org.hyperskill.app.auth.injection.AuthCredentialsComponent import org.hyperskill.app.auth.injection.AuthSocialComponent import org.hyperskill.app.auth.injection.PlatformAuthCredentialsComponent @@ -20,10 +21,15 @@ import org.hyperskill.app.interview_preparation_onboarding.injection.PlatformInt import org.hyperskill.app.interview_preparation_onboarding.injection.PlatformInterviewPreparationOnboardingComponentImpl import org.hyperskill.app.leaderboard.injection.PlatformLeaderboardComponent import org.hyperskill.app.leaderboard.injection.PlatformLeaderboardComponentImpl +import org.hyperskill.app.manage_subscription.injection.PlatformManageSubscriptionComponent +import org.hyperskill.app.manage_subscription.injection.PlatformManageSubscriptionComponentImpl import org.hyperskill.app.notification.remote.injection.AndroidPlatformPushNotificationsPlatformDataComponent import org.hyperskill.app.notification.remote.injection.PlatformPushNotificationsDataComponent import org.hyperskill.app.notifications_onboarding.injection.PlatformNotificationsOnboardingComponent import org.hyperskill.app.notifications_onboarding.injection.PlatformNotificationsOnboardingComponentImpl +import org.hyperskill.app.paywall.domain.model.PaywallTransitionSource +import org.hyperskill.app.paywall.injection.PlatformPaywallComponent +import org.hyperskill.app.paywall.injection.PlatformPaywallComponentImpl import org.hyperskill.app.play_services.injection.PlayServicesCheckerComponent import org.hyperskill.app.play_services.injection.PlayServicesCheckerComponentImpl import org.hyperskill.app.profile.injection.PlatformProfileComponent @@ -40,6 +46,9 @@ import org.hyperskill.app.project_selection.details.injection.ProjectSelectionDe import org.hyperskill.app.project_selection.list.injection.PlatformProjectSelectionListComponent import org.hyperskill.app.project_selection.list.injection.PlatformProjectSelectionListComponentImpl import org.hyperskill.app.project_selection.list.injection.ProjectSelectionListParams +import org.hyperskill.app.purchases.domain.AndroidPurchaseManager +import org.hyperskill.app.purchases.injection.PurchaseComponent +import org.hyperskill.app.purchases.injection.PurchaseComponentImpl import org.hyperskill.app.request_review.injection.PlatformRequestReviewComponent import org.hyperskill.app.request_review.injection.PlatformRequestReviewComponentImpl import org.hyperskill.app.search.injection.PlatformSearchComponent @@ -69,6 +78,14 @@ import org.hyperskill.app.welcome.injection.WelcomeComponent abstract class CommonAndroidAppGraphImpl : CommonAndroidAppGraph, BaseAppGraph() { + override fun buildPurchaseComponent(): PurchaseComponent = + PurchaseComponentImpl( + AndroidPurchaseManager( + application = application, + isDebugMode = BuildConfig.DEBUG + ) + ) + override fun buildPlatformAuthSocialWebViewComponent(): PlatformAuthSocialWebViewComponent = PlatformAuthSocialWebViewComponentImpl(authSocialComponent = buildAuthSocialComponent()) @@ -200,7 +217,7 @@ abstract class CommonAndroidAppGraphImpl : CommonAndroidAppGraph, BaseAppGraph() PlatformProgressScreenComponentImpl(buildProgressScreenComponent()) override fun buildPlayServicesCheckerComponent(): PlayServicesCheckerComponent = - PlayServicesCheckerComponentImpl(context, sentryComponent) + PlayServicesCheckerComponentImpl(application, sentryComponent) override fun buildPlatformPushNotificationsDataComponent(): PlatformPushNotificationsDataComponent = AndroidPlatformPushNotificationsPlatformDataComponent( @@ -244,4 +261,16 @@ abstract class CommonAndroidAppGraphImpl : CommonAndroidAppGraph, BaseAppGraph() PlatformRequestReviewComponentImpl( requestReviewComponent = buildRequestReviewModalComponent(stepRoute) ) + + override fun buildPlatformPaywallComponent( + paywallTransitionSource: PaywallTransitionSource + ): PlatformPaywallComponent = + PlatformPaywallComponentImpl( + paywallComponent = buildPaywallComponent(paywallTransitionSource) + ) + + override fun buildPlatformManageSubscriptionComponent(): PlatformManageSubscriptionComponent = + PlatformManageSubscriptionComponentImpl( + manageSubscriptionComponent = buildManageSubscriptionComponent() + ) } \ No newline at end of file diff --git a/shared/src/androidMain/kotlin/org/hyperskill/app/manage_subscription/injection/PlatformManageSubscriptionComponent.kt b/shared/src/androidMain/kotlin/org/hyperskill/app/manage_subscription/injection/PlatformManageSubscriptionComponent.kt new file mode 100644 index 0000000000..821b5b1a08 --- /dev/null +++ b/shared/src/androidMain/kotlin/org/hyperskill/app/manage_subscription/injection/PlatformManageSubscriptionComponent.kt @@ -0,0 +1,7 @@ +package org.hyperskill.app.manage_subscription.injection + +import org.hyperskill.app.core.injection.ReduxViewModelFactory + +interface PlatformManageSubscriptionComponent { + val reduxViewModelFactory: ReduxViewModelFactory +} \ No newline at end of file diff --git a/shared/src/androidMain/kotlin/org/hyperskill/app/manage_subscription/injection/PlatformManageSubscriptionComponentImpl.kt b/shared/src/androidMain/kotlin/org/hyperskill/app/manage_subscription/injection/PlatformManageSubscriptionComponentImpl.kt new file mode 100644 index 0000000000..bd0c704647 --- /dev/null +++ b/shared/src/androidMain/kotlin/org/hyperskill/app/manage_subscription/injection/PlatformManageSubscriptionComponentImpl.kt @@ -0,0 +1,20 @@ +package org.hyperskill.app.manage_subscription.injection + +import org.hyperskill.app.core.flowredux.presentation.wrapWithFlowView +import org.hyperskill.app.core.injection.ReduxViewModelFactory +import org.hyperskill.app.manage_subscription.presentation.ManageSubscriptionViewModel + +class PlatformManageSubscriptionComponentImpl( + private val manageSubscriptionComponent: ManageSubscriptionComponent +) : PlatformManageSubscriptionComponent { + override val reduxViewModelFactory: ReduxViewModelFactory + get() = ReduxViewModelFactory( + mapOf( + ManageSubscriptionViewModel::class.java to { + ManageSubscriptionViewModel( + manageSubscriptionComponent.manageSubscriptionFeature.wrapWithFlowView() + ) + } + ) + ) +} \ No newline at end of file diff --git a/shared/src/androidMain/kotlin/org/hyperskill/app/manage_subscription/presentation/ManageSubscriptionViewModel.kt b/shared/src/androidMain/kotlin/org/hyperskill/app/manage_subscription/presentation/ManageSubscriptionViewModel.kt new file mode 100644 index 0000000000..133cb89c24 --- /dev/null +++ b/shared/src/androidMain/kotlin/org/hyperskill/app/manage_subscription/presentation/ManageSubscriptionViewModel.kt @@ -0,0 +1,24 @@ +package org.hyperskill.app.manage_subscription.presentation + +import org.hyperskill.app.core.flowredux.presentation.FlowView +import org.hyperskill.app.core.flowredux.presentation.ReduxFlowViewModel +import org.hyperskill.app.manage_subscription.presentation.ManageSubscriptionFeature.Action.ViewAction +import org.hyperskill.app.manage_subscription.presentation.ManageSubscriptionFeature.Message +import org.hyperskill.app.manage_subscription.presentation.ManageSubscriptionFeature.ViewState + +class ManageSubscriptionViewModel( + viewContainer: FlowView +) : ReduxFlowViewModel(viewContainer) { + + init { + onNewMessage(Message.Initialize) + } + + fun onRetryClick() { + onNewMessage(Message.RetryContentLoading) + } + + fun onActionButtonClick() { + onNewMessage(Message.ActionButtonClicked) + } +} \ No newline at end of file diff --git a/shared/src/androidMain/kotlin/org/hyperskill/app/paywall/injection/PlatformPaywallComponent.kt b/shared/src/androidMain/kotlin/org/hyperskill/app/paywall/injection/PlatformPaywallComponent.kt new file mode 100644 index 0000000000..7e899b57db --- /dev/null +++ b/shared/src/androidMain/kotlin/org/hyperskill/app/paywall/injection/PlatformPaywallComponent.kt @@ -0,0 +1,7 @@ +package org.hyperskill.app.paywall.injection + +import org.hyperskill.app.core.injection.ReduxViewModelFactory + +interface PlatformPaywallComponent { + val reduxViewModelFactory: ReduxViewModelFactory +} \ No newline at end of file diff --git a/shared/src/androidMain/kotlin/org/hyperskill/app/paywall/injection/PlatformPaywallComponentImpl.kt b/shared/src/androidMain/kotlin/org/hyperskill/app/paywall/injection/PlatformPaywallComponentImpl.kt new file mode 100644 index 0000000000..a9f876f657 --- /dev/null +++ b/shared/src/androidMain/kotlin/org/hyperskill/app/paywall/injection/PlatformPaywallComponentImpl.kt @@ -0,0 +1,20 @@ +package org.hyperskill.app.paywall.injection + +import org.hyperskill.app.core.flowredux.presentation.wrapWithFlowView +import org.hyperskill.app.core.injection.ReduxViewModelFactory +import org.hyperskill.app.paywall.presentation.PaywallViewModel + +class PlatformPaywallComponentImpl( + private val paywallComponent: PaywallComponent +) : PlatformPaywallComponent { + override val reduxViewModelFactory: ReduxViewModelFactory + get() = ReduxViewModelFactory( + mapOf( + PaywallViewModel::class.java to { + PaywallViewModel( + paywallComponent.paywallFeature.wrapWithFlowView() + ) + } + ) + ) +} \ No newline at end of file diff --git a/shared/src/androidMain/kotlin/org/hyperskill/app/paywall/presentation/PaywallViewModel.kt b/shared/src/androidMain/kotlin/org/hyperskill/app/paywall/presentation/PaywallViewModel.kt new file mode 100644 index 0000000000..eaf0f4d5d9 --- /dev/null +++ b/shared/src/androidMain/kotlin/org/hyperskill/app/paywall/presentation/PaywallViewModel.kt @@ -0,0 +1,38 @@ +package org.hyperskill.app.paywall.presentation + +import android.app.Activity +import org.hyperskill.app.core.flowredux.presentation.FlowView +import org.hyperskill.app.core.flowredux.presentation.ReduxFlowViewModel +import org.hyperskill.app.paywall.presentation.PaywallFeature.Action.ViewAction +import org.hyperskill.app.paywall.presentation.PaywallFeature.Message +import org.hyperskill.app.paywall.presentation.PaywallFeature.ViewState +import org.hyperskill.app.purchases.domain.model.AndroidPurchaseParams + +class PaywallViewModel( + reduxViewContainer: FlowView +) : ReduxFlowViewModel(reduxViewContainer) { + + init { + onNewMessage(Message.Initialize) + } + + fun onBuySubscriptionClick(activity: Activity) { + onNewMessage( + Message.BuySubscriptionClicked( + AndroidPurchaseParams(activity) + ) + ) + } + + fun onContinueWithLimitsClick() { + onNewMessage(Message.ContinueWithLimitsClicked) + } + + fun onRetryLoadingClicked() { + onNewMessage(Message.RetryContentLoading) + } + + fun onTermsOfServiceClick() { + onNewMessage(Message.ClickedTermsOfServiceAndPrivacyPolicy) + } +} \ No newline at end of file diff --git a/shared/src/androidMain/kotlin/org/hyperskill/app/profile/presentation/ProfileSettingsViewModel.kt b/shared/src/androidMain/kotlin/org/hyperskill/app/profile/presentation/ProfileSettingsViewModel.kt index 170d14658d..8404ae75d5 100644 --- a/shared/src/androidMain/kotlin/org/hyperskill/app/profile/presentation/ProfileSettingsViewModel.kt +++ b/shared/src/androidMain/kotlin/org/hyperskill/app/profile/presentation/ProfileSettingsViewModel.kt @@ -1,15 +1,11 @@ package org.hyperskill.app.profile.presentation -import org.hyperskill.app.profile_settings.presentation.ProfileSettingsFeature +import org.hyperskill.app.profile_settings.presentation.ProfileSettingsFeature.Action.ViewAction +import org.hyperskill.app.profile_settings.presentation.ProfileSettingsFeature.Message +import org.hyperskill.app.profile_settings.presentation.ProfileSettingsFeature.ViewState import ru.nobird.android.view.redux.viewmodel.ReduxViewModel import ru.nobird.app.presentation.redux.container.ReduxViewContainer class ProfileSettingsViewModel( - reduxViewContainer: ReduxViewContainer< - ProfileSettingsFeature.State, - ProfileSettingsFeature.Message, - ProfileSettingsFeature.Action.ViewAction> -) : ReduxViewModel< - ProfileSettingsFeature.State, - ProfileSettingsFeature.Message, - ProfileSettingsFeature.Action.ViewAction>(reduxViewContainer) \ No newline at end of file + reduxViewContainer: ReduxViewContainer +) : ReduxViewModel(reduxViewContainer) \ No newline at end of file diff --git a/shared/src/androidMain/kotlin/org/hyperskill/app/purchases/domain/AndroidPurchaseManager.kt b/shared/src/androidMain/kotlin/org/hyperskill/app/purchases/domain/AndroidPurchaseManager.kt new file mode 100644 index 0000000000..700fff08ce --- /dev/null +++ b/shared/src/androidMain/kotlin/org/hyperskill/app/purchases/domain/AndroidPurchaseManager.kt @@ -0,0 +1,114 @@ +package org.hyperskill.app.purchases.domain + +import android.app.Activity +import android.app.Application +import com.revenuecat.purchases.LogLevel +import com.revenuecat.purchases.PurchaseParams +import com.revenuecat.purchases.Purchases +import com.revenuecat.purchases.PurchasesConfiguration +import com.revenuecat.purchases.PurchasesErrorCode +import com.revenuecat.purchases.PurchasesException +import com.revenuecat.purchases.PurchasesTransactionException +import com.revenuecat.purchases.awaitCustomerInfo +import com.revenuecat.purchases.awaitGetProducts +import com.revenuecat.purchases.awaitLogIn +import com.revenuecat.purchases.awaitPurchase +import com.revenuecat.purchases.models.StoreProduct +import org.hyperskill.app.BuildConfig +import org.hyperskill.app.purchases.domain.model.AndroidPurchaseParams +import org.hyperskill.app.purchases.domain.model.PlatformPurchaseParams +import org.hyperskill.app.purchases.domain.model.PurchaseManager +import org.hyperskill.app.purchases.domain.model.PurchaseResult + +class AndroidPurchaseManager( + private val application: Application, + private val isDebugMode: Boolean +) : PurchaseManager { + + override fun isConfigured(): Boolean = + Purchases.isConfigured + + override fun configure(userId: Long) { + Purchases.logLevel = if (isDebugMode) LogLevel.DEBUG else LogLevel.INFO + Purchases.configure( + PurchasesConfiguration + .Builder( + context = application, + apiKey = BuildConfig.REVENUE_CAT_GOOGLE_API_KEY + ) + .appUserID(userId.toString()) + .build() + ) + } + + override suspend fun login(userId: Long): Result = + kotlin.runCatching { + Purchases.sharedInstance.awaitLogIn(userId.toString()) + } + + override suspend fun purchase( + productId: String, + platformPurchaseParams: PlatformPurchaseParams + ): Result = + runCatching { + val product = try { + fetchProduct(productId) ?: return@runCatching PurchaseResult.Error.NoProductFound(productId) + } catch (e: PurchasesException) { + return@runCatching mapProductFetchException(productId, e) + } + val activity = (platformPurchaseParams as AndroidPurchaseParams).activity + purchase(activity, product) + } + + private fun mapProductFetchException(productId: String, e: PurchasesException): PurchaseResult = + PurchaseResult.Error.ErrorWhileFetchingProduct( + productId = productId, + originMessage = e.message, + underlyingErrorMessage = e.error.underlyingErrorMessage + ) + + private suspend fun purchase(activity: Activity, product: StoreProduct): PurchaseResult = + try { + val purchaseResult = Purchases.sharedInstance.awaitPurchase( + PurchaseParams.Builder(activity, product).build() + ) + PurchaseResult.Succeed( + orderId = purchaseResult.storeTransaction.orderId, + productIds = purchaseResult.storeTransaction.productIds + ) + } catch (e: PurchasesTransactionException) { + mapException(e) + } + + private fun mapException(e: PurchasesTransactionException): PurchaseResult { + if (e.userCancelled) return PurchaseResult.CancelledByUser + return when (e.error.code) { + PurchasesErrorCode.ReceiptAlreadyInUseError -> + PurchaseResult.Error.ReceiptAlreadyInUseError(e.message, e.underlyingErrorMessage) + PurchasesErrorCode.PaymentPendingError -> + PurchaseResult.Error.PaymentPendingError(e.message, e.underlyingErrorMessage) + PurchasesErrorCode.ProductAlreadyPurchasedError -> + PurchaseResult.Error.ProductAlreadyPurchasedError(e.message, e.underlyingErrorMessage) + PurchasesErrorCode.PurchaseNotAllowedError -> + PurchaseResult.Error.PurchaseNotAllowedError(e.message, e.underlyingErrorMessage) + PurchasesErrorCode.StoreProblemError -> + PurchaseResult.Error.StoreProblemError(e.message, e.underlyingErrorMessage) + else -> PurchaseResult.Error.OtherError(e.message, e.underlyingErrorMessage) + } + } + + override suspend fun getManagementUrl(): Result = + kotlin.runCatching { + Purchases.sharedInstance.awaitCustomerInfo().managementURL?.toString() + } + + override suspend fun getFormattedProductPrice(productId: String): Result = + kotlin.runCatching { + fetchProduct(productId)?.price?.formatted + } + + private suspend fun fetchProduct(productId: String): StoreProduct? = + Purchases.sharedInstance + .awaitGetProducts(listOf(productId)) + .firstOrNull() +} \ No newline at end of file diff --git a/shared/src/androidMain/kotlin/org/hyperskill/app/purchases/domain/model/AndroidPurchaseParams.kt b/shared/src/androidMain/kotlin/org/hyperskill/app/purchases/domain/model/AndroidPurchaseParams.kt new file mode 100644 index 0000000000..30fd1cb3c2 --- /dev/null +++ b/shared/src/androidMain/kotlin/org/hyperskill/app/purchases/domain/model/AndroidPurchaseParams.kt @@ -0,0 +1,7 @@ +package org.hyperskill.app.purchases.domain.model + +import android.app.Activity + +data class AndroidPurchaseParams( + val activity: Activity +) : PlatformPurchaseParams \ No newline at end of file diff --git a/shared/src/androidUnitTest/kotlin/org/hyperskill/step_quiz/AndroidStepQuizTest.kt b/shared/src/androidUnitTest/kotlin/org/hyperskill/step_quiz/AndroidStepQuizTest.kt index 50f5cad3b2..c60df85c4b 100644 --- a/shared/src/androidUnitTest/kotlin/org/hyperskill/step_quiz/AndroidStepQuizTest.kt +++ b/shared/src/androidUnitTest/kotlin/org/hyperskill/step_quiz/AndroidStepQuizTest.kt @@ -78,6 +78,7 @@ class AndroidStepQuizTest { submissionState, isProblemsLimitReached = false, problemsLimitReachedModalText = null, + isSubscriptionPurchaseEnabled = true, problemsOnboardingFlags = ProblemsOnboardingFlags( isParsonsOnboardingShown = false, isFillBlanksInputModeOnboardingShown = false, diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/analytic/domain/model/hyperskill/HyperskillAnalyticRoute.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/analytic/domain/model/hyperskill/HyperskillAnalyticRoute.kt index a884601792..ec337bed5d 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/analytic/domain/model/hyperskill/HyperskillAnalyticRoute.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/analytic/domain/model/hyperskill/HyperskillAnalyticRoute.kt @@ -100,9 +100,14 @@ sealed class HyperskillAnalyticRoute { open class Profile : HyperskillAnalyticRoute() { override val path: String = "/profile" - class Settings : Profile() { + open class Settings : Profile() { override val path: String = "${super.path}/settings" + + object ManageSubscription : Settings() { + override val path: String + get() = "${super.path}/manage-subscription" + } } } @@ -159,6 +164,11 @@ sealed class HyperskillAnalyticRoute { override val path: String = "SpringBoard" } + object Paywall : HyperskillAnalyticRoute() { + override val path: String + get() = "/paywall" + } + /** * Represents a special route that is used to track the first time the app is launched (ALTAPPS-1139). */ diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/analytic/domain/model/hyperskill/HyperskillAnalyticTarget.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/analytic/domain/model/hyperskill/HyperskillAnalyticTarget.kt index 151d101006..66f500ebe9 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/analytic/domain/model/hyperskill/HyperskillAnalyticTarget.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/analytic/domain/model/hyperskill/HyperskillAnalyticTarget.kt @@ -119,5 +119,13 @@ enum class HyperskillAnalyticTarget(val targetName: String) { WRITE_A_REQUEST("write_a_request"), MAYBE_LATER("maybe_later"), CHOICE("choice"), - SOLVE_ON_THE_WEB_VERSION("solve_on_the_web_version") + SOLVE_ON_THE_WEB_VERSION("solve_on_the_web_version"), + ACTIVE_SUBSCRIPTION_DETAILS("active_subscription_details"), + SUBSCRIPTION_SUGGESTION_DETAILS("subscription_suggestion_details"), + BUY_SUBSCRIPTION("buy_subscription"), + CONTINUE_WITH_LIMITS("continue_with_limits"), + UNLOCK_UNLIMITED_PROBLEMS("unlock_unlimited_problems"), + MANAGE_SUBSCRIPTION("manage_subscription"), + RENEW_SUBSCRIPTION("renew_subscription"), + HYPERSKILL_TERMS_OF_SERVICE_AND_PRIVACY_POLICY("hyperskill_terms_of_service_and_privacy_policy") } \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/core/domain/platform/Platform.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/core/domain/platform/Platform.kt index 74cd19ac19..e22b5f7836 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/core/domain/platform/Platform.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/core/domain/platform/Platform.kt @@ -11,4 +11,9 @@ expect class Platform() { val feedbackName: String val appNameResource: StringResource + + /** + * A boolean flag that indicates whether the platform supports subscription purchase. + */ + val isSubscriptionPurchaseEnabled: Boolean } \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/core/injection/AppGraph.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/core/injection/AppGraph.kt index 1e7a136d0d..eab7e0719c 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/core/injection/AppGraph.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/core/injection/AppGraph.kt @@ -13,7 +13,6 @@ import org.hyperskill.app.debug.injection.DebugComponent import org.hyperskill.app.devices.injection.DevicesDataComponent import org.hyperskill.app.discussions.injection.DiscussionsDataComponent import org.hyperskill.app.first_problem_onboarding.injection.FirstProblemOnboardingComponent -import org.hyperskill.app.freemium.injection.FreemiumDataComponent import org.hyperskill.app.gamification_toolbar.domain.model.GamificationToolbarScreen import org.hyperskill.app.gamification_toolbar.injection.GamificationToolbarComponent import org.hyperskill.app.home.injection.HomeComponent @@ -29,6 +28,7 @@ import org.hyperskill.app.logging.inject.LoggerComponent import org.hyperskill.app.magic_links.injection.MagicLinksDataComponent import org.hyperskill.app.main.injection.MainComponent import org.hyperskill.app.main.injection.MainDataComponent +import org.hyperskill.app.manage_subscription.injection.ManageSubscriptionComponent import org.hyperskill.app.network.injection.NetworkComponent import org.hyperskill.app.notification.click_handling.injection.NotificationClickHandlingComponent import org.hyperskill.app.notification.local.injection.NotificationComponent @@ -37,6 +37,8 @@ import org.hyperskill.app.notification.remote.injection.PlatformPushNotification import org.hyperskill.app.notification.remote.injection.PushNotificationsComponent import org.hyperskill.app.notifications_onboarding.injection.NotificationsOnboardingComponent import org.hyperskill.app.onboarding.injection.OnboardingDataComponent +import org.hyperskill.app.paywall.domain.model.PaywallTransitionSource +import org.hyperskill.app.paywall.injection.PaywallComponent import org.hyperskill.app.problems_limit.domain.model.ProblemsLimitScreen import org.hyperskill.app.problems_limit.injection.ProblemsLimitComponent import org.hyperskill.app.products.injection.ProductsDataComponent @@ -50,6 +52,7 @@ import org.hyperskill.app.project_selection.details.injection.ProjectSelectionDe import org.hyperskill.app.project_selection.list.injection.ProjectSelectionListComponent import org.hyperskill.app.projects.injection.ProjectsDataComponent import org.hyperskill.app.providers.injection.ProvidersDataComponent +import org.hyperskill.app.purchases.injection.PurchaseComponent import org.hyperskill.app.reactions.injection.ReactionsDataComponent import org.hyperskill.app.request_review.injection.RequestReviewDataComponent import org.hyperskill.app.request_review.modal.injection.RequestReviewModalComponent @@ -72,6 +75,7 @@ import org.hyperskill.app.streaks.injection.StreakFlowDataComponent import org.hyperskill.app.streaks.injection.StreaksDataComponent import org.hyperskill.app.study_plan.screen.injection.StudyPlanScreenComponent import org.hyperskill.app.study_plan.widget.injection.StudyPlanWidgetComponent +import org.hyperskill.app.subscriptions.injection.SubscriptionsDataComponent import org.hyperskill.app.topics.injection.TopicsDataComponent import org.hyperskill.app.topics_repetitions.injection.TopicsRepetitionsComponent import org.hyperskill.app.topics_repetitions.injection.TopicsRepetitionsDataComponent @@ -103,9 +107,12 @@ interface AppGraph { val notificationFlowDataComponent: NotificationFlowDataComponent val stateRepositoriesComponent: StateRepositoriesComponent val profileDataComponent: ProfileDataComponent + val subscriptionDataComponent: SubscriptionsDataComponent fun buildHyperskillAnalyticEngineComponent(): HyperskillAnalyticEngineComponent + fun buildPurchaseComponent(): PurchaseComponent + /** * Auth components */ @@ -157,7 +164,6 @@ interface AppGraph { fun buildProjectSelectionListComponent(): ProjectSelectionListComponent fun buildProjectSelectionDetailsComponent(): ProjectSelectionDetailsComponent fun buildStagesDataComponent(): StagesDataComponent - fun buildFreemiumDataComponent(): FreemiumDataComponent fun buildProblemsLimitComponent(screen: ProblemsLimitScreen): ProblemsLimitComponent fun buildProvidersDataComponent(): ProvidersDataComponent fun buildStreakRecoveryComponent(): StreakRecoveryComponent @@ -182,6 +188,8 @@ interface AppGraph { fun buildInterviewPreparationOnboardingComponent(): InterviewPreparationOnboardingComponent fun buildRequestReviewDataComponent(): RequestReviewDataComponent fun buildRequestReviewModalComponent(stepRoute: StepRoute): RequestReviewModalComponent + fun buildPaywallComponent(paywallTransitionSource: PaywallTransitionSource): PaywallComponent + fun buildManageSubscriptionComponent(): ManageSubscriptionComponent fun buildUsersQuestionnaireDataComponent(): UsersQuestionnaireDataComponent fun buildUsersQuestionnaireWidgetComponent(): UsersQuestionnaireWidgetComponent fun buildUsersQuestionnaireOnboardingComponent(): UsersQuestionnaireOnboardingComponent diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/core/injection/BaseAppGraph.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/core/injection/BaseAppGraph.kt index 40616c7913..afcd9998b1 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/core/injection/BaseAppGraph.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/core/injection/BaseAppGraph.kt @@ -24,8 +24,6 @@ import org.hyperskill.app.discussions.injection.DiscussionsDataComponent import org.hyperskill.app.discussions.injection.DiscussionsDataComponentImpl import org.hyperskill.app.first_problem_onboarding.injection.FirstProblemOnboardingComponent import org.hyperskill.app.first_problem_onboarding.injection.FirstProblemOnboardingComponentImpl -import org.hyperskill.app.freemium.injection.FreemiumDataComponent -import org.hyperskill.app.freemium.injection.FreemiumDataComponentImpl import org.hyperskill.app.gamification_toolbar.domain.model.GamificationToolbarScreen import org.hyperskill.app.gamification_toolbar.injection.GamificationToolbarComponent import org.hyperskill.app.gamification_toolbar.injection.GamificationToolbarComponentImpl @@ -55,6 +53,8 @@ import org.hyperskill.app.main.injection.MainComponent import org.hyperskill.app.main.injection.MainComponentImpl import org.hyperskill.app.main.injection.MainDataComponent import org.hyperskill.app.main.injection.MainDataComponentImpl +import org.hyperskill.app.manage_subscription.injection.ManageSubscriptionComponent +import org.hyperskill.app.manage_subscription.injection.ManageSubscriptionComponentImpl import org.hyperskill.app.network.injection.NetworkComponent import org.hyperskill.app.network.injection.NetworkComponentImpl import org.hyperskill.app.notification.click_handling.injection.NotificationClickHandlingComponent @@ -69,6 +69,9 @@ import org.hyperskill.app.notifications_onboarding.injection.NotificationsOnboar import org.hyperskill.app.notifications_onboarding.injection.NotificationsOnboardingComponentImpl import org.hyperskill.app.onboarding.injection.OnboardingDataComponent import org.hyperskill.app.onboarding.injection.OnboardingDataComponentImpl +import org.hyperskill.app.paywall.domain.model.PaywallTransitionSource +import org.hyperskill.app.paywall.injection.PaywallComponent +import org.hyperskill.app.paywall.injection.PaywallComponentImpl import org.hyperskill.app.problems_limit.domain.model.ProblemsLimitScreen import org.hyperskill.app.problems_limit.injection.ProblemsLimitComponent import org.hyperskill.app.problems_limit.injection.ProblemsLimitComponentImpl @@ -135,6 +138,8 @@ import org.hyperskill.app.study_plan.screen.injection.StudyPlanScreenComponent import org.hyperskill.app.study_plan.screen.injection.StudyPlanScreenComponentImpl import org.hyperskill.app.study_plan.widget.injection.StudyPlanWidgetComponent import org.hyperskill.app.study_plan.widget.injection.StudyPlanWidgetComponentImpl +import org.hyperskill.app.subscriptions.injection.SubscriptionsDataComponent +import org.hyperskill.app.subscriptions.injection.SubscriptionsDataComponentImpl import org.hyperskill.app.topics.injection.TopicsDataComponent import org.hyperskill.app.topics.injection.TopicsDataComponentImpl import org.hyperskill.app.topics_repetitions.injection.TopicsRepetitionsComponent @@ -217,6 +222,10 @@ abstract class BaseAppGraph : AppGraph { ) } + override val subscriptionDataComponent: SubscriptionsDataComponent by lazy { + SubscriptionsDataComponentImpl(this) + } + override fun buildHyperskillAnalyticEngineComponent(): HyperskillAnalyticEngineComponent = HyperskillAnalyticEngineComponentImpl(this) @@ -421,9 +430,6 @@ abstract class BaseAppGraph : AppGraph { override fun buildStagesDataComponent(): StagesDataComponent = StagesDataComponentImpl(this) - override fun buildFreemiumDataComponent(): FreemiumDataComponent = - FreemiumDataComponentImpl(this) - override fun buildProvidersDataComponent(): ProvidersDataComponent = ProvidersDataComponentImpl(this) @@ -490,6 +496,14 @@ abstract class BaseAppGraph : AppGraph { override fun buildRequestReviewModalComponent(stepRoute: StepRoute): RequestReviewModalComponent = RequestReviewModalComponentImpl(appGraph = this, stepRoute = stepRoute) + override fun buildPaywallComponent( + paywallTransitionSource: PaywallTransitionSource + ): PaywallComponent = + PaywallComponentImpl(paywallTransitionSource, this) + + override fun buildManageSubscriptionComponent(): ManageSubscriptionComponent = + ManageSubscriptionComponentImpl(this) + override fun buildUsersQuestionnaireDataComponent(): UsersQuestionnaireDataComponent = UsersQuestionnaireDataComponentImpl(this) diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/core/injection/StateRepositoriesComponentImpl.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/core/injection/StateRepositoriesComponentImpl.kt index a67cf06873..6602143df8 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/core/injection/StateRepositoriesComponentImpl.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/core/injection/StateRepositoriesComponentImpl.kt @@ -12,7 +12,9 @@ import org.hyperskill.app.learning_activities.remote.LearningActivitiesRemoteDat import org.hyperskill.app.study_plan.data.repository.CurrentStudyPlanStateRepositoryImpl import org.hyperskill.app.study_plan.domain.repository.CurrentStudyPlanStateRepository import org.hyperskill.app.study_plan.remote.StudyPlanRemoteDataSourceImpl +import org.hyperskill.app.subscriptions.cache.CurrentSubscriptionStateHolderImpl import org.hyperskill.app.subscriptions.data.repository.CurrentSubscriptionStateRepositoryImpl +import org.hyperskill.app.subscriptions.data.source.CurrentSubscriptionStateHolder import org.hyperskill.app.subscriptions.data.source.SubscriptionsRemoteDataSource import org.hyperskill.app.subscriptions.domain.repository.CurrentSubscriptionStateRepository import org.hyperskill.app.subscriptions.remote.SubscriptionsRemoteDataSourceImpl @@ -27,8 +29,15 @@ class StateRepositoriesComponentImpl(appGraph: AppGraph) : StateRepositoriesComp private val subscriptionsRemoteDataSource: SubscriptionsRemoteDataSource = SubscriptionsRemoteDataSourceImpl(authorizedHttpClient) + private val currentSubscriptionStateHolder: CurrentSubscriptionStateHolder by lazy { + CurrentSubscriptionStateHolderImpl( + json = appGraph.commonComponent.json, + settings = appGraph.commonComponent.settings + ) + } + override val currentSubscriptionStateRepository: CurrentSubscriptionStateRepository = - CurrentSubscriptionStateRepositoryImpl(subscriptionsRemoteDataSource) + CurrentSubscriptionStateRepositoryImpl(subscriptionsRemoteDataSource, currentSubscriptionStateHolder) /** * Study plan diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/core/view/mapper/date/MonthFormatter.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/core/view/mapper/date/MonthFormatter.kt index 50989299c3..a360567111 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/core/view/mapper/date/MonthFormatter.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/core/view/mapper/date/MonthFormatter.kt @@ -25,4 +25,21 @@ internal object MonthFormatter { Month.DECEMBER -> "Dec" else -> throw IllegalArgumentException("MonthFormatter: unknown month $month") } + + fun formatMonth(month: Month): String = + when (month) { + Month.JANUARY -> "January" + Month.FEBRUARY -> "February" + Month.MARCH -> "March" + Month.APRIL -> "April" + Month.MAY -> "May" + Month.JUNE -> "June" + Month.JULY -> "July" + Month.AUGUST -> "August" + Month.SEPTEMBER -> "September" + Month.OCTOBER -> "October" + Month.NOVEMBER -> "November" + Month.DECEMBER -> "December" + else -> throw IllegalArgumentException("MonthFormatter: unknown month $month") + } } \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/core/view/mapper/date/SharedDateFormatter.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/core/view/mapper/date/SharedDateFormatter.kt index 9bc4e39891..f8c3f0d69f 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/core/view/mapper/date/SharedDateFormatter.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/core/view/mapper/date/SharedDateFormatter.kt @@ -4,7 +4,10 @@ import kotlin.math.max import kotlin.time.Duration import kotlin.time.DurationUnit import kotlin.time.toDuration +import kotlinx.datetime.Instant import kotlinx.datetime.LocalDate +import kotlinx.datetime.TimeZone +import kotlinx.datetime.toLocalDateTime import org.hyperskill.app.SharedResources import org.hyperskill.app.core.view.mapper.ResourceProvider @@ -229,4 +232,16 @@ class SharedDateFormatter(private val resourceProvider: ResourceProvider) { */ fun formatDayNumericAndMonthShort(localDate: LocalDate): String = "${localDate.dayOfMonth} ${MonthFormatter.formatMonthToShort(localDate.month)}" + + fun formatSubscriptionValidUntil(instant: Instant, timeZone: TimeZone = TimeZone.currentSystemDefault()): String { + val localDateTime = instant.toLocalDateTime(timeZone) + val month = MonthFormatter.formatMonth(localDateTime.month) + val dayOfMonth = localDateTime.date.dayOfMonth + val hour = formatHoursOrMinutesWithLeadingZero(localDateTime.hour) + val minutes = formatHoursOrMinutesWithLeadingZero(localDateTime.minute) + return "$month $dayOfMonth, ${localDateTime.year}, $hour:$minutes" + } + + private fun formatHoursOrMinutesWithLeadingZero(count: Int): String = + if (count <= 9) "0$count" else count.toString() } \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/freemium/domain/interactor/FreemiumInteractor.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/freemium/domain/interactor/FreemiumInteractor.kt deleted file mode 100644 index c1f6f8a510..0000000000 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/freemium/domain/interactor/FreemiumInteractor.kt +++ /dev/null @@ -1,60 +0,0 @@ -package org.hyperskill.app.freemium.domain.interactor - -import org.hyperskill.app.core.domain.repository.updateState -import org.hyperskill.app.profile.domain.model.isFreemiumIncreaseLimitsForFirstStepCompletionEnabled -import org.hyperskill.app.profile.domain.repository.CurrentProfileStateRepository -import org.hyperskill.app.subscriptions.domain.model.isFreemium -import org.hyperskill.app.subscriptions.domain.repository.CurrentSubscriptionStateRepository - -class FreemiumInteractor( - private val currentSubscriptionStateRepository: CurrentSubscriptionStateRepository, - private val currentProfileStateRepository: CurrentProfileStateRepository -) { - companion object { - private const val SOLVING_FIRST_STEP_ADDITIONAL_LIMIT_VALUE = 10 - } - - suspend fun isFreemiumEnabled(): Result = - currentSubscriptionStateRepository.getState().map { it.isFreemium } - - suspend fun getStepsLimitTotal(): Result = - currentSubscriptionStateRepository.getState().map { it.stepsLimitTotal } - - suspend fun isProblemsLimitReached(): Result = - kotlin.runCatching { - if (isFreemiumEnabled().getOrThrow()) { - val subscription = currentSubscriptionStateRepository - .getState() - .getOrThrow() - - subscription.isFreemium && subscription.stepsLimitLeft == 0 - } else { - false - } - } - - suspend fun onStepSolved() { - if (isFreemiumEnabled().getOrDefault(false)) { - currentSubscriptionStateRepository.getState().getOrNull()?.let { - currentSubscriptionStateRepository.updateState( - it.copy(stepsLimitLeft = it.stepsLimitLeft?.dec()) - ) - } - } - currentProfileStateRepository.getState().onSuccess { currentProfile -> - if (currentProfile.features.isFreemiumIncreaseLimitsForFirstStepCompletionEnabled && - currentProfile.gamification.passedProblems == 0 - ) { - currentSubscriptionStateRepository.updateState { - it.copy( - stepsLimitTotal = it.stepsLimitTotal?.plus(SOLVING_FIRST_STEP_ADDITIONAL_LIMIT_VALUE), - stepsLimitLeft = it.stepsLimitLeft?.plus(SOLVING_FIRST_STEP_ADDITIONAL_LIMIT_VALUE) - ) - } - currentProfileStateRepository.updateState { - it.copy(gamification = it.gamification.copy(passedProblems = it.gamification.passedProblems + 1)) - } - } - } - } -} \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/freemium/injection/FreemiumDataComponent.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/freemium/injection/FreemiumDataComponent.kt deleted file mode 100644 index 762be62d7e..0000000000 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/freemium/injection/FreemiumDataComponent.kt +++ /dev/null @@ -1,7 +0,0 @@ -package org.hyperskill.app.freemium.injection - -import org.hyperskill.app.freemium.domain.interactor.FreemiumInteractor - -interface FreemiumDataComponent { - val freemiumInteractor: FreemiumInteractor -} \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/freemium/injection/FreemiumDataComponentImpl.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/freemium/injection/FreemiumDataComponentImpl.kt deleted file mode 100644 index 9c85ae425e..0000000000 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/freemium/injection/FreemiumDataComponentImpl.kt +++ /dev/null @@ -1,19 +0,0 @@ -package org.hyperskill.app.freemium.injection - -import org.hyperskill.app.core.injection.AppGraph -import org.hyperskill.app.freemium.domain.interactor.FreemiumInteractor -import org.hyperskill.app.profile.domain.repository.CurrentProfileStateRepository -import org.hyperskill.app.subscriptions.domain.repository.CurrentSubscriptionStateRepository - -class FreemiumDataComponentImpl( - appGraph: AppGraph -) : FreemiumDataComponent { - private val currentSubscriptionStateRepository: CurrentSubscriptionStateRepository = - appGraph.stateRepositoriesComponent.currentSubscriptionStateRepository - - private val currentProfileStateRepository: CurrentProfileStateRepository = - appGraph.profileDataComponent.currentProfileStateRepository - - override val freemiumInteractor: FreemiumInteractor - get() = FreemiumInteractor(currentSubscriptionStateRepository, currentProfileStateRepository) -} \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/home/injection/HomeComponentImpl.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/home/injection/HomeComponentImpl.kt index 924cdb2afb..f5d8e53d02 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/home/injection/HomeComponentImpl.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/home/injection/HomeComponentImpl.kt @@ -28,7 +28,7 @@ internal class HomeComponentImpl(private val appGraph: AppGraph) : HomeComponent appGraph.profileDataComponent.currentProfileStateRepository, appGraph.buildTopicsRepetitionsDataComponent().topicsRepetitionsInteractor, appGraph.buildStepDataComponent().stepInteractor, - appGraph.buildFreemiumDataComponent().freemiumInteractor, + appGraph.stateRepositoriesComponent.currentSubscriptionStateRepository, appGraph.analyticComponent.analyticInteractor, appGraph.sentryComponent.sentryInteractor, appGraph.commonComponent.dateFormatter, diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/home/injection/HomeFeatureBuilder.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/home/injection/HomeFeatureBuilder.kt index c25aeacc55..dafe68543d 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/home/injection/HomeFeatureBuilder.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/home/injection/HomeFeatureBuilder.kt @@ -10,7 +10,6 @@ import org.hyperskill.app.core.domain.BuildVariant import org.hyperskill.app.core.presentation.ActionDispatcherOptions import org.hyperskill.app.core.presentation.transformState import org.hyperskill.app.core.view.mapper.date.SharedDateFormatter -import org.hyperskill.app.freemium.domain.interactor.FreemiumInteractor import org.hyperskill.app.gamification_toolbar.presentation.GamificationToolbarActionDispatcher import org.hyperskill.app.gamification_toolbar.presentation.GamificationToolbarFeature import org.hyperskill.app.gamification_toolbar.presentation.GamificationToolbarReducer @@ -28,6 +27,7 @@ import org.hyperskill.app.profile.domain.repository.CurrentProfileStateRepositor import org.hyperskill.app.sentry.domain.interactor.SentryInteractor import org.hyperskill.app.step.domain.interactor.StepInteractor import org.hyperskill.app.step_completion.domain.flow.TopicCompletedFlow +import org.hyperskill.app.subscriptions.domain.repository.CurrentSubscriptionStateRepository import org.hyperskill.app.topics_repetitions.domain.flow.TopicRepeatedFlow import org.hyperskill.app.topics_repetitions.domain.interactor.TopicsRepetitionsInteractor import ru.nobird.app.core.model.safeCast @@ -44,7 +44,7 @@ internal object HomeFeatureBuilder { currentProfileStateRepository: CurrentProfileStateRepository, topicsRepetitionsInteractor: TopicsRepetitionsInteractor, stepInteractor: StepInteractor, - freemiumInteractor: FreemiumInteractor, + currentSubscriptionStateRepository: CurrentSubscriptionStateRepository, analyticInteractor: AnalyticInteractor, sentryInteractor: SentryInteractor, dateFormatter: SharedDateFormatter, @@ -72,7 +72,7 @@ internal object HomeFeatureBuilder { currentProfileStateRepository, topicsRepetitionsInteractor, stepInteractor, - freemiumInteractor, + currentSubscriptionStateRepository, analyticInteractor, sentryInteractor, dateFormatter, diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/home/presentation/HomeActionDispatcher.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/home/presentation/HomeActionDispatcher.kt index f065cb4e4a..a86998c5b0 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/home/presentation/HomeActionDispatcher.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/home/presentation/HomeActionDispatcher.kt @@ -13,7 +13,6 @@ import org.hyperskill.app.analytic.domain.interactor.AnalyticInteractor import org.hyperskill.app.core.presentation.ActionDispatcherOptions import org.hyperskill.app.core.utils.DateTimeUtils import org.hyperskill.app.core.view.mapper.date.SharedDateFormatter -import org.hyperskill.app.freemium.domain.interactor.FreemiumInteractor import org.hyperskill.app.home.domain.interactor.HomeInteractor import org.hyperskill.app.home.presentation.HomeFeature.Action import org.hyperskill.app.home.presentation.HomeFeature.InternalAction @@ -25,6 +24,7 @@ import org.hyperskill.app.sentry.domain.model.transaction.HyperskillSentryTransa import org.hyperskill.app.sentry.domain.withTransaction import org.hyperskill.app.step.domain.interactor.StepInteractor import org.hyperskill.app.step_completion.domain.flow.TopicCompletedFlow +import org.hyperskill.app.subscriptions.domain.repository.CurrentSubscriptionStateRepository import org.hyperskill.app.topics_repetitions.domain.flow.TopicRepeatedFlow import org.hyperskill.app.topics_repetitions.domain.interactor.TopicsRepetitionsInteractor import ru.nobird.app.presentation.redux.dispatcher.CoroutineActionDispatcher @@ -35,7 +35,7 @@ internal class HomeActionDispatcher( private val currentProfileStateRepository: CurrentProfileStateRepository, private val topicsRepetitionsInteractor: TopicsRepetitionsInteractor, private val stepInteractor: StepInteractor, - private val freemiumInteractor: FreemiumInteractor, + private val currentSubscriptionStateRepository: CurrentSubscriptionStateRepository, private val analyticInteractor: AnalyticInteractor, private val sentryInteractor: SentryInteractor, private val dateFormatter: SharedDateFormatter, @@ -114,12 +114,16 @@ internal class HomeActionDispatcher( .getOrThrow() val problemOfDayStateResult = async { getProblemOfDayState(currentProfile.dailyStep) } val repetitionsStateResult = async { getRepetitionsState() } - val isFreemiumEnabledResult = async { freemiumInteractor.isFreemiumEnabled() } + val areProblemsLimited = async { + currentSubscriptionStateRepository + .getState() + .map { it.type.areProblemsLimited } + } setOf( Message.HomeSuccess( problemOfDayState = problemOfDayStateResult.await().getOrThrow(), repetitionsState = repetitionsStateResult.await().getOrThrow(), - isFreemiumEnabled = isFreemiumEnabledResult.await().getOrThrow() + areProblemsLimited = areProblemsLimited.await().getOrThrow() ), Message.ReadyToLaunchNextProblemInTimer ) diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/home/presentation/HomeFeature.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/home/presentation/HomeFeature.kt index 23afa15fb7..94003345ea 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/home/presentation/HomeFeature.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/home/presentation/HomeFeature.kt @@ -51,7 +51,7 @@ object HomeFeature { * * @property problemOfDayState Problem of the day state. * @property repetitionsState Topics repetitions state. - * @property isFreemiumEnabled A boolean flag that indicates about is freemium enabled. + * @property areProblemsLimited A boolean flag that indicates that problem limits are enabled. * @property isRefreshing A boolean flag that indicates about is pull-to-refresh is ongoing. * * @see Streak @@ -60,7 +60,7 @@ object HomeFeature { data class Content( val problemOfDayState: ProblemOfDayState, val repetitionsState: RepetitionsState, - val isFreemiumEnabled: Boolean, + val areProblemsLimited: Boolean, internal val isRefreshing: Boolean = false ) : HomeState @@ -111,7 +111,7 @@ object HomeFeature { data class HomeSuccess( val problemOfDayState: ProblemOfDayState, val repetitionsState: RepetitionsState, - val isFreemiumEnabled: Boolean + val areProblemsLimited: Boolean ) : Message object HomeFailure : Message diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/home/presentation/HomeReducer.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/home/presentation/HomeReducer.kt index 5783b3d559..0a4e54b47e 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/home/presentation/HomeReducer.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/home/presentation/HomeReducer.kt @@ -37,7 +37,7 @@ internal class HomeReducer( homeState = HomeState.Content( problemOfDayState = message.problemOfDayState, repetitionsState = message.repetitionsState, - isFreemiumEnabled = message.isFreemiumEnabled + areProblemsLimited = message.areProblemsLimited ) ) to emptySet() } diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/main/domain/interactor/AppInteractor.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/main/domain/interactor/AppInteractor.kt index f6c349ac79..8e3fd9dbe8 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/main/domain/interactor/AppInteractor.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/main/domain/interactor/AppInteractor.kt @@ -2,9 +2,12 @@ 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.core.domain.DataSourceType 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.model.Profile +import org.hyperskill.app.profile.domain.model.isNewUser import org.hyperskill.app.profile.domain.repository.CurrentProfileStateRepository import org.hyperskill.app.progresses.domain.repository.ProgressesRepository import org.hyperskill.app.projects.domain.repository.ProjectsRepository @@ -50,4 +53,26 @@ class AppInteractor( analyticInteractor.logEvent(AppLaunchFirstTimeHyperskillAnalyticEvent) } } + + suspend fun fetchProfile(isAuthorized: Boolean): Result = + if (isAuthorized) { + currentProfileStateRepository + .getStateWithSource(forceUpdate = false) + .fold( + onSuccess = { (profile, usedDataSourceType) -> + /** + * ALTAPPS-693: + * If cached user is new, we need to fetch profile from remote to check if track selected + */ + if (profile.isNewUser && usedDataSourceType == DataSourceType.CACHE) { + currentProfileStateRepository.getState(forceUpdate = true) + } else { + Result.success(profile) + } + }, + onFailure = { currentProfileStateRepository.getState(forceUpdate = true) } + ) + } else { + currentProfileStateRepository.getState(forceUpdate = true) + } } \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/main/injection/AppFeatureBuilder.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/main/injection/AppFeatureBuilder.kt index b6fd090400..5747db3b1d 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/main/injection/AppFeatureBuilder.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/main/injection/AppFeatureBuilder.kt @@ -18,9 +18,12 @@ import org.hyperskill.app.notification.click_handling.presentation.NotificationC import org.hyperskill.app.notification.local.domain.interactor.NotificationInteractor import org.hyperskill.app.notification.remote.domain.interactor.PushNotificationsInteractor import org.hyperskill.app.profile.domain.repository.CurrentProfileStateRepository +import org.hyperskill.app.purchases.domain.interactor.PurchaseInteractor import org.hyperskill.app.sentry.domain.interactor.SentryInteractor import org.hyperskill.app.streak_recovery.presentation.StreakRecoveryActionDispatcher import org.hyperskill.app.streak_recovery.presentation.StreakRecoveryReducer +import org.hyperskill.app.subscriptions.domain.interactor.SubscriptionsInteractor +import org.hyperskill.app.subscriptions.domain.repository.CurrentSubscriptionStateRepository import org.hyperskill.app.welcome_onboarding.presentation.WelcomeOnboardingActionDispatcher import org.hyperskill.app.welcome_onboarding.presentation.WelcomeOnboardingReducer import ru.nobird.app.core.model.safeCast @@ -47,25 +50,33 @@ internal object AppFeatureBuilder { pushNotificationsInteractor: PushNotificationsInteractor, welcomeOnboardingReducer: WelcomeOnboardingReducer, welcomeOnboardingActionDispatcher: WelcomeOnboardingActionDispatcher, + purchaseInteractor: PurchaseInteractor, + currentSubscriptionStateRepository: CurrentSubscriptionStateRepository, + subscriptionsInteractor: SubscriptionsInteractor, platform: Platform, logger: Logger, buildVariant: BuildVariant ): Feature { val appReducer = AppReducer( - streakRecoveryReducer, - clickedNotificationReducer, - welcomeOnboardingReducer, + streakRecoveryReducer = streakRecoveryReducer, + notificationClickHandlingReducer = clickedNotificationReducer, + welcomeOnboardingReducer = welcomeOnboardingReducer, platformType = platform.platformType ).wrapWithLogger(buildVariant, logger, LOG_TAG) + val appActionDispatcher = AppActionDispatcher( - ActionDispatcherOptions(), - appInteractor, - authInteractor, - currentProfileStateRepository, - sentryInteractor, - stateRepositoriesComponent, - notificationsInteractor, - pushNotificationsInteractor + config = ActionDispatcherOptions(), + appInteractor = appInteractor, + authInteractor = authInteractor, + sentryInteractor = sentryInteractor, + stateRepositoriesComponent = stateRepositoriesComponent, + notificationsInteractor = notificationsInteractor, + pushNotificationsInteractor = pushNotificationsInteractor, + purchaseInteractor = purchaseInteractor, + currentSubscriptionStateRepository = currentSubscriptionStateRepository, + subscriptionsInteractor = subscriptionsInteractor, + isSubscriptionPurchaseEnabled = platform.isSubscriptionPurchaseEnabled, + logger.withTag(LOG_TAG) ) return ReduxFeature(initialState ?: State.Idle, appReducer) diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/main/injection/MainComponentImpl.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/main/injection/MainComponentImpl.kt index fd2eb9c781..5b6cad28df 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/main/injection/MainComponentImpl.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/main/injection/MainComponentImpl.kt @@ -17,27 +17,31 @@ internal class MainComponentImpl(private val appGraph: AppGraph) : MainComponent private val welcomeOnboardingComponent: WelcomeOnboardingComponent = appGraph.buildWelcomeOnboardingComponent() + /*ktlint-disable*/ override fun appFeature( initialState: AppFeature.State? ): Feature = AppFeatureBuilder.build( - initialState, - appGraph.buildMainDataComponent().appInteractor, - appGraph.authComponent.authInteractor, - appGraph.profileDataComponent.currentProfileStateRepository, - appGraph.sentryComponent.sentryInteractor, - appGraph.stateRepositoriesComponent, - streakRecoveryComponent.streakRecoveryReducer, - streakRecoveryComponent.streakRecoveryActionDispatcher, - clickedNotificationComponent.notificationClickHandlingReducer, - clickedNotificationComponent.notificationClickHandlingActionDispatcher, - appGraph.buildNotificationComponent().notificationInteractor, - appGraph.buildPushNotificationsComponent().pushNotificationsInteractor, - welcomeOnboardingComponent.welcomeOnboardingReducer, - welcomeOnboardingComponent.welcomeOnboardingActionDispatcher, - appGraph.commonComponent.platform, - appGraph.loggerComponent.logger, - appGraph.commonComponent.buildKonfig.buildVariant + initialState = initialState, + appInteractor = appGraph.buildMainDataComponent().appInteractor, + authInteractor = appGraph.authComponent.authInteractor, + currentProfileStateRepository = appGraph.profileDataComponent.currentProfileStateRepository, + sentryInteractor = appGraph.sentryComponent.sentryInteractor, + stateRepositoriesComponent = appGraph.stateRepositoriesComponent, + streakRecoveryReducer = streakRecoveryComponent.streakRecoveryReducer, + streakRecoveryActionDispatcher = streakRecoveryComponent.streakRecoveryActionDispatcher, + clickedNotificationReducer = clickedNotificationComponent.notificationClickHandlingReducer, + notificationClickHandlingActionDispatcher = clickedNotificationComponent.notificationClickHandlingActionDispatcher, + notificationsInteractor = appGraph.buildNotificationComponent().notificationInteractor, + pushNotificationsInteractor = appGraph.buildPushNotificationsComponent().pushNotificationsInteractor, + welcomeOnboardingReducer = welcomeOnboardingComponent.welcomeOnboardingReducer, + welcomeOnboardingActionDispatcher = welcomeOnboardingComponent.welcomeOnboardingActionDispatcher, + purchaseInteractor = appGraph.buildPurchaseComponent().purchaseInteractor, + currentSubscriptionStateRepository = appGraph.stateRepositoriesComponent.currentSubscriptionStateRepository, + subscriptionsInteractor = appGraph.subscriptionDataComponent.subscriptionsInteractor, + platform = appGraph.commonComponent.platform, + logger = appGraph.loggerComponent.logger, + buildVariant = appGraph.commonComponent.buildKonfig.buildVariant ) override fun appFeature(): Feature = diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/main/presentation/AppActionDispatcher.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/main/presentation/AppActionDispatcher.kt index 06484459b9..8cae43187d 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/main/presentation/AppActionDispatcher.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/main/presentation/AppActionDispatcher.kt @@ -1,5 +1,9 @@ package org.hyperskill.app.main.presentation +import co.touchlab.kermit.Logger +import kotlinx.coroutines.async +import kotlinx.coroutines.coroutineScope +import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import org.hyperskill.app.auth.domain.interactor.AuthInteractor @@ -12,24 +16,34 @@ import org.hyperskill.app.main.presentation.AppFeature.Action import org.hyperskill.app.main.presentation.AppFeature.Message import org.hyperskill.app.notification.local.domain.interactor.NotificationInteractor import org.hyperskill.app.notification.remote.domain.interactor.PushNotificationsInteractor -import org.hyperskill.app.profile.domain.model.Profile -import org.hyperskill.app.profile.domain.model.isNewUser -import org.hyperskill.app.profile.domain.repository.CurrentProfileStateRepository +import org.hyperskill.app.purchases.domain.interactor.PurchaseInteractor import org.hyperskill.app.sentry.domain.interactor.SentryInteractor import org.hyperskill.app.sentry.domain.model.breadcrumb.HyperskillSentryBreadcrumbBuilder import org.hyperskill.app.sentry.domain.model.transaction.HyperskillSentryTransactionBuilder +import org.hyperskill.app.sentry.domain.withTransaction +import org.hyperskill.app.subscriptions.domain.interactor.SubscriptionsInteractor +import org.hyperskill.app.subscriptions.domain.model.Subscription +import org.hyperskill.app.subscriptions.domain.model.SubscriptionType +import org.hyperskill.app.subscriptions.domain.model.isExpired +import org.hyperskill.app.subscriptions.domain.model.isValidTillPassed +import org.hyperskill.app.subscriptions.domain.repository.CurrentSubscriptionStateRepository import ru.nobird.app.presentation.redux.dispatcher.CoroutineActionDispatcher internal class AppActionDispatcher( config: ActionDispatcherOptions, private val appInteractor: AppInteractor, private val authInteractor: AuthInteractor, - private val currentProfileStateRepository: CurrentProfileStateRepository, private val sentryInteractor: SentryInteractor, private val stateRepositoriesComponent: StateRepositoriesComponent, private val notificationsInteractor: NotificationInteractor, private val pushNotificationsInteractor: PushNotificationsInteractor, + private val purchaseInteractor: PurchaseInteractor, + private val currentSubscriptionStateRepository: CurrentSubscriptionStateRepository, + private val subscriptionsInteractor: SubscriptionsInteractor, + private val isSubscriptionPurchaseEnabled: Boolean, + private val logger: Logger ) : CoroutineActionDispatcher(config.createConfig()) { + init { authInteractor .observeUserDeauthorization() @@ -50,75 +64,116 @@ internal class AppActionDispatcher( onNewMessage(Message.UserDeauthorized(it.reason)) } .launchIn(actionScope) + + if (isSubscriptionPurchaseEnabled) { + currentSubscriptionStateRepository + .changes + .distinctUntilChanged() + .onEach { subscription -> + onNewMessage(AppFeature.InternalMessage.SubscriptionChanged(subscription)) + } + .launchIn(actionScope) + } } override suspend fun doSuspendableAction(action: Action) { when (action) { - is Action.DetermineUserAccountStatus -> { - val isAuthorized = authInteractor.isAuthorized() - .getOrDefault(false) - - val transaction = HyperskillSentryTransactionBuilder.buildAppScreenRemoteDataLoading(isAuthorized) - sentryInteractor.startTransaction(transaction) - - sentryInteractor.addBreadcrumb(HyperskillSentryBreadcrumbBuilder.buildAppDetermineUserAccountStatus()) - - // TODO: Move this logic to reducer - val profileResult: Result = if (isAuthorized) { - currentProfileStateRepository - .getStateWithSource(forceUpdate = false) - .fold( - onSuccess = { (profile, usedDataSourceType) -> - /** - * ALTAPPS-693: - * If cached user is new, we need to fetch profile from remote to check if track selected - */ - if (profile.isNewUser && usedDataSourceType == DataSourceType.CACHE) { - currentProfileStateRepository.getState(forceUpdate = true) - } else { - Result.success(profile) - } - }, - onFailure = { currentProfileStateRepository.getState(forceUpdate = true) } - ) - } else { - currentProfileStateRepository.getState(forceUpdate = true) - } - - profileResult - .fold( - onSuccess = { profile -> - sentryInteractor.addBreadcrumb( - HyperskillSentryBreadcrumbBuilder.buildAppDetermineUserAccountStatusSuccess() - ) - sentryInteractor.finishTransaction(transaction) - onNewMessage(Message.UserAccountStatus(profile, action.pushNotificationData)) - }, - onFailure = { exception -> - sentryInteractor.addBreadcrumb( - HyperskillSentryBreadcrumbBuilder.buildAppDetermineUserAccountStatusError(exception) - ) - sentryInteractor.finishTransaction(transaction, exception) - onNewMessage(Message.UserAccountStatusError) - } - ) - } + is Action.FetchAppStartupConfig -> + handleFetchAppStartupConfig(action, ::onNewMessage) is Action.IdentifyUserInSentry -> sentryInteractor.setUsedId(action.userId) is Action.ClearUserInSentry -> sentryInteractor.clearCurrentUser() is Action.UpdateDailyLearningNotificationTime -> handleUpdateDailyLearningNotificationTime() - is Action.SendPushNotificationsToken -> { + is Action.SendPushNotificationsToken -> pushNotificationsInteractor.renewFCMToken() - } - is Action.LogAppLaunchFirstTimeAnalyticEventIfNeeded -> { + is Action.IdentifyUserInPurchaseSdk -> + handleIdentifyUserInPurchaseSdk(action.userId) + is Action.LogAppLaunchFirstTimeAnalyticEventIfNeeded -> appInteractor.logAppLaunchFirstTimeAnalyticEventIfNeeded() - } + is AppFeature.InternalAction.FetchSubscription -> + handleFetchSubscription(::onNewMessage) + is AppFeature.InternalAction.RefreshSubscriptionOnExpiration -> + subscriptionsInteractor.refreshSubscriptionOnExpirationIfNeeded(action.subscription) + is AppFeature.InternalAction.CancelSubscriptionRefresh -> + subscriptionsInteractor.cancelSubscriptionRefresh() else -> {} } } + private suspend fun handleFetchAppStartupConfig( + action: Action.FetchAppStartupConfig, + onNewMessage: (Message) -> Unit + ) { + val isAuthorized = + authInteractor.isAuthorized().getOrDefault(false) + + sentryInteractor.withTransaction( + transaction = HyperskillSentryTransactionBuilder.buildAppScreenRemoteDataLoading(isAuthorized), + onError = { e -> + sentryInteractor.addBreadcrumb( + HyperskillSentryBreadcrumbBuilder.buildAppDetermineUserAccountStatusError(e) + ) + Message.FetchAppStartupConfigError + } + ) { + coroutineScope { + sentryInteractor.addBreadcrumb(HyperskillSentryBreadcrumbBuilder.buildAppDetermineUserAccountStatus()) + + val profileDeferred = async { appInteractor.fetchProfile(isAuthorized) } + val subscriptionDeferred = async { fetchSubscription(isAuthorized) } + + val profile = profileDeferred.await().getOrThrow() + val subscription = subscriptionDeferred.await() + + sentryInteractor.addBreadcrumb( + HyperskillSentryBreadcrumbBuilder.buildAppDetermineUserAccountStatusSuccess() + ) + + Message.FetchAppStartupConfigSuccess( + profile = profile, + subscription = subscription, + notificationData = action.pushNotificationData + ) + } + }.let(onNewMessage) + } + + private suspend fun fetchSubscription(isAuthorized: Boolean = true): Subscription? = + if (isAuthorized && isSubscriptionPurchaseEnabled) { + currentSubscriptionStateRepository + .getStateWithSource(forceUpdate = false) + .fold( + onSuccess = { (subscription, usedDataSourceType) -> + // Fetch subscription from remote + // if the user has the mobile-only subscription + // and its valid time is passed + val shouldFetchSubscriptionFromRemote = + usedDataSourceType == DataSourceType.CACHE && + subscription.type == SubscriptionType.MOBILE_ONLY && + (subscription.isExpired || subscription.isValidTillPassed()) + if (shouldFetchSubscriptionFromRemote) { + currentSubscriptionStateRepository + .getState(forceUpdate = true) + .getOrNull() + } else { + subscription + } + }, + onFailure = { + currentSubscriptionStateRepository + .getState(forceUpdate = true) + .onFailure { e -> + logger.e(e) { "Failed to fetch subscription" } + } + .getOrNull() + } + ) + } else { + null + } + private suspend fun handleUpdateDailyLearningNotificationTime() { notificationsInteractor .updateTimeZone() @@ -128,4 +183,24 @@ internal class AppActionDispatcher( ) } } + + private suspend fun handleIdentifyUserInPurchaseSdk(userId: Long) { + if (isSubscriptionPurchaseEnabled) { + purchaseInteractor + .login(userId) + .onFailure { + logger.e(it) { + "Failed to login user in the purchase sdk" + } + } + } + } + + private suspend fun handleFetchSubscription(onNewMessage: (Message) -> Unit) { + fetchSubscription()?.let { + onNewMessage( + AppFeature.InternalMessage.SubscriptionChanged(it) + ) + } + } } \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/main/presentation/AppFeature.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/main/presentation/AppFeature.kt index 23289395aa..c1cdf44f99 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/main/presentation/AppFeature.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/main/presentation/AppFeature.kt @@ -4,11 +4,14 @@ import kotlinx.serialization.Serializable import org.hyperskill.app.auth.domain.model.UserDeauthorized.Reason import org.hyperskill.app.notification.click_handling.presentation.NotificationClickHandlingFeature import org.hyperskill.app.notification.remote.domain.model.PushNotificationData +import org.hyperskill.app.paywall.domain.model.PaywallTransitionSource import org.hyperskill.app.profile.domain.model.Profile import org.hyperskill.app.streak_recovery.presentation.StreakRecoveryFeature +import org.hyperskill.app.subscriptions.domain.model.Subscription import org.hyperskill.app.welcome_onboarding.presentation.WelcomeOnboardingFeature -interface AppFeature { +object AppFeature { + @Serializable sealed interface State { @Serializable @@ -25,8 +28,14 @@ interface AppFeature { val isAuthorized: Boolean, val isMobileLeaderboardsEnabled: Boolean, internal val streakRecoveryState: StreakRecoveryFeature.State = StreakRecoveryFeature.State(), - internal val welcomeOnboardingState: WelcomeOnboardingFeature.State = WelcomeOnboardingFeature.State() - ) : State + internal val welcomeOnboardingState: WelcomeOnboardingFeature.State = WelcomeOnboardingFeature.State(), + internal val isMobileOnlySubscriptionEnabled: Boolean, + internal val subscription: Subscription? = null, + internal val appShowsCount: Int = 1 + ) : State { + internal fun incrementAppShowsCount(): Ready = + copy(appShowsCount = appShowsCount + 1) + } } sealed interface Message { @@ -35,11 +44,14 @@ interface AppFeature { val forceUpdate: Boolean = false ) : Message - data class UserAccountStatus( + object AppBecomesActive : Message + + data class FetchAppStartupConfigSuccess( val profile: Profile, + val subscription: Subscription?, val notificationData: PushNotificationData? ) : Message - object UserAccountStatusError : Message + object FetchAppStartupConfigError : Message data class UserAuthorized( val profile: Profile, @@ -68,8 +80,14 @@ interface AppFeature { ) : Message } + internal sealed interface InternalMessage : Message { + data class SubscriptionChanged( + val subscription: Subscription + ) : InternalMessage + } + sealed interface Action { - data class DetermineUserAccountStatus( + data class FetchAppStartupConfig( val pushNotificationData: PushNotificationData? ) : Action @@ -98,12 +116,18 @@ interface AppFeature { data class IdentifyUserInSentry(val userId: Long) : Action object ClearUserInSentry : Action + data class IdentifyUserInPurchaseSdk(val userId: Long) : Action + sealed interface ViewAction : Action { sealed interface NavigateTo : ViewAction { data class AuthScreen(val isInSignUpMode: Boolean = false) : NavigateTo object TrackSelectionScreen : NavigateTo - object OnboardingScreen : NavigateTo + object WelcomeScreen : NavigateTo object StudyPlan : NavigateTo + data class Paywall(val paywallTransitionSource: PaywallTransitionSource) : NavigateTo + data class StudyPlanWithPaywall( + val paywallTransitionSource: PaywallTransitionSource + ) : NavigateTo } /** @@ -120,4 +144,12 @@ interface AppFeature { ) : ViewAction } } + + internal sealed interface InternalAction : Action { + object FetchSubscription : InternalAction + + data class RefreshSubscriptionOnExpiration(val subscription: Subscription) : InternalAction + + object CancelSubscriptionRefresh : InternalAction + } } \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/main/presentation/AppReducer.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/main/presentation/AppReducer.kt index 904ec407ee..1081cdbbe2 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/main/presentation/AppReducer.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/main/presentation/AppReducer.kt @@ -3,15 +3,21 @@ package org.hyperskill.app.main.presentation import org.hyperskill.app.auth.domain.model.UserDeauthorized import org.hyperskill.app.core.domain.platform.PlatformType import org.hyperskill.app.main.presentation.AppFeature.Action +import org.hyperskill.app.main.presentation.AppFeature.InternalAction +import org.hyperskill.app.main.presentation.AppFeature.InternalMessage import org.hyperskill.app.main.presentation.AppFeature.Message import org.hyperskill.app.main.presentation.AppFeature.State import org.hyperskill.app.notification.click_handling.presentation.NotificationClickHandlingFeature import org.hyperskill.app.notification.click_handling.presentation.NotificationClickHandlingReducer +import org.hyperskill.app.paywall.domain.model.PaywallTransitionSource import org.hyperskill.app.profile.domain.model.Profile import org.hyperskill.app.profile.domain.model.isMobileLeaderboardsEnabled +import org.hyperskill.app.profile.domain.model.isMobileOnlySubscriptionEnabled import org.hyperskill.app.profile.domain.model.isNewUser import org.hyperskill.app.streak_recovery.presentation.StreakRecoveryFeature import org.hyperskill.app.streak_recovery.presentation.StreakRecoveryReducer +import org.hyperskill.app.subscriptions.domain.model.Subscription +import org.hyperskill.app.subscriptions.domain.model.isFreemium import org.hyperskill.app.welcome_onboarding.presentation.WelcomeOnboardingFeature import org.hyperskill.app.welcome_onboarding.presentation.WelcomeOnboardingReducer import org.hyperskill.app.welcome_onboarding.presentation.getFinishAction @@ -25,6 +31,11 @@ internal class AppReducer( private val welcomeOnboardingReducer: WelcomeOnboardingReducer, private val platformType: PlatformType ) : StateReducer { + + companion object { + internal const val APP_SHOWS_COUNT_TILL_PAYWALL = 3 + } + override fun reduce( state: State, message: Message @@ -33,36 +44,38 @@ internal class AppReducer( is Message.Initialize -> { if (state is State.Idle || (state is State.NetworkError && message.forceUpdate)) { State.Loading to setOf( - Action.DetermineUserAccountStatus(message.pushNotificationData), + Action.FetchAppStartupConfig(message.pushNotificationData), Action.LogAppLaunchFirstTimeAnalyticEventIfNeeded ) } else { null } } - is Message.UserAccountStatus -> - handleUserAccountStatus(state, message) - is Message.UserAccountStatusError -> + is Message.FetchAppStartupConfigSuccess -> + handleFetchAppStartupConfigSuccess(state, message) + is Message.FetchAppStartupConfigError -> if (state is State.Loading) { State.NetworkError to emptySet() } else { null } + is Message.AppBecomesActive -> handleAppBecomesActive(state) is Message.UserAuthorized -> handleUserAuthorized(state, message) is Message.UserDeauthorized -> if (state is State.Ready && state.isAuthorized) { val navigateToViewAction = when (message.reason) { UserDeauthorized.Reason.TOKEN_REFRESH_FAILURE -> - Action.ViewAction.NavigateTo.OnboardingScreen + Action.ViewAction.NavigateTo.WelcomeScreen UserDeauthorized.Reason.SIGN_OUT -> Action.ViewAction.NavigateTo.AuthScreen() } State.Ready( isAuthorized = false, - isMobileLeaderboardsEnabled = false - ) to setOf(Action.ClearUserInSentry, navigateToViewAction) + isMobileLeaderboardsEnabled = false, + isMobileOnlySubscriptionEnabled = false + ) to getDeauthorizedUserActions() + setOf(navigateToViewAction) } else { null } @@ -84,15 +97,36 @@ internal class AppReducer( state to reduceNotificationClickHandlingMessage(message.message) is Message.WelcomeOnboardingMessage -> reduceWelcomeOnboardingMessage(state, message.message) + is InternalMessage.SubscriptionChanged -> + handleSubscriptionChanged(state, message) } ?: (state to emptySet()) - private fun handleUserAccountStatus( + private fun handleFetchAppStartupConfigSuccess( state: State, - message: Message.UserAccountStatus + message: Message.FetchAppStartupConfigSuccess ): ReducerResult = if (state is State.Loading) { val isAuthorized = !message.profile.isGuest + val (streakRecoveryState, streakRecoveryActions) = + if (isAuthorized && message.notificationData == null) { + reduceStreakRecoveryMessage( + StreakRecoveryFeature.State(), + StreakRecoveryFeature.Message.Initialize + ) + } else { + StreakRecoveryFeature.State() to emptySet() + } + + val readyState = State.Ready( + isAuthorized = isAuthorized, + isMobileLeaderboardsEnabled = message.profile.features.isMobileLeaderboardsEnabled, + streakRecoveryState = streakRecoveryState, + appShowsCount = 0, // This is a hack to show paywall on the first app start + subscription = message.subscription, + isMobileOnlySubscriptionEnabled = message.profile.features.isMobileOnlySubscriptionEnabled + ) + val actions: Set = buildSet { if (isAuthorized) { @@ -109,10 +143,22 @@ internal class AppReducer( ) message.profile.isNewUser -> add(Action.ViewAction.NavigateTo.TrackSelectionScreen) + shouldShowPaywall(readyState) -> + add( + Action.ViewAction.NavigateTo.StudyPlanWithPaywall( + PaywallTransitionSource.APP_BECOMES_ACTIVE + ) + ) else -> add(Action.ViewAction.NavigateTo.StudyPlan) } - addAll(getOnAuthorizedAppStartUpActions(message.profile.id, platformType)) + addAll( + getOnAuthorizedAppStartUpActions( + profileId = message.profile.id, + subscription = message.subscription, + platformType = platformType + ) + ) } else { if (message.notificationData != null) { addAll( @@ -126,25 +172,12 @@ internal class AppReducer( ) } addAll(getNotAuthorizedAppStartUpActions()) - add(Action.ViewAction.NavigateTo.OnboardingScreen) + add(Action.ViewAction.NavigateTo.WelcomeScreen) } + addAll(streakRecoveryActions) } - val (streakRecoveryState, streakRecoveryActions) = - if (isAuthorized && message.notificationData == null) { - reduceStreakRecoveryMessage( - StreakRecoveryFeature.State(), - StreakRecoveryFeature.Message.Initialize - ) - } else { - StreakRecoveryFeature.State() to emptySet() - } - - State.Ready( - isAuthorized = isAuthorized, - isMobileLeaderboardsEnabled = message.profile.features.isMobileLeaderboardsEnabled, - streakRecoveryState = streakRecoveryState - ) to actions + streakRecoveryActions + readyState.incrementAppShowsCount() to actions } else { state to emptySet() } @@ -156,7 +189,8 @@ internal class AppReducer( if (state is State.Ready && !state.isAuthorized) { val authState = State.Ready( isAuthorized = true, - isMobileLeaderboardsEnabled = message.profile.features.isMobileLeaderboardsEnabled + isMobileLeaderboardsEnabled = message.profile.features.isMobileLeaderboardsEnabled, + isMobileOnlySubscriptionEnabled = message.profile.features.isMobileOnlySubscriptionEnabled ) val (onboardingState, onboardingActions) = reduceWelcomeOnboardingMessage( WelcomeOnboardingFeature.State(), @@ -171,6 +205,26 @@ internal class AppReducer( state to emptySet() } + private fun handleAppBecomesActive(state: State): ReducerResult = + if (state is State.Ready) { + state.incrementAppShowsCount() to + if (shouldShowPaywall(state)) { + setOf( + Action.ViewAction.NavigateTo.Paywall(PaywallTransitionSource.APP_BECOMES_ACTIVE) + ) + } else { + emptySet() + } + } else { + state to emptySet() + } + + private fun shouldShowPaywall(state: State.Ready): Boolean = + state.isAuthorized && + state.isMobileOnlySubscriptionEnabled && + state.subscription?.isFreemium == true && + state.appShowsCount % APP_SHOWS_COUNT_TILL_PAYWALL == 0 + private fun reduceStreakRecoveryMessage( state: StreakRecoveryFeature.State, message: StreakRecoveryFeature.Message @@ -266,10 +320,13 @@ internal class AppReducer( private fun getOnAuthorizedAppStartUpActions( profileId: Long, + subscription: Subscription?, platformType: PlatformType ): Set = setOfNotNull( Action.IdentifyUserInSentry(userId = profileId), + Action.IdentifyUserInPurchaseSdk(userId = profileId), + subscription?.let(InternalAction::RefreshSubscriptionOnExpiration), Action.UpdateDailyLearningNotificationTime, if (platformType == PlatformType.ANDROID) { // Don't send push token on app startup for IOS @@ -285,8 +342,28 @@ internal class AppReducer( private fun getAuthorizedUserActions(profile: Profile): Set = setOf( + InternalAction.FetchSubscription, Action.IdentifyUserInSentry(userId = profile.id), + Action.IdentifyUserInPurchaseSdk(userId = profile.id), Action.UpdateDailyLearningNotificationTime, Action.SendPushNotificationsToken ) + + private fun getDeauthorizedUserActions(): Set = + setOf( + Action.ClearUserInSentry, + InternalAction.CancelSubscriptionRefresh + ) + + private fun handleSubscriptionChanged( + state: State, + message: InternalMessage.SubscriptionChanged + ): ReducerResult = + if (state is State.Ready) { + state.copy(subscription = message.subscription) to setOf( + InternalAction.RefreshSubscriptionOnExpiration(message.subscription) + ) + } else { + state to emptySet() + } } \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/manage_subscription/domain/analytic/ManageSubscriptionClickedManageHyperskillAnalyticEvent.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/manage_subscription/domain/analytic/ManageSubscriptionClickedManageHyperskillAnalyticEvent.kt new file mode 100644 index 0000000000..3e1b840822 --- /dev/null +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/manage_subscription/domain/analytic/ManageSubscriptionClickedManageHyperskillAnalyticEvent.kt @@ -0,0 +1,29 @@ +package org.hyperskill.app.manage_subscription.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.HyperskillAnalyticPart +import org.hyperskill.app.analytic.domain.model.hyperskill.HyperskillAnalyticRoute +import org.hyperskill.app.analytic.domain.model.hyperskill.HyperskillAnalyticTarget + +/** + * Represents a click analytic event of the manage subscription button. + * + * JSON payload: + * ``` + * { + * "route": "/profile/settings/manage-subscription", + * "action": "click", + * "part": "main", + * "target": "manage_subscription" + * } + * ``` + * + * @see HyperskillAnalyticEvent + */ +object ManageSubscriptionClickedManageHyperskillAnalyticEvent : HyperskillAnalyticEvent( + HyperskillAnalyticRoute.Profile.Settings.ManageSubscription, + HyperskillAnalyticAction.CLICK, + HyperskillAnalyticPart.MAIN, + HyperskillAnalyticTarget.MANAGE_SUBSCRIPTION +) \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/manage_subscription/domain/analytic/ManageSubscriptionViewedHyperskillAnalyticEvent.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/manage_subscription/domain/analytic/ManageSubscriptionViewedHyperskillAnalyticEvent.kt new file mode 100644 index 0000000000..6880bab27b --- /dev/null +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/manage_subscription/domain/analytic/ManageSubscriptionViewedHyperskillAnalyticEvent.kt @@ -0,0 +1,23 @@ +package org.hyperskill.app.manage_subscription.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 a view analytic event. + * + * JSON payload: + * ``` + * { + * "route": "/profile/settings/manage-subscription", + * "action": "view" + * } + * ``` + * + * @see HyperskillAnalyticEvent + */ +object ManageSubscriptionViewedHyperskillAnalyticEvent : HyperskillAnalyticEvent( + HyperskillAnalyticRoute.Profile.Settings.ManageSubscription, + HyperskillAnalyticAction.VIEW +) \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/manage_subscription/domain/analytic/RenewSubscriptionClickedManageHyperskillAnalyticEvent.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/manage_subscription/domain/analytic/RenewSubscriptionClickedManageHyperskillAnalyticEvent.kt new file mode 100644 index 0000000000..4ee130cf3a --- /dev/null +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/manage_subscription/domain/analytic/RenewSubscriptionClickedManageHyperskillAnalyticEvent.kt @@ -0,0 +1,29 @@ +package org.hyperskill.app.manage_subscription.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.HyperskillAnalyticPart +import org.hyperskill.app.analytic.domain.model.hyperskill.HyperskillAnalyticRoute +import org.hyperskill.app.analytic.domain.model.hyperskill.HyperskillAnalyticTarget + +/** + * Represents a click analytic event of the manage subscription button. + * + * JSON payload: + * ``` + * { + * "route": "/profile/settings/manage-subscription", + * "action": "click", + * "part": "main", + * "target": "renew_subscription" + * } + * ``` + * + * @see HyperskillAnalyticEvent + */ +object RenewSubscriptionClickedManageHyperskillAnalyticEvent : HyperskillAnalyticEvent( + HyperskillAnalyticRoute.Profile.Settings.ManageSubscription, + HyperskillAnalyticAction.CLICK, + HyperskillAnalyticPart.MAIN, + HyperskillAnalyticTarget.RENEW_SUBSCRIPTION +) \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/manage_subscription/injection/ManageSubscriptionComponent.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/manage_subscription/injection/ManageSubscriptionComponent.kt new file mode 100644 index 0000000000..51e5966619 --- /dev/null +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/manage_subscription/injection/ManageSubscriptionComponent.kt @@ -0,0 +1,10 @@ +package org.hyperskill.app.manage_subscription.injection + +import org.hyperskill.app.manage_subscription.presentation.ManageSubscriptionFeature.Action +import org.hyperskill.app.manage_subscription.presentation.ManageSubscriptionFeature.Message +import org.hyperskill.app.manage_subscription.presentation.ManageSubscriptionFeature.ViewState +import ru.nobird.app.presentation.redux.feature.Feature + +interface ManageSubscriptionComponent { + val manageSubscriptionFeature: Feature +} \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/manage_subscription/injection/ManageSubscriptionComponentImpl.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/manage_subscription/injection/ManageSubscriptionComponentImpl.kt new file mode 100644 index 0000000000..fa7dec45df --- /dev/null +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/manage_subscription/injection/ManageSubscriptionComponentImpl.kt @@ -0,0 +1,23 @@ +package org.hyperskill.app.manage_subscription.injection + +import org.hyperskill.app.core.injection.AppGraph +import org.hyperskill.app.manage_subscription.presentation.ManageSubscriptionFeature.Action +import org.hyperskill.app.manage_subscription.presentation.ManageSubscriptionFeature.Message +import org.hyperskill.app.manage_subscription.presentation.ManageSubscriptionFeature.ViewState +import ru.nobird.app.presentation.redux.feature.Feature + +internal class ManageSubscriptionComponentImpl( + private val appGraph: AppGraph +) : ManageSubscriptionComponent { + override val manageSubscriptionFeature: Feature + get() = ManageSubscriptionFeatureBuilder.build( + analyticInteractor = appGraph.analyticComponent.analyticInteractor, + currentSubscriptionStateRepository = appGraph.stateRepositoriesComponent.currentSubscriptionStateRepository, + purchaseInteractor = appGraph.buildPurchaseComponent().purchaseInteractor, + sentryInteractor = appGraph.sentryComponent.sentryInteractor, + resourceProvider = appGraph.commonComponent.resourceProvider, + dateFormatter = appGraph.commonComponent.dateFormatter, + logger = appGraph.loggerComponent.logger, + buildVariant = appGraph.commonComponent.buildKonfig.buildVariant + ) +} \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/manage_subscription/injection/ManageSubscriptionFeatureBuilder.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/manage_subscription/injection/ManageSubscriptionFeatureBuilder.kt new file mode 100644 index 0000000000..31c6c5d9d6 --- /dev/null +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/manage_subscription/injection/ManageSubscriptionFeatureBuilder.kt @@ -0,0 +1,60 @@ +package org.hyperskill.app.manage_subscription.injection + +import co.touchlab.kermit.Logger +import org.hyperskill.app.analytic.domain.interactor.AnalyticInteractor +import org.hyperskill.app.core.domain.BuildVariant +import org.hyperskill.app.core.presentation.ActionDispatcherOptions +import org.hyperskill.app.core.presentation.transformState +import org.hyperskill.app.core.view.mapper.ResourceProvider +import org.hyperskill.app.core.view.mapper.date.SharedDateFormatter +import org.hyperskill.app.logging.presentation.wrapWithLogger +import org.hyperskill.app.manage_subscription.presentation.ManageSubscriptionActionDispatcher +import org.hyperskill.app.manage_subscription.presentation.ManageSubscriptionFeature.Action +import org.hyperskill.app.manage_subscription.presentation.ManageSubscriptionFeature.Message +import org.hyperskill.app.manage_subscription.presentation.ManageSubscriptionFeature.State +import org.hyperskill.app.manage_subscription.presentation.ManageSubscriptionFeature.ViewState +import org.hyperskill.app.manage_subscription.presentation.ManageSubscriptionReducer +import org.hyperskill.app.manage_subscription.view.mapper.ManageSubscriptionViewStateMapper +import org.hyperskill.app.purchases.domain.interactor.PurchaseInteractor +import org.hyperskill.app.sentry.domain.interactor.SentryInteractor +import org.hyperskill.app.subscriptions.domain.repository.CurrentSubscriptionStateRepository +import ru.nobird.app.presentation.redux.dispatcher.wrapWithActionDispatcher +import ru.nobird.app.presentation.redux.feature.Feature +import ru.nobird.app.presentation.redux.feature.ReduxFeature + +internal object ManageSubscriptionFeatureBuilder { + private const val LOG_TAG = "ManageSubscriptionFeature" + + fun build( + analyticInteractor: AnalyticInteractor, + currentSubscriptionStateRepository: CurrentSubscriptionStateRepository, + purchaseInteractor: PurchaseInteractor, + sentryInteractor: SentryInteractor, + resourceProvider: ResourceProvider, + dateFormatter: SharedDateFormatter, + logger: Logger, + buildVariant: BuildVariant + ): Feature { + val manageSubscriptionReducer = + ManageSubscriptionReducer() + .wrapWithLogger(buildVariant, logger, LOG_TAG) + + val manageSubscriptionActionDispatcher = ManageSubscriptionActionDispatcher( + config = ActionDispatcherOptions(), + analyticInteractor = analyticInteractor, + currentSubscriptionStateRepository = currentSubscriptionStateRepository, + purchaseInteractor = purchaseInteractor, + sentryInteractor = sentryInteractor, + logger = logger.withTag(LOG_TAG) + ) + + val viewStateMapper = ManageSubscriptionViewStateMapper(resourceProvider, dateFormatter) + + return ReduxFeature( + initialState = State.Idle, + reducer = manageSubscriptionReducer + ) + .wrapWithActionDispatcher(manageSubscriptionActionDispatcher) + .transformState(viewStateMapper::map) + } +} \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/manage_subscription/presentation/ManageSubscriptionActionDispatcher.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/manage_subscription/presentation/ManageSubscriptionActionDispatcher.kt new file mode 100644 index 0000000000..f7d72db916 --- /dev/null +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/manage_subscription/presentation/ManageSubscriptionActionDispatcher.kt @@ -0,0 +1,86 @@ +package org.hyperskill.app.manage_subscription.presentation + +import co.touchlab.kermit.Logger +import kotlinx.coroutines.async +import kotlinx.coroutines.coroutineScope +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach +import org.hyperskill.app.analytic.domain.interactor.AnalyticInteractor +import org.hyperskill.app.core.presentation.ActionDispatcherOptions +import org.hyperskill.app.manage_subscription.presentation.ManageSubscriptionFeature.Action +import org.hyperskill.app.manage_subscription.presentation.ManageSubscriptionFeature.InternalAction +import org.hyperskill.app.manage_subscription.presentation.ManageSubscriptionFeature.InternalMessage +import org.hyperskill.app.manage_subscription.presentation.ManageSubscriptionFeature.Message +import org.hyperskill.app.purchases.domain.interactor.PurchaseInteractor +import org.hyperskill.app.sentry.domain.interactor.SentryInteractor +import org.hyperskill.app.sentry.domain.model.transaction.HyperskillSentryTransactionBuilder +import org.hyperskill.app.sentry.domain.withTransaction +import org.hyperskill.app.subscriptions.domain.repository.CurrentSubscriptionStateRepository +import ru.nobird.app.presentation.redux.dispatcher.CoroutineActionDispatcher + +internal class ManageSubscriptionActionDispatcher( + config: ActionDispatcherOptions, + private val analyticInteractor: AnalyticInteractor, + private val currentSubscriptionStateRepository: CurrentSubscriptionStateRepository, + private val purchaseInteractor: PurchaseInteractor, + private val sentryInteractor: SentryInteractor, + private val logger: Logger +) : CoroutineActionDispatcher(config.createConfig()) { + + init { + currentSubscriptionStateRepository + .changes + .distinctUntilChanged() + .onEach { subscription -> + onNewMessage( + InternalMessage.SubscriptionChanged( + subscription = subscription, + manageSubscriptionUrl = purchaseInteractor.getManagementUrl().getOrNull() + ) + ) + } + .launchIn(actionScope) + } + + override suspend fun doSuspendableAction(action: Action) { + when (action) { + is InternalAction.FetchSubscription -> + handleFetchSubscription(::onNewMessage) + is InternalAction.LogAnalyticEvent -> + analyticInteractor.logEvent(action.analyticEvent) + else -> { + // no op + } + } + } + + private suspend fun handleFetchSubscription( + onNewMessage: (Message) -> Unit + ) { + sentryInteractor.withTransaction( + transaction = HyperskillSentryTransactionBuilder.buildManageSubscriptionFeatureFetchSubscription(), + onError = { + InternalMessage.FetchSubscriptionError + } + ) { + coroutineScope { + val subscriptionDeferred = async { + currentSubscriptionStateRepository + .getState(forceUpdate = true) + .onFailure { + logger.e(it) { "Failed to load subscription" } + } + } + val manageSubscriptionUrlDeferred = async { + purchaseInteractor.getManagementUrl() + } + + InternalMessage.FetchSubscriptionSuccess( + subscription = subscriptionDeferred.await().getOrThrow(), + manageSubscriptionUrl = manageSubscriptionUrlDeferred.await().getOrThrow() + ) + } + }.let(onNewMessage) + } +} \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/manage_subscription/presentation/ManageSubscriptionFeature.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/manage_subscription/presentation/ManageSubscriptionFeature.kt new file mode 100644 index 0000000000..4697acc298 --- /dev/null +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/manage_subscription/presentation/ManageSubscriptionFeature.kt @@ -0,0 +1,71 @@ +package org.hyperskill.app.manage_subscription.presentation + +import org.hyperskill.app.analytic.domain.model.AnalyticEvent +import org.hyperskill.app.paywall.domain.model.PaywallTransitionSource +import org.hyperskill.app.subscriptions.domain.model.Subscription + +object ManageSubscriptionFeature { + internal sealed interface State { + object Idle : State + + object Loading : State + + object Error : State + + data class Content( + val subscription: Subscription, + val manageSubscriptionUrl: String? + ) : State + } + + sealed interface ViewState { + object Idle : ViewState + + object Loading : ViewState + + object Error : ViewState + + data class Content( + val validUntilFormatted: String?, + val buttonText: String? + ) : ViewState + } + + sealed interface Message { + object Initialize : Message + object RetryContentLoading : Message + + object ActionButtonClicked : Message + + object ViewedEventMessage : Message + } + + internal sealed interface InternalMessage : Message { + object FetchSubscriptionError : InternalMessage + data class FetchSubscriptionSuccess( + val subscription: Subscription, + val manageSubscriptionUrl: String? + ) : InternalMessage + + data class SubscriptionChanged( + val subscription: Subscription, + val manageSubscriptionUrl: String? + ) : InternalMessage + } + + sealed interface Action { + sealed interface ViewAction : Action { + data class OpenUrl(val url: String) : ViewAction + + sealed interface NavigateTo : ViewAction { + data class Paywall(val paywallTransitionSource: PaywallTransitionSource) : NavigateTo + } + } + } + + internal sealed interface InternalAction : Action { + object FetchSubscription : InternalAction + + data class LogAnalyticEvent(val analyticEvent: AnalyticEvent) : InternalAction + } +} \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/manage_subscription/presentation/ManageSubscriptionReducer.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/manage_subscription/presentation/ManageSubscriptionReducer.kt new file mode 100644 index 0000000000..a407910376 --- /dev/null +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/manage_subscription/presentation/ManageSubscriptionReducer.kt @@ -0,0 +1,86 @@ +package org.hyperskill.app.manage_subscription.presentation + +import org.hyperskill.app.manage_subscription.domain.analytic.ManageSubscriptionClickedManageHyperskillAnalyticEvent +import org.hyperskill.app.manage_subscription.domain.analytic.ManageSubscriptionViewedHyperskillAnalyticEvent +import org.hyperskill.app.manage_subscription.domain.analytic.RenewSubscriptionClickedManageHyperskillAnalyticEvent +import org.hyperskill.app.manage_subscription.presentation.ManageSubscriptionFeature.Action +import org.hyperskill.app.manage_subscription.presentation.ManageSubscriptionFeature.InternalAction +import org.hyperskill.app.manage_subscription.presentation.ManageSubscriptionFeature.InternalMessage +import org.hyperskill.app.manage_subscription.presentation.ManageSubscriptionFeature.Message +import org.hyperskill.app.manage_subscription.presentation.ManageSubscriptionFeature.State +import org.hyperskill.app.paywall.domain.model.PaywallTransitionSource +import org.hyperskill.app.subscriptions.domain.model.isExpired +import ru.nobird.app.presentation.redux.reducer.StateReducer + +private typealias ReducerResult = Pair> + +internal class ManageSubscriptionReducer : StateReducer { + override fun reduce(state: State, message: Message): ReducerResult = + when (message) { + Message.Initialize -> handleInitialize(state) + InternalMessage.FetchSubscriptionError -> handleFetchSubscriptionError() + is InternalMessage.FetchSubscriptionSuccess -> handleFetchSubscriptionSuccess(message) + Message.RetryContentLoading -> fetchSubscription() + Message.ActionButtonClicked -> handleActionButtonClicked(state) + is InternalMessage.SubscriptionChanged -> handleSubscriptionChanged(state, message) + Message.ViewedEventMessage -> handleViewedEventMessage(state) + } + + private fun handleInitialize(state: State): ReducerResult = + if (state is State.Idle) { + fetchSubscription() + } else { + state to emptySet() + } + + private fun fetchSubscription(): ReducerResult = + State.Loading to setOf(InternalAction.FetchSubscription) + + private fun handleFetchSubscriptionError(): ReducerResult = + State.Error to emptySet() + + private fun handleFetchSubscriptionSuccess( + message: InternalMessage.FetchSubscriptionSuccess + ): ReducerResult = + State.Content( + subscription = message.subscription, + manageSubscriptionUrl = message.manageSubscriptionUrl + ) to emptySet() + + private fun handleActionButtonClicked(state: State): ReducerResult = + if (state is State.Content) { + state to when { + state.isSubscriptionManagementEnabled -> { + setOfNotNull( + InternalAction.LogAnalyticEvent(ManageSubscriptionClickedManageHyperskillAnalyticEvent), + state.manageSubscriptionUrl?.let(Action.ViewAction::OpenUrl) + ) + } + state.subscription.isExpired -> { + setOf( + InternalAction.LogAnalyticEvent(RenewSubscriptionClickedManageHyperskillAnalyticEvent), + Action.ViewAction.NavigateTo.Paywall(PaywallTransitionSource.MANAGE_SUBSCRIPTION) + ) + } + else -> emptySet() + } + } else { + state to emptySet() + } + + private fun handleSubscriptionChanged( + state: State, + message: InternalMessage.SubscriptionChanged + ): ReducerResult = + if (state is State.Content) { + State.Content( + subscription = message.subscription, + manageSubscriptionUrl = message.manageSubscriptionUrl + ) to setOf() + } else { + state to emptySet() + } + + private fun handleViewedEventMessage(state: State): ReducerResult = + state to setOf(InternalAction.LogAnalyticEvent(ManageSubscriptionViewedHyperskillAnalyticEvent)) +} \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/manage_subscription/presentation/ManageSubscriptionStateExtensions.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/manage_subscription/presentation/ManageSubscriptionStateExtensions.kt new file mode 100644 index 0000000000..6ae06be721 --- /dev/null +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/manage_subscription/presentation/ManageSubscriptionStateExtensions.kt @@ -0,0 +1,6 @@ +package org.hyperskill.app.manage_subscription.presentation + +import org.hyperskill.app.subscriptions.domain.model.isActive + +internal val ManageSubscriptionFeature.State.Content.isSubscriptionManagementEnabled: Boolean + get() = subscription.isActive && manageSubscriptionUrl != null \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/manage_subscription/view/mapper/ManageSubscriptionViewStateMapper.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/manage_subscription/view/mapper/ManageSubscriptionViewStateMapper.kt new file mode 100644 index 0000000000..36a604a213 --- /dev/null +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/manage_subscription/view/mapper/ManageSubscriptionViewStateMapper.kt @@ -0,0 +1,43 @@ +package org.hyperskill.app.manage_subscription.view.mapper + +import kotlinx.datetime.Instant +import org.hyperskill.app.SharedResources +import org.hyperskill.app.core.view.mapper.ResourceProvider +import org.hyperskill.app.core.view.mapper.date.SharedDateFormatter +import org.hyperskill.app.manage_subscription.presentation.ManageSubscriptionFeature.State +import org.hyperskill.app.manage_subscription.presentation.ManageSubscriptionFeature.ViewState +import org.hyperskill.app.manage_subscription.presentation.isSubscriptionManagementEnabled +import org.hyperskill.app.subscriptions.domain.model.isExpired + +internal class ManageSubscriptionViewStateMapper( + private val resourceProvider: ResourceProvider, + private val dateFormatter: SharedDateFormatter +) { + fun map(state: State): ViewState = + when (state) { + State.Idle -> ViewState.Idle + State.Loading -> ViewState.Loading + State.Error -> ViewState.Error + is State.Content -> mapContent(state) + } + + private fun mapContent(content: State.Content): ViewState.Content = + ViewState.Content( + validUntilFormatted = content.subscription.validTill?.let(::formatValidUntil), + buttonText = when { + content.isSubscriptionManagementEnabled -> + resourceProvider.getString(SharedResources.strings.manage_subscription_manage_btn) + content.subscription.isExpired -> + resourceProvider.getString(SharedResources.strings.manage_subscription_renew_btn) + else -> null + } + ) + + private fun formatValidUntil(validUntil: Instant): String { + val formattedDate: String = dateFormatter.formatSubscriptionValidUntil(validUntil) + return resourceProvider.getString( + SharedResources.strings.manage_subscription_valid_until, + formattedDate + ) + } +} \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/paywall/domain/analytic/PaywallAnalyticParams.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/paywall/domain/analytic/PaywallAnalyticParams.kt new file mode 100644 index 0000000000..f702014415 --- /dev/null +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/paywall/domain/analytic/PaywallAnalyticParams.kt @@ -0,0 +1,5 @@ +package org.hyperskill.app.paywall.domain.analytic + +internal object PaywallAnalyticParams { + const val PARAM_TRANSITION_SOURCE: String = "source" +} \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/paywall/domain/analytic/PaywallClickedBuySubscriptionHyperskillAnalyticEvent.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/paywall/domain/analytic/PaywallClickedBuySubscriptionHyperskillAnalyticEvent.kt new file mode 100644 index 0000000000..4fa3bccaa7 --- /dev/null +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/paywall/domain/analytic/PaywallClickedBuySubscriptionHyperskillAnalyticEvent.kt @@ -0,0 +1,44 @@ +package org.hyperskill.app.paywall.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.HyperskillAnalyticPart +import org.hyperskill.app.analytic.domain.model.hyperskill.HyperskillAnalyticRoute +import org.hyperskill.app.analytic.domain.model.hyperskill.HyperskillAnalyticTarget +import org.hyperskill.app.paywall.domain.model.PaywallTransitionSource + +/** + * Represents a click analytic event of the buy subscription button. + * + * JSON payload: + * ``` + * { + * "route": "/paywall", + * "action": "click", + * "part": "main", + * "target": "buy_subscription", + * "context": + * { + * "source": "login" + * } + * } + * ``` + * + * @see HyperskillAnalyticEvent + */ +class PaywallClickedBuySubscriptionHyperskillAnalyticEvent( + private val paywallTransitionSource: PaywallTransitionSource +) : HyperskillAnalyticEvent( + HyperskillAnalyticRoute.Paywall, + HyperskillAnalyticAction.CLICK, + HyperskillAnalyticPart.MAIN, + HyperskillAnalyticTarget.BUY_SUBSCRIPTION +) { + override val params: Map + get() = super.params + + mapOf( + PARAM_CONTEXT to mapOf( + PaywallAnalyticParams.PARAM_TRANSITION_SOURCE to paywallTransitionSource.analyticName + ) + ) +} \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/paywall/domain/analytic/PaywallClickedContinueWithLimitsHyperskillAnalyticEvent.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/paywall/domain/analytic/PaywallClickedContinueWithLimitsHyperskillAnalyticEvent.kt new file mode 100644 index 0000000000..37485b5ab2 --- /dev/null +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/paywall/domain/analytic/PaywallClickedContinueWithLimitsHyperskillAnalyticEvent.kt @@ -0,0 +1,44 @@ +package org.hyperskill.app.paywall.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.HyperskillAnalyticPart +import org.hyperskill.app.analytic.domain.model.hyperskill.HyperskillAnalyticRoute +import org.hyperskill.app.analytic.domain.model.hyperskill.HyperskillAnalyticTarget +import org.hyperskill.app.paywall.domain.model.PaywallTransitionSource + +/** + * Represents a click analytic event of the continue-with-limits button. + * + * JSON payload: + * ``` + * { + * "route": "/paywall", + * "action": "click", + * "part": "main", + * "target": "continue_with_limits", + * "context": + * { + * "source": "login" + * } + * } + * ``` + * + * @see HyperskillAnalyticEvent + */ +class PaywallClickedContinueWithLimitsHyperskillAnalyticEvent( + private val paywallTransitionSource: PaywallTransitionSource +) : HyperskillAnalyticEvent( + HyperskillAnalyticRoute.Paywall, + HyperskillAnalyticAction.CLICK, + HyperskillAnalyticPart.MAIN, + HyperskillAnalyticTarget.CONTINUE_WITH_LIMITS +) { + override val params: Map + get() = super.params + + mapOf( + PARAM_CONTEXT to mapOf( + PaywallAnalyticParams.PARAM_TRANSITION_SOURCE to paywallTransitionSource.analyticName + ) + ) +} \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/paywall/domain/analytic/PaywallClickedRetryContentLoadingHyperskillAnalyticEvent.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/paywall/domain/analytic/PaywallClickedRetryContentLoadingHyperskillAnalyticEvent.kt new file mode 100644 index 0000000000..4eb7bd7b4c --- /dev/null +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/paywall/domain/analytic/PaywallClickedRetryContentLoadingHyperskillAnalyticEvent.kt @@ -0,0 +1,44 @@ +package org.hyperskill.app.paywall.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.HyperskillAnalyticPart +import org.hyperskill.app.analytic.domain.model.hyperskill.HyperskillAnalyticRoute +import org.hyperskill.app.analytic.domain.model.hyperskill.HyperskillAnalyticTarget +import org.hyperskill.app.paywall.domain.model.PaywallTransitionSource + +/** + * Represents a click analytic event of the error state placeholder retry button. + * + * JSON payload: + * ``` + * { + * "route": "/paywall", + * "action": "click", + * "part": "main", + * "target": "retry", + * "context": + * { + * "source": "login" + * } + * } + * ``` + * + * @see HyperskillAnalyticEvent + */ +class PaywallClickedRetryContentLoadingHyperskillAnalyticEvent( + private val paywallTransitionSource: PaywallTransitionSource +) : HyperskillAnalyticEvent( + HyperskillAnalyticRoute.Paywall, + HyperskillAnalyticAction.CLICK, + HyperskillAnalyticPart.MAIN, + HyperskillAnalyticTarget.RETRY +) { + override val params: Map + get() = super.params + + mapOf( + PARAM_CONTEXT to mapOf( + PaywallAnalyticParams.PARAM_TRANSITION_SOURCE to paywallTransitionSource.analyticName + ) + ) +} \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/paywall/domain/analytic/PaywallClickedTermsOfServiceAndPrivacyPolicyHyperskillAnalyticEvent.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/paywall/domain/analytic/PaywallClickedTermsOfServiceAndPrivacyPolicyHyperskillAnalyticEvent.kt new file mode 100644 index 0000000000..0d711f140c --- /dev/null +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/paywall/domain/analytic/PaywallClickedTermsOfServiceAndPrivacyPolicyHyperskillAnalyticEvent.kt @@ -0,0 +1,44 @@ +package org.hyperskill.app.paywall.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.HyperskillAnalyticPart +import org.hyperskill.app.analytic.domain.model.hyperskill.HyperskillAnalyticRoute +import org.hyperskill.app.analytic.domain.model.hyperskill.HyperskillAnalyticTarget +import org.hyperskill.app.paywall.domain.model.PaywallTransitionSource + +/** + * Represents a click analytic event of the terms of service and privacy policy button. + * + * JSON payload: + * ``` + * { + * "route": "/paywall", + * "action": "click", + * "part": "main", + * "target": "hyperskill_terms_of_service_and_privacy_policy", + * "context": + * { + * "source": "login" + * } + * } + * ``` + * + * @see HyperskillAnalyticEvent + */ +class PaywallClickedTermsOfServiceAndPrivacyPolicyHyperskillAnalyticEvent( + private val paywallTransitionSource: PaywallTransitionSource +) : HyperskillAnalyticEvent( + HyperskillAnalyticRoute.Paywall, + HyperskillAnalyticAction.CLICK, + HyperskillAnalyticPart.MAIN, + HyperskillAnalyticTarget.HYPERSKILL_TERMS_OF_SERVICE_AND_PRIVACY_POLICY +) { + override val params: Map + get() = super.params + + mapOf( + PARAM_CONTEXT to mapOf( + PaywallAnalyticParams.PARAM_TRANSITION_SOURCE to paywallTransitionSource.analyticName + ) + ) +} \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/paywall/domain/analytic/PaywallViewedHyperskillAnalyticEvent.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/paywall/domain/analytic/PaywallViewedHyperskillAnalyticEvent.kt new file mode 100644 index 0000000000..5b8d866a26 --- /dev/null +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/paywall/domain/analytic/PaywallViewedHyperskillAnalyticEvent.kt @@ -0,0 +1,38 @@ +package org.hyperskill.app.paywall.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 +import org.hyperskill.app.paywall.domain.model.PaywallTransitionSource + +/** + * Represents a view analytic event. + * + * JSON payload: + * ``` + * { + * "route": "/paywall", + * "action": "view", + * "context": + * { + * "source": "login" + * } + * } + * ``` + * + * @see HyperskillAnalyticEvent + */ +class PaywallViewedHyperskillAnalyticEvent( + private val paywallTransitionSource: PaywallTransitionSource +) : HyperskillAnalyticEvent( + HyperskillAnalyticRoute.Paywall, + HyperskillAnalyticAction.VIEW +) { + override val params: Map + get() = super.params + + mapOf( + PARAM_CONTEXT to mapOf( + PaywallAnalyticParams.PARAM_TRANSITION_SOURCE to paywallTransitionSource.analyticName + ) + ) +} \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/paywall/domain/model/PaywallTransitionSource.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/paywall/domain/model/PaywallTransitionSource.kt new file mode 100644 index 0000000000..70cc6ec2af --- /dev/null +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/paywall/domain/model/PaywallTransitionSource.kt @@ -0,0 +1,9 @@ +package org.hyperskill.app.paywall.domain.model + +enum class PaywallTransitionSource(val analyticName: String) { + APP_BECOMES_ACTIVE("app_becomes_active"), + LOGIN("login"), + PROFILE_SETTINGS("profile_settings"), + PROBLEMS_LIMIT_MODAL("problems_limit_modal"), + MANAGE_SUBSCRIPTION("manage_subscription") +} \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/paywall/injection/PaywallComponent.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/paywall/injection/PaywallComponent.kt new file mode 100644 index 0000000000..502a167030 --- /dev/null +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/paywall/injection/PaywallComponent.kt @@ -0,0 +1,10 @@ +package org.hyperskill.app.paywall.injection + +import org.hyperskill.app.paywall.presentation.PaywallFeature.Action +import org.hyperskill.app.paywall.presentation.PaywallFeature.Message +import org.hyperskill.app.paywall.presentation.PaywallFeature.ViewState +import ru.nobird.app.presentation.redux.feature.Feature + +interface PaywallComponent { + val paywallFeature: Feature +} \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/paywall/injection/PaywallComponentImpl.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/paywall/injection/PaywallComponentImpl.kt new file mode 100644 index 0000000000..f3ee7c9ab8 --- /dev/null +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/paywall/injection/PaywallComponentImpl.kt @@ -0,0 +1,26 @@ +package org.hyperskill.app.paywall.injection + +import org.hyperskill.app.core.injection.AppGraph +import org.hyperskill.app.paywall.domain.model.PaywallTransitionSource +import org.hyperskill.app.paywall.presentation.PaywallFeature.Action +import org.hyperskill.app.paywall.presentation.PaywallFeature.Message +import org.hyperskill.app.paywall.presentation.PaywallFeature.ViewState +import ru.nobird.app.presentation.redux.feature.Feature + +internal class PaywallComponentImpl( + private val paywallTransitionSource: PaywallTransitionSource, + private val appGraph: AppGraph +) : PaywallComponent { + override val paywallFeature: Feature + get() = PaywallFeatureBuilder.build( + paywallTransitionSource = paywallTransitionSource, + analyticInteractor = appGraph.analyticComponent.analyticInteractor, + purchaseInteractor = appGraph.buildPurchaseComponent().purchaseInteractor, + resourceProvider = appGraph.commonComponent.resourceProvider, + subscriptionsRepository = appGraph.subscriptionDataComponent.subscriptionsRepository, + sentryInteractor = appGraph.sentryComponent.sentryInteractor, + currentSubscriptionStateRepository = appGraph.stateRepositoriesComponent.currentSubscriptionStateRepository, + logger = appGraph.loggerComponent.logger, + buildVariant = appGraph.commonComponent.buildKonfig.buildVariant + ) +} \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/paywall/injection/PaywallFeatureBuilder.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/paywall/injection/PaywallFeatureBuilder.kt new file mode 100644 index 0000000000..a3af6e8444 --- /dev/null +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/paywall/injection/PaywallFeatureBuilder.kt @@ -0,0 +1,67 @@ +package org.hyperskill.app.paywall.injection + +import co.touchlab.kermit.Logger +import org.hyperskill.app.analytic.domain.interactor.AnalyticInteractor +import org.hyperskill.app.core.domain.BuildVariant +import org.hyperskill.app.core.presentation.ActionDispatcherOptions +import org.hyperskill.app.core.presentation.transformState +import org.hyperskill.app.core.view.mapper.ResourceProvider +import org.hyperskill.app.logging.presentation.wrapWithLogger +import org.hyperskill.app.paywall.domain.model.PaywallTransitionSource +import org.hyperskill.app.paywall.presentation.PaywallActionDispatcher +import org.hyperskill.app.paywall.presentation.PaywallFeature +import org.hyperskill.app.paywall.presentation.PaywallFeature.Action +import org.hyperskill.app.paywall.presentation.PaywallFeature.Message +import org.hyperskill.app.paywall.presentation.PaywallFeature.ViewState +import org.hyperskill.app.paywall.presentation.PaywallReducer +import org.hyperskill.app.paywall.view.PaywallViewStateMapper +import org.hyperskill.app.purchases.domain.interactor.PurchaseInteractor +import org.hyperskill.app.sentry.domain.interactor.SentryInteractor +import org.hyperskill.app.subscriptions.domain.repository.CurrentSubscriptionStateRepository +import org.hyperskill.app.subscriptions.domain.repository.SubscriptionsRepository +import ru.nobird.app.presentation.redux.dispatcher.wrapWithActionDispatcher +import ru.nobird.app.presentation.redux.feature.Feature +import ru.nobird.app.presentation.redux.feature.ReduxFeature + +internal object PaywallFeatureBuilder { + private const val LOG_TAG = "PaywallFeature" + + fun build( + paywallTransitionSource: PaywallTransitionSource, + analyticInteractor: AnalyticInteractor, + purchaseInteractor: PurchaseInteractor, + resourceProvider: ResourceProvider, + subscriptionsRepository: SubscriptionsRepository, + currentSubscriptionStateRepository: CurrentSubscriptionStateRepository, + sentryInteractor: SentryInteractor, + logger: Logger, + buildVariant: BuildVariant + ): Feature { + val paywallReducer = PaywallReducer( + paywallTransitionSource = paywallTransitionSource, + resourceProvider = resourceProvider + ) + .wrapWithLogger(buildVariant, logger, LOG_TAG) + + val paywallActionDispatcher = PaywallActionDispatcher( + config = ActionDispatcherOptions(), + analyticInteractor = analyticInteractor, + purchaseInteractor = purchaseInteractor, + subscriptionsRepository = subscriptionsRepository, + sentryInteractor = sentryInteractor, + currentSubscriptionStateRepository = currentSubscriptionStateRepository, + logger = logger.withTag(LOG_TAG) + ) + + val viewStateMapper = PaywallViewStateMapper(resourceProvider) + + return ReduxFeature( + initialState = PaywallFeature.State.Idle, + reducer = paywallReducer + ) + .wrapWithActionDispatcher(paywallActionDispatcher) + .transformState { state -> + viewStateMapper.map(state, paywallTransitionSource) + } + } +} \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/paywall/presentation/PaywallActionDispatcher.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/paywall/presentation/PaywallActionDispatcher.kt new file mode 100644 index 0000000000..689449c905 --- /dev/null +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/paywall/presentation/PaywallActionDispatcher.kt @@ -0,0 +1,109 @@ +package org.hyperskill.app.paywall.presentation + +import co.touchlab.kermit.Logger +import org.hyperskill.app.analytic.domain.interactor.AnalyticInteractor +import org.hyperskill.app.core.presentation.ActionDispatcherOptions +import org.hyperskill.app.paywall.presentation.PaywallFeature.Action +import org.hyperskill.app.paywall.presentation.PaywallFeature.InternalAction +import org.hyperskill.app.paywall.presentation.PaywallFeature.InternalMessage +import org.hyperskill.app.paywall.presentation.PaywallFeature.Message +import org.hyperskill.app.purchases.domain.interactor.PurchaseInteractor +import org.hyperskill.app.purchases.domain.model.PurchaseResult +import org.hyperskill.app.sentry.domain.interactor.SentryInteractor +import org.hyperskill.app.sentry.domain.model.transaction.HyperskillSentryTransactionBuilder +import org.hyperskill.app.sentry.domain.withTransaction +import org.hyperskill.app.subscriptions.domain.repository.CurrentSubscriptionStateRepository +import org.hyperskill.app.subscriptions.domain.repository.SubscriptionsRepository +import ru.nobird.app.presentation.redux.dispatcher.CoroutineActionDispatcher + +internal class PaywallActionDispatcher( + config: ActionDispatcherOptions, + private val analyticInteractor: AnalyticInteractor, + private val purchaseInteractor: PurchaseInteractor, + private val subscriptionsRepository: SubscriptionsRepository, + private val currentSubscriptionStateRepository: CurrentSubscriptionStateRepository, + private val sentryInteractor: SentryInteractor, + private val logger: Logger +) : CoroutineActionDispatcher(config.createConfig()) { + override suspend fun doSuspendableAction(action: Action) { + when (action) { + is InternalAction.FetchMobileOnlyPrice -> + handleFetchMobileOnlyPrice(::onNewMessage) + is InternalAction.StartMobileOnlySubscriptionPurchase -> + handleStartMobileOnlySubscriptionPurchase(action, ::onNewMessage) + is InternalAction.SyncSubscription -> + handleSyncSubscription(::onNewMessage) + is InternalAction.LogWrongSubscriptionTypeAfterSync -> + handleLogWrongSubscriptionTypeAfterSync(action) + is InternalAction.LogAnalyticEvent -> + analyticInteractor.logEvent(action.analyticEvent) + else -> { + // no op + } + } + } + + private suspend fun handleFetchMobileOnlyPrice(onNewMessage: (Message) -> Unit) { + sentryInteractor.withTransaction( + transaction = HyperskillSentryTransactionBuilder.buildPaywallFetchSubscriptionPrice(), + onError = { InternalMessage.FetchMobileOnlyPriceError } + ) { + val price = purchaseInteractor + .getFormattedMobileOnlySubscriptionPrice() + .getOrThrow() + + if (price != null) { + InternalMessage.FetchMobileOnlyPriceSuccess(price) + } else { + logger.e { "Receive null instead of formatted mobile-only subscription price" } + InternalMessage.FetchMobileOnlyPriceError + } + }.let(onNewMessage) + } + + private suspend fun handleStartMobileOnlySubscriptionPurchase( + action: InternalAction.StartMobileOnlySubscriptionPurchase, + onNewMessage: (Message) -> Unit + ) { + sentryInteractor.withTransaction( + transaction = HyperskillSentryTransactionBuilder.buildPaywallFeaturePurchaseSubscription(), + onError = { InternalMessage.MobileOnlySubscriptionPurchaseError } + ) { + val purchaseResult = purchaseInteractor + .purchaseMobileOnlySubscription(action.purchaseParams) + .getOrThrow() + + if (purchaseResult is PurchaseResult.Error) { + logger.e { getPurchaseErrorMessage(purchaseResult) } + } + + InternalMessage.MobileOnlySubscriptionPurchaseSuccess(purchaseResult) + }.let(onNewMessage) + } + + private fun getPurchaseErrorMessage(error: PurchaseResult.Error): String = + "Subscription purchase failed!\n${error.message}\n${error.underlyingErrorMessage}" + + private suspend fun handleSyncSubscription(onNewMessage: (Message) -> Unit) { + sentryInteractor.withTransaction( + transaction = HyperskillSentryTransactionBuilder.buildPaywallFeatureSyncSubscription(), + onError = { InternalMessage.SubscriptionSyncError } + ) { + val subscription = subscriptionsRepository.syncSubscription().getOrThrow() + currentSubscriptionStateRepository.updateState(subscription) + InternalMessage.SubscriptionSyncSuccess(subscription) + }.let(onNewMessage) + } + + private fun handleLogWrongSubscriptionTypeAfterSync( + action: InternalAction.LogWrongSubscriptionTypeAfterSync + ) { + logger.e { + """ + Wrong subscription type after sync: + expected=${action.expectedSubscriptionType} + actual=${action.actualSubscriptionType} + """.trimIndent() + } + } +} \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/paywall/presentation/PaywallFeature.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/paywall/presentation/PaywallFeature.kt new file mode 100644 index 0000000000..828f520692 --- /dev/null +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/paywall/presentation/PaywallFeature.kt @@ -0,0 +1,112 @@ +package org.hyperskill.app.paywall.presentation + +import dev.icerock.moko.resources.StringResource +import org.hyperskill.app.SharedResources +import org.hyperskill.app.analytic.domain.model.AnalyticEvent +import org.hyperskill.app.purchases.domain.model.PlatformPurchaseParams +import org.hyperskill.app.purchases.domain.model.PurchaseResult +import org.hyperskill.app.subscriptions.domain.model.Subscription +import org.hyperskill.app.subscriptions.domain.model.SubscriptionType + +object PaywallFeature { + internal sealed interface State { + object Idle : State + object Loading : State + object Error : State + data class Content( + val formattedPrice: String, + val isPurchaseSyncLoadingShowed: Boolean = false + ) : State + } + + data class ViewState( + val isToolbarVisible: Boolean, + val contentState: ViewStateContent + ) + + sealed interface ViewStateContent { + object Idle : ViewStateContent + object Loading : ViewStateContent + object Error : ViewStateContent + data class Content( + val buyButtonText: String, + val isContinueWithLimitsButtonVisible: Boolean + ) : ViewStateContent + + object SubscriptionSyncLoading : ViewStateContent + } + + sealed interface Message { + object Initialize : Message + + object RetryContentLoading : Message + + object ContinueWithLimitsClicked : Message + + data class BuySubscriptionClicked( + val purchaseParams: PlatformPurchaseParams + ) : Message + + object ClickedTermsOfServiceAndPrivacyPolicy : Message + + object ViewedEventMessage : Message + } + + internal sealed interface InternalMessage : Message { + object FetchMobileOnlyPriceError : InternalMessage + data class FetchMobileOnlyPriceSuccess(val formattedPrice: String) : InternalMessage + + object MobileOnlySubscriptionPurchaseError : InternalMessage + data class MobileOnlySubscriptionPurchaseSuccess( + val purchaseResult: PurchaseResult + ) : InternalMessage + + object SubscriptionSyncError : InternalMessage + data class SubscriptionSyncSuccess(val subscription: Subscription) : InternalMessage + } + + sealed interface Action { + sealed interface ViewAction : Action { + object CompletePaywall : ViewAction + + object ClosePaywall : ViewAction + + object StudyPlan : ViewAction + + data class ShowMessage( + val messageKind: MessageKind + ) : ViewAction + + data class OpenUrl(val url: String) : ViewAction + + sealed interface NavigateTo : ViewAction { + object BackToProfileSettings : NavigateTo + } + } + } + + enum class MessageKind( + val stringRes: StringResource + ) { + GENERAL(SharedResources.strings.paywall_purchase_error_message), + PENDING_PURCHASE(SharedResources.strings.paywall_pending_purchase), + SUBSCRIPTION_WILL_BECOME_AVAILABLE_SOON(SharedResources.strings.paywall_subscription_sync_delayed) + } + + internal sealed interface InternalAction : Action { + object FetchMobileOnlyPrice : InternalAction + + data class StartMobileOnlySubscriptionPurchase( + val purchaseParams: PlatformPurchaseParams + ) : InternalAction + + object SyncSubscription : InternalAction + + data class LogWrongSubscriptionTypeAfterSync( + val expectedSubscriptionType: SubscriptionType, + val actualSubscriptionType: SubscriptionType + ) : InternalAction + + data class LogAnalyticEvent(val analyticEvent: AnalyticEvent) : InternalAction + } +} \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/paywall/presentation/PaywallReducer.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/paywall/presentation/PaywallReducer.kt new file mode 100644 index 0000000000..998c00bc25 --- /dev/null +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/paywall/presentation/PaywallReducer.kt @@ -0,0 +1,201 @@ +package org.hyperskill.app.paywall.presentation + +import org.hyperskill.app.SharedResources +import org.hyperskill.app.core.view.mapper.ResourceProvider +import org.hyperskill.app.paywall.domain.analytic.PaywallClickedBuySubscriptionHyperskillAnalyticEvent +import org.hyperskill.app.paywall.domain.analytic.PaywallClickedContinueWithLimitsHyperskillAnalyticEvent +import org.hyperskill.app.paywall.domain.analytic.PaywallClickedRetryContentLoadingHyperskillAnalyticEvent +import org.hyperskill.app.paywall.domain.analytic.PaywallClickedTermsOfServiceAndPrivacyPolicyHyperskillAnalyticEvent +import org.hyperskill.app.paywall.domain.analytic.PaywallViewedHyperskillAnalyticEvent +import org.hyperskill.app.paywall.domain.model.PaywallTransitionSource +import org.hyperskill.app.paywall.presentation.PaywallFeature.Action +import org.hyperskill.app.paywall.presentation.PaywallFeature.InternalAction +import org.hyperskill.app.paywall.presentation.PaywallFeature.InternalMessage +import org.hyperskill.app.paywall.presentation.PaywallFeature.Message +import org.hyperskill.app.paywall.presentation.PaywallFeature.State +import org.hyperskill.app.purchases.domain.model.PurchaseResult +import org.hyperskill.app.subscriptions.domain.model.SubscriptionType +import ru.nobird.app.presentation.redux.reducer.StateReducer + +private typealias ReducerResult = Pair> + +internal class PaywallReducer( + private val paywallTransitionSource: PaywallTransitionSource, + private val resourceProvider: ResourceProvider +) : StateReducer { + override fun reduce(state: State, message: Message): ReducerResult = + when (message) { + Message.Initialize -> fetchMobileOnlyPrice() + Message.RetryContentLoading -> + fetchMobileOnlyPrice( + setOf( + InternalAction.LogAnalyticEvent( + PaywallClickedRetryContentLoadingHyperskillAnalyticEvent(paywallTransitionSource) + ) + ) + ) + is InternalMessage.FetchMobileOnlyPriceSuccess -> + handleFetchMobileOnlyPriceSuccess(message) + InternalMessage.FetchMobileOnlyPriceError -> + handleFetchMobileOnlyPriceError() + Message.ContinueWithLimitsClicked -> + handleContinueWithLimitsClicked(state) + is Message.BuySubscriptionClicked -> + handleBuySubscriptionClicked(state, message) + is InternalMessage.MobileOnlySubscriptionPurchaseSuccess -> + handleMobileOnlySubscriptionPurchaseSuccess(state, message) + InternalMessage.MobileOnlySubscriptionPurchaseError -> + handleMobileOnlySubscriptionPurchaseError(state) + is InternalMessage.SubscriptionSyncSuccess -> + handleSubscriptionSyncSuccess(state, message) + InternalMessage.SubscriptionSyncError -> + handleSubscriptionSyncError(state) + Message.ClickedTermsOfServiceAndPrivacyPolicy -> + handleClickedTermsOfServiceAndPrivacyPolicy(state) + Message.ViewedEventMessage -> + handleViewedEventMessage(state) + } + + private fun fetchMobileOnlyPrice(actions: Set = emptySet()): ReducerResult = + State.Loading to setOf(InternalAction.FetchMobileOnlyPrice) + actions + + private fun handleFetchMobileOnlyPriceSuccess( + message: InternalMessage.FetchMobileOnlyPriceSuccess + ): ReducerResult = + State.Content(message.formattedPrice) to emptySet() + + private fun handleFetchMobileOnlyPriceError(): ReducerResult = + State.Error to setOf() + + private fun handleContinueWithLimitsClicked( + state: State + ): ReducerResult = + state to setOf( + InternalAction.LogAnalyticEvent( + PaywallClickedContinueWithLimitsHyperskillAnalyticEvent( + paywallTransitionSource + ) + ), + getTargetScreenNavigationAction(paywallTransitionSource) + ) + + private fun handleBuySubscriptionClicked( + state: State, + message: Message.BuySubscriptionClicked + ): ReducerResult = + state to setOf( + InternalAction.LogAnalyticEvent( + PaywallClickedBuySubscriptionHyperskillAnalyticEvent( + paywallTransitionSource + ) + ), + InternalAction.StartMobileOnlySubscriptionPurchase(message.purchaseParams) + ) + + private fun handleMobileOnlySubscriptionPurchaseSuccess( + state: State, + message: InternalMessage.MobileOnlySubscriptionPurchaseSuccess + ): ReducerResult = + if (state is State.Content) { + when (message.purchaseResult) { + is PurchaseResult.Succeed, + is PurchaseResult.Error.ProductAlreadyPurchasedError -> { + state.copy(isPurchaseSyncLoadingShowed = true) to + setOf(InternalAction.SyncSubscription) + } + PurchaseResult.CancelledByUser -> state to emptySet() + is PurchaseResult.Error.PaymentPendingError -> { + state to setOf( + Action.ViewAction.ShowMessage( + PaywallFeature.MessageKind.PENDING_PURCHASE + ), + getTargetScreenNavigationAction(paywallTransitionSource) + ) + } + + is PurchaseResult.Error.ErrorWhileFetchingProduct, + is PurchaseResult.Error.NoProductFound, + is PurchaseResult.Error.PurchaseNotAllowedError, + is PurchaseResult.Error.ReceiptAlreadyInUseError, + is PurchaseResult.Error.StoreProblemError, + is PurchaseResult.Error.OtherError -> handleMobileOnlySubscriptionPurchaseError(state) + } + } else { + state to emptySet() + } + + private fun handleMobileOnlySubscriptionPurchaseError(state: State): ReducerResult = + state to setOf(Action.ViewAction.ShowMessage(PaywallFeature.MessageKind.GENERAL)) + + private fun handleSubscriptionSyncSuccess( + state: State, + message: InternalMessage.SubscriptionSyncSuccess + ): ReducerResult = + if (state is State.Content) { + state.copy(isPurchaseSyncLoadingShowed = false) to + if (message.subscription.type == SubscriptionType.MOBILE_ONLY) { + setOf(getTargetScreenNavigationAction(paywallTransitionSource)) + } else { + setOf( + Action.ViewAction.ShowMessage( + PaywallFeature.MessageKind.SUBSCRIPTION_WILL_BECOME_AVAILABLE_SOON + ), + InternalAction.LogWrongSubscriptionTypeAfterSync( + expectedSubscriptionType = SubscriptionType.MOBILE_ONLY, + actualSubscriptionType = message.subscription.type + ), + getTargetScreenNavigationAction(paywallTransitionSource) + ) + } + } else { + state to emptySet() + } + + private fun handleSubscriptionSyncError(state: State): ReducerResult = + if (state is State.Content) { + state.copy(isPurchaseSyncLoadingShowed = false) to setOf( + Action.ViewAction.ShowMessage( + PaywallFeature.MessageKind.SUBSCRIPTION_WILL_BECOME_AVAILABLE_SOON + ), + getTargetScreenNavigationAction(paywallTransitionSource) + ) + } else { + state to emptySet() + } + + private fun getTargetScreenNavigationAction( + paywallTransitionSource: PaywallTransitionSource + ): Action.ViewAction = + when (paywallTransitionSource) { + PaywallTransitionSource.APP_BECOMES_ACTIVE, + PaywallTransitionSource.MANAGE_SUBSCRIPTION -> + Action.ViewAction.ClosePaywall + PaywallTransitionSource.LOGIN -> + Action.ViewAction.CompletePaywall + PaywallTransitionSource.PROFILE_SETTINGS -> + Action.ViewAction.NavigateTo.BackToProfileSettings + PaywallTransitionSource.PROBLEMS_LIMIT_MODAL -> + Action.ViewAction.StudyPlan + } + + private fun handleClickedTermsOfServiceAndPrivacyPolicy(state: State): ReducerResult = + if (state is State.Content) { + state to setOf( + InternalAction.LogAnalyticEvent( + PaywallClickedTermsOfServiceAndPrivacyPolicyHyperskillAnalyticEvent(paywallTransitionSource) + ), + Action.ViewAction.OpenUrl( + resourceProvider.getString(SharedResources.strings.paywall_tos_and_privacy_url) + ) + ) + } else { + state to emptySet() + } + + private fun handleViewedEventMessage(state: State): ReducerResult = + state to setOf( + InternalAction.LogAnalyticEvent( + PaywallViewedHyperskillAnalyticEvent(paywallTransitionSource) + ) + ) +} \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/paywall/view/PaywallViewStateMapper.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/paywall/view/PaywallViewStateMapper.kt new file mode 100644 index 0000000000..d603b3d9fb --- /dev/null +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/paywall/view/PaywallViewStateMapper.kt @@ -0,0 +1,52 @@ +package org.hyperskill.app.paywall.view + +import org.hyperskill.app.SharedResources +import org.hyperskill.app.core.view.mapper.ResourceProvider +import org.hyperskill.app.paywall.domain.model.PaywallTransitionSource +import org.hyperskill.app.paywall.domain.model.PaywallTransitionSource.APP_BECOMES_ACTIVE +import org.hyperskill.app.paywall.domain.model.PaywallTransitionSource.LOGIN +import org.hyperskill.app.paywall.domain.model.PaywallTransitionSource.MANAGE_SUBSCRIPTION +import org.hyperskill.app.paywall.domain.model.PaywallTransitionSource.PROBLEMS_LIMIT_MODAL +import org.hyperskill.app.paywall.domain.model.PaywallTransitionSource.PROFILE_SETTINGS +import org.hyperskill.app.paywall.presentation.PaywallFeature.State +import org.hyperskill.app.paywall.presentation.PaywallFeature.ViewState +import org.hyperskill.app.paywall.presentation.PaywallFeature.ViewStateContent + +internal class PaywallViewStateMapper( + private val resourceProvider: ResourceProvider +) { + fun map( + state: State, + paywallTransitionSource: PaywallTransitionSource + ): ViewState = + ViewState( + isToolbarVisible = when (paywallTransitionSource) { + APP_BECOMES_ACTIVE, LOGIN -> false + MANAGE_SUBSCRIPTION, + PROFILE_SETTINGS, + PROBLEMS_LIMIT_MODAL -> true + }, + contentState = when (state) { + State.Idle -> ViewStateContent.Idle + State.Loading -> ViewStateContent.Loading + State.Error -> ViewStateContent.Error + is State.Content -> + if (state.isPurchaseSyncLoadingShowed) { + ViewStateContent.SubscriptionSyncLoading + } else { + ViewStateContent.Content( + buyButtonText = resourceProvider.getString( + SharedResources.strings.paywall_mobile_only_buy_btn, + state.formattedPrice + ), + isContinueWithLimitsButtonVisible = when (paywallTransitionSource) { + PROFILE_SETTINGS, MANAGE_SUBSCRIPTION -> false + APP_BECOMES_ACTIVE, + LOGIN, + PROBLEMS_LIMIT_MODAL -> true + } + ) + } + } + ) +} \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/problems_limit/injection/ProblemsLimitComponentImpl.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/problems_limit/injection/ProblemsLimitComponentImpl.kt index c0d77c428f..4b937bdd15 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/problems_limit/injection/ProblemsLimitComponentImpl.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/problems_limit/injection/ProblemsLimitComponentImpl.kt @@ -17,7 +17,6 @@ internal class ProblemsLimitComponentImpl( override val problemsLimitActionDispatcher: ProblemsLimitActionDispatcher get() = ProblemsLimitActionDispatcher( config = ActionDispatcherOptions(), - freemiumInteractor = appGraph.buildFreemiumDataComponent().freemiumInteractor, sentryInteractor = appGraph.sentryComponent.sentryInteractor, analyticInteractor = appGraph.analyticComponent.analyticInteractor, currentSubscriptionStateRepository = appGraph.stateRepositoriesComponent.currentSubscriptionStateRepository diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/problems_limit/presentation/ProblemsLimitActionDispatcher.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/problems_limit/presentation/ProblemsLimitActionDispatcher.kt index ed6c5362ed..d262ab1eb1 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/problems_limit/presentation/ProblemsLimitActionDispatcher.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/problems_limit/presentation/ProblemsLimitActionDispatcher.kt @@ -7,18 +7,17 @@ import kotlinx.coroutines.sync.withLock import org.hyperskill.app.analytic.domain.interactor.AnalyticInteractor import org.hyperskill.app.core.presentation.ActionDispatcherOptions import org.hyperskill.app.core.presentation.Timer -import org.hyperskill.app.freemium.domain.interactor.FreemiumInteractor import org.hyperskill.app.problems_limit.presentation.ProblemsLimitFeature.Action import org.hyperskill.app.problems_limit.presentation.ProblemsLimitFeature.InternalAction import org.hyperskill.app.problems_limit.presentation.ProblemsLimitFeature.InternalMessage import org.hyperskill.app.problems_limit.presentation.ProblemsLimitFeature.Message import org.hyperskill.app.sentry.domain.interactor.SentryInteractor +import org.hyperskill.app.sentry.domain.withTransaction import org.hyperskill.app.subscriptions.domain.repository.CurrentSubscriptionStateRepository import ru.nobird.app.presentation.redux.dispatcher.CoroutineActionDispatcher class ProblemsLimitActionDispatcher( config: ActionDispatcherOptions, - private val freemiumInteractor: FreemiumInteractor, private val sentryInteractor: SentryInteractor, private val analyticInteractor: AnalyticInteractor, private val currentSubscriptionStateRepository: CurrentSubscriptionStateRepository @@ -37,32 +36,16 @@ class ProblemsLimitActionDispatcher( override suspend fun doSuspendableAction(action: Action) { when (action) { is InternalAction.LoadSubscription -> { - val sentryTransaction = action.screen.sentryTransaction - sentryInteractor.startTransaction(sentryTransaction) - - val isFreemiumEnabled = freemiumInteractor - .isFreemiumEnabled() - .getOrElse { - sentryInteractor.finishTransaction(sentryTransaction, throwable = it) - return onNewMessage(InternalMessage.LoadSubscriptionResultError) - } - - onNewMessage( - currentSubscriptionStateRepository.getState(forceUpdate = action.forceUpdate) - .fold( - onSuccess = { - sentryInteractor.finishTransaction(sentryTransaction) - InternalMessage.LoadSubscriptionResultSuccess( - subscription = it, - isFreemiumEnabled = isFreemiumEnabled - ) - }, - onFailure = { - sentryInteractor.finishTransaction(sentryTransaction, throwable = it) - InternalMessage.LoadSubscriptionResultError - } - ) - ) + sentryInteractor.withTransaction( + transaction = action.screen.sentryTransaction, + onError = { InternalMessage.LoadSubscriptionResultError } + ) { + InternalMessage.LoadSubscriptionResultSuccess( + subscription = currentSubscriptionStateRepository + .getState(forceUpdate = action.forceUpdate) + .getOrThrow() + ) + }.let(::onNewMessage) } is InternalAction.LaunchTimer -> { timerMutex.withLock { diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/problems_limit/presentation/ProblemsLimitFeature.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/problems_limit/presentation/ProblemsLimitFeature.kt index 2a2341c8d5..6c78669bcd 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/problems_limit/presentation/ProblemsLimitFeature.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/problems_limit/presentation/ProblemsLimitFeature.kt @@ -13,7 +13,6 @@ object ProblemsLimitFeature { data class Content( val subscription: Subscription, - val isFreemiumEnabled: Boolean, val updateIn: Duration?, internal val isRefreshing: Boolean = false ) : State @@ -57,8 +56,7 @@ object ProblemsLimitFeature { object LoadSubscriptionResultError : InternalMessage data class LoadSubscriptionResultSuccess( - val subscription: Subscription, - val isFreemiumEnabled: Boolean + val subscription: Subscription ) : InternalMessage data class UpdateInChanged(val newUpdateIn: Duration) : InternalMessage diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/problems_limit/presentation/ProblemsLimitReducer.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/problems_limit/presentation/ProblemsLimitReducer.kt index 077b75c12e..b3a77feb25 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/problems_limit/presentation/ProblemsLimitReducer.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/problems_limit/presentation/ProblemsLimitReducer.kt @@ -41,7 +41,6 @@ class ProblemsLimitReducer(private val screen: ProblemsLimitScreen) : StateReduc State.Content( subscription = message.subscription, - isFreemiumEnabled = message.isFreemiumEnabled, updateIn = updateIn ) to buildSet { if (updateIn != null) { @@ -77,7 +76,7 @@ class ProblemsLimitReducer(private val screen: ProblemsLimitScreen) : StateReduc ) is State.NetworkError -> State.Loading to setOf( - InternalAction.LoadSubscription(screen = screen, forceUpdate = false) + InternalAction.LoadSubscription(screen = screen, forceUpdate = true) ) else -> null diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/problems_limit/view/mapper/ProblemsLimitViewStateMapper.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/problems_limit/view/mapper/ProblemsLimitViewStateMapper.kt index 771dcc1312..9009729481 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/problems_limit/view/mapper/ProblemsLimitViewStateMapper.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/problems_limit/view/mapper/ProblemsLimitViewStateMapper.kt @@ -4,6 +4,7 @@ import org.hyperskill.app.SharedResources import org.hyperskill.app.core.view.mapper.ResourceProvider import org.hyperskill.app.core.view.mapper.date.SharedDateFormatter import org.hyperskill.app.problems_limit.presentation.ProblemsLimitFeature +import org.hyperskill.app.subscriptions.domain.model.areProblemsLimited class ProblemsLimitViewStateMapper( private val resourceProvider: ResourceProvider, @@ -18,7 +19,7 @@ class ProblemsLimitViewStateMapper( val stepsLimitLeft = state.subscription.stepsLimitLeft val stepsLimitTotal = state.subscription.stepsLimitTotal when { - !state.isFreemiumEnabled || + !state.subscription.areProblemsLimited || stepsLimitLeft == null || stepsLimitTotal == null -> ProblemsLimitFeature.ViewState.Content.Empty else -> ProblemsLimitFeature.ViewState.Content.Widget( diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/profile/domain/model/FeatureKeys.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/profile/domain/model/FeatureKeys.kt index 9f170573e6..7a49802723 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/profile/domain/model/FeatureKeys.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/profile/domain/model/FeatureKeys.kt @@ -8,6 +8,7 @@ internal object FeatureKeys { const val LEARNING_PATH_DIVIDED_TRACK_TOPICS = "learning_path.divided_track_topics" const val MOBILE_LEADERBOARDS = "mobile_leaderboards" const val MOBILE_INTERVIEW_PREPARATION = "mobile.interview_preparation" + const val MOBILE_ONLY_SUBSCRIPTION = "mobile.mobile_only_subscription" const val MOBILE_USERS_QUESTIONNAIRE = "mobile.users_questionnaire" const val MOBILE_SHORT_THEORY = "mobile.short_theory" } \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/profile/domain/model/FeaturesMap.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/profile/domain/model/FeaturesMap.kt index 67ce5136fd..da75f47aff 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/profile/domain/model/FeaturesMap.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/profile/domain/model/FeaturesMap.kt @@ -23,6 +23,9 @@ val FeaturesMap.isMobileLeaderboardsEnabled: Boolean val FeaturesMap.isMobileInterviewPreparationEnabled: Boolean get() = get(FeatureKeys.MOBILE_INTERVIEW_PREPARATION) ?: false +val FeaturesMap.isMobileOnlySubscriptionEnabled: Boolean + get() = get(FeatureKeys.MOBILE_ONLY_SUBSCRIPTION) ?: false + val FeaturesMap.isMobileUsersQuestionnaireEnabled: Boolean get() = get(FeatureKeys.MOBILE_USERS_QUESTIONNAIRE) ?: false diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/profile_settings/domain/analytic/ProfileSettingsClickedHyperskillAnalyticEvent.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/profile_settings/domain/analytic/ProfileSettingsClickedHyperskillAnalyticEvent.kt index 4089b8234c..25793cba7e 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/profile_settings/domain/analytic/ProfileSettingsClickedHyperskillAnalyticEvent.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/profile_settings/domain/analytic/ProfileSettingsClickedHyperskillAnalyticEvent.kt @@ -92,8 +92,8 @@ import org.hyperskill.app.analytic.domain.model.hyperskill.HyperskillAnalyticTar * @see HyperskillAnalyticEvent */ class ProfileSettingsClickedHyperskillAnalyticEvent( - part: HyperskillAnalyticPart = HyperskillAnalyticPart.MAIN, - target: HyperskillAnalyticTarget + target: HyperskillAnalyticTarget, + part: HyperskillAnalyticPart = HyperskillAnalyticPart.MAIN ) : HyperskillAnalyticEvent( HyperskillAnalyticRoute.Profile.Settings(), HyperskillAnalyticAction.CLICK, diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/profile_settings/injection/ProfileSettingsComponent.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/profile_settings/injection/ProfileSettingsComponent.kt index ef8dff6022..43eeddbc11 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/profile_settings/injection/ProfileSettingsComponent.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/profile_settings/injection/ProfileSettingsComponent.kt @@ -1,11 +1,12 @@ package org.hyperskill.app.profile_settings.injection import org.hyperskill.app.profile_settings.domain.interactor.ProfileSettingsInteractor -import org.hyperskill.app.profile_settings.presentation.ProfileSettingsFeature +import org.hyperskill.app.profile_settings.presentation.ProfileSettingsFeature.Action +import org.hyperskill.app.profile_settings.presentation.ProfileSettingsFeature.Message +import org.hyperskill.app.profile_settings.presentation.ProfileSettingsFeature.ViewState import ru.nobird.app.presentation.redux.feature.Feature interface ProfileSettingsComponent { - val profileSettingsFeature: Feature< - ProfileSettingsFeature.State, ProfileSettingsFeature.Message, ProfileSettingsFeature.Action> + val profileSettingsFeature: Feature val profileSettingsInteractor: ProfileSettingsInteractor } \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/profile_settings/injection/ProfileSettingsComponentImpl.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/profile_settings/injection/ProfileSettingsComponentImpl.kt index 818b84a18a..303fbf04ee 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/profile_settings/injection/ProfileSettingsComponentImpl.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/profile_settings/injection/ProfileSettingsComponentImpl.kt @@ -1,41 +1,45 @@ package org.hyperskill.app.profile_settings.injection import org.hyperskill.app.core.injection.AppGraph -import org.hyperskill.app.magic_links.domain.interactor.UrlPathProcessor import org.hyperskill.app.profile_settings.cache.ProfileSettingsCacheDataSourceImpl import org.hyperskill.app.profile_settings.data.repository.ProfileSettingsRepositoryImpl import org.hyperskill.app.profile_settings.data.source.ProfileSettingsCacheDataSource import org.hyperskill.app.profile_settings.domain.interactor.ProfileSettingsInteractor import org.hyperskill.app.profile_settings.domain.repository.ProfileSettingsRepository -import org.hyperskill.app.profile_settings.presentation.ProfileSettingsFeature +import org.hyperskill.app.profile_settings.presentation.ProfileSettingsFeature.Action +import org.hyperskill.app.profile_settings.presentation.ProfileSettingsFeature.Message +import org.hyperskill.app.profile_settings.presentation.ProfileSettingsFeature.ViewState import ru.nobird.app.presentation.redux.feature.Feature -class ProfileSettingsComponentImpl(private val appGraph: AppGraph) : ProfileSettingsComponent { +internal class ProfileSettingsComponentImpl( + private val appGraph: AppGraph +) : ProfileSettingsComponent { private val profileSettingsCacheDataSource: ProfileSettingsCacheDataSource = ProfileSettingsCacheDataSourceImpl( appGraph.commonComponent.json, appGraph.commonComponent.settings ) + private val profileSettingsRepository: ProfileSettingsRepository = ProfileSettingsRepositoryImpl(profileSettingsCacheDataSource) + override val profileSettingsInteractor: ProfileSettingsInteractor = ProfileSettingsInteractor(profileSettingsRepository) - private val urlPathProcessor: UrlPathProcessor = - appGraph.buildMagicLinksDataComponent().urlPathProcessor - - override val profileSettingsFeature: Feature< - ProfileSettingsFeature.State, ProfileSettingsFeature.Message, ProfileSettingsFeature.Action> + override val profileSettingsFeature: Feature get() = ProfileSettingsFeatureBuilder.build( - profileSettingsInteractor, - appGraph.profileDataComponent.currentProfileStateRepository, - appGraph.analyticComponent.analyticInteractor, - appGraph.networkComponent.authorizationFlow, - appGraph.commonComponent.platform, - appGraph.commonComponent.userAgentInfo, - appGraph.commonComponent.resourceProvider, - urlPathProcessor, - appGraph.loggerComponent.logger, - appGraph.commonComponent.buildKonfig.buildVariant + profileSettingsInteractor = profileSettingsInteractor, + currentProfileStateRepository = appGraph.profileDataComponent.currentProfileStateRepository, + analyticInteractor = appGraph.analyticComponent.analyticInteractor, + authorizationFlow = appGraph.networkComponent.authorizationFlow, + platform = appGraph.commonComponent.platform, + userAgentInfo = appGraph.commonComponent.userAgentInfo, + resourceProvider = appGraph.commonComponent.resourceProvider, + urlPathProcessor = appGraph.buildMagicLinksDataComponent().urlPathProcessor, + currentSubscriptionStateRepository = appGraph.stateRepositoriesComponent.currentSubscriptionStateRepository, + purchaseInteractor = appGraph.buildPurchaseComponent().purchaseInteractor, + sentryInteractor = appGraph.sentryComponent.sentryInteractor, + logger = appGraph.loggerComponent.logger, + buildVariant = appGraph.commonComponent.buildKonfig.buildVariant ) } \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/profile_settings/injection/ProfileSettingsFeatureBuilder.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/profile_settings/injection/ProfileSettingsFeatureBuilder.kt index be65fb3117..19654a8ba3 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/profile_settings/injection/ProfileSettingsFeatureBuilder.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/profile_settings/injection/ProfileSettingsFeatureBuilder.kt @@ -7,6 +7,7 @@ import org.hyperskill.app.auth.domain.model.UserDeauthorized import org.hyperskill.app.core.domain.BuildVariant import org.hyperskill.app.core.domain.platform.Platform import org.hyperskill.app.core.presentation.ActionDispatcherOptions +import org.hyperskill.app.core.presentation.transformState import org.hyperskill.app.core.remote.UserAgentInfo import org.hyperskill.app.core.view.mapper.ResourceProvider import org.hyperskill.app.logging.presentation.wrapWithLogger @@ -17,12 +18,17 @@ import org.hyperskill.app.profile_settings.presentation.ProfileSettingsActionDis import org.hyperskill.app.profile_settings.presentation.ProfileSettingsFeature.Action import org.hyperskill.app.profile_settings.presentation.ProfileSettingsFeature.Message import org.hyperskill.app.profile_settings.presentation.ProfileSettingsFeature.State +import org.hyperskill.app.profile_settings.presentation.ProfileSettingsFeature.ViewState import org.hyperskill.app.profile_settings.presentation.ProfileSettingsReducer +import org.hyperskill.app.profile_settings.view.ProfileSettingsViewStateMapper +import org.hyperskill.app.purchases.domain.interactor.PurchaseInteractor +import org.hyperskill.app.sentry.domain.interactor.SentryInteractor +import org.hyperskill.app.subscriptions.domain.repository.CurrentSubscriptionStateRepository import ru.nobird.app.presentation.redux.dispatcher.wrapWithActionDispatcher import ru.nobird.app.presentation.redux.feature.Feature import ru.nobird.app.presentation.redux.feature.ReduxFeature -object ProfileSettingsFeatureBuilder { +internal object ProfileSettingsFeatureBuilder { private const val LOG_TAG = "ProfileSettingsFeature" fun build( @@ -34,23 +40,33 @@ object ProfileSettingsFeatureBuilder { userAgentInfo: UserAgentInfo, resourceProvider: ResourceProvider, urlPathProcessor: UrlPathProcessor, + currentSubscriptionStateRepository: CurrentSubscriptionStateRepository, + purchaseInteractor: PurchaseInteractor, + sentryInteractor: SentryInteractor, logger: Logger, buildVariant: BuildVariant - ): Feature { + ): Feature { val profileSettingsReducer = ProfileSettingsReducer().wrapWithLogger(buildVariant, logger, LOG_TAG) val profileSettingsActionDispatcher = ProfileSettingsActionDispatcher( - ActionDispatcherOptions(), - profileSettingsInteractor, - currentProfileStateRepository, - analyticInteractor, - authorizationFlow, - platform, - userAgentInfo, - resourceProvider, - urlPathProcessor + config = ActionDispatcherOptions(), + profileSettingsInteractor = profileSettingsInteractor, + currentProfileStateRepository = currentProfileStateRepository, + analyticInteractor = analyticInteractor, + authorizationFlow = authorizationFlow, + platform = platform, + userAgentInfo = userAgentInfo, + resourceProvider = resourceProvider, + urlPathProcessor = urlPathProcessor, + currentSubscriptionStateRepository = currentSubscriptionStateRepository, + purchaseInteractor = purchaseInteractor, + sentryInteractor = sentryInteractor, + logger = logger.withTag(LOG_TAG) ) + val viewStateMapper = ProfileSettingsViewStateMapper(resourceProvider) + return ReduxFeature(State.Idle, profileSettingsReducer) .wrapWithActionDispatcher(profileSettingsActionDispatcher) + .transformState(viewStateMapper::map) } } \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/profile_settings/presentation/ProfileSettingsActionDispatcher.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/profile_settings/presentation/ProfileSettingsActionDispatcher.kt index 395a7afdd6..12a6018bc5 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/profile_settings/presentation/ProfileSettingsActionDispatcher.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/profile_settings/presentation/ProfileSettingsActionDispatcher.kt @@ -1,6 +1,11 @@ package org.hyperskill.app.profile_settings.presentation +import co.touchlab.kermit.Logger +import kotlinx.coroutines.async +import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach import org.hyperskill.app.SharedResources.strings import org.hyperskill.app.analytic.domain.interactor.AnalyticInteractor import org.hyperskill.app.auth.domain.model.UserDeauthorized @@ -10,14 +15,20 @@ import org.hyperskill.app.core.presentation.ActionDispatcherOptions import org.hyperskill.app.core.remote.UserAgentInfo import org.hyperskill.app.core.view.mapper.ResourceProvider import org.hyperskill.app.magic_links.domain.interactor.UrlPathProcessor +import org.hyperskill.app.profile.domain.model.isMobileOnlySubscriptionEnabled import org.hyperskill.app.profile.domain.repository.CurrentProfileStateRepository import org.hyperskill.app.profile_settings.domain.interactor.ProfileSettingsInteractor import org.hyperskill.app.profile_settings.domain.model.FeedbackEmailDataBuilder import org.hyperskill.app.profile_settings.presentation.ProfileSettingsFeature.Action import org.hyperskill.app.profile_settings.presentation.ProfileSettingsFeature.Message +import org.hyperskill.app.purchases.domain.interactor.PurchaseInteractor +import org.hyperskill.app.sentry.domain.interactor.SentryInteractor +import org.hyperskill.app.sentry.domain.model.transaction.HyperskillSentryTransactionBuilder +import org.hyperskill.app.sentry.domain.withTransaction +import org.hyperskill.app.subscriptions.domain.repository.CurrentSubscriptionStateRepository import ru.nobird.app.presentation.redux.dispatcher.CoroutineActionDispatcher -class ProfileSettingsActionDispatcher( +internal class ProfileSettingsActionDispatcher( config: ActionDispatcherOptions, private val profileSettingsInteractor: ProfileSettingsInteractor, private val currentProfileStateRepository: CurrentProfileStateRepository, @@ -26,14 +37,26 @@ class ProfileSettingsActionDispatcher( private val platform: Platform, private val userAgentInfo: UserAgentInfo, private val resourceProvider: ResourceProvider, - private val urlPathProcessor: UrlPathProcessor + private val urlPathProcessor: UrlPathProcessor, + private val currentSubscriptionStateRepository: CurrentSubscriptionStateRepository, + private val purchaseInteractor: PurchaseInteractor, + private val sentryInteractor: SentryInteractor, + private val logger: Logger ) : CoroutineActionDispatcher(config.createConfig()) { + + init { + currentSubscriptionStateRepository + .changes + .onEach { subscription -> + onNewMessage(Message.OnSubscriptionChanged(subscription)) + } + .launchIn(actionScope) + } + override suspend fun doSuspendableAction(action: Action) { when (action) { - is Action.FetchProfileSettings -> { - val profileSettings = profileSettingsInteractor.getProfileSettings() - onNewMessage(Message.ProfileSettingsSuccess(profileSettings)) - } + is Action.FetchProfileSettings -> + handleFetchProfileSettings(platform.isSubscriptionPurchaseEnabled, ::onNewMessage) is Action.ChangeTheme -> profileSettingsInteractor.changeTheme(action.theme) is Action.SignOut -> @@ -72,4 +95,64 @@ class ProfileSettingsActionDispatcher( onNewMessage(Message.GetMagicLinkReceiveFailure) } ) + + private suspend fun handleFetchProfileSettings( + isSubscriptionPurchaseEnabled: Boolean, + onNewMessage: (Message) -> Unit + ) { + val message = if (isSubscriptionPurchaseEnabled && isMobileOnlySubscriptionEnabled()) { + sentryInteractor.withTransaction( + HyperskillSentryTransactionBuilder.buildProfileSettingsFeatureFetchSubscription(), + onError = { + Message.ProfileSettingsSuccess( + profileSettings = profileSettingsInteractor.getProfileSettings() + ) + } + ) { + fetchProfileSettingsWithSubscription() + } + } else { + Message.ProfileSettingsSuccess( + profileSettings = profileSettingsInteractor.getProfileSettings() + ) + } + onNewMessage(message) + } + + private suspend fun fetchProfileSettingsWithSubscription(): Message.ProfileSettingsSuccess = + coroutineScope { + val subscriptionDeferred = async { + currentSubscriptionStateRepository.getState(forceUpdate = true) + } + val priceDeferred = async { + purchaseInteractor.getFormattedMobileOnlySubscriptionPrice() + } + Message.ProfileSettingsSuccess( + profileSettings = profileSettingsInteractor.getProfileSettings(), + subscription = subscriptionDeferred + .await() + .onFailure { + logger.e(it) { + "Failed to load subscription" + } + } + .getOrNull(), + mobileOnlyFormattedPrice = priceDeferred + .await() + .onFailure { + logger.e(it) { + "Failed to load subscription price" + } + } + .getOrNull() + ) + } + + private suspend fun isMobileOnlySubscriptionEnabled(): Boolean = + currentProfileStateRepository + .getState() + .getOrNull() + ?.features + ?.isMobileOnlySubscriptionEnabled + ?: false } \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/profile_settings/presentation/ProfileSettingsFeature.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/profile_settings/presentation/ProfileSettingsFeature.kt index 69f9f5739b..e3aaf1d198 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/profile_settings/presentation/ProfileSettingsFeature.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/profile_settings/presentation/ProfileSettingsFeature.kt @@ -2,12 +2,14 @@ package org.hyperskill.app.profile_settings.presentation import org.hyperskill.app.analytic.domain.model.AnalyticEvent import org.hyperskill.app.core.domain.url.HyperskillUrlPath +import org.hyperskill.app.paywall.domain.model.PaywallTransitionSource import org.hyperskill.app.profile_settings.domain.model.FeedbackEmailData import org.hyperskill.app.profile_settings.domain.model.ProfileSettings import org.hyperskill.app.profile_settings.domain.model.Theme +import org.hyperskill.app.subscriptions.domain.model.Subscription -interface ProfileSettingsFeature { - sealed interface State { +object ProfileSettingsFeature { + internal sealed interface State { object Idle : State object Loading : State @@ -16,16 +18,34 @@ interface ProfileSettingsFeature { */ data class Content( val profileSettings: ProfileSettings, + val subscription: Subscription?, + val mobileOnlyFormattedPrice: String?, val isLoadingMagicLink: Boolean = false ) : State + } + + sealed interface ViewState { + object Idle : ViewState + object Loading : ViewState - object Error : State + data class Content( + val profileSettings: ProfileSettings, + val subscriptionState: SubscriptionState?, + val isLoadingMagicLink: Boolean + ) : ViewState { + data class SubscriptionState( + val description: String + ) + } } sealed interface Message { - data class InitMessage(val forceUpdate: Boolean = false) : Message - data class ProfileSettingsSuccess(val profileSettings: ProfileSettings) : Message - object ProfileSettingsError : Message + object InitMessage : Message + data class ProfileSettingsSuccess( + val profileSettings: ProfileSettings, + val subscription: Subscription? = null, + val mobileOnlyFormattedPrice: String? = null + ) : Message data class ThemeChanged(val theme: Theme) : Message object SignOutConfirmed : Message object DismissScreen : Message @@ -38,6 +58,12 @@ interface ProfileSettingsFeature { data class DeleteAccountNoticeHidden(val isConfirmed: Boolean) : Message + object SubscriptionDetailsClicked : Message + + data class OnSubscriptionChanged( + val subscription: Subscription + ) : Message + /** * Analytic */ @@ -79,6 +105,10 @@ interface ProfileSettingsFeature { sealed interface NavigateTo : ViewAction { object ParentScreen : NavigateTo + + object SubscriptionManagement : NavigateTo + + data class Paywall(val paywallTransitionSource: PaywallTransitionSource) : NavigateTo } } } diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/profile_settings/presentation/ProfileSettingsReducer.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/profile_settings/presentation/ProfileSettingsReducer.kt index 79088cd2c9..b19a118236 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/profile_settings/presentation/ProfileSettingsReducer.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/profile_settings/presentation/ProfileSettingsReducer.kt @@ -3,6 +3,7 @@ package org.hyperskill.app.profile_settings.presentation import org.hyperskill.app.analytic.domain.model.hyperskill.HyperskillAnalyticPart import org.hyperskill.app.analytic.domain.model.hyperskill.HyperskillAnalyticTarget import org.hyperskill.app.core.domain.url.HyperskillUrlPath +import org.hyperskill.app.paywall.domain.model.PaywallTransitionSource import org.hyperskill.app.profile_settings.domain.analytic.ProfileSettingsClickedHyperskillAnalyticEvent import org.hyperskill.app.profile_settings.domain.analytic.ProfileSettingsDeleteAccountNoticeHiddenHyperskillAnalyticEvent import org.hyperskill.app.profile_settings.domain.analytic.ProfileSettingsDeleteAccountNoticeShownHyperskillAnalyticEvent @@ -12,27 +13,32 @@ import org.hyperskill.app.profile_settings.domain.analytic.ProfileSettingsViewed import org.hyperskill.app.profile_settings.presentation.ProfileSettingsFeature.Action import org.hyperskill.app.profile_settings.presentation.ProfileSettingsFeature.Message import org.hyperskill.app.profile_settings.presentation.ProfileSettingsFeature.State +import org.hyperskill.app.subscriptions.domain.model.SubscriptionType import ru.nobird.app.presentation.redux.reducer.StateReducer -class ProfileSettingsReducer : StateReducer { +private typealias ReducerResult = Pair> + +internal class ProfileSettingsReducer : StateReducer { override fun reduce(state: State, message: Message): Pair> = when (message) { is Message.InitMessage -> { - if (state is State.Idle || - (message.forceUpdate && (state is State.Content || state is State.Error)) - ) { + if (state is State.Idle) { State.Loading to setOf(Action.FetchProfileSettings) } else { null } } is Message.ProfileSettingsSuccess -> - State.Content(message.profileSettings) to emptySet() - is Message.ProfileSettingsError -> - State.Error to emptySet() + State.Content( + profileSettings = message.profileSettings, + subscription = message.subscription, + mobileOnlyFormattedPrice = message.mobileOnlyFormattedPrice + ) to emptySet() + is Message.OnSubscriptionChanged -> + handleSubscriptionChanged(state, message) is Message.ThemeChanged -> if (state is State.Content) { - State.Content(state.profileSettings.copy(theme = message.theme)) to + state.copy(state.profileSettings.copy(theme = message.theme)) to setOf(Action.ChangeTheme(message.theme)) } else { null @@ -55,8 +61,8 @@ class ProfileSettingsReducer : StateReducer { state to setOf( Action.LogAnalyticEvent( ProfileSettingsClickedHyperskillAnalyticEvent( - HyperskillAnalyticPart.HEAD, - HyperskillAnalyticTarget.DONE + target = HyperskillAnalyticTarget.DONE, + part = HyperskillAnalyticPart.HEAD ) ) ) @@ -165,5 +171,48 @@ class ProfileSettingsReducer : StateReducer { null } } + is Message.SubscriptionDetailsClicked -> + handleSubscriptionDetailsClicked(state) } ?: (state to emptySet()) + + private fun handleSubscriptionChanged( + state: State, + message: Message.OnSubscriptionChanged + ): ReducerResult = + if (state is State.Content) { + state.copy(subscription = message.subscription) to emptySet() + } else { + state to emptySet() + } + + private fun handleSubscriptionDetailsClicked( + state: State + ): ReducerResult = + if (state is State.Content) { + state to when (state.subscription?.type) { + SubscriptionType.MOBILE_ONLY -> + setOf( + Action.LogAnalyticEvent( + ProfileSettingsClickedHyperskillAnalyticEvent( + HyperskillAnalyticTarget.ACTIVE_SUBSCRIPTION_DETAILS + ) + ), + Action.ViewAction.NavigateTo.SubscriptionManagement + ) + SubscriptionType.FREEMIUM -> + setOf( + Action.LogAnalyticEvent( + ProfileSettingsClickedHyperskillAnalyticEvent( + HyperskillAnalyticTarget.SUBSCRIPTION_SUGGESTION_DETAILS + ) + ), + Action.ViewAction.NavigateTo.Paywall( + PaywallTransitionSource.PROFILE_SETTINGS + ) + ) + else -> emptySet() + } + } else { + state to emptySet() + } } \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/profile_settings/view/ProfileSettingsViewStateMapper.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/profile_settings/view/ProfileSettingsViewStateMapper.kt new file mode 100644 index 0000000000..0ffd1634fb --- /dev/null +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/profile_settings/view/ProfileSettingsViewStateMapper.kt @@ -0,0 +1,54 @@ +package org.hyperskill.app.profile_settings.view + +import org.hyperskill.app.SharedResources +import org.hyperskill.app.core.view.mapper.ResourceProvider +import org.hyperskill.app.profile_settings.presentation.ProfileSettingsFeature +import org.hyperskill.app.profile_settings.presentation.ProfileSettingsFeature.ViewState +import org.hyperskill.app.subscriptions.domain.model.SubscriptionType + +internal class ProfileSettingsViewStateMapper( + private val resourceProvider: ResourceProvider +) { + fun map(state: ProfileSettingsFeature.State): ViewState = + when (state) { + ProfileSettingsFeature.State.Idle -> ViewState.Idle + ProfileSettingsFeature.State.Loading -> ViewState.Loading + is ProfileSettingsFeature.State.Content -> mapContentState(state) + } + + private fun mapContentState(state: ProfileSettingsFeature.State.Content): ViewState.Content = + ViewState.Content( + profileSettings = state.profileSettings, + isLoadingMagicLink = state.isLoadingMagicLink, + subscriptionState = if (isSubscriptionVisible(state)) { + when (state.subscription?.type) { + SubscriptionType.MOBILE_ONLY -> + ViewState.Content.SubscriptionState( + resourceProvider.getString(SharedResources.strings.settings_subscription_mobile_only) + ) + SubscriptionType.FREEMIUM -> { + state.mobileOnlyFormattedPrice?.let { + ViewState.Content.SubscriptionState( + resourceProvider.getString( + SharedResources.strings.settings_subscription_mobile_only_suggestion, + state.mobileOnlyFormattedPrice + ) + ) + } + } + else -> null + } + } else { + null + } + ) + + private fun isSubscriptionVisible(state: ProfileSettingsFeature.State.Content): Boolean = + state.subscription != null && + state.mobileOnlyFormattedPrice != null && + when (state.subscription.type) { + SubscriptionType.FREEMIUM, + SubscriptionType.MOBILE_ONLY -> true + else -> false + } +} \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/progress_screen/view/ProgressScreenViewStateMapper.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/progress_screen/view/ProgressScreenViewStateMapper.kt index f7fa49a563..de461e02e4 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/progress_screen/view/ProgressScreenViewStateMapper.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/progress_screen/view/ProgressScreenViewStateMapper.kt @@ -5,7 +5,6 @@ import org.hyperskill.app.core.view.mapper.ResourceProvider import org.hyperskill.app.core.view.mapper.date.SharedDateFormatter import org.hyperskill.app.progress_screen.presentation.ProgressScreenFeature import org.hyperskill.app.progress_screen.view.ProgressScreenViewState.TrackProgressViewState.Content.AppliedTopicsState -import org.hyperskill.app.subscriptions.domain.model.isFreemium import org.hyperskill.app.track.domain.model.Track import org.hyperskill.app.track.domain.model.asLevelByProjectIdMap import ru.nobird.app.core.model.safeCast @@ -52,7 +51,9 @@ internal class ProgressScreenViewStateMapper( ): ProgressScreenViewState.TrackProgressViewState.Content { val track = trackProgressContent.trackWithProgress.track val trackProgress = trackProgressContent.trackWithProgress.trackProgress - val isProjectUnavailable = trackProgressContent.subscription.isFreemium || track.projects.isEmpty() + val isProjectUnavailable = + !trackProgressContent.subscription.type.isProjectInfoAvailable || + track.projects.isEmpty() return ProgressScreenViewState.TrackProgressViewState.Content( title = track.title, diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/purchases/domain/interactor/PurchaseInteractor.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/purchases/domain/interactor/PurchaseInteractor.kt new file mode 100644 index 0000000000..d0013100ce --- /dev/null +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/purchases/domain/interactor/PurchaseInteractor.kt @@ -0,0 +1,37 @@ +package org.hyperskill.app.purchases.domain.interactor + +import org.hyperskill.app.purchases.domain.model.PlatformPurchaseParams +import org.hyperskill.app.purchases.domain.model.PurchaseManager +import org.hyperskill.app.purchases.domain.model.PurchaseResult + +class PurchaseInteractor( + private val purchaseManager: PurchaseManager +) { + companion object { + private const val MOBILE_ONLY_SUBSCRIPTION_PRODUCT_ID: String = "premium_mobile" + } + + /** + * Identifies user in the payment sdk with provided [userId]. + * Must be called just after login event. + */ + suspend fun login(userId: Long): Result = + if (!purchaseManager.isConfigured()) { + runCatching { + purchaseManager.configure(userId) + } + } else { + purchaseManager.login(userId) + } + + suspend fun purchaseMobileOnlySubscription( + platformPurchaseParams: PlatformPurchaseParams + ): Result = + purchaseManager.purchase(MOBILE_ONLY_SUBSCRIPTION_PRODUCT_ID, platformPurchaseParams) + + suspend fun getManagementUrl(): Result = + purchaseManager.getManagementUrl() + + suspend fun getFormattedMobileOnlySubscriptionPrice(): Result = + purchaseManager.getFormattedProductPrice(MOBILE_ONLY_SUBSCRIPTION_PRODUCT_ID) +} \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/purchases/domain/model/PlatformPurchaseParams.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/purchases/domain/model/PlatformPurchaseParams.kt new file mode 100644 index 0000000000..59f63d7177 --- /dev/null +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/purchases/domain/model/PlatformPurchaseParams.kt @@ -0,0 +1,3 @@ +package org.hyperskill.app.purchases.domain.model + +interface PlatformPurchaseParams \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/purchases/domain/model/PurchaseManager.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/purchases/domain/model/PurchaseManager.kt new file mode 100644 index 0000000000..512a74baf8 --- /dev/null +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/purchases/domain/model/PurchaseManager.kt @@ -0,0 +1,35 @@ +package org.hyperskill.app.purchases.domain.model + +/** + * Represents an interface that both platforms should implement. + */ +interface PurchaseManager { + + fun isConfigured(): Boolean + + /** + * Setups the payment sdk with provided [userId] + */ + fun configure(userId: Long) + + /** + * Identifies user in the payment sdk with provided [userId]. + * Must be called just after login event. + */ + suspend fun login(userId: Long): Result + + /** + * Makes purchase of the product with [productId]. + */ + suspend fun purchase( + productId: String, + platformPurchaseParams: PlatformPurchaseParams + ): Result + + suspend fun getManagementUrl(): Result + + /** + * Returns formatted product price with currency by [productId] + */ + suspend fun getFormattedProductPrice(productId: String): Result +} \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/purchases/domain/model/PurchaseResult.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/purchases/domain/model/PurchaseResult.kt new file mode 100644 index 0000000000..1ea0708fe0 --- /dev/null +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/purchases/domain/model/PurchaseResult.kt @@ -0,0 +1,94 @@ +package org.hyperskill.app.purchases.domain.model + +sealed interface PurchaseResult { + data class Succeed( + val orderId: String?, + val productIds: List + ) : PurchaseResult + + object CancelledByUser : PurchaseResult + + sealed interface Error : PurchaseResult { + + val message: String + + val underlyingErrorMessage: String? + + class ErrorWhileFetchingProduct( + val productId: String, + val originMessage: String, + override val underlyingErrorMessage: String? + ) : Error { + override val message: String + get() = "Error while fetching product with id=$productId" + } + + class NoProductFound( + val productId: String + ) : Error { + override val message: String + get() = "Can't find product with id=$productId" + + override val underlyingErrorMessage: String? + get() = null + } + + /** + * The receipt is already in use by another subscriber. + * Log in with the previous account or contact support + * to get your purchases transferred to regain access. + */ + class ReceiptAlreadyInUseError( + override val message: String, + override val underlyingErrorMessage: String? + ) : Error + + /** + * The purchase is pending and may be completed at a later time. + * This can happen when awaiting parental approval or going + * through extra authentication flows for credit cards in some countries. + */ + class PaymentPendingError( + override val message: String, + override val underlyingErrorMessage: String? + ) : Error + + /** + * Subscription is already purchased. Log in with the account + * that originally performed this purchase if you're using a different one. + */ + class ProductAlreadyPurchasedError( + override val message: String, + override val underlyingErrorMessage: String? + ) : Error + + /** + * Purchasing wasn't allowed, which is common if the card is declined + * or the purchase is not available in the country + * you're trying to purchase from. + */ + class PurchaseNotAllowedError( + override val message: String, + override val underlyingErrorMessage: String? + ) : Error + + /** + * There was a problem with the Google Play Store. + * This is a generic Google error, and there's not enough information + * to determine the cause. + */ + class StoreProblemError( + override val message: String, + override val underlyingErrorMessage: String? + ) : Error + + /** + * Some other kind of error. + * For example, configuration error or invalid user id error. + */ + data class OtherError( + override val message: String, + override val underlyingErrorMessage: String? + ) : Error + } +} \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/purchases/injection/PurchaseComponent.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/purchases/injection/PurchaseComponent.kt new file mode 100644 index 0000000000..d648740106 --- /dev/null +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/purchases/injection/PurchaseComponent.kt @@ -0,0 +1,7 @@ +package org.hyperskill.app.purchases.injection + +import org.hyperskill.app.purchases.domain.interactor.PurchaseInteractor + +interface PurchaseComponent { + val purchaseInteractor: PurchaseInteractor +} \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/purchases/injection/PurchaseComponentImpl.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/purchases/injection/PurchaseComponentImpl.kt new file mode 100644 index 0000000000..b5b5942b2c --- /dev/null +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/purchases/injection/PurchaseComponentImpl.kt @@ -0,0 +1,12 @@ +package org.hyperskill.app.purchases.injection + +import org.hyperskill.app.purchases.domain.interactor.PurchaseInteractor +import org.hyperskill.app.purchases.domain.model.PurchaseManager + +class PurchaseComponentImpl( + purchaseManager: PurchaseManager +) : PurchaseComponent { + + override val purchaseInteractor: PurchaseInteractor = + PurchaseInteractor(purchaseManager) +} \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/sentry/domain/model/transaction/HyperskillSentryTransactionBuilder.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/sentry/domain/model/transaction/HyperskillSentryTransactionBuilder.kt index f599b85a04..047794f8e0 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/sentry/domain/model/transaction/HyperskillSentryTransactionBuilder.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/sentry/domain/model/transaction/HyperskillSentryTransactionBuilder.kt @@ -327,4 +327,40 @@ object HyperskillSentryTransactionBuilder { name = "streak-recovery-feature-fetch-streak", operation = HyperskillSentryTransactionOperation.API_LOAD ) + + /** + * ProfileSettingsFeature + */ + fun buildProfileSettingsFeatureFetchSubscription(): HyperskillSentryTransaction = + HyperskillSentryTransaction( + name = "profile-settings-feature-fetch-subscription", + operation = HyperskillSentryTransactionOperation.API_LOAD + ) + + fun buildManageSubscriptionFeatureFetchSubscription(): HyperskillSentryTransaction = + HyperskillSentryTransaction( + name = "manage-subscription-feature-fetch-subscription", + operation = HyperskillSentryTransactionOperation.API_LOAD + ) + + /** + * PaywallFeature + */ + fun buildPaywallFeatureSyncSubscription(): HyperskillSentryTransaction = + HyperskillSentryTransaction( + name = "paywall-feature-sync-subscription", + operation = HyperskillSentryTransactionOperation.API_LOAD + ) + + fun buildPaywallFeaturePurchaseSubscription(): HyperskillSentryTransaction = + HyperskillSentryTransaction( + name = "paywall-feature-purchase-subscription", + operation = HyperskillSentryTransactionOperation.API_LOAD + ) + + fun buildPaywallFetchSubscriptionPrice(): HyperskillSentryTransaction = + HyperskillSentryTransaction( + name = "paywall-feature-fetch-subscription-price", + operation = HyperskillSentryTransactionOperation.API_LOAD + ) } \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/step_completion/injection/StepCompletionComponentImpl.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/step_completion/injection/StepCompletionComponentImpl.kt index 0c861dfdeb..37080cab45 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/step_completion/injection/StepCompletionComponentImpl.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/step_completion/injection/StepCompletionComponentImpl.kt @@ -23,7 +23,7 @@ internal class StepCompletionComponentImpl( analyticInteractor = appGraph.analyticComponent.analyticInteractor, resourceProvider = appGraph.commonComponent.resourceProvider, sentryInteractor = appGraph.sentryComponent.sentryInteractor, - freemiumInteractor = appGraph.buildFreemiumDataComponent().freemiumInteractor, + subscriptionsInteractor = appGraph.subscriptionDataComponent.subscriptionsInteractor, shareStreakInteractor = appGraph.buildShareStreakDataComponent().shareStreakInteractor, requestReviewInteractor = appGraph.buildRequestReviewDataComponent().requestReviewInteractor, nextLearningActivityStateRepository = appGraph.stateRepositoriesComponent diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/step_completion/presentation/StepCompletionActionDispatcher.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/step_completion/presentation/StepCompletionActionDispatcher.kt index 49d8cb6b68..46e88291ff 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/step_completion/presentation/StepCompletionActionDispatcher.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/step_completion/presentation/StepCompletionActionDispatcher.kt @@ -9,7 +9,6 @@ import org.hyperskill.app.analytic.domain.interactor.AnalyticInteractor import org.hyperskill.app.core.domain.repository.updateState import org.hyperskill.app.core.presentation.ActionDispatcherOptions import org.hyperskill.app.core.view.mapper.ResourceProvider -import org.hyperskill.app.freemium.domain.interactor.FreemiumInteractor import org.hyperskill.app.gamification_toolbar.domain.repository.CurrentGamificationToolbarDataStateRepository import org.hyperskill.app.interview_steps.domain.repository.InterviewStepsStateRepository import org.hyperskill.app.learning_activities.domain.repository.NextLearningActivityStateRepository @@ -34,6 +33,7 @@ import org.hyperskill.app.step_completion.presentation.StepCompletionFeature.Int import org.hyperskill.app.step_completion.presentation.StepCompletionFeature.Message import org.hyperskill.app.step_quiz.domain.repository.SubmissionRepository import org.hyperskill.app.streaks.domain.model.StreakState +import org.hyperskill.app.subscriptions.domain.interactor.SubscriptionsInteractor import org.hyperskill.app.topics.domain.repository.TopicsRepository import ru.nobird.app.core.model.mutate import ru.nobird.app.presentation.redux.dispatcher.CoroutineActionDispatcher @@ -47,7 +47,7 @@ class StepCompletionActionDispatcher( private val analyticInteractor: AnalyticInteractor, private val resourceProvider: ResourceProvider, private val sentryInteractor: SentryInteractor, - private val freemiumInteractor: FreemiumInteractor, + private val subscriptionsInteractor: SubscriptionsInteractor, private val shareStreakInteractor: ShareStreakInteractor, private val requestReviewInteractor: RequestReviewInteractor, private val nextLearningActivityStateRepository: NextLearningActivityStateRepository, @@ -101,7 +101,7 @@ class StepCompletionActionDispatcher( handleCheckTopicCompletionStatusAction(action, ::onNewMessage) } is Action.UpdateProblemsLimit -> { - freemiumInteractor.onStepSolved() + subscriptionsInteractor.onStepSolved() } is Action.UpdateLastTimeShareStreakShown -> { shareStreakInteractor.setLastTimeShareStreakShown() diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz/domain/analytic/ProblemsLimitReachedModalClickedUnlockUnlimitedProblemsHSAnalyticEvent.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz/domain/analytic/ProblemsLimitReachedModalClickedUnlockUnlimitedProblemsHSAnalyticEvent.kt new file mode 100644 index 0000000000..eda6bea8f0 --- /dev/null +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz/domain/analytic/ProblemsLimitReachedModalClickedUnlockUnlimitedProblemsHSAnalyticEvent.kt @@ -0,0 +1,30 @@ +package org.hyperskill.app.step_quiz.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.HyperskillAnalyticPart +import org.hyperskill.app.analytic.domain.model.hyperskill.HyperskillAnalyticRoute +import org.hyperskill.app.analytic.domain.model.hyperskill.HyperskillAnalyticTarget + +/** + * Represents click on the "Unlock unlimited problems" button in problems limit reached modal analytic event. + * + * JSON payload: + * ``` + * { + * "route": "/learn/step/1", + * "action": "click", + * "part": "problems_limit_reached_modal", + * "target": "unlock_unlimited_problems" + * } + * ``` + * @see HyperskillAnalyticEvent + */ +class ProblemsLimitReachedModalClickedUnlockUnlimitedProblemsHSAnalyticEvent( + route: HyperskillAnalyticRoute +) : HyperskillAnalyticEvent( + route, + HyperskillAnalyticAction.CLICK, + HyperskillAnalyticPart.PROBLEMS_LIMIT_REACHED_MODAL, + HyperskillAnalyticTarget.UNLOCK_UNLIMITED_PROBLEMS +) \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz/injection/StepQuizComponentImpl.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz/injection/StepQuizComponentImpl.kt index 256f4b36ad..6d3e569a07 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz/injection/StepQuizComponentImpl.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz/injection/StepQuizComponentImpl.kt @@ -47,19 +47,20 @@ internal class StepQuizComponentImpl( override val stepQuizFeature: Feature get() = StepQuizFeatureBuilder.build( - stepRoute, - stepQuizInteractor, - stepQuizReplyValidator, - appGraph.profileDataComponent.currentProfileStateRepository, - appGraph.buildFreemiumDataComponent().freemiumInteractor, - appGraph.buildMagicLinksDataComponent().urlPathProcessor, - appGraph.analyticComponent.analyticInteractor, - appGraph.sentryComponent.sentryInteractor, - appGraph.buildOnboardingDataComponent().onboardingInteractor, - stepQuizHintsComponent.stepQuizHintsReducer, - stepQuizHintsComponent.stepQuizHintsActionDispatcher, - appGraph.commonComponent.resourceProvider, - appGraph.loggerComponent.logger, - appGraph.commonComponent.buildKonfig.buildVariant + stepRoute = stepRoute, + stepQuizInteractor = stepQuizInteractor, + stepQuizReplyValidator = stepQuizReplyValidator, + currentProfileStateRepository = appGraph.profileDataComponent.currentProfileStateRepository, + currentSubscriptionStateRepository = appGraph.stateRepositoriesComponent.currentSubscriptionStateRepository, + urlPathProcessor = appGraph.buildMagicLinksDataComponent().urlPathProcessor, + analyticInteractor = appGraph.analyticComponent.analyticInteractor, + sentryInteractor = appGraph.sentryComponent.sentryInteractor, + onboardingInteractor = appGraph.buildOnboardingDataComponent().onboardingInteractor, + stepQuizHintsReducer = stepQuizHintsComponent.stepQuizHintsReducer, + stepQuizHintsActionDispatcher = stepQuizHintsComponent.stepQuizHintsActionDispatcher, + resourceProvider = appGraph.commonComponent.resourceProvider, + logger = appGraph.loggerComponent.logger, + buildVariant = appGraph.commonComponent.buildKonfig.buildVariant, + platform = appGraph.commonComponent.platform ) } \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz/injection/StepQuizFeatureBuilder.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz/injection/StepQuizFeatureBuilder.kt index c19ff0955b..efb1430572 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz/injection/StepQuizFeatureBuilder.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz/injection/StepQuizFeatureBuilder.kt @@ -3,9 +3,9 @@ package org.hyperskill.app.step_quiz.injection import co.touchlab.kermit.Logger import org.hyperskill.app.analytic.domain.interactor.AnalyticInteractor import org.hyperskill.app.core.domain.BuildVariant +import org.hyperskill.app.core.domain.platform.Platform import org.hyperskill.app.core.presentation.ActionDispatcherOptions import org.hyperskill.app.core.view.mapper.ResourceProvider -import org.hyperskill.app.freemium.domain.interactor.FreemiumInteractor import org.hyperskill.app.logging.presentation.wrapWithLogger import org.hyperskill.app.magic_links.domain.interactor.UrlPathProcessor import org.hyperskill.app.onboarding.domain.interactor.OnboardingInteractor @@ -20,6 +20,7 @@ import org.hyperskill.app.step_quiz.presentation.StepQuizReducer import org.hyperskill.app.step_quiz_hints.presentation.StepQuizHintsActionDispatcher import org.hyperskill.app.step_quiz_hints.presentation.StepQuizHintsFeature import org.hyperskill.app.step_quiz_hints.presentation.StepQuizHintsReducer +import org.hyperskill.app.subscriptions.domain.repository.CurrentSubscriptionStateRepository import ru.nobird.app.core.model.safeCast import ru.nobird.app.presentation.redux.dispatcher.transform import ru.nobird.app.presentation.redux.dispatcher.wrapWithActionDispatcher @@ -34,7 +35,7 @@ internal object StepQuizFeatureBuilder { stepQuizInteractor: StepQuizInteractor, stepQuizReplyValidator: StepQuizReplyValidator, currentProfileStateRepository: CurrentProfileStateRepository, - freemiumInteractor: FreemiumInteractor, + currentSubscriptionStateRepository: CurrentSubscriptionStateRepository, urlPathProcessor: UrlPathProcessor, analyticInteractor: AnalyticInteractor, sentryInteractor: SentryInteractor, @@ -43,23 +44,25 @@ internal object StepQuizFeatureBuilder { stepQuizHintsActionDispatcher: StepQuizHintsActionDispatcher, resourceProvider: ResourceProvider, logger: Logger, - buildVariant: BuildVariant + buildVariant: BuildVariant, + platform: Platform ): Feature { val stepQuizReducer = StepQuizReducer( stepRoute = stepRoute, stepQuizHintsReducer = stepQuizHintsReducer ).wrapWithLogger(buildVariant, logger, LOG_TAG) val stepQuizActionDispatcher = StepQuizActionDispatcher( - ActionDispatcherOptions(), - stepQuizInteractor, - stepQuizReplyValidator, - currentProfileStateRepository, - freemiumInteractor, - urlPathProcessor, - analyticInteractor, - sentryInteractor, - onboardingInteractor, - resourceProvider + config = ActionDispatcherOptions(), + stepQuizInteractor = stepQuizInteractor, + stepQuizReplyValidator = stepQuizReplyValidator, + currentProfileStateRepository = currentProfileStateRepository, + currentSubscriptionStateRepository = currentSubscriptionStateRepository, + urlPathProcessor = urlPathProcessor, + analyticInteractor = analyticInteractor, + sentryInteractor = sentryInteractor, + onboardingInteractor = onboardingInteractor, + resourceProvider = resourceProvider, + platform = platform ) return ReduxFeature( diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz/presentation/StepQuizActionDispatcher.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz/presentation/StepQuizActionDispatcher.kt index fc3958af8d..afd8739617 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz/presentation/StepQuizActionDispatcher.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz/presentation/StepQuizActionDispatcher.kt @@ -2,15 +2,17 @@ package org.hyperskill.app.step_quiz.presentation import org.hyperskill.app.SharedResources import org.hyperskill.app.analytic.domain.interactor.AnalyticInteractor +import org.hyperskill.app.core.domain.platform.Platform import org.hyperskill.app.core.domain.url.HyperskillUrlPath import org.hyperskill.app.core.presentation.ActionDispatcherOptions import org.hyperskill.app.core.view.mapper.ResourceProvider -import org.hyperskill.app.freemium.domain.interactor.FreemiumInteractor import org.hyperskill.app.magic_links.domain.interactor.UrlPathProcessor import org.hyperskill.app.onboarding.domain.interactor.OnboardingInteractor +import org.hyperskill.app.profile.domain.model.isMobileOnlySubscriptionEnabled import org.hyperskill.app.profile.domain.repository.CurrentProfileStateRepository import org.hyperskill.app.sentry.domain.interactor.SentryInteractor import org.hyperskill.app.sentry.domain.model.transaction.HyperskillSentryTransactionBuilder +import org.hyperskill.app.sentry.domain.withTransaction import org.hyperskill.app.step_quiz.domain.interactor.StepQuizInteractor import org.hyperskill.app.step_quiz.domain.model.attempts.Attempt import org.hyperskill.app.step_quiz.domain.model.submissions.SubmissionStatus @@ -21,6 +23,10 @@ import org.hyperskill.app.step_quiz.presentation.StepQuizFeature.InternalAction import org.hyperskill.app.step_quiz.presentation.StepQuizFeature.InternalMessage import org.hyperskill.app.step_quiz.presentation.StepQuizFeature.Message import org.hyperskill.app.step_quiz_fill_blanks.model.FillBlanksMode +import org.hyperskill.app.subscriptions.domain.model.Subscription +import org.hyperskill.app.subscriptions.domain.model.isFreemium +import org.hyperskill.app.subscriptions.domain.model.isProblemLimitReached +import org.hyperskill.app.subscriptions.domain.repository.CurrentSubscriptionStateRepository import ru.nobird.app.presentation.redux.dispatcher.CoroutineActionDispatcher internal class StepQuizActionDispatcher( @@ -28,82 +34,18 @@ internal class StepQuizActionDispatcher( private val stepQuizInteractor: StepQuizInteractor, private val stepQuizReplyValidator: StepQuizReplyValidator, private val currentProfileStateRepository: CurrentProfileStateRepository, - private val freemiumInteractor: FreemiumInteractor, + private val currentSubscriptionStateRepository: CurrentSubscriptionStateRepository, private val urlPathProcessor: UrlPathProcessor, private val analyticInteractor: AnalyticInteractor, private val sentryInteractor: SentryInteractor, private val onboardingInteractor: OnboardingInteractor, - private val resourceProvider: ResourceProvider + private val resourceProvider: ResourceProvider, + private val platform: Platform ) : CoroutineActionDispatcher(config.createConfig()) { override suspend fun doSuspendableAction(action: Action) { when (action) { - is Action.FetchAttempt -> { - val sentryTransaction = HyperskillSentryTransactionBuilder.buildStepQuizScreenRemoteDataLoading() - sentryInteractor.startTransaction(sentryTransaction) - - val currentProfile = currentProfileStateRepository - .getState(forceUpdate = false) - .getOrElse { - sentryInteractor.finishTransaction(sentryTransaction, throwable = it) - onNewMessage(Message.FetchAttemptError(it)) - return - } - - val isProblemsLimitReached = freemiumInteractor - .isProblemsLimitReached() - .getOrElse { - sentryInteractor.finishTransaction(sentryTransaction, throwable = it) - onNewMessage(Message.FetchAttemptError(it)) - return - } - - val problemsLimitReachedModalText = freemiumInteractor - .getStepsLimitTotal() - .map { - it?.let { stepsLimitTotal -> - resourceProvider.getString( - SharedResources.strings.problems_limit_reached_modal_description, - stepsLimitTotal - ) - } - } - .getOrElse { - sentryInteractor.finishTransaction(sentryTransaction, throwable = it) - onNewMessage(Message.FetchAttemptError(it)) - return - } - - val message = stepQuizInteractor - .getAttempt(action.step.id, currentProfile.id) - .fold( - onSuccess = { attempt -> - val message = getSubmissionState(attempt.id, action.step.id, currentProfile.id).fold( - onSuccess = { - Message.FetchAttemptSuccess( - step = action.step, - attempt = attempt, - submissionState = it, - isProblemsLimitReached = isProblemsLimitReached, - problemsLimitReachedModalText = problemsLimitReachedModalText, - problemsOnboardingFlags = onboardingInteractor.getProblemsOnboardingFlags() - ) - }, - onFailure = { - Message.FetchAttemptError(it) - } - ) - message - }, - onFailure = { Message.FetchAttemptError(it) } - ) - - sentryInteractor.finishTransaction( - transaction = sentryTransaction, - throwable = (message as? Message.FetchAttemptError)?.throwable - ) - - onNewMessage(message) - } + is Action.FetchAttempt -> + handleFetchAttempt(action, ::onNewMessage) is Action.CreateAttempt -> { if (StepQuizResolver.isNeedRecreateAttemptForNewSubmission(action.step)) { val sentryTransaction = HyperskillSentryTransactionBuilder.buildStepQuizCreateAttempt() @@ -227,6 +169,55 @@ internal class StepQuizActionDispatcher( } } + private suspend fun handleFetchAttempt( + action: Action.FetchAttempt, + onNewMessage: (Message) -> Unit + ) { + sentryInteractor.withTransaction( + transaction = HyperskillSentryTransactionBuilder.buildStepQuizScreenRemoteDataLoading(), + onError = { + Message.FetchAttemptError(it) + } + ) { + val currentProfile = + currentProfileStateRepository + .getState() + .getOrThrow() + + val subscription = + currentSubscriptionStateRepository + .getState() + .getOrThrow() + + val attempt = + stepQuizInteractor + .getAttempt(action.step.id, currentProfile.id) + .getOrThrow() + + val submissionState = + getSubmissionState(attempt.id, action.step.id, currentProfile.id) + .getOrThrow() + + val isSubscriptionPurchaseEnabled = + platform.isSubscriptionPurchaseEnabled && + currentProfile.features.isMobileOnlySubscriptionEnabled && + subscription.isFreemium + + Message.FetchAttemptSuccess( + step = action.step, + attempt = attempt, + submissionState = submissionState, + isProblemsLimitReached = subscription.isProblemLimitReached, + problemsLimitReachedModalText = getProblemsLimitReachedModalText( + subscription, + isSubscriptionPurchaseEnabled + ), + isSubscriptionPurchaseEnabled = isSubscriptionPurchaseEnabled, + problemsOnboardingFlags = onboardingInteractor.getProblemsOnboardingFlags() + ) + }.let(onNewMessage) + } + private suspend fun getSubmissionState( attemptId: Long, stepId: Long, @@ -241,4 +232,19 @@ internal class StepQuizActionDispatcher( StepQuizFeature.SubmissionState.Loaded(submission) } } + + private fun getProblemsLimitReachedModalText( + subscription: Subscription, + isSubscriptionPurchaseEnabled: Boolean + ): String? = + subscription.stepsLimitTotal?.let { stepsLimitTotal -> + resourceProvider.getString( + if (isSubscriptionPurchaseEnabled) { + SharedResources.strings.problems_limit_reached_modal_unlock_unlimited_problems_description + } else { + SharedResources.strings.problems_limit_reached_modal_description + }, + stepsLimitTotal + ) + } } \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz/presentation/StepQuizFeature.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz/presentation/StepQuizFeature.kt index e75406aca4..3bd84f786d 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz/presentation/StepQuizFeature.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz/presentation/StepQuizFeature.kt @@ -3,6 +3,7 @@ package org.hyperskill.app.step_quiz.presentation import kotlinx.serialization.Serializable import org.hyperskill.app.analytic.domain.model.AnalyticEvent import org.hyperskill.app.onboarding.domain.model.ProblemsOnboardingFlags +import org.hyperskill.app.paywall.domain.model.PaywallTransitionSource import org.hyperskill.app.step.domain.model.Step import org.hyperskill.app.step.domain.model.StepContext import org.hyperskill.app.step.domain.model.StepRoute @@ -59,6 +60,7 @@ object StepQuizFeature { val submissionState: SubmissionState, val isProblemsLimitReached: Boolean, val problemsLimitReachedModalText: String?, + val isSubscriptionPurchaseEnabled: Boolean, val problemsOnboardingFlags: ProblemsOnboardingFlags ) : Message data class FetchAttemptError(val throwable: Throwable) : Message @@ -99,6 +101,8 @@ object StepQuizFeature { */ object ProblemsLimitReachedModalGoToHomeScreenClicked : Message + object ProblemsLimitReachedModalUnlockUnlimitedProblemsClicked : Message + /** * Problem onboarding modal */ @@ -180,7 +184,10 @@ object StepQuizFeature { object RequestResetCode : ViewAction - data class ShowProblemsLimitReachedModal(val modalText: String) : ViewAction + data class ShowProblemsLimitReachedModal( + val modalText: String, + val isUnlockUnlimitedProblemsButtonVisible: Boolean + ) : ViewAction data class ShowProblemOnboardingModal(val modalType: ProblemOnboardingModal) : ViewAction @@ -200,6 +207,8 @@ object StepQuizFeature { object StudyPlan : NavigateTo data class StepScreen(val stepRoute: StepRoute) : NavigateTo + + data class Paywall(val paywallTransitionSource: PaywallTransitionSource) : NavigateTo } } } diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz/presentation/StepQuizReducer.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz/presentation/StepQuizReducer.kt index 5bfe3fecd5..01340ae6ad 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz/presentation/StepQuizReducer.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz/presentation/StepQuizReducer.kt @@ -2,12 +2,14 @@ package org.hyperskill.app.step_quiz.presentation import kotlinx.datetime.Clock import org.hyperskill.app.onboarding.domain.model.ProblemsOnboardingFlags +import org.hyperskill.app.paywall.domain.model.PaywallTransitionSource import org.hyperskill.app.step.domain.model.BlockName import org.hyperskill.app.step.domain.model.Step import org.hyperskill.app.step.domain.model.StepRoute import org.hyperskill.app.step_quiz.domain.analytic.ProblemOnboardingModalHiddenHyperskillAnalyticEvent import org.hyperskill.app.step_quiz.domain.analytic.ProblemOnboardingModalShownHyperskillAnalyticEvent import org.hyperskill.app.step_quiz.domain.analytic.ProblemsLimitReachedModalClickedGoToHomeScreenHyperskillAnalyticEvent +import org.hyperskill.app.step_quiz.domain.analytic.ProblemsLimitReachedModalClickedUnlockUnlimitedProblemsHSAnalyticEvent import org.hyperskill.app.step_quiz.domain.analytic.ProblemsLimitReachedModalHiddenHyperskillAnalyticEvent import org.hyperskill.app.step_quiz.domain.analytic.ProblemsLimitReachedModalShownHyperskillAnalyticEvent import org.hyperskill.app.step_quiz.domain.analytic.StepQuizClickedCodeDetailsHyperskillAnalyticEvent @@ -216,6 +218,13 @@ internal class StepQuizReducer( ProblemsLimitReachedModalClickedGoToHomeScreenHyperskillAnalyticEvent(stepRoute.analyticRoute) ) ) + is Message.ProblemsLimitReachedModalUnlockUnlimitedProblemsClicked -> + state to setOf( + Action.ViewAction.NavigateTo.Paywall(PaywallTransitionSource.PROBLEMS_LIMIT_MODAL), + Action.LogAnalyticEvent( + ProblemsLimitReachedModalClickedUnlockUnlimitedProblemsHSAnalyticEvent(stepRoute.analyticRoute) + ) + ) is Message.ClickedCodeDetailsEventMessage -> if (state.stepQuizState is StepQuizState.AttemptLoaded) { val event = StepQuizClickedCodeDetailsHyperskillAnalyticEvent(stepRoute.analyticRoute) @@ -355,7 +364,10 @@ internal class StepQuizReducer( val actions = if (isProblemsLimitReached && message.problemsLimitReachedModalText != null) { setOf( - Action.ViewAction.ShowProblemsLimitReachedModal(message.problemsLimitReachedModalText) + Action.ViewAction.ShowProblemsLimitReachedModal( + modalText = message.problemsLimitReachedModalText, + isUnlockUnlimitedProblemsButtonVisible = message.isSubscriptionPurchaseEnabled + ) ) } else { getProblemOnboardingModalActions( diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_hints/injection/StepQuizHintsComponentImpl.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_hints/injection/StepQuizHintsComponentImpl.kt index 6e1b43462f..5a4f455c65 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_hints/injection/StepQuizHintsComponentImpl.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_hints/injection/StepQuizHintsComponentImpl.kt @@ -29,7 +29,7 @@ class StepQuizHintsComponentImpl( commentsInteractor = appGraph.buildCommentsDataComponent().commentsInteractor, reactionsInteractor = appGraph.buildReactionsDataComponent().reactionsInteractor, userStorageInteractor = appGraph.buildUserStorageComponent().userStorageInteractor, - freemiumInteractor = appGraph.buildFreemiumDataComponent().freemiumInteractor, + currentSubscriptionStateRepository = appGraph.stateRepositoriesComponent.currentSubscriptionStateRepository, analyticInteractor = appGraph.analyticComponent.analyticInteractor, sentryInteractor = appGraph.sentryComponent.sentryInteractor ) diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_hints/presentation/StepQuizHintsActionDispatcher.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_hints/presentation/StepQuizHintsActionDispatcher.kt index 44c7b926d7..24bee7eca8 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_hints/presentation/StepQuizHintsActionDispatcher.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_hints/presentation/StepQuizHintsActionDispatcher.kt @@ -3,7 +3,6 @@ package org.hyperskill.app.step_quiz_hints.presentation import org.hyperskill.app.analytic.domain.interactor.AnalyticInteractor import org.hyperskill.app.comments.domain.interactor.CommentsInteractor import org.hyperskill.app.core.presentation.ActionDispatcherOptions -import org.hyperskill.app.freemium.domain.interactor.FreemiumInteractor import org.hyperskill.app.likes.domain.interactor.LikesInteractor import org.hyperskill.app.reactions.domain.interactor.ReactionsInteractor import org.hyperskill.app.reactions.domain.model.ReactionType @@ -13,6 +12,7 @@ import org.hyperskill.app.step_quiz_hints.domain.interactor.StepQuizHintsInterac import org.hyperskill.app.step_quiz_hints.domain.model.HintState import org.hyperskill.app.step_quiz_hints.presentation.StepQuizHintsFeature.Action import org.hyperskill.app.step_quiz_hints.presentation.StepQuizHintsFeature.Message +import org.hyperskill.app.subscriptions.domain.repository.CurrentSubscriptionStateRepository import org.hyperskill.app.user_storage.domain.interactor.UserStorageInteractor import org.hyperskill.app.user_storage.domain.model.UserStoragePathBuilder import ru.nobird.app.presentation.redux.dispatcher.CoroutineActionDispatcher @@ -24,7 +24,7 @@ class StepQuizHintsActionDispatcher( private val commentsInteractor: CommentsInteractor, private val reactionsInteractor: ReactionsInteractor, private val userStorageInteractor: UserStorageInteractor, - private val freemiumInteractor: FreemiumInteractor, + private val currentSubscriptionStateRepository: CurrentSubscriptionStateRepository, private val analyticInteractor: AnalyticInteractor, private val sentryInteractor: SentryInteractor ) : CoroutineActionDispatcher(config.createConfig()) { @@ -36,7 +36,13 @@ class StepQuizHintsActionDispatcher( val hintsIds = stepQuizHintsInteractor.getNotSeenHintsIds(action.stepId) - val isFreemiumEnabled = freemiumInteractor.isFreemiumEnabled().getOrDefault(false) + val areHintsLimited = + currentSubscriptionStateRepository + .getState() + .getOrNull() + ?.type + ?.areHintsLimited + ?: false val lastSeenHint = stepQuizHintsInteractor.getLastSeenHint(action.stepId) @@ -56,7 +62,7 @@ class StepQuizHintsActionDispatcher( hintsIds = hintsIds, lastSeenHint = lastSeenHint, lastSeenHintHasReaction = lastSeenHintHasReaction, - isFreemiumEnabled = isFreemiumEnabled, + areHintsLimited = areHintsLimited, stepId = action.stepId ) ) @@ -119,7 +125,7 @@ class StepQuizHintsActionDispatcher( Message.NextHintLoaded( it, action.remainingHintsIds, - action.isFreemiumEnabled, + action.areHintsLimited, action.stepId ) ) @@ -136,7 +142,7 @@ class StepQuizHintsActionDispatcher( Message.NextHintLoadingError( action.nextHintId, action.remainingHintsIds, - action.isFreemiumEnabled, + action.areHintsLimited, action.stepId ) ) diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_hints/presentation/StepQuizHintsFeature.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_hints/presentation/StepQuizHintsFeature.kt index b7edb66e57..71bdf00184 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_hints/presentation/StepQuizHintsFeature.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_hints/presentation/StepQuizHintsFeature.kt @@ -24,14 +24,14 @@ object StepQuizHintsFeature { * @property hintsIds remaining hints to be displayed * @property currentHint current hint to be displayed * @property hintHasReaction flag true, if user created reaction or reported hint - * @property isFreemiumEnabled used for showing only one hint for freemium + * @property areHintsLimited used for showing only one hint for some subscriptions * @property stepId used for analytic route */ data class Content( val hintsIds: List, val currentHint: Comment?, val hintHasReaction: Boolean, - val isFreemiumEnabled: Boolean, + val areHintsLimited: Boolean, val stepId: Long ) : State @@ -40,13 +40,13 @@ object StepQuizHintsFeature { * * @property nextHintId next hint to be loaded * @property hintsIds remaining hints to be displayed - * @property isFreemiumEnabled used for showing only one hint for freemium + * @property areHintsLimited used for showing only one hint for some subscriptions * @property stepId used for analytic route */ data class NetworkError( val nextHintId: Long, val hintsIds: List, - val isFreemiumEnabled: Boolean, + val areHintsLimited: Boolean, val stepId: Long ) : State } @@ -89,14 +89,14 @@ object StepQuizHintsFeature { * Message to fill state with ready data * * @property hintsIds hints ids to be displayed - * @property isFreemiumEnabled used for showing only one hint for freemium + * @property areHintsLimited used for showing only one hint for some subscriptions * @property stepId used for analytic route */ data class HintsIdsLoaded( val hintsIds: List, val lastSeenHint: Comment?, val lastSeenHintHasReaction: Boolean, - val isFreemiumEnabled: Boolean, + val areHintsLimited: Boolean, val stepId: Long ) : Message @@ -142,13 +142,13 @@ object StepQuizHintsFeature { * * @property nextHint new loaded hint * @property remainingHintsIds next hints ids to be displayed - * @property isFreemiumEnabled used for showing only one hint for freemium + * @property areHintsLimited used for showing only one hint for some subscriptions * @property stepId used for analytic route */ data class NextHintLoaded( val nextHint: Comment, val remainingHintsIds: List, - val isFreemiumEnabled: Boolean, + val areHintsLimited: Boolean, val stepId: Long ) : Message @@ -157,13 +157,13 @@ object StepQuizHintsFeature { * * @property nextHintId next hint to be loaded * @property remainingHintsIds remaining hints ids - * @property isFreemiumEnabled used for showing only one hint for freemium + * @property areHintsLimited used for showing only one hint for some subscriptions * @property stepId used for analytic route */ data class NextHintLoadingError( val nextHintId: Long, val remainingHintsIds: List, - val isFreemiumEnabled: Boolean, + val areHintsLimited: Boolean, val stepId: Long ) : Message @@ -205,13 +205,13 @@ object StepQuizHintsFeature { * * @property nextHintId hint ID to load hint details * @property remainingHintsIds next hints ids to be displayed - * @property isFreemiumEnabled used for showing only one hint for freemium + * @property areHintsLimited used for showing only one hint for some subscriptions * @property stepId used for analytic route */ data class FetchNextHint( val nextHintId: Long, val remainingHintsIds: List, - val isFreemiumEnabled: Boolean, + val areHintsLimited: Boolean, val stepId: Long ) : Action diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_hints/presentation/StepQuizHintsReducer.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_hints/presentation/StepQuizHintsReducer.kt index d877d1961c..c9a662ed13 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_hints/presentation/StepQuizHintsReducer.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_hints/presentation/StepQuizHintsReducer.kt @@ -26,7 +26,7 @@ class StepQuizHintsReducer(private val stepRoute: StepRoute) : StateReducer { StepQuizHintsFeature.ViewState.HintState.REACT_TO_HINT } - state.hintHasReaction && state.hintsIds.isNotEmpty() && !state.isFreemiumEnabled -> { + state.hintHasReaction && state.hintsIds.isNotEmpty() && !state.areHintsLimited -> { StepQuizHintsFeature.ViewState.HintState.SEE_NEXT_HINT } else -> StepQuizHintsFeature.ViewState.HintState.LAST_HINT diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/subscriptions/cache/CurrentSubscriptionStateHolderImpl.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/subscriptions/cache/CurrentSubscriptionStateHolderImpl.kt new file mode 100644 index 0000000000..5ecc19dfae --- /dev/null +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/subscriptions/cache/CurrentSubscriptionStateHolderImpl.kt @@ -0,0 +1,33 @@ +package org.hyperskill.app.subscriptions.cache + +import com.russhwolf.settings.Settings +import com.russhwolf.settings.contains +import kotlinx.serialization.json.Json +import org.hyperskill.app.subscriptions.data.source.CurrentSubscriptionStateHolder +import org.hyperskill.app.subscriptions.domain.model.Subscription + +internal class CurrentSubscriptionStateHolderImpl( + private val json: Json, + private val settings: Settings +) : CurrentSubscriptionStateHolder { + override suspend fun getState(): Subscription? = + if (settings.contains(SubscriptionCacheKeys.CURRENT_SUBSCRIPTION)) { + json.decodeFromString( + Subscription.serializer(), + settings.getString(SubscriptionCacheKeys.CURRENT_SUBSCRIPTION) + ) + } else { + null + } + + override suspend fun setState(newState: Subscription) { + settings.putString( + SubscriptionCacheKeys.CURRENT_SUBSCRIPTION, + json.encodeToString(Subscription.serializer(), newState) + ) + } + + override fun resetState() { + settings.remove(SubscriptionCacheKeys.CURRENT_SUBSCRIPTION) + } +} \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/subscriptions/cache/SubscriptionCacheKeys.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/subscriptions/cache/SubscriptionCacheKeys.kt new file mode 100644 index 0000000000..e1a57b4cef --- /dev/null +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/subscriptions/cache/SubscriptionCacheKeys.kt @@ -0,0 +1,5 @@ +package org.hyperskill.app.subscriptions.cache + +internal object SubscriptionCacheKeys { + const val CURRENT_SUBSCRIPTION = "current_subscription" +} \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/subscriptions/data/repository/CurrentSubscriptionStateRepositoryImpl.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/subscriptions/data/repository/CurrentSubscriptionStateRepositoryImpl.kt index d1370eb2c5..026de22c60 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/subscriptions/data/repository/CurrentSubscriptionStateRepositoryImpl.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/subscriptions/data/repository/CurrentSubscriptionStateRepositoryImpl.kt @@ -1,12 +1,14 @@ package org.hyperskill.app.subscriptions.data.repository import org.hyperskill.app.core.data.repository.BaseStateRepository +import org.hyperskill.app.subscriptions.data.source.CurrentSubscriptionStateHolder import org.hyperskill.app.subscriptions.data.source.SubscriptionsRemoteDataSource import org.hyperskill.app.subscriptions.domain.model.Subscription import org.hyperskill.app.subscriptions.domain.repository.CurrentSubscriptionStateRepository -class CurrentSubscriptionStateRepositoryImpl( - private val subscriptionsRemoteDataSource: SubscriptionsRemoteDataSource +internal class CurrentSubscriptionStateRepositoryImpl( + private val subscriptionsRemoteDataSource: SubscriptionsRemoteDataSource, + override val stateHolder: CurrentSubscriptionStateHolder ) : CurrentSubscriptionStateRepository, BaseStateRepository() { override suspend fun loadState(): Result = subscriptionsRemoteDataSource.getCurrentSubscription() diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/subscriptions/data/repository/SubscriptionsRepositoryImpl.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/subscriptions/data/repository/SubscriptionsRepositoryImpl.kt new file mode 100644 index 0000000000..7ce4f51a20 --- /dev/null +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/subscriptions/data/repository/SubscriptionsRepositoryImpl.kt @@ -0,0 +1,12 @@ +package org.hyperskill.app.subscriptions.data.repository + +import org.hyperskill.app.subscriptions.data.source.SubscriptionsRemoteDataSource +import org.hyperskill.app.subscriptions.domain.model.Subscription +import org.hyperskill.app.subscriptions.domain.repository.SubscriptionsRepository + +internal class SubscriptionsRepositoryImpl( + private val subscriptionsRemoteDataSource: SubscriptionsRemoteDataSource +) : SubscriptionsRepository { + override suspend fun syncSubscription(): Result = + subscriptionsRemoteDataSource.syncSubscription() +} \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/subscriptions/data/source/CurrentSubscriptionStateHolder.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/subscriptions/data/source/CurrentSubscriptionStateHolder.kt new file mode 100644 index 0000000000..8b0c7fd6fe --- /dev/null +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/subscriptions/data/source/CurrentSubscriptionStateHolder.kt @@ -0,0 +1,6 @@ +package org.hyperskill.app.subscriptions.data.source + +import org.hyperskill.app.core.domain.repository.StateHolder +import org.hyperskill.app.subscriptions.domain.model.Subscription + +internal interface CurrentSubscriptionStateHolder : StateHolder \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/subscriptions/data/source/SubscriptionsRemoteDataSource.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/subscriptions/data/source/SubscriptionsRemoteDataSource.kt index efb33563fd..ef81b9bc07 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/subscriptions/data/source/SubscriptionsRemoteDataSource.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/subscriptions/data/source/SubscriptionsRemoteDataSource.kt @@ -4,4 +4,6 @@ import org.hyperskill.app.subscriptions.domain.model.Subscription interface SubscriptionsRemoteDataSource { suspend fun getCurrentSubscription(): Result + + suspend fun syncSubscription(): Result } \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/subscriptions/domain/interactor/SubscriptionsInteractor.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/subscriptions/domain/interactor/SubscriptionsInteractor.kt new file mode 100644 index 0000000000..b8e220610e --- /dev/null +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/subscriptions/domain/interactor/SubscriptionsInteractor.kt @@ -0,0 +1,118 @@ +package org.hyperskill.app.subscriptions.domain.interactor + +import co.touchlab.kermit.Logger +import kotlin.time.DurationUnit +import kotlin.time.toDuration +import kotlinx.coroutines.Job +import kotlinx.coroutines.coroutineScope +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch +import kotlinx.datetime.Clock +import kotlinx.datetime.Instant +import kotlinx.datetime.TimeZone +import kotlinx.datetime.toInstant +import kotlinx.datetime.toLocalDateTime +import org.hyperskill.app.auth.domain.interactor.AuthInteractor +import org.hyperskill.app.core.domain.repository.updateState +import org.hyperskill.app.profile.domain.model.isFreemiumIncreaseLimitsForFirstStepCompletionEnabled +import org.hyperskill.app.profile.domain.repository.CurrentProfileStateRepository +import org.hyperskill.app.subscriptions.domain.model.Subscription +import org.hyperskill.app.subscriptions.domain.model.SubscriptionType +import org.hyperskill.app.subscriptions.domain.model.areProblemsLimited +import org.hyperskill.app.subscriptions.domain.model.isActive +import org.hyperskill.app.subscriptions.domain.repository.CurrentSubscriptionStateRepository + +class SubscriptionsInteractor( + private val currentSubscriptionStateRepository: CurrentSubscriptionStateRepository, + private val currentProfileStateRepository: CurrentProfileStateRepository, + private val authInteractor: AuthInteractor, + logger: Logger +) { + companion object { + private const val LOG_TAG = "SubscriptionsInteractor" + private const val SOLVING_FIRST_STEP_ADDITIONAL_LIMIT_VALUE = 10 + } + + private val logger: Logger = logger.withTag(LOG_TAG) + + private var refreshMobileOnlySubscriptionJob: Job? = null + + suspend fun onStepSolved() { + currentSubscriptionStateRepository.updateState { subscription -> + if (subscription.areProblemsLimited) { + subscription.copy(stepsLimitLeft = subscription.stepsLimitLeft?.dec()) + } else { + subscription + } + } + currentProfileStateRepository.getState().onSuccess { currentProfile -> + if (currentProfile.features.isFreemiumIncreaseLimitsForFirstStepCompletionEnabled && + currentProfile.gamification.passedProblems == 0 + ) { + currentSubscriptionStateRepository.updateState { + it.copy( + stepsLimitTotal = it.stepsLimitTotal?.plus(SOLVING_FIRST_STEP_ADDITIONAL_LIMIT_VALUE), + stepsLimitLeft = it.stepsLimitLeft?.plus(SOLVING_FIRST_STEP_ADDITIONAL_LIMIT_VALUE) + ) + } + currentProfileStateRepository.updateState { + it.copy(gamification = it.gamification.copy(passedProblems = it.gamification.passedProblems + 1)) + } + } + } + } + + suspend fun refreshSubscriptionOnExpirationIfNeeded(subscription: Subscription) { + cancelSubscriptionRefresh() + + val isActiveMobileOnlySubscription = + subscription.type == SubscriptionType.MOBILE_ONLY && subscription.isActive + + if (isActiveMobileOnlySubscription && subscription.validTill != null) { + coroutineScope { + refreshMobileOnlySubscriptionJob = launch { + refreshMobileOnlySubscriptionOnExpiration(subscription.validTill) + } + refreshMobileOnlySubscriptionJob?.invokeOnCompletion { + refreshMobileOnlySubscriptionJob = null + } + } + } + } + + private suspend fun refreshMobileOnlySubscriptionOnExpiration( + subscriptionValidTill: Instant + ) { + val nowByUTC = Clock.System.now() + .toLocalDateTime(TimeZone.UTC) + .toInstant(TimeZone.UTC) + + // ALTAPPS-1155: Add one minute to wait until the subscription is synced on the backend + val delayDuration = subscriptionValidTill - nowByUTC + 1.toDuration(DurationUnit.MINUTES) + logger.d { "Wait ${delayDuration.inWholeSeconds} seconds for subscription expiration to refresh it" } + + delay(delayDuration) + if (isUserAuthorized()) { + currentSubscriptionStateRepository + .getState(forceUpdate = true) + .onSuccess { freshSubscription -> + logger.d { + """Subscription successfully refreshed. + Type=${freshSubscription.type},status=${freshSubscription.status} + """.trimMargin() + } + } + .onFailure { e -> + logger.e(e) { "Failed to refresh subscription" } + } + } + } + + fun cancelSubscriptionRefresh() { + refreshMobileOnlySubscriptionJob?.cancel() + refreshMobileOnlySubscriptionJob = null + } + + private suspend fun isUserAuthorized(): Boolean = + authInteractor.isAuthorized().getOrDefault(false) +} \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/subscriptions/domain/model/Subscription.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/subscriptions/domain/model/Subscription.kt index d36a64e300..45bc7f2501 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/subscriptions/domain/model/Subscription.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/subscriptions/domain/model/Subscription.kt @@ -1,23 +1,66 @@ package org.hyperskill.app.subscriptions.domain.model +import kotlinx.datetime.Clock import kotlinx.datetime.Instant +import kotlinx.datetime.TimeZone +import kotlinx.datetime.toInstant +import kotlinx.datetime.toLocalDateTime import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable +import org.hyperskill.app.subscriptions.cache.CurrentSubscriptionStateHolderImpl /** + * Represents a user subscription. + * + * Warning! + * This model is stored in the cache. + * Adding new field or modifying old ones, + * check that all fields will be deserialized from cache without an error. + * All the new optional fields must have default values. + * @see [CurrentSubscriptionStateHolderImpl] + * * If the [stepsLimitResetTime] is null, then the user doesn't have submissions yet */ @Serializable data class Subscription( @SerialName("type") val type: SubscriptionType = SubscriptionType.UNKNOWN, + val status: SubscriptionStatus = SubscriptionStatus.ACTIVE, @SerialName("steps_limit_total") - val stepsLimitTotal: Int?, + val stepsLimitTotal: Int? = null, @SerialName("steps_limit_left") - val stepsLimitLeft: Int?, + val stepsLimitLeft: Int? = null, @SerialName("steps_limit_reset_time") - val stepsLimitResetTime: Instant? + val stepsLimitResetTime: Instant? = null, + @SerialName("valid_till") + val validTill: Instant? = null ) -val Subscription.isFreemium: Boolean - get() = type == SubscriptionType.FREEMIUM \ No newline at end of file +internal val Subscription.areProblemsLimited: Boolean + get() = when (type) { + SubscriptionType.MOBILE_ONLY -> type.areProblemsLimited || status != SubscriptionStatus.ACTIVE + else -> type.areProblemsLimited + } + +internal val Subscription.isProblemLimitReached: Boolean + get() = areProblemsLimited && stepsLimitLeft == 0 + +internal val Subscription.isFreemium: Boolean + get() = type == SubscriptionType.FREEMIUM || + type == SubscriptionType.MOBILE_ONLY && status != SubscriptionStatus.ACTIVE + +internal val Subscription.isActive: Boolean + get() = status == SubscriptionStatus.ACTIVE + +internal val Subscription.isExpired: Boolean + get() = status == SubscriptionStatus.EXPIRED + +internal fun Subscription.isValidTillPassed(): Boolean = + if (validTill != null) { + val nowByUTC = Clock.System.now() + .toLocalDateTime(TimeZone.UTC) + .toInstant(TimeZone.UTC) + validTill < nowByUTC + } else { + false + } \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/subscriptions/domain/model/SubscriptionStatus.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/subscriptions/domain/model/SubscriptionStatus.kt new file mode 100644 index 0000000000..265bdb4730 --- /dev/null +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/subscriptions/domain/model/SubscriptionStatus.kt @@ -0,0 +1,16 @@ +package org.hyperskill.app.subscriptions.domain.model + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +enum class SubscriptionStatus { + @SerialName("pending") + PENDING, + @SerialName("not_active") + NOT_ACTIVE, + @SerialName("active") + ACTIVE, + @SerialName("expired") + EXPIRED +} \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/subscriptions/domain/model/SubscriptionType.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/subscriptions/domain/model/SubscriptionType.kt index 69190768f6..28d364d28e 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/subscriptions/domain/model/SubscriptionType.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/subscriptions/domain/model/SubscriptionType.kt @@ -4,15 +4,21 @@ import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable @Serializable -enum class SubscriptionType { +enum class SubscriptionType( + val isProjectSelectionEnabled: Boolean = false, + val isProjectInfoAvailable: Boolean = true, + val isCertificateAvailable: Boolean = true, + val areHintsLimited: Boolean = false, + val areProblemsLimited: Boolean = false +) { @SerialName("personal") - PERSONAL, + PERSONAL(isProjectSelectionEnabled = true), @SerialName("commercial") COMMERCIAL, @SerialName("team member") TEAM_MEMBER, @SerialName("trial") - TRIAL, + TRIAL(isProjectSelectionEnabled = true), @SerialName("content trial") CONTENT_TRIAL, @SerialName("organization trial") @@ -27,10 +33,23 @@ enum class SubscriptionType { JETBRAINS_TEAM, @SerialName("free") FREE, + @SerialName("freemium") - FREEMIUM, + FREEMIUM( + isCertificateAvailable = false, + isProjectInfoAvailable = false, + areHintsLimited = true, + areProblemsLimited = true + ), + @SerialName("mobile only") + MOBILE_ONLY( + isCertificateAvailable = false, + isProjectInfoAvailable = false, + areHintsLimited = true + ), + @SerialName("premium") - PREMIUM, + PREMIUM(isProjectSelectionEnabled = true), @SerialName("unknown") UNKNOWN diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/subscriptions/domain/repository/SubscriptionsRepository.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/subscriptions/domain/repository/SubscriptionsRepository.kt new file mode 100644 index 0000000000..de53d163c3 --- /dev/null +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/subscriptions/domain/repository/SubscriptionsRepository.kt @@ -0,0 +1,7 @@ +package org.hyperskill.app.subscriptions.domain.repository + +import org.hyperskill.app.subscriptions.domain.model.Subscription + +interface SubscriptionsRepository { + suspend fun syncSubscription(): Result +} \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/subscriptions/injection/SubscriptionsDataComponent.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/subscriptions/injection/SubscriptionsDataComponent.kt new file mode 100644 index 0000000000..ee6c09db8e --- /dev/null +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/subscriptions/injection/SubscriptionsDataComponent.kt @@ -0,0 +1,9 @@ +package org.hyperskill.app.subscriptions.injection + +import org.hyperskill.app.subscriptions.domain.interactor.SubscriptionsInteractor +import org.hyperskill.app.subscriptions.domain.repository.SubscriptionsRepository + +interface SubscriptionsDataComponent { + val subscriptionsRepository: SubscriptionsRepository + val subscriptionsInteractor: SubscriptionsInteractor +} \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/subscriptions/injection/SubscriptionsDataComponentImpl.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/subscriptions/injection/SubscriptionsDataComponentImpl.kt new file mode 100644 index 0000000000..467471ae67 --- /dev/null +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/subscriptions/injection/SubscriptionsDataComponentImpl.kt @@ -0,0 +1,43 @@ +package org.hyperskill.app.subscriptions.injection + +import co.touchlab.kermit.Logger +import org.hyperskill.app.auth.domain.interactor.AuthInteractor +import org.hyperskill.app.core.injection.AppGraph +import org.hyperskill.app.profile.domain.repository.CurrentProfileStateRepository +import org.hyperskill.app.subscriptions.data.repository.SubscriptionsRepositoryImpl +import org.hyperskill.app.subscriptions.domain.interactor.SubscriptionsInteractor +import org.hyperskill.app.subscriptions.domain.repository.CurrentSubscriptionStateRepository +import org.hyperskill.app.subscriptions.domain.repository.SubscriptionsRepository +import org.hyperskill.app.subscriptions.remote.SubscriptionsRemoteDataSourceImpl + +class SubscriptionsDataComponentImpl( + appGraph: AppGraph +) : SubscriptionsDataComponent { + + private val subscriptionsRemoteDataSource = + SubscriptionsRemoteDataSourceImpl(appGraph.networkComponent.authorizedHttpClient) + + private val currentSubscriptionStateRepository: CurrentSubscriptionStateRepository = + appGraph.stateRepositoriesComponent.currentSubscriptionStateRepository + + private val currentProfileStateRepository: CurrentProfileStateRepository = + appGraph.profileDataComponent.currentProfileStateRepository + + private val authInteractor: AuthInteractor = + appGraph.authComponent.authInteractor + + private val logger: Logger = + appGraph.loggerComponent.logger + + override val subscriptionsRepository: SubscriptionsRepository = + SubscriptionsRepositoryImpl(subscriptionsRemoteDataSource) + + override val subscriptionsInteractor: SubscriptionsInteractor by lazy { + SubscriptionsInteractor( + currentSubscriptionStateRepository = currentSubscriptionStateRepository, + currentProfileStateRepository = currentProfileStateRepository, + authInteractor = authInteractor, + logger = logger + ) + } +} \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/subscriptions/remote/SubscriptionsRemoteDataSourceImpl.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/subscriptions/remote/SubscriptionsRemoteDataSourceImpl.kt index 3bc56bfec9..ee4215f4b7 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/subscriptions/remote/SubscriptionsRemoteDataSourceImpl.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/subscriptions/remote/SubscriptionsRemoteDataSourceImpl.kt @@ -3,6 +3,7 @@ package org.hyperskill.app.subscriptions.remote import io.ktor.client.HttpClient import io.ktor.client.call.body import io.ktor.client.request.get +import io.ktor.client.request.post import io.ktor.http.ContentType import io.ktor.http.contentType import org.hyperskill.app.subscriptions.data.source.SubscriptionsRemoteDataSource @@ -13,9 +14,16 @@ class SubscriptionsRemoteDataSourceImpl( private val httpClient: HttpClient ) : SubscriptionsRemoteDataSource { override suspend fun getCurrentSubscription(): Result = - kotlin.runCatching { + runCatching { httpClient.get("/api/subscriptions/current") { contentType(ContentType.Application.Json) }.body().subscriptions.first() } + + override suspend fun syncSubscription(): Result = + runCatching { + httpClient.post("/api/subscription-infos/refresh-mobile") { + contentType(ContentType.Application.Json) + }.body().subscriptions.first() + } } \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/track_selection/details/presentation/TrackSelectionDetailsFeature.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/track_selection/details/presentation/TrackSelectionDetailsFeature.kt index 9d539ce01a..1e774666f7 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/track_selection/details/presentation/TrackSelectionDetailsFeature.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/track_selection/details/presentation/TrackSelectionDetailsFeature.kt @@ -22,10 +22,7 @@ object TrackSelectionDetailsFeature { val subscriptionType: SubscriptionType, val profile: Profile, val providers: List - ) : ContentState { - val isFreemiumEnabled: Boolean - get() = subscriptionType == SubscriptionType.FREEMIUM - } + ) : ContentState object NetworkError : ContentState } diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/track_selection/details/presentation/TrackSelectionDetailsReducer.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/track_selection/details/presentation/TrackSelectionDetailsReducer.kt index 51193e3233..cf1464f251 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/track_selection/details/presentation/TrackSelectionDetailsReducer.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/track_selection/details/presentation/TrackSelectionDetailsReducer.kt @@ -1,6 +1,5 @@ package org.hyperskill.app.track_selection.details.presentation -import org.hyperskill.app.subscriptions.domain.model.SubscriptionType import org.hyperskill.app.track.domain.model.getAllProjects import org.hyperskill.app.track_selection.details.domain.analytic.TrackSelectionDetailsClickedRetryContentLoadingHyperskillAnalyticEvent import org.hyperskill.app.track_selection.details.domain.analytic.TrackSelectionDetailsSelectButtonClickedHyperskillAnalyticEvent @@ -135,15 +134,12 @@ internal class TrackSelectionDetailsReducer : StateReducer { - val trackRelatedProjects = - state.trackWithProgress.track.getAllProjects(state.contentState.profile.isBeta) - trackRelatedProjects.isNotEmpty() - } - else -> false + return if (state.contentState.subscriptionType.isProjectSelectionEnabled) { + val trackRelatedProjects = + state.trackWithProgress.track.getAllProjects(state.contentState.profile.isBeta) + trackRelatedProjects.isNotEmpty() + } else { + false } } diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/track_selection/details/view/TrackSelectionDetailsViewStateMapper.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/track_selection/details/view/TrackSelectionDetailsViewStateMapper.kt index 6feed00ce2..2ada256e48 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/track_selection/details/view/TrackSelectionDetailsViewStateMapper.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/track_selection/details/view/TrackSelectionDetailsViewStateMapper.kt @@ -32,8 +32,12 @@ internal class TrackSelectionDetailsViewStateMapper( formattedRating = formatRating(trackProgress.averageRating()), formattedTimeToComplete = formatTimeToComplete(track.secondsToComplete), formattedTopicsCount = formatTopicsCount(track.totalTopicsCount), - formattedProjectsCount = formatProjectsCount(contentState.isFreemiumEnabled, track.projects.size), - isCertificateAvailable = !contentState.isFreemiumEnabled && track.canIssueCertificate, + formattedProjectsCount = formatProjectsCount( + isProjectCountEnabled = contentState.subscriptionType.isProjectInfoAvailable, + projectsCount = track.projects.size + ), + isCertificateAvailable = contentState.subscriptionType.isCertificateAvailable && + track.canIssueCertificate, mainProvider = contentState.providers .firstOrNull { it.id == track.providerId } ?.let { mainProvider -> @@ -76,10 +80,10 @@ internal class TrackSelectionDetailsViewStateMapper( ) private fun formatProjectsCount( - isFreemiumEnabled: Boolean, + isProjectCountEnabled: Boolean, projectsCount: Int ): String? = - if (!isFreemiumEnabled) { + if (isProjectCountEnabled) { resourceProvider.getString( SharedResources.strings.track_selection_details_projects_text_template, resourceProvider.getQuantityString(SharedResources.plurals.projects, projectsCount, projectsCount) diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/welcome_onboarding/injection/WelcomeOnboardingComponentImpl.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/welcome_onboarding/injection/WelcomeOnboardingComponentImpl.kt index 622a014690..756d2a0626 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/welcome_onboarding/injection/WelcomeOnboardingComponentImpl.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/welcome_onboarding/injection/WelcomeOnboardingComponentImpl.kt @@ -11,12 +11,14 @@ internal class WelcomeOnboardingComponentImpl( ) : WelcomeOnboardingComponent { override val welcomeOnboardingReducer: WelcomeOnboardingReducer get() = WelcomeOnboardingReducer( + isSubscriptionPurchaseEnabled = appGraph.commonComponent.platform.isSubscriptionPurchaseEnabled, isUsersQuestionnaireOnboardingEnabled = appGraph.commonComponent.platform.platformType == PlatformType.IOS ) override val welcomeOnboardingActionDispatcher: WelcomeOnboardingActionDispatcher get() = WelcomeOnboardingActionDispatcher( config = ActionDispatcherOptions(), - onboardingInteractor = appGraph.buildOnboardingDataComponent().onboardingInteractor + onboardingInteractor = appGraph.buildOnboardingDataComponent().onboardingInteractor, + currentSubscriptionStateRepository = appGraph.stateRepositoriesComponent.currentSubscriptionStateRepository ) } \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/welcome_onboarding/presentation/WelcomeOnboardingActionDispatcher.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/welcome_onboarding/presentation/WelcomeOnboardingActionDispatcher.kt index 4fe055e31d..8717cbe68d 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/welcome_onboarding/presentation/WelcomeOnboardingActionDispatcher.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/welcome_onboarding/presentation/WelcomeOnboardingActionDispatcher.kt @@ -2,6 +2,7 @@ package org.hyperskill.app.welcome_onboarding.presentation import org.hyperskill.app.core.presentation.ActionDispatcherOptions import org.hyperskill.app.onboarding.domain.interactor.OnboardingInteractor +import org.hyperskill.app.subscriptions.domain.repository.CurrentSubscriptionStateRepository import org.hyperskill.app.welcome_onboarding.presentation.WelcomeOnboardingFeature.Action import org.hyperskill.app.welcome_onboarding.presentation.WelcomeOnboardingFeature.InternalAction import org.hyperskill.app.welcome_onboarding.presentation.WelcomeOnboardingFeature.InternalMessage @@ -10,7 +11,8 @@ import ru.nobird.app.presentation.redux.dispatcher.CoroutineActionDispatcher class WelcomeOnboardingActionDispatcher( config: ActionDispatcherOptions, - private val onboardingInteractor: OnboardingInteractor + private val onboardingInteractor: OnboardingInteractor, + private val currentSubscriptionStateRepository: CurrentSubscriptionStateRepository ) : CoroutineActionDispatcher(config.createConfig()) { override suspend fun doSuspendableAction(action: Action) { when (action) { @@ -21,6 +23,14 @@ class WelcomeOnboardingActionDispatcher( ) ) } + InternalAction.FetchSubscription -> { + currentSubscriptionStateRepository.getState() + .fold( + onSuccess = InternalMessage::FetchSubscriptionSuccess, + onFailure = { InternalMessage.FetchSubscriptionError } + ) + .let(::onNewMessage) + } else -> { // no op } diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/welcome_onboarding/presentation/WelcomeOnboardingFeature.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/welcome_onboarding/presentation/WelcomeOnboardingFeature.kt index 61c980a988..b552b97557 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/welcome_onboarding/presentation/WelcomeOnboardingFeature.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/welcome_onboarding/presentation/WelcomeOnboardingFeature.kt @@ -1,8 +1,10 @@ package org.hyperskill.app.welcome_onboarding.presentation import kotlinx.serialization.Serializable +import org.hyperskill.app.paywall.domain.model.PaywallTransitionSource import org.hyperskill.app.profile.domain.model.Profile import org.hyperskill.app.step.domain.model.StepRoute +import org.hyperskill.app.subscriptions.domain.model.Subscription import org.hyperskill.app.welcome_onboarding.presentation.WelcomeOnboardingFeature.Action object WelcomeOnboardingFeature { @@ -12,6 +14,8 @@ object WelcomeOnboardingFeature { sealed interface Message { object NotificationOnboardingCompleted : Message + object PaywallCompleted : Message + object UsersQuestionnaireOnboardingCompleted : Message data class FirstProblemOnboardingCompleted(val firstProblemStepRoute: StepRoute?) : Message @@ -26,6 +30,10 @@ object WelcomeOnboardingFeature { data class FirstProblemOnboardingDataFetched( val wasFirstProblemOnboardingShown: Boolean ) : InternalMessage + + data class FetchSubscriptionSuccess(val subscription: Subscription) : InternalMessage + + object FetchSubscriptionError : InternalMessage } sealed interface Action { @@ -40,12 +48,15 @@ object WelcomeOnboardingFeature { data class FirstProblemOnboardingScreen(val isNewUserMode: Boolean) : NavigateTo data class StudyPlanWithStep(val stepRoute: StepRoute) : NavigateTo + + data class Paywall(val paywallTransitionSource: PaywallTransitionSource) : NavigateTo } } } internal sealed interface InternalAction : Action { object FetchFirstProblemOnboardingData : InternalAction + object FetchSubscription : InternalAction } } diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/welcome_onboarding/presentation/WelcomeOnboardingReducer.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/welcome_onboarding/presentation/WelcomeOnboardingReducer.kt index 137c4572f5..c54d0e1ef2 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/welcome_onboarding/presentation/WelcomeOnboardingReducer.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/welcome_onboarding/presentation/WelcomeOnboardingReducer.kt @@ -1,6 +1,9 @@ package org.hyperskill.app.welcome_onboarding.presentation +import org.hyperskill.app.paywall.domain.model.PaywallTransitionSource +import org.hyperskill.app.profile.domain.model.isMobileOnlySubscriptionEnabled import org.hyperskill.app.profile.domain.model.isNewUser +import org.hyperskill.app.subscriptions.domain.model.isFreemium import org.hyperskill.app.welcome_onboarding.presentation.WelcomeOnboardingFeature.Action import org.hyperskill.app.welcome_onboarding.presentation.WelcomeOnboardingFeature.Action.ViewAction import org.hyperskill.app.welcome_onboarding.presentation.WelcomeOnboardingFeature.InternalAction @@ -12,6 +15,7 @@ import ru.nobird.app.presentation.redux.reducer.StateReducer private typealias ReducerResult = Pair> class WelcomeOnboardingReducer( + private val isSubscriptionPurchaseEnabled: Boolean, private val isUsersQuestionnaireOnboardingEnabled: Boolean ) : StateReducer { override fun reduce(state: State, message: Message): ReducerResult = @@ -25,6 +29,13 @@ class WelcomeOnboardingReducer( Message.UsersQuestionnaireOnboardingCompleted -> handleUsersQuestionnaireOnboardingCompleted(state) + is InternalMessage.FetchSubscriptionSuccess -> + handleFetchSubscriptionSuccess(state, message) + is InternalMessage.FetchSubscriptionError -> + handleFetchSubscriptionError(state) + is Message.PaywallCompleted -> + handlePaywallCompleted(state) + is InternalMessage.FirstProblemOnboardingDataFetched -> handleFirstProblemOnboardingDataFetched(state, message) is Message.FirstProblemOnboardingCompleted -> @@ -54,6 +65,26 @@ class WelcomeOnboardingReducer( private fun handleUsersQuestionnaireOnboardingCompleted( state: State ): ReducerResult = + if (isSubscriptionPurchaseEnabled && state.profile?.features?.isMobileOnlySubscriptionEnabled == true) { + state to setOf(InternalAction.FetchSubscription) + } else { + handlePaywallCompleted(state) + } + + private fun handleFetchSubscriptionSuccess( + state: State, + message: InternalMessage.FetchSubscriptionSuccess + ): ReducerResult = + if (message.subscription.isFreemium) { + state to setOf(ViewAction.NavigateTo.Paywall(PaywallTransitionSource.LOGIN)) + } else { + handlePaywallCompleted(state) + } + + private fun handleFetchSubscriptionError(state: State): ReducerResult = + handlePaywallCompleted(state) + + private fun handlePaywallCompleted(state: State): ReducerResult = if (state.profile?.isNewUser == false) { state to setOf(InternalAction.FetchFirstProblemOnboardingData) } else { diff --git a/shared/src/commonMain/resources/MR/base/strings.xml b/shared/src/commonMain/resources/MR/base/strings.xml index e276768cf2..d3366aa5aa 100644 --- a/shared/src/commonMain/resources/MR/base/strings.xml +++ b/shared/src/commonMain/resources/MR/base/strings.xml @@ -263,6 +263,10 @@ https://support.hyperskill.org/hc/en-us/requests/new Rate us in the App Store https://apps.apple.com/app/id1637230833?action=write-review + Subscription + Mobile only + Try Mobile only plan for %s + Details Rate us on the Play Store https://play.google.com/store/apps/details?id=org.hyperskill.app.android @@ -424,9 +428,11 @@ Learn next - + You\'ve reached your daily limit You\'ve solved %d problems today. Great job! Tomorrow new problems will be available to you. + You\'ve solved %d problems today. Great job! Unlock unlimited problems with Mobile only plan. + Unlock unlimited problems %d/%d problems left @@ -627,4 +633,33 @@ Wow! You\'ve reached level %d You\'ve earned the %s badge by reaching level %d! Amazing job! + + + Access to all tracks + Unlimited problems per day in the app + 1 hint per problem + + + Solve unlimited problems with Mobile only plan + Subscribe for %s/month + Continue with daily limits + Oops! We were unable to load the subscriptions data. + Subscription + Purchase failed. Please try again. + Paid functionality will become available to you shortly + Paid functionality will become available after success payment + The app is updating. Please wait a moment + Hyperskill Terms of Service and Privacy Policy + https://hi.hyperskill.org/terms + + + Subscription + Your current plan: + Hyperskill Mobile only + Valid until %s + Plan details: + Please be aware that with the Mobile оnly plan on hyperskill.org, there is a limit on the number of problems you can solve per day. + Manage subscription + Renew subscription + \ No newline at end of file diff --git a/shared/src/commonTest/kotlin/org/hyperskill/StepQuizHintsViewStateMapperTest.kt b/shared/src/commonTest/kotlin/org/hyperskill/StepQuizHintsViewStateMapperTest.kt index df85ca8a69..2a1610cd56 100644 --- a/shared/src/commonTest/kotlin/org/hyperskill/StepQuizHintsViewStateMapperTest.kt +++ b/shared/src/commonTest/kotlin/org/hyperskill/StepQuizHintsViewStateMapperTest.kt @@ -12,7 +12,7 @@ class StepQuizHintsViewStateMapperTest { hintsIds = emptyList(), currentHint = null, hintHasReaction = false, - isFreemiumEnabled = false, + areHintsLimited = false, stepId = 0L ) assertIs(StepQuizHintsViewStateMapper.mapState(featureState)) diff --git a/shared/src/commonTest/kotlin/org/hyperskill/core/view/mapper/date/SharedDateFormatterTest.kt b/shared/src/commonTest/kotlin/org/hyperskill/core/view/mapper/date/SharedDateFormatterTest.kt index 0a372f2778..24a62504ad 100644 --- a/shared/src/commonTest/kotlin/org/hyperskill/core/view/mapper/date/SharedDateFormatterTest.kt +++ b/shared/src/commonTest/kotlin/org/hyperskill/core/view/mapper/date/SharedDateFormatterTest.kt @@ -5,6 +5,9 @@ import kotlin.test.assertEquals import kotlin.time.DurationUnit import kotlin.time.toDuration import kotlinx.datetime.LocalDate +import kotlinx.datetime.LocalDateTime +import kotlinx.datetime.TimeZone +import kotlinx.datetime.toInstant import org.hyperskill.ResourceProviderStub import org.hyperskill.app.core.view.mapper.date.SharedDateFormatter @@ -110,4 +113,15 @@ class SharedDateFormatterTest { val expected = "2 Nov" assertEquals(expected, dateFormatter.formatDayNumericAndMonthShort(given)) } + + @Test + fun `Format subscription valid until`() { + mapOf( + LocalDateTime.parse("2024-01-27T02:00:00") to "January 27, 2024, 02:00", + LocalDateTime.parse("2024-01-27T12:09:00") to "January 27, 2024, 12:09" + ).forEach { (localDate, expected) -> + val actual = localDate.toInstant(TimeZone.UTC) + assertEquals(expected, dateFormatter.formatSubscriptionValidUntil(actual, TimeZone.UTC)) + } + } } \ No newline at end of file diff --git a/shared/src/commonTest/kotlin/org/hyperskill/main/AppFeatureTest.kt b/shared/src/commonTest/kotlin/org/hyperskill/main/AppFeatureTest.kt index 741003a7dd..057677a361 100644 --- a/shared/src/commonTest/kotlin/org/hyperskill/main/AppFeatureTest.kt +++ b/shared/src/commonTest/kotlin/org/hyperskill/main/AppFeatureTest.kt @@ -1,6 +1,7 @@ package org.hyperskill.main import kotlin.test.Test +import kotlin.test.assertContains import kotlin.test.assertTrue import org.hyperskill.ResourceProviderStub import org.hyperskill.app.core.domain.platform.PlatformType @@ -10,27 +11,36 @@ import org.hyperskill.app.notification.click_handling.presentation.NotificationC import org.hyperskill.app.notification.remote.domain.model.PushNotificationCategory import org.hyperskill.app.notification.remote.domain.model.PushNotificationData import org.hyperskill.app.notification.remote.domain.model.PushNotificationType +import org.hyperskill.app.paywall.domain.model.PaywallTransitionSource +import org.hyperskill.app.profile.domain.model.FeatureKeys import org.hyperskill.app.profile.domain.model.Profile import org.hyperskill.app.streak_recovery.presentation.StreakRecoveryFeature import org.hyperskill.app.streak_recovery.presentation.StreakRecoveryReducer +import org.hyperskill.app.subscriptions.domain.model.Subscription +import org.hyperskill.app.subscriptions.domain.model.SubscriptionType import org.hyperskill.app.welcome_onboarding.presentation.WelcomeOnboardingReducer import org.hyperskill.profile.stub +import org.hyperskill.subscriptions.stub class AppFeatureTest { private val appReducer = AppReducer( - StreakRecoveryReducer(resourceProvider = ResourceProviderStub()), - NotificationClickHandlingReducer(), - WelcomeOnboardingReducer(isUsersQuestionnaireOnboardingEnabled = true), - PlatformType.ANDROID + streakRecoveryReducer = StreakRecoveryReducer(resourceProvider = ResourceProviderStub()), + notificationClickHandlingReducer = NotificationClickHandlingReducer(), + welcomeOnboardingReducer = WelcomeOnboardingReducer( + isSubscriptionPurchaseEnabled = true, + isUsersQuestionnaireOnboardingEnabled = true + ), + platformType = PlatformType.ANDROID ) @Test fun `Streak recovery should be initialized only when user is authorized and already selected track`() { val (_, actions) = appReducer.reduce( AppFeature.State.Loading, - AppFeature.Message.UserAccountStatus( - Profile.stub(isGuest = false, trackId = 1), - null + AppFeature.Message.FetchAppStartupConfigSuccess( + profile = Profile.stub(isGuest = false, trackId = 1), + notificationData = null, + subscription = null ) ) assertTrue { @@ -45,7 +55,11 @@ class AppFeatureTest { fun `Streak recovery should NOT be initialized when user is NOT authorized`() { val (_, actions) = appReducer.reduce( AppFeature.State.Loading, - AppFeature.Message.UserAccountStatus(Profile.stub(isGuest = true), null) + AppFeature.Message.FetchAppStartupConfigSuccess( + profile = Profile.stub(isGuest = true), + notificationData = null, + subscription = null + ) ) assertNoStreakRecoveryActions(actions) } @@ -54,9 +68,10 @@ class AppFeatureTest { fun `Streak recovery should NOT be initialized in case of push notification handling`() { val (_, actions) = appReducer.reduce( AppFeature.State.Loading, - AppFeature.Message.UserAccountStatus( - Profile.stub(isGuest = true, trackId = 1), - PushNotificationData( + AppFeature.Message.FetchAppStartupConfigSuccess( + profile = Profile.stub(isGuest = true, trackId = 1), + subscription = null, + notificationData = PushNotificationData( typeString = PushNotificationType.STREAK_NEW.name, categoryString = PushNotificationCategory.CONTINUE_LEARNING.backendName!! ) @@ -72,4 +87,102 @@ class AppFeatureTest { } } } + + @Test + fun `Paywall should be shown for user with freemium subscription on app startup`() { + val (state, actions) = appReducer.reduce( + AppFeature.State.Loading, + AppFeature.Message.FetchAppStartupConfigSuccess( + profile = Profile.stub( + isGuest = false, + trackId = 1, + featuresMap = mapOf(FeatureKeys.MOBILE_ONLY_SUBSCRIPTION to true) + ), + subscription = Subscription.stub(SubscriptionType.FREEMIUM), + notificationData = null + ) + ) + assertContains( + actions, + AppFeature.Action.ViewAction.NavigateTo.StudyPlanWithPaywall(PaywallTransitionSource.APP_BECOMES_ACTIVE) + ) + assertTrue { + state is AppFeature.State.Ready && state.appShowsCount == 1 + } + } + + @Test + fun `Paywall should not be shown for user with non freemium subscription on app startup`() { + SubscriptionType + .values() + .filterNot { it == SubscriptionType.FREEMIUM } + .forEach { subscriptionType -> + val (_, actions) = appReducer.reduce( + AppFeature.State.Loading, + AppFeature.Message.FetchAppStartupConfigSuccess( + profile = Profile.stub( + isGuest = false, + trackId = 1, + featuresMap = mapOf(FeatureKeys.MOBILE_ONLY_SUBSCRIPTION to true) + ), + subscription = Subscription.stub(subscriptionType), + notificationData = null + ) + ) + assertNoPaywallViewAction(actions) + } + } + + @Test + fun `Paywall should be shown for user with freemium subscription every 3 time when app shown`() { + var state: AppFeature.State = AppFeature.State.Ready( + isAuthorized = true, + isMobileLeaderboardsEnabled = false, + subscription = Subscription.stub(SubscriptionType.FREEMIUM), + isMobileOnlySubscriptionEnabled = true + ) + for (i in 1..AppReducer.APP_SHOWS_COUNT_TILL_PAYWALL + 1) { + val (newState, actions) = appReducer.reduce( + state, + AppFeature.Message.AppBecomesActive + ) + state = newState + + if (i % AppReducer.APP_SHOWS_COUNT_TILL_PAYWALL == 0) { + assertContains( + actions, + AppFeature.Action.ViewAction.NavigateTo.Paywall( + PaywallTransitionSource.APP_BECOMES_ACTIVE + ) + ) + } else { + assertNoPaywallViewAction(actions) + } + } + } + + @Test + fun `Paywall should not be shown if mobile only subscription feature is disabled`() { + val (_, actions) = appReducer.reduce( + AppFeature.State.Loading, + AppFeature.Message.FetchAppStartupConfigSuccess( + profile = Profile.stub( + isGuest = false, + trackId = 1, + featuresMap = mapOf(FeatureKeys.MOBILE_ONLY_SUBSCRIPTION to false) + ), + subscription = Subscription.stub(SubscriptionType.FREEMIUM), + notificationData = null + ) + ) + assertNoPaywallViewAction(actions) + } + + private fun assertNoPaywallViewAction(actions: Set) { + assertTrue { + actions.none { + it is AppFeature.Action.ViewAction.NavigateTo.StudyPlanWithPaywall + } + } + } } \ No newline at end of file diff --git a/shared/src/commonTest/kotlin/org/hyperskill/profile/ProfileStub.kt b/shared/src/commonTest/kotlin/org/hyperskill/profile/ProfileStub.kt index 8733cd4ab5..4d1e44fb66 100644 --- a/shared/src/commonTest/kotlin/org/hyperskill/profile/ProfileStub.kt +++ b/shared/src/commonTest/kotlin/org/hyperskill/profile/ProfileStub.kt @@ -8,7 +8,8 @@ fun Profile.Companion.stub( isBeta: Boolean = false, isGuest: Boolean = false, trackId: Long? = null, - projectId: Long? = null + projectId: Long? = null, + featuresMap: Map = emptyMap() ): Profile = Profile( id = id, @@ -35,5 +36,5 @@ fun Profile.Companion.stub( trackTitle = null, projectId = projectId, isBeta = isBeta, - featuresMap = emptyMap() + featuresMap = featuresMap ) \ No newline at end of file diff --git a/shared/src/commonTest/kotlin/org/hyperskill/progress_screen/ProgressScreenTest.kt b/shared/src/commonTest/kotlin/org/hyperskill/progress_screen/ProgressScreenTest.kt index adaf6d82bf..4181c6fabf 100644 --- a/shared/src/commonTest/kotlin/org/hyperskill/progress_screen/ProgressScreenTest.kt +++ b/shared/src/commonTest/kotlin/org/hyperskill/progress_screen/ProgressScreenTest.kt @@ -196,29 +196,33 @@ class ProgressScreenTest { } @Test - fun `User on freemium doesn't see applied topics and graduated projects`() { - val state = ProgressScreenFeature.State( - trackProgressState = ProgressScreenFeature.TrackProgressState.Content( - trackWithProgress = TrackWithProgress.stub(trackId = 1L, projects = listOf(1, 2, 3)), - studyPlan = StudyPlan.stub(), - profile = Profile.stub(), - subscription = Subscription.stub(type = SubscriptionType.FREEMIUM) - ), - projectProgressState = ProgressScreenFeature.ProjectProgressState.Empty, - isTrackProgressRefreshing = false, - isProjectProgressRefreshing = false - ) - - val viewState = viewStateMapper.map(state) + fun `User with freemium or mobile-only subscription doesn't see applied topics and graduated projects`() { + listOf( + SubscriptionType.FREEMIUM, + SubscriptionType.MOBILE_ONLY + ).forEach { subscriptionType -> + val state = ProgressScreenFeature.State( + trackProgressState = ProgressScreenFeature.TrackProgressState.Content( + trackWithProgress = TrackWithProgress.stub(trackId = 1L, projects = listOf(1, 2, 3)), + studyPlan = StudyPlan.stub(), + profile = Profile.stub(), + subscription = Subscription.stub(type = subscriptionType) + ), + projectProgressState = ProgressScreenFeature.ProjectProgressState.Empty, + isTrackProgressRefreshing = false, + isProjectProgressRefreshing = false + ) - val trackProgressContentState = viewState.trackProgressViewState as TrackProgressViewState.Content + val viewState = viewStateMapper.map(state) - assertEquals( - TrackProgressViewState.Content.AppliedTopicsState.Empty, - trackProgressContentState.appliedTopicsState - ) + val trackProgressContentState = viewState.trackProgressViewState as TrackProgressViewState.Content - assertNull(trackProgressContentState.completedGraduateProjectsCount) + assertEquals( + TrackProgressViewState.Content.AppliedTopicsState.Empty, + trackProgressContentState.appliedTopicsState + ) + assertNull(trackProgressContentState.completedGraduateProjectsCount) + } } @Test diff --git a/shared/src/commonTest/kotlin/org/hyperskill/step_quiz/StepQuizTest.kt b/shared/src/commonTest/kotlin/org/hyperskill/step_quiz/StepQuizTest.kt index 123bf74679..b9b7da1d98 100644 --- a/shared/src/commonTest/kotlin/org/hyperskill/step_quiz/StepQuizTest.kt +++ b/shared/src/commonTest/kotlin/org/hyperskill/step_quiz/StepQuizTest.kt @@ -50,6 +50,7 @@ class StepQuizTest { submissionState, isProblemsLimitReached = true, problemsLimitReachedModalText = "", + isSubscriptionPurchaseEnabled = true, problemsOnboardingFlags = ProblemsOnboardingFlags( isParsonsOnboardingShown = false, isFillBlanksInputModeOnboardingShown = false, @@ -95,6 +96,7 @@ class StepQuizTest { submissionState, isProblemsLimitReached = true, problemsLimitReachedModalText = "", + isSubscriptionPurchaseEnabled = true, problemsOnboardingFlags = ProblemsOnboardingFlags( isParsonsOnboardingShown = false, isFillBlanksInputModeOnboardingShown = false, @@ -143,6 +145,7 @@ class StepQuizTest { submissionState, isProblemsLimitReached = false, problemsLimitReachedModalText = null, + isSubscriptionPurchaseEnabled = true, problemsOnboardingFlags = ProblemsOnboardingFlags( isParsonsOnboardingShown = false, isFillBlanksInputModeOnboardingShown = false, @@ -205,6 +208,7 @@ class StepQuizTest { submissionState, isProblemsLimitReached = false, problemsLimitReachedModalText = null, + isSubscriptionPurchaseEnabled = true, problemsOnboardingFlags = ProblemsOnboardingFlags( isParsonsOnboardingShown = false, isFillBlanksInputModeOnboardingShown = false, diff --git a/shared/src/commonTest/kotlin/org/hyperskill/subscriptions/SubscriptionSerializationTest.kt b/shared/src/commonTest/kotlin/org/hyperskill/subscriptions/SubscriptionSerializationTest.kt new file mode 100644 index 0000000000..0f99a7fb59 --- /dev/null +++ b/shared/src/commonTest/kotlin/org/hyperskill/subscriptions/SubscriptionSerializationTest.kt @@ -0,0 +1,40 @@ +package org.hyperskill.subscriptions + +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlinx.datetime.Instant +import org.hyperskill.app.network.injection.NetworkModule +import org.hyperskill.app.subscriptions.domain.model.Subscription +import org.hyperskill.app.subscriptions.domain.model.SubscriptionStatus +import org.hyperskill.app.subscriptions.domain.model.SubscriptionType + +class SubscriptionSerializationTest { + companion object { + private const val TEST_JSON = """ + { + "type": "freemium", + "steps_limit_total": 10, + "steps_limit_left": 9, + "steps_limit_reset_time": "2022-11-16T12:19:14.782644Z", + "valid_till": "2099-01-01T01:00:00Z" + } + """ + + private val EXPECTED_SUBSCRIPTION: Subscription = + Subscription( + type = SubscriptionType.FREEMIUM, + status = SubscriptionStatus.ACTIVE, + stepsLimitTotal = 10, + stepsLimitLeft = 9, + stepsLimitResetTime = Instant.parse("2022-11-16T12:19:14.782644Z"), + validTill = Instant.parse("2099-01-01T01:00:00Z") + ) + } + + @Test + fun `Serialized subscription should be deserialized normally`() { + val json = NetworkModule.provideJson() + val actualModel = json.decodeFromString(Subscription.serializer(), TEST_JSON) + assertEquals(EXPECTED_SUBSCRIPTION, actualModel) + } +} \ No newline at end of file diff --git a/shared/src/commonTest/kotlin/org/hyperskill/subscriptions/SubscriptionStub.kt b/shared/src/commonTest/kotlin/org/hyperskill/subscriptions/SubscriptionStub.kt index c2c3dc9a8f..80a13ceae5 100644 --- a/shared/src/commonTest/kotlin/org/hyperskill/subscriptions/SubscriptionStub.kt +++ b/shared/src/commonTest/kotlin/org/hyperskill/subscriptions/SubscriptionStub.kt @@ -1,14 +1,18 @@ package org.hyperskill.subscriptions import org.hyperskill.app.subscriptions.domain.model.Subscription +import org.hyperskill.app.subscriptions.domain.model.SubscriptionStatus import org.hyperskill.app.subscriptions.domain.model.SubscriptionType fun Subscription.Companion.stub( - type: SubscriptionType = SubscriptionType.PREMIUM + type: SubscriptionType = SubscriptionType.PREMIUM, + status: SubscriptionStatus = SubscriptionStatus.ACTIVE ): Subscription = Subscription( type = type, + status = status, stepsLimitLeft = null, stepsLimitTotal = null, - stepsLimitResetTime = null + stepsLimitResetTime = null, + validTill = null ) \ No newline at end of file diff --git a/shared/src/commonTest/kotlin/org/hyperskill/track_selection/details/TrackSelectionDetailsTest.kt b/shared/src/commonTest/kotlin/org/hyperskill/track_selection/details/TrackSelectionDetailsTest.kt index fdcee625e7..4e78f95f5b 100644 --- a/shared/src/commonTest/kotlin/org/hyperskill/track_selection/details/TrackSelectionDetailsTest.kt +++ b/shared/src/commonTest/kotlin/org/hyperskill/track_selection/details/TrackSelectionDetailsTest.kt @@ -263,27 +263,32 @@ class TrackSelectionDetailsTest { } @Test - fun `Certificate and projects info should not be available for freemium user`() { - val state = TrackSelectionDetailsFeature.State( - trackWithProgress = TrackWithProgress.stub(), - isTrackSelected = false, - isNewUserMode = false, - isTrackLoadingShowed = false, - contentState = ContentState.Content( - providers = emptyList(), - profile = Profile.stub(), - subscriptionType = SubscriptionType.FREEMIUM + fun `Certificate and projects info should not be available for freemium or mobile-only user`() { + listOf( + SubscriptionType.FREEMIUM, + SubscriptionType.MOBILE_ONLY + ).forEach { subscriptionType -> + val state = TrackSelectionDetailsFeature.State( + trackWithProgress = TrackWithProgress.stub(), + isTrackSelected = false, + isNewUserMode = false, + isTrackLoadingShowed = false, + contentState = ContentState.Content( + providers = emptyList(), + profile = Profile.stub(), + subscriptionType = subscriptionType + ) ) - ) - val viewState = viewStateMapper.map(state) + val viewState = viewStateMapper.map(state) - assertFalse { - (viewState as ViewState.Content).isCertificateAvailable + assertFalse { + (viewState as ViewState.Content).isCertificateAvailable + } + assertNull( + (viewState as ViewState.Content).formattedProjectsCount + ) } - assertNull( - (viewState as ViewState.Content).formattedProjectsCount - ) } @Test diff --git a/shared/src/iosMain/kotlin/org/hyperskill/app/core/domain/platform/Platform.kt b/shared/src/iosMain/kotlin/org/hyperskill/app/core/domain/platform/Platform.kt index bc020ed73f..fedb2643b1 100644 --- a/shared/src/iosMain/kotlin/org/hyperskill/app/core/domain/platform/Platform.kt +++ b/shared/src/iosMain/kotlin/org/hyperskill/app/core/domain/platform/Platform.kt @@ -14,4 +14,6 @@ actual class Platform actual constructor() { actual val feedbackName: String = "iOS" actual val appNameResource: StringResource = SharedResources.strings.ios_app_name + + actual val isSubscriptionPurchaseEnabled: Boolean = false } \ No newline at end of file diff --git a/shared/src/iosMain/kotlin/org/hyperskill/app/core/injection/IosAppComponentImpl.kt b/shared/src/iosMain/kotlin/org/hyperskill/app/core/injection/IosAppComponentImpl.kt index 6991f95821..8ff9b658da 100644 --- a/shared/src/iosMain/kotlin/org/hyperskill/app/core/injection/IosAppComponentImpl.kt +++ b/shared/src/iosMain/kotlin/org/hyperskill/app/core/injection/IosAppComponentImpl.kt @@ -9,6 +9,9 @@ import org.hyperskill.app.core.domain.BuildVariant import org.hyperskill.app.core.remote.UserAgentInfo import org.hyperskill.app.notification.remote.injection.IosPlatformPushNotificationsDataComponent import org.hyperskill.app.notification.remote.injection.PlatformPushNotificationsDataComponent +import org.hyperskill.app.purchase.domain.model.IOSPurchaseManager +import org.hyperskill.app.purchases.injection.PurchaseComponent +import org.hyperskill.app.purchases.injection.PurchaseComponentImpl import org.hyperskill.app.sentry.domain.model.manager.SentryManager import org.hyperskill.app.sentry.injection.SentryComponent import org.hyperskill.app.sentry.injection.SentryComponentImpl @@ -40,6 +43,9 @@ abstract class IosAppComponentImpl( iosFCMTokenProvider = getIosFCMTokenProvider() ) + override fun buildPurchaseComponent(): PurchaseComponent = + PurchaseComponentImpl(IOSPurchaseManager()) + override fun buildApplicationShortcutsDataComponent(): ApplicationShortcutsDataComponent = ApplicationShortcutsDataComponentImpl(this) } \ No newline at end of file diff --git a/shared/src/iosMain/kotlin/org/hyperskill/app/purchase/domain/model/IOSPurchaseManager.kt b/shared/src/iosMain/kotlin/org/hyperskill/app/purchase/domain/model/IOSPurchaseManager.kt new file mode 100644 index 0000000000..a7ebdfb8ea --- /dev/null +++ b/shared/src/iosMain/kotlin/org/hyperskill/app/purchase/domain/model/IOSPurchaseManager.kt @@ -0,0 +1,27 @@ +package org.hyperskill.app.purchase.domain.model + +import org.hyperskill.app.purchases.domain.model.PlatformPurchaseParams +import org.hyperskill.app.purchases.domain.model.PurchaseManager +import org.hyperskill.app.purchases.domain.model.PurchaseResult + +// TODO: ALTAPPS-1110 +class IOSPurchaseManager : PurchaseManager { + override fun isConfigured(): Boolean = false + + override fun configure(userId: Long) {} + + override suspend fun login(userId: Long): Result = + Result.failure(IllegalStateException("iOS platform not supports purchases")) + + override suspend fun purchase( + productId: String, + platformPurchaseParams: PlatformPurchaseParams + ): Result = + Result.failure(IllegalStateException("iOS platform not supports purchases")) + + override suspend fun getManagementUrl(): Result = + Result.failure(IllegalStateException("iOS platform not supports purchases")) + + override suspend fun getFormattedProductPrice(productId: String): Result = + Result.failure(IllegalStateException("iOS platform not supports purchases")) +} \ No newline at end of file diff --git a/shared/src/iosMain/kotlin/org/hyperskill/app/purchase/domain/model/IOSPurchaseParams.kt b/shared/src/iosMain/kotlin/org/hyperskill/app/purchase/domain/model/IOSPurchaseParams.kt new file mode 100644 index 0000000000..e3c312af8d --- /dev/null +++ b/shared/src/iosMain/kotlin/org/hyperskill/app/purchase/domain/model/IOSPurchaseParams.kt @@ -0,0 +1,5 @@ +package org.hyperskill.app.purchase.domain.model + +import org.hyperskill.app.purchases.domain.model.PlatformPurchaseParams + +object IOSPurchaseParams : PlatformPurchaseParams \ No newline at end of file From 1038ef3bb7f33108da368ad27eb5416afee1adb7 Mon Sep 17 00:00:00 2001 From: Ivan Magda Date: Thu, 29 Feb 2024 09:26:19 +0800 Subject: [PATCH 097/104] Bump build number --- gradle/app.versions.toml | 2 +- .../NotificationServiceExtension/Info.plist | 2 +- .../iosHyperskillApp.xcodeproj/project.pbxproj | 16 ++++++++-------- iosHyperskillApp/iosHyperskillApp/Info.plist | 2 +- .../iosHyperskillAppTests/Info.plist | 2 +- .../iosHyperskillAppUITests/Info.plist | 2 +- 6 files changed, 13 insertions(+), 13 deletions(-) diff --git a/gradle/app.versions.toml b/gradle/app.versions.toml index 3918425d88..ef568bfa5f 100644 --- a/gradle/app.versions.toml +++ b/gradle/app.versions.toml @@ -3,4 +3,4 @@ minSdk = '24' targetSdk = '33' compileSdk = '33' versionName = '1.50' -versionCode = '339' \ No newline at end of file +versionCode = '340' \ No newline at end of file diff --git a/iosHyperskillApp/NotificationServiceExtension/Info.plist b/iosHyperskillApp/NotificationServiceExtension/Info.plist index dfcaadc66c..ef7dbb7dd1 100644 --- a/iosHyperskillApp/NotificationServiceExtension/Info.plist +++ b/iosHyperskillApp/NotificationServiceExtension/Info.plist @@ -9,7 +9,7 @@ CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleVersion - 345 + 346 CFBundleShortVersionString 1.50 CFBundlePackageType diff --git a/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj b/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj index 056f267f9a..73c5e450fc 100644 --- a/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj +++ b/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj @@ -5185,7 +5185,7 @@ buildSettings = { CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 345; + CURRENT_PROJECT_VERSION = 346; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = 3DWS674B2M; GENERATE_INFOPLIST_FILE = NO; @@ -5206,7 +5206,7 @@ buildSettings = { CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 345; + CURRENT_PROJECT_VERSION = 346; DEVELOPMENT_TEAM = 3DWS674B2M; GENERATE_INFOPLIST_FILE = NO; INFOPLIST_FILE = iosHyperskillAppUITests/Info.plist; @@ -5227,7 +5227,7 @@ BUNDLE_LOADER = "$(TEST_HOST)"; CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 345; + CURRENT_PROJECT_VERSION = 346; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = 3DWS674B2M; INFOPLIST_FILE = iosHyperskillAppTests/Info.plist; @@ -5248,7 +5248,7 @@ BUNDLE_LOADER = "$(TEST_HOST)"; CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 345; + CURRENT_PROJECT_VERSION = 346; DEVELOPMENT_TEAM = 3DWS674B2M; INFOPLIST_FILE = iosHyperskillAppTests/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; @@ -5269,7 +5269,7 @@ CODE_SIGN_ENTITLEMENTS = NotificationServiceExtension/NotificationServiceExtension.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 345; + CURRENT_PROJECT_VERSION = 346; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = 3DWS674B2M; INFOPLIST_FILE = NotificationServiceExtension/Info.plist; @@ -5297,7 +5297,7 @@ CODE_SIGN_ENTITLEMENTS = NotificationServiceExtension/NotificationServiceExtension.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 345; + CURRENT_PROJECT_VERSION = 346; DEVELOPMENT_TEAM = 3DWS674B2M; INFOPLIST_FILE = NotificationServiceExtension/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; @@ -5442,7 +5442,7 @@ CODE_SIGN_ENTITLEMENTS = iosHyperskillApp/iosHyperskillApp.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 345; + CURRENT_PROJECT_VERSION = 346; DEVELOPMENT_ASSET_PATHS = "\"iosHyperskillApp/Preview Content\""; DEVELOPMENT_TEAM = 3DWS674B2M; ENABLE_PREVIEWS = YES; @@ -5478,7 +5478,7 @@ CODE_SIGN_ENTITLEMENTS = iosHyperskillApp/iosHyperskillApp.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 345; + CURRENT_PROJECT_VERSION = 346; DEVELOPMENT_ASSET_PATHS = "\"iosHyperskillApp/Preview Content\""; DEVELOPMENT_TEAM = 3DWS674B2M; ENABLE_PREVIEWS = YES; diff --git a/iosHyperskillApp/iosHyperskillApp/Info.plist b/iosHyperskillApp/iosHyperskillApp/Info.plist index 0ea38d11af..cc9fe84225 100644 --- a/iosHyperskillApp/iosHyperskillApp/Info.plist +++ b/iosHyperskillApp/iosHyperskillApp/Info.plist @@ -36,7 +36,7 @@ CFBundleVersion - 345 + 346 FirebaseAppDelegateProxyEnabled FirebaseMessagingAutoInitEnabled diff --git a/iosHyperskillApp/iosHyperskillAppTests/Info.plist b/iosHyperskillApp/iosHyperskillAppTests/Info.plist index ad17a555d0..f963799206 100644 --- a/iosHyperskillApp/iosHyperskillAppTests/Info.plist +++ b/iosHyperskillApp/iosHyperskillAppTests/Info.plist @@ -15,6 +15,6 @@ CFBundleShortVersionString 1.50 CFBundleVersion - 345 + 346 diff --git a/iosHyperskillApp/iosHyperskillAppUITests/Info.plist b/iosHyperskillApp/iosHyperskillAppUITests/Info.plist index 164949e93c..76423dee51 100644 --- a/iosHyperskillApp/iosHyperskillAppUITests/Info.plist +++ b/iosHyperskillApp/iosHyperskillAppUITests/Info.plist @@ -15,6 +15,6 @@ CFBundleShortVersionString 1.50 CFBundleVersion - 345 + 346 From 16acbc3aa92b34180feee94c69d94e93e6e297d2 Mon Sep 17 00:00:00 2001 From: Aleksandr Zhukov Date: Thu, 29 Feb 2024 05:48:29 +0400 Subject: [PATCH 098/104] Android: Onboarding questionnaire (#924) ^ALTAPPS-1145 --- .../extensions/PaddingValuesExtensions.kt | 20 +++ .../main/view/ui/activity/MainActivity.kt | 18 ++- .../UsersQuestionnaireOnboardingFragment.kt | 69 ++++++++ .../UsersQuestionnaireOnboardingScreen.kt | 11 ++ .../UsersQuestionnaireOnboardingDefaults.kt | 13 ++ ...rsQuestionnaireOnboardingPreviewDefault.kt | 54 +++++++ .../ui/UsersQuestionnaireOnboardingScreen.kt | 149 ++++++++++++++++++ .../ui/UsersQuestionnaireOptionsList.kt | 80 ++++++++++ .../core/injection/CommonAndroidAppGraph.kt | 3 + .../injection/CommonAndroidAppGraphImpl.kt | 7 + ...rmUsersQuestionnaireOnboardingComponent.kt | 7 + ...ersQuestionnaireOnboardingComponentImpl.kt | 20 +++ .../UsersQuestionnaireOnboardingViewModel.kt | 27 ++++ .../app/main/injection/AppFeatureBuilder.kt | 2 - .../app/main/injection/MainComponentImpl.kt | 1 - .../WelcomeOnboardingComponentImpl.kt | 4 +- .../presentation/WelcomeOnboardingReducer.kt | 5 +- .../org/hyperskill/main/AppFeatureTest.kt | 3 +- 18 files changed, 481 insertions(+), 12 deletions(-) create mode 100644 androidHyperskillApp/src/main/java/org/hyperskill/app/android/core/extensions/PaddingValuesExtensions.kt create mode 100644 androidHyperskillApp/src/main/java/org/hyperskill/app/android/users_questionnaire/onboarding/fragment/UsersQuestionnaireOnboardingFragment.kt create mode 100644 androidHyperskillApp/src/main/java/org/hyperskill/app/android/users_questionnaire/onboarding/navigation/UsersQuestionnaireOnboardingScreen.kt create mode 100644 androidHyperskillApp/src/main/java/org/hyperskill/app/android/users_questionnaire/onboarding/ui/UsersQuestionnaireOnboardingDefaults.kt create mode 100644 androidHyperskillApp/src/main/java/org/hyperskill/app/android/users_questionnaire/onboarding/ui/UsersQuestionnaireOnboardingPreviewDefault.kt create mode 100644 androidHyperskillApp/src/main/java/org/hyperskill/app/android/users_questionnaire/onboarding/ui/UsersQuestionnaireOnboardingScreen.kt create mode 100644 androidHyperskillApp/src/main/java/org/hyperskill/app/android/users_questionnaire/onboarding/ui/UsersQuestionnaireOptionsList.kt create mode 100644 shared/src/androidMain/kotlin/org/hyperskill/app/users_questionnaire/onboarding/injection/PlatformUsersQuestionnaireOnboardingComponent.kt create mode 100644 shared/src/androidMain/kotlin/org/hyperskill/app/users_questionnaire/onboarding/injection/PlatformUsersQuestionnaireOnboardingComponentImpl.kt create mode 100644 shared/src/androidMain/kotlin/org/hyperskill/app/users_questionnaire/onboarding/presentation/UsersQuestionnaireOnboardingViewModel.kt diff --git a/androidHyperskillApp/src/main/java/org/hyperskill/app/android/core/extensions/PaddingValuesExtensions.kt b/androidHyperskillApp/src/main/java/org/hyperskill/app/android/core/extensions/PaddingValuesExtensions.kt new file mode 100644 index 0000000000..dad7dced1d --- /dev/null +++ b/androidHyperskillApp/src/main/java/org/hyperskill/app/android/core/extensions/PaddingValuesExtensions.kt @@ -0,0 +1,20 @@ +package org.hyperskill.app.android.core.extensions + +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.calculateEndPadding +import androidx.compose.foundation.layout.calculateStartPadding +import androidx.compose.runtime.Composable +import androidx.compose.ui.platform.LocalLayoutDirection + +@Composable +operator fun PaddingValues.plus(other: PaddingValues): PaddingValues { + val layoutDirection = LocalLayoutDirection.current + return PaddingValues( + start = this.calculateStartPadding(layoutDirection) + + other.calculateStartPadding(layoutDirection), + top = this.calculateTopPadding() + other.calculateTopPadding(), + end = this.calculateEndPadding(layoutDirection) + + other.calculateEndPadding(layoutDirection), + bottom = this.calculateBottomPadding() + other.calculateBottomPadding(), + ) +} \ No newline at end of file diff --git a/androidHyperskillApp/src/main/java/org/hyperskill/app/android/main/view/ui/activity/MainActivity.kt b/androidHyperskillApp/src/main/java/org/hyperskill/app/android/main/view/ui/activity/MainActivity.kt index 3545de9089..f16200ec48 100644 --- a/androidHyperskillApp/src/main/java/org/hyperskill/app/android/main/view/ui/activity/MainActivity.kt +++ b/androidHyperskillApp/src/main/java/org/hyperskill/app/android/main/view/ui/activity/MainActivity.kt @@ -48,6 +48,8 @@ import org.hyperskill.app.android.paywall.navigation.PaywallScreen import org.hyperskill.app.android.step.view.screen.StepScreen import org.hyperskill.app.android.streak_recovery.view.delegate.StreakRecoveryViewActionDelegate import org.hyperskill.app.android.track_selection.list.navigation.TrackSelectionListScreen +import org.hyperskill.app.android.users_questionnaire.onboarding.fragment.UsersQuestionnaireOnboardingFragment +import org.hyperskill.app.android.users_questionnaire.onboarding.navigation.UsersQuestionnaireOnboardingScreen import org.hyperskill.app.android.welcome.navigation.WelcomeScreen import org.hyperskill.app.main.presentation.AppFeature import org.hyperskill.app.main.presentation.MainViewModel @@ -139,6 +141,7 @@ class MainActivity : observeAuthFlowSuccess() observeNotificationsOnboardingFlowFinished() observeFirstProblemOnboardingFlowFinished() + observeUsersQuestionnaireOnboardingCompleted() observePaywallCompleted() mainViewModel.logScreenOrientation(screenOrientation = resources.configuration.screenOrientation) @@ -228,6 +231,19 @@ class MainActivity : } } + private fun observeUsersQuestionnaireOnboardingCompleted() { + lifecycleScope.launch { + router + .observeResult(UsersQuestionnaireOnboardingFragment.USERS_QUESTIONNAIRE_ONBOARDING_FINISHED) + .flowWithLifecycle(lifecycle, Lifecycle.State.STARTED) + .collectLatest { + mainViewModel.onNewMessage( + WelcomeOnboardingFeature.Message.UsersQuestionnaireOnboardingCompleted + ) + } + } + } + override fun onNewIntent(intent: Intent?) { super.onNewIntent(intent) if (intent != null) { @@ -280,7 +296,7 @@ class MainActivity : is WelcomeOnboardingFeature.Action.ViewAction.NavigateTo.Paywall -> router.newRootScreen(PaywallScreen(viewAction.paywallTransitionSource)) WelcomeOnboardingFeature.Action.ViewAction.NavigateTo.UsersQuestionnaireOnboardingScreen -> - TODO("ALTAPPS-1145: Implement QuestionnaireOnboardingScreen navigation") + router.newRootScreen(UsersQuestionnaireOnboardingScreen) } is AppFeature.Action.ViewAction.StreakRecoveryViewAction -> StreakRecoveryViewActionDelegate.handleViewAction( diff --git a/androidHyperskillApp/src/main/java/org/hyperskill/app/android/users_questionnaire/onboarding/fragment/UsersQuestionnaireOnboardingFragment.kt b/androidHyperskillApp/src/main/java/org/hyperskill/app/android/users_questionnaire/onboarding/fragment/UsersQuestionnaireOnboardingFragment.kt new file mode 100644 index 0000000000..7db581339e --- /dev/null +++ b/androidHyperskillApp/src/main/java/org/hyperskill/app/android/users_questionnaire/onboarding/fragment/UsersQuestionnaireOnboardingFragment.kt @@ -0,0 +1,69 @@ +package org.hyperskill.app.android.users_questionnaire.onboarding.fragment + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.compose.ui.platform.ComposeView +import androidx.compose.ui.platform.ViewCompositionStrategy +import androidx.fragment.app.Fragment +import androidx.fragment.app.viewModels +import androidx.lifecycle.ViewModelProvider +import com.google.android.material.snackbar.Snackbar +import org.hyperskill.app.android.HyperskillApp +import org.hyperskill.app.android.core.view.ui.navigation.requireAppRouter +import org.hyperskill.app.android.core.view.ui.widget.compose.HyperskillTheme +import org.hyperskill.app.android.users_questionnaire.onboarding.ui.UsersQuestionnaireOnboardingScreen +import org.hyperskill.app.core.view.handleActions +import org.hyperskill.app.users_questionnaire.onboarding.presentation.UsersQuestionnaireOnboardingFeature.Action.ViewAction +import org.hyperskill.app.users_questionnaire.onboarding.presentation.UsersQuestionnaireOnboardingViewModel + +class UsersQuestionnaireOnboardingFragment : Fragment() { + companion object { + const val USERS_QUESTIONNAIRE_ONBOARDING_FINISHED = "USERS_QUESTIONNAIRE_ONBOARDING_FINISHED" + fun newInstance(): UsersQuestionnaireOnboardingFragment = + UsersQuestionnaireOnboardingFragment() + } + + private var viewModelFactory: ViewModelProvider.Factory? = null + private val usersQuestionnaireOnboardingViewModel: UsersQuestionnaireOnboardingViewModel by viewModels { + requireNotNull(viewModelFactory) + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + injectComponent() + usersQuestionnaireOnboardingViewModel.handleActions(this, onAction = ::onAction) + } + + private fun injectComponent() { + val platformUsersQuestionnaireOnboardingComponent = + HyperskillApp.graph().buildPlatformUsersQuestionnaireOnboardingComponent() + viewModelFactory = platformUsersQuestionnaireOnboardingComponent.reduxViewModelFactory + } + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View = + ComposeView(requireContext()).apply { + setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnLifecycleDestroyed(viewLifecycleOwner)) + setContent { + HyperskillTheme { + UsersQuestionnaireOnboardingScreen(viewModel = usersQuestionnaireOnboardingViewModel) + } + } + } + + private fun onAction(action: ViewAction) { + when (action) { + ViewAction.CompleteUsersQuestionnaireOnboarding -> { + requireAppRouter().sendResult(USERS_QUESTIONNAIRE_ONBOARDING_FINISHED, Any()) + } + is ViewAction.ShowSendSuccessMessage -> { + Snackbar.make(requireView(), action.message, Snackbar.LENGTH_SHORT).show() + } + } + } +} \ No newline at end of file diff --git a/androidHyperskillApp/src/main/java/org/hyperskill/app/android/users_questionnaire/onboarding/navigation/UsersQuestionnaireOnboardingScreen.kt b/androidHyperskillApp/src/main/java/org/hyperskill/app/android/users_questionnaire/onboarding/navigation/UsersQuestionnaireOnboardingScreen.kt new file mode 100644 index 0000000000..97486bbe39 --- /dev/null +++ b/androidHyperskillApp/src/main/java/org/hyperskill/app/android/users_questionnaire/onboarding/navigation/UsersQuestionnaireOnboardingScreen.kt @@ -0,0 +1,11 @@ +package org.hyperskill.app.android.users_questionnaire.onboarding.navigation + +import androidx.fragment.app.Fragment +import androidx.fragment.app.FragmentFactory +import com.github.terrakok.cicerone.androidx.FragmentScreen +import org.hyperskill.app.android.users_questionnaire.onboarding.fragment.UsersQuestionnaireOnboardingFragment + +object UsersQuestionnaireOnboardingScreen : FragmentScreen { + override fun createFragment(factory: FragmentFactory): Fragment = + UsersQuestionnaireOnboardingFragment.newInstance() +} \ No newline at end of file diff --git a/androidHyperskillApp/src/main/java/org/hyperskill/app/android/users_questionnaire/onboarding/ui/UsersQuestionnaireOnboardingDefaults.kt b/androidHyperskillApp/src/main/java/org/hyperskill/app/android/users_questionnaire/onboarding/ui/UsersQuestionnaireOnboardingDefaults.kt new file mode 100644 index 0000000000..a214007985 --- /dev/null +++ b/androidHyperskillApp/src/main/java/org/hyperskill/app/android/users_questionnaire/onboarding/ui/UsersQuestionnaireOnboardingDefaults.kt @@ -0,0 +1,13 @@ +package org.hyperskill.app.android.users_questionnaire.onboarding.ui + +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.ui.unit.dp + +object UsersQuestionnaireOnboardingDefaults { + val ContentPadding: PaddingValues = PaddingValues( + top = 20.dp, + start = 20.dp, + end = 20.dp, + bottom = 8.dp + ) +} \ No newline at end of file diff --git a/androidHyperskillApp/src/main/java/org/hyperskill/app/android/users_questionnaire/onboarding/ui/UsersQuestionnaireOnboardingPreviewDefault.kt b/androidHyperskillApp/src/main/java/org/hyperskill/app/android/users_questionnaire/onboarding/ui/UsersQuestionnaireOnboardingPreviewDefault.kt new file mode 100644 index 0000000000..712ad5e188 --- /dev/null +++ b/androidHyperskillApp/src/main/java/org/hyperskill/app/android/users_questionnaire/onboarding/ui/UsersQuestionnaireOnboardingPreviewDefault.kt @@ -0,0 +1,54 @@ +package org.hyperskill.app.android.users_questionnaire.onboarding.ui + +import org.hyperskill.app.users_questionnaire.onboarding.presentation.UsersQuestionnaireOnboardingFeature + +object UsersQuestionnaireOnboardingPreviewDefault { + + private enum class SelectedChoice { + NONE, + FIRST, + LAST + } + + private fun getViewState( + selectedChoice: SelectedChoice, + isSendButtonEnabled: Boolean + ) = + UsersQuestionnaireOnboardingFeature.ViewState( + title = "How did you hear about Hyperskill?", + choices = listOf( + "Google", + "Youtube", + "Instagram", + "Tiktok", + "News", + "Friends", + "Other" + ), + selectedChoice = when (selectedChoice) { + SelectedChoice.NONE -> null + SelectedChoice.FIRST -> "Google" + SelectedChoice.LAST -> "Other" + }, + textInputValue = when (selectedChoice) { + SelectedChoice.NONE -> null + SelectedChoice.FIRST, + SelectedChoice.LAST -> "example text" + }, + isTextInputVisible = selectedChoice == SelectedChoice.LAST, + isSendButtonEnabled = when (selectedChoice) { + SelectedChoice.NONE -> false + SelectedChoice.FIRST, + SelectedChoice.LAST -> isSendButtonEnabled + } + ) + + fun getUnselectedViewState(): UsersQuestionnaireOnboardingFeature.ViewState = + getViewState(SelectedChoice.NONE, false) + + fun getFirstOptionSelectedViewState(): UsersQuestionnaireOnboardingFeature.ViewState = + getViewState(SelectedChoice.FIRST, true) + + fun getOtherOptionSelectedViewState(isSendButtonEnabled: Boolean): UsersQuestionnaireOnboardingFeature.ViewState = + getViewState(SelectedChoice.LAST, isSendButtonEnabled) +} \ No newline at end of file diff --git a/androidHyperskillApp/src/main/java/org/hyperskill/app/android/users_questionnaire/onboarding/ui/UsersQuestionnaireOnboardingScreen.kt b/androidHyperskillApp/src/main/java/org/hyperskill/app/android/users_questionnaire/onboarding/ui/UsersQuestionnaireOnboardingScreen.kt new file mode 100644 index 0000000000..c4df6796e9 --- /dev/null +++ b/androidHyperskillApp/src/main/java/org/hyperskill/app/android/users_questionnaire/onboarding/ui/UsersQuestionnaireOnboardingScreen.kt @@ -0,0 +1,149 @@ +package org.hyperskill.app.android.users_questionnaire.onboarding.ui + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll +import androidx.compose.material.MaterialTheme +import androidx.compose.material.Scaffold +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.DisposableEffect +import androidx.compose.runtime.getValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.tooling.preview.PreviewParameter +import androidx.compose.ui.tooling.preview.PreviewParameterProvider +import androidx.compose.ui.unit.dp +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import org.hyperskill.app.R +import org.hyperskill.app.android.core.extensions.plus +import org.hyperskill.app.android.core.view.ui.widget.compose.HyperskillButton +import org.hyperskill.app.android.core.view.ui.widget.compose.HyperskillTextButton +import org.hyperskill.app.android.core.view.ui.widget.compose.HyperskillTheme +import org.hyperskill.app.users_questionnaire.onboarding.presentation.UsersQuestionnaireOnboardingFeature +import org.hyperskill.app.users_questionnaire.onboarding.presentation.UsersQuestionnaireOnboardingFeature.ViewState +import org.hyperskill.app.users_questionnaire.onboarding.presentation.UsersQuestionnaireOnboardingViewModel + +@Composable +fun UsersQuestionnaireOnboardingScreen(viewModel: UsersQuestionnaireOnboardingViewModel) { + DisposableEffect(viewModel) { + viewModel.onNewMessage( + UsersQuestionnaireOnboardingFeature.Message.ViewedEventMessage + ) + onDispose { + // no op + } + } + val viewState by viewModel.state.collectAsStateWithLifecycle() + UsersQuestionnaireOnboardingScreen( + viewState = viewState, + onChoiceClicked = viewModel::onChoiceClicked, + onTextInputChanged = viewModel::onTextInputChanged, + onSendClick = viewModel::onSendButtonClick, + onSkipClick = viewModel::onSkipButtonClick + ) +} + +@Composable +fun UsersQuestionnaireOnboardingScreen( + viewState: ViewState, + onChoiceClicked: (String) -> Unit, + onTextInputChanged: (String) -> Unit, + onSendClick: () -> Unit, + onSkipClick: () -> Unit, + modifier: Modifier = Modifier +) { + Scaffold { padding -> + Column( + modifier = modifier + .fillMaxSize() + .verticalScroll(rememberScrollState()) + .padding(padding + UsersQuestionnaireOnboardingDefaults.ContentPadding) + ) { + Text( + text = viewState.title, + style = MaterialTheme.typography.h5, + textAlign = TextAlign.Center, + modifier = Modifier.fillMaxWidth() + ) + Spacer(modifier = Modifier.height(20.dp)) + UsersQuestionnaireOptionsList( + choices = viewState.choices, + selectedChoice = viewState.selectedChoice, + textInputValue = viewState.textInputValue, + isTextInputVisible = viewState.isTextInputVisible, + onChoiceClicked = onChoiceClicked, + onTextInputChanged = onTextInputChanged, + onDoneClick = onSendClick + ) + Spacer(modifier = Modifier.height(32.dp)) + Column(verticalArrangement = Arrangement.spacedBy(16.dp)) { + HyperskillButton( + onClick = onSendClick, + enabled = viewState.isSendButtonEnabled, + modifier = Modifier.fillMaxWidth() + ) { + Text(text = stringResource(id = R.string.users_questionnaire_onboarding_send_button_text)) + } + HyperskillTextButton( + onClick = onSkipClick, + modifier = Modifier.fillMaxWidth() + ) { + Text(text = stringResource(id = R.string.users_questionnaire_onboarding_skip_button_text)) + } + } + } + } +} + +private class UsersQuestionnaireOnboardingPreviewParameterProvider : PreviewParameterProvider { + override val values: Sequence + get() = sequenceOf( + UsersQuestionnaireOnboardingPreviewDefault.getUnselectedViewState(), + UsersQuestionnaireOnboardingPreviewDefault.getFirstOptionSelectedViewState(), + UsersQuestionnaireOnboardingPreviewDefault.getOtherOptionSelectedViewState(false), + UsersQuestionnaireOnboardingPreviewDefault.getOtherOptionSelectedViewState(true) + ) +} + +@Preview(device = "id:pixel_3", showSystemUi = true) +@Composable +private fun UsersQuestionnaireOnboardingScreenPreview( + @PreviewParameter(UsersQuestionnaireOnboardingPreviewParameterProvider::class) + viewState: ViewState +) { + HyperskillTheme { + UsersQuestionnaireOnboardingScreen( + viewState = viewState, + onChoiceClicked = {}, + onTextInputChanged = {}, + onSendClick = {}, + onSkipClick = {} + ) + } +} + +@Preview(device = "id:Nexus S", showSystemUi = true) +@Composable +private fun UsersQuestionnaireOnboardingScreenPreviewSmallDevice( + @PreviewParameter(UsersQuestionnaireOnboardingPreviewParameterProvider::class) + viewState: ViewState +) { + HyperskillTheme { + UsersQuestionnaireOnboardingScreen( + viewState = viewState, + onChoiceClicked = {}, + onTextInputChanged = {}, + onSendClick = {}, + onSkipClick = {} + ) + } +} \ No newline at end of file diff --git a/androidHyperskillApp/src/main/java/org/hyperskill/app/android/users_questionnaire/onboarding/ui/UsersQuestionnaireOptionsList.kt b/androidHyperskillApp/src/main/java/org/hyperskill/app/android/users_questionnaire/onboarding/ui/UsersQuestionnaireOptionsList.kt new file mode 100644 index 0000000000..70a2723271 --- /dev/null +++ b/androidHyperskillApp/src/main/java/org/hyperskill/app/android/users_questionnaire/onboarding/ui/UsersQuestionnaireOptionsList.kt @@ -0,0 +1,80 @@ +package org.hyperskill.app.android.users_questionnaire.onboarding.ui + +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.requiredSize +import androidx.compose.foundation.text.KeyboardActions +import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.material.ExperimentalMaterialApi +import androidx.compose.material.LocalMinimumInteractiveComponentEnforcement +import androidx.compose.material.MaterialTheme +import androidx.compose.material.OutlinedTextField +import androidx.compose.material.RadioButton +import androidx.compose.material.RadioButtonDefaults +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.runtime.key +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.input.ImeAction +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.dp +import org.hyperskill.app.R + +@OptIn(ExperimentalMaterialApi::class) +@Composable +fun UsersQuestionnaireOptionsList( + choices: List, + selectedChoice: String?, + textInputValue: String?, + isTextInputVisible: Boolean, + onChoiceClicked: (String) -> Unit, + onTextInputChanged: (String) -> Unit, + onDoneClick: () -> Unit, + modifier: Modifier = Modifier +) { + Column( + verticalArrangement = Arrangement.spacedBy(16.dp), + modifier = modifier + ) { + choices.forEach { choice -> + key(choice) { + Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) { + Box(modifier = Modifier.requiredSize(24.dp)) { + CompositionLocalProvider(LocalMinimumInteractiveComponentEnforcement provides false) { + RadioButton( + selected = choice == selectedChoice, + onClick = { onChoiceClicked(choice) }, + colors = RadioButtonDefaults.colors(selectedColor = MaterialTheme.colors.primary), + modifier = Modifier.align(Alignment.Center) + ) + } + } + Text( + text = choice, + textAlign = TextAlign.Center, + modifier = Modifier.align(Alignment.CenterVertically) + ) + } + } + } + AnimatedVisibility(visible = isTextInputVisible) { + OutlinedTextField( + value = textInputValue ?: "", + onValueChange = onTextInputChanged, + placeholder = { + Text(text = stringResource(id = R.string.users_questionnaire_onboarding_text_input_placeholder)) + }, + keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done), + keyboardActions = KeyboardActions(onDone = { onDoneClick() }), + modifier = Modifier.fillMaxWidth() + ) + } + } +} \ No newline at end of file diff --git a/shared/src/androidMain/kotlin/org/hyperskill/app/core/injection/CommonAndroidAppGraph.kt b/shared/src/androidMain/kotlin/org/hyperskill/app/core/injection/CommonAndroidAppGraph.kt index 15ad60ff8a..cf1ea58bf1 100644 --- a/shared/src/androidMain/kotlin/org/hyperskill/app/core/injection/CommonAndroidAppGraph.kt +++ b/shared/src/androidMain/kotlin/org/hyperskill/app/core/injection/CommonAndroidAppGraph.kt @@ -42,6 +42,7 @@ import org.hyperskill.app.track_selection.details.injection.PlatformTrackSelecti import org.hyperskill.app.track_selection.details.injection.TrackSelectionDetailsParams import org.hyperskill.app.track_selection.list.injection.PlatformTrackSelectionListComponent import org.hyperskill.app.track_selection.list.injection.TrackSelectionListParams +import org.hyperskill.app.users_questionnaire.onboarding.injection.PlatformUsersQuestionnaireOnboardingComponent import org.hyperskill.app.welcome.injection.PlatformWelcomeComponent import org.hyperskill.app.welcome.injection.WelcomeComponent @@ -114,6 +115,8 @@ interface CommonAndroidAppGraph : AppGraph { stepRoute: StepRoute ): PlatformRequestReviewComponent + fun buildPlatformUsersQuestionnaireOnboardingComponent(): PlatformUsersQuestionnaireOnboardingComponent + fun buildPlatformPaywallComponent(paywallTransitionSource: PaywallTransitionSource): PlatformPaywallComponent fun buildPlatformManageSubscriptionComponent(): PlatformManageSubscriptionComponent diff --git a/shared/src/androidMain/kotlin/org/hyperskill/app/core/injection/CommonAndroidAppGraphImpl.kt b/shared/src/androidMain/kotlin/org/hyperskill/app/core/injection/CommonAndroidAppGraphImpl.kt index 46e63158ec..655a6c0278 100644 --- a/shared/src/androidMain/kotlin/org/hyperskill/app/core/injection/CommonAndroidAppGraphImpl.kt +++ b/shared/src/androidMain/kotlin/org/hyperskill/app/core/injection/CommonAndroidAppGraphImpl.kt @@ -72,6 +72,8 @@ import org.hyperskill.app.track_selection.details.injection.TrackSelectionDetail import org.hyperskill.app.track_selection.list.injection.PlatformTrackSelectionListComponent import org.hyperskill.app.track_selection.list.injection.PlatformTrackSelectionListComponentImpl import org.hyperskill.app.track_selection.list.injection.TrackSelectionListParams +import org.hyperskill.app.users_questionnaire.onboarding.injection.PlatformUsersQuestionnaireOnboardingComponent +import org.hyperskill.app.users_questionnaire.onboarding.injection.PlatformUsersQuestionnaireOnboardingComponentImpl import org.hyperskill.app.welcome.injection.PlatformWelcomeComponent import org.hyperskill.app.welcome.injection.PlatformWelcomeComponentImpl import org.hyperskill.app.welcome.injection.WelcomeComponent @@ -262,6 +264,11 @@ abstract class CommonAndroidAppGraphImpl : CommonAndroidAppGraph, BaseAppGraph() requestReviewComponent = buildRequestReviewModalComponent(stepRoute) ) + override fun buildPlatformUsersQuestionnaireOnboardingComponent(): PlatformUsersQuestionnaireOnboardingComponent = + PlatformUsersQuestionnaireOnboardingComponentImpl( + usersQuestionnaireOnboardingComponent = buildUsersQuestionnaireOnboardingComponent() + ) + override fun buildPlatformPaywallComponent( paywallTransitionSource: PaywallTransitionSource ): PlatformPaywallComponent = diff --git a/shared/src/androidMain/kotlin/org/hyperskill/app/users_questionnaire/onboarding/injection/PlatformUsersQuestionnaireOnboardingComponent.kt b/shared/src/androidMain/kotlin/org/hyperskill/app/users_questionnaire/onboarding/injection/PlatformUsersQuestionnaireOnboardingComponent.kt new file mode 100644 index 0000000000..d1b6fc8d9b --- /dev/null +++ b/shared/src/androidMain/kotlin/org/hyperskill/app/users_questionnaire/onboarding/injection/PlatformUsersQuestionnaireOnboardingComponent.kt @@ -0,0 +1,7 @@ +package org.hyperskill.app.users_questionnaire.onboarding.injection + +import org.hyperskill.app.core.injection.ReduxViewModelFactory + +interface PlatformUsersQuestionnaireOnboardingComponent { + val reduxViewModelFactory: ReduxViewModelFactory +} \ No newline at end of file diff --git a/shared/src/androidMain/kotlin/org/hyperskill/app/users_questionnaire/onboarding/injection/PlatformUsersQuestionnaireOnboardingComponentImpl.kt b/shared/src/androidMain/kotlin/org/hyperskill/app/users_questionnaire/onboarding/injection/PlatformUsersQuestionnaireOnboardingComponentImpl.kt new file mode 100644 index 0000000000..1d98590ddd --- /dev/null +++ b/shared/src/androidMain/kotlin/org/hyperskill/app/users_questionnaire/onboarding/injection/PlatformUsersQuestionnaireOnboardingComponentImpl.kt @@ -0,0 +1,20 @@ +package org.hyperskill.app.users_questionnaire.onboarding.injection + +import org.hyperskill.app.core.flowredux.presentation.wrapWithFlowView +import org.hyperskill.app.core.injection.ReduxViewModelFactory +import org.hyperskill.app.users_questionnaire.onboarding.presentation.UsersQuestionnaireOnboardingViewModel + +internal class PlatformUsersQuestionnaireOnboardingComponentImpl( + private val usersQuestionnaireOnboardingComponent: UsersQuestionnaireOnboardingComponent +) : PlatformUsersQuestionnaireOnboardingComponent { + override val reduxViewModelFactory: ReduxViewModelFactory + get() = ReduxViewModelFactory( + mapOf( + UsersQuestionnaireOnboardingViewModel::class.java to { + UsersQuestionnaireOnboardingViewModel( + usersQuestionnaireOnboardingComponent.usersQuestionnaireOnboardingFeature.wrapWithFlowView() + ) + } + ) + ) +} \ No newline at end of file diff --git a/shared/src/androidMain/kotlin/org/hyperskill/app/users_questionnaire/onboarding/presentation/UsersQuestionnaireOnboardingViewModel.kt b/shared/src/androidMain/kotlin/org/hyperskill/app/users_questionnaire/onboarding/presentation/UsersQuestionnaireOnboardingViewModel.kt new file mode 100644 index 0000000000..4bd8be2bc4 --- /dev/null +++ b/shared/src/androidMain/kotlin/org/hyperskill/app/users_questionnaire/onboarding/presentation/UsersQuestionnaireOnboardingViewModel.kt @@ -0,0 +1,27 @@ +package org.hyperskill.app.users_questionnaire.onboarding.presentation + +import org.hyperskill.app.core.flowredux.presentation.FlowView +import org.hyperskill.app.core.flowredux.presentation.ReduxFlowViewModel +import org.hyperskill.app.users_questionnaire.onboarding.presentation.UsersQuestionnaireOnboardingFeature.Action.ViewAction +import org.hyperskill.app.users_questionnaire.onboarding.presentation.UsersQuestionnaireOnboardingFeature.Message +import org.hyperskill.app.users_questionnaire.onboarding.presentation.UsersQuestionnaireOnboardingFeature.ViewState + +class UsersQuestionnaireOnboardingViewModel( + viewContainer: FlowView +) : ReduxFlowViewModel(viewContainer) { + fun onChoiceClicked(choice: String) { + onNewMessage(Message.ClickedChoice(choice)) + } + + fun onTextInputChanged(text: String) { + onNewMessage(Message.TextInputValueChanged(text)) + } + + fun onSendButtonClick() { + onNewMessage(Message.SendButtonClicked) + } + + fun onSkipButtonClick() { + onNewMessage(Message.SkipButtonClicked) + } +} \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/main/injection/AppFeatureBuilder.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/main/injection/AppFeatureBuilder.kt index 5747db3b1d..93f29f0e42 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/main/injection/AppFeatureBuilder.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/main/injection/AppFeatureBuilder.kt @@ -17,7 +17,6 @@ import org.hyperskill.app.notification.click_handling.presentation.NotificationC import org.hyperskill.app.notification.click_handling.presentation.NotificationClickHandlingReducer import org.hyperskill.app.notification.local.domain.interactor.NotificationInteractor import org.hyperskill.app.notification.remote.domain.interactor.PushNotificationsInteractor -import org.hyperskill.app.profile.domain.repository.CurrentProfileStateRepository import org.hyperskill.app.purchases.domain.interactor.PurchaseInteractor import org.hyperskill.app.sentry.domain.interactor.SentryInteractor import org.hyperskill.app.streak_recovery.presentation.StreakRecoveryActionDispatcher @@ -39,7 +38,6 @@ internal object AppFeatureBuilder { initialState: State?, appInteractor: AppInteractor, authInteractor: AuthInteractor, - currentProfileStateRepository: CurrentProfileStateRepository, sentryInteractor: SentryInteractor, stateRepositoriesComponent: StateRepositoriesComponent, streakRecoveryReducer: StreakRecoveryReducer, diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/main/injection/MainComponentImpl.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/main/injection/MainComponentImpl.kt index 5b6cad28df..cfb377418c 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/main/injection/MainComponentImpl.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/main/injection/MainComponentImpl.kt @@ -25,7 +25,6 @@ internal class MainComponentImpl(private val appGraph: AppGraph) : MainComponent initialState = initialState, appInteractor = appGraph.buildMainDataComponent().appInteractor, authInteractor = appGraph.authComponent.authInteractor, - currentProfileStateRepository = appGraph.profileDataComponent.currentProfileStateRepository, sentryInteractor = appGraph.sentryComponent.sentryInteractor, stateRepositoriesComponent = appGraph.stateRepositoriesComponent, streakRecoveryReducer = streakRecoveryComponent.streakRecoveryReducer, diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/welcome_onboarding/injection/WelcomeOnboardingComponentImpl.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/welcome_onboarding/injection/WelcomeOnboardingComponentImpl.kt index 756d2a0626..dfefaa6d63 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/welcome_onboarding/injection/WelcomeOnboardingComponentImpl.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/welcome_onboarding/injection/WelcomeOnboardingComponentImpl.kt @@ -1,6 +1,5 @@ package org.hyperskill.app.welcome_onboarding.injection -import org.hyperskill.app.core.domain.platform.PlatformType import org.hyperskill.app.core.injection.AppGraph import org.hyperskill.app.core.presentation.ActionDispatcherOptions import org.hyperskill.app.welcome_onboarding.presentation.WelcomeOnboardingActionDispatcher @@ -11,8 +10,7 @@ internal class WelcomeOnboardingComponentImpl( ) : WelcomeOnboardingComponent { override val welcomeOnboardingReducer: WelcomeOnboardingReducer get() = WelcomeOnboardingReducer( - isSubscriptionPurchaseEnabled = appGraph.commonComponent.platform.isSubscriptionPurchaseEnabled, - isUsersQuestionnaireOnboardingEnabled = appGraph.commonComponent.platform.platformType == PlatformType.IOS + isSubscriptionPurchaseEnabled = appGraph.commonComponent.platform.isSubscriptionPurchaseEnabled ) override val welcomeOnboardingActionDispatcher: WelcomeOnboardingActionDispatcher diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/welcome_onboarding/presentation/WelcomeOnboardingReducer.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/welcome_onboarding/presentation/WelcomeOnboardingReducer.kt index c54d0e1ef2..8f91d32f20 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/welcome_onboarding/presentation/WelcomeOnboardingReducer.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/welcome_onboarding/presentation/WelcomeOnboardingReducer.kt @@ -15,8 +15,7 @@ import ru.nobird.app.presentation.redux.reducer.StateReducer private typealias ReducerResult = Pair> class WelcomeOnboardingReducer( - private val isSubscriptionPurchaseEnabled: Boolean, - private val isUsersQuestionnaireOnboardingEnabled: Boolean + private val isSubscriptionPurchaseEnabled: Boolean ) : StateReducer { override fun reduce(state: State, message: Message): ReducerResult = when (message) { @@ -56,7 +55,7 @@ class WelcomeOnboardingReducer( private fun handleNotificationOnboardingCompleted( state: State ): ReducerResult = - if (isUsersQuestionnaireOnboardingEnabled && state.profile?.isNewUser == true) { + if (state.profile?.isNewUser == true) { state to setOf(ViewAction.NavigateTo.UsersQuestionnaireOnboardingScreen) } else { handleUsersQuestionnaireOnboardingCompleted(state) diff --git a/shared/src/commonTest/kotlin/org/hyperskill/main/AppFeatureTest.kt b/shared/src/commonTest/kotlin/org/hyperskill/main/AppFeatureTest.kt index 057677a361..78816f7a80 100644 --- a/shared/src/commonTest/kotlin/org/hyperskill/main/AppFeatureTest.kt +++ b/shared/src/commonTest/kotlin/org/hyperskill/main/AppFeatureTest.kt @@ -27,8 +27,7 @@ class AppFeatureTest { streakRecoveryReducer = StreakRecoveryReducer(resourceProvider = ResourceProviderStub()), notificationClickHandlingReducer = NotificationClickHandlingReducer(), welcomeOnboardingReducer = WelcomeOnboardingReducer( - isSubscriptionPurchaseEnabled = true, - isUsersQuestionnaireOnboardingEnabled = true + isSubscriptionPurchaseEnabled = true ), platformType = PlatformType.ANDROID ) From c8981bab2980fc9718e1823ade82b11ba3674046 Mon Sep 17 00:00:00 2001 From: github-actions Date: Thu, 29 Feb 2024 01:49:08 +0000 Subject: [PATCH 099/104] Bump build number --- gradle/app.versions.toml | 2 +- .../NotificationServiceExtension/Info.plist | 2 +- .../iosHyperskillApp.xcodeproj/project.pbxproj | 16 ++++++++-------- iosHyperskillApp/iosHyperskillApp/Info.plist | 2 +- .../iosHyperskillAppTests/Info.plist | 2 +- .../iosHyperskillAppUITests/Info.plist | 2 +- 6 files changed, 13 insertions(+), 13 deletions(-) diff --git a/gradle/app.versions.toml b/gradle/app.versions.toml index ef568bfa5f..3642affb6c 100644 --- a/gradle/app.versions.toml +++ b/gradle/app.versions.toml @@ -3,4 +3,4 @@ minSdk = '24' targetSdk = '33' compileSdk = '33' versionName = '1.50' -versionCode = '340' \ No newline at end of file +versionCode = '341' \ No newline at end of file diff --git a/iosHyperskillApp/NotificationServiceExtension/Info.plist b/iosHyperskillApp/NotificationServiceExtension/Info.plist index ef7dbb7dd1..ba06cf2e42 100644 --- a/iosHyperskillApp/NotificationServiceExtension/Info.plist +++ b/iosHyperskillApp/NotificationServiceExtension/Info.plist @@ -9,7 +9,7 @@ CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleVersion - 346 + 347 CFBundleShortVersionString 1.50 CFBundlePackageType diff --git a/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj b/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj index 73c5e450fc..ba1abc3fb7 100644 --- a/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj +++ b/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj @@ -5185,7 +5185,7 @@ buildSettings = { CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 346; + CURRENT_PROJECT_VERSION = 347; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = 3DWS674B2M; GENERATE_INFOPLIST_FILE = NO; @@ -5206,7 +5206,7 @@ buildSettings = { CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 346; + CURRENT_PROJECT_VERSION = 347; DEVELOPMENT_TEAM = 3DWS674B2M; GENERATE_INFOPLIST_FILE = NO; INFOPLIST_FILE = iosHyperskillAppUITests/Info.plist; @@ -5227,7 +5227,7 @@ BUNDLE_LOADER = "$(TEST_HOST)"; CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 346; + CURRENT_PROJECT_VERSION = 347; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = 3DWS674B2M; INFOPLIST_FILE = iosHyperskillAppTests/Info.plist; @@ -5248,7 +5248,7 @@ BUNDLE_LOADER = "$(TEST_HOST)"; CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 346; + CURRENT_PROJECT_VERSION = 347; DEVELOPMENT_TEAM = 3DWS674B2M; INFOPLIST_FILE = iosHyperskillAppTests/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; @@ -5269,7 +5269,7 @@ CODE_SIGN_ENTITLEMENTS = NotificationServiceExtension/NotificationServiceExtension.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 346; + CURRENT_PROJECT_VERSION = 347; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = 3DWS674B2M; INFOPLIST_FILE = NotificationServiceExtension/Info.plist; @@ -5297,7 +5297,7 @@ CODE_SIGN_ENTITLEMENTS = NotificationServiceExtension/NotificationServiceExtension.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 346; + CURRENT_PROJECT_VERSION = 347; DEVELOPMENT_TEAM = 3DWS674B2M; INFOPLIST_FILE = NotificationServiceExtension/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; @@ -5442,7 +5442,7 @@ CODE_SIGN_ENTITLEMENTS = iosHyperskillApp/iosHyperskillApp.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 346; + CURRENT_PROJECT_VERSION = 347; DEVELOPMENT_ASSET_PATHS = "\"iosHyperskillApp/Preview Content\""; DEVELOPMENT_TEAM = 3DWS674B2M; ENABLE_PREVIEWS = YES; @@ -5478,7 +5478,7 @@ CODE_SIGN_ENTITLEMENTS = iosHyperskillApp/iosHyperskillApp.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 346; + CURRENT_PROJECT_VERSION = 347; DEVELOPMENT_ASSET_PATHS = "\"iosHyperskillApp/Preview Content\""; DEVELOPMENT_TEAM = 3DWS674B2M; ENABLE_PREVIEWS = YES; diff --git a/iosHyperskillApp/iosHyperskillApp/Info.plist b/iosHyperskillApp/iosHyperskillApp/Info.plist index cc9fe84225..57080dbc44 100644 --- a/iosHyperskillApp/iosHyperskillApp/Info.plist +++ b/iosHyperskillApp/iosHyperskillApp/Info.plist @@ -36,7 +36,7 @@ CFBundleVersion - 346 + 347 FirebaseAppDelegateProxyEnabled FirebaseMessagingAutoInitEnabled diff --git a/iosHyperskillApp/iosHyperskillAppTests/Info.plist b/iosHyperskillApp/iosHyperskillAppTests/Info.plist index f963799206..e1b60ab2ae 100644 --- a/iosHyperskillApp/iosHyperskillAppTests/Info.plist +++ b/iosHyperskillApp/iosHyperskillAppTests/Info.plist @@ -15,6 +15,6 @@ CFBundleShortVersionString 1.50 CFBundleVersion - 346 + 347 diff --git a/iosHyperskillApp/iosHyperskillAppUITests/Info.plist b/iosHyperskillApp/iosHyperskillAppUITests/Info.plist index 76423dee51..a7463534cd 100644 --- a/iosHyperskillApp/iosHyperskillAppUITests/Info.plist +++ b/iosHyperskillApp/iosHyperskillAppUITests/Info.plist @@ -15,6 +15,6 @@ CFBundleShortVersionString 1.50 CFBundleVersion - 346 + 347 From 9b59df9d272104181969a1b10f653d303b310eef Mon Sep 17 00:00:00 2001 From: Ivan Magda Date: Thu, 29 Feb 2024 15:32:06 +0700 Subject: [PATCH 100/104] Shared: Support experiment with charging limit for wrong submissions (#921) ^ALTAPPS-1160 --- .../dialog/ProblemsLimitReachedBottomSheet.kt | 25 ++- .../view/fragment/DefaultStepQuizFragment.kt | 5 +- .../fragment_problems_limit_reached.xml | 4 +- .../Sources/Models/Constants/Strings.swift | 4 - ...blemsLimitReachedModalViewController.swift | 19 +- .../Modules/StepQuiz/Views/StepQuizView.swift | 9 +- .../step_quiz/AndroidStepQuizTest.kt | 3 +- .../injection/ChallengeWidgetComponentImpl.kt | 2 +- .../ChallengeWidgetActionDispatcher.kt | 6 +- .../hyperskill/app/core/injection/AppGraph.kt | 3 +- .../app/core/injection/BaseAppGraph.kt | 7 +- .../GamificationToolbarComponentImpl.kt | 4 +- .../GamificationToolbarActionDispatcher.kt | 6 +- .../home/domain/interactor/HomeInteractor.kt | 10 - .../app/home/injection/HomeComponentImpl.kt | 6 +- .../app/home/injection/HomeFeatureBuilder.kt | 8 +- .../home/presentation/HomeActionDispatcher.kt | 19 +- .../injection/ProblemsLimitComponentImpl.kt | 3 +- .../ProblemsLimitActionDispatcher.kt | 41 ++-- .../presentation/ProblemsLimitFeature.kt | 4 +- .../presentation/ProblemsLimitReducer.kt | 1 + .../mapper/ProblemsLimitViewStateMapper.kt | 27 ++- .../app/profile/domain/model/FeatureKeys.kt | 1 + .../app/profile/domain/model/FeaturesMap.kt | 3 + .../CurrentProfileStateRepository.kt | 8 +- .../profile/injection/ProfileComponentImpl.kt | 2 +- .../injection/ProfileFeatureBuilder.kt | 6 +- .../presentation/ProfileActionDispatcher.kt | 6 +- .../RequestReviewDataComponentImpl.kt | 2 +- .../injection/StageImplementComponent.kt | 7 +- .../injection/StageImplementComponentImpl.kt | 11 +- .../injection/StageImplementFeatureBuilder.kt | 19 +- .../StageImplementActionDispatcher.kt | 6 +- .../data/flow/StepCompletedFlowImpl.kt | 16 ++ .../domain/flow/StepCompletedFlow.kt | 5 + .../injection/StepCompletionComponentImpl.kt | 2 +- .../StepCompletionFlowDataComponent.kt | 2 + .../StepCompletionFlowDataComponentImpl.kt | 5 + .../StepCompletionActionDispatcher.kt | 8 +- .../presentation/StepCompletionFeature.kt | 3 +- .../presentation/StepCompletionReducer.kt | 19 +- .../repository/SubmissionRepositoryImpl.kt | 8 +- .../domain/interactor/StepQuizInteractor.kt | 7 +- .../domain/repository/SubmissionRepository.kt | 10 +- .../injection/StepQuizComponentImpl.kt | 12 +- .../injection/StepQuizFeatureBuilder.kt | 6 +- .../presentation/StepQuizActionDispatcher.kt | 133 ++++++++--- .../step_quiz/presentation/StepQuizFeature.kt | 23 +- .../step_quiz/presentation/StepQuizReducer.kt | 46 +++- .../presentation/StepQuizResolver.kt | 15 ++ .../interactor/SubscriptionsInteractor.kt | 73 ++++-- .../model/FreemiumChargeLimitsStrategy.kt | 6 + .../domain/model/Subscription.kt | 2 +- .../CurrentSubscriptionStateRepository.kt | 8 +- .../TopicsRepetitionsComponentImpl.kt | 4 +- .../TopicsRepetitionsDataComponentImpl.kt | 2 +- .../TopicsRepetitionsFeatureBuilder.kt | 8 +- .../TopicsRepetitionsActionDispatcher.kt | 8 +- .../presentation/TopicsRepetitionsReducer.kt | 2 +- .../commonMain/resources/MR/base/strings.xml | 10 +- .../step_completion/StepCompletionTest.kt | 69 ++++++ .../org/hyperskill/step_quiz/StepQuizTest.kt | 210 +++++++++++++++++- 62 files changed, 759 insertions(+), 250 deletions(-) delete mode 100644 shared/src/commonMain/kotlin/org/hyperskill/app/home/domain/interactor/HomeInteractor.kt create mode 100644 shared/src/commonMain/kotlin/org/hyperskill/app/step_completion/data/flow/StepCompletedFlowImpl.kt create mode 100644 shared/src/commonMain/kotlin/org/hyperskill/app/step_completion/domain/flow/StepCompletedFlow.kt create mode 100644 shared/src/commonMain/kotlin/org/hyperskill/app/subscriptions/domain/model/FreemiumChargeLimitsStrategy.kt diff --git a/androidHyperskillApp/src/main/java/org/hyperskill/app/android/problems_limit/dialog/ProblemsLimitReachedBottomSheet.kt b/androidHyperskillApp/src/main/java/org/hyperskill/app/android/problems_limit/dialog/ProblemsLimitReachedBottomSheet.kt index 566dac62a5..b5616d939e 100644 --- a/androidHyperskillApp/src/main/java/org/hyperskill/app/android/problems_limit/dialog/ProblemsLimitReachedBottomSheet.kt +++ b/androidHyperskillApp/src/main/java/org/hyperskill/app/android/problems_limit/dialog/ProblemsLimitReachedBottomSheet.kt @@ -13,11 +13,11 @@ import com.google.android.material.bottomsheet.BottomSheetBehavior import com.google.android.material.bottomsheet.BottomSheetDialog import com.google.android.material.bottomsheet.BottomSheetDialogFragment import org.hyperskill.app.android.R +import org.hyperskill.app.android.core.extensions.argument import org.hyperskill.app.android.databinding.FragmentProblemsLimitReachedBinding import org.hyperskill.app.android.view.base.ui.extension.wrapWithTheme import org.hyperskill.app.step_quiz.presentation.StepQuizFeature import org.hyperskill.app.step_quiz.presentation.StepQuizViewModel -import ru.nobird.android.view.base.ui.extension.argument class ProblemsLimitReachedBottomSheet : BottomSheetDialogFragment() { @@ -26,17 +26,16 @@ class ProblemsLimitReachedBottomSheet : BottomSheetDialogFragment() { const val TAG = "ProblemsLimitReachedBottomSheet" fun newInstance( - modalText: String, - isUnlockUnlimitedProblemsButtonVisible: Boolean + modalData: StepQuizFeature.ProblemsLimitReachedModalData ): ProblemsLimitReachedBottomSheet = ProblemsLimitReachedBottomSheet().apply { - this.modalText = modalText - this.isUnlockUnlimitedProblemsButtonVisible = isUnlockUnlimitedProblemsButtonVisible + this.modalData = modalData } } - private var modalText: String by argument() - private var isUnlockUnlimitedProblemsButtonVisible: Boolean by argument() + private var modalData: StepQuizFeature.ProblemsLimitReachedModalData by argument( + serializer = StepQuizFeature.ProblemsLimitReachedModalData.serializer() + ) private val viewBinding: FragmentProblemsLimitReachedBinding by viewBinding( FragmentProblemsLimitReachedBinding::bind @@ -77,15 +76,21 @@ class ProblemsLimitReachedBottomSheet : BottomSheetDialogFragment() { problemsLimitReachedHomeButton.setOnClickListener { viewModel.onNewMessage(StepQuizFeature.Message.ProblemsLimitReachedModalGoToHomeScreenClicked) } - problemsLimitReachedUnlimitedProblemsButton.isVisible = isUnlockUnlimitedProblemsButtonVisible - if (isUnlockUnlimitedProblemsButtonVisible) { + + problemsLimitReachedModalTitle.text = modalData.title + problemsLimitReachedDescription.text = modalData.description + + if (modalData.unlockLimitsButtonText != null) { + problemsLimitReachedUnlimitedProblemsButton.isVisible = true + problemsLimitReachedUnlimitedProblemsButton.text = modalData.unlockLimitsButtonText problemsLimitReachedUnlimitedProblemsButton.setOnClickListener { viewModel.onNewMessage( StepQuizFeature.Message.ProblemsLimitReachedModalUnlockUnlimitedProblemsClicked ) } + } else { + problemsLimitReachedUnlimitedProblemsButton.isVisible = false } - problemsLimitReachedDescription.text = modalText } } diff --git a/androidHyperskillApp/src/main/java/org/hyperskill/app/android/step_quiz/view/fragment/DefaultStepQuizFragment.kt b/androidHyperskillApp/src/main/java/org/hyperskill/app/android/step_quiz/view/fragment/DefaultStepQuizFragment.kt index 641257920e..c3f3ae2848 100644 --- a/androidHyperskillApp/src/main/java/org/hyperskill/app/android/step_quiz/view/fragment/DefaultStepQuizFragment.kt +++ b/androidHyperskillApp/src/main/java/org/hyperskill/app/android/step_quiz/view/fragment/DefaultStepQuizFragment.kt @@ -287,10 +287,7 @@ abstract class DefaultStepQuizFragment : } is StepQuizFeature.Action.ViewAction.ShowProblemsLimitReachedModal -> { ProblemsLimitReachedBottomSheet - .newInstance( - modalText = action.modalText, - isUnlockUnlimitedProblemsButtonVisible = action.isUnlockUnlimitedProblemsButtonVisible - ) + .newInstance(action.modalData) .showIfNotExists(childFragmentManager, ProblemsLimitReachedBottomSheet.TAG) } is StepQuizFeature.Action.ViewAction.ShowProblemOnboardingModal -> { diff --git a/androidHyperskillApp/src/main/res/layout/fragment_problems_limit_reached.xml b/androidHyperskillApp/src/main/res/layout/fragment_problems_limit_reached.xml index e01ee2577c..f100f009ff 100644 --- a/androidHyperskillApp/src/main/res/layout/fragment_problems_limit_reached.xml +++ b/androidHyperskillApp/src/main/res/layout/fragment_problems_limit_reached.xml @@ -37,11 +37,11 @@ android:id="@+id/problemsLimitReachedModalTitle" android:layout_width="match_parent" android:layout_height="wrap_content" - android:text="@string/problems_limit_reached_modal_title" android:textAppearance="@style/TextAppearance.AppCompat.Headline" android:textSize="34sp" android:layout_marginHorizontal="20dp" android:layout_marginTop="32dp" + tools:text="@string/problems_limit_reached_modal_title" /> , + stepCompletedFlow: StepCompletedFlow, topicCompletedFlow: TopicCompletedFlow, dailyStepCompletedFlow: DailyStepCompletedFlow, private val challengesRepository: ChallengesRepository, @@ -41,7 +41,7 @@ class ChallengeWidgetActionDispatcher( } init { - solvedStepsSharedFlow + stepCompletedFlow.observe() .distinctUntilChanged() .onEach { onNewMessage(InternalMessage.StepSolved) diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/core/injection/AppGraph.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/core/injection/AppGraph.kt index eab7e0719c..94ed989e74 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/core/injection/AppGraph.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/core/injection/AppGraph.kt @@ -99,7 +99,6 @@ interface AppGraph { val mainComponent: MainComponent val analyticComponent: AnalyticComponent val sentryComponent: SentryComponent - val submissionDataComponent: SubmissionDataComponent val streakFlowDataComponent: StreakFlowDataComponent val topicsRepetitionsFlowDataComponent: TopicsRepetitionsFlowDataComponent val stepCompletionFlowDataComponent: StepCompletionFlowDataComponent @@ -129,6 +128,8 @@ interface AppGraph { fun buildStepCompletionComponent(stepRoute: StepRoute): StepCompletionComponent fun buildStageImplementComponent(projectId: Long, stageId: Long): StageImplementComponent + fun buildSubmissionDataComponent(): SubmissionDataComponent + fun buildStudyPlanWidgetComponent(): StudyPlanWidgetComponent fun buildStudyPlanScreenComponent(): StudyPlanScreenComponent diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/core/injection/BaseAppGraph.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/core/injection/BaseAppGraph.kt index afcd9998b1..d9b7c4e608 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/core/injection/BaseAppGraph.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/core/injection/BaseAppGraph.kt @@ -183,10 +183,6 @@ abstract class BaseAppGraph : AppGraph { LoggerComponentImpl(this) } - override val submissionDataComponent: SubmissionDataComponent by lazy { - SubmissionDataComponentImpl(this) - } - override val authComponent: AuthComponent by lazy { AuthComponentImpl(this) } @@ -292,6 +288,9 @@ abstract class BaseAppGraph : AppGraph { override fun buildStageImplementComponent(projectId: Long, stageId: Long): StageImplementComponent = StageImplementComponentImpl(this, projectId = projectId, stageId = stageId) + override fun buildSubmissionDataComponent(): SubmissionDataComponent = + SubmissionDataComponentImpl(this) + override fun buildTrackDataComponent(): TrackDataComponent = TrackDataComponentImpl(this) diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/gamification_toolbar/injection/GamificationToolbarComponentImpl.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/gamification_toolbar/injection/GamificationToolbarComponentImpl.kt index d60e5e4d43..f0c34f5336 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/gamification_toolbar/injection/GamificationToolbarComponentImpl.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/gamification_toolbar/injection/GamificationToolbarComponentImpl.kt @@ -6,7 +6,7 @@ import org.hyperskill.app.gamification_toolbar.domain.model.GamificationToolbarS import org.hyperskill.app.gamification_toolbar.presentation.GamificationToolbarActionDispatcher import org.hyperskill.app.gamification_toolbar.presentation.GamificationToolbarReducer -class GamificationToolbarComponentImpl( +internal class GamificationToolbarComponentImpl( private val appGraph: AppGraph, private val screen: GamificationToolbarScreen ) : GamificationToolbarComponent { @@ -16,7 +16,7 @@ class GamificationToolbarComponentImpl( override val gamificationToolbarActionDispatcher: GamificationToolbarActionDispatcher get() = GamificationToolbarActionDispatcher( config = ActionDispatcherOptions(), - submissionRepository = appGraph.submissionDataComponent.submissionRepository, + stepCompletedFlow = appGraph.stepCompletionFlowDataComponent.stepCompletedFlow, streakFlow = appGraph.streakFlowDataComponent.streakFlow, currentStudyPlanStateRepository = appGraph.stateRepositoriesComponent.currentStudyPlanStateRepository, topicCompletedFlow = appGraph.stepCompletionFlowDataComponent.topicCompletedFlow, diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/gamification_toolbar/presentation/GamificationToolbarActionDispatcher.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/gamification_toolbar/presentation/GamificationToolbarActionDispatcher.kt index 12e59b2842..181c4ad537 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/gamification_toolbar/presentation/GamificationToolbarActionDispatcher.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/gamification_toolbar/presentation/GamificationToolbarActionDispatcher.kt @@ -12,15 +12,15 @@ import org.hyperskill.app.gamification_toolbar.presentation.GamificationToolbarF import org.hyperskill.app.gamification_toolbar.presentation.GamificationToolbarFeature.Message import org.hyperskill.app.sentry.domain.interactor.SentryInteractor import org.hyperskill.app.sentry.domain.withTransaction +import org.hyperskill.app.step_completion.domain.flow.StepCompletedFlow import org.hyperskill.app.step_completion.domain.flow.TopicCompletedFlow -import org.hyperskill.app.step_quiz.domain.repository.SubmissionRepository import org.hyperskill.app.streaks.domain.flow.StreakFlow import org.hyperskill.app.study_plan.domain.repository.CurrentStudyPlanStateRepository import ru.nobird.app.presentation.redux.dispatcher.CoroutineActionDispatcher class GamificationToolbarActionDispatcher( config: ActionDispatcherOptions, - submissionRepository: SubmissionRepository, + stepCompletedFlow: StepCompletedFlow, streakFlow: StreakFlow, currentStudyPlanStateRepository: CurrentStudyPlanStateRepository, topicCompletedFlow: TopicCompletedFlow, @@ -30,7 +30,7 @@ class GamificationToolbarActionDispatcher( ) : CoroutineActionDispatcher(config.createConfig()) { init { - submissionRepository.solvedStepsSharedFlow + stepCompletedFlow.observe() .onEach { onNewMessage(InternalMessage.StepSolved) } .launchIn(actionScope) diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/home/domain/interactor/HomeInteractor.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/home/domain/interactor/HomeInteractor.kt deleted file mode 100644 index 49e6869344..0000000000 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/home/domain/interactor/HomeInteractor.kt +++ /dev/null @@ -1,10 +0,0 @@ -package org.hyperskill.app.home.domain.interactor - -import kotlinx.coroutines.flow.SharedFlow -import org.hyperskill.app.step_quiz.domain.repository.SubmissionRepository - -class HomeInteractor( - submissionRepository: SubmissionRepository -) { - val solvedStepsSharedFlow: SharedFlow = submissionRepository.solvedStepsMutableSharedFlow -} \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/home/injection/HomeComponentImpl.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/home/injection/HomeComponentImpl.kt index f5d8e53d02..0ac28570cc 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/home/injection/HomeComponentImpl.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/home/injection/HomeComponentImpl.kt @@ -4,15 +4,11 @@ import org.hyperskill.app.challenges.widget.injection.ChallengeWidgetComponent import org.hyperskill.app.core.injection.AppGraph import org.hyperskill.app.gamification_toolbar.domain.model.GamificationToolbarScreen import org.hyperskill.app.gamification_toolbar.injection.GamificationToolbarComponent -import org.hyperskill.app.home.domain.interactor.HomeInteractor import org.hyperskill.app.home.presentation.HomeFeature import org.hyperskill.app.interview_preparation.injection.InterviewPreparationWidgetComponent import ru.nobird.app.presentation.redux.feature.Feature internal class HomeComponentImpl(private val appGraph: AppGraph) : HomeComponent { - private val homeInteractor: HomeInteractor = - HomeInteractor(appGraph.submissionDataComponent.submissionRepository) - private val gamificationToolbarComponent: GamificationToolbarComponent = appGraph.buildGamificationToolbarComponent(GamificationToolbarScreen.HOME) @@ -24,7 +20,6 @@ internal class HomeComponentImpl(private val appGraph: AppGraph) : HomeComponent override val homeFeature: Feature get() = HomeFeatureBuilder.build( - homeInteractor, appGraph.profileDataComponent.currentProfileStateRepository, appGraph.buildTopicsRepetitionsDataComponent().topicsRepetitionsInteractor, appGraph.buildStepDataComponent().stepInteractor, @@ -34,6 +29,7 @@ internal class HomeComponentImpl(private val appGraph: AppGraph) : HomeComponent appGraph.commonComponent.dateFormatter, appGraph.topicsRepetitionsFlowDataComponent.topicRepeatedFlow, appGraph.stepCompletionFlowDataComponent.topicCompletedFlow, + appGraph.stepCompletionFlowDataComponent.stepCompletedFlow, gamificationToolbarComponent.gamificationToolbarReducer, gamificationToolbarComponent.gamificationToolbarActionDispatcher, challengeWidgetComponent.challengeWidgetReducer, diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/home/injection/HomeFeatureBuilder.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/home/injection/HomeFeatureBuilder.kt index dafe68543d..104dfe051f 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/home/injection/HomeFeatureBuilder.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/home/injection/HomeFeatureBuilder.kt @@ -13,7 +13,6 @@ import org.hyperskill.app.core.view.mapper.date.SharedDateFormatter import org.hyperskill.app.gamification_toolbar.presentation.GamificationToolbarActionDispatcher import org.hyperskill.app.gamification_toolbar.presentation.GamificationToolbarFeature import org.hyperskill.app.gamification_toolbar.presentation.GamificationToolbarReducer -import org.hyperskill.app.home.domain.interactor.HomeInteractor import org.hyperskill.app.home.presentation.HomeActionDispatcher import org.hyperskill.app.home.presentation.HomeFeature import org.hyperskill.app.home.presentation.HomeReducer @@ -26,6 +25,7 @@ import org.hyperskill.app.logging.presentation.wrapWithLogger import org.hyperskill.app.profile.domain.repository.CurrentProfileStateRepository import org.hyperskill.app.sentry.domain.interactor.SentryInteractor import org.hyperskill.app.step.domain.interactor.StepInteractor +import org.hyperskill.app.step_completion.domain.flow.StepCompletedFlow import org.hyperskill.app.step_completion.domain.flow.TopicCompletedFlow import org.hyperskill.app.subscriptions.domain.repository.CurrentSubscriptionStateRepository import org.hyperskill.app.topics_repetitions.domain.flow.TopicRepeatedFlow @@ -40,7 +40,6 @@ internal object HomeFeatureBuilder { private const val LOG_TAG = "HomeFeature" fun build( - homeInteractor: HomeInteractor, currentProfileStateRepository: CurrentProfileStateRepository, topicsRepetitionsInteractor: TopicsRepetitionsInteractor, stepInteractor: StepInteractor, @@ -50,6 +49,7 @@ internal object HomeFeatureBuilder { dateFormatter: SharedDateFormatter, topicRepeatedFlow: TopicRepeatedFlow, topicCompletedFlow: TopicCompletedFlow, + stepCompletedFlow: StepCompletedFlow, gamificationToolbarReducer: GamificationToolbarReducer, gamificationToolbarActionDispatcher: GamificationToolbarActionDispatcher, challengeWidgetReducer: ChallengeWidgetReducer, @@ -68,7 +68,6 @@ internal object HomeFeatureBuilder { ).wrapWithLogger(buildVariant, logger, LOG_TAG) val homeActionDispatcher = HomeActionDispatcher( ActionDispatcherOptions(), - homeInteractor, currentProfileStateRepository, topicsRepetitionsInteractor, stepInteractor, @@ -77,7 +76,8 @@ internal object HomeFeatureBuilder { sentryInteractor, dateFormatter, topicRepeatedFlow, - topicCompletedFlow + topicCompletedFlow, + stepCompletedFlow ) val homeViewStateMapper = HomeViewStateMapper( challengeWidgetViewStateMapper = challengeWidgetViewStateMapper, diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/home/presentation/HomeActionDispatcher.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/home/presentation/HomeActionDispatcher.kt index a86998c5b0..407d671675 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/home/presentation/HomeActionDispatcher.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/home/presentation/HomeActionDispatcher.kt @@ -13,7 +13,6 @@ import org.hyperskill.app.analytic.domain.interactor.AnalyticInteractor import org.hyperskill.app.core.presentation.ActionDispatcherOptions import org.hyperskill.app.core.utils.DateTimeUtils import org.hyperskill.app.core.view.mapper.date.SharedDateFormatter -import org.hyperskill.app.home.domain.interactor.HomeInteractor import org.hyperskill.app.home.presentation.HomeFeature.Action import org.hyperskill.app.home.presentation.HomeFeature.InternalAction import org.hyperskill.app.home.presentation.HomeFeature.InternalMessage @@ -23,15 +22,16 @@ import org.hyperskill.app.sentry.domain.interactor.SentryInteractor import org.hyperskill.app.sentry.domain.model.transaction.HyperskillSentryTransactionBuilder import org.hyperskill.app.sentry.domain.withTransaction import org.hyperskill.app.step.domain.interactor.StepInteractor +import org.hyperskill.app.step_completion.domain.flow.StepCompletedFlow import org.hyperskill.app.step_completion.domain.flow.TopicCompletedFlow import org.hyperskill.app.subscriptions.domain.repository.CurrentSubscriptionStateRepository +import org.hyperskill.app.subscriptions.domain.repository.areProblemsLimited import org.hyperskill.app.topics_repetitions.domain.flow.TopicRepeatedFlow import org.hyperskill.app.topics_repetitions.domain.interactor.TopicsRepetitionsInteractor import ru.nobird.app.presentation.redux.dispatcher.CoroutineActionDispatcher internal class HomeActionDispatcher( config: ActionDispatcherOptions, - homeInteractor: HomeInteractor, private val currentProfileStateRepository: CurrentProfileStateRepository, private val topicsRepetitionsInteractor: TopicsRepetitionsInteractor, private val stepInteractor: StepInteractor, @@ -40,7 +40,8 @@ internal class HomeActionDispatcher( private val sentryInteractor: SentryInteractor, private val dateFormatter: SharedDateFormatter, topicRepeatedFlow: TopicRepeatedFlow, - topicCompletedFlow: TopicCompletedFlow + topicCompletedFlow: TopicCompletedFlow, + stepCompletedFlow: StepCompletedFlow ) : CoroutineActionDispatcher(config.createConfig()) { private var isTimerLaunched: Boolean = false @@ -49,7 +50,7 @@ internal class HomeActionDispatcher( } init { - homeInteractor.solvedStepsSharedFlow + stepCompletedFlow.observe() .onEach { onNewMessage(InternalMessage.StepQuizSolved(it)) } .launchIn(actionScope) @@ -112,18 +113,16 @@ internal class HomeActionDispatcher( val currentProfile = currentProfileStateRepository .getState(forceUpdate = true) // ALTAPPS-303: Get from remote to get a relevant problem of the day .getOrThrow() + val problemOfDayStateResult = async { getProblemOfDayState(currentProfile.dailyStep) } val repetitionsStateResult = async { getRepetitionsState() } - val areProblemsLimited = async { - currentSubscriptionStateRepository - .getState() - .map { it.type.areProblemsLimited } - } + val areProblemsLimited = async { currentSubscriptionStateRepository.areProblemsLimited() } + setOf( Message.HomeSuccess( problemOfDayState = problemOfDayStateResult.await().getOrThrow(), repetitionsState = repetitionsStateResult.await().getOrThrow(), - areProblemsLimited = areProblemsLimited.await().getOrThrow() + areProblemsLimited = areProblemsLimited.await() ), Message.ReadyToLaunchNextProblemInTimer ) diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/problems_limit/injection/ProblemsLimitComponentImpl.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/problems_limit/injection/ProblemsLimitComponentImpl.kt index 4b937bdd15..fe95b66abd 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/problems_limit/injection/ProblemsLimitComponentImpl.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/problems_limit/injection/ProblemsLimitComponentImpl.kt @@ -19,7 +19,8 @@ internal class ProblemsLimitComponentImpl( config = ActionDispatcherOptions(), sentryInteractor = appGraph.sentryComponent.sentryInteractor, analyticInteractor = appGraph.analyticComponent.analyticInteractor, - currentSubscriptionStateRepository = appGraph.stateRepositoriesComponent.currentSubscriptionStateRepository + currentSubscriptionStateRepository = appGraph.stateRepositoriesComponent.currentSubscriptionStateRepository, + currentProfileStateRepository = appGraph.profileDataComponent.currentProfileStateRepository ) override val problemsLimitViewStateMapper: ProblemsLimitViewStateMapper diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/problems_limit/presentation/ProblemsLimitActionDispatcher.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/problems_limit/presentation/ProblemsLimitActionDispatcher.kt index d262ab1eb1..8d25999dcf 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/problems_limit/presentation/ProblemsLimitActionDispatcher.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/problems_limit/presentation/ProblemsLimitActionDispatcher.kt @@ -11,6 +11,8 @@ import org.hyperskill.app.problems_limit.presentation.ProblemsLimitFeature.Actio import org.hyperskill.app.problems_limit.presentation.ProblemsLimitFeature.InternalAction import org.hyperskill.app.problems_limit.presentation.ProblemsLimitFeature.InternalMessage import org.hyperskill.app.problems_limit.presentation.ProblemsLimitFeature.Message +import org.hyperskill.app.profile.domain.repository.CurrentProfileStateRepository +import org.hyperskill.app.profile.domain.repository.isFreemiumWrongSubmissionChargeLimitsEnabled import org.hyperskill.app.sentry.domain.interactor.SentryInteractor import org.hyperskill.app.sentry.domain.withTransaction import org.hyperskill.app.subscriptions.domain.repository.CurrentSubscriptionStateRepository @@ -20,7 +22,8 @@ class ProblemsLimitActionDispatcher( config: ActionDispatcherOptions, private val sentryInteractor: SentryInteractor, private val analyticInteractor: AnalyticInteractor, - private val currentSubscriptionStateRepository: CurrentSubscriptionStateRepository + private val currentSubscriptionStateRepository: CurrentSubscriptionStateRepository, + private val currentProfileStateRepository: CurrentProfileStateRepository ) : CoroutineActionDispatcher(config.createConfig()) { init { currentSubscriptionStateRepository.changes @@ -35,18 +38,8 @@ class ProblemsLimitActionDispatcher( override suspend fun doSuspendableAction(action: Action) { when (action) { - is InternalAction.LoadSubscription -> { - sentryInteractor.withTransaction( - transaction = action.screen.sentryTransaction, - onError = { InternalMessage.LoadSubscriptionResultError } - ) { - InternalMessage.LoadSubscriptionResultSuccess( - subscription = currentSubscriptionStateRepository - .getState(forceUpdate = action.forceUpdate) - .getOrThrow() - ) - }.let(::onNewMessage) - } + is InternalAction.LoadSubscription -> + handleLoadSubscriptionAction(action, ::onNewMessage) is InternalAction.LaunchTimer -> { timerMutex.withLock { timer?.stop() @@ -68,4 +61,26 @@ class ProblemsLimitActionDispatcher( analyticInteractor.logEvent(action.analyticEvent) } } + + private suspend fun handleLoadSubscriptionAction( + action: InternalAction.LoadSubscription, + onNewMessage: (Message) -> Unit + ) { + sentryInteractor.withTransaction( + action.screen.sentryTransaction, + onError = { InternalMessage.LoadSubscriptionResultError } + ) { + val currentSubscription = currentSubscriptionStateRepository + .getState(forceUpdate = action.forceUpdate) + .getOrThrow() + + val isFreemiumWrongSubmissionChargeLimitsEnabled = + currentProfileStateRepository.isFreemiumWrongSubmissionChargeLimitsEnabled() + + InternalMessage.LoadSubscriptionResultSuccess( + subscription = currentSubscription, + isFreemiumWrongSubmissionChargeLimitsEnabled = isFreemiumWrongSubmissionChargeLimitsEnabled + ) + }.let(onNewMessage) + } } \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/problems_limit/presentation/ProblemsLimitFeature.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/problems_limit/presentation/ProblemsLimitFeature.kt index 6c78669bcd..daf9dfe073 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/problems_limit/presentation/ProblemsLimitFeature.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/problems_limit/presentation/ProblemsLimitFeature.kt @@ -13,6 +13,7 @@ object ProblemsLimitFeature { data class Content( val subscription: Subscription, + val isFreemiumWrongSubmissionChargeLimitsEnabled: Boolean, val updateIn: Duration?, internal val isRefreshing: Boolean = false ) : State @@ -56,7 +57,8 @@ object ProblemsLimitFeature { object LoadSubscriptionResultError : InternalMessage data class LoadSubscriptionResultSuccess( - val subscription: Subscription + val subscription: Subscription, + val isFreemiumWrongSubmissionChargeLimitsEnabled: Boolean ) : InternalMessage data class UpdateInChanged(val newUpdateIn: Duration) : InternalMessage diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/problems_limit/presentation/ProblemsLimitReducer.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/problems_limit/presentation/ProblemsLimitReducer.kt index b3a77feb25..8f09fd77c0 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/problems_limit/presentation/ProblemsLimitReducer.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/problems_limit/presentation/ProblemsLimitReducer.kt @@ -41,6 +41,7 @@ class ProblemsLimitReducer(private val screen: ProblemsLimitScreen) : StateReduc State.Content( subscription = message.subscription, + isFreemiumWrongSubmissionChargeLimitsEnabled = message.isFreemiumWrongSubmissionChargeLimitsEnabled, updateIn = updateIn ) to buildSet { if (updateIn != null) { diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/problems_limit/view/mapper/ProblemsLimitViewStateMapper.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/problems_limit/view/mapper/ProblemsLimitViewStateMapper.kt index 9009729481..17abc70477 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/problems_limit/view/mapper/ProblemsLimitViewStateMapper.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/problems_limit/view/mapper/ProblemsLimitViewStateMapper.kt @@ -24,10 +24,10 @@ class ProblemsLimitViewStateMapper( stepsLimitTotal == null -> ProblemsLimitFeature.ViewState.Content.Empty else -> ProblemsLimitFeature.ViewState.Content.Widget( progress = (stepsLimitLeft.toFloat() / stepsLimitTotal), - stepsLimitLabel = resourceProvider.getString( - SharedResources.strings.problems_limit_widget_problems_limit, - state.subscription.stepsLimitLeft, - state.subscription.stepsLimitTotal + stepsLimitLabel = getStepsLimitLabel( + state.isFreemiumWrongSubmissionChargeLimitsEnabled, + stepsLimitLeft, + stepsLimitTotal ), updateInLabel = state.updateIn?.let { updateIn -> resourceProvider.getString( @@ -39,4 +39,23 @@ class ProblemsLimitViewStateMapper( } } } + + private fun getStepsLimitLabel( + isFreemiumWrongSubmissionChargeLimitsEnabled: Boolean, + stepsLimitLeft: Int, + stepsLimitTotal: Int + ): String = + if (isFreemiumWrongSubmissionChargeLimitsEnabled) { + resourceProvider.getString( + SharedResources.strings.problems_limit_widget_lives_left, + stepsLimitLeft, + stepsLimitTotal + ) + } else { + resourceProvider.getString( + SharedResources.strings.problems_limit_widget_problems_limit, + stepsLimitLeft, + stepsLimitTotal + ) + } } \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/profile/domain/model/FeatureKeys.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/profile/domain/model/FeatureKeys.kt index 7a49802723..4ba98c106b 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/profile/domain/model/FeatureKeys.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/profile/domain/model/FeatureKeys.kt @@ -5,6 +5,7 @@ internal object FeatureKeys { const val RECOMMENDATIONS_KOTLIN_PROJECTS = "recommendations.kotlin_projects" const val RECOMMENDATIONS_PYTHON_PROJECTS = "recommendations.python_projects" const val FREEMIUM_INCREASE_LIMITS_FOR_FIRST_STEP_COMPLETION = "freemium.increase_limits_for_first_step_completion" + const val FREEMIUM_WRONG_SUBMISSION_CHARGE_LIMITS = "freemium.wrong_submission_charge_limits" const val LEARNING_PATH_DIVIDED_TRACK_TOPICS = "learning_path.divided_track_topics" const val MOBILE_LEADERBOARDS = "mobile_leaderboards" const val MOBILE_INTERVIEW_PREPARATION = "mobile.interview_preparation" diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/profile/domain/model/FeaturesMap.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/profile/domain/model/FeaturesMap.kt index da75f47aff..b72575ca3f 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/profile/domain/model/FeaturesMap.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/profile/domain/model/FeaturesMap.kt @@ -14,6 +14,9 @@ val FeaturesMap.isRecommendationsPythonProjectsFeatureEnabled: Boolean val FeaturesMap.isFreemiumIncreaseLimitsForFirstStepCompletionEnabled: Boolean get() = get(FeatureKeys.FREEMIUM_INCREASE_LIMITS_FOR_FIRST_STEP_COMPLETION) ?: false +val FeaturesMap.isFreemiumWrongSubmissionChargeLimitsEnabled: Boolean + get() = get(FeatureKeys.FREEMIUM_WRONG_SUBMISSION_CHARGE_LIMITS) ?: false + val FeaturesMap.isLearningPathDividedTrackTopicsEnabled: Boolean get() = get(FeatureKeys.LEARNING_PATH_DIVIDED_TRACK_TOPICS) ?: false diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/profile/domain/repository/CurrentProfileStateRepository.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/profile/domain/repository/CurrentProfileStateRepository.kt index 900ec6fe3b..9db7bbce77 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/profile/domain/repository/CurrentProfileStateRepository.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/profile/domain/repository/CurrentProfileStateRepository.kt @@ -5,10 +5,16 @@ import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.map import org.hyperskill.app.core.domain.repository.StateRepository import org.hyperskill.app.profile.domain.model.Profile +import org.hyperskill.app.profile.domain.model.isFreemiumWrongSubmissionChargeLimitsEnabled interface CurrentProfileStateRepository : StateRepository internal fun CurrentProfileStateRepository.observeHypercoinsBalance(): Flow = changes .map { it.gamification.hypercoinsBalance } - .distinctUntilChanged() \ No newline at end of file + .distinctUntilChanged() + +internal suspend fun CurrentProfileStateRepository.isFreemiumWrongSubmissionChargeLimitsEnabled(): Boolean = + getState(forceUpdate = false) + .map { it.features.isFreemiumWrongSubmissionChargeLimitsEnabled } + .getOrDefault(defaultValue = false) \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/profile/injection/ProfileComponentImpl.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/profile/injection/ProfileComponentImpl.kt index 701b1497f7..948df1e447 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/profile/injection/ProfileComponentImpl.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/profile/injection/ProfileComponentImpl.kt @@ -19,7 +19,7 @@ internal class ProfileComponentImpl( urlPathProcessor = appGraph.buildMagicLinksDataComponent().urlPathProcessor, streakFlow = appGraph.streakFlowDataComponent.streakFlow, dailyStudyRemindersEnabledFlow = appGraph.notificationFlowDataComponent.dailyStudyRemindersEnabledFlow, - submissionRepository = appGraph.submissionDataComponent.submissionRepository, + stepCompletedFlow = appGraph.stepCompletionFlowDataComponent.stepCompletedFlow, badgesRepository = appGraph.buildBadgesDataComponent().badgesRepository, logger = appGraph.loggerComponent.logger, buildVariant = appGraph.commonComponent.buildKonfig.buildVariant diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/profile/injection/ProfileFeatureBuilder.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/profile/injection/ProfileFeatureBuilder.kt index 426d06b46d..4e80cc2074 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/profile/injection/ProfileFeatureBuilder.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/profile/injection/ProfileFeatureBuilder.kt @@ -17,7 +17,7 @@ import org.hyperskill.app.profile.presentation.ProfileFeature.Message import org.hyperskill.app.profile.presentation.ProfileFeature.State import org.hyperskill.app.profile.presentation.ProfileReducer import org.hyperskill.app.sentry.domain.interactor.SentryInteractor -import org.hyperskill.app.step_quiz.domain.repository.SubmissionRepository +import org.hyperskill.app.step_completion.domain.flow.StepCompletedFlow import org.hyperskill.app.streaks.domain.flow.StreakFlow import org.hyperskill.app.streaks.domain.interactor.StreaksInteractor import ru.nobird.app.presentation.redux.dispatcher.wrapWithActionDispatcher @@ -37,7 +37,7 @@ internal object ProfileFeatureBuilder { urlPathProcessor: UrlPathProcessor, streakFlow: StreakFlow, dailyStudyRemindersEnabledFlow: DailyStudyRemindersEnabledFlow, - submissionRepository: SubmissionRepository, + stepCompletedFlow: StepCompletedFlow, badgesRepository: BadgesRepository, logger: Logger, buildVariant: BuildVariant @@ -54,7 +54,7 @@ internal object ProfileFeatureBuilder { urlPathProcessor = urlPathProcessor, streakFlow = streakFlow, dailyStudyRemindersEnabledFlow = dailyStudyRemindersEnabledFlow, - solvedStepsSharedFlow = submissionRepository.solvedStepsSharedFlow, + stepCompletedFlow = stepCompletedFlow, badgesRepository = badgesRepository ) diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/profile/presentation/ProfileActionDispatcher.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/profile/presentation/ProfileActionDispatcher.kt index 88b71fb07f..e19a494f06 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/profile/presentation/ProfileActionDispatcher.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/profile/presentation/ProfileActionDispatcher.kt @@ -2,7 +2,6 @@ package org.hyperskill.app.profile.presentation import kotlinx.coroutines.async import kotlinx.coroutines.coroutineScope -import kotlinx.coroutines.flow.SharedFlow import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach @@ -22,6 +21,7 @@ import org.hyperskill.app.profile.presentation.ProfileFeature.Action import org.hyperskill.app.profile.presentation.ProfileFeature.Message import org.hyperskill.app.sentry.domain.interactor.SentryInteractor import org.hyperskill.app.sentry.domain.model.transaction.HyperskillSentryTransactionBuilder +import org.hyperskill.app.step_completion.domain.flow.StepCompletedFlow import org.hyperskill.app.streaks.domain.flow.StreakFlow import org.hyperskill.app.streaks.domain.interactor.StreaksInteractor import org.hyperskill.app.streaks.domain.model.Streak @@ -38,12 +38,12 @@ internal class ProfileActionDispatcher( private val urlPathProcessor: UrlPathProcessor, private val streakFlow: StreakFlow, dailyStudyRemindersEnabledFlow: DailyStudyRemindersEnabledFlow, - solvedStepsSharedFlow: SharedFlow, + stepCompletedFlow: StepCompletedFlow, private val badgesRepository: BadgesRepository ) : CoroutineActionDispatcher(config.createConfig()) { init { - solvedStepsSharedFlow + stepCompletedFlow.observe() .onEach { onNewMessage(Message.StepQuizSolved) } .launchIn(actionScope) diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/request_review/injection/RequestReviewDataComponentImpl.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/request_review/injection/RequestReviewDataComponentImpl.kt index 74e0ef3711..0d4718fc2c 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/request_review/injection/RequestReviewDataComponentImpl.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/request_review/injection/RequestReviewDataComponentImpl.kt @@ -19,6 +19,6 @@ internal class RequestReviewDataComponentImpl( override val requestReviewInteractor: RequestReviewInteractor get() = RequestReviewInteractor( requestReviewRepository = requestReviewRepository, - submissionRepository = appGraph.submissionDataComponent.submissionRepository + submissionRepository = appGraph.buildSubmissionDataComponent().submissionRepository ) } \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/stage_implement/injection/StageImplementComponent.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/stage_implement/injection/StageImplementComponent.kt index 019059d704..0f7ade8dd9 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/stage_implement/injection/StageImplementComponent.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/stage_implement/injection/StageImplementComponent.kt @@ -1,9 +1,10 @@ package org.hyperskill.app.stage_implement.injection -import org.hyperskill.app.stage_implement.presentation.StageImplementFeature +import org.hyperskill.app.stage_implement.presentation.StageImplementFeature.Action +import org.hyperskill.app.stage_implement.presentation.StageImplementFeature.Message +import org.hyperskill.app.stage_implement.presentation.StageImplementFeature.ViewState import ru.nobird.app.presentation.redux.feature.Feature interface StageImplementComponent { - val stageImplementFeature: Feature< - StageImplementFeature.ViewState, StageImplementFeature.Message, StageImplementFeature.Action> + val stageImplementFeature: Feature } \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/stage_implement/injection/StageImplementComponentImpl.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/stage_implement/injection/StageImplementComponentImpl.kt index 7dbc796495..ad8f4ff4a2 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/stage_implement/injection/StageImplementComponentImpl.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/stage_implement/injection/StageImplementComponentImpl.kt @@ -1,16 +1,17 @@ package org.hyperskill.app.stage_implement.injection import org.hyperskill.app.core.injection.AppGraph -import org.hyperskill.app.stage_implement.presentation.StageImplementFeature +import org.hyperskill.app.stage_implement.presentation.StageImplementFeature.Action +import org.hyperskill.app.stage_implement.presentation.StageImplementFeature.Message +import org.hyperskill.app.stage_implement.presentation.StageImplementFeature.ViewState import ru.nobird.app.presentation.redux.feature.Feature -class StageImplementComponentImpl( +internal class StageImplementComponentImpl( private val appGraph: AppGraph, private val projectId: Long, private val stageId: Long ) : StageImplementComponent { - override val stageImplementFeature: Feature< - StageImplementFeature.ViewState, StageImplementFeature.Message, StageImplementFeature.Action> + override val stageImplementFeature: Feature get() = StageImplementFeatureBuilder.build( projectId, stageId, @@ -20,7 +21,7 @@ class StageImplementComponentImpl( appGraph.sentryComponent.sentryInteractor, appGraph.commonComponent.resourceProvider, appGraph.profileDataComponent.currentProfileStateRepository, - appGraph.submissionDataComponent.submissionRepository, + appGraph.stepCompletionFlowDataComponent.stepCompletedFlow, appGraph.loggerComponent.logger, appGraph.commonComponent.buildKonfig.buildVariant ) diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/stage_implement/injection/StageImplementFeatureBuilder.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/stage_implement/injection/StageImplementFeatureBuilder.kt index eb1f6f7960..e12beb5be7 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/stage_implement/injection/StageImplementFeatureBuilder.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/stage_implement/injection/StageImplementFeatureBuilder.kt @@ -13,10 +13,13 @@ import org.hyperskill.app.progresses.domain.interactor.ProgressesInteractor import org.hyperskill.app.sentry.domain.interactor.SentryInteractor import org.hyperskill.app.stage_implement.presentation.StageImplementActionDispatcher import org.hyperskill.app.stage_implement.presentation.StageImplementFeature +import org.hyperskill.app.stage_implement.presentation.StageImplementFeature.Action +import org.hyperskill.app.stage_implement.presentation.StageImplementFeature.Message +import org.hyperskill.app.stage_implement.presentation.StageImplementFeature.ViewState import org.hyperskill.app.stage_implement.presentation.StageImplementReducer import org.hyperskill.app.stage_implement.view.mapper.StageImplementViewStateMapper import org.hyperskill.app.stages.domain.interactor.StagesInteractor -import org.hyperskill.app.step_quiz.domain.repository.SubmissionRepository +import org.hyperskill.app.step_completion.domain.flow.StepCompletedFlow import ru.nobird.app.presentation.redux.dispatcher.wrapWithActionDispatcher import ru.nobird.app.presentation.redux.feature.Feature import ru.nobird.app.presentation.redux.feature.ReduxFeature @@ -33,16 +36,20 @@ internal object StageImplementFeatureBuilder { sentryInteractor: SentryInteractor, resourceProvider: ResourceProvider, currentProfileStateRepository: CurrentProfileStateRepository, - submissionRepository: SubmissionRepository, + stepCompletedFlow: StepCompletedFlow, logger: Logger, buildVariant: BuildVariant - ): Feature { - val analyticRoute = HyperskillAnalyticRoute.Projects.Stages.Implement(projectId = projectId, stageId = stageId) - val stageImplementReducer = StageImplementReducer(analyticRoute).wrapWithLogger(buildVariant, logger, LOG_TAG) + ): Feature { + val analyticRoute = HyperskillAnalyticRoute.Projects.Stages.Implement( + projectId = projectId, + stageId = stageId + ) + val stageImplementReducer = StageImplementReducer(analyticRoute) + .wrapWithLogger(buildVariant, logger, LOG_TAG) val stageImplementActionDispatcher = StageImplementActionDispatcher( ActionDispatcherOptions(), - submissionRepository, + stepCompletedFlow, currentProfileStateRepository, stagesInteractor, progressesInteractor, diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/stage_implement/presentation/StageImplementActionDispatcher.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/stage_implement/presentation/StageImplementActionDispatcher.kt index 18933b2abc..f983816952 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/stage_implement/presentation/StageImplementActionDispatcher.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/stage_implement/presentation/StageImplementActionDispatcher.kt @@ -18,12 +18,12 @@ import org.hyperskill.app.stage_implement.presentation.StageImplementFeature.Int import org.hyperskill.app.stage_implement.presentation.StageImplementFeature.InternalMessage import org.hyperskill.app.stage_implement.presentation.StageImplementFeature.Message import org.hyperskill.app.stages.domain.interactor.StagesInteractor -import org.hyperskill.app.step_quiz.domain.repository.SubmissionRepository +import org.hyperskill.app.step_completion.domain.flow.StepCompletedFlow import ru.nobird.app.presentation.redux.dispatcher.CoroutineActionDispatcher internal class StageImplementActionDispatcher( config: ActionDispatcherOptions, - submissionRepository: SubmissionRepository, + stepCompletedFlow: StepCompletedFlow, private val currentProfileStateRepository: CurrentProfileStateRepository, private val stagesInteractor: StagesInteractor, private val progressesInteractor: ProgressesInteractor, @@ -32,7 +32,7 @@ internal class StageImplementActionDispatcher( private val resourceProvider: ResourceProvider ) : CoroutineActionDispatcher(config.createConfig()) { init { - submissionRepository.solvedStepsMutableSharedFlow + stepCompletedFlow.observe() .onEach { onNewMessage(InternalMessage.StepSolved(it)) } .launchIn(actionScope) } diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/step_completion/data/flow/StepCompletedFlowImpl.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/step_completion/data/flow/StepCompletedFlowImpl.kt new file mode 100644 index 0000000000..e6098791e1 --- /dev/null +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/step_completion/data/flow/StepCompletedFlowImpl.kt @@ -0,0 +1,16 @@ +package org.hyperskill.app.step_completion.data.flow + +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableSharedFlow +import org.hyperskill.app.step_completion.domain.flow.StepCompletedFlow + +internal class StepCompletedFlowImpl : StepCompletedFlow { + private val stepSolvedMutableSharedFlow = MutableSharedFlow() + + override fun observe(): Flow = + stepSolvedMutableSharedFlow + + override suspend fun notifyDataChanged(data: Long) { + stepSolvedMutableSharedFlow.emit(data) + } +} \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/step_completion/domain/flow/StepCompletedFlow.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/step_completion/domain/flow/StepCompletedFlow.kt new file mode 100644 index 0000000000..7549344d95 --- /dev/null +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/step_completion/domain/flow/StepCompletedFlow.kt @@ -0,0 +1,5 @@ +package org.hyperskill.app.step_completion.domain.flow + +import org.hyperskill.app.core.domain.flow.SharedDataFlow + +interface StepCompletedFlow : SharedDataFlow \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/step_completion/injection/StepCompletionComponentImpl.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/step_completion/injection/StepCompletionComponentImpl.kt index 37080cab45..10b01c018e 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/step_completion/injection/StepCompletionComponentImpl.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/step_completion/injection/StepCompletionComponentImpl.kt @@ -16,7 +16,7 @@ internal class StepCompletionComponentImpl( override val stepCompletionActionDispatcher: StepCompletionActionDispatcher get() = StepCompletionActionDispatcher( ActionDispatcherOptions(), - submissionRepository = appGraph.submissionDataComponent.submissionRepository, + stepCompletedFlow = appGraph.stepCompletionFlowDataComponent.stepCompletedFlow, stepInteractor = appGraph.buildStepDataComponent().stepInteractor, progressesInteractor = appGraph.buildProgressesDataComponent().progressesInteractor, topicsRepository = appGraph.buildTopicsDataComponent().topicsRepository, diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/step_completion/injection/StepCompletionFlowDataComponent.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/step_completion/injection/StepCompletionFlowDataComponent.kt index 1cf8a7293c..270791bae8 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/step_completion/injection/StepCompletionFlowDataComponent.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/step_completion/injection/StepCompletionFlowDataComponent.kt @@ -1,9 +1,11 @@ package org.hyperskill.app.step_completion.injection import org.hyperskill.app.step_completion.domain.flow.DailyStepCompletedFlow +import org.hyperskill.app.step_completion.domain.flow.StepCompletedFlow import org.hyperskill.app.step_completion.domain.flow.TopicCompletedFlow interface StepCompletionFlowDataComponent { val topicCompletedFlow: TopicCompletedFlow val dailyStepCompletedFlow: DailyStepCompletedFlow + val stepCompletedFlow: StepCompletedFlow } \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/step_completion/injection/StepCompletionFlowDataComponentImpl.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/step_completion/injection/StepCompletionFlowDataComponentImpl.kt index f1d0b563f2..723d2575e3 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/step_completion/injection/StepCompletionFlowDataComponentImpl.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/step_completion/injection/StepCompletionFlowDataComponentImpl.kt @@ -1,8 +1,10 @@ package org.hyperskill.app.step_completion.injection import org.hyperskill.app.step_completion.data.flow.DailyStepCompletedFlowImpl +import org.hyperskill.app.step_completion.data.flow.StepCompletedFlowImpl import org.hyperskill.app.step_completion.data.flow.TopicCompletedFlowImpl import org.hyperskill.app.step_completion.domain.flow.DailyStepCompletedFlow +import org.hyperskill.app.step_completion.domain.flow.StepCompletedFlow import org.hyperskill.app.step_completion.domain.flow.TopicCompletedFlow internal class StepCompletionFlowDataComponentImpl : StepCompletionFlowDataComponent { @@ -11,4 +13,7 @@ internal class StepCompletionFlowDataComponentImpl : StepCompletionFlowDataCompo override val dailyStepCompletedFlow: DailyStepCompletedFlow = DailyStepCompletedFlowImpl() + + override val stepCompletedFlow: StepCompletedFlow = + StepCompletedFlowImpl() } \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/step_completion/presentation/StepCompletionActionDispatcher.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/step_completion/presentation/StepCompletionActionDispatcher.kt index 46e88291ff..dd9078efff 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/step_completion/presentation/StepCompletionActionDispatcher.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/step_completion/presentation/StepCompletionActionDispatcher.kt @@ -26,12 +26,12 @@ import org.hyperskill.app.step.domain.model.StepRoute import org.hyperskill.app.step_completion.domain.analytic.StepCompletionStepSolvedAppsFlyerAnalyticEvent import org.hyperskill.app.step_completion.domain.analytic.StepCompletionTopicCompletedAppsFlyerAnalyticEvent import org.hyperskill.app.step_completion.domain.flow.DailyStepCompletedFlow +import org.hyperskill.app.step_completion.domain.flow.StepCompletedFlow import org.hyperskill.app.step_completion.domain.flow.TopicCompletedFlow import org.hyperskill.app.step_completion.presentation.StepCompletionFeature.Action import org.hyperskill.app.step_completion.presentation.StepCompletionFeature.InternalAction import org.hyperskill.app.step_completion.presentation.StepCompletionFeature.InternalMessage import org.hyperskill.app.step_completion.presentation.StepCompletionFeature.Message -import org.hyperskill.app.step_quiz.domain.repository.SubmissionRepository import org.hyperskill.app.streaks.domain.model.StreakState import org.hyperskill.app.subscriptions.domain.interactor.SubscriptionsInteractor import org.hyperskill.app.topics.domain.repository.TopicsRepository @@ -40,7 +40,7 @@ import ru.nobird.app.presentation.redux.dispatcher.CoroutineActionDispatcher class StepCompletionActionDispatcher( config: ActionDispatcherOptions, - submissionRepository: SubmissionRepository, + stepCompletedFlow: StepCompletedFlow, private val stepInteractor: StepInteractor, private val progressesInteractor: ProgressesInteractor, private val topicsRepository: TopicsRepository, @@ -60,7 +60,7 @@ class StepCompletionActionDispatcher( ) : CoroutineActionDispatcher(config.createConfig()) { init { - submissionRepository.solvedStepsSharedFlow + stepCompletedFlow.observe() .onEach(::handleStepSolved) .launchIn(actionScope) } @@ -101,7 +101,7 @@ class StepCompletionActionDispatcher( handleCheckTopicCompletionStatusAction(action, ::onNewMessage) } is Action.UpdateProblemsLimit -> { - subscriptionsInteractor.onStepSolved() + subscriptionsInteractor.chargeProblemsLimits(action.chargeStrategy) } is Action.UpdateLastTimeShareStreakShown -> { shareStreakInteractor.setLastTimeShareStreakShown() diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/step_completion/presentation/StepCompletionFeature.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/step_completion/presentation/StepCompletionFeature.kt index b6ac67fd05..7a9f52dd51 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/step_completion/presentation/StepCompletionFeature.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/step_completion/presentation/StepCompletionFeature.kt @@ -5,6 +5,7 @@ import org.hyperskill.app.analytic.domain.model.AnalyticEvent import org.hyperskill.app.learning_activities.domain.model.LearningActivity import org.hyperskill.app.step.domain.model.Step import org.hyperskill.app.step.domain.model.StepRoute +import org.hyperskill.app.subscriptions.domain.model.FreemiumChargeLimitsStrategy object StepCompletionFeature { fun createState(step: Step, stepRoute: StepRoute): State = @@ -145,7 +146,7 @@ object StepCompletionFeature { data class CheckTopicCompletionStatus(val topicId: Long) : Action - object UpdateProblemsLimit : Action + data class UpdateProblemsLimit(val chargeStrategy: FreemiumChargeLimitsStrategy) : Action object UpdateLastTimeShareStreakShown : Action diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/step_completion/presentation/StepCompletionReducer.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/step_completion/presentation/StepCompletionReducer.kt index e3bdc043e0..51213e6b0d 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/step_completion/presentation/StepCompletionReducer.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/step_completion/presentation/StepCompletionReducer.kt @@ -27,6 +27,8 @@ import org.hyperskill.app.step_completion.presentation.StepCompletionFeature.Int import org.hyperskill.app.step_completion.presentation.StepCompletionFeature.InternalMessage import org.hyperskill.app.step_completion.presentation.StepCompletionFeature.Message import org.hyperskill.app.step_completion.presentation.StepCompletionFeature.State +import org.hyperskill.app.step_quiz.presentation.StepQuizResolver +import org.hyperskill.app.subscriptions.domain.model.FreemiumChargeLimitsStrategy import ru.nobird.app.presentation.redux.reducer.StateReducer private typealias StepCompletionReducerResult = Pair> @@ -297,15 +299,14 @@ class StepCompletionReducer(private val stepRoute: StepRoute) : StateReducer - state to setOf(Action.UpdateProblemsLimit) - is StepRoute.InterviewPreparation -> - state to setOf( - Action.UpdateProblemsLimit, - InternalAction.MarkInterviewStepAsSolved(message.stepId) - ) - else -> state to emptySet() + state to buildSet { + if (StepQuizResolver.isStepHasLimitedAttempts(stepRoute)) { + add(Action.UpdateProblemsLimit(FreemiumChargeLimitsStrategy.AFTER_CORRECT_SUBMISSION)) + } + + if (stepRoute is StepRoute.InterviewPreparation) { + add(InternalAction.MarkInterviewStepAsSolved(message.stepId)) + } } } else { state to emptySet() diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz/data/repository/SubmissionRepositoryImpl.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz/data/repository/SubmissionRepositoryImpl.kt index 9bdb954a27..c693665684 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz/data/repository/SubmissionRepositoryImpl.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz/data/repository/SubmissionRepositoryImpl.kt @@ -1,6 +1,5 @@ package org.hyperskill.app.step_quiz.data.repository -import kotlinx.coroutines.flow.MutableSharedFlow import org.hyperskill.app.step.domain.model.StepContext import org.hyperskill.app.step_quiz.data.source.SubmissionCacheDataSource import org.hyperskill.app.step_quiz.data.source.SubmissionRemoteDataSource @@ -8,12 +7,10 @@ import org.hyperskill.app.step_quiz.domain.model.submissions.Reply import org.hyperskill.app.step_quiz.domain.model.submissions.Submission import org.hyperskill.app.step_quiz.domain.repository.SubmissionRepository -class SubmissionRepositoryImpl( +internal class SubmissionRepositoryImpl( private val submissionRemoteDataSource: SubmissionRemoteDataSource, private val submissionCacheDataSource: SubmissionCacheDataSource ) : SubmissionRepository { - override val solvedStepsMutableSharedFlow: MutableSharedFlow = MutableSharedFlow() - override suspend fun getSubmissionsForStep( stepId: Long, userId: Long, @@ -31,9 +28,8 @@ class SubmissionRepositoryImpl( ): Result = submissionRemoteDataSource.createSubmission(attemptId, reply, solvingContext) - override suspend fun notifyStepSolved(stepId: Long) { + override fun incrementSolvedStepsCount() { submissionCacheDataSource.incrementSolvedStepsCount() - solvedStepsMutableSharedFlow.emit(stepId) } override fun getSolvedStepsCount(): Long = diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz/domain/interactor/StepQuizInteractor.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz/domain/interactor/StepQuizInteractor.kt index 094b3f4d58..ce918bb4e2 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz/domain/interactor/StepQuizInteractor.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz/domain/interactor/StepQuizInteractor.kt @@ -3,6 +3,7 @@ package org.hyperskill.app.step_quiz.domain.interactor import kotlin.time.Duration.Companion.seconds import kotlinx.coroutines.delay import org.hyperskill.app.step.domain.model.StepContext +import org.hyperskill.app.step_completion.domain.flow.StepCompletedFlow import org.hyperskill.app.step_quiz.domain.model.attempts.Attempt import org.hyperskill.app.step_quiz.domain.model.attempts.AttemptStatus import org.hyperskill.app.step_quiz.domain.model.submissions.Reply @@ -13,7 +14,8 @@ import org.hyperskill.app.step_quiz.domain.repository.SubmissionRepository class StepQuizInteractor( private val attemptRepository: AttemptRepository, - private val submissionRepository: SubmissionRepository + private val submissionRepository: SubmissionRepository, + private val stepCompletedFlow: StepCompletedFlow ) { companion object { private val POLL_SUBMISSION_INTERVAL = 1.seconds @@ -59,7 +61,8 @@ class StepQuizInteractor( } if (evaluatedSubmission.status == SubmissionStatus.CORRECT) { - submissionRepository.notifyStepSolved(stepId) + submissionRepository.incrementSolvedStepsCount() + stepCompletedFlow.notifyDataChanged(stepId) } return Result.success(evaluatedSubmission) diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz/domain/repository/SubmissionRepository.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz/domain/repository/SubmissionRepository.kt index de62be087f..a62861a05d 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz/domain/repository/SubmissionRepository.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz/domain/repository/SubmissionRepository.kt @@ -1,17 +1,10 @@ package org.hyperskill.app.step_quiz.domain.repository -import kotlinx.coroutines.flow.MutableSharedFlow -import kotlinx.coroutines.flow.SharedFlow import org.hyperskill.app.step.domain.model.StepContext import org.hyperskill.app.step_quiz.domain.model.submissions.Reply import org.hyperskill.app.step_quiz.domain.model.submissions.Submission interface SubmissionRepository { - val solvedStepsMutableSharedFlow: MutableSharedFlow - - val solvedStepsSharedFlow: SharedFlow - get() = solvedStepsMutableSharedFlow - suspend fun getSubmissionsForStep( stepId: Long, userId: Long, @@ -35,7 +28,6 @@ interface SubmissionRepository { solvingContext: StepContext ): Result - suspend fun notifyStepSolved(stepId: Long) - + fun incrementSolvedStepsCount() fun getSolvedStepsCount(): Long } \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz/injection/StepQuizComponentImpl.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz/injection/StepQuizComponentImpl.kt index 6d3e569a07..1aebb2d1d5 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz/injection/StepQuizComponentImpl.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz/injection/StepQuizComponentImpl.kt @@ -30,16 +30,16 @@ internal class StepQuizComponentImpl( override val stepQuizTitleMapper: StepQuizTitleMapper get() = StepQuizTitleMapper(appGraph.commonComponent.resourceProvider) - private val attemptRemoteDataSource: AttemptRemoteDataSource = AttemptRemoteDataSourceImpl( - appGraph.networkComponent.authorizedHttpClient - ) + private val attemptRemoteDataSource: AttemptRemoteDataSource = + AttemptRemoteDataSourceImpl(appGraph.networkComponent.authorizedHttpClient) private val attemptRepository: AttemptRepository = AttemptRepositoryImpl(attemptRemoteDataSource) override val stepQuizInteractor: StepQuizInteractor = StepQuizInteractor( - attemptRepository, - appGraph.submissionDataComponent.submissionRepository + attemptRepository = attemptRepository, + submissionRepository = appGraph.buildSubmissionDataComponent().submissionRepository, + stepCompletedFlow = appGraph.stepCompletionFlowDataComponent.stepCompletedFlow ) private val stepQuizHintsComponent: StepQuizHintsComponent = @@ -50,8 +50,8 @@ internal class StepQuizComponentImpl( stepRoute = stepRoute, stepQuizInteractor = stepQuizInteractor, stepQuizReplyValidator = stepQuizReplyValidator, + subscriptionsInteractor = appGraph.subscriptionDataComponent.subscriptionsInteractor, currentProfileStateRepository = appGraph.profileDataComponent.currentProfileStateRepository, - currentSubscriptionStateRepository = appGraph.stateRepositoriesComponent.currentSubscriptionStateRepository, urlPathProcessor = appGraph.buildMagicLinksDataComponent().urlPathProcessor, analyticInteractor = appGraph.analyticComponent.analyticInteractor, sentryInteractor = appGraph.sentryComponent.sentryInteractor, diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz/injection/StepQuizFeatureBuilder.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz/injection/StepQuizFeatureBuilder.kt index efb1430572..4dab112cc5 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz/injection/StepQuizFeatureBuilder.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz/injection/StepQuizFeatureBuilder.kt @@ -20,7 +20,7 @@ import org.hyperskill.app.step_quiz.presentation.StepQuizReducer import org.hyperskill.app.step_quiz_hints.presentation.StepQuizHintsActionDispatcher import org.hyperskill.app.step_quiz_hints.presentation.StepQuizHintsFeature import org.hyperskill.app.step_quiz_hints.presentation.StepQuizHintsReducer -import org.hyperskill.app.subscriptions.domain.repository.CurrentSubscriptionStateRepository +import org.hyperskill.app.subscriptions.domain.interactor.SubscriptionsInteractor import ru.nobird.app.core.model.safeCast import ru.nobird.app.presentation.redux.dispatcher.transform import ru.nobird.app.presentation.redux.dispatcher.wrapWithActionDispatcher @@ -34,8 +34,8 @@ internal object StepQuizFeatureBuilder { stepRoute: StepRoute, stepQuizInteractor: StepQuizInteractor, stepQuizReplyValidator: StepQuizReplyValidator, + subscriptionsInteractor: SubscriptionsInteractor, currentProfileStateRepository: CurrentProfileStateRepository, - currentSubscriptionStateRepository: CurrentSubscriptionStateRepository, urlPathProcessor: UrlPathProcessor, analyticInteractor: AnalyticInteractor, sentryInteractor: SentryInteractor, @@ -55,8 +55,8 @@ internal object StepQuizFeatureBuilder { config = ActionDispatcherOptions(), stepQuizInteractor = stepQuizInteractor, stepQuizReplyValidator = stepQuizReplyValidator, + subscriptionsInteractor = subscriptionsInteractor, currentProfileStateRepository = currentProfileStateRepository, - currentSubscriptionStateRepository = currentSubscriptionStateRepository, urlPathProcessor = urlPathProcessor, analyticInteractor = analyticInteractor, sentryInteractor = sentryInteractor, diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz/presentation/StepQuizActionDispatcher.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz/presentation/StepQuizActionDispatcher.kt index afd8739617..7583ff8689 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz/presentation/StepQuizActionDispatcher.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz/presentation/StepQuizActionDispatcher.kt @@ -8,8 +8,11 @@ import org.hyperskill.app.core.presentation.ActionDispatcherOptions import org.hyperskill.app.core.view.mapper.ResourceProvider import org.hyperskill.app.magic_links.domain.interactor.UrlPathProcessor import org.hyperskill.app.onboarding.domain.interactor.OnboardingInteractor +import org.hyperskill.app.profile.domain.model.Profile +import org.hyperskill.app.profile.domain.model.isFreemiumWrongSubmissionChargeLimitsEnabled import org.hyperskill.app.profile.domain.model.isMobileOnlySubscriptionEnabled import org.hyperskill.app.profile.domain.repository.CurrentProfileStateRepository +import org.hyperskill.app.profile.domain.repository.isFreemiumWrongSubmissionChargeLimitsEnabled import org.hyperskill.app.sentry.domain.interactor.SentryInteractor import org.hyperskill.app.sentry.domain.model.transaction.HyperskillSentryTransactionBuilder import org.hyperskill.app.sentry.domain.withTransaction @@ -23,18 +26,18 @@ import org.hyperskill.app.step_quiz.presentation.StepQuizFeature.InternalAction import org.hyperskill.app.step_quiz.presentation.StepQuizFeature.InternalMessage import org.hyperskill.app.step_quiz.presentation.StepQuizFeature.Message import org.hyperskill.app.step_quiz_fill_blanks.model.FillBlanksMode +import org.hyperskill.app.subscriptions.domain.interactor.SubscriptionsInteractor import org.hyperskill.app.subscriptions.domain.model.Subscription import org.hyperskill.app.subscriptions.domain.model.isFreemium -import org.hyperskill.app.subscriptions.domain.model.isProblemLimitReached -import org.hyperskill.app.subscriptions.domain.repository.CurrentSubscriptionStateRepository +import org.hyperskill.app.subscriptions.domain.model.isProblemsLimitReached import ru.nobird.app.presentation.redux.dispatcher.CoroutineActionDispatcher internal class StepQuizActionDispatcher( config: ActionDispatcherOptions, private val stepQuizInteractor: StepQuizInteractor, private val stepQuizReplyValidator: StepQuizReplyValidator, + private val subscriptionsInteractor: SubscriptionsInteractor, private val currentProfileStateRepository: CurrentProfileStateRepository, - private val currentSubscriptionStateRepository: CurrentSubscriptionStateRepository, private val urlPathProcessor: UrlPathProcessor, private val analyticInteractor: AnalyticInteractor, private val sentryInteractor: SentryInteractor, @@ -155,6 +158,8 @@ internal class StepQuizActionDispatcher( } } } + is InternalAction.UpdateProblemsLimit -> + handleUpdateProblemsLimitAction(action, ::onNewMessage) is InternalAction.CreateMagicLinkForUnsupportedQuiz -> { urlPathProcessor .processUrlPath(HyperskillUrlPath.Step(action.stepRoute)) @@ -175,17 +180,14 @@ internal class StepQuizActionDispatcher( ) { sentryInteractor.withTransaction( transaction = HyperskillSentryTransactionBuilder.buildStepQuizScreenRemoteDataLoading(), - onError = { - Message.FetchAttemptError(it) - } + onError = { Message.FetchAttemptError(it) } ) { - val currentProfile = - currentProfileStateRepository - .getState() + val currentSubscription = + subscriptionsInteractor.getCurrentSubscription() .getOrThrow() - val subscription = - currentSubscriptionStateRepository + val currentProfile = + currentProfileStateRepository .getState() .getOrThrow() @@ -198,21 +200,22 @@ internal class StepQuizActionDispatcher( getSubmissionState(attempt.id, action.step.id, currentProfile.id) .getOrThrow() - val isSubscriptionPurchaseEnabled = - platform.isSubscriptionPurchaseEnabled && - currentProfile.features.isMobileOnlySubscriptionEnabled && - subscription.isFreemium + val isProblemsLimitReached = currentSubscription.isProblemsLimitReached + val problemsLimitReachedModalData = if (isProblemsLimitReached) { + getProblemsLimitReachedModalData( + currentSubscription, + isSubscriptionPurchaseEnabled(currentProfile, currentSubscription) + ) + } else { + null + } Message.FetchAttemptSuccess( step = action.step, attempt = attempt, submissionState = submissionState, - isProblemsLimitReached = subscription.isProblemLimitReached, - problemsLimitReachedModalText = getProblemsLimitReachedModalText( - subscription, - isSubscriptionPurchaseEnabled - ), - isSubscriptionPurchaseEnabled = isSubscriptionPurchaseEnabled, + isProblemsLimitReached = isProblemsLimitReached, + problemsLimitReachedModalData = problemsLimitReachedModalData, problemsOnboardingFlags = onboardingInteractor.getProblemsOnboardingFlags() ) }.let(onNewMessage) @@ -233,18 +236,88 @@ internal class StepQuizActionDispatcher( } } - private fun getProblemsLimitReachedModalText( + internal fun isSubscriptionPurchaseEnabled( + currentProfile: Profile, + currentSubscription: Subscription + ): Boolean = + platform.isSubscriptionPurchaseEnabled && + currentProfile.features.isMobileOnlySubscriptionEnabled && + currentSubscription.isFreemium + + private suspend fun getProblemsLimitReachedModalData( subscription: Subscription, isSubscriptionPurchaseEnabled: Boolean - ): String? = - subscription.stepsLimitTotal?.let { stepsLimitTotal -> - resourceProvider.getString( - if (isSubscriptionPurchaseEnabled) { - SharedResources.strings.problems_limit_reached_modal_unlock_unlimited_problems_description + ): StepQuizFeature.ProblemsLimitReachedModalData? { + val stepsLimitTotal = subscription.stepsLimitTotal ?: return null + + return if (currentProfileStateRepository.isFreemiumWrongSubmissionChargeLimitsEnabled()) { + StepQuizFeature.ProblemsLimitReachedModalData( + title = resourceProvider.getString( + SharedResources.strings.problems_limit_reached_modal_no_lives_left_title, + stepsLimitTotal + ), + description = resourceProvider.getString( + if (isSubscriptionPurchaseEnabled) { + SharedResources.strings.problems_limit_reached_modal_unlock_unlimited_lives_description + } else { + SharedResources.strings.problems_limit_reached_modal_no_lives_left_description + } + ), + unlockLimitsButtonText = if (isSubscriptionPurchaseEnabled) { + resourceProvider.getString( + SharedResources.strings.problems_limit_reached_modal_unlock_unlimited_lives_button + ) + } else { + null + } + ) + } else { + StepQuizFeature.ProblemsLimitReachedModalData( + title = resourceProvider.getString(SharedResources.strings.problems_limit_reached_modal_title), + description = resourceProvider.getString( + if (isSubscriptionPurchaseEnabled) { + SharedResources.strings.problems_limit_reached_modal_unlock_unlimited_problems_description + } else { + SharedResources.strings.problems_limit_reached_modal_description + }, + stepsLimitTotal + ), + unlockLimitsButtonText = if (isSubscriptionPurchaseEnabled) { + resourceProvider.getString( + SharedResources.strings.problems_limit_reached_modal_unlock_unlimited_problems_button + ) } else { - SharedResources.strings.problems_limit_reached_modal_description - }, - stepsLimitTotal + null + } ) } + } + + private suspend fun handleUpdateProblemsLimitAction( + action: InternalAction.UpdateProblemsLimit, + onNewMessage: (Message) -> Unit + ) { + val currentProfile = currentProfileStateRepository.getState().getOrElse { return } + if (!currentProfile.features.isFreemiumWrongSubmissionChargeLimitsEnabled) return + + subscriptionsInteractor.chargeProblemsLimits(action.chargeStrategy) + + val subscription = subscriptionsInteractor.getCurrentSubscription().getOrElse { return } + val problemsLimitReachedModalData = + if (subscription.isProblemsLimitReached) { + getProblemsLimitReachedModalData( + subscription = subscription, + isSubscriptionPurchaseEnabled = isSubscriptionPurchaseEnabled(currentProfile, subscription) + ) + } else { + null + } + + onNewMessage( + InternalMessage.UpdateProblemsLimitResult( + isProblemsLimitReached = subscription.isProblemsLimitReached, + problemsLimitReachedModalData = problemsLimitReachedModalData + ) + ) + } } \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz/presentation/StepQuizFeature.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz/presentation/StepQuizFeature.kt index 3bd84f786d..8351632140 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz/presentation/StepQuizFeature.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz/presentation/StepQuizFeature.kt @@ -13,6 +13,7 @@ import org.hyperskill.app.step_quiz.domain.model.submissions.Submission import org.hyperskill.app.step_quiz.domain.validation.ReplyValidationResult import org.hyperskill.app.step_quiz_fill_blanks.model.FillBlanksMode import org.hyperskill.app.step_quiz_hints.presentation.StepQuizHintsFeature +import org.hyperskill.app.subscriptions.domain.model.FreemiumChargeLimitsStrategy object StepQuizFeature { data class State( @@ -52,6 +53,13 @@ object StepQuizFeature { data class FillBlanks(val mode: FillBlanksMode) : ProblemOnboardingModal } + @Serializable + data class ProblemsLimitReachedModalData( + val title: String, + val description: String, + val unlockLimitsButtonText: String? + ) + sealed interface Message { data class InitWithStep(val step: Step, val forceUpdate: Boolean = false) : Message data class FetchAttemptSuccess( @@ -59,8 +67,7 @@ object StepQuizFeature { val attempt: Attempt, val submissionState: SubmissionState, val isProblemsLimitReached: Boolean, - val problemsLimitReachedModalText: String?, - val isSubscriptionPurchaseEnabled: Boolean, + val problemsLimitReachedModalData: ProblemsLimitReachedModalData?, val problemsOnboardingFlags: ProblemsOnboardingFlags ) : Message data class FetchAttemptError(val throwable: Throwable) : Message @@ -144,6 +151,11 @@ object StepQuizFeature { } internal sealed interface InternalMessage : Message { + data class UpdateProblemsLimitResult( + val isProblemsLimitReached: Boolean, + val problemsLimitReachedModalData: ProblemsLimitReachedModalData? + ) : InternalMessage + object CreateMagicLinkForUnsupportedQuizError : InternalMessage data class CreateMagicLinkForUnsupportedQuizSuccess(val url: String) : InternalMessage } @@ -184,10 +196,7 @@ object StepQuizFeature { object RequestResetCode : ViewAction - data class ShowProblemsLimitReachedModal( - val modalText: String, - val isUnlockUnlimitedProblemsButtonVisible: Boolean - ) : ViewAction + data class ShowProblemsLimitReachedModal(val modalData: ProblemsLimitReachedModalData) : ViewAction data class ShowProblemOnboardingModal(val modalType: ProblemOnboardingModal) : ViewAction @@ -214,6 +223,8 @@ object StepQuizFeature { } internal sealed interface InternalAction : Action { + data class UpdateProblemsLimit(val chargeStrategy: FreemiumChargeLimitsStrategy) : InternalAction + data class CreateMagicLinkForUnsupportedQuiz(val stepRoute: StepRoute) : InternalAction } } \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz/presentation/StepQuizReducer.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz/presentation/StepQuizReducer.kt index 01340ae6ad..b2b36cb701 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz/presentation/StepQuizReducer.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz/presentation/StepQuizReducer.kt @@ -39,6 +39,7 @@ import org.hyperskill.app.step_quiz_fill_blanks.model.FillBlanksMode import org.hyperskill.app.step_quiz_fill_blanks.presentation.FillBlanksResolver import org.hyperskill.app.step_quiz_hints.presentation.StepQuizHintsFeature import org.hyperskill.app.step_quiz_hints.presentation.StepQuizHintsReducer +import org.hyperskill.app.subscriptions.domain.model.FreemiumChargeLimitsStrategy import ru.nobird.app.presentation.redux.reducer.StateReducer internal typealias StepQuizReducerResult = Pair> @@ -162,7 +163,13 @@ internal class StepQuizReducer( attempt = message.newAttempt ?: state.stepQuizState.attempt, submissionState = StepQuizFeature.SubmissionState.Loaded(message.submission) ) - ) to emptySet() + ) to buildSet { + if (message.submission.status == SubmissionStatus.WRONG && + StepQuizResolver.isStepHasLimitedAttempts(stepRoute) + ) { + add(InternalAction.UpdateProblemsLimit(FreemiumChargeLimitsStrategy.AFTER_WRONG_SUBMISSION)) + } + } } else { null } @@ -211,6 +218,8 @@ internal class StepQuizReducer( null } } + is InternalMessage.UpdateProblemsLimitResult -> + handleUpdateProblemsLimitResult(state, message) is Message.ProblemsLimitReachedModalGoToHomeScreenClicked -> state to setOf( Action.ViewAction.NavigateTo.Home, @@ -356,18 +365,12 @@ internal class StepQuizReducer( if (StepQuizResolver.isIdeRequired(message.step, message.submissionState)) { state.copy(stepQuizState = StepQuizState.Unsupported) to emptySet() } else { - val isProblemsLimitReached = when (stepRoute) { - is StepRoute.Repeat, - is StepRoute.LearnDaily -> false - else -> message.isProblemsLimitReached - } + val isProblemsLimitReached = + StepQuizResolver.isStepHasLimitedAttempts(stepRoute) && message.isProblemsLimitReached - val actions = if (isProblemsLimitReached && message.problemsLimitReachedModalText != null) { + val actions = if (isProblemsLimitReached && message.problemsLimitReachedModalData != null) { setOf( - Action.ViewAction.ShowProblemsLimitReachedModal( - modalText = message.problemsLimitReachedModalText, - isUnlockUnlimitedProblemsButtonVisible = message.isSubscriptionPurchaseEnabled - ) + Action.ViewAction.ShowProblemsLimitReachedModal(message.problemsLimitReachedModalData) ) } else { getProblemOnboardingModalActions( @@ -418,6 +421,27 @@ internal class StepQuizReducer( ) to stepQuizActions + stepQuizHintsActions } + private fun handleUpdateProblemsLimitResult( + state: State, + message: InternalMessage.UpdateProblemsLimitResult + ): StepQuizReducerResult? = + if (state.stepQuizState is StepQuizState.AttemptLoaded) { + val isProblemsLimitReached = + StepQuizResolver.isStepHasLimitedAttempts(stepRoute) && message.isProblemsLimitReached + + state.copy( + stepQuizState = state.stepQuizState.copy( + isProblemsLimitReached = isProblemsLimitReached + ) + ) to buildSet { + if (isProblemsLimitReached && message.problemsLimitReachedModalData != null) { + add(Action.ViewAction.ShowProblemsLimitReachedModal(message.problemsLimitReachedModalData)) + } + } + } else { + null + } + private fun handleTheoryToolbarItemClicked(state: State): StepQuizReducerResult = if (state.stepQuizState is StepQuizState.AttemptLoaded && state.stepQuizState.isTheoryAvailable diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz/presentation/StepQuizResolver.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz/presentation/StepQuizResolver.kt index aefcbc4779..03de2864dc 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz/presentation/StepQuizResolver.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz/presentation/StepQuizResolver.kt @@ -123,6 +123,21 @@ object StepQuizResolver { } } + /** + * @return Returns `true` if step route has limited number of attempts. + */ + internal fun isStepHasLimitedAttempts(stepRoute: StepRoute): Boolean = + when (stepRoute) { + is StepRoute.Learn.Step, + is StepRoute.InterviewPreparation, + is StepRoute.StageImplement -> true + is StepRoute.Learn.TheoryOpenedFromPractice, + is StepRoute.Learn.TheoryOpenedFromSearch, + is StepRoute.LearnDaily, + is StepRoute.Repeat.Practice, + is StepRoute.Repeat.Theory -> false + } + /** * Is used to avoid unnecessary UI re-rendering each time on reply change. */ diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/subscriptions/domain/interactor/SubscriptionsInteractor.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/subscriptions/domain/interactor/SubscriptionsInteractor.kt index b8e220610e..d5cfe32e30 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/subscriptions/domain/interactor/SubscriptionsInteractor.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/subscriptions/domain/interactor/SubscriptionsInteractor.kt @@ -16,11 +16,13 @@ import org.hyperskill.app.auth.domain.interactor.AuthInteractor import org.hyperskill.app.core.domain.repository.updateState import org.hyperskill.app.profile.domain.model.isFreemiumIncreaseLimitsForFirstStepCompletionEnabled import org.hyperskill.app.profile.domain.repository.CurrentProfileStateRepository +import org.hyperskill.app.profile.domain.repository.isFreemiumWrongSubmissionChargeLimitsEnabled +import org.hyperskill.app.subscriptions.domain.model.FreemiumChargeLimitsStrategy import org.hyperskill.app.subscriptions.domain.model.Subscription import org.hyperskill.app.subscriptions.domain.model.SubscriptionType -import org.hyperskill.app.subscriptions.domain.model.areProblemsLimited import org.hyperskill.app.subscriptions.domain.model.isActive import org.hyperskill.app.subscriptions.domain.repository.CurrentSubscriptionStateRepository +import org.hyperskill.app.subscriptions.domain.repository.areProblemsLimited class SubscriptionsInteractor( private val currentSubscriptionStateRepository: CurrentSubscriptionStateRepository, @@ -37,31 +39,62 @@ class SubscriptionsInteractor( private var refreshMobileOnlySubscriptionJob: Job? = null - suspend fun onStepSolved() { - currentSubscriptionStateRepository.updateState { subscription -> - if (subscription.areProblemsLimited) { - subscription.copy(stepsLimitLeft = subscription.stepsLimitLeft?.dec()) - } else { - subscription + // Problems limits + + suspend fun getCurrentSubscription(): Result = + currentSubscriptionStateRepository.getState(forceUpdate = false) + + suspend fun chargeProblemsLimits(chargeStrategy: FreemiumChargeLimitsStrategy) { + if (currentSubscriptionStateRepository.areProblemsLimited()) { + when (chargeStrategy) { + FreemiumChargeLimitsStrategy.AFTER_WRONG_SUBMISSION -> chargeLimitsAfterWrongSubmission() + FreemiumChargeLimitsStrategy.AFTER_CORRECT_SUBMISSION -> chargeLimitsAfterCorrectSubmission() } } - currentProfileStateRepository.getState().onSuccess { currentProfile -> - if (currentProfile.features.isFreemiumIncreaseLimitsForFirstStepCompletionEnabled && - currentProfile.gamification.passedProblems == 0 - ) { - currentSubscriptionStateRepository.updateState { - it.copy( - stepsLimitTotal = it.stepsLimitTotal?.plus(SOLVING_FIRST_STEP_ADDITIONAL_LIMIT_VALUE), - stepsLimitLeft = it.stepsLimitLeft?.plus(SOLVING_FIRST_STEP_ADDITIONAL_LIMIT_VALUE) - ) - } - currentProfileStateRepository.updateState { - it.copy(gamification = it.gamification.copy(passedProblems = it.gamification.passedProblems + 1)) - } + } + + private suspend fun chargeLimitsAfterWrongSubmission() { + if (currentProfileStateRepository.isFreemiumWrongSubmissionChargeLimitsEnabled()) { + decreaseStepsLimitLeft() + } + } + + private suspend fun decreaseStepsLimitLeft() { + currentSubscriptionStateRepository.updateState { subscription -> + subscription.copy(stepsLimitLeft = subscription.stepsLimitLeft?.dec()) + } + } + + private suspend fun chargeLimitsAfterCorrectSubmission() { + increaseLimitsForFirstStepCompletionIfNeeded() + + if (!currentProfileStateRepository.isFreemiumWrongSubmissionChargeLimitsEnabled()) { + decreaseStepsLimitLeft() + } + } + + private suspend fun increaseLimitsForFirstStepCompletionIfNeeded() { + val currentProfile = currentProfileStateRepository + .getState(forceUpdate = false) + .getOrElse { return } + + if (currentProfile.features.isFreemiumIncreaseLimitsForFirstStepCompletionEnabled && + currentProfile.gamification.passedProblems == 0 + ) { + currentSubscriptionStateRepository.updateState { + it.copy( + stepsLimitTotal = it.stepsLimitTotal?.plus(SOLVING_FIRST_STEP_ADDITIONAL_LIMIT_VALUE), + stepsLimitLeft = it.stepsLimitLeft?.plus(SOLVING_FIRST_STEP_ADDITIONAL_LIMIT_VALUE) + ) + } + currentProfileStateRepository.updateState { + it.copy(gamification = it.gamification.copy(passedProblems = it.gamification.passedProblems + 1)) } } } + // Refresh mobile only subscription + suspend fun refreshSubscriptionOnExpirationIfNeeded(subscription: Subscription) { cancelSubscriptionRefresh() diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/subscriptions/domain/model/FreemiumChargeLimitsStrategy.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/subscriptions/domain/model/FreemiumChargeLimitsStrategy.kt new file mode 100644 index 0000000000..6dec69d777 --- /dev/null +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/subscriptions/domain/model/FreemiumChargeLimitsStrategy.kt @@ -0,0 +1,6 @@ +package org.hyperskill.app.subscriptions.domain.model + +enum class FreemiumChargeLimitsStrategy { + AFTER_WRONG_SUBMISSION, + AFTER_CORRECT_SUBMISSION +} \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/subscriptions/domain/model/Subscription.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/subscriptions/domain/model/Subscription.kt index 45bc7f2501..d46e20ea87 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/subscriptions/domain/model/Subscription.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/subscriptions/domain/model/Subscription.kt @@ -42,7 +42,7 @@ internal val Subscription.areProblemsLimited: Boolean else -> type.areProblemsLimited } -internal val Subscription.isProblemLimitReached: Boolean +internal val Subscription.isProblemsLimitReached: Boolean get() = areProblemsLimited && stepsLimitLeft == 0 internal val Subscription.isFreemium: Boolean diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/subscriptions/domain/repository/CurrentSubscriptionStateRepository.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/subscriptions/domain/repository/CurrentSubscriptionStateRepository.kt index 2a95409c46..9c1ef376ca 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/subscriptions/domain/repository/CurrentSubscriptionStateRepository.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/subscriptions/domain/repository/CurrentSubscriptionStateRepository.kt @@ -2,5 +2,11 @@ package org.hyperskill.app.subscriptions.domain.repository import org.hyperskill.app.core.domain.repository.StateRepository import org.hyperskill.app.subscriptions.domain.model.Subscription +import org.hyperskill.app.subscriptions.domain.model.areProblemsLimited -interface CurrentSubscriptionStateRepository : StateRepository \ No newline at end of file +interface CurrentSubscriptionStateRepository : StateRepository + +internal suspend fun CurrentSubscriptionStateRepository.areProblemsLimited(): Boolean = + getState(forceUpdate = false) + .map { it.areProblemsLimited } + .getOrDefault(defaultValue = false) \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/topics_repetitions/injection/TopicsRepetitionsComponentImpl.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/topics_repetitions/injection/TopicsRepetitionsComponentImpl.kt index 1e28630721..e306c3d25c 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/topics_repetitions/injection/TopicsRepetitionsComponentImpl.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/topics_repetitions/injection/TopicsRepetitionsComponentImpl.kt @@ -7,7 +7,7 @@ import org.hyperskill.app.topics_repetitions.presentation.TopicsRepetitionsFeatu import org.hyperskill.app.topics_repetitions.view.mapper.TopicsRepetitionsViewDataMapper import ru.nobird.app.presentation.redux.feature.Feature -class TopicsRepetitionsComponentImpl(private val appGraph: AppGraph) : TopicsRepetitionsComponent { +internal class TopicsRepetitionsComponentImpl(private val appGraph: AppGraph) : TopicsRepetitionsComponent { override val topicsRepetitionsFeature: Feature get() = TopicsRepetitionsFeatureBuilder.build( appGraph.buildTopicsRepetitionsDataComponent().topicsRepetitionsInteractor, @@ -15,7 +15,7 @@ class TopicsRepetitionsComponentImpl(private val appGraph: AppGraph) : TopicsRep appGraph.analyticComponent.analyticInteractor, appGraph.sentryComponent.sentryInteractor, appGraph.topicsRepetitionsFlowDataComponent.topicRepeatedFlow, - appGraph.submissionDataComponent.submissionRepository, + appGraph.stepCompletionFlowDataComponent.stepCompletedFlow, appGraph.loggerComponent.logger, appGraph.commonComponent.buildKonfig.buildVariant ) diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/topics_repetitions/injection/TopicsRepetitionsDataComponentImpl.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/topics_repetitions/injection/TopicsRepetitionsDataComponentImpl.kt index fd8a93d16e..6835072f46 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/topics_repetitions/injection/TopicsRepetitionsDataComponentImpl.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/topics_repetitions/injection/TopicsRepetitionsDataComponentImpl.kt @@ -7,7 +7,7 @@ import org.hyperskill.app.topics_repetitions.domain.interactor.TopicsRepetitions import org.hyperskill.app.topics_repetitions.domain.repository.TopicsRepetitionsRepository import org.hyperskill.app.topics_repetitions.remote.TopicsRepetitionsRemoteDataSourceImpl -class TopicsRepetitionsDataComponentImpl(appGraph: AppGraph) : TopicsRepetitionsDataComponent { +internal class TopicsRepetitionsDataComponentImpl(appGraph: AppGraph) : TopicsRepetitionsDataComponent { private val topicsRepetitionsRemoteDataSource: TopicsRepetitionsRemoteDataSource = TopicsRepetitionsRemoteDataSourceImpl(appGraph.networkComponent.authorizedHttpClient) diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/topics_repetitions/injection/TopicsRepetitionsFeatureBuilder.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/topics_repetitions/injection/TopicsRepetitionsFeatureBuilder.kt index 0e5fe3bd74..ebdff52d72 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/topics_repetitions/injection/TopicsRepetitionsFeatureBuilder.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/topics_repetitions/injection/TopicsRepetitionsFeatureBuilder.kt @@ -7,7 +7,7 @@ import org.hyperskill.app.core.presentation.ActionDispatcherOptions import org.hyperskill.app.logging.presentation.wrapWithLogger import org.hyperskill.app.profile.domain.repository.CurrentProfileStateRepository import org.hyperskill.app.sentry.domain.interactor.SentryInteractor -import org.hyperskill.app.step_quiz.domain.repository.SubmissionRepository +import org.hyperskill.app.step_completion.domain.flow.StepCompletedFlow import org.hyperskill.app.topics_repetitions.domain.flow.TopicRepeatedFlow import org.hyperskill.app.topics_repetitions.domain.interactor.TopicsRepetitionsInteractor import org.hyperskill.app.topics_repetitions.presentation.TopicsRepetitionsActionDispatcher @@ -19,7 +19,7 @@ import ru.nobird.app.presentation.redux.dispatcher.wrapWithActionDispatcher import ru.nobird.app.presentation.redux.feature.Feature import ru.nobird.app.presentation.redux.feature.ReduxFeature -object TopicsRepetitionsFeatureBuilder { +internal object TopicsRepetitionsFeatureBuilder { private const val LOG_TAG = "TopicsRepetitionsFeature" fun build( @@ -28,7 +28,7 @@ object TopicsRepetitionsFeatureBuilder { analyticInteractor: AnalyticInteractor, sentryInteractor: SentryInteractor, topicRepeatedFlow: TopicRepeatedFlow, - submissionRepository: SubmissionRepository, + stepCompletedFlow: StepCompletedFlow, logger: Logger, buildVariant: BuildVariant ): Feature { @@ -41,7 +41,7 @@ object TopicsRepetitionsFeatureBuilder { analyticInteractor, sentryInteractor, topicRepeatedFlow, - submissionRepository + stepCompletedFlow ) return ReduxFeature(State.Idle, topicsRepetitionsReducer) diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/topics_repetitions/presentation/TopicsRepetitionsActionDispatcher.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/topics_repetitions/presentation/TopicsRepetitionsActionDispatcher.kt index 330a9bdf9c..7b51593578 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/topics_repetitions/presentation/TopicsRepetitionsActionDispatcher.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/topics_repetitions/presentation/TopicsRepetitionsActionDispatcher.kt @@ -8,7 +8,7 @@ import org.hyperskill.app.core.presentation.ActionDispatcherOptions import org.hyperskill.app.profile.domain.repository.CurrentProfileStateRepository import org.hyperskill.app.sentry.domain.interactor.SentryInteractor import org.hyperskill.app.sentry.domain.model.transaction.HyperskillSentryTransactionBuilder -import org.hyperskill.app.step_quiz.domain.repository.SubmissionRepository +import org.hyperskill.app.step_completion.domain.flow.StepCompletedFlow import org.hyperskill.app.topics_repetitions.domain.flow.TopicRepeatedFlow import org.hyperskill.app.topics_repetitions.domain.interactor.TopicsRepetitionsInteractor import org.hyperskill.app.topics_repetitions.presentation.TopicsRepetitionsFeature.Action @@ -16,17 +16,17 @@ import org.hyperskill.app.topics_repetitions.presentation.TopicsRepetitionsFeatu import org.hyperskill.app.topics_repetitions.presentation.TopicsRepetitionsFeature.Message import ru.nobird.app.presentation.redux.dispatcher.CoroutineActionDispatcher -class TopicsRepetitionsActionDispatcher( +internal class TopicsRepetitionsActionDispatcher( config: ActionDispatcherOptions, private val topicsRepetitionsInteractor: TopicsRepetitionsInteractor, private val currentProfileStateRepository: CurrentProfileStateRepository, private val analyticInteractor: AnalyticInteractor, private val sentryInteractor: SentryInteractor, private val topicRepeatedFlow: TopicRepeatedFlow, - submissionRepository: SubmissionRepository + stepCompletedFlow: StepCompletedFlow ) : CoroutineActionDispatcher(config.createConfig()) { init { - submissionRepository.solvedStepsMutableSharedFlow + stepCompletedFlow.observe() .onEach { onNewMessage(Message.StepCompleted(it)) } .launchIn(actionScope) } diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/topics_repetitions/presentation/TopicsRepetitionsReducer.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/topics_repetitions/presentation/TopicsRepetitionsReducer.kt index 423615e443..e78f8f5b30 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/topics_repetitions/presentation/TopicsRepetitionsReducer.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/topics_repetitions/presentation/TopicsRepetitionsReducer.kt @@ -11,7 +11,7 @@ import org.hyperskill.app.topics_repetitions.presentation.TopicsRepetitionsFeatu import org.hyperskill.app.topics_repetitions.presentation.TopicsRepetitionsFeature.State import ru.nobird.app.presentation.redux.reducer.StateReducer -class TopicsRepetitionsReducer : StateReducer { +internal class TopicsRepetitionsReducer : StateReducer { override fun reduce(state: State, message: Message): Pair> = when (message) { is Message.Initialize -> diff --git a/shared/src/commonMain/resources/MR/base/strings.xml b/shared/src/commonMain/resources/MR/base/strings.xml index d3366aa5aa..43ebe851bf 100644 --- a/shared/src/commonMain/resources/MR/base/strings.xml +++ b/shared/src/commonMain/resources/MR/base/strings.xml @@ -430,14 +430,22 @@ You\'ve reached your daily limit + You\'ve used all %d of your lives for today + You\'ve solved %d problems today. Great job! Tomorrow new problems will be available to you. + Take a break and come back tomorrow for another exciting day of practice! You\'ve solved %d problems today. Great job! Unlock unlimited problems with Mobile only plan. - Unlock unlimited problems + Great job! Unlock unlimited lives with Mobile only plan. + + Unlock unlimited problems + Unlock unlimited lives %d/%d problems left Reset in %s + %d/%d lives left + Study plan for: %s diff --git a/shared/src/commonTest/kotlin/org/hyperskill/step_completion/StepCompletionTest.kt b/shared/src/commonTest/kotlin/org/hyperskill/step_completion/StepCompletionTest.kt index af42d5fe37..ae7bfaded6 100644 --- a/shared/src/commonTest/kotlin/org/hyperskill/step_completion/StepCompletionTest.kt +++ b/shared/src/commonTest/kotlin/org/hyperskill/step_completion/StepCompletionTest.kt @@ -1,11 +1,14 @@ package org.hyperskill.step_completion import kotlin.test.Test +import kotlin.test.assertContains +import kotlin.test.assertEquals import kotlin.test.assertTrue import org.hyperskill.app.step.domain.model.Step import org.hyperskill.app.step.domain.model.StepRoute import org.hyperskill.app.step_completion.presentation.StepCompletionFeature import org.hyperskill.app.step_completion.presentation.StepCompletionReducer +import org.hyperskill.app.subscriptions.domain.model.FreemiumChargeLimitsStrategy import org.hyperskill.step.domain.model.stub class StepCompletionTest { @@ -29,4 +32,70 @@ class StepCompletionTest { } } } + + @Test + fun `Not current step solved do nothing`() { + val stepRoute = StepRoute.LearnDaily(1L) + val initialState = StepCompletionFeature.createState(Step.stub(stepRoute.stepId), stepRoute) + + val reducer = StepCompletionReducer(stepRoute) + val (actualState, actualActions) = reducer.reduce( + initialState, + StepCompletionFeature.Message.StepSolved(2L) + ) + + assertEquals(initialState, actualState) + assertTrue(actualActions.isEmpty()) + } + + @Test + fun `Solved step with limited attempts updates problems limits`() { + val stepRoute = StepRoute.Learn.Step(1L) + val initialState = StepCompletionFeature.createState(Step.stub(stepRoute.stepId), stepRoute) + + val reducer = StepCompletionReducer(stepRoute) + val (actualState, actualActions) = reducer.reduce( + initialState, + StepCompletionFeature.Message.StepSolved(stepRoute.stepId) + ) + + assertEquals(initialState, actualState) + assertContains( + actualActions, + StepCompletionFeature.Action.UpdateProblemsLimit(FreemiumChargeLimitsStrategy.AFTER_CORRECT_SUBMISSION) + ) + } + + @Test + fun `Solved step without limited attempts not updates problems limits`() { + val stepRoute = StepRoute.LearnDaily(1L) + val initialState = StepCompletionFeature.createState(Step.stub(stepRoute.stepId), stepRoute) + + val reducer = StepCompletionReducer(stepRoute) + val (actualState, actualActions) = reducer.reduce( + initialState, + StepCompletionFeature.Message.StepSolved(stepRoute.stepId) + ) + + assertEquals(initialState, actualState) + assertTrue(actualActions.isEmpty()) + } + + @Test + fun `Solved interview preparation step marks it as solved`() { + val stepRoute = StepRoute.InterviewPreparation(1L) + val initialState = StepCompletionFeature.createState(Step.stub(stepRoute.stepId), stepRoute) + + val reducer = StepCompletionReducer(stepRoute) + val (actualState, actualActions) = reducer.reduce( + initialState, + StepCompletionFeature.Message.StepSolved(stepRoute.stepId) + ) + + assertEquals(initialState, actualState) + assertContains( + actualActions, + StepCompletionFeature.InternalAction.MarkInterviewStepAsSolved(stepRoute.stepId) + ) + } } \ No newline at end of file diff --git a/shared/src/commonTest/kotlin/org/hyperskill/step_quiz/StepQuizTest.kt b/shared/src/commonTest/kotlin/org/hyperskill/step_quiz/StepQuizTest.kt index b9b7da1d98..3cee2bf992 100644 --- a/shared/src/commonTest/kotlin/org/hyperskill/step_quiz/StepQuizTest.kt +++ b/shared/src/commonTest/kotlin/org/hyperskill/step_quiz/StepQuizTest.kt @@ -1,6 +1,7 @@ package org.hyperskill.step_quiz import kotlin.test.Test +import kotlin.test.assertContains import kotlin.test.assertEquals import kotlin.test.assertTrue import org.hyperskill.app.onboarding.domain.model.ProblemsOnboardingFlags @@ -8,10 +9,13 @@ import org.hyperskill.app.step.domain.model.Step import org.hyperskill.app.step.domain.model.StepRoute import org.hyperskill.app.step_quiz.domain.analytic.StepQuizClickedTheoryToolbarItemHyperskillAnalyticEvent import org.hyperskill.app.step_quiz.domain.model.attempts.Attempt +import org.hyperskill.app.step_quiz.domain.model.submissions.Submission +import org.hyperskill.app.step_quiz.domain.model.submissions.SubmissionStatus import org.hyperskill.app.step_quiz.presentation.StepQuizFeature import org.hyperskill.app.step_quiz.presentation.StepQuizReducer import org.hyperskill.app.step_quiz_hints.presentation.StepQuizHintsFeature import org.hyperskill.app.step_quiz_hints.presentation.StepQuizHintsReducer +import org.hyperskill.app.subscriptions.domain.model.FreemiumChargeLimitsStrategy import org.hyperskill.step.domain.model.stub import org.hyperskill.step_quiz.domain.model.stub @@ -49,8 +53,11 @@ class StepQuizTest { attempt, submissionState, isProblemsLimitReached = true, - problemsLimitReachedModalText = "", - isSubscriptionPurchaseEnabled = true, + problemsLimitReachedModalData = StepQuizFeature.ProblemsLimitReachedModalData( + title = "", + description = "", + unlockLimitsButtonText = null + ), problemsOnboardingFlags = ProblemsOnboardingFlags( isParsonsOnboardingShown = false, isFillBlanksInputModeOnboardingShown = false, @@ -95,8 +102,11 @@ class StepQuizTest { attempt, submissionState, isProblemsLimitReached = true, - problemsLimitReachedModalText = "", - isSubscriptionPurchaseEnabled = true, + problemsLimitReachedModalData = StepQuizFeature.ProblemsLimitReachedModalData( + title = "", + description = "", + unlockLimitsButtonText = null + ), problemsOnboardingFlags = ProblemsOnboardingFlags( isParsonsOnboardingShown = false, isFillBlanksInputModeOnboardingShown = false, @@ -111,6 +121,192 @@ class StepQuizTest { } } + @Test + fun `Receiving wrong submission for step with limited attempts updates problems limits`() { + val step = Step.stub(id = 1) + val initialState = StepQuizFeature.State( + stepQuizState = StepQuizFeature.StepQuizState.AttemptLoaded( + step = step, + attempt = Attempt.stub(), + submissionState = StepQuizFeature.SubmissionState.Empty(), + isProblemsLimitReached = false, + isTheoryAvailable = false + ), + stepQuizHintsState = StepQuizHintsFeature.State.Idle + ) + + val reducer = StepQuizReducer( + stepRoute = StepRoute.Learn.Step(step.id), + stepQuizHintsReducer = StepQuizHintsReducer(StepRoute.Learn.Step(step.id)) + ) + + val (actualState, actualActions) = reducer.reduce( + initialState, + StepQuizFeature.Message.CreateSubmissionSuccess(Submission.stub(status = SubmissionStatus.WRONG)) + ) + + val expectedState = StepQuizFeature.State( + stepQuizState = StepQuizFeature.StepQuizState.AttemptLoaded( + step = step, + attempt = Attempt.stub(), + submissionState = StepQuizFeature.SubmissionState.Loaded( + Submission.stub(status = SubmissionStatus.WRONG) + ), + isProblemsLimitReached = false, + isTheoryAvailable = false + ), + stepQuizHintsState = StepQuizHintsFeature.State.Idle + ) + + assertEquals(expectedState, actualState) + assertContains( + actualActions, + StepQuizFeature.InternalAction.UpdateProblemsLimit(FreemiumChargeLimitsStrategy.AFTER_WRONG_SUBMISSION) + ) + } + + @Test + fun `Receiving wrong submission for step without limited attempts not updates problems limits`() { + val step = Step.stub(id = 1) + val initialState = StepQuizFeature.State( + stepQuizState = StepQuizFeature.StepQuizState.AttemptLoaded( + step = step, + attempt = Attempt.stub(), + submissionState = StepQuizFeature.SubmissionState.Empty(), + isProblemsLimitReached = false, + isTheoryAvailable = false + ), + stepQuizHintsState = StepQuizHintsFeature.State.Idle + ) + + val reducer = StepQuizReducer( + stepRoute = StepRoute.LearnDaily(step.id), + stepQuizHintsReducer = StepQuizHintsReducer(StepRoute.LearnDaily(step.id)) + ) + + val (actualState, actualActions) = reducer.reduce( + initialState, + StepQuizFeature.Message.CreateSubmissionSuccess(Submission.stub(status = SubmissionStatus.WRONG)) + ) + + val expectedState = StepQuizFeature.State( + stepQuizState = StepQuizFeature.StepQuizState.AttemptLoaded( + step = step, + attempt = Attempt.stub(), + submissionState = StepQuizFeature.SubmissionState.Loaded( + Submission.stub(status = SubmissionStatus.WRONG) + ), + isProblemsLimitReached = false, + isTheoryAvailable = false + ), + stepQuizHintsState = StepQuizHintsFeature.State.Idle + ) + + assertEquals(expectedState, actualState) + assertTrue(actualActions.isEmpty()) + } + + @Test + fun `When updated problems limits reached for step with limited attempts blocks solving`() { + val step = Step.stub(id = 1) + val initialState = StepQuizFeature.State( + stepQuizState = StepQuizFeature.StepQuizState.AttemptLoaded( + step = step, + attempt = Attempt.stub(), + submissionState = StepQuizFeature.SubmissionState.Empty(), + isProblemsLimitReached = false, + isTheoryAvailable = false + ), + stepQuizHintsState = StepQuizHintsFeature.State.Idle + ) + + val reducer = StepQuizReducer( + stepRoute = StepRoute.Learn.Step(step.id), + stepQuizHintsReducer = StepQuizHintsReducer(StepRoute.Learn.Step(step.id)) + ) + + val (actualState, actualActions) = reducer.reduce( + initialState, + StepQuizFeature.InternalMessage.UpdateProblemsLimitResult( + isProblemsLimitReached = true, + problemsLimitReachedModalData = StepQuizFeature.ProblemsLimitReachedModalData( + title = "", + description = "", + unlockLimitsButtonText = null + ) + ) + ) + + val expectedState = StepQuizFeature.State( + stepQuizState = StepQuizFeature.StepQuizState.AttemptLoaded( + step = step, + attempt = Attempt.stub(), + submissionState = StepQuizFeature.SubmissionState.Empty(), + isProblemsLimitReached = true, + isTheoryAvailable = false + ), + stepQuizHintsState = StepQuizHintsFeature.State.Idle + ) + + assertEquals(expectedState, actualState) + assertContains( + actualActions, + StepQuizFeature.Action.ViewAction.ShowProblemsLimitReachedModal( + StepQuizFeature.ProblemsLimitReachedModalData( + title = "", + description = "", + unlockLimitsButtonText = null + ) + ) + ) + } + + @Test + fun `When updated problems limits reached for step without limited attempts not blocks solving`() { + val step = Step.stub(id = 1) + val initialState = StepQuizFeature.State( + stepQuizState = StepQuizFeature.StepQuizState.AttemptLoaded( + step = step, + attempt = Attempt.stub(), + submissionState = StepQuizFeature.SubmissionState.Empty(), + isProblemsLimitReached = false, + isTheoryAvailable = false + ), + stepQuizHintsState = StepQuizHintsFeature.State.Idle + ) + + val reducer = StepQuizReducer( + stepRoute = StepRoute.LearnDaily(step.id), + stepQuizHintsReducer = StepQuizHintsReducer(StepRoute.LearnDaily(step.id)) + ) + + val (actualState, actualActions) = reducer.reduce( + initialState, + StepQuizFeature.InternalMessage.UpdateProblemsLimitResult( + isProblemsLimitReached = true, + problemsLimitReachedModalData = StepQuizFeature.ProblemsLimitReachedModalData( + title = "", + description = "", + unlockLimitsButtonText = null + ) + ) + ) + + val expectedState = StepQuizFeature.State( + stepQuizState = StepQuizFeature.StepQuizState.AttemptLoaded( + step = step, + attempt = Attempt.stub(), + submissionState = StepQuizFeature.SubmissionState.Empty(), + isProblemsLimitReached = false, + isTheoryAvailable = false + ), + stepQuizHintsState = StepQuizHintsFeature.State.Idle + ) + + assertEquals(expectedState, actualState) + assertTrue(actualActions.isEmpty()) + } + @Test fun `TheoryToolbarClicked message navigates to step screen when theory available`() { val topicTheoryId = 2L @@ -144,8 +340,7 @@ class StepQuizTest { attempt, submissionState, isProblemsLimitReached = false, - problemsLimitReachedModalText = null, - isSubscriptionPurchaseEnabled = true, + problemsLimitReachedModalData = null, problemsOnboardingFlags = ProblemsOnboardingFlags( isParsonsOnboardingShown = false, isFillBlanksInputModeOnboardingShown = false, @@ -207,8 +402,7 @@ class StepQuizTest { attempt, submissionState, isProblemsLimitReached = false, - problemsLimitReachedModalText = null, - isSubscriptionPurchaseEnabled = true, + problemsLimitReachedModalData = null, problemsOnboardingFlags = ProblemsOnboardingFlags( isParsonsOnboardingShown = false, isFillBlanksInputModeOnboardingShown = false, From 9cb1dce702f97163a2deb8d17dbc3be08087cffb Mon Sep 17 00:00:00 2001 From: github-actions Date: Thu, 29 Feb 2024 08:32:48 +0000 Subject: [PATCH 101/104] Bump build number --- gradle/app.versions.toml | 2 +- .../NotificationServiceExtension/Info.plist | 2 +- .../iosHyperskillApp.xcodeproj/project.pbxproj | 16 ++++++++-------- iosHyperskillApp/iosHyperskillApp/Info.plist | 2 +- .../iosHyperskillAppTests/Info.plist | 2 +- .../iosHyperskillAppUITests/Info.plist | 2 +- 6 files changed, 13 insertions(+), 13 deletions(-) diff --git a/gradle/app.versions.toml b/gradle/app.versions.toml index 3642affb6c..a7df48dcac 100644 --- a/gradle/app.versions.toml +++ b/gradle/app.versions.toml @@ -3,4 +3,4 @@ minSdk = '24' targetSdk = '33' compileSdk = '33' versionName = '1.50' -versionCode = '341' \ No newline at end of file +versionCode = '342' \ No newline at end of file diff --git a/iosHyperskillApp/NotificationServiceExtension/Info.plist b/iosHyperskillApp/NotificationServiceExtension/Info.plist index ba06cf2e42..8fb416cb16 100644 --- a/iosHyperskillApp/NotificationServiceExtension/Info.plist +++ b/iosHyperskillApp/NotificationServiceExtension/Info.plist @@ -9,7 +9,7 @@ CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleVersion - 347 + 348 CFBundleShortVersionString 1.50 CFBundlePackageType diff --git a/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj b/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj index ba1abc3fb7..095b813231 100644 --- a/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj +++ b/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj @@ -5185,7 +5185,7 @@ buildSettings = { CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 347; + CURRENT_PROJECT_VERSION = 348; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = 3DWS674B2M; GENERATE_INFOPLIST_FILE = NO; @@ -5206,7 +5206,7 @@ buildSettings = { CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 347; + CURRENT_PROJECT_VERSION = 348; DEVELOPMENT_TEAM = 3DWS674B2M; GENERATE_INFOPLIST_FILE = NO; INFOPLIST_FILE = iosHyperskillAppUITests/Info.plist; @@ -5227,7 +5227,7 @@ BUNDLE_LOADER = "$(TEST_HOST)"; CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 347; + CURRENT_PROJECT_VERSION = 348; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = 3DWS674B2M; INFOPLIST_FILE = iosHyperskillAppTests/Info.plist; @@ -5248,7 +5248,7 @@ BUNDLE_LOADER = "$(TEST_HOST)"; CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 347; + CURRENT_PROJECT_VERSION = 348; DEVELOPMENT_TEAM = 3DWS674B2M; INFOPLIST_FILE = iosHyperskillAppTests/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; @@ -5269,7 +5269,7 @@ CODE_SIGN_ENTITLEMENTS = NotificationServiceExtension/NotificationServiceExtension.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 347; + CURRENT_PROJECT_VERSION = 348; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = 3DWS674B2M; INFOPLIST_FILE = NotificationServiceExtension/Info.plist; @@ -5297,7 +5297,7 @@ CODE_SIGN_ENTITLEMENTS = NotificationServiceExtension/NotificationServiceExtension.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 347; + CURRENT_PROJECT_VERSION = 348; DEVELOPMENT_TEAM = 3DWS674B2M; INFOPLIST_FILE = NotificationServiceExtension/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; @@ -5442,7 +5442,7 @@ CODE_SIGN_ENTITLEMENTS = iosHyperskillApp/iosHyperskillApp.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 347; + CURRENT_PROJECT_VERSION = 348; DEVELOPMENT_ASSET_PATHS = "\"iosHyperskillApp/Preview Content\""; DEVELOPMENT_TEAM = 3DWS674B2M; ENABLE_PREVIEWS = YES; @@ -5478,7 +5478,7 @@ CODE_SIGN_ENTITLEMENTS = iosHyperskillApp/iosHyperskillApp.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 347; + CURRENT_PROJECT_VERSION = 348; DEVELOPMENT_ASSET_PATHS = "\"iosHyperskillApp/Preview Content\""; DEVELOPMENT_TEAM = 3DWS674B2M; ENABLE_PREVIEWS = YES; diff --git a/iosHyperskillApp/iosHyperskillApp/Info.plist b/iosHyperskillApp/iosHyperskillApp/Info.plist index 57080dbc44..f89d72d853 100644 --- a/iosHyperskillApp/iosHyperskillApp/Info.plist +++ b/iosHyperskillApp/iosHyperskillApp/Info.plist @@ -36,7 +36,7 @@ CFBundleVersion - 347 + 348 FirebaseAppDelegateProxyEnabled FirebaseMessagingAutoInitEnabled diff --git a/iosHyperskillApp/iosHyperskillAppTests/Info.plist b/iosHyperskillApp/iosHyperskillAppTests/Info.plist index e1b60ab2ae..c54e4ee373 100644 --- a/iosHyperskillApp/iosHyperskillAppTests/Info.plist +++ b/iosHyperskillApp/iosHyperskillAppTests/Info.plist @@ -15,6 +15,6 @@ CFBundleShortVersionString 1.50 CFBundleVersion - 347 + 348 diff --git a/iosHyperskillApp/iosHyperskillAppUITests/Info.plist b/iosHyperskillApp/iosHyperskillAppUITests/Info.plist index a7463534cd..207df02919 100644 --- a/iosHyperskillApp/iosHyperskillAppUITests/Info.plist +++ b/iosHyperskillApp/iosHyperskillAppUITests/Info.plist @@ -15,6 +15,6 @@ CFBundleShortVersionString 1.50 CFBundleVersion - 347 + 348 From 07858fc18ffa60cddb3ae6b12540ebd7b201dd48 Mon Sep 17 00:00:00 2001 From: Ivan Magda Date: Fri, 1 Mar 2024 11:36:21 +0800 Subject: [PATCH 102/104] ALTAPPS-947: Correct text in restore streak cancellation alert --- shared/src/commonMain/resources/MR/base/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shared/src/commonMain/resources/MR/base/strings.xml b/shared/src/commonMain/resources/MR/base/strings.xml index 43ebe851bf..1634fed498 100644 --- a/shared/src/commonMain/resources/MR/base/strings.xml +++ b/shared/src/commonMain/resources/MR/base/strings.xml @@ -506,7 +506,7 @@ No, thanks Streak successfully recovered Failed to recover a streak - Streak recovery successfully cancelled + Streak recovery cancelled Failed to cancel a streak recovery From 064e32c653d7b439369d6f7befd28da66445621c Mon Sep 17 00:00:00 2001 From: github-actions Date: Fri, 1 Mar 2024 03:37:13 +0000 Subject: [PATCH 103/104] Bump build number --- gradle/app.versions.toml | 2 +- .../NotificationServiceExtension/Info.plist | 2 +- .../iosHyperskillApp.xcodeproj/project.pbxproj | 16 ++++++++-------- iosHyperskillApp/iosHyperskillApp/Info.plist | 2 +- .../iosHyperskillAppTests/Info.plist | 2 +- .../iosHyperskillAppUITests/Info.plist | 2 +- 6 files changed, 13 insertions(+), 13 deletions(-) diff --git a/gradle/app.versions.toml b/gradle/app.versions.toml index a7df48dcac..bc12a6d0cc 100644 --- a/gradle/app.versions.toml +++ b/gradle/app.versions.toml @@ -3,4 +3,4 @@ minSdk = '24' targetSdk = '33' compileSdk = '33' versionName = '1.50' -versionCode = '342' \ No newline at end of file +versionCode = '343' \ No newline at end of file diff --git a/iosHyperskillApp/NotificationServiceExtension/Info.plist b/iosHyperskillApp/NotificationServiceExtension/Info.plist index 8fb416cb16..0d6586ff92 100644 --- a/iosHyperskillApp/NotificationServiceExtension/Info.plist +++ b/iosHyperskillApp/NotificationServiceExtension/Info.plist @@ -9,7 +9,7 @@ CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleVersion - 348 + 349 CFBundleShortVersionString 1.50 CFBundlePackageType diff --git a/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj b/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj index 095b813231..82882ed87f 100644 --- a/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj +++ b/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj @@ -5185,7 +5185,7 @@ buildSettings = { CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 348; + CURRENT_PROJECT_VERSION = 349; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = 3DWS674B2M; GENERATE_INFOPLIST_FILE = NO; @@ -5206,7 +5206,7 @@ buildSettings = { CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 348; + CURRENT_PROJECT_VERSION = 349; DEVELOPMENT_TEAM = 3DWS674B2M; GENERATE_INFOPLIST_FILE = NO; INFOPLIST_FILE = iosHyperskillAppUITests/Info.plist; @@ -5227,7 +5227,7 @@ BUNDLE_LOADER = "$(TEST_HOST)"; CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 348; + CURRENT_PROJECT_VERSION = 349; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = 3DWS674B2M; INFOPLIST_FILE = iosHyperskillAppTests/Info.plist; @@ -5248,7 +5248,7 @@ BUNDLE_LOADER = "$(TEST_HOST)"; CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 348; + CURRENT_PROJECT_VERSION = 349; DEVELOPMENT_TEAM = 3DWS674B2M; INFOPLIST_FILE = iosHyperskillAppTests/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; @@ -5269,7 +5269,7 @@ CODE_SIGN_ENTITLEMENTS = NotificationServiceExtension/NotificationServiceExtension.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 348; + CURRENT_PROJECT_VERSION = 349; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = 3DWS674B2M; INFOPLIST_FILE = NotificationServiceExtension/Info.plist; @@ -5297,7 +5297,7 @@ CODE_SIGN_ENTITLEMENTS = NotificationServiceExtension/NotificationServiceExtension.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 348; + CURRENT_PROJECT_VERSION = 349; DEVELOPMENT_TEAM = 3DWS674B2M; INFOPLIST_FILE = NotificationServiceExtension/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; @@ -5442,7 +5442,7 @@ CODE_SIGN_ENTITLEMENTS = iosHyperskillApp/iosHyperskillApp.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 348; + CURRENT_PROJECT_VERSION = 349; DEVELOPMENT_ASSET_PATHS = "\"iosHyperskillApp/Preview Content\""; DEVELOPMENT_TEAM = 3DWS674B2M; ENABLE_PREVIEWS = YES; @@ -5478,7 +5478,7 @@ CODE_SIGN_ENTITLEMENTS = iosHyperskillApp/iosHyperskillApp.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 348; + CURRENT_PROJECT_VERSION = 349; DEVELOPMENT_ASSET_PATHS = "\"iosHyperskillApp/Preview Content\""; DEVELOPMENT_TEAM = 3DWS674B2M; ENABLE_PREVIEWS = YES; diff --git a/iosHyperskillApp/iosHyperskillApp/Info.plist b/iosHyperskillApp/iosHyperskillApp/Info.plist index f89d72d853..6de951c0d2 100644 --- a/iosHyperskillApp/iosHyperskillApp/Info.plist +++ b/iosHyperskillApp/iosHyperskillApp/Info.plist @@ -36,7 +36,7 @@ CFBundleVersion - 348 + 349 FirebaseAppDelegateProxyEnabled FirebaseMessagingAutoInitEnabled diff --git a/iosHyperskillApp/iosHyperskillAppTests/Info.plist b/iosHyperskillApp/iosHyperskillAppTests/Info.plist index c54e4ee373..aaf6aeadc0 100644 --- a/iosHyperskillApp/iosHyperskillAppTests/Info.plist +++ b/iosHyperskillApp/iosHyperskillAppTests/Info.plist @@ -15,6 +15,6 @@ CFBundleShortVersionString 1.50 CFBundleVersion - 348 + 349 diff --git a/iosHyperskillApp/iosHyperskillAppUITests/Info.plist b/iosHyperskillApp/iosHyperskillAppUITests/Info.plist index 207df02919..f7dad4f2a2 100644 --- a/iosHyperskillApp/iosHyperskillAppUITests/Info.plist +++ b/iosHyperskillApp/iosHyperskillAppUITests/Info.plist @@ -15,6 +15,6 @@ CFBundleShortVersionString 1.50 CFBundleVersion - 348 + 349 From 4e8ae4037ff45701381b80309ed4b78f9d34b4c8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 1 Mar 2024 12:08:29 +0800 Subject: [PATCH 104/104] GitHub Actions: Bump actions/cache from 4.0.0 to 4.0.1 (#927) * GitHub Actions: Bump actions/cache from 4.0.0 to 4.0.1 Bumps [actions/cache](https://github.com/actions/cache) from 4.0.0 to 4.0.1. - [Release notes](https://github.com/actions/cache/releases) - [Changelog](https://github.com/actions/cache/blob/main/RELEASES.md) - [Commits](https://github.com/actions/cache/compare/v4.0.0...v4.0.1) --- updated-dependencies: - dependency-name: actions/cache dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] * Bump remaining usages --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Ivan Magda --- .github/actions/setup-android/action.yml | 6 +++--- .github/actions/setup-ios/action.yml | 10 +++++----- .github/workflows/ci.yml | 2 +- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/.github/actions/setup-android/action.yml b/.github/actions/setup-android/action.yml index ae07aa057d..3335f61e58 100644 --- a/.github/actions/setup-android/action.yml +++ b/.github/actions/setup-android/action.yml @@ -76,7 +76,7 @@ runs: # Cache Gradle dependencies - name: Setup Gradle Dependencies Cache - uses: actions/cache@v4.0.0 + uses: actions/cache@v4.0.1 with: path: ~/.gradle/caches key: ${{ runner.os }}-gradle-caches-${{ hashFiles('**/*.gradle', '**/*.gradle.kts') }} @@ -85,7 +85,7 @@ runs: # Cache Gradle Wrapper - name: Setup Gradle Wrapper Cache - uses: actions/cache@v4.0.0 + uses: actions/cache@v4.0.1 with: path: ~/.gradle/wrapper key: ${{ runner.os }}-gradle-wrapper-${{ hashFiles('**/gradle*properties') }} @@ -94,7 +94,7 @@ runs: # Cache Kotlin/Native compiler - name: Setup Kotlin/Native Compiler Cache - uses: actions/cache@v4.0.0 + uses: actions/cache@v4.0.1 with: path: ~/.konan key: ${{ runner.os }}-kotlin-native-compiler-${{ hashFiles('gradle/libs.versions.toml') }} diff --git a/.github/actions/setup-ios/action.yml b/.github/actions/setup-ios/action.yml index c33b951bdd..5466c48dd2 100644 --- a/.github/actions/setup-ios/action.yml +++ b/.github/actions/setup-ios/action.yml @@ -63,7 +63,7 @@ runs: # Cache Gradle dependencies - name: Setup Gradle Dependencies Cache - uses: actions/cache@v4.0.0 + uses: actions/cache@v4.0.1 with: path: ~/.gradle/caches key: ${{ runner.os }}-gradle-caches-${{ hashFiles('**/*.gradle', '**/*.gradle.kts') }} @@ -72,7 +72,7 @@ runs: # Cache Gradle Wrapper - name: Setup Gradle Wrapper Cache - uses: actions/cache@v4.0.0 + uses: actions/cache@v4.0.1 with: path: ~/.gradle/wrapper key: ${{ runner.os }}-gradle-wrapper-${{ hashFiles('**/gradle*properties') }} @@ -81,7 +81,7 @@ runs: # Cache Kotlin/Native compiler - name: Setup Kotlin/Native Compiler Cache - uses: actions/cache@v4.0.0 + uses: actions/cache@v4.0.1 with: path: ~/.konan key: ${{ runner.os }}-kotlin-native-compiler-${{ hashFiles('gradle/libs.versions.toml') }} @@ -100,7 +100,7 @@ runs: # Cache Pods dependencies - name: Cache Pods - uses: actions/cache@v4.0.0 + uses: actions/cache@v4.0.1 id: cache-pods with: path: './iosHyperskillApp/Pods' @@ -111,7 +111,7 @@ runs: # Cache CocoaPods - name: Cache CocoaPods if: steps.cache-pods.outputs.cache-hit != 'true' - uses: actions/cache@v4.0.0 + uses: actions/cache@v4.0.1 with: path: | ~/.cocoapods diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4dc55e9011..76aa95a27d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -94,7 +94,7 @@ jobs: working-directory: './iosHyperskillApp' - name: Cache Pods - uses: actions/cache@v4.0.0 + uses: actions/cache@v4.0.1 id: cache-pods with: path: './iosHyperskillApp/Pods'