diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 12391c4501..a680c73f85 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -119,6 +119,9 @@ jobs: status: ${{ steps.status.outputs.status }} steps: + - name: Clone Repo # Needed for reading commit message for Firebase App Distribution + uses: actions/checkout@v3 + - name: Download Android Artifacts uses: actions/download-artifact@v3 with: diff --git a/PrivacyPolicy.md b/PrivacyPolicy.md index 9344c95793..4107321378 100644 --- a/PrivacyPolicy.md +++ b/PrivacyPolicy.md @@ -1,63 +1,67 @@ ## Privacy Policy -built the Currency Converter Calculator app as a Free app. This SERVICE is provided by at no cost and is intended for use as is. +Oztechan built the Currency Converter&Calculator app as an Ad Supported app. This SERVICE is provided by Oztechan at no cost and is intended for use as is. -This page is used to inform visitors regarding my policies with the collection, use, and disclosure of Personal Information if anyone decided to use my Service. +This page is used to inform visitors regarding our policies with the collection, use, and disclosure of Personal Information if anyone decided to use our Service. -If you choose to use my Service, then you agree to the collection and use of information in relation to this policy. The Personal Information that I collect is used for providing and improving the Service. I will not use or share your information with anyone except as described in this Privacy Policy. +If you choose to use our Service, then you agree to the collection and use of information in relation to this policy. The Personal Information that we collect is used for providing and improving the Service. We will not use or share your information with anyone except as described in this Privacy Policy. -The terms used in this Privacy Policy have the same meanings as in our Terms and Conditions, which is accessible at Currency Converter Calculator unless otherwise defined in this Privacy Policy. +The terms used in this Privacy Policy have the same meanings as in our Terms and Conditions, which are accessible at Currency Converter&Calculator unless otherwise defined in this Privacy Policy. ## Information Collection and Use -For a better experience, while using our Service, I may require you to provide us with certain personally identifiable information. The information that I request will be retained on your device and is not collected by me in any way. +For a better experience, while using our Service, we may require you to provide us with certain personally identifiable information. The information that we request will be retained by us and used as described in this privacy policy. -The app does use third party services that may collect information used to identify you. +The app does use third-party services that may collect information used to identify you. -Link to privacy policy of third party service providers used by the app +Link to the privacy policy of third-party service providers used by the app + +* [Google Play Services](https://www.google.com/policies/privacy/) +* [AdMob](https://support.google.com/admob/answer/6128543?hl=en) +* [Google Analytics for Firebase](https://firebase.google.com/policies/analytics) +* [Firebase Crashlytics](https://firebase.google.com/support/privacy/) -* [Google Play Services](https://www.google.com/policies/privacy/) -* [AdMob](https://support.google.com/admob/answer/6128543?hl=en) -* [Firebase Analytics](https://firebase.google.com/policies/analytics) ## Log Data -I want to inform you that whenever you use my Service, in a case of an error in the app I collect data and information (through third party products) on your phone called Log Data. This Log Data may include information such as your device Internet Protocol (“IP”) address, device name, operating system version, the configuration of the app when utilizing my Service, the time and date of your use of the Service, and other statistics. +We want to inform you that whenever you use our Service, in a case of an error in the app we collect data and information (through third-party products) on your phone called Log Data. This Log Data may include information such as your device Internet Protocol (“IP”) address, device name, operating system version, the configuration of the app when utilizing our Service, the time and date of your use of the Service, and other statistics. ## Cookies Cookies are files with a small amount of data that are commonly used as anonymous unique identifiers. These are sent to your browser from the websites that you visit and are stored on your device's internal memory. -This Service does not use these “cookies” explicitly. However, the app may use third party code and libraries that use “cookies” to collect information and improve their services. You have the option to either accept or refuse these cookies and know when a cookie is being sent to your device. If you choose to refuse our cookies, you may not be able to use some portions of this Service. +This Service does not use these “cookies” explicitly. However, the app may use third-party code and libraries that use “cookies” to collect information and improve their services. You have the option to either accept or refuse these cookies and know when a cookie is being sent to your device. If you choose to refuse our cookies, you may not be able to use some portions of this Service. ## Service Providers -I may employ third-party companies and individuals due to the following reasons: +We may employ third-party companies and individuals due to the following reasons: -* To facilitate our Service; -* To provide the Service on our behalf; -* To perform Service-related services; or -* To assist us in analyzing how our Service is used. +* To facilitate our Service; +* To provide the Service on our behalf; +* To perform Service-related services; or +* To assist us in analyzing how our Service is used. -I want to inform users of this Service that these third parties have access to your Personal Information. The reason is to perform the tasks assigned to them on our behalf. However, they are obligated not to disclose or use the information for any other purpose. +We want to inform users of this Service that these third parties have access to their Personal Information. The reason is to perform the tasks assigned to them on our behalf. However, they are obligated not to disclose or use the information for any other purpose. ## Security -I value your trust in providing us your Personal Information, thus we are striving to use commercially acceptable means of protecting it. But remember that no method of transmission over the internet, or method of electronic storage is 100% secure and reliable, and I cannot guarantee its absolute security. +We value your trust in providing us your Personal Information, thus we are striving to use commercially acceptable means of protecting it. But remember that no method of transmission over the internet, or method of electronic storage is 100% secure and reliable, and we cannot guarantee its absolute security. ## Links to Other Sites -This Service may contain links to other sites. If you click on a third-party link, you will be directed to that site. Note that these external sites are not operated by me. Therefore, I strongly advise you to review the Privacy Policy of these websites. I have no control over and assume no responsibility for the content, privacy policies, or practices of any third-party sites or services. +This Service may contain links to other sites. If you click on a third-party link, you will be directed to that site. Note that these external sites are not operated by us. Therefore, we strongly advise you to review the Privacy Policy of these websites. We have no control over and assume no responsibility for the content, privacy policies, or practices of any third-party sites or services. ## Children’s Privacy -These Services do not address anyone under the age of 13\. I do not knowingly collect personally identifiable information from children under 13\. In the case I discover that a child under 13 has provided me with personal information, I immediately delete this from our servers. If you are a parent or guardian and you are aware that your child has provided us with personal information, please contact me so that I will be able to do necessary actions. +These Services do not address anyone under the age of 13. We do not knowingly collect personally identifiable information from children under 13 years of age. In the case we discover that a child under 13 has provided us with personal information, we immediately delete this from our servers. If you are a parent or guardian and you are aware that your child has provided us with personal information, please contact us so that we will be able to do the necessary actions. ## Changes to This Privacy Policy -I may update our Privacy Policy from time to time. Thus, you are advised to review this page periodically for any changes. I will notify you of any changes by posting the new Privacy Policy on this page. These changes are effective immediately after they are posted on this page. +We may update our Privacy Policy from time to time. Thus, you are advised to review this page periodically for any changes. We will notify you of any changes by posting the new Privacy Policy on this page. + +This policy is effective as of 2023-03-15 ## Contact Us -If you have any questions or suggestions about my Privacy Policy, do not hesitate to contact me. +If you have any questions or suggestions about our Privacy Policy, do not hesitate to contact us at oztechan@gmail.com. -This privacy policy page was created at [privacypolicytemplate.net](https://privacypolicytemplate.net) and modified/generated by [App Privacy Policy Generator](https://app-privacy-policy-generator.firebaseapp.com/) +This privacy policy page was created at [privacypolicytemplate.net](https://privacypolicytemplate.net) and modified/generated by [App Privacy Policy Generator](https://app-privacy-policy-generator.nisrulz.com/) \ No newline at end of file diff --git a/android/app/src/main/kotlin/com/oztechan/ccc/android/app/Application.kt b/android/app/src/main/kotlin/com/oztechan/ccc/android/app/Application.kt index 0e43385db6..49a085549f 100644 --- a/android/app/src/main/kotlin/com/oztechan/ccc/android/app/Application.kt +++ b/android/app/src/main/kotlin/com/oztechan/ccc/android/app/Application.kt @@ -16,7 +16,6 @@ import com.oztechan.ccc.android.app.di.initKoin import com.oztechan.ccc.android.core.ad.initAds import com.oztechan.ccc.client.core.analytics.initAnalytics -@Suppress("unused") class Application : Application() { override fun onCreate() { diff --git a/android/ui/mobile/src/main/kotlin/com/oztechan/ccc/android/ui/mobile/content/calculator/CalculatorFragment.kt b/android/ui/mobile/src/main/kotlin/com/oztechan/ccc/android/ui/mobile/content/calculator/CalculatorFragment.kt index 4f04325152..21c1eff3d8 100755 --- a/android/ui/mobile/src/main/kotlin/com/oztechan/ccc/android/ui/mobile/content/calculator/CalculatorFragment.kt +++ b/android/ui/mobile/src/main/kotlin/com/oztechan/ccc/android/ui/mobile/content/calculator/CalculatorFragment.kt @@ -127,7 +127,8 @@ class CalculatorFragment : BaseVBFragment() { ) } - CalculatorEffect.TooBigNumber -> view?.showSnack(R.string.text_too_big_number) + CalculatorEffect.TooBigInput -> view?.showSnack(R.string.text_too_big_input) + CalculatorEffect.TooBigOutput -> view?.showSnack(R.string.text_too_big_output) CalculatorEffect.OpenBar -> navigate( R.id.calculatorFragment, CalculatorFragmentDirections.actionCalculatorFragmentToSelectCurrencyBottomSheet() diff --git a/android/ui/mobile/src/main/kotlin/com/oztechan/ccc/android/ui/mobile/content/premium/PremiumAdapter.kt b/android/ui/mobile/src/main/kotlin/com/oztechan/ccc/android/ui/mobile/content/premium/PremiumAdapter.kt index b6f7989a6e..1631e925d6 100644 --- a/android/ui/mobile/src/main/kotlin/com/oztechan/ccc/android/ui/mobile/content/premium/PremiumAdapter.kt +++ b/android/ui/mobile/src/main/kotlin/com/oztechan/ccc/android/ui/mobile/content/premium/PremiumAdapter.kt @@ -5,8 +5,8 @@ import android.view.ViewGroup import androidx.recyclerview.widget.DiffUtil import com.github.submob.basemob.adapter.BaseVBRecyclerViewAdapter import com.oztechan.ccc.android.ui.mobile.databinding.ItemPremiumBinding -import com.oztechan.ccc.client.core.shared.model.PremiumType import com.oztechan.ccc.client.viewmodel.premium.PremiumEvent +import com.oztechan.ccc.client.viewmodel.premium.model.PremiumType class PremiumAdapter( private val premiumEvent: PremiumEvent diff --git a/android/ui/mobile/src/main/kotlin/com/oztechan/ccc/android/ui/mobile/content/premium/PremiumBottomSheet.kt b/android/ui/mobile/src/main/kotlin/com/oztechan/ccc/android/ui/mobile/content/premium/PremiumBottomSheet.kt index cd36fcb780..7e302afdc3 100644 --- a/android/ui/mobile/src/main/kotlin/com/oztechan/ccc/android/ui/mobile/content/premium/PremiumBottomSheet.kt +++ b/android/ui/mobile/src/main/kotlin/com/oztechan/ccc/android/ui/mobile/content/premium/PremiumBottomSheet.kt @@ -23,9 +23,9 @@ import com.oztechan.ccc.android.ui.mobile.util.toPremiumDataList import com.oztechan.ccc.android.ui.mobile.util.visibleIf import com.oztechan.ccc.client.core.analytics.AnalyticsManager import com.oztechan.ccc.client.core.analytics.model.ScreenName -import com.oztechan.ccc.client.core.shared.model.PremiumType import com.oztechan.ccc.client.viewmodel.premium.PremiumEffect import com.oztechan.ccc.client.viewmodel.premium.PremiumViewModel +import com.oztechan.ccc.client.viewmodel.premium.model.PremiumType import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import org.koin.android.ext.android.inject diff --git a/android/ui/mobile/src/main/kotlin/com/oztechan/ccc/android/ui/mobile/content/watchers/WatchersView.kt b/android/ui/mobile/src/main/kotlin/com/oztechan/ccc/android/ui/mobile/content/watchers/WatchersView.kt index 931da3f1c0..f3ac2f5470 100644 --- a/android/ui/mobile/src/main/kotlin/com/oztechan/ccc/android/ui/mobile/content/watchers/WatchersView.kt +++ b/android/ui/mobile/src/main/kotlin/com/oztechan/ccc/android/ui/mobile/content/watchers/WatchersView.kt @@ -53,7 +53,7 @@ fun NavHostController.WatchersView( is WatchersEffect.SelectTarget -> navigate("select_currency") WatchersEffect.InvalidInput -> snackbarHostState.showSnackbar(it.javaClass.simpleName) WatchersEffect.MaximumNumberOfWatchers -> snackbarHostState.showSnackbar(it.javaClass.simpleName) - WatchersEffect.TooBigNumber -> snackbarHostState.showSnackbar(it.javaClass.simpleName) + WatchersEffect.TooBigInput -> snackbarHostState.showSnackbar(it.javaClass.simpleName) } } } diff --git a/android/ui/mobile/src/main/kotlin/com/oztechan/ccc/android/ui/mobile/util/Dialog.kt b/android/ui/mobile/src/main/kotlin/com/oztechan/ccc/android/ui/mobile/util/Dialog.kt index 8328e04329..301ec1d14c 100644 --- a/android/ui/mobile/src/main/kotlin/com/oztechan/ccc/android/ui/mobile/util/Dialog.kt +++ b/android/ui/mobile/src/main/kotlin/com/oztechan/ccc/android/ui/mobile/util/Dialog.kt @@ -1,7 +1,6 @@ /* * Copyright (c) 2020 Mustafa Ozhan. All rights reserved. */ -@file:Suppress("unused") package com.oztechan.ccc.android.ui.mobile.util diff --git a/android/ui/mobile/src/main/kotlin/com/oztechan/ccc/android/ui/mobile/util/PurchaseUtil.kt b/android/ui/mobile/src/main/kotlin/com/oztechan/ccc/android/ui/mobile/util/PurchaseUtil.kt index 6f43ae8c5d..b5c81b8dff 100644 --- a/android/ui/mobile/src/main/kotlin/com/oztechan/ccc/android/ui/mobile/util/PurchaseUtil.kt +++ b/android/ui/mobile/src/main/kotlin/com/oztechan/ccc/android/ui/mobile/util/PurchaseUtil.kt @@ -2,9 +2,9 @@ package com.oztechan.ccc.android.ui.mobile.util import com.oztechan.ccc.android.core.billing.model.ProductDetails import com.oztechan.ccc.android.core.billing.model.PurchaseHistoryRecord -import com.oztechan.ccc.client.core.shared.model.OldPurchase -import com.oztechan.ccc.client.core.shared.model.PremiumData -import com.oztechan.ccc.client.core.shared.model.PremiumType +import com.oztechan.ccc.client.viewmodel.premium.model.OldPurchase +import com.oztechan.ccc.client.viewmodel.premium.model.PremiumData +import com.oztechan.ccc.client.viewmodel.premium.model.PremiumType internal fun List.toPremiumDataList(): List = map { PremiumData(it.price, it.description, it.id) diff --git a/android/ui/mobile/src/main/res/values/styles.xml b/android/ui/mobile/src/main/res/values/styles.xml index be5329304a..0abf7fe06d 100755 --- a/android/ui/mobile/src/main/res/values/styles.xml +++ b/android/ui/mobile/src/main/res/values/styles.xml @@ -1,7 +1,7 @@ - + diff --git a/android/ui/mobile/src/test/kotlin/com/oztechan/ccc/android/ui/mobile/util/PurchaseUtilTest.kt b/android/ui/mobile/src/test/kotlin/com/oztechan/ccc/android/ui/mobile/util/PurchaseUtilTest.kt index 33007086c6..4726006ef4 100644 --- a/android/ui/mobile/src/test/kotlin/com/oztechan/ccc/android/ui/mobile/util/PurchaseUtilTest.kt +++ b/android/ui/mobile/src/test/kotlin/com/oztechan/ccc/android/ui/mobile/util/PurchaseUtilTest.kt @@ -2,7 +2,7 @@ package com.oztechan.ccc.android.ui.mobile.util import com.oztechan.ccc.android.core.billing.model.ProductDetails import com.oztechan.ccc.android.core.billing.model.PurchaseHistoryRecord -import com.oztechan.ccc.client.core.shared.model.PremiumType +import com.oztechan.ccc.client.viewmodel.premium.model.PremiumType import kotlin.test.Test import kotlin.test.assertEquals diff --git a/android/ui/widget/android-ui-widget.gradle.kts b/android/ui/widget/android-ui-widget.gradle.kts index 28328f1cf6..df78009199 100644 --- a/android/ui/widget/android-ui-widget.gradle.kts +++ b/android/ui/widget/android-ui-widget.gradle.kts @@ -44,5 +44,8 @@ dependencies { implementation(project(Modules.Common.Core.model)) - implementation(project(Modules.Client.Core.res)) + Modules.Client.Core.apply { + implementation(project(res)) + implementation(project(analytics)) + } } diff --git a/android/ui/widget/src/main/kotlin/com/oztechan/ccc/android/ui/widget/AppWidgetReceiver.kt b/android/ui/widget/src/main/kotlin/com/oztechan/ccc/android/ui/widget/AppWidgetReceiver.kt index d49f84da82..98fdf99c6d 100644 --- a/android/ui/widget/src/main/kotlin/com/oztechan/ccc/android/ui/widget/AppWidgetReceiver.kt +++ b/android/ui/widget/src/main/kotlin/com/oztechan/ccc/android/ui/widget/AppWidgetReceiver.kt @@ -9,6 +9,8 @@ import androidx.glance.appwidget.GlanceAppWidgetReceiver import com.oztechan.ccc.android.ui.widget.action.WidgetAction import com.oztechan.ccc.android.ui.widget.action.WidgetAction.Companion.mapToWidgetAction import com.oztechan.ccc.android.viewmodel.widget.WidgetViewModel +import com.oztechan.ccc.client.core.analytics.AnalyticsManager +import com.oztechan.ccc.client.core.analytics.model.UserProperty import kotlinx.coroutines.runBlocking import org.koin.core.component.KoinComponent import org.koin.core.component.inject @@ -17,6 +19,7 @@ class AppWidgetReceiver : GlanceAppWidgetReceiver(), KoinComponent { override val glanceAppWidget: GlanceAppWidget = AppWidget() private val viewModel: WidgetViewModel by inject() + private val analyticsManager: AnalyticsManager by inject() private fun refreshData( context: Context, @@ -41,25 +44,51 @@ class AppWidgetReceiver : GlanceAppWidgetReceiver(), KoinComponent { override fun onReceive(context: Context, intent: Intent) { super.onReceive(context, intent) - when (intent.action.mapToWidgetAction()) { - WidgetAction.IDLE -> Unit - WidgetAction.REFRESH -> refreshData(context) - WidgetAction.NEXT_BASE -> refreshData(context, true) - WidgetAction.PREVIOUS_BASE -> refreshData(context, false) - WidgetAction.OPEN_APP -> - context.packageManager - .getLaunchIntentForPackage(context.packageName) - ?.apply { - addFlags( - Intent.FLAG_ACTIVITY_CLEAR_TASK or - Intent.FLAG_ACTIVITY_CLEAR_TOP or - Intent.FLAG_ACTIVITY_NEW_TASK - ) - }?.let { - context.startActivity(it) - } - - else -> error("undefined widget action") + intent.action.let { + it.mapToWidgetAction()?.executeWidgetAction(context) + ?: it.executeSystemAction(context) } } + + private fun WidgetAction.executeWidgetAction(context: Context) = when (this) { + WidgetAction.IDLE -> Unit + WidgetAction.REFRESH -> refreshData(context) + WidgetAction.NEXT_BASE -> refreshData(context, true) + WidgetAction.PREVIOUS_BASE -> refreshData(context, false) + WidgetAction.OPEN_APP -> + context.packageManager + .getLaunchIntentForPackage(context.packageName) + ?.apply { + addFlags( + Intent.FLAG_ACTIVITY_CLEAR_TASK or + Intent.FLAG_ACTIVITY_CLEAR_TOP or + Intent.FLAG_ACTIVITY_NEW_TASK + ) + }?.let { + context.startActivity(it) + } + } + + private fun String?.executeSystemAction(context: Context) = when (this) { + AppWidgetManager.ACTION_APPWIDGET_DELETED -> analyticsManager.setUserProperty( + UserProperty.HasWidget(false.toString()) + ) + + AppWidgetManager.ACTION_APPWIDGET_ENABLED -> analyticsManager.setUserProperty( + UserProperty.HasWidget(true.toString()) + ) + + AppWidgetManager.ACTION_APPWIDGET_UPDATE, + AppWidgetManager.ACTION_APPWIDGET_OPTIONS_CHANGED -> refreshData(context) + + // defined but no action needed system events + AppWidgetManager.ACTION_APPWIDGET_DISABLED, + AppWidgetManager.ACTION_APPWIDGET_BIND, + AppWidgetManager.ACTION_APPWIDGET_PICK, + AppWidgetManager.ACTION_APPWIDGET_CONFIGURE, + AppWidgetManager.ACTION_APPWIDGET_RESTORED, + AppWidgetManager.ACTION_APPWIDGET_HOST_RESTORED -> Unit + + else -> error("undefined widget action") + } } diff --git a/android/ui/widget/src/main/kotlin/com/oztechan/ccc/android/ui/widget/action/WidgetAction.kt b/android/ui/widget/src/main/kotlin/com/oztechan/ccc/android/ui/widget/action/WidgetAction.kt index 9d3a1be3a1..b7e7f0b98e 100644 --- a/android/ui/widget/src/main/kotlin/com/oztechan/ccc/android/ui/widget/action/WidgetAction.kt +++ b/android/ui/widget/src/main/kotlin/com/oztechan/ccc/android/ui/widget/action/WidgetAction.kt @@ -1,6 +1,5 @@ package com.oztechan.ccc.android.ui.widget.action -import android.appwidget.AppWidgetManager import androidx.glance.action.ActionParameters import androidx.glance.action.actionParametersOf import androidx.glance.appwidget.action.actionRunCallback @@ -13,24 +12,8 @@ enum class WidgetAction { IDLE; companion object { - fun String?.mapToWidgetAction() = when (this) { - // no needed to refresh - AppWidgetManager.ACTION_APPWIDGET_DISABLED, - AppWidgetManager.ACTION_APPWIDGET_DELETED, - AppWidgetManager.ACTION_APPWIDGET_ENABLED -> IDLE - - // unknown ones perhaps no need to refresh (yet) - AppWidgetManager.ACTION_APPWIDGET_BIND, - AppWidgetManager.ACTION_APPWIDGET_PICK, - AppWidgetManager.ACTION_APPWIDGET_CONFIGURE, - AppWidgetManager.ACTION_APPWIDGET_RESTORED, - AppWidgetManager.ACTION_APPWIDGET_HOST_RESTORED -> IDLE - - AppWidgetManager.ACTION_APPWIDGET_UPDATE, - AppWidgetManager.ACTION_APPWIDGET_OPTIONS_CHANGED -> REFRESH - - else -> WidgetAction.values().firstOrNull { this == it.name } - } + fun String?.mapToWidgetAction() = WidgetAction.values() + .firstOrNull { this == it.name } fun WidgetAction.toActionCallback() = actionRunCallback( actionParametersOf( diff --git a/android/ui/widget/src/test/kotlin/com/oztechan/ccc/android/ui/widget/action/WidgetActionTest.kt b/android/ui/widget/src/test/kotlin/com/oztechan/ccc/android/ui/widget/action/WidgetActionTest.kt index 3ace3c30f9..63b4ec0a9a 100644 --- a/android/ui/widget/src/test/kotlin/com/oztechan/ccc/android/ui/widget/action/WidgetActionTest.kt +++ b/android/ui/widget/src/test/kotlin/com/oztechan/ccc/android/ui/widget/action/WidgetActionTest.kt @@ -1,6 +1,5 @@ package com.oztechan.ccc.android.ui.widget.action -import android.appwidget.AppWidgetManager import com.oztechan.ccc.android.ui.widget.action.WidgetAction.Companion.mapToWidgetAction import org.junit.Test import kotlin.test.assertEquals @@ -15,20 +14,4 @@ class WidgetActionTest { assertEquals(it, it.name.mapToWidgetAction()) } } - - @Test - fun `AppWidgetManager actions mapped to correct WidgetActions`() { - assertEquals(WidgetAction.IDLE, AppWidgetManager.ACTION_APPWIDGET_DISABLED.mapToWidgetAction()) - assertEquals(WidgetAction.IDLE, AppWidgetManager.ACTION_APPWIDGET_DELETED.mapToWidgetAction()) - assertEquals(WidgetAction.IDLE, AppWidgetManager.ACTION_APPWIDGET_ENABLED.mapToWidgetAction()) - - assertEquals(WidgetAction.IDLE, AppWidgetManager.ACTION_APPWIDGET_BIND.mapToWidgetAction()) - assertEquals(WidgetAction.IDLE, AppWidgetManager.ACTION_APPWIDGET_PICK.mapToWidgetAction()) - assertEquals(WidgetAction.IDLE, AppWidgetManager.ACTION_APPWIDGET_CONFIGURE.mapToWidgetAction()) - assertEquals(WidgetAction.IDLE, AppWidgetManager.ACTION_APPWIDGET_RESTORED.mapToWidgetAction()) - assertEquals(WidgetAction.IDLE, AppWidgetManager.ACTION_APPWIDGET_HOST_RESTORED.mapToWidgetAction()) - - assertEquals(WidgetAction.REFRESH, AppWidgetManager.ACTION_APPWIDGET_UPDATE.mapToWidgetAction()) - assertEquals(WidgetAction.REFRESH, AppWidgetManager.ACTION_APPWIDGET_OPTIONS_CHANGED.mapToWidgetAction()) - } } diff --git a/android/viewmodel/widget/src/main/kotlin/com/oztechan/ccc/android/viewmodel/widget/WidgetViewModel.kt b/android/viewmodel/widget/src/main/kotlin/com/oztechan/ccc/android/viewmodel/widget/WidgetViewModel.kt index 1027b1930a..310f9f04ce 100644 --- a/android/viewmodel/widget/src/main/kotlin/com/oztechan/ccc/android/viewmodel/widget/WidgetViewModel.kt +++ b/android/viewmodel/widget/src/main/kotlin/com/oztechan/ccc/android/viewmodel/widget/WidgetViewModel.kt @@ -69,7 +69,7 @@ class WidgetViewModel( it - 1 } }.let { - it % activeCurrencies.size // it handles index -1 and index size +1 + (it + activeCurrencies.size) % activeCurrencies.size // it handles index -1 and index size +1 } calculationStorage.currentBase = activeCurrencies[newBaseIndex].code diff --git a/android/viewmodel/widget/src/test/kotlin/com/oztechan/ccc/android/viewmodel/widget/WidgetViewModelTest.kt b/android/viewmodel/widget/src/test/kotlin/com/oztechan/ccc/android/viewmodel/widget/WidgetViewModelTest.kt index 6668c909f2..ca97fa7425 100644 --- a/android/viewmodel/widget/src/test/kotlin/com/oztechan/ccc/android/viewmodel/widget/WidgetViewModelTest.kt +++ b/android/viewmodel/widget/src/test/kotlin/com/oztechan/ccc/android/viewmodel/widget/WidgetViewModelTest.kt @@ -51,11 +51,11 @@ class WidgetViewModelTest { private val appStorage = mock(classOf()) private val base = "EUR" - private val fistBase = "USD" + private val firstBase = "USD" private val lastBase = "TRY" private val activeCurrencyList = listOf( - Currency(code = fistBase, name = "Dollar", symbol = "$", isActive = true), + Currency(code = firstBase, name = "Dollar", symbol = "$", isActive = true), Currency(code = base, name = "Euro", symbol = "€", isActive = true), Currency(code = lastBase, name = "Turkish Lira", symbol = "₺", isActive = true) ) @@ -89,6 +89,66 @@ class WidgetViewModelTest { } } + @Test + fun `ArrayIndexOutOfBoundsException never thrown`() = runTest { + // first currency + given(calculationStorage) + .invocation { currentBase } + .thenReturn(firstBase) + + given(backendApiService) + .coroutine { getConversion(firstBase) } + .thenReturn(conversion) + + repeat(activeCurrencyList.count()) { + viewModel.refreshWidgetData() + } + repeat(activeCurrencyList.count()) { + viewModel.refreshWidgetData(true) + } + repeat(activeCurrencyList.count()) { + viewModel.refreshWidgetData(false) + } + + // middle currency + given(calculationStorage) + .invocation { currentBase } + .thenReturn(base) + + given(backendApiService) + .coroutine { getConversion(base) } + .thenReturn(conversion) + + repeat(activeCurrencyList.count()) { + viewModel.refreshWidgetData() + } + repeat(activeCurrencyList.count()) { + viewModel.refreshWidgetData(true) + } + repeat(activeCurrencyList.count()) { + viewModel.refreshWidgetData(false) + } + + // last currency + given(calculationStorage) + .invocation { currentBase } + .thenReturn(lastBase) + + given(backendApiService) + .coroutine { getConversion(lastBase) } + .thenReturn(conversion) + + repeat(activeCurrencyList.count()) { + viewModel.refreshWidgetData() + } + repeat(activeCurrencyList.count()) { + viewModel.refreshWidgetData(true) + } + repeat(activeCurrencyList.count()) { + viewModel.refreshWidgetData(false) + } + } + @Test fun `init sets isPremium and currentBase`() { assertEquals(base, viewModel.state.currentBase) @@ -180,7 +240,7 @@ class WidgetViewModelTest { verify(calculationStorage) .setter(calculationStorage::currentBase) - .with(eq(fistBase)) + .with(eq(firstBase)) .wasInvoked() viewModel.refreshWidgetData(true) @@ -221,7 +281,7 @@ class WidgetViewModelTest { verify(calculationStorage) .setter(calculationStorage::currentBase) - .with(eq(fistBase)) + .with(eq(firstBase)) .wasInvoked() } } diff --git a/backend/controller/sync/src/main/kotlin/com/oztechan/ccc/backend/controller/sync/SyncControllerImpl.kt b/backend/controller/sync/src/main/kotlin/com/oztechan/ccc/backend/controller/sync/SyncControllerImpl.kt index 156b28ac4a..f67355382a 100644 --- a/backend/controller/sync/src/main/kotlin/com/oztechan/ccc/backend/controller/sync/SyncControllerImpl.kt +++ b/backend/controller/sync/src/main/kotlin/com/oztechan/ccc/backend/controller/sync/SyncControllerImpl.kt @@ -9,7 +9,6 @@ import com.oztechan.ccc.common.datasource.conversion.ConversionDataSource import kotlinx.coroutines.delay import kotlin.time.Duration.Companion.seconds -@Suppress("unused") internal class SyncControllerImpl( private val premiumApiService: PremiumApiService, private val freeApiService: FreeApiService, diff --git a/buildSrc/src/main/kotlin/config/DeviceFlavour.kt b/buildSrc/src/main/kotlin/config/DeviceFlavour.kt index dc512413cd..c8cebb2ef0 100644 --- a/buildSrc/src/main/kotlin/config/DeviceFlavour.kt +++ b/buildSrc/src/main/kotlin/config/DeviceFlavour.kt @@ -4,7 +4,6 @@ enum class DeviceFlavour { GOOGLE, HUAWEI; - @Suppress("unused") companion object { val google = GOOGLE.name.lowercase() val huawei = HUAWEI.name.lowercase() diff --git a/client/core/analytics/src/commonMain/kotlin/com/oztechan/ccc/client/core/analytics/model/ScreenName.kt b/client/core/analytics/src/commonMain/kotlin/com/oztechan/ccc/client/core/analytics/model/ScreenName.kt index c66d3d9f70..4fdc5f657d 100644 --- a/client/core/analytics/src/commonMain/kotlin/com/oztechan/ccc/client/core/analytics/model/ScreenName.kt +++ b/client/core/analytics/src/commonMain/kotlin/com/oztechan/ccc/client/core/analytics/model/ScreenName.kt @@ -5,8 +5,6 @@ sealed class ScreenName { object SelectCurrency : ScreenName() object Currencies : ScreenName() object Settings : ScreenName() - - @Suppress("unused") // used in iOS object Watchers : ScreenName() object Premium : ScreenName() data class Slider(val position: Int) : ScreenName() diff --git a/client/core/analytics/src/commonMain/kotlin/com/oztechan/ccc/client/core/analytics/model/UserProperty.kt b/client/core/analytics/src/commonMain/kotlin/com/oztechan/ccc/client/core/analytics/model/UserProperty.kt index 62b2c18fca..3d1d6a5b2b 100644 --- a/client/core/analytics/src/commonMain/kotlin/com/oztechan/ccc/client/core/analytics/model/UserProperty.kt +++ b/client/core/analytics/src/commonMain/kotlin/com/oztechan/ccc/client/core/analytics/model/UserProperty.kt @@ -9,4 +9,5 @@ sealed class UserProperty(val key: String, open val value: String) { data class IsPremium(override val value: String) : UserProperty("is_premium", value) data class SessionCount(override val value: String) : UserProperty("session_count", value) data class DevicePlatform(override val value: String) : UserProperty("device_platform", value) + data class HasWidget(override val value: String) : UserProperty("has_widget", value) // android only } diff --git a/client/core/res/src/commonMain/resources/MR/base/strings.xml b/client/core/res/src/commonMain/resources/MR/base/strings.xml index eeda4f6487..506086fdfc 100644 --- a/client/core/res/src/commonMain/resources/MR/base/strings.xml +++ b/client/core/res/src/commonMain/resources/MR/base/strings.xml @@ -40,7 +40,8 @@ OK Select Cancel - Too big number. + The input is too long. + The output is too long. Online! Last update: %s Cached! Last update: %s Offline! Last update: %s diff --git a/client/core/res/src/iosMain/kotlin/com/oztechan/ccc/client/core/res/IOSResources.kt b/client/core/res/src/iosMain/kotlin/com/oztechan/ccc/client/core/res/IOSResources.kt index b22d3e1c02..33629cb205 100644 --- a/client/core/res/src/iosMain/kotlin/com/oztechan/ccc/client/core/res/IOSResources.kt +++ b/client/core/res/src/iosMain/kotlin/com/oztechan/ccc/client/core/res/IOSResources.kt @@ -5,15 +5,12 @@ package com.oztechan.ccc.client.core.res -import dev.icerock.moko.graphics.toUIColor import dev.icerock.moko.resources.ColorResource import dev.icerock.moko.resources.StringResource import dev.icerock.moko.resources.desc.Resource import dev.icerock.moko.resources.desc.ResourceFormatted import dev.icerock.moko.resources.desc.StringDesc -import dev.icerock.moko.resources.getColor -import platform.UIKit.UIColor -import platform.UIKit.UIScreen +import dev.icerock.moko.resources.getUIColor fun getString(stringResource: StringResource): StringDesc { return StringDesc.Resource(stringResource) @@ -23,8 +20,4 @@ fun getString(stringResource: StringResource, parameter: Any): StringDesc { return StringDesc.ResourceFormatted(stringResource, parameter) } -fun getColor(colorResource: ColorResource): UIColor { - return colorResource.getColor( - UIScreen.mainScreen.traitCollection.userInterfaceStyle - ).toUIColor() -} +fun getColor(colorResource: ColorResource) = colorResource.getUIColor() diff --git a/client/viewmodel/calculator/src/commonMain/kotlin/com/oztechan/ccc/client/viewmodel/calculator/CalculatorSEED.kt b/client/viewmodel/calculator/src/commonMain/kotlin/com/oztechan/ccc/client/viewmodel/calculator/CalculatorSEED.kt index 79ca52c3af..ff7392b5f4 100644 --- a/client/viewmodel/calculator/src/commonMain/kotlin/com/oztechan/ccc/client/viewmodel/calculator/CalculatorSEED.kt +++ b/client/viewmodel/calculator/src/commonMain/kotlin/com/oztechan/ccc/client/viewmodel/calculator/CalculatorSEED.kt @@ -39,7 +39,8 @@ sealed class CalculatorEffect : BaseEffect() { object Error : CalculatorEffect() object FewCurrency : CalculatorEffect() object OpenBar : CalculatorEffect() - object TooBigNumber : CalculatorEffect() + object TooBigInput : CalculatorEffect() + object TooBigOutput : CalculatorEffect() object OpenSettings : CalculatorEffect() object ShowPasteRequest : CalculatorEffect() data class CopyToClipboard(val amount: String) : CalculatorEffect() diff --git a/client/viewmodel/calculator/src/commonMain/kotlin/com/oztechan/ccc/client/viewmodel/calculator/CalculatorViewModel.kt b/client/viewmodel/calculator/src/commonMain/kotlin/com/oztechan/ccc/client/viewmodel/calculator/CalculatorViewModel.kt index 54c64babd0..52ea191898 100644 --- a/client/viewmodel/calculator/src/commonMain/kotlin/com/oztechan/ccc/client/viewmodel/calculator/CalculatorViewModel.kt +++ b/client/viewmodel/calculator/src/commonMain/kotlin/com/oztechan/ccc/client/viewmodel/calculator/CalculatorViewModel.kt @@ -5,7 +5,6 @@ package com.oztechan.ccc.client.viewmodel.calculator import co.touchlab.kermit.Logger import com.github.submob.scopemob.mapTo -import com.github.submob.scopemob.whether import com.github.submob.scopemob.whetherNot import com.oztechan.ccc.client.core.analytics.AnalyticsManager import com.oztechan.ccc.client.core.analytics.model.Event @@ -75,9 +74,10 @@ class CalculatorViewModel( currencyList = currencyDataSource.getActiveCurrencies(), base = calculationStorage.currentBase, input = calculationStorage.lastInput, + loading = true ) } - fetchConversion() + updateConversion() observeBase() observeInput() } @@ -107,19 +107,19 @@ class CalculatorViewModel( } .launchIn(viewModelScope) - private fun fetchConversion() = data.conversion?.let { - calculateConversions(it, ConversionState.Cached(it.date)) - } ?: viewModelScope.launch { + private fun updateConversion() { _state.update { copy(loading = true) } - runCatching { backendApiService.getConversion(calculationStorage.currentBase) } - .onFailure(::fetchConversionFailed) - .onSuccess(::fetchConversionSuccess) - .also { - _state.update { copy(loading = false) } - } + + data.conversion?.let { + calculateConversions(it, ConversionState.Cached(it.date)) + } ?: viewModelScope.launch { + runCatching { backendApiService.getConversion(calculationStorage.currentBase) } + .onFailure(::updateConversionFailed) + .onSuccess(::updateConversionSuccess) + } } - private fun fetchConversionSuccess(conversion: Conversion) = conversion.copy(date = nowAsDateString()) + private fun updateConversionSuccess(conversion: Conversion) = conversion.copy(date = nowAsDateString()) .let { data.conversion = it calculateConversions(it, ConversionState.Online(it.date)) @@ -129,8 +129,8 @@ class CalculatorViewModel( } } - private fun fetchConversionFailed(t: Throwable) = viewModelScope.launchIgnored { - Logger.w(t) { "CalculatorViewModel getConversionFailed" } + private fun updateConversionFailed(t: Throwable) = viewModelScope.launchIgnored { + Logger.w(t) { "CalculatorViewModel updateConversionFailed" } conversionDataSource.getConversionByBase( calculationStorage.currentBase )?.let { @@ -140,45 +140,49 @@ class CalculatorViewModel( _effect.emit(CalculatorEffect.Error) - _state.update { - copy(conversionState = ConversionState.Error) - } - } - } - - private fun calculateOutput(input: String) = viewModelScope.launch { - data.parser - .calculate(input.toSupportedCharacters(), MAXIMUM_FLOATING_POINT) - .mapTo { if (isFinite()) getFormatted(calculationStorage.precision) else "" } - .whether( - { output -> output.length <= MAXIMUM_OUTPUT }, - { input.length <= MAXIMUM_INPUT } - )?.let { output -> - _state.update { copy(output = output) } - state.value.currencyList.size - .whether { it < MINIMUM_ACTIVE_CURRENCY } - ?.whetherNot { state.value.input.isEmpty() } - ?.let { _effect.emit(CalculatorEffect.FewCurrency) } - ?: run { fetchConversion() } - } ?: run { - _effect.emit(CalculatorEffect.TooBigNumber) - _state.update { - copy(input = input.dropLast(1)) - } + calculateConversions(null, ConversionState.Error) } } - private fun calculateConversions(conversion: Conversion, conversionState: ConversionState) = _state.update { + private fun calculateConversions(conversion: Conversion?, conversionState: ConversionState) = _state.update { copy( currencyList = _state.value.currencyList.onEach { it.rate = conversion.calculateRate(it.code, _state.value.output) .getFormatted(calculationStorage.precision) .toStandardDigits() }, - conversionState = conversionState + conversionState = conversionState, + loading = false ) } + private fun calculateOutput(input: String) = viewModelScope.launch { + val output = data.parser + .calculate(input.toSupportedCharacters(), MAXIMUM_FLOATING_POINT) + .mapTo { if (isFinite()) getFormatted(calculationStorage.precision) else "" } + + _state.update { copy(output = output) } + + when { + input.length > MAXIMUM_INPUT -> { + _effect.emit(CalculatorEffect.TooBigInput) + _state.update { copy(input = input.dropLast(1)) } + } + + output.length > MAXIMUM_OUTPUT -> { + _effect.emit(CalculatorEffect.TooBigOutput) + _state.update { copy(input = input.dropLast(1)) } + } + + state.value.currencyList.size < MINIMUM_ACTIVE_CURRENCY -> { + _effect.emit(CalculatorEffect.FewCurrency) + _state.update { copy(loading = false) } + } + + else -> updateConversion() + } + } + private fun currentBaseChanged(newBase: String, shouldTrack: Boolean = false) = viewModelScope.launchIgnored { data.conversion = null calculationStorage.currentBase = newBase diff --git a/client/viewmodel/calculator/src/commonTest/kotlin/com/oztechan/ccc/client/viewmodel/calculator/CalculatorViewModelTest.kt b/client/viewmodel/calculator/src/commonTest/kotlin/com/oztechan/ccc/client/viewmodel/calculator/CalculatorViewModelTest.kt index 1af7f2c28b..fa54737ac5 100644 --- a/client/viewmodel/calculator/src/commonTest/kotlin/com/oztechan/ccc/client/viewmodel/calculator/CalculatorViewModelTest.kt +++ b/client/viewmodel/calculator/src/commonTest/kotlin/com/oztechan/ccc/client/viewmodel/calculator/CalculatorViewModelTest.kt @@ -235,24 +235,24 @@ internal class CalculatorViewModelTest { } @Test - fun `when input is too long it should drop the last digit and give warning`() = runTest { + fun `when input is too long it should drop the last digit and give TooBigInput effect`() = runTest { val fortyFiveDigitNumber = "1234567890+1234567890+1234567890+1234567890+1" viewModel.effect.onSubscription { viewModel.event.onKeyPress(fortyFiveDigitNumber) }.firstOrNull().let { - assertIs(it) + assertIs(it) assertFalse { viewModel.state.value.loading } assertEquals(fortyFiveDigitNumber.dropLast(1), viewModel.state.value.input) } } @Test - fun `when output is too long it should drop the last digit and give warning`() = runTest { + fun `when output is too long it should drop the last digit and give TooBigOutput effect`() = runTest { val nineteenDigitNumber = "123 567 901 345 789" viewModel.effect.onSubscription { viewModel.event.onKeyPress(nineteenDigitNumber) }.firstOrNull().let { - assertIs(it) + assertIs(it) assertFalse { viewModel.state.value.loading } assertEquals(nineteenDigitNumber.dropLast(1), viewModel.state.value.input) } diff --git a/client/viewmodel/premium/client-viewmodel-premium.gradle.kts b/client/viewmodel/premium/client-viewmodel-premium.gradle.kts index c52c08f3cd..20ef1ca389 100644 --- a/client/viewmodel/premium/client-viewmodel-premium.gradle.kts +++ b/client/viewmodel/premium/client-viewmodel-premium.gradle.kts @@ -21,6 +21,7 @@ kotlin { implementation(koinCore) implementation(kermit) implementation(coroutines) + implementation(kotlinXDateTime) } Modules.Client.Core.apply { diff --git a/client/viewmodel/premium/src/commonMain/kotlin/com/oztechan/ccc/client/viewmodel/premium/PremiumSEED.kt b/client/viewmodel/premium/src/commonMain/kotlin/com/oztechan/ccc/client/viewmodel/premium/PremiumSEED.kt index c7c8577cea..6b0a9cc893 100644 --- a/client/viewmodel/premium/src/commonMain/kotlin/com/oztechan/ccc/client/viewmodel/premium/PremiumSEED.kt +++ b/client/viewmodel/premium/src/commonMain/kotlin/com/oztechan/ccc/client/viewmodel/premium/PremiumSEED.kt @@ -1,9 +1,9 @@ package com.oztechan.ccc.client.viewmodel.premium -import com.oztechan.ccc.client.core.shared.model.PremiumType import com.oztechan.ccc.client.core.viewmodel.BaseEffect import com.oztechan.ccc.client.core.viewmodel.BaseEvent import com.oztechan.ccc.client.core.viewmodel.BaseState +import com.oztechan.ccc.client.viewmodel.premium.model.PremiumType // State data class PremiumState( diff --git a/client/viewmodel/premium/src/commonMain/kotlin/com/oztechan/ccc/client/viewmodel/premium/PremiumViewModel.kt b/client/viewmodel/premium/src/commonMain/kotlin/com/oztechan/ccc/client/viewmodel/premium/PremiumViewModel.kt index 87e063532d..ad64980ab8 100644 --- a/client/viewmodel/premium/src/commonMain/kotlin/com/oztechan/ccc/client/viewmodel/premium/PremiumViewModel.kt +++ b/client/viewmodel/premium/src/commonMain/kotlin/com/oztechan/ccc/client/viewmodel/premium/PremiumViewModel.kt @@ -6,10 +6,6 @@ package com.oztechan.ccc.client.viewmodel.premium import co.touchlab.kermit.Logger import com.github.submob.scopemob.whether -import com.oztechan.ccc.client.core.shared.model.OldPurchase -import com.oztechan.ccc.client.core.shared.model.PremiumData -import com.oztechan.ccc.client.core.shared.model.PremiumType -import com.oztechan.ccc.client.core.shared.util.calculatePremiumEnd import com.oztechan.ccc.client.core.shared.util.isNotPassed import com.oztechan.ccc.client.core.shared.util.nowAsLong import com.oztechan.ccc.client.core.viewmodel.BaseData @@ -17,6 +13,10 @@ import com.oztechan.ccc.client.core.viewmodel.BaseSEEDViewModel import com.oztechan.ccc.client.core.viewmodel.util.launchIgnored import com.oztechan.ccc.client.core.viewmodel.util.update import com.oztechan.ccc.client.storage.app.AppStorage +import com.oztechan.ccc.client.viewmodel.premium.model.OldPurchase +import com.oztechan.ccc.client.viewmodel.premium.model.PremiumData +import com.oztechan.ccc.client.viewmodel.premium.model.PremiumType +import com.oztechan.ccc.client.viewmodel.premium.util.calculatePremiumEnd import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asSharedFlow diff --git a/client/core/shared/src/commonMain/kotlin/com/oztechan/ccc/client/core/shared/model/OldPurchase.kt b/client/viewmodel/premium/src/commonMain/kotlin/com/oztechan/ccc/client/viewmodel/premium/model/OldPurchase.kt similarity index 56% rename from client/core/shared/src/commonMain/kotlin/com/oztechan/ccc/client/core/shared/model/OldPurchase.kt rename to client/viewmodel/premium/src/commonMain/kotlin/com/oztechan/ccc/client/viewmodel/premium/model/OldPurchase.kt index 22645c7043..4faaf34958 100644 --- a/client/core/shared/src/commonMain/kotlin/com/oztechan/ccc/client/core/shared/model/OldPurchase.kt +++ b/client/viewmodel/premium/src/commonMain/kotlin/com/oztechan/ccc/client/viewmodel/premium/model/OldPurchase.kt @@ -1,4 +1,4 @@ -package com.oztechan.ccc.client.core.shared.model +package com.oztechan.ccc.client.viewmodel.premium.model data class OldPurchase( val date: Long, diff --git a/client/core/shared/src/commonMain/kotlin/com/oztechan/ccc/client/core/shared/model/PremiumData.kt b/client/viewmodel/premium/src/commonMain/kotlin/com/oztechan/ccc/client/viewmodel/premium/model/PremiumData.kt similarity index 62% rename from client/core/shared/src/commonMain/kotlin/com/oztechan/ccc/client/core/shared/model/PremiumData.kt rename to client/viewmodel/premium/src/commonMain/kotlin/com/oztechan/ccc/client/viewmodel/premium/model/PremiumData.kt index a6fde6d679..c63496396a 100644 --- a/client/core/shared/src/commonMain/kotlin/com/oztechan/ccc/client/core/shared/model/PremiumData.kt +++ b/client/viewmodel/premium/src/commonMain/kotlin/com/oztechan/ccc/client/viewmodel/premium/model/PremiumData.kt @@ -1,4 +1,4 @@ -package com.oztechan.ccc.client.core.shared.model +package com.oztechan.ccc.client.viewmodel.premium.model data class PremiumData( var cost: String, diff --git a/client/core/shared/src/commonMain/kotlin/com/oztechan/ccc/client/core/shared/model/PremiumType.kt b/client/viewmodel/premium/src/commonMain/kotlin/com/oztechan/ccc/client/viewmodel/premium/model/PremiumType.kt similarity index 84% rename from client/core/shared/src/commonMain/kotlin/com/oztechan/ccc/client/core/shared/model/PremiumType.kt rename to client/viewmodel/premium/src/commonMain/kotlin/com/oztechan/ccc/client/viewmodel/premium/model/PremiumType.kt index a75674d9f7..0d2a014c5b 100644 --- a/client/core/shared/src/commonMain/kotlin/com/oztechan/ccc/client/core/shared/model/PremiumType.kt +++ b/client/viewmodel/premium/src/commonMain/kotlin/com/oztechan/ccc/client/viewmodel/premium/model/PremiumType.kt @@ -2,9 +2,9 @@ * Copyright (c) 2021 Mustafa Ozhan. All rights reserved. */ -package com.oztechan.ccc.client.core.shared.model +package com.oztechan.ccc.client.viewmodel.premium.model -import com.oztechan.ccc.client.core.shared.util.REWARDED_AD_PREMIUM_IN_DAYS +import com.oztechan.ccc.client.viewmodel.premium.util.REWARDED_AD_PREMIUM_IN_DAYS enum class PremiumType(val data: PremiumData) { VIDEO(PremiumData("Watch Video", "$REWARDED_AD_PREMIUM_IN_DAYS Days", "")), diff --git a/client/core/shared/src/commonMain/kotlin/com/oztechan/ccc/client/core/shared/util/PremiumUtil.kt b/client/viewmodel/premium/src/commonMain/kotlin/com/oztechan/ccc/client/viewmodel/premium/util/PremiumUtil.kt similarity index 84% rename from client/core/shared/src/commonMain/kotlin/com/oztechan/ccc/client/core/shared/util/PremiumUtil.kt rename to client/viewmodel/premium/src/commonMain/kotlin/com/oztechan/ccc/client/viewmodel/premium/util/PremiumUtil.kt index bf65f0483e..61f4f0da47 100644 --- a/client/core/shared/src/commonMain/kotlin/com/oztechan/ccc/client/core/shared/util/PremiumUtil.kt +++ b/client/viewmodel/premium/src/commonMain/kotlin/com/oztechan/ccc/client/viewmodel/premium/util/PremiumUtil.kt @@ -1,6 +1,8 @@ -package com.oztechan.ccc.client.core.shared.util +package com.oztechan.ccc.client.viewmodel.premium.util -import com.oztechan.ccc.client.core.shared.model.PremiumType +import com.oztechan.ccc.client.core.shared.util.nowAsLong +import com.oztechan.ccc.client.core.shared.util.toInstant +import com.oztechan.ccc.client.viewmodel.premium.model.PremiumType import kotlinx.datetime.DateTimeUnit import kotlinx.datetime.TimeZone import kotlinx.datetime.plus diff --git a/client/viewmodel/premium/src/commonTest/kotlin/com/oztechan/ccc/client/viewmodel/premium/PremiumViewModelTest.kt b/client/viewmodel/premium/src/commonTest/kotlin/com/oztechan/ccc/client/viewmodel/premium/PremiumViewModelTest.kt index 7e2263a87f..9522df4b21 100644 --- a/client/viewmodel/premium/src/commonTest/kotlin/com/oztechan/ccc/client/viewmodel/premium/PremiumViewModelTest.kt +++ b/client/viewmodel/premium/src/commonTest/kotlin/com/oztechan/ccc/client/viewmodel/premium/PremiumViewModelTest.kt @@ -6,12 +6,12 @@ package com.oztechan.ccc.client.viewmodel.premium import co.touchlab.kermit.CommonWriter import co.touchlab.kermit.Logger -import com.oztechan.ccc.client.core.shared.model.OldPurchase -import com.oztechan.ccc.client.core.shared.model.PremiumData -import com.oztechan.ccc.client.core.shared.model.PremiumType -import com.oztechan.ccc.client.core.shared.util.calculatePremiumEnd import com.oztechan.ccc.client.core.shared.util.nowAsLong import com.oztechan.ccc.client.storage.app.AppStorage +import com.oztechan.ccc.client.viewmodel.premium.model.OldPurchase +import com.oztechan.ccc.client.viewmodel.premium.model.PremiumData +import com.oztechan.ccc.client.viewmodel.premium.model.PremiumType +import com.oztechan.ccc.client.viewmodel.premium.util.calculatePremiumEnd import io.mockative.Mock import io.mockative.classOf import io.mockative.configure diff --git a/client/core/shared/src/commonTest/kotlin/com/oztechan/ccc/client/core/shared/model/PremiumTypeTest.kt b/client/viewmodel/premium/src/commonTest/kotlin/com/oztechan/ccc/client/viewmodel/premium/model/PremiumTypeTest.kt similarity index 88% rename from client/core/shared/src/commonTest/kotlin/com/oztechan/ccc/client/core/shared/model/PremiumTypeTest.kt rename to client/viewmodel/premium/src/commonTest/kotlin/com/oztechan/ccc/client/viewmodel/premium/model/PremiumTypeTest.kt index 34fa84435f..138f006467 100644 --- a/client/core/shared/src/commonTest/kotlin/com/oztechan/ccc/client/core/shared/model/PremiumTypeTest.kt +++ b/client/viewmodel/premium/src/commonTest/kotlin/com/oztechan/ccc/client/viewmodel/premium/model/PremiumTypeTest.kt @@ -1,4 +1,4 @@ -package com.oztechan.ccc.client.core.shared.model +package com.oztechan.ccc.client.viewmodel.premium.model import kotlin.test.Test import kotlin.test.assertEquals diff --git a/client/core/shared/src/commonTest/kotlin/com/oztechan/ccc/client/core/shared/util/PremiumUtilTest.kt b/client/viewmodel/premium/src/commonTest/kotlin/com/oztechan/ccc/client/viewmodel/premium/util/PremiumUtilTest.kt similarity index 87% rename from client/core/shared/src/commonTest/kotlin/com/oztechan/ccc/client/core/shared/util/PremiumUtilTest.kt rename to client/viewmodel/premium/src/commonTest/kotlin/com/oztechan/ccc/client/viewmodel/premium/util/PremiumUtilTest.kt index 41b30cbadd..556beca873 100644 --- a/client/core/shared/src/commonTest/kotlin/com/oztechan/ccc/client/core/shared/util/PremiumUtilTest.kt +++ b/client/viewmodel/premium/src/commonTest/kotlin/com/oztechan/ccc/client/viewmodel/premium/util/PremiumUtilTest.kt @@ -1,6 +1,8 @@ -package com.oztechan.ccc.client.core.shared.util +package com.oztechan.ccc.client.viewmodel.premium.util -import com.oztechan.ccc.client.core.shared.model.PremiumType +import com.oztechan.ccc.client.core.shared.util.nowAsLong +import com.oztechan.ccc.client.core.shared.util.toInstant +import com.oztechan.ccc.client.viewmodel.premium.model.PremiumType import kotlinx.datetime.DateTimePeriod import kotlinx.datetime.TimeZone import kotlinx.datetime.plus diff --git a/client/viewmodel/watchers/src/commonMain/kotlin/com/oztechan/ccc/client/viewmodel/watchers/WatchersSEED.kt b/client/viewmodel/watchers/src/commonMain/kotlin/com/oztechan/ccc/client/viewmodel/watchers/WatchersSEED.kt index 894c3bc6ca..c8ce55bd2b 100644 --- a/client/viewmodel/watchers/src/commonMain/kotlin/com/oztechan/ccc/client/viewmodel/watchers/WatchersSEED.kt +++ b/client/viewmodel/watchers/src/commonMain/kotlin/com/oztechan/ccc/client/viewmodel/watchers/WatchersSEED.kt @@ -14,7 +14,7 @@ sealed class WatchersEffect : BaseEffect() { object Back : WatchersEffect() data class SelectBase(val watcher: Watcher) : WatchersEffect() data class SelectTarget(val watcher: Watcher) : WatchersEffect() - object TooBigNumber : WatchersEffect() + object TooBigInput : WatchersEffect() object InvalidInput : WatchersEffect() object MaximumNumberOfWatchers : WatchersEffect() } diff --git a/client/viewmodel/watchers/src/commonMain/kotlin/com/oztechan/ccc/client/viewmodel/watchers/WatchersViewModel.kt b/client/viewmodel/watchers/src/commonMain/kotlin/com/oztechan/ccc/client/viewmodel/watchers/WatchersViewModel.kt index c059e1638b..1598829673 100644 --- a/client/viewmodel/watchers/src/commonMain/kotlin/com/oztechan/ccc/client/viewmodel/watchers/WatchersViewModel.kt +++ b/client/viewmodel/watchers/src/commonMain/kotlin/com/oztechan/ccc/client/viewmodel/watchers/WatchersViewModel.kt @@ -105,7 +105,7 @@ class WatchersViewModel( Logger.d { "WatcherViewModel onRateChange $watcher $rate" } return if (rate.length > MAXIMUM_INPUT) { - viewModelScope.launch { _effect.emit(WatchersEffect.TooBigNumber) } + viewModelScope.launch { _effect.emit(WatchersEffect.TooBigInput) } rate.dropLast(1) } else { rate.toSupportedCharacters().toStandardDigits().toDoubleOrNull()?.let { diff --git a/client/viewmodel/watchers/src/commonTest/kotlin/com/oztechan/ccc/client/viewmodel/watchers/WatchersViewModelTest.kt b/client/viewmodel/watchers/src/commonTest/kotlin/com/oztechan/ccc/client/viewmodel/watchers/WatchersViewModelTest.kt index f839e638f5..fc46d3f7f2 100644 --- a/client/viewmodel/watchers/src/commonTest/kotlin/com/oztechan/ccc/client/viewmodel/watchers/WatchersViewModelTest.kt +++ b/client/viewmodel/watchers/src/commonTest/kotlin/com/oztechan/ccc/client/viewmodel/watchers/WatchersViewModelTest.kt @@ -248,7 +248,7 @@ internal class WatchersViewModelTest { assertEquals(rate.dropLast(1), viewModel.event.onRateChange(watcher, rate)) }.firstOrNull().let { assertNotNull(it) - assertIs(it) + assertIs(it) } } } diff --git a/common/core/model/src/commonMain/kotlin/com/oztechan/ccc/common/core/model/CurrencyType.kt b/common/core/model/src/commonMain/kotlin/com/oztechan/ccc/common/core/model/CurrencyType.kt index 1d056e2030..d16321de55 100755 --- a/common/core/model/src/commonMain/kotlin/com/oztechan/ccc/common/core/model/CurrencyType.kt +++ b/common/core/model/src/commonMain/kotlin/com/oztechan/ccc/common/core/model/CurrencyType.kt @@ -3,7 +3,6 @@ */ package com.oztechan.ccc.common.core.model -@Suppress("LargeClass", "unused") enum class CurrencyType { AED, AFN, ALL, AMD, ANG, AOA, ARS, AUD, AWG, AZN, BAM, BBD, BDT, BGN, BHD, BIF, BMD, BND, BOB, BRL, BSD, BTC, BTN, BWP, BYN, BZD, CAD, CDF, CHF, CLF, CLP, CNH, CNY, COP, CRC, CUC, CUP, CVE, diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index a08f9bdace..708377dccd 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -4,44 +4,44 @@ kotlin = "1.8.10" ksp = "1.8.10-1.0.9" detekt = "1.22.0" androidGradlePlugin = "7.4.2" -composeCompiler = "1.4.3" -compose = "1.3.3" +composeCompiler = "1.4.4" +compose = "1.4.1" glance = "1.0.0-alpha05" material3 = "1.0.1" -androidDesugaring = "2.0.2" +androidDesugaring = "2.0.3" androidMaterial = "1.8.0" -composeActivity = "1.6.1" +composeActivity = "1.7.0" constraintLayout = "2.1.4" -koinCore = "3.3.3" -koinCompose = "3.4.2" -koinAndroid = "3.3.3" -koinKtor = "3.3.1" +koinCore = "3.4.0" +koinCompose = "3.4.3" +koinAndroid = "3.4.0" +koinKtor = "3.4.0" ktor = "2.2.4" multiplatformSettings = "1.0.0" -firebaseAnalytics = "21.2.0" -firebaseRemoteConfig = "21.2.1" +firebaseAnalytics = "21.2.1" +firebaseRemoteConfig = "21.3.0" gsm = "4.3.15" firebasePer = "20.3.1" firebasePerPlugin = "1.4.2" crashlytics = "2.9.4" -googleAds = "21.5.0" -huaweiAds = "3.4.61.303" +googleAds = "22.0.0" +huaweiAds = "3.4.61.304" huaweiOsm="1.3.35" navigation = "2.5.3" playCore = "1.10.3" kotlinXDateTime = "0.4.0" coroutines = "1.6.4" -billing = "5.1.0" +billing = "5.2.0" leakCanary = "2.10" sqlDelight = "1.5.5" -lifecycle = "2.6.0" -mokoResources = "0.20.1" +lifecycle = "2.6.1" +mokoResources = "0.21.1" buildKonfig = "0.13.3" splashScreen = "1.0.0" kover = "0.6.1" rootBeer = "0.1.0" -mockative = "1.3.1" -firebaseCrashlytics = "18.3.5" +mockative = "1.4.0" +firebaseCrashlytics = "18.3.6" anrWatchDog = "1.4.0" kermit = "1.2.2" diff --git a/ios/CCC/UI/Calculator/CalculatorView.swift b/ios/CCC/UI/Calculator/CalculatorView.swift index e34a37cc6d..db95fe458d 100644 --- a/ios/CCC/UI/Calculator/CalculatorView.swift +++ b/ios/CCC/UI/Calculator/CalculatorView.swift @@ -23,7 +23,8 @@ struct CalculatorView: View { @Environment(\.colorScheme) var colorScheme @EnvironmentObject private var navigationStack: NavigationStackCompat @State var isBarShown = false - @State var isTooBigNumberSnackShown = false + @State var isTooBigInputSnackShown = false + @State var isTooBigOutputSnackShown = false @State var isGenericErrorSnackShown = false @State var isFewCurrencySnackShown = false @State var isCopyClipboardSnackShown = false @@ -101,11 +102,18 @@ struct CalculatorView: View { } .navigationViewStyle(StackNavigationViewStyle()) .popup( - isPresented: $isTooBigNumberSnackShown, + isPresented: $isTooBigInputSnackShown, type: .toast, autohideIn: 2.0 ) { - SnackView(text: Res.strings().text_too_big_number.get()) + SnackView(text: Res.strings().text_too_big_input.get()) + } + .popup( + isPresented: $isTooBigOutputSnackShown, + type: .toast, + autohideIn: 2.0 + ) { + SnackView(text: Res.strings().text_too_big_output.get()) } .popup(isPresented: $isPasteRequestSnackShown, type: .toast, @@ -182,8 +190,10 @@ struct CalculatorView: View { isGenericErrorSnackShown.toggle() case is CalculatorEffect.FewCurrency: isFewCurrencySnackShown.toggle() - case is CalculatorEffect.TooBigNumber: - isTooBigNumberSnackShown.toggle() + case is CalculatorEffect.TooBigInput: + isTooBigInputSnackShown.toggle() + case is CalculatorEffect.TooBigOutput: + isTooBigOutputSnackShown.toggle() case is CalculatorEffect.OpenBar: isBarShown = true case is CalculatorEffect.OpenSettings: diff --git a/ios/CCC/UI/Watchers/WatchersView.swift b/ios/CCC/UI/Watchers/WatchersView.swift index 89eb30774f..658e6b3734 100644 --- a/ios/CCC/UI/Watchers/WatchersView.swift +++ b/ios/CCC/UI/Watchers/WatchersView.swift @@ -27,7 +27,7 @@ struct WatchersView: View { @State var targetBarInfo = BarInfo(isShown: false, watcher: nil) @State var isInvalidInputSnackShown = false @State var isMaxWatchersSnackShown = false - @State var isTooBigNumberSnackShown = false + @State var isTooBigInputSnackShown = false private let analyticsManager: AnalyticsManager = koin.get() @@ -127,11 +127,11 @@ struct WatchersView: View { SnackView(text: Res.strings().text_maximum_number_of_watchers.get()) } .popup( - isPresented: $isTooBigNumberSnackShown, + isPresented: $isTooBigInputSnackShown, type: .toast, autohideIn: 2.0 ) { - SnackView(text: Res.strings().text_too_big_number.get()) + SnackView(text: Res.strings().text_too_big_input.get()) } .sheet( isPresented: $baseBarInfo.isShown, @@ -190,8 +190,8 @@ struct WatchersView: View { case let selectTargetEffect as WatchersEffect.SelectTarget: targetBarInfo.watcher = selectTargetEffect.watcher targetBarInfo.isShown.toggle() - case is WatchersEffect.TooBigNumber: - isTooBigNumberSnackShown.toggle() + case is WatchersEffect.TooBigInput: + isTooBigInputSnackShown.toggle() case is WatchersEffect.InvalidInput: isInvalidInputSnackShown.toggle() case is WatchersEffect.MaximumNumberOfWatchers: diff --git a/ios/Gemfile.lock b/ios/Gemfile.lock index dfd873bff5..3bfdb0270d 100644 --- a/ios/Gemfile.lock +++ b/ios/Gemfile.lock @@ -3,13 +3,13 @@ GEM specs: CFPropertyList (3.0.6) rexml - addressable (2.8.1) + addressable (2.8.4) public_suffix (>= 2.0.2, < 6.0) artifactory (3.0.15) atomos (0.1.3) aws-eventstream (1.2.0) - aws-partitions (1.725.0) - aws-sdk-core (3.170.0) + aws-partitions (1.743.0) + aws-sdk-core (3.171.0) aws-eventstream (~> 1, >= 1.0.2) aws-partitions (~> 1, >= 1.651.0) aws-sigv4 (~> 1.5) @@ -17,7 +17,7 @@ GEM aws-sdk-kms (1.63.0) aws-sdk-core (~> 3, >= 3.165.0) aws-sigv4 (~> 1.1) - aws-sdk-s3 (1.119.1) + aws-sdk-s3 (1.120.1) aws-sdk-core (~> 3, >= 3.165.0) aws-sdk-kms (~> 1) aws-sigv4 (~> 1.4) @@ -107,7 +107,7 @@ GEM xcpretty-travis-formatter (>= 0.0.3) fastlane-plugin-firebase_app_distribution (0.5.0) gh_inspector (1.1.3) - google-apis-androidpublisher_v3 (0.36.0) + google-apis-androidpublisher_v3 (0.38.0) google-apis-core (>= 0.11.0, < 2.a) google-apis-core (0.11.0) addressable (~> 2.5, >= 2.5.1) @@ -138,7 +138,7 @@ GEM google-cloud-core (~> 1.6) googleauth (>= 0.16.2, < 2.a) mini_mime (~> 1.0) - googleauth (1.3.0) + googleauth (1.5.0) faraday (>= 0.17.3, < 3.a) jwt (>= 1.4, < 3.0) memoist (~> 0.16) diff --git a/ios/provider/src/iosMain/kotlin/com/oztechan/ccc/ios/provider/di/Koin.kt b/ios/provider/src/iosMain/kotlin/com/oztechan/ccc/ios/provider/di/Koin.kt index 5e9428bfa0..d97dd73ecd 100644 --- a/ios/provider/src/iosMain/kotlin/com/oztechan/ccc/ios/provider/di/Koin.kt +++ b/ios/provider/src/iosMain/kotlin/com/oztechan/ccc/ios/provider/di/Koin.kt @@ -2,8 +2,6 @@ * Copyright (c) 2021 Mustafa Ozhan. All rights reserved. */ -@file:Suppress("unused") - package com.oztechan.ccc.ios.provider.di import co.touchlab.kermit.Logger @@ -44,6 +42,7 @@ import org.koin.core.parameter.parametersOf import org.koin.dsl.module import platform.Foundation.NSUserDefaults +@Suppress("unused") fun initKoin( userDefaults: NSUserDefaults, analyticsManager: AnalyticsManager @@ -106,6 +105,7 @@ private fun getIOSPlatformModule(userDefaults: NSUserDefaults) = module { single { Device.IOS } } +@Suppress("unused") fun Koin.getDependency(objCObject: ObjCObject): T = when (objCObject) { is ObjCClass -> getOriginalKotlinClass(objCObject) is ObjCProtocol -> getOriginalKotlinClass(objCObject) diff --git a/settings.gradle.kts b/settings.gradle.kts index a1714f76f9..583fc2589f 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -10,7 +10,7 @@ pluginManagement { } } plugins { - id("com.gradle.enterprise") version ("3.12.3") + id("com.gradle.enterprise") version ("3.12.6") } gradleEnterprise { diff --git a/submodule/basemob b/submodule/basemob index 9d52a02078..3acb8668bc 160000 --- a/submodule/basemob +++ b/submodule/basemob @@ -1 +1 @@ -Subproject commit 9d52a02078d72965330dd88b25cfb20b5fbd3b35 +Subproject commit 3acb8668bc0e8954e6bc69a6481ef0ba2868a4c8 diff --git a/submodule/logmob b/submodule/logmob index 661d66fa17..e73d356e64 160000 --- a/submodule/logmob +++ b/submodule/logmob @@ -1 +1 @@ -Subproject commit 661d66fa17a49bafa3bee3190b64255b6c28c442 +Subproject commit e73d356e64e27f419b254001184d44b96e239dd0 diff --git a/submodule/parsermob b/submodule/parsermob index 13d2f13bab..7fae5c915f 160000 --- a/submodule/parsermob +++ b/submodule/parsermob @@ -1 +1 @@ -Subproject commit 13d2f13babc723a21feeda004a50ab613db08085 +Subproject commit 7fae5c915f966107ad32f5077327007f5a2b56e8 diff --git a/submodule/scopemob b/submodule/scopemob index ff09a48d14..72de3ee69a 160000 --- a/submodule/scopemob +++ b/submodule/scopemob @@ -1 +1 @@ -Subproject commit ff09a48d1479b0dee0c463cb156a5df43c4a5a8e +Subproject commit 72de3ee69abbac182bff06129668b203298dfb6b