From 387c83ce3eecd3b60f0ed298a9b050b19338259d Mon Sep 17 00:00:00 2001 From: github-actions Date: Mon, 8 Jan 2024 12:44:59 +0000 Subject: [PATCH 01/19] Set version number to 1.49 --- 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 68d3b855eb..5d826a57a4 100644 --- a/gradle/app.versions.toml +++ b/gradle/app.versions.toml @@ -2,5 +2,5 @@ minSdk = '24' targetSdk = '33' compileSdk = '33' -versionName = '1.48' +versionName = '1.49' versionCode = '296' \ No newline at end of file diff --git a/iosHyperskillApp/NotificationServiceExtension/Info.plist b/iosHyperskillApp/NotificationServiceExtension/Info.plist index e80121e5bf..d843c6708d 100644 --- a/iosHyperskillApp/NotificationServiceExtension/Info.plist +++ b/iosHyperskillApp/NotificationServiceExtension/Info.plist @@ -11,7 +11,7 @@ CFBundleVersion 298 CFBundleShortVersionString - 1.48 + 1.49 CFBundlePackageType $(PRODUCT_BUNDLE_PACKAGE_TYPE) CFBundleExecutable diff --git a/iosHyperskillApp/iosHyperskillApp/Info.plist b/iosHyperskillApp/iosHyperskillApp/Info.plist index 335c224782..b4613b6a0f 100644 --- a/iosHyperskillApp/iosHyperskillApp/Info.plist +++ b/iosHyperskillApp/iosHyperskillApp/Info.plist @@ -23,7 +23,7 @@ CFBundlePackageType $(PRODUCT_BUNDLE_PACKAGE_TYPE) CFBundleShortVersionString - 1.48 + 1.49 CFBundleURLTypes diff --git a/iosHyperskillApp/iosHyperskillAppTests/Info.plist b/iosHyperskillApp/iosHyperskillAppTests/Info.plist index 59fd02d610..6d46680610 100644 --- a/iosHyperskillApp/iosHyperskillAppTests/Info.plist +++ b/iosHyperskillApp/iosHyperskillAppTests/Info.plist @@ -13,7 +13,7 @@ CFBundlePackageType BNDL CFBundleShortVersionString - 1.48 + 1.49 CFBundleVersion 298 diff --git a/iosHyperskillApp/iosHyperskillAppUITests/Info.plist b/iosHyperskillApp/iosHyperskillAppUITests/Info.plist index 0ad2a50783..a45298ecaf 100644 --- a/iosHyperskillApp/iosHyperskillAppUITests/Info.plist +++ b/iosHyperskillApp/iosHyperskillAppUITests/Info.plist @@ -13,7 +13,7 @@ CFBundlePackageType $(PRODUCT_BUNDLE_PACKAGE_TYPE) CFBundleShortVersionString - 1.48 + 1.49 CFBundleVersion 298 From 98d9cf95f54e276cd5e5950e57edd545f768d4b2 Mon Sep 17 00:00:00 2001 From: github-actions Date: Mon, 8 Jan 2024 12:45:03 +0000 Subject: [PATCH 02/19] 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 5d826a57a4..e84b875a36 100644 --- a/gradle/app.versions.toml +++ b/gradle/app.versions.toml @@ -3,4 +3,4 @@ minSdk = '24' targetSdk = '33' compileSdk = '33' versionName = '1.49' -versionCode = '296' \ No newline at end of file +versionCode = '297' \ No newline at end of file diff --git a/iosHyperskillApp/NotificationServiceExtension/Info.plist b/iosHyperskillApp/NotificationServiceExtension/Info.plist index d843c6708d..ab51b52cec 100644 --- a/iosHyperskillApp/NotificationServiceExtension/Info.plist +++ b/iosHyperskillApp/NotificationServiceExtension/Info.plist @@ -9,7 +9,7 @@ CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleVersion - 298 + 299 CFBundleShortVersionString 1.49 CFBundlePackageType diff --git a/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj b/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj index e34767f33c..2eb01692f3 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 = 298; + CURRENT_PROJECT_VERSION = 299; 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 = 298; + CURRENT_PROJECT_VERSION = 299; 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 = 298; + CURRENT_PROJECT_VERSION = 299; 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 = 298; + CURRENT_PROJECT_VERSION = 299; 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 = 298; + CURRENT_PROJECT_VERSION = 299; 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 = 298; + CURRENT_PROJECT_VERSION = 299; 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 = 298; + CURRENT_PROJECT_VERSION = 299; 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 = 298; + CURRENT_PROJECT_VERSION = 299; 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 b4613b6a0f..b85727ab91 100644 --- a/iosHyperskillApp/iosHyperskillApp/Info.plist +++ b/iosHyperskillApp/iosHyperskillApp/Info.plist @@ -36,7 +36,7 @@ CFBundleVersion - 298 + 299 FirebaseAppDelegateProxyEnabled FirebaseMessagingAutoInitEnabled diff --git a/iosHyperskillApp/iosHyperskillAppTests/Info.plist b/iosHyperskillApp/iosHyperskillAppTests/Info.plist index 6d46680610..4c30dcca27 100644 --- a/iosHyperskillApp/iosHyperskillAppTests/Info.plist +++ b/iosHyperskillApp/iosHyperskillAppTests/Info.plist @@ -15,6 +15,6 @@ CFBundleShortVersionString 1.49 CFBundleVersion - 298 + 299 diff --git a/iosHyperskillApp/iosHyperskillAppUITests/Info.plist b/iosHyperskillApp/iosHyperskillAppUITests/Info.plist index a45298ecaf..9f21201ccb 100644 --- a/iosHyperskillApp/iosHyperskillAppUITests/Info.plist +++ b/iosHyperskillApp/iosHyperskillAppUITests/Info.plist @@ -15,6 +15,6 @@ CFBundleShortVersionString 1.49 CFBundleVersion - 298 + 299 From 85ccf4ad22f57f40882669c4dfd5144285354386 Mon Sep 17 00:00:00 2001 From: Aleksandr Zhukov Date: Tue, 9 Jan 2024 13:37:21 +0400 Subject: [PATCH 03/19] Shared protect app from errors due to cache models serialization (#843) --- .github/PULL_REQUEST_TEMPLATE.md | 3 +- .../app/auth/remote/model/AuthResponse.kt | 12 +++ .../app/devices/domain/model/Device.kt | 11 +++ .../app/profile/domain/model/Profile.kt | 11 +++ .../domain/model/ProfileSettings.kt | 11 +++ .../auth/AuthResponseSerializationTest.kt | 36 ++++++++ .../device/DeviceSerializationTest.kt | 35 ++++++++ .../profile/ProfileSerializationTest.kt | 84 +++++++++++++++++++ .../ProfileSettingsSerializationTest.kt | 25 ++++++ 9 files changed, 227 insertions(+), 1 deletion(-) create mode 100644 shared/src/commonTest/kotlin/org/hyperskill/auth/AuthResponseSerializationTest.kt create mode 100644 shared/src/commonTest/kotlin/org/hyperskill/device/DeviceSerializationTest.kt create mode 100644 shared/src/commonTest/kotlin/org/hyperskill/profile/ProfileSerializationTest.kt create mode 100644 shared/src/commonTest/kotlin/org/hyperskill/profile_settings/ProfileSettingsSerializationTest.kt diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index e94b66ac3b..62664abb28 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -10,6 +10,7 @@ _Before Code Review:_ - [ ] View analytics events are added for new screens; - [ ] New analytics events are documented; - [ ] All checks have been passed; -- [ ] Changes have been checked locally. +- [ ] Changes have been checked locally; +- [ ] Saved in the cache models serialization checked locally. **Description** diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/auth/remote/model/AuthResponse.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/auth/remote/model/AuthResponse.kt index 9c0fe6595b..30abd51192 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/auth/remote/model/AuthResponse.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/auth/remote/model/AuthResponse.kt @@ -2,7 +2,19 @@ package org.hyperskill.app.auth.remote.model import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable +import org.hyperskill.app.auth.remote.source.AuthRemoteDataSourceImpl +import org.hyperskill.app.network.NetworkBuilder +/** + * Represents a response for an auth request. + * + * 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 [NetworkBuilder.buildAuthorizedClient], [AuthRemoteDataSourceImpl] + */ @Serializable data class AuthResponse( @SerialName("access_token") val accessToken: String, diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/devices/domain/model/Device.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/devices/domain/model/Device.kt index a52264d779..36888b133c 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/devices/domain/model/Device.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/devices/domain/model/Device.kt @@ -2,7 +2,18 @@ package org.hyperskill.app.devices.domain.model import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable +import org.hyperskill.app.devices.cache.CurrentDeviceCacheDataSourceImpl +/** + * Represents a user's device. + * + * 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 [CurrentDeviceCacheDataSourceImpl] + */ @Serializable data class Device( @SerialName("name") diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/profile/domain/model/Profile.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/profile/domain/model/Profile.kt index b0ccb4ee7d..70cf1bbf44 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/profile/domain/model/Profile.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/profile/domain/model/Profile.kt @@ -3,7 +3,18 @@ package org.hyperskill.app.profile.domain.model import kotlinx.datetime.TimeZone import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable +import org.hyperskill.app.profile.cache.CurrentProfileStateHolderImpl +/** + * Represents a user profile. + * + * 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 [CurrentProfileStateHolderImpl] + */ @Serializable data class Profile( @SerialName("id") diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/profile_settings/domain/model/ProfileSettings.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/profile_settings/domain/model/ProfileSettings.kt index 0f1b2a1fa5..01dae7bd5b 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/profile_settings/domain/model/ProfileSettings.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/profile_settings/domain/model/ProfileSettings.kt @@ -2,7 +2,18 @@ package org.hyperskill.app.profile_settings.domain.model import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable +import org.hyperskill.app.profile_settings.cache.ProfileSettingsCacheDataSourceImpl +/** + * Represents profile settings. + * + * 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 [ProfileSettingsCacheDataSourceImpl] + */ @Serializable data class ProfileSettings( @SerialName("theme") diff --git a/shared/src/commonTest/kotlin/org/hyperskill/auth/AuthResponseSerializationTest.kt b/shared/src/commonTest/kotlin/org/hyperskill/auth/AuthResponseSerializationTest.kt new file mode 100644 index 0000000000..015443f420 --- /dev/null +++ b/shared/src/commonTest/kotlin/org/hyperskill/auth/AuthResponseSerializationTest.kt @@ -0,0 +1,36 @@ +package org.hyperskill.auth + +import kotlin.test.Test +import kotlin.test.assertEquals +import org.hyperskill.app.auth.remote.model.AuthResponse +import org.hyperskill.app.network.injection.NetworkModule + +class AuthResponseSerializationTest { + companion object { + private const val TEST_JSON = """ + { + "access_token": "some_access_toke", + "expires_in": "1000", + "refresh_token": "some_refresh_token", + "token_type": "some_token_type", + "scope": "some_scope" + } + """ + + private val EXPECTED_AUTH_RESPONSE: AuthResponse = + AuthResponse( + accessToken = "some_access_toke", + expiresIn = 1000, + refreshToken = "some_refresh_token", + tokenType = "some_token_type", + scope = "some_scope" + ) + } + + @Test + fun `Serialized AuthResponse should be deserialized normally`() { + val json = NetworkModule.provideJson() + val actualAuthResponse = json.decodeFromString(AuthResponse.serializer(), TEST_JSON) + assertEquals(EXPECTED_AUTH_RESPONSE, actualAuthResponse) + } +} \ No newline at end of file diff --git a/shared/src/commonTest/kotlin/org/hyperskill/device/DeviceSerializationTest.kt b/shared/src/commonTest/kotlin/org/hyperskill/device/DeviceSerializationTest.kt new file mode 100644 index 0000000000..bdfc6b31a1 --- /dev/null +++ b/shared/src/commonTest/kotlin/org/hyperskill/device/DeviceSerializationTest.kt @@ -0,0 +1,35 @@ +package org.hyperskill.device + +import kotlin.test.Test +import kotlin.test.assertEquals +import org.hyperskill.app.devices.domain.model.Device +import org.hyperskill.app.devices.domain.model.DeviceType +import org.hyperskill.app.network.injection.NetworkModule + +class DeviceSerializationTest { + companion object { + private const val TEST_JSON = """ + { + "name": "Android 33", + "registration_id": "long_registration_id", + "active": true, + "type": "some_unknown_type" + } + """ + + private val EXPECTED_DEVICE: Device = + Device( + name = "Android 33", + registrationId = "long_registration_id", + isActive = true, + type = DeviceType.UNKNOWN + ) + } + + @Test + fun `Serialized device should be deserialized normally`() { + val json = NetworkModule.provideJson() + val actualDevice = json.decodeFromString(Device.serializer(), TEST_JSON) + assertEquals(EXPECTED_DEVICE, actualDevice) + } +} \ No newline at end of file diff --git a/shared/src/commonTest/kotlin/org/hyperskill/profile/ProfileSerializationTest.kt b/shared/src/commonTest/kotlin/org/hyperskill/profile/ProfileSerializationTest.kt new file mode 100644 index 0000000000..2be0d97f85 --- /dev/null +++ b/shared/src/commonTest/kotlin/org/hyperskill/profile/ProfileSerializationTest.kt @@ -0,0 +1,84 @@ +package org.hyperskill.profile + +import kotlin.test.Test +import kotlin.test.assertEquals +import org.hyperskill.app.network.injection.NetworkModule +import org.hyperskill.app.profile.domain.model.Gamification +import org.hyperskill.app.profile.domain.model.Profile + +class ProfileSerializationTest { + + companion object { + private const val TEST_JSON = """ + { + "id": 12345678, + "avatar": "https://example.com/avatar.jpg", + "bio": "Just a bio", + "fullname": "John Doe", + "gamification": { + "hypercoins": 414, + "passed_projects": 0 + }, + "completed_tracks": [1, 2, 3, 4, 5], + "country": null, + "languages": null, + "experience": "5 years", + "github_username": "johndoe", + "linkedin_username": "johndoe", + "twitter_username": "johndoetwitter", + "reddit_username": "jdoe", + "facebook_username": "jdoefacebook", + "daily_step": null, + "is_guest": false, + "is_staff": true, + "track_id": null, + "track_title": null, + "project": null, + "is_beta": false, + "timezone": null, + "notification_hour": null, + "features": { + "feature1": true, + "feature2": false + } + } + """ + + private val EXPECTED_PROFILE: Profile = + Profile( + id = 12345678L, + avatar = "https://example.com/avatar.jpg", + bio = "Just a bio", + fullname = "John Doe", + gamification = Gamification( + hypercoinsBalance = 414, + passedProjectsCount = 0 + ), + completedTracks = listOf(1L, 2L, 3L, 4L, 5L), + country = null, + languages = null, + experience = "5 years", + githubUsername = "johndoe", + linkedinUsername = "johndoe", + twitterUsername = "johndoetwitter", + redditUsername = "jdoe", + facebookUsername = "jdoefacebook", + dailyStep = null, + isGuest = false, + isStaff = true, + trackId = null, + trackTitle = null, + projectId = null, + isBeta = false, + timeZone = null, + notificationHour = null, + featuresMap = mapOf("feature1" to true, "feature2" to false) + ) + } + @Test + fun `Serialized profile should be deserialized normally`() { + val json = NetworkModule.provideJson() + val actualModel = json.decodeFromString(Profile.serializer(), TEST_JSON) + assertEquals(EXPECTED_PROFILE, actualModel) + } +} \ No newline at end of file diff --git a/shared/src/commonTest/kotlin/org/hyperskill/profile_settings/ProfileSettingsSerializationTest.kt b/shared/src/commonTest/kotlin/org/hyperskill/profile_settings/ProfileSettingsSerializationTest.kt new file mode 100644 index 0000000000..7617b9fd4b --- /dev/null +++ b/shared/src/commonTest/kotlin/org/hyperskill/profile_settings/ProfileSettingsSerializationTest.kt @@ -0,0 +1,25 @@ +package org.hyperskill.profile_settings + +import kotlin.test.Test +import kotlin.test.assertEquals +import org.hyperskill.app.network.injection.NetworkModule +import org.hyperskill.app.profile_settings.domain.model.ProfileSettings +import org.hyperskill.app.profile_settings.domain.model.Theme + +class ProfileSettingsSerializationTest { + companion object { + private const val TEST_JSON = """ + { + "theme": "system" + } + """ + } + + @Test + fun `Serialized profileSettings should be deserialized normally`() { + val json = NetworkModule.provideJson() + val expectedProfileSettings = ProfileSettings(Theme.SYSTEM) + val actualProfileSettings = json.decodeFromString(ProfileSettings.serializer(), TEST_JSON) + assertEquals(expectedProfileSettings, actualProfileSettings) + } +} \ No newline at end of file From a076568b34796adde8b8e936b10b1d395adc875f Mon Sep 17 00:00:00 2001 From: github-actions Date: Tue, 9 Jan 2024 09:38:19 +0000 Subject: [PATCH 04/19] 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 e84b875a36..066c52823c 100644 --- a/gradle/app.versions.toml +++ b/gradle/app.versions.toml @@ -3,4 +3,4 @@ minSdk = '24' targetSdk = '33' compileSdk = '33' versionName = '1.49' -versionCode = '297' \ No newline at end of file +versionCode = '298' \ No newline at end of file diff --git a/iosHyperskillApp/NotificationServiceExtension/Info.plist b/iosHyperskillApp/NotificationServiceExtension/Info.plist index ab51b52cec..d88f90667a 100644 --- a/iosHyperskillApp/NotificationServiceExtension/Info.plist +++ b/iosHyperskillApp/NotificationServiceExtension/Info.plist @@ -9,7 +9,7 @@ CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleVersion - 299 + 300 CFBundleShortVersionString 1.49 CFBundlePackageType diff --git a/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj b/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj index 2eb01692f3..a92d7a4201 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 = 299; + CURRENT_PROJECT_VERSION = 300; 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 = 299; + CURRENT_PROJECT_VERSION = 300; 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 = 299; + CURRENT_PROJECT_VERSION = 300; 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 = 299; + CURRENT_PROJECT_VERSION = 300; 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 = 299; + CURRENT_PROJECT_VERSION = 300; 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 = 299; + CURRENT_PROJECT_VERSION = 300; 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 = 299; + CURRENT_PROJECT_VERSION = 300; 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 = 299; + CURRENT_PROJECT_VERSION = 300; 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 b85727ab91..cdded4b6bd 100644 --- a/iosHyperskillApp/iosHyperskillApp/Info.plist +++ b/iosHyperskillApp/iosHyperskillApp/Info.plist @@ -36,7 +36,7 @@ CFBundleVersion - 299 + 300 FirebaseAppDelegateProxyEnabled FirebaseMessagingAutoInitEnabled diff --git a/iosHyperskillApp/iosHyperskillAppTests/Info.plist b/iosHyperskillApp/iosHyperskillAppTests/Info.plist index 4c30dcca27..9d119a0c53 100644 --- a/iosHyperskillApp/iosHyperskillAppTests/Info.plist +++ b/iosHyperskillApp/iosHyperskillAppTests/Info.plist @@ -15,6 +15,6 @@ CFBundleShortVersionString 1.49 CFBundleVersion - 299 + 300 diff --git a/iosHyperskillApp/iosHyperskillAppUITests/Info.plist b/iosHyperskillApp/iosHyperskillAppUITests/Info.plist index 9f21201ccb..65b8dd1a2f 100644 --- a/iosHyperskillApp/iosHyperskillAppUITests/Info.plist +++ b/iosHyperskillApp/iosHyperskillAppUITests/Info.plist @@ -15,6 +15,6 @@ CFBundleShortVersionString 1.49 CFBundleVersion - 299 + 300 From 38599bd90f3906dfb314e7f7aeeba78d6cef5c00 Mon Sep 17 00:00:00 2001 From: Ivan Magda Date: Tue, 9 Jan 2024 18:14:56 +0700 Subject: [PATCH 05/19] Shared: Fix gems are decreased but streak recovery cost is 0 for the first time (#841) ^ALTAPPS-836 --- .../app/main/presentation/AppFeature.kt | 1 + .../app/main/presentation/AppReducer.kt | 34 ++++++++++++++----- .../StreakRecoveryActionDispatcher.kt | 14 ++++---- .../presentation/StreakRecoveryFeature.kt | 10 ++++-- .../presentation/StreakRecoveryReducer.kt | 27 ++++++++++----- .../streak_recovery/StreakRecoveryTest.kt | 14 ++++---- .../streaks/domain/model/StreakStub.kt | 29 ++++++++++++++++ 7 files changed, 96 insertions(+), 33 deletions(-) create mode 100644 shared/src/commonTest/kotlin/org/hyperskill/streaks/domain/model/StreakStub.kt 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 9159dfae7a..d0bb39a7ab 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 @@ -24,6 +24,7 @@ interface AppFeature { data class Ready( val isAuthorized: Boolean, val isMobileLeaderboardsEnabled: Boolean, + internal val streakRecoveryState: StreakRecoveryFeature.State = StreakRecoveryFeature.State(), internal val welcomeOnboardingState: WelcomeOnboardingFeature.State = WelcomeOnboardingFeature.State() ) : State } 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 eb5d36a952..c6783b55cb 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 @@ -69,7 +69,13 @@ internal class AppReducer( is Message.OpenNewUserScreen -> state to setOf(Action.ViewAction.NavigateTo.TrackSelectionScreen) is Message.StreakRecoveryMessage -> - state to reduceStreakRecoveryMessage(message.message) + if (state is State.Ready) { + val (streakRecoveryState, streakRecoveryActions) = + reduceStreakRecoveryMessage(state.streakRecoveryState, message.message) + state.copy(streakRecoveryState = streakRecoveryState) to streakRecoveryActions + } else { + null + } is Message.NotificationClicked -> handleNotificationClicked(state, message) is Message.NotificationClickHandlingMessage -> @@ -120,16 +126,23 @@ internal class AppReducer( addAll(getNotAuthorizedAppStartUpActions()) add(Action.ViewAction.NavigateTo.OnboardingScreen) } + } - if (isAuthorized && message.notificationData == null) { - addAll(reduceStreakRecoveryMessage(StreakRecoveryFeature.Message.Initialize)) - } + 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 - ) to actions + isMobileLeaderboardsEnabled = message.profile.features.isMobileLeaderboardsEnabled, + streakRecoveryState = streakRecoveryState + ) to actions + streakRecoveryActions } else { state to emptySet() } @@ -157,11 +170,12 @@ internal class AppReducer( } private fun reduceStreakRecoveryMessage( + state: StreakRecoveryFeature.State, message: StreakRecoveryFeature.Message - ): Set { - val (_, streakRecoveryActions) = streakRecoveryReducer.reduce(StreakRecoveryFeature.State, message) + ): Pair> { + val (streakRecoveryState, streakRecoveryActions) = streakRecoveryReducer.reduce(state, message) - return streakRecoveryActions + val actions = streakRecoveryActions .map { if (it is StreakRecoveryFeature.Action.ViewAction) { Action.ViewAction.StreakRecoveryViewAction(it) @@ -170,6 +184,8 @@ internal class AppReducer( } } .toSet() + + return streakRecoveryState to actions } private fun handleNotificationClicked( diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/streak_recovery/presentation/StreakRecoveryActionDispatcher.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/streak_recovery/presentation/StreakRecoveryActionDispatcher.kt index 2d833a411a..3f0663e700 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/streak_recovery/presentation/StreakRecoveryActionDispatcher.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/streak_recovery/presentation/StreakRecoveryActionDispatcher.kt @@ -38,7 +38,7 @@ class StreakRecoveryActionDispatcher( val message = if (streak != null) { StreakRecoveryFeature.FetchStreakResult.Success( - streak.canBeRecovered, + streak, streak.recoveryPrice.toString(), resourceProvider.getQuantityString( SharedResources.plurals.gems_without_count, streak.recoveryPrice @@ -53,7 +53,7 @@ class StreakRecoveryActionDispatcher( onNewMessage(message) } - StreakRecoveryFeature.InternalAction.RecoverStreak -> { + is StreakRecoveryFeature.InternalAction.RecoverStreak -> { val message = streaksInteractor .recoverStreak() .fold( @@ -62,10 +62,9 @@ class StreakRecoveryActionDispatcher( streakFlow.notifyDataChanged(newStreak) currentProfileStateRepository.updateState { currentProfile -> - currentProfile.copy( - hypercoinsBalance = currentProfile.gamification.hypercoinsBalance - - newStreak.recoveryPrice - ) + val newHypercoinsBalance = + currentProfile.gamification.hypercoinsBalance - action.streak.recoveryPrice + currentProfile.copy(hypercoinsBalance = newHypercoinsBalance) } } StreakRecoveryFeature.RecoverStreakResult.Success( @@ -112,6 +111,9 @@ class StreakRecoveryActionDispatcher( onNewMessage(message) } + is StreakRecoveryFeature.InternalAction.CaptureSentryErrorMessage -> { + sentryInteractor.captureErrorMessage(action.message) + } is StreakRecoveryFeature.InternalAction.LogAnalyticEvent -> { analyticInteractor.logEvent(action.event) } diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/streak_recovery/presentation/StreakRecoveryFeature.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/streak_recovery/presentation/StreakRecoveryFeature.kt index d5f5d2e28e..7c86779a7b 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/streak_recovery/presentation/StreakRecoveryFeature.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/streak_recovery/presentation/StreakRecoveryFeature.kt @@ -1,9 +1,12 @@ package org.hyperskill.app.streak_recovery.presentation +import kotlinx.serialization.Serializable import org.hyperskill.app.analytic.domain.model.hyperskill.HyperskillAnalyticEvent +import org.hyperskill.app.streaks.domain.model.Streak object StreakRecoveryFeature { - object State + @Serializable + data class State(val streak: Streak? = null) sealed interface Message { object Initialize : Message @@ -21,7 +24,7 @@ object StreakRecoveryFeature { internal sealed interface FetchStreakResult : Message { data class Success( - val canRecoveryStreak: Boolean, + val streak: Streak, val recoveryPriceAmountLabel: String, val recoveryPriceGemsLabel: String, val modalText: String @@ -62,10 +65,11 @@ object StreakRecoveryFeature { internal sealed interface InternalAction : Action { object FetchStreak : InternalAction - object RecoverStreak : InternalAction + data class RecoverStreak(val streak: Streak) : InternalAction object CancelStreakRecovery : InternalAction + data class CaptureSentryErrorMessage(val message: String) : InternalAction data class LogAnalyticEvent(val event: HyperskillAnalyticEvent) : InternalAction } } \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/streak_recovery/presentation/StreakRecoveryReducer.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/streak_recovery/presentation/StreakRecoveryReducer.kt index da9c3245d7..993623a8b1 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/streak_recovery/presentation/StreakRecoveryReducer.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/streak_recovery/presentation/StreakRecoveryReducer.kt @@ -19,8 +19,8 @@ class StreakRecoveryReducer : StateReducer { state to setOf(InternalAction.FetchStreak) } is StreakRecoveryFeature.FetchStreakResult.Success -> { - if (message.canRecoveryStreak) { - state to setOf( + if (message.streak.canBeRecovered) { + state.copy(streak = message.streak) to setOf( Action.ViewAction.ShowRecoveryStreakModal( message.recoveryPriceAmountLabel, message.recoveryPriceGemsLabel, @@ -35,13 +35,22 @@ class StreakRecoveryReducer : StateReducer { null } Message.RestoreStreakClicked -> { - state to setOf( - Action.ViewAction.ShowNetworkRequestStatus.Loading, - InternalAction.RecoverStreak, - InternalAction.LogAnalyticEvent( - StreakRecoveryModalClickedRestoreStreakHyperskillAnalyticEvent() + if (state.streak != null) { + state to setOf( + Action.ViewAction.ShowNetworkRequestStatus.Loading, + InternalAction.RecoverStreak(state.streak), + InternalAction.LogAnalyticEvent( + StreakRecoveryModalClickedRestoreStreakHyperskillAnalyticEvent() + ) ) - ) + } else { + state to setOf( + InternalAction.LogAnalyticEvent( + StreakRecoveryModalClickedRestoreStreakHyperskillAnalyticEvent() + ), + InternalAction.CaptureSentryErrorMessage("StreakRecovery: restore streak, no streak found") + ) + } } Message.NoThanksClicked -> { state to setOf( @@ -80,7 +89,7 @@ class StreakRecoveryReducer : StateReducer { ) } Message.StreakRecoveryModalHiddenEventMessage -> { - state to setOf( + State() to setOf( InternalAction.LogAnalyticEvent(StreakRecoveryModalHiddenHyperskillAnalyticEvent()) ) } diff --git a/shared/src/commonTest/kotlin/org/hyperskill/streak_recovery/StreakRecoveryTest.kt b/shared/src/commonTest/kotlin/org/hyperskill/streak_recovery/StreakRecoveryTest.kt index 3727131574..a70674008b 100644 --- a/shared/src/commonTest/kotlin/org/hyperskill/streak_recovery/StreakRecoveryTest.kt +++ b/shared/src/commonTest/kotlin/org/hyperskill/streak_recovery/StreakRecoveryTest.kt @@ -4,6 +4,8 @@ import kotlin.test.Test import kotlin.test.assertTrue import org.hyperskill.app.streak_recovery.presentation.StreakRecoveryFeature import org.hyperskill.app.streak_recovery.presentation.StreakRecoveryReducer +import org.hyperskill.app.streaks.domain.model.Streak +import org.hyperskill.streaks.domain.model.stub class StreakRecoveryTest { private val streakRecoveryReducer = StreakRecoveryReducer() @@ -11,8 +13,8 @@ class StreakRecoveryTest { @Test fun `Streak recovery modal should be shown if recovery is available`() { val (_, actions) = streakRecoveryReducer.reduce( - StreakRecoveryFeature.State, - StreakRecoveryFeature.FetchStreakResult.Success(true, "", "", "") + StreakRecoveryFeature.State(), + StreakRecoveryFeature.FetchStreakResult.Success(Streak.stub(canBeRecovered = true), "", "", "") ) assertTrue { actions.any { @@ -24,8 +26,8 @@ class StreakRecoveryTest { @Test fun `Streak recovery modal should NOT be shown if recovery is NOT available`() { val (_, actions) = streakRecoveryReducer.reduce( - StreakRecoveryFeature.State, - StreakRecoveryFeature.FetchStreakResult.Success(false, "", "", "") + StreakRecoveryFeature.State(), + StreakRecoveryFeature.FetchStreakResult.Success(Streak.stub(canBeRecovered = false), "", "", "") ) assertTrue { actions.none { @@ -37,7 +39,7 @@ class StreakRecoveryTest { @Test fun `Recover streak action should be dispatched if 'Restore streak' clicked`() { val (_, actions) = streakRecoveryReducer.reduce( - StreakRecoveryFeature.State, + StreakRecoveryFeature.State(Streak.stub(canBeRecovered = true)), StreakRecoveryFeature.Message.RestoreStreakClicked ) assertTrue { @@ -50,7 +52,7 @@ class StreakRecoveryTest { @Test fun `Cancel streak recovery action should be dispatched if 'No thanks' clicked`() { val (_, actions) = streakRecoveryReducer.reduce( - StreakRecoveryFeature.State, + StreakRecoveryFeature.State(), StreakRecoveryFeature.Message.NoThanksClicked ) assertTrue { diff --git a/shared/src/commonTest/kotlin/org/hyperskill/streaks/domain/model/StreakStub.kt b/shared/src/commonTest/kotlin/org/hyperskill/streaks/domain/model/StreakStub.kt new file mode 100644 index 0000000000..14c15d5c86 --- /dev/null +++ b/shared/src/commonTest/kotlin/org/hyperskill/streaks/domain/model/StreakStub.kt @@ -0,0 +1,29 @@ +package org.hyperskill.streaks.domain.model + +import org.hyperskill.app.streaks.domain.model.HistoricalStreak +import org.hyperskill.app.streaks.domain.model.Streak + +fun Streak.Companion.stub( + userId: Long = 0L, + currentStreak: Int = 0, + maxStreak: Int = 0, + isNewRecord: Boolean = false, + canFreeze: Boolean = false, + canBuyFreeze: Boolean = false, + canBeRecovered: Boolean = false, + recoveryPrice: Int = 0, + previousStreak: Int = 0, + history: List = emptyList() +): Streak = + Streak( + userId = userId, + currentStreak = currentStreak, + maxStreak = maxStreak, + isNewRecord = isNewRecord, + canFreeze = canFreeze, + canBuyFreeze = canBuyFreeze, + canBeRecovered = canBeRecovered, + recoveryPrice = recoveryPrice, + previousStreak = previousStreak, + history = history + ) \ No newline at end of file From ee927d4272bb557b6256f3a14b17b540f9aac226 Mon Sep 17 00:00:00 2001 From: github-actions Date: Tue, 9 Jan 2024 11:15:46 +0000 Subject: [PATCH 06/19] 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 066c52823c..cc775732cd 100644 --- a/gradle/app.versions.toml +++ b/gradle/app.versions.toml @@ -3,4 +3,4 @@ minSdk = '24' targetSdk = '33' compileSdk = '33' versionName = '1.49' -versionCode = '298' \ No newline at end of file +versionCode = '299' \ No newline at end of file diff --git a/iosHyperskillApp/NotificationServiceExtension/Info.plist b/iosHyperskillApp/NotificationServiceExtension/Info.plist index d88f90667a..26b4ffd13a 100644 --- a/iosHyperskillApp/NotificationServiceExtension/Info.plist +++ b/iosHyperskillApp/NotificationServiceExtension/Info.plist @@ -9,7 +9,7 @@ CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleVersion - 300 + 301 CFBundleShortVersionString 1.49 CFBundlePackageType diff --git a/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj b/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj index a92d7a4201..7f0284e490 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 = 300; + CURRENT_PROJECT_VERSION = 301; 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 = 300; + CURRENT_PROJECT_VERSION = 301; 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 = 300; + CURRENT_PROJECT_VERSION = 301; 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 = 300; + CURRENT_PROJECT_VERSION = 301; 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 = 300; + CURRENT_PROJECT_VERSION = 301; 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 = 300; + CURRENT_PROJECT_VERSION = 301; 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 = 300; + CURRENT_PROJECT_VERSION = 301; 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 = 300; + CURRENT_PROJECT_VERSION = 301; 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 cdded4b6bd..ddf1ec2c53 100644 --- a/iosHyperskillApp/iosHyperskillApp/Info.plist +++ b/iosHyperskillApp/iosHyperskillApp/Info.plist @@ -36,7 +36,7 @@ CFBundleVersion - 300 + 301 FirebaseAppDelegateProxyEnabled FirebaseMessagingAutoInitEnabled diff --git a/iosHyperskillApp/iosHyperskillAppTests/Info.plist b/iosHyperskillApp/iosHyperskillAppTests/Info.plist index 9d119a0c53..6e63b03123 100644 --- a/iosHyperskillApp/iosHyperskillAppTests/Info.plist +++ b/iosHyperskillApp/iosHyperskillAppTests/Info.plist @@ -15,6 +15,6 @@ CFBundleShortVersionString 1.49 CFBundleVersion - 300 + 301 diff --git a/iosHyperskillApp/iosHyperskillAppUITests/Info.plist b/iosHyperskillApp/iosHyperskillAppUITests/Info.plist index 65b8dd1a2f..a45a42552b 100644 --- a/iosHyperskillApp/iosHyperskillAppUITests/Info.plist +++ b/iosHyperskillApp/iosHyperskillAppUITests/Info.plist @@ -15,6 +15,6 @@ CFBundleShortVersionString 1.49 CFBundleVersion - 300 + 301 From fdd139dec9516eaeee8812332513590faa6271f0 Mon Sep 17 00:00:00 2001 From: Ivan Magda Date: Tue, 9 Jan 2024 18:25:54 +0700 Subject: [PATCH 07/19] iOS: Add a state for the first streak recovery (#844) ^ALTAPPS-853 --- .../Sources/Models/Constants/Strings.swift | 1 + .../ViewControllers/AppViewController.swift | 2 + .../Modals/StreakRecoveryModalView.swift | 73 ++++++++------ .../StreakRecoveryModalViewController.swift | 4 + .../BadgeView/BadgeView+ConcreateTypes.swift | 34 +++---- .../HyperskillSentryTransactionBuilder.kt | 9 ++ .../injection/StreakRecoveryComponentImpl.kt | 18 ++-- .../StreakRecoveryActionDispatcher.kt | 96 +++++++++++++------ .../presentation/StreakRecoveryFeature.kt | 11 ++- .../presentation/StreakRecoveryReducer.kt | 53 +++++++--- .../commonMain/resources/MR/base/plurals.xml | 4 + .../commonMain/resources/MR/base/strings.xml | 1 + .../org/hyperskill/main/AppFeatureTest.kt | 3 +- .../products/domain/model/ProductStub.kt | 12 +++ .../streak_recovery/StreakRecoveryTest.kt | 11 ++- 15 files changed, 224 insertions(+), 108 deletions(-) create mode 100644 shared/src/commonTest/kotlin/org/hyperskill/products/domain/model/ProductStub.kt diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Models/Constants/Strings.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Models/Constants/Strings.swift index 90dc276bee..f812304664 100644 --- a/iosHyperskillApp/iosHyperskillApp/Sources/Models/Constants/Strings.swift +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Models/Constants/Strings.swift @@ -32,6 +32,7 @@ enum Strings { static let bestRating = sharedStrings.badge_best_rating_text.localized() static let fastestToComplete = sharedStrings.badge_fastest_to_complete_text.localized() static let beta = sharedStrings.badge_beta_text.localized() + static let firstTimeOffer = sharedStrings.badge_first_time_offer_text.localized() } // MARK: - TabBar - diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/App/ViewControllers/AppViewController.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/App/ViewControllers/AppViewController.swift index 56a5a76f75..f73a5c5f30 100644 --- a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/App/ViewControllers/AppViewController.swift +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/App/ViewControllers/AppViewController.swift @@ -149,6 +149,8 @@ extension AppViewController: AppViewControllerProtocol { recoveryPriceAmount: showRecoveryStreakModal.recoveryPriceAmountLabel, recoveryPriceLabel: showRecoveryStreakModal.recoveryPriceGemsLabel, modalText: showRecoveryStreakModal.modalText, + isFirstTimeOffer: showRecoveryStreakModal.isFirstTimeOffer, + nextRecoveryPriceText: showRecoveryStreakModal.nextRecoveryPriceText, delegate: viewModel ) presentIfPanModalWithCustomModalPresentationStyle(modalViewController) diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/Streak/Views/Modals/StreakRecoveryModalView.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/Streak/Views/Modals/StreakRecoveryModalView.swift index 8d53779b72..841905d153 100644 --- a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/Streak/Views/Modals/StreakRecoveryModalView.swift +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/Streak/Views/Modals/StreakRecoveryModalView.swift @@ -3,7 +3,7 @@ import SwiftUI extension StreakRecoveryModalView { struct Appearance { let largeSpacing: CGFloat = 32 - let gemsBadgeWidthHeight: CGFloat = 36 + let gemsBadgeIconSize = CGSize(width: 36, height: 36) let warningBottomPadding: CGFloat = 4 } } @@ -14,6 +14,9 @@ struct StreakRecoveryModalView: View { let recoveryPriceAmount: String let recoveryPriceLabel: String let modalText: String + let isFirstTimeOffer: Bool + let nextRecoveryPriceText: String? + let restoreStreakButtonTapped: () -> Void let noThanksButtonTapped: () -> Void @@ -34,16 +37,24 @@ struct StreakRecoveryModalView: View { .foregroundColor(.primaryText) .font(.body) - #warning("Migrate to the HypercoinLabel") - HStack(alignment: .center, spacing: LayoutInsets.smallInset) { - Image(Images.StepQuiz.ProblemOfDaySolvedModal.gemsBadge) - .renderingMode(.original) - .resizable() - .aspectRatio(contentMode: .fit) - .frame(widthHeight: appearance.gemsBadgeWidthHeight) + HStack(spacing: LayoutInsets.smallInset) { + HypercoinLabel( + title: { + Text("\(Text(recoveryPriceAmount).bold()) \(recoveryPriceLabel)") + .font(.body) + .foregroundColor(.primaryText) + }, + appearance: .init(iconSize: appearance.gemsBadgeIconSize) + ) + + if isFirstTimeOffer { + BadgeView.firstTimeOffer() + } + } - Text("\(Text(recoveryPriceAmount).bold()) \(recoveryPriceLabel)") - .foregroundColor(.primaryText) + if let nextRecoveryPriceText { + Text(nextRecoveryPriceText) + .foregroundColor(.tertiaryText) .font(.body) } } @@ -69,26 +80,26 @@ struct StreakRecoveryModalView: View { } } -struct StreakRecoveryModalView_Previews: PreviewProvider { - static var previews: some View { - Group { - StreakRecoveryModalView( - recoveryPriceAmount: "25", - recoveryPriceLabel: "gems", - modalText: "Good to see you back! You used to study daily and had a N-day streak. Great job!", - restoreStreakButtonTapped: {}, - noThanksButtonTapped: {} - ) +#Preview { + StreakRecoveryModalView( + recoveryPriceAmount: "25", + recoveryPriceLabel: "gems", + modalText: "Good to see you back! You used to study daily and had a N-day streak. Great job!", + isFirstTimeOffer: false, + nextRecoveryPriceText: nil, + restoreStreakButtonTapped: {}, + noThanksButtonTapped: {} + ) +} - StreakRecoveryModalView( - recoveryPriceAmount: "25", - recoveryPriceLabel: "gems", - modalText: "Good to see you back! You used to study daily and had a N-day streak. Great job!", - restoreStreakButtonTapped: {}, - noThanksButtonTapped: {} - ) - .preferredColorScheme(.dark) - } - .previewLayout(.sizeThatFits) - } +#Preview { + StreakRecoveryModalView( + recoveryPriceAmount: "0", + recoveryPriceLabel: "gems", + modalText: "Good to see you back! You used to study daily and had a N-day streak. Great job!", + isFirstTimeOffer: true, + nextRecoveryPriceText: "Then for 25 gems", + restoreStreakButtonTapped: {}, + noThanksButtonTapped: {} + ) } diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/Streak/Views/Modals/StreakRecoveryModalViewController.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/Streak/Views/Modals/StreakRecoveryModalViewController.swift index bf448639b5..a2fd362dcb 100644 --- a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/Streak/Views/Modals/StreakRecoveryModalViewController.swift +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/Streak/Views/Modals/StreakRecoveryModalViewController.swift @@ -17,12 +17,16 @@ class StreakRecoveryModalViewController: PanModalSwiftUIViewController BadgeView { BadgeView(text: Strings.Common.completed, style: .green) } -} -// MARK: - BadgeViewConcreateTypes_Previews: PreviewProvider - + static func firstTimeOffer() -> BadgeView { + BadgeView(text: Strings.Badge.firstTimeOffer, style: .green) + } +} -struct BadgeViewConcreateTypes_Previews: PreviewProvider { - static var previews: some View { - VStack { - BadgeView.ideRequired() +#Preview { + VStack { + BadgeView.ideRequired() - BadgeView.current() + BadgeView.current() - BadgeView.solveUnlimited() - BadgeView.repeatUnlimited() + BadgeView.solveUnlimited() + BadgeView.repeatUnlimited() - BadgeView.selected() - BadgeView.bestRating() - BadgeView.fastestToComplete() + BadgeView.selected() + BadgeView.bestRating() + BadgeView.fastestToComplete() - BadgeView.beta() - BadgeView.completed() - } - .padding() - .previewLayout(.sizeThatFits) + BadgeView.beta() + BadgeView.completed() + BadgeView.firstTimeOffer() } + .padding() } 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 49c627e079..f599b85a04 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 @@ -318,4 +318,13 @@ object HyperskillSentryTransactionBuilder { name = "interview-preparation-widget-feature-fetch-interview-steps", operation = HyperskillSentryTransactionOperation.API_LOAD ) + + /** + * StreakRecoveryFeature + */ + fun buildStreakRecoveryFeatureFetchStreak(): HyperskillSentryTransaction = + HyperskillSentryTransaction( + name = "streak-recovery-feature-fetch-streak", + operation = HyperskillSentryTransactionOperation.API_LOAD + ) } \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/streak_recovery/injection/StreakRecoveryComponentImpl.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/streak_recovery/injection/StreakRecoveryComponentImpl.kt index 49c9728446..7bdf766757 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/streak_recovery/injection/StreakRecoveryComponentImpl.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/streak_recovery/injection/StreakRecoveryComponentImpl.kt @@ -5,20 +5,22 @@ import org.hyperskill.app.core.presentation.ActionDispatcherOptions import org.hyperskill.app.streak_recovery.presentation.StreakRecoveryActionDispatcher import org.hyperskill.app.streak_recovery.presentation.StreakRecoveryReducer -class StreakRecoveryComponentImpl( +internal class StreakRecoveryComponentImpl( private val appGraph: AppGraph ) : StreakRecoveryComponent { override val streakRecoveryReducer: StreakRecoveryReducer - get() = StreakRecoveryReducer() + get() = StreakRecoveryReducer(resourceProvider = appGraph.commonComponent.resourceProvider) override val streakRecoveryActionDispatcher: StreakRecoveryActionDispatcher get() = StreakRecoveryActionDispatcher( ActionDispatcherOptions(), - appGraph.profileDataComponent.currentProfileStateRepository, - appGraph.buildStreaksDataComponent().streaksInteractor, - appGraph.analyticComponent.analyticInteractor, - appGraph.sentryComponent.sentryInteractor, - appGraph.streakFlowDataComponent.streakFlow, - appGraph.commonComponent.resourceProvider + currentProfileStateRepository = appGraph.profileDataComponent.currentProfileStateRepository, + streaksInteractor = appGraph.buildStreaksDataComponent().streaksInteractor, + productsInteractor = appGraph.buildProductsDataComponent().productsInteractor, + analyticInteractor = appGraph.analyticComponent.analyticInteractor, + sentryInteractor = appGraph.sentryComponent.sentryInteractor, + logger = appGraph.loggerComponent.logger, + streakFlow = appGraph.streakFlowDataComponent.streakFlow, + resourceProvider = appGraph.commonComponent.resourceProvider ) } \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/streak_recovery/presentation/StreakRecoveryActionDispatcher.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/streak_recovery/presentation/StreakRecoveryActionDispatcher.kt index 3f0663e700..a1a02b8b05 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/streak_recovery/presentation/StreakRecoveryActionDispatcher.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/streak_recovery/presentation/StreakRecoveryActionDispatcher.kt @@ -1,13 +1,20 @@ package org.hyperskill.app.streak_recovery.presentation +import co.touchlab.kermit.Logger +import co.touchlab.kermit.Severity +import kotlinx.coroutines.async +import kotlinx.coroutines.coroutineScope import org.hyperskill.app.SharedResources 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.products.domain.interactor.ProductsInteractor import org.hyperskill.app.profile.domain.model.copy 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.streak_recovery.presentation.StreakRecoveryFeature.Action import org.hyperskill.app.streak_recovery.presentation.StreakRecoveryFeature.Message import org.hyperskill.app.streaks.domain.flow.StreakFlow @@ -18,40 +25,21 @@ class StreakRecoveryActionDispatcher( config: ActionDispatcherOptions, private val currentProfileStateRepository: CurrentProfileStateRepository, private val streaksInteractor: StreaksInteractor, + private val productsInteractor: ProductsInteractor, private val analyticInteractor: AnalyticInteractor, private val sentryInteractor: SentryInteractor, + private val logger: Logger, private val streakFlow: StreakFlow, private val resourceProvider: ResourceProvider ) : CoroutineActionDispatcher(config.createConfig()) { + companion object { + private const val LOG_TAG = "StreakRecovery" + } + override suspend fun doSuspendableAction(action: Action) { when (action) { StreakRecoveryFeature.InternalAction.FetchStreak -> { - val currentProfile = currentProfileStateRepository - .getState() - .onFailure { sentryInteractor.captureErrorMessage("StreakRecovery: fetch streak $it") } - .getOrElse { return onNewMessage(StreakRecoveryFeature.FetchStreakResult.Error) } - - val streak = streaksInteractor - .getUserStreak(currentProfile.id) - .onFailure { sentryInteractor.captureErrorMessage("StreakRecovery: fetch streak $it") } - .getOrElse { return onNewMessage(StreakRecoveryFeature.FetchStreakResult.Error) } - - val message = if (streak != null) { - StreakRecoveryFeature.FetchStreakResult.Success( - streak, - streak.recoveryPrice.toString(), - resourceProvider.getQuantityString( - SharedResources.plurals.gems_without_count, streak.recoveryPrice - ), - resourceProvider.getString( - SharedResources.strings.streak_recovery_modal_text, streak.previousStreak - ) - ) - } else { - StreakRecoveryFeature.FetchStreakResult.Error - } - - onNewMessage(message) + handleFetchStreakAction(::onNewMessage) } is StreakRecoveryFeature.InternalAction.RecoverStreak -> { val message = streaksInteractor @@ -74,7 +62,12 @@ class StreakRecoveryActionDispatcher( ) }, onFailure = { - sentryInteractor.captureErrorMessage("StreakRecovery: recover streak $it") + logger.log( + severity = Severity.Error, + tag = LOG_TAG, + throwable = null, + message = "recover streak $it" + ) StreakRecoveryFeature.RecoverStreakResult.Error( resourceProvider.getString( SharedResources.strings.streak_recovery_modal_recover_streak_error_message @@ -100,7 +93,12 @@ class StreakRecoveryActionDispatcher( ) }, onFailure = { - sentryInteractor.captureErrorMessage("StreakRecovery: cancel streak recovery $it") + logger.log( + severity = Severity.Error, + tag = LOG_TAG, + throwable = null, + message = "cancel streak recovery $it" + ) StreakRecoveryFeature.CancelStreakRecoveryResult.Error( resourceProvider.getString( SharedResources.strings.streak_recovery_modal_cancel_streak_recovery_error_message @@ -111,8 +109,13 @@ class StreakRecoveryActionDispatcher( onNewMessage(message) } - is StreakRecoveryFeature.InternalAction.CaptureSentryErrorMessage -> { - sentryInteractor.captureErrorMessage(action.message) + is StreakRecoveryFeature.InternalAction.CaptureErrorMessage -> { + logger.log( + severity = Severity.Error, + tag = LOG_TAG, + throwable = null, + message = action.message + ) } is StreakRecoveryFeature.InternalAction.LogAnalyticEvent -> { analyticInteractor.logEvent(action.event) @@ -122,4 +125,37 @@ class StreakRecoveryActionDispatcher( } } } + + private suspend fun handleFetchStreakAction(onNewMessage: (Message) -> Unit) { + sentryInteractor.withTransaction( + HyperskillSentryTransactionBuilder.buildStreakRecoveryFeatureFetchStreak(), + onError = { + logger.log( + severity = Severity.Error, + tag = LOG_TAG, + throwable = null, + message = "fetch streak $it" + ) + StreakRecoveryFeature.FetchStreakResult.Error + } + ) { + coroutineScope { + val currentProfile = currentProfileStateRepository + .getState(forceUpdate = false) + .getOrThrow() + + val streakResult = async { streaksInteractor.getUserStreak(currentProfile.id) } + val streakFreezeProductResult = async { productsInteractor.getStreakFreezeProduct() } + + val streak = streakResult.await().getOrThrow() + val streakFreezeProduct = streakFreezeProductResult.await().getOrThrow() + + if (streak != null) { + StreakRecoveryFeature.FetchStreakResult.Success(streak, streakFreezeProduct) + } else { + StreakRecoveryFeature.FetchStreakResult.Error + } + } + }.let(onNewMessage) + } } \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/streak_recovery/presentation/StreakRecoveryFeature.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/streak_recovery/presentation/StreakRecoveryFeature.kt index 7c86779a7b..06e0948280 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/streak_recovery/presentation/StreakRecoveryFeature.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/streak_recovery/presentation/StreakRecoveryFeature.kt @@ -2,6 +2,7 @@ package org.hyperskill.app.streak_recovery.presentation import kotlinx.serialization.Serializable import org.hyperskill.app.analytic.domain.model.hyperskill.HyperskillAnalyticEvent +import org.hyperskill.app.products.domain.model.Product import org.hyperskill.app.streaks.domain.model.Streak object StreakRecoveryFeature { @@ -25,9 +26,7 @@ object StreakRecoveryFeature { internal sealed interface FetchStreakResult : Message { data class Success( val streak: Streak, - val recoveryPriceAmountLabel: String, - val recoveryPriceGemsLabel: String, - val modalText: String + val streakFreezeProduct: Product ) : FetchStreakResult object Error : FetchStreakResult @@ -49,7 +48,9 @@ object StreakRecoveryFeature { val recoveryPriceAmountLabel: String, // passed separately from price because price amount is highlighted with bold val recoveryPriceGemsLabel: String, - val modalText: String + val modalText: String, + val isFirstTimeOffer: Boolean, + val nextRecoveryPriceText: String? ) : ViewAction object HideStreakRecoveryModal : ViewAction @@ -69,7 +70,7 @@ object StreakRecoveryFeature { object CancelStreakRecovery : InternalAction - data class CaptureSentryErrorMessage(val message: String) : InternalAction + data class CaptureErrorMessage(val message: String) : InternalAction data class LogAnalyticEvent(val event: HyperskillAnalyticEvent) : InternalAction } } \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/streak_recovery/presentation/StreakRecoveryReducer.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/streak_recovery/presentation/StreakRecoveryReducer.kt index 993623a8b1..12c34e008b 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/streak_recovery/presentation/StreakRecoveryReducer.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/streak_recovery/presentation/StreakRecoveryReducer.kt @@ -1,5 +1,7 @@ package org.hyperskill.app.streak_recovery.presentation +import org.hyperskill.app.SharedResources +import org.hyperskill.app.core.view.mapper.ResourceProvider import org.hyperskill.app.streak_recovery.domain.analytic.StreakRecoveryModalClickedNoThanksHyperskillAnalyticEvent import org.hyperskill.app.streak_recovery.domain.analytic.StreakRecoveryModalClickedRestoreStreakHyperskillAnalyticEvent import org.hyperskill.app.streak_recovery.domain.analytic.StreakRecoveryModalHiddenHyperskillAnalyticEvent @@ -12,24 +14,16 @@ import ru.nobird.app.presentation.redux.reducer.StateReducer private typealias ReducerResult = Pair> -class StreakRecoveryReducer : StateReducer { +class StreakRecoveryReducer( + private val resourceProvider: ResourceProvider +) : StateReducer { override fun reduce(state: State, message: Message): ReducerResult = when (message) { Message.Initialize -> { state to setOf(InternalAction.FetchStreak) } is StreakRecoveryFeature.FetchStreakResult.Success -> { - if (message.streak.canBeRecovered) { - state.copy(streak = message.streak) to setOf( - Action.ViewAction.ShowRecoveryStreakModal( - message.recoveryPriceAmountLabel, - message.recoveryPriceGemsLabel, - message.modalText - ) - ) - } else { - null - } + handleFetchStreakResultSuccessMessage(state, message) } StreakRecoveryFeature.FetchStreakResult.Error -> { null @@ -48,7 +42,7 @@ class StreakRecoveryReducer : StateReducer { InternalAction.LogAnalyticEvent( StreakRecoveryModalClickedRestoreStreakHyperskillAnalyticEvent() ), - InternalAction.CaptureSentryErrorMessage("StreakRecovery: restore streak, no streak found") + InternalAction.CaptureErrorMessage("restore streak, no streak found") ) } } @@ -94,4 +88,37 @@ class StreakRecoveryReducer : StateReducer { ) } } ?: (state to emptySet()) + + private fun handleFetchStreakResultSuccessMessage( + state: State, + message: StreakRecoveryFeature.FetchStreakResult.Success + ): ReducerResult? = + if (message.streak.canBeRecovered) { + val isFirstTimeOffer = message.streak.recoveryPrice == 0 + val nextRecoveryPriceText = if (isFirstTimeOffer) { + resourceProvider.getQuantityString( + SharedResources.plurals.streak_recovery_next_recovery_description, + message.streakFreezeProduct.price, + message.streakFreezeProduct.price + ) + } else { + null + } + + state.copy(streak = message.streak) to setOf( + Action.ViewAction.ShowRecoveryStreakModal( + recoveryPriceAmountLabel = message.streak.recoveryPrice.toString(), + recoveryPriceGemsLabel = resourceProvider.getQuantityString( + SharedResources.plurals.gems_without_count, message.streak.recoveryPrice + ), + modalText = resourceProvider.getString( + SharedResources.strings.streak_recovery_modal_text, message.streak.previousStreak + ), + isFirstTimeOffer = isFirstTimeOffer, + nextRecoveryPriceText = nextRecoveryPriceText + ) + ) + } else { + null + } } \ No newline at end of file diff --git a/shared/src/commonMain/resources/MR/base/plurals.xml b/shared/src/commonMain/resources/MR/base/plurals.xml index 62b958f259..0c0924b39f 100644 --- a/shared/src/commonMain/resources/MR/base/plurals.xml +++ b/shared/src/commonMain/resources/MR/base/plurals.xml @@ -144,4 +144,8 @@ problem at the hard level to solve problems at the hard level to solve + + Then for %d gem + Then for %d gems + \ 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 e46605fbbc..7fc4a79abc 100644 --- a/shared/src/commonMain/resources/MR/base/strings.xml +++ b/shared/src/commonMain/resources/MR/base/strings.xml @@ -29,6 +29,7 @@ Best rating Fastest to complete Beta + first-time offer Search diff --git a/shared/src/commonTest/kotlin/org/hyperskill/main/AppFeatureTest.kt b/shared/src/commonTest/kotlin/org/hyperskill/main/AppFeatureTest.kt index 330ee31742..125ebae269 100644 --- a/shared/src/commonTest/kotlin/org/hyperskill/main/AppFeatureTest.kt +++ b/shared/src/commonTest/kotlin/org/hyperskill/main/AppFeatureTest.kt @@ -2,6 +2,7 @@ package org.hyperskill.main import kotlin.test.Test import kotlin.test.assertTrue +import org.hyperskill.ResourceProviderStub import org.hyperskill.app.core.domain.platform.PlatformType import org.hyperskill.app.main.presentation.AppFeature import org.hyperskill.app.main.presentation.AppReducer @@ -17,7 +18,7 @@ import org.hyperskill.profile.stub class AppFeatureTest { private val appReducer = AppReducer( - StreakRecoveryReducer(), + StreakRecoveryReducer(resourceProvider = ResourceProviderStub()), NotificationClickHandlingReducer(), WelcomeOnboardingReducer(), PlatformType.ANDROID diff --git a/shared/src/commonTest/kotlin/org/hyperskill/products/domain/model/ProductStub.kt b/shared/src/commonTest/kotlin/org/hyperskill/products/domain/model/ProductStub.kt new file mode 100644 index 0000000000..2a6ab7eae0 --- /dev/null +++ b/shared/src/commonTest/kotlin/org/hyperskill/products/domain/model/ProductStub.kt @@ -0,0 +1,12 @@ +package org.hyperskill.products.domain.model + +import org.hyperskill.app.products.domain.model.Product + +fun Product.Companion.stub( + id: Long = 0L, + price: Int = 0 +): Product = + Product( + id = id, + price = price + ) \ No newline at end of file diff --git a/shared/src/commonTest/kotlin/org/hyperskill/streak_recovery/StreakRecoveryTest.kt b/shared/src/commonTest/kotlin/org/hyperskill/streak_recovery/StreakRecoveryTest.kt index a70674008b..4fa32ce025 100644 --- a/shared/src/commonTest/kotlin/org/hyperskill/streak_recovery/StreakRecoveryTest.kt +++ b/shared/src/commonTest/kotlin/org/hyperskill/streak_recovery/StreakRecoveryTest.kt @@ -2,19 +2,24 @@ package org.hyperskill.streak_recovery import kotlin.test.Test import kotlin.test.assertTrue +import org.hyperskill.ResourceProviderStub +import org.hyperskill.app.products.domain.model.Product import org.hyperskill.app.streak_recovery.presentation.StreakRecoveryFeature import org.hyperskill.app.streak_recovery.presentation.StreakRecoveryReducer import org.hyperskill.app.streaks.domain.model.Streak +import org.hyperskill.products.domain.model.stub import org.hyperskill.streaks.domain.model.stub class StreakRecoveryTest { - private val streakRecoveryReducer = StreakRecoveryReducer() + private val streakRecoveryReducer = StreakRecoveryReducer( + resourceProvider = ResourceProviderStub() + ) @Test fun `Streak recovery modal should be shown if recovery is available`() { val (_, actions) = streakRecoveryReducer.reduce( StreakRecoveryFeature.State(), - StreakRecoveryFeature.FetchStreakResult.Success(Streak.stub(canBeRecovered = true), "", "", "") + StreakRecoveryFeature.FetchStreakResult.Success(Streak.stub(canBeRecovered = true), Product.stub()) ) assertTrue { actions.any { @@ -27,7 +32,7 @@ class StreakRecoveryTest { fun `Streak recovery modal should NOT be shown if recovery is NOT available`() { val (_, actions) = streakRecoveryReducer.reduce( StreakRecoveryFeature.State(), - StreakRecoveryFeature.FetchStreakResult.Success(Streak.stub(canBeRecovered = false), "", "", "") + StreakRecoveryFeature.FetchStreakResult.Success(Streak.stub(canBeRecovered = false), Product.stub()) ) assertTrue { actions.none { From 133246275f589b1f052ec70e46d5f376e57ae1c3 Mon Sep 17 00:00:00 2001 From: github-actions Date: Tue, 9 Jan 2024 11:26:41 +0000 Subject: [PATCH 08/19] 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 cc775732cd..ec1d57ac17 100644 --- a/gradle/app.versions.toml +++ b/gradle/app.versions.toml @@ -3,4 +3,4 @@ minSdk = '24' targetSdk = '33' compileSdk = '33' versionName = '1.49' -versionCode = '299' \ No newline at end of file +versionCode = '300' \ No newline at end of file diff --git a/iosHyperskillApp/NotificationServiceExtension/Info.plist b/iosHyperskillApp/NotificationServiceExtension/Info.plist index 26b4ffd13a..d62897d053 100644 --- a/iosHyperskillApp/NotificationServiceExtension/Info.plist +++ b/iosHyperskillApp/NotificationServiceExtension/Info.plist @@ -9,7 +9,7 @@ CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleVersion - 301 + 302 CFBundleShortVersionString 1.49 CFBundlePackageType diff --git a/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj b/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj index 7f0284e490..74a325afff 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 = 301; + CURRENT_PROJECT_VERSION = 302; 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 = 301; + CURRENT_PROJECT_VERSION = 302; 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 = 301; + CURRENT_PROJECT_VERSION = 302; 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 = 301; + CURRENT_PROJECT_VERSION = 302; 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 = 301; + CURRENT_PROJECT_VERSION = 302; 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 = 301; + CURRENT_PROJECT_VERSION = 302; 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 = 301; + CURRENT_PROJECT_VERSION = 302; 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 = 301; + CURRENT_PROJECT_VERSION = 302; 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 ddf1ec2c53..c8b84d6703 100644 --- a/iosHyperskillApp/iosHyperskillApp/Info.plist +++ b/iosHyperskillApp/iosHyperskillApp/Info.plist @@ -36,7 +36,7 @@ CFBundleVersion - 301 + 302 FirebaseAppDelegateProxyEnabled FirebaseMessagingAutoInitEnabled diff --git a/iosHyperskillApp/iosHyperskillAppTests/Info.plist b/iosHyperskillApp/iosHyperskillAppTests/Info.plist index 6e63b03123..92da8741a0 100644 --- a/iosHyperskillApp/iosHyperskillAppTests/Info.plist +++ b/iosHyperskillApp/iosHyperskillAppTests/Info.plist @@ -15,6 +15,6 @@ CFBundleShortVersionString 1.49 CFBundleVersion - 301 + 302 diff --git a/iosHyperskillApp/iosHyperskillAppUITests/Info.plist b/iosHyperskillApp/iosHyperskillAppUITests/Info.plist index a45a42552b..e5a2289b94 100644 --- a/iosHyperskillApp/iosHyperskillAppUITests/Info.plist +++ b/iosHyperskillApp/iosHyperskillAppUITests/Info.plist @@ -15,6 +15,6 @@ CFBundleShortVersionString 1.49 CFBundleVersion - 301 + 302 From 4e93c3ed5f95187a3f0000666787e04b1360c041 Mon Sep 17 00:00:00 2001 From: Ivan Magda Date: Wed, 10 Jan 2024 14:19:21 +0700 Subject: [PATCH 09/19] fastlane: Update metadata (#846) --- .../metadata/android/en-US/changelogs/default.txt | 6 +----- iosHyperskillApp/fastlane/Fastfile | 2 +- .../fastlane/metadata/en-US/release_notes.txt | 9 +-------- .../metadata/review_information/email_address.txt | Bin 44 -> 61 bytes .../metadata/review_information/first_name.txt | Bin 27 -> 30 bytes .../metadata/review_information/last_name.txt | Bin 28 -> 29 bytes 6 files changed, 3 insertions(+), 14 deletions(-) diff --git a/androidHyperskillApp/fastlane/metadata/android/en-US/changelogs/default.txt b/androidHyperskillApp/fastlane/metadata/android/en-US/changelogs/default.txt index 8001c189cd..8d49f9747b 100644 --- a/androidHyperskillApp/fastlane/metadata/android/en-US/changelogs/default.txt +++ b/androidHyperskillApp/fastlane/metadata/android/en-US/changelogs/default.txt @@ -1,5 +1 @@ -In the new version of My Hyperskill: -• Added a retry button for problems -• Bug fixes and improvements using your feedback - -We are working hard to bring even more Hyperskill features to your mobile phone. Currently, My Hyperskill app is in beta, so we would love to hear any feedback from you. Feel free to contact us at hello@hyperskill.org and share your thoughts! \ No newline at end of file +Bug fixes, minor improvements, and more. \ No newline at end of file diff --git a/iosHyperskillApp/fastlane/Fastfile b/iosHyperskillApp/fastlane/Fastfile index 3aeacb24cf..4df334d708 100644 --- a/iosHyperskillApp/fastlane/Fastfile +++ b/iosHyperskillApp/fastlane/Fastfile @@ -144,7 +144,7 @@ platform :ios do load_app_store_connect_api_token end - # sync_device_info() + sync_device_info() match_adhoc() # Disable automatic code signing -> build -> enable again diff --git a/iosHyperskillApp/fastlane/metadata/en-US/release_notes.txt b/iosHyperskillApp/fastlane/metadata/en-US/release_notes.txt index 5d09562832..8d49f9747b 100644 --- a/iosHyperskillApp/fastlane/metadata/en-US/release_notes.txt +++ b/iosHyperskillApp/fastlane/metadata/en-US/release_notes.txt @@ -1,8 +1 @@ -In the new version of My Hyperskill: -• Added a retry button for problems -• Updated appearance of the daily study reminders time picker on the Profile screen -• Authorization session forwarding on continuing in the Web-Version -• Meet hints for problems. On the Problem screen, you will see "See hint" button, tap on it to see hints. -• Bug fixes and improvements using your feedback - -We are working hard to bring even more Hyperskill features to your mobile phone. Currently, My Hyperskill app is in beta, so we would love to hear any feedback from you. Feel free to contact us at hello@hyperskill.org and share your thoughts! \ No newline at end of file +Bug fixes, minor improvements, and more. \ No newline at end of file diff --git a/iosHyperskillApp/fastlane/metadata/review_information/email_address.txt b/iosHyperskillApp/fastlane/metadata/review_information/email_address.txt index 8279f6220a13b06fea7f17e4c56f0ec53ba14096..1f4bd0ad9a1ca04a0d28503213a85a6c2ac3de93 100644 GIT binary patch literal 61 zcmV-D0K)$OM@dveQdv+`0Dc}*@M!8+1?cAhR{xymfA5+D?iQZNqf5Hz?2qCDSVMsA TR9A&+PCR970$ClWCF?N{Os5-s literal 44 zcmZQ@_Y83kiVO&0_-(`au=SmRU3AZ<1qrAaSvJu!F>pNdSgV3I6~9 literal 27 jcmZQ@_Y83kiVO&0xF@yBWX4Y>iKc=O!O2;(&szfkd$$Sk diff --git a/iosHyperskillApp/fastlane/metadata/review_information/last_name.txt b/iosHyperskillApp/fastlane/metadata/review_information/last_name.txt index 8d365462d78fbc0f41adfe527c137e6ac297d03a..6c5c48e8f2d11bfbf0a74695bf4b2d71056c57a2 100644 GIT binary patch literal 29 lcmZQ@_Y83kiVO&0SbJt#-1-ZAzPAn~mZqF4II#SuIRL5K4P^iT literal 28 kcmZQ@_Y83kiVO&0DBS(VjpKdL{nH)Nw~tQwy0Y#c0G+T6xc~qF From d0129800910c54d72813090f5ed6536c9504aea4 Mon Sep 17 00:00:00 2001 From: github-actions Date: Wed, 10 Jan 2024 07:20:14 +0000 Subject: [PATCH 10/19] 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 ec1d57ac17..7af4aa2246 100644 --- a/gradle/app.versions.toml +++ b/gradle/app.versions.toml @@ -3,4 +3,4 @@ minSdk = '24' targetSdk = '33' compileSdk = '33' versionName = '1.49' -versionCode = '300' \ No newline at end of file +versionCode = '301' \ No newline at end of file diff --git a/iosHyperskillApp/NotificationServiceExtension/Info.plist b/iosHyperskillApp/NotificationServiceExtension/Info.plist index d62897d053..9eb2dac92a 100644 --- a/iosHyperskillApp/NotificationServiceExtension/Info.plist +++ b/iosHyperskillApp/NotificationServiceExtension/Info.plist @@ -9,7 +9,7 @@ CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleVersion - 302 + 303 CFBundleShortVersionString 1.49 CFBundlePackageType diff --git a/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj b/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj index 74a325afff..90f3f6de21 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 = 302; + CURRENT_PROJECT_VERSION = 303; 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 = 302; + CURRENT_PROJECT_VERSION = 303; 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 = 302; + CURRENT_PROJECT_VERSION = 303; 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 = 302; + CURRENT_PROJECT_VERSION = 303; 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 = 302; + CURRENT_PROJECT_VERSION = 303; 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 = 302; + CURRENT_PROJECT_VERSION = 303; 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 = 302; + CURRENT_PROJECT_VERSION = 303; 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 = 302; + CURRENT_PROJECT_VERSION = 303; 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 c8b84d6703..46bdd9ceeb 100644 --- a/iosHyperskillApp/iosHyperskillApp/Info.plist +++ b/iosHyperskillApp/iosHyperskillApp/Info.plist @@ -36,7 +36,7 @@ CFBundleVersion - 302 + 303 FirebaseAppDelegateProxyEnabled FirebaseMessagingAutoInitEnabled diff --git a/iosHyperskillApp/iosHyperskillAppTests/Info.plist b/iosHyperskillApp/iosHyperskillAppTests/Info.plist index 92da8741a0..ab2b6691a0 100644 --- a/iosHyperskillApp/iosHyperskillAppTests/Info.plist +++ b/iosHyperskillApp/iosHyperskillAppTests/Info.plist @@ -15,6 +15,6 @@ CFBundleShortVersionString 1.49 CFBundleVersion - 302 + 303 diff --git a/iosHyperskillApp/iosHyperskillAppUITests/Info.plist b/iosHyperskillApp/iosHyperskillAppUITests/Info.plist index e5a2289b94..16552be86c 100644 --- a/iosHyperskillApp/iosHyperskillAppUITests/Info.plist +++ b/iosHyperskillApp/iosHyperskillAppUITests/Info.plist @@ -15,6 +15,6 @@ CFBundleShortVersionString 1.49 CFBundleVersion - 302 + 303 From f718d12e0b4b9e8f7172c0366a30177674be5610 Mon Sep 17 00:00:00 2001 From: Aleksandr Zhukov Date: Wed, 10 Jan 2024 12:51:31 +0400 Subject: [PATCH 11/19] Android update streak recovery bottom sheet (#845) --- .../StreakRecoveryViewActionDelegate.kt | 9 ++--- .../fragment/StreakRecoveryDialogFragment.kt | 19 +++++------ .../res/layout/fragment_streak_recovery.xml | 34 ++++++++++++++----- .../presentation/StreakRecoveryFeature.kt | 1 + 4 files changed, 38 insertions(+), 25 deletions(-) diff --git a/androidHyperskillApp/src/main/java/org/hyperskill/app/android/streak_recovery/view/delegate/StreakRecoveryViewActionDelegate.kt b/androidHyperskillApp/src/main/java/org/hyperskill/app/android/streak_recovery/view/delegate/StreakRecoveryViewActionDelegate.kt index 236fee6ad5..4f4ab1b5cb 100644 --- a/androidHyperskillApp/src/main/java/org/hyperskill/app/android/streak_recovery/view/delegate/StreakRecoveryViewActionDelegate.kt +++ b/androidHyperskillApp/src/main/java/org/hyperskill/app/android/streak_recovery/view/delegate/StreakRecoveryViewActionDelegate.kt @@ -18,13 +18,8 @@ object StreakRecoveryViewActionDelegate { when (viewAction) { is StreakRecoveryFeature.Action.ViewAction.ShowRecoveryStreakModal -> { StreakRecoveryDialogFragment - .newInstance( - StreakRecoveryDialogFragment.Params( - recoveryPriceAmountLabel = viewAction.recoveryPriceAmountLabel, - recoveryPriceGemsLabel = viewAction.recoveryPriceGemsLabel, - modalText = viewAction.modalText - ) - ).showIfNotExists(fragmentManager, StreakRecoveryDialogFragment.TAG) + .newInstance(viewAction) + .showIfNotExists(fragmentManager, StreakRecoveryDialogFragment.TAG) } StreakRecoveryFeature.Action.ViewAction.HideStreakRecoveryModal -> { fragmentManager diff --git a/androidHyperskillApp/src/main/java/org/hyperskill/app/android/streak_recovery/view/fragment/StreakRecoveryDialogFragment.kt b/androidHyperskillApp/src/main/java/org/hyperskill/app/android/streak_recovery/view/fragment/StreakRecoveryDialogFragment.kt index 5303a6b05e..ed173689ec 100644 --- a/androidHyperskillApp/src/main/java/org/hyperskill/app/android/streak_recovery/view/fragment/StreakRecoveryDialogFragment.kt +++ b/androidHyperskillApp/src/main/java/org/hyperskill/app/android/streak_recovery/view/fragment/StreakRecoveryDialogFragment.kt @@ -6,12 +6,12 @@ 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 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.FragmentStreakRecoveryBinding @@ -19,17 +19,18 @@ import org.hyperskill.app.android.view.base.ui.extension.wrapWithTheme import org.hyperskill.app.main.presentation.AppFeature import org.hyperskill.app.main.presentation.MainViewModel import org.hyperskill.app.streak_recovery.presentation.StreakRecoveryFeature +import org.hyperskill.app.streak_recovery.presentation.StreakRecoveryFeature.Action.ViewAction.ShowRecoveryStreakModal class StreakRecoveryDialogFragment : BottomSheetDialogFragment() { companion object { const val TAG: String = "StreakRecoveryDialogFragment" - fun newInstance(params: Params): StreakRecoveryDialogFragment = + fun newInstance(params: ShowRecoveryStreakModal): StreakRecoveryDialogFragment = StreakRecoveryDialogFragment().apply { this.params = params } } - private var params: Params by argument(Params.serializer()) + private var params: ShowRecoveryStreakModal by argument(ShowRecoveryStreakModal.serializer()) private val viewModel: MainViewModel by viewModels(ownerProducer = ::requireActivity) @@ -72,6 +73,11 @@ class StreakRecoveryDialogFragment : BottomSheetDialogFragment() { streakRecoverySubtitle.text = params.modalText streakRecoveryGemsTextView.text = params.recoveryPriceGemsLabel streakRecoveryGemsAmountTextView.text = params.recoveryPriceAmountLabel + streakRecoveryFirstTimeBadge.isVisible = params.isFirstTimeOffer + streakRecoveryNextRecoveryPrice.isVisible = params.nextRecoveryPriceText != null + if (params.nextRecoveryPriceText != null) { + streakRecoveryNextRecoveryPrice.text = params.nextRecoveryPriceText + } streakRecoveryBuyButton.setOnClickListener { viewModel.onNewMessage( AppFeature.Message.StreakRecoveryMessage( @@ -97,11 +103,4 @@ class StreakRecoveryDialogFragment : BottomSheetDialogFragment() { ) ) } - - @Serializable - data class Params( - val recoveryPriceAmountLabel: String, - val recoveryPriceGemsLabel: String, - val modalText: String - ) } \ No newline at end of file diff --git a/androidHyperskillApp/src/main/res/layout/fragment_streak_recovery.xml b/androidHyperskillApp/src/main/res/layout/fragment_streak_recovery.xml index abe86c6675..45fae25fd2 100644 --- a/androidHyperskillApp/src/main/res/layout/fragment_streak_recovery.xml +++ b/androidHyperskillApp/src/main/res/layout/fragment_streak_recovery.xml @@ -6,6 +6,7 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:paddingVertical="20dp" + android:paddingHorizontal="20dp" > @@ -66,7 +64,6 @@ android:gravity="center_vertical" android:drawablePadding="6dp" android:layout_marginTop="20dp" - android:layout_marginStart="20dp" app:layout_constraintTop_toBottomOf="@+id/streakRecoverySubtitle" app:layout_constraintStart_toStartOf="parent" app:drawableStartCompat="@drawable/ic_gems_count" @@ -86,6 +83,30 @@ app:layout_constraintStart_toEndOf="@+id/streakRecoveryGemsAmountTextView" /> + + + + @@ -116,7 +135,6 @@ app:strokeColor="@color/color_primary_alpha_38" android:text="@string/streak_recovery_modal_no_thanks" android:layout_marginTop="8dp" - android:layout_marginHorizontal="20dp" app:layout_constraintTop_toBottomOf="@+id/streakRecoveryBuyButton" /> diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/streak_recovery/presentation/StreakRecoveryFeature.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/streak_recovery/presentation/StreakRecoveryFeature.kt index 06e0948280..3a44af287e 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/streak_recovery/presentation/StreakRecoveryFeature.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/streak_recovery/presentation/StreakRecoveryFeature.kt @@ -44,6 +44,7 @@ object StreakRecoveryFeature { sealed interface Action { sealed interface ViewAction : Action { + @Serializable data class ShowRecoveryStreakModal( val recoveryPriceAmountLabel: String, // passed separately from price because price amount is highlighted with bold From f141262495d0618a0204b51dbf0fb98b6a1d5304 Mon Sep 17 00:00:00 2001 From: github-actions Date: Wed, 10 Jan 2024 08:52:22 +0000 Subject: [PATCH 12/19] 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 7af4aa2246..3224b76bcf 100644 --- a/gradle/app.versions.toml +++ b/gradle/app.versions.toml @@ -3,4 +3,4 @@ minSdk = '24' targetSdk = '33' compileSdk = '33' versionName = '1.49' -versionCode = '301' \ No newline at end of file +versionCode = '302' \ No newline at end of file diff --git a/iosHyperskillApp/NotificationServiceExtension/Info.plist b/iosHyperskillApp/NotificationServiceExtension/Info.plist index 9eb2dac92a..202c10b7cf 100644 --- a/iosHyperskillApp/NotificationServiceExtension/Info.plist +++ b/iosHyperskillApp/NotificationServiceExtension/Info.plist @@ -9,7 +9,7 @@ CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleVersion - 303 + 304 CFBundleShortVersionString 1.49 CFBundlePackageType diff --git a/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj b/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj index 90f3f6de21..3c97387c11 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 = 303; + CURRENT_PROJECT_VERSION = 304; 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 = 303; + CURRENT_PROJECT_VERSION = 304; 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 = 303; + CURRENT_PROJECT_VERSION = 304; 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 = 303; + CURRENT_PROJECT_VERSION = 304; 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 = 303; + CURRENT_PROJECT_VERSION = 304; 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 = 303; + CURRENT_PROJECT_VERSION = 304; 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 = 303; + CURRENT_PROJECT_VERSION = 304; 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 = 303; + CURRENT_PROJECT_VERSION = 304; 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 46bdd9ceeb..e0fc51d265 100644 --- a/iosHyperskillApp/iosHyperskillApp/Info.plist +++ b/iosHyperskillApp/iosHyperskillApp/Info.plist @@ -36,7 +36,7 @@ CFBundleVersion - 303 + 304 FirebaseAppDelegateProxyEnabled FirebaseMessagingAutoInitEnabled diff --git a/iosHyperskillApp/iosHyperskillAppTests/Info.plist b/iosHyperskillApp/iosHyperskillAppTests/Info.plist index ab2b6691a0..4cbdaaef80 100644 --- a/iosHyperskillApp/iosHyperskillAppTests/Info.plist +++ b/iosHyperskillApp/iosHyperskillAppTests/Info.plist @@ -15,6 +15,6 @@ CFBundleShortVersionString 1.49 CFBundleVersion - 303 + 304 diff --git a/iosHyperskillApp/iosHyperskillAppUITests/Info.plist b/iosHyperskillApp/iosHyperskillAppUITests/Info.plist index 16552be86c..b502780d01 100644 --- a/iosHyperskillApp/iosHyperskillAppUITests/Info.plist +++ b/iosHyperskillApp/iosHyperskillAppUITests/Info.plist @@ -15,6 +15,6 @@ CFBundleShortVersionString 1.49 CFBundleVersion - 303 + 304 From e61954b9bf562a4c46f9959f57be4b3b1cc0e31c Mon Sep 17 00:00:00 2001 From: Ivan Magda Date: Thu, 11 Jan 2024 14:28:12 +0700 Subject: [PATCH 13/19] GitHub Actions: AI Code Review (#847) ^ALTAPPS-1098 --- .github/workflows/gpt_review.yml | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 .github/workflows/gpt_review.yml diff --git a/.github/workflows/gpt_review.yml b/.github/workflows/gpt_review.yml new file mode 100644 index 0000000000..a02695d706 --- /dev/null +++ b/.github/workflows/gpt_review.yml @@ -0,0 +1,32 @@ +name: GPT Code Review + +on: + pull_request: + types: [opened, reopened, synchronize] + +concurrency: + group: ${{ github.workflow }}-${{ github.ref_name }} + cancel-in-progress: true + +permissions: + contents: read + pull-requests: write + +jobs: + code-review: + if: ${{ contains(github.event.*.labels.*.name, 'gpt-review') }} + runs-on: ubuntu-22.04 + timeout-minutes: 5 + steps: + - uses: anc95/ChatGPT-CodeReview@main + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} + LANGUAGE: English + OPENAI_API_ENDPOINT: https://api.openai.com/v1 + MODEL: gpt-3.5-turbo-16k # https://platform.openai.com/docs/models + PROMPT: 'Provide suggestions for improving the changes in this PR. If the PR has no clear issues, mention that no suggestions are needed. You must look at the code to see if there are any bugs, improvements, or optimizations. You must praise good changes. You must ask questions if something is not clear. This PR is for the Kotlin Multiplatform Mobile project, where the main languages are Kotlin and Swift.' + top_p: 1 # https://platform.openai.com/docs/api-reference/chat/create#chat/create-top_p + temperature: 1 # https://platform.openai.com/docs/api-reference/chat/create#chat/create-temperature + max_tokens: 10000 + MAX_PATCH_LENGTH: 10000 # if the patch/diff length is large than MAX_PATCH_LENGTH, will be ignored and won't review. \ No newline at end of file From 5092b5ff2ac095dfbb8009297524059f0f9d9676 Mon Sep 17 00:00:00 2001 From: github-actions Date: Thu, 11 Jan 2024 07:29:01 +0000 Subject: [PATCH 14/19] 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 3224b76bcf..1efe1cebb3 100644 --- a/gradle/app.versions.toml +++ b/gradle/app.versions.toml @@ -3,4 +3,4 @@ minSdk = '24' targetSdk = '33' compileSdk = '33' versionName = '1.49' -versionCode = '302' \ No newline at end of file +versionCode = '303' \ No newline at end of file diff --git a/iosHyperskillApp/NotificationServiceExtension/Info.plist b/iosHyperskillApp/NotificationServiceExtension/Info.plist index 202c10b7cf..898d4658c8 100644 --- a/iosHyperskillApp/NotificationServiceExtension/Info.plist +++ b/iosHyperskillApp/NotificationServiceExtension/Info.plist @@ -9,7 +9,7 @@ CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleVersion - 304 + 305 CFBundleShortVersionString 1.49 CFBundlePackageType diff --git a/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj b/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj index 3c97387c11..7d0121551b 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 = 304; + CURRENT_PROJECT_VERSION = 305; 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 = 304; + CURRENT_PROJECT_VERSION = 305; 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 = 304; + CURRENT_PROJECT_VERSION = 305; 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 = 304; + CURRENT_PROJECT_VERSION = 305; 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 = 304; + CURRENT_PROJECT_VERSION = 305; 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 = 304; + CURRENT_PROJECT_VERSION = 305; 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 = 304; + CURRENT_PROJECT_VERSION = 305; 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 = 304; + CURRENT_PROJECT_VERSION = 305; 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 e0fc51d265..fc83dda896 100644 --- a/iosHyperskillApp/iosHyperskillApp/Info.plist +++ b/iosHyperskillApp/iosHyperskillApp/Info.plist @@ -36,7 +36,7 @@ CFBundleVersion - 304 + 305 FirebaseAppDelegateProxyEnabled FirebaseMessagingAutoInitEnabled diff --git a/iosHyperskillApp/iosHyperskillAppTests/Info.plist b/iosHyperskillApp/iosHyperskillAppTests/Info.plist index 4cbdaaef80..2b3fa272e6 100644 --- a/iosHyperskillApp/iosHyperskillAppTests/Info.plist +++ b/iosHyperskillApp/iosHyperskillAppTests/Info.plist @@ -15,6 +15,6 @@ CFBundleShortVersionString 1.49 CFBundleVersion - 304 + 305 diff --git a/iosHyperskillApp/iosHyperskillAppUITests/Info.plist b/iosHyperskillApp/iosHyperskillAppUITests/Info.plist index b502780d01..4b7cc74d96 100644 --- a/iosHyperskillApp/iosHyperskillAppUITests/Info.plist +++ b/iosHyperskillApp/iosHyperskillAppUITests/Info.plist @@ -15,6 +15,6 @@ CFBundleShortVersionString 1.49 CFBundleVersion - 304 + 305 From 8488ba50505c6648068a4ca751f8c271f9d1a79c Mon Sep 17 00:00:00 2001 From: Aleksandr Zhukov Date: Thu, 11 Jan 2024 13:33:47 +0400 Subject: [PATCH 15/19] Rename android app (#848) --- androidHyperskillApp/src/main/AndroidManifest.xml | 2 +- .../auth/view/ui/fragment/AuthSocialFragment.kt | 14 ++++++++++---- .../src/main/res/layout/fragment_welcome.xml | 2 +- .../src/main/res/values/strings.xml | 3 --- .../Sources/Models/Constants/Strings.swift | 7 ++++--- shared/build.gradle.kts | 1 + .../app/core/domain/platform/Platform.kt | 5 +++++ .../app/core/domain/platform/Platform.kt | 4 ++++ .../ProfileSettingsActionDispatcher.kt | 2 +- .../src/commonMain/resources/MR/base/strings.xml | 11 +++++++---- .../app/core/domain/platform/Platform.kt | 4 ++++ 11 files changed, 38 insertions(+), 17 deletions(-) diff --git a/androidHyperskillApp/src/main/AndroidManifest.xml b/androidHyperskillApp/src/main/AndroidManifest.xml index 2edc661025..3231dc6630 100644 --- a/androidHyperskillApp/src/main/AndroidManifest.xml +++ b/androidHyperskillApp/src/main/AndroidManifest.xml @@ -12,7 +12,7 @@ android:maxSdkVersion="26"/> + android:text="@string/android_onboarding_title" /> - - My Hyperskill - Loading Please wait… Oops! It seems like the network error. diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Models/Constants/Strings.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Models/Constants/Strings.swift index f812304664..64ced883c4 100644 --- a/iosHyperskillApp/iosHyperskillApp/Sources/Models/Constants/Strings.swift +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Models/Constants/Strings.swift @@ -7,6 +7,7 @@ enum Strings { // MARK: - Common - enum Common { + static let appName = sharedStrings.ios_app_name.localized() static let connectionError = sharedStrings.connection_error.localized() static let done = sharedStrings.done.localized() static let yes = sharedStrings.yes.localized() @@ -51,8 +52,8 @@ enum Strings { // MARK: Social enum Social { - static let logInTitle = sharedStrings.auth_log_in_title.localized() - static let signUpTitle = sharedStrings.auth_sign_up_title.localized() + static let logInTitle = sharedStrings.auth_log_in_title.format(args_: [Common.appName]).localized() + static let signUpTitle = sharedStrings.auth_sign_up_title.format(args_: [Common.appName]).localized() static let jetBrainsAccount = sharedStrings.auth_jetbrains_account_text.localized() static let googleAccount = sharedStrings.auth_google_account_text.localized() static let gitHubAccount = sharedStrings.auth_github_account_text.localized() @@ -486,7 +487,7 @@ enum Strings { // MARK: - Welcome - enum Welcome { - static let title = sharedStrings.onboarding_title.localized() + static let title = sharedStrings.ios_onboarding_title.localized() static let text = sharedStrings.onboarding_text.localized() static let primaryButton = sharedStrings.onboarding_primary_button_text.localized() static let secondaryButton = sharedStrings.onboarding_secondary_button_text.localized() diff --git a/shared/build.gradle.kts b/shared/build.gradle.kts index b7ec66c98f..12c0ccf9f8 100644 --- a/shared/build.gradle.kts +++ b/shared/build.gradle.kts @@ -103,6 +103,7 @@ kotlin { } val iosMain by creating { + dependsOn(commonMain) dependencies { implementation(libs.ktor.ios) } 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 16a2ccdec5..51c02f56aa 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 @@ -1,5 +1,8 @@ package org.hyperskill.app.core.domain.platform +import dev.icerock.moko.resources.StringResource +import org.hyperskill.app.SharedResources + actual class Platform actual constructor() { actual val platformType: PlatformType = PlatformType.ANDROID actual val platformDescription: String = "Android ${android.os.Build.VERSION.SDK_INT}" @@ -7,4 +10,6 @@ actual class Platform actual constructor() { actual val analyticName: String = "android" actual val feedbackName: String = "Android" + + actual val appNameResource: StringResource = SharedResources.strings.android_app_name } \ 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 7809c38a3c..74cd19ac19 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 @@ -1,5 +1,7 @@ package org.hyperskill.app.core.domain.platform +import dev.icerock.moko.resources.StringResource + expect class Platform() { val platformType: PlatformType val platformDescription: String @@ -7,4 +9,6 @@ expect class Platform() { val analyticName: String val feedbackName: String + + val appNameResource: StringResource } \ 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 6db01b62d5..395a7afdd6 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 @@ -45,7 +45,7 @@ class ProfileSettingsActionDispatcher( val feedbackEmailData = FeedbackEmailDataBuilder.build( supportEmail = resourceProvider.getString(strings.settings_send_feedback_support_email), - applicationName = resourceProvider.getString(strings.settings_send_feedback_application_name), + applicationName = resourceProvider.getString(platform.appNameResource), platform = platform, userId = currentProfile?.id, applicationVersion = userAgentInfo.versionCode diff --git a/shared/src/commonMain/resources/MR/base/strings.xml b/shared/src/commonMain/resources/MR/base/strings.xml index 7fc4a79abc..ad4d74c066 100644 --- a/shared/src/commonMain/resources/MR/base/strings.xml +++ b/shared/src/commonMain/resources/MR/base/strings.xml @@ -1,6 +1,9 @@ + My Hyperskill + Hyperskill + Connection was lost Sorry, something went wrong: please try again later. Unable to open link: %s @@ -36,8 +39,8 @@ User name - Sign in to My Hyperskill - Sign up to My Hyperskill + Sign in to %s + Sign up to %s JetBrains Account Google GitHub @@ -243,7 +246,6 @@ Report a problem Send feedback academy@jetbrains.com - My Hyperskill Version Rate this application Sign out @@ -390,7 +392,8 @@ Me: If the IDE knows I\'m missing a semicolon, why won\'t it just add it itself? 🤔 - My Hyperskill + My Hyperskill + Hyperskill Code a little every day, wherever you are. Start for free I already have an account 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 5d9e088074..bc020ed73f 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 @@ -1,5 +1,7 @@ package org.hyperskill.app.core.domain.platform +import dev.icerock.moko.resources.StringResource +import org.hyperskill.app.SharedResources import platform.UIKit.UIDevice actual class Platform actual constructor() { @@ -10,4 +12,6 @@ actual class Platform actual constructor() { actual val analyticName: String = "ios" actual val feedbackName: String = "iOS" + + actual val appNameResource: StringResource = SharedResources.strings.ios_app_name } \ No newline at end of file From 5aae08ca62c3c923a9c0416b8dfdaa4ef0db7998 Mon Sep 17 00:00:00 2001 From: github-actions Date: Thu, 11 Jan 2024 09:34:39 +0000 Subject: [PATCH 16/19] 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 1efe1cebb3..3222c1eff3 100644 --- a/gradle/app.versions.toml +++ b/gradle/app.versions.toml @@ -3,4 +3,4 @@ minSdk = '24' targetSdk = '33' compileSdk = '33' versionName = '1.49' -versionCode = '303' \ No newline at end of file +versionCode = '304' \ No newline at end of file diff --git a/iosHyperskillApp/NotificationServiceExtension/Info.plist b/iosHyperskillApp/NotificationServiceExtension/Info.plist index 898d4658c8..928c39fa81 100644 --- a/iosHyperskillApp/NotificationServiceExtension/Info.plist +++ b/iosHyperskillApp/NotificationServiceExtension/Info.plist @@ -9,7 +9,7 @@ CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleVersion - 305 + 306 CFBundleShortVersionString 1.49 CFBundlePackageType diff --git a/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj b/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj index 7d0121551b..80961eddf4 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 = 305; + CURRENT_PROJECT_VERSION = 306; 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 = 305; + CURRENT_PROJECT_VERSION = 306; 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 = 305; + CURRENT_PROJECT_VERSION = 306; 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 = 305; + CURRENT_PROJECT_VERSION = 306; 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 = 305; + CURRENT_PROJECT_VERSION = 306; 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 = 305; + CURRENT_PROJECT_VERSION = 306; 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 = 305; + CURRENT_PROJECT_VERSION = 306; 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 = 305; + CURRENT_PROJECT_VERSION = 306; 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 fc83dda896..ca65c4eb21 100644 --- a/iosHyperskillApp/iosHyperskillApp/Info.plist +++ b/iosHyperskillApp/iosHyperskillApp/Info.plist @@ -36,7 +36,7 @@ CFBundleVersion - 305 + 306 FirebaseAppDelegateProxyEnabled FirebaseMessagingAutoInitEnabled diff --git a/iosHyperskillApp/iosHyperskillAppTests/Info.plist b/iosHyperskillApp/iosHyperskillAppTests/Info.plist index 2b3fa272e6..62b66dcb7f 100644 --- a/iosHyperskillApp/iosHyperskillAppTests/Info.plist +++ b/iosHyperskillApp/iosHyperskillAppTests/Info.plist @@ -15,6 +15,6 @@ CFBundleShortVersionString 1.49 CFBundleVersion - 305 + 306 diff --git a/iosHyperskillApp/iosHyperskillAppUITests/Info.plist b/iosHyperskillApp/iosHyperskillAppUITests/Info.plist index 4b7cc74d96..4d6900935f 100644 --- a/iosHyperskillApp/iosHyperskillAppUITests/Info.plist +++ b/iosHyperskillApp/iosHyperskillAppUITests/Info.plist @@ -15,6 +15,6 @@ CFBundleShortVersionString 1.49 CFBundleVersion - 305 + 306 From f1118c0a1d4919365165b9621f3d07ea2fe5ec62 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 12 Jan 2024 15:34:27 +0700 Subject: [PATCH 17/19] GitHub Actions: Bump actions/cache from 3.3.2 to 3.3.3 (#850) Bumps [actions/cache](https://github.com/actions/cache) from 3.3.2 to 3.3.3. - [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.2...v3.3.3) --- updated-dependencies: - dependency-name: actions/cache 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/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a01d405382..edeec5903a 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.2 + uses: actions/cache@v3.3.3 id: cache-pods with: path: './iosHyperskillApp/Pods' From d84731c3f77dff0c3164d8bb2d8ca35a4009b2db Mon Sep 17 00:00:00 2001 From: Aleksandr Zhukov Date: Fri, 12 Jan 2024 12:47:06 +0400 Subject: [PATCH 18/19] Android: Update splash screen year from 2023 to 2024 (#849) ^ALTAPPS-1113 --- .../main/res/drawable-hdpi/ic_subtitle.webp | Bin 1100 -> 2014 bytes .../main/res/drawable-mdpi/ic_subtitle.webp | Bin 744 -> 1544 bytes .../main/res/drawable-xhdpi/ic_subtitle.webp | Bin 1520 -> 2492 bytes .../main/res/drawable-xxhdpi/ic_subtitle.webp | Bin 2366 -> 3886 bytes .../res/drawable-xxxhdpi/ic_subtitle.webp | Bin 2904 -> 5006 bytes .../main/res/drawable/ic_splash_subtitle.xml | 12 ++++++------ 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/androidHyperskillApp/src/main/res/drawable-hdpi/ic_subtitle.webp b/androidHyperskillApp/src/main/res/drawable-hdpi/ic_subtitle.webp index dd8a617d347722e482d7fa5ca650452f4a1404e6..687a8a32eaf4e8788854c927015977c965f5eb85 100644 GIT binary patch literal 2014 zcmaKtd0Z1o7RQSugfk!tawC`MfM7^=AzYGUxJDKckRyo8O)^~JhGm3+A}qr_V-&dp zXb^-E5fL1eLxw{}2nsAWh;poAfT$pbkm=|j&L6W|^{Ln2dhb{Ls=B+nTpVm|$Hf5f zupv6TJL5bh0RU*I?E#<%fCGu-+%E!kfk?m5BA`%sL~OK+9Z}QMi>xWO07Rhrx%dUd z(7w=LT1d4u{zd!T@Glkr-zgCo6cYeRHlU^(4JC)>BoDEC$QM2;WdAR0DddD$S}de- z7IL(k3kl*th>wQ+iT(e?0kmkLJ_piRhKI!oa|w%Zb<#l*XI!DHIMmcY3~&K6$ob#^H@Z3c30E$)@d$3Cre^M zQ0yX3ZC{)Nf+?TyG-}p=F3S^DushTFZ(gG|ZqF7OSfEjx-w)2GoJ~r7YkCJ`7$rJ& zA-PLKI!P{hw4-GA(mIuMe*?GYXIa&9z4h_EZ1`!Cntw<}IE}T*#8z^hp4V1vPUkXf z$80#_apojRt=wCnXBpA6hki4kj6XXOPg-6DE2-qNT1l`{^2mn^)3tahOf9+hK+ji^ zO8=O0pAcULL+u+AEnlT~liW7*d~#wGDwM8X`nW!#jyC$QBt@s=S&98fngP>^LZQeY zQO$C9tE;IYXE&9hl^oahH(5c$Gt=Ky4KT72Sk&H1`jtR>qJ2CAzO<-nufwd#K6HB@ z-pPoVfsEc(A@*6yrd+1PmErtT_xgjG}V@nU8IYe0u#u;A=t8`arZrJKik8m@(z zJs9|7c$prjxIVO56!EIK-l7QQEjaHG19y-!+KnY*V91#Z!J{vJ=FkHX`@Qbl@MeS7 zMx+Y=en=k{wWuboEXxpI*t(Z)0sCeE89#1}a?C|$EnDgyG%+ps-YC-cQF>Z^kYg*q zGiP^L6lF=Hs^<=xa7t5j!g?Jf8M`6Rx(R$DOrsaL!iZ{?q*!sKYhR|@ zv9GO3?dnIXj(nsFzxJMxI%FLQOfQCmY@Yc~;Z9UTt~9yGln}PK zpp$?w96&Oo+Me{icrM%_4cRSPG}*#Q-gaps``_p9wzUscMzPtr?3>Dl6*hS{8Ukg> z7~Y#a-u8gibw@b4iPQPFo;Vv=GP6u7fy9E@_M%*3+iK=8@3bXVWRIs0QU*dVgg%K| zBb;3BLzPhInK)`hW6`ABa67(ire-P9dfaAu;YClk(Fgp8L*F*GR_?5Vgq|x8`mLtf z@a`WZX8-Y9Z(Jy)Q^QN?q5T*UUWbo?AAZLOhUo^}8A!U7KP1B6KbdI{>XEN)CwLwl z%fx3@$ZG|2k?@2#veTmK5&BhxIg(BejN@_XW|lT(iW?Ck8;GLg|7 z4|MejEAC}r`$!Y!puwyD1NN7?5Xa6FHkmdWzNYfljoan5>Id5n8@6KEBc3&kiK57% ztFtiv{!imcBmM21a}WEpCua?cNW3ny5QpLuzRLbmL~BiaoiYC8T4~DZb3Bjb2Jhy? zKE=Fa?m?dzbCH4z<=6lWBlNr9Z#CiSFsjA*jyjX}Cfrlxg>@12p`8#@uQK%5HG|X8 zR%CJHIf?nlES0tew5`>Q{q)kEP-IVU6DW0fqN|2?nxE!#_6*2f_E$^)8*NU ZXNS`Tf}tf9MS&p7<|lz*#+eQUz#pv1Cba+n literal 1100 zcmV-S1he~6Nk&FQ1ONb6MM6+kP&iCC1ONapm%#`SzaSP&<}clu!u-!aLAEX430{Ip znrb@+H2}b|c>n;=wr#6!D#?fAon38`=09JFMKsf{;+7tmV$$-_&7pP8BhT_d;Aiheu z;a`yM@9pp-NgkLaWk`~=LYA~ak|YCyWPnLhgz^rB7%#mL<3$$ZEVtate!q_RR7zg0 z6=QVrYfh5f?Vj`fYLA2%XDP%uJ;MRhK?Q;&U^;lHk}CLvc8JoHbJB4FUa_rVCt9Jp zmH}6Jz*m~ZT9#=q2Lq-A6v+zho(ZB-iPAA$R=@|gZes#C9r zX5$9zPA9SoQBSY}s~))9o8$p6slc@}b!I@Sa1YHH_@|N8^Z+IKkt?t}oyZETdV(vk zt3SmU3)JZy$<|*xDv+cASVvtCnB&3Fm|Kc8%hIU;KRS^Wxa%1^6gSXZA-^T+$KDLp zDMQkUd`BT4Ah+87JZ^{GJyr)26w{*)m|if;P?wXr3ef=HvrYh{3imi@jj1PD$N;fZ z3RF8yhGJ?P;0_LOjUOncwli}L74R?E#?j8D2z7;`UhLdqmoj7xA8bUD?eM2n%me#b z5)@Ovfu$y|jB*8zOv23(~XxHeqc-2-;FgJ#H$Vp0Tt&apr- z4X%(68>GVmOzL}JI@|#FCTRtzZiFO*f7yUlFY?x~zsU|sL;XpTrpjO(n;5kbSD@MnGQ%cmrW++` zW7tbp=w`jjwnLruHz@PW_N8?9oY*%lYnFlHc}9Sxu5IInNEvIp3YuoV}zu=ZmB{=lOx( zHSbN&oV+)^=ui4xyGU2yI`)bF3pS`g(YL|TtoHHR8g?SZ0%TKvg8GrN9qOzzNzR-; S?5Fv_Zw>oG*ba5p2NeJThz*DU diff --git a/androidHyperskillApp/src/main/res/drawable-mdpi/ic_subtitle.webp b/androidHyperskillApp/src/main/res/drawable-mdpi/ic_subtitle.webp index e23d0900522eca7ec68771d70b5d1365911ad9c3..2bec9dc68e5bcb93f9a5ae9d0c81c32786d77fed 100644 GIT binary patch literal 1544 zcmaKsdog zt!ip#vmA;pN*7TQX+@58Sx(4hBsOv>ihU=4=#O@v_q@;V^FGh_Jn!@VZr&d5?tll_ z5`ObQ6xOH%0r3>u$|9ePNx zOkea!HT&1Hw$1vBivJI)9pD}cMUppYn#7{bQBOLEb$H8cpyE6$JB$f7R- z*v$Z7Pp-r`1pqfQ0M>P{#7v|BtB(OxK3j>c+!Fyimi?(54tb@pFo3Bd0F6KZ?G^y_ zpiepE{-ZY%N*SQOqS4?%ByfQQ9>9lC*o0^cdZ^z3#fib(aTSZInqWM{r6N^p>%V8I zVnZE<;LU{;DQhgny;i7u+N-5ZNl*Eyg``jV8;hp0U{A8>blKZH80X4JIo93%l9EW6 zU+YvSHN<%lv$9xDES1`{8j4bo>MN4y0i7##vPmXBZf3(ZD)Y;w;WE&Uo1fx~OXXSF;^(LDOKS;?8@lUt&Zuk-+ z`OFrV`FQR8EM4-NTE}wyhBn;OK+h|=cL%38Rn1Vb*d)Emlres^&U>6JINF!2OpL=b z{6}Jp4^AeR8uyTLlpJ%JJ+)esJk4Kla@%pUjuzJ4p>gh{z9izfmYxLD8ZtY#NGme+ z+jy{S42ozif_0u%{=;E4=WkV5N(=PUu(#+`3lGifgQ*K=DUSy1Y|nIERVhC&&3SCb zE+!fN+-)T@Ao{k&hwUU za0tWEOj1plo{x!2?)$Y#(|}}Tm)!6yp~JDP(=c8@53aq$^Y@8k$J__w4;-4?aFP@| zfMetanJ@0{P}<-pQM@tl>m5Q9@`r-jSa+{Qgl*-P*d#QLMp^o%dljL7GbsRl;zVUQ zi?J*LJ)l_=k2_zupz#C<7|vcgSbK^4wiT+$<5~S52P&O=3WPa5N-TiI#Z0q@MN3OP e`(H0DvCFk9l9v|5n>lw-gjt%r)I5Z$0{jcV>nvjc literal 744 zcmV;PuF=(2O%XXttaWcrZ+Bt4JQ2mQ7H!*q(YS{<%V!^+G<^@t<~7s zwXt7~`aQkf(b)>jpsW=~0wI_D(E={-ncr}rR(E1(O*IEsx;2_6wFNV;-Tq)8H( z%p|E1HQrcW)$u*&+;6y4sge?y9QG7*qe>f|Y;sCRTP8J%rOh?4#->LNjb=)C=a`0P zT>X?c+}?|0ml6bNW0f42qy%P=BqdrfaFYbHhLvK@rl*)Q>%Is!aA;BQ#a{cpQ>Dlt zb#tdio?z`E8k{7lg0E??IafU;>@2#^oLR$^&W-e5nEE`JXd&b3z6MUt94CX=g$>}l z1DZYM$|gxh>)ODaq8n%GtFng2C#f;-oCamk_)3t+!N58xOdEW4yR$!IWmg{{NCARm zPEkLVBq`yqssQC#g+>d5W|A&op)pI+#dMTS(v_mh9<1^GxCsjO>~G?gexrC2B@DVCEom3VZonI0I-^tvB%?{>Yy=}4dN^8!H8 a2Nj)WPmxVIX*^~7fR3N=u}7!brvL!Kr(lQx diff --git a/androidHyperskillApp/src/main/res/drawable-xhdpi/ic_subtitle.webp b/androidHyperskillApp/src/main/res/drawable-xhdpi/ic_subtitle.webp index abc3b03c6e2ec75d5e586cfc0321a763205cfee6..b93f6fe381d47ba28a0f9ad438e2b2d85b691f30 100644 GIT binary patch literal 2492 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 WUg>xJuS5CckXz}mLfC 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 literal 2366 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 bsq38H^JgY9cXvl$hcCr?M^iD^HvsTIiN@(J literal 2904 zcmV-e3#ar_Nk&Fc3jhFDMM6+kP&iCO3jhEwJH!_d#~>C=<}clu!u-!aLAEX430{Ip znrb@+H2}b|eEW%2XEJ$|4wmF7X>Zl%o zl385?DP1e59Q9hk%%*>B^LG?C zqQRvhZbX4=L)?g*YeU?KoNGhe$h85vP5R$|P8K@aSGB)H@vs zr;-M@6;*6VBcb!zLRd-{RYd!f6SjMa~Jxoa@g6 zIWNdDz-}|H}M7`TGPoD4YIL-@l z3^3L$1M}7e_Fimm8y(G9Z2ao_TT&+qxspgB}V#qA{Hi* zOpqhu2{#7XCb24kY>Vc>B{Z z!ZIerK$QjGXsLbksx-MOmOqtvdGcw+t-hxx03+Abzmwm^$ZsaY(gQbYa?$}N6Decv z5FWHj#`L8Dm$pxhlzp&z40Ta@4@Qou+YoJOi15SQ0hVOoxbr4zlB?o*S1Isz`1Asd zo1z;9m`karFOM4^F_i`1xEEi3#TkKT&*)Sr$qEqF>6IAxxQ)6r2OEMo;CN|zK#~rY z(5;|~W4o((t5*&q^0giI1bd+ukQdj;7${tsk@U2v)6_s2QKDgVD#G~jIzUKrwamdR z8vt-Qa2#;p4P3MNV!2D;6U_HvC9&fp_5>K)DKM!6i*Jg#3q=FRMAO8274F56K>}O(LwL>=}s84=0(7g+wS?l`t6* zuF}SIgJ`-OntZFN@}(D@e?N{$<=z?mrdggyUEDZr%z5POQo)D0w-kDRd(rmiu0nI`)u#(3;t3in_L|{~(^CS06vz2SkmFP- zAqj7a>HEzB%FP0$UoBlM(_7690nK~FQIc;5CoK#gey5lUUEt>l8VSL(Z;TkT3=#!O zM?{vJwT~Y|&WJA29TOjnW1#@1($ED$WV?;n}QFm>%c$att zNvPu$luQJccgce?`P<<^ogqpTXU;0H4O;U8C&q#z9;hGlR<9E@|5l6$cU2Tk>_pcW zc1cflcxj`eN^?5=b_UNXu&X_R^_c;pIvO~>?M@gtk&F%@3;L+DQ-ROX>=M_gYK=BZ zswn-@vOB>D*c*4$-V~@EK)RVVX1z&-Z?r^Y56NM>Pp1HocZsW%+Up{i{Y>lSdOxWE zZ9>HM#sDpcW$Q+a)x$fFGQc3c7R%ri80ow|E9YW1gRRu$XNt`8Cw4+&tj^dm{(&u*BAL!DDbk)hD+)*N|9wb$ApAEByed*z*st&J)x-}QWCU!iEb1ciB+hxyCL1OgGHroA-jcJ{=mwA){{%b!^{y?im3=t z;`=RuOnO#HXyNbVK6}Bj;p#%Wp5XQufhtx$m$IPV7S5?PrQo9K!Xm8y4w3el8=st0>Bha3^K&n9}M%AT3 z6UYc80cj!YMDDW(EIiUhU`bT^Vn|h1?Xt5c7$zcJFB0a5!~v3y~JoqtR_%)@_lJr|gb1?;vSp6Gys8@-A@3G(lPqxsUV|45YHv<8FV|{_AU}a1k zc5CL}7=k{I!6DaVg4VJGjpll2=T^`}sXbCUofj-`UW!{sw}^+j~@l`rdkn`EK9%G&zpsxU4?}nGiU?8DGq*266M!>H7*}wjQNWV>>>G_cKU)FubGwwI z704>Sua6&)J9Ui-F}`YkyoL(8Mt9KDin^i)qX9h{FYX%Pd~EEU46Fm=MA4eeHTg5_ zd7czC(a;4frM!>8pMXzn!8f5W<}CuAQBo)~7e;!LmB`P$F}+I;8NFB;rNTl& z(p2;dQF=s_a3Vh+< From eebf20cf9c6b93523eeea66b20855d68e15f5a45 Mon Sep 17 00:00:00 2001 From: github-actions Date: Fri, 12 Jan 2024 08:47:49 +0000 Subject: [PATCH 19/19] 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 3222c1eff3..4f93fa703e 100644 --- a/gradle/app.versions.toml +++ b/gradle/app.versions.toml @@ -3,4 +3,4 @@ minSdk = '24' targetSdk = '33' compileSdk = '33' versionName = '1.49' -versionCode = '304' \ No newline at end of file +versionCode = '305' \ No newline at end of file