diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 370cb5ab85..c43873232c 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1 +1 @@ -* @Oztechan/core-team +* @Oztechan/core-team @mustafaozhan diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 7703c71938..45c2895e4d 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -8,7 +8,7 @@ on: concurrency: group: ${{ github.ref }} - cancel-in-progress: true + cancel-in-progress: ${{ !contains(github.ref, 'develop')}} env: BASE_URL_BACKEND: ${{ secrets.BASE_URL_BACKEND }} @@ -90,9 +90,12 @@ jobs: distribution: 'temurin' - name: Assemble - uses: gradle/gradle-build-action@v3.1.0 + uses: gradle/actions/setup-gradle@v3.3.0 with: arguments: assemble + build-scan-publish: true + build-scan-terms-of-use-url: "https://gradle.com/help/legal-terms-of-use" + build-scan-terms-of-use-agree: "yes" - name: Upload Android Artifacts uses: actions/upload-artifact@v4.3.1 @@ -112,7 +115,7 @@ jobs: run: echo "status=success" >> $GITHUB_OUTPUT DistributeAndroid: - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 needs: [ GradleBuild ] if: github.event_name == 'push' outputs: @@ -123,7 +126,7 @@ jobs: uses: actions/checkout@v4.1.1 - name: Download Android Artifacts - uses: actions/download-artifact@v4.1.4 + uses: actions/download-artifact@v4.1.5 with: name: androidArtifacts @@ -212,7 +215,7 @@ jobs: run: echo "status=success" >> $GITHUB_OUTPUT DistributeIOS: - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 needs: [ XCodeBuild ] if: github.event_name == 'push' outputs: @@ -222,7 +225,7 @@ jobs: uses: actions/checkout@v4.1.1 - name: Download iOS IPA - uses: actions/download-artifact@v4.1.4 + uses: actions/download-artifact@v4.1.5 with: name: iOSArtifacts path: ios @@ -262,9 +265,12 @@ jobs: distribution: 'temurin' - name: Run Quality Jobs - uses: gradle/gradle-build-action@v3.1.0 + uses: gradle/actions/setup-gradle@v3.3.0 with: arguments: check koverMergedXmlReport --parallel + build-scan-publish: true + build-scan-terms-of-use-url: "https://gradle.com/help/legal-terms-of-use" + build-scan-terms-of-use-agree: "yes" - name: Upload Coverage Report uses: actions/upload-artifact@v4.3.1 @@ -281,7 +287,7 @@ jobs: run: echo "status=success" >> $GITHUB_OUTPUT UploadQualityReports: - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 needs: [ Quality ] outputs: status: ${{ steps.status.outputs.status }} @@ -290,13 +296,13 @@ jobs: uses: actions/checkout@v4.1.1 - name: Download Coverage Report - uses: actions/download-artifact@v4.1.4 + uses: actions/download-artifact@v4.1.5 with: name: coverageReport path: build - name: Upload coverage to Codecov - uses: codecov/codecov-action@v4.1.0 + uses: codecov/codecov-action@v4.3.0 with: token: ${{ secrets.CODECOV_TOKEN }} files: build/report.xml @@ -329,7 +335,7 @@ jobs: run: echo "status=success" >> $GITHUB_OUTPUT CodeAnalysis: - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 outputs: status: ${{ steps.status.outputs.status }} steps: @@ -346,9 +352,12 @@ jobs: distribution: 'temurin' - name: Detekt - uses: gradle/gradle-build-action@v3.1.0 + uses: gradle/actions/setup-gradle@v3.3.0 with: arguments: detektAll + build-scan-publish: true + build-scan-terms-of-use-url: "https://gradle.com/help/legal-terms-of-use" + build-scan-terms-of-use-agree: "yes" - name: SwiftLint uses: norio-nomura/action-swiftlint@3.2.1 @@ -366,7 +375,7 @@ jobs: run: echo "status=success" >> $GITHUB_OUTPUT Notify: - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 needs: [ GradleBuild, XCodeBuild, Quality, CodeAnalysis, DistributeAndroid, DistributeIOS, UploadQualityReports ] if: always() steps: diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 3e1a458370..0a53f5f4f3 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -54,7 +54,7 @@ env: jobs: GenerateGradleArtifacts: - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 outputs: status: ${{ steps.status.outputs.status }} steps: @@ -85,9 +85,12 @@ jobs: distribution: 'temurin' - name: Generate Artifacts - uses: gradle/gradle-build-action@v3.1.0 + uses: gradle/actions/setup-gradle@v3.3.0 with: arguments: :android:app:bundleRelease :backend:app:jar --parallel + build-scan-publish: true + build-scan-terms-of-use-url: "https://gradle.com/help/legal-terms-of-use" + build-scan-terms-of-use-agree: "yes" - name: Upload Google App Bundle uses: actions/upload-artifact@v4.3.1 @@ -112,14 +115,14 @@ jobs: run: echo "status=success" >> $GITHUB_OUTPUT UploadToGooglePlay: - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 needs: [ GenerateGradleArtifacts ] outputs: status: ${{ steps.status.outputs.status }} steps: - name: Download App Bundle - uses: actions/download-artifact@v4.1.4 + uses: actions/download-artifact@v4.1.5 with: name: googleBundle @@ -148,14 +151,14 @@ jobs: run: echo "status=success" >> $GITHUB_OUTPUT UploadToHuaweiAppGallery: - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 needs: [ GenerateGradleArtifacts ] outputs: status: ${{ steps.status.outputs.status }} steps: - name: Download App Bundle - uses: actions/download-artifact@v4.1.4 + uses: actions/download-artifact@v4.1.5 with: name: huaweiBundle @@ -180,14 +183,14 @@ jobs: run: echo "status=success" >> $GITHUB_OUTPUT DeployToServer: - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 needs: [ GenerateGradleArtifacts ] outputs: status: ${{ steps.status.outputs.status }} steps: - name: Download Backend Jar - uses: actions/download-artifact@v4.1.4 + uses: actions/download-artifact@v4.1.5 with: name: backendJar path: artifact @@ -246,7 +249,7 @@ jobs: run: echo "status=success" >> $GITHUB_OUTPUT Notify: - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 needs: [ GenerateGradleArtifacts, UploadToGooglePlay, UploadToHuaweiAppGallery, DeployToServer, UploadToAppStore ] if: always() steps: diff --git a/android/app/src/googleDebug/res/values/strings.xml b/android/app/src/googleDebug/res/values/strings.xml new file mode 100644 index 0000000000..d6894d603b --- /dev/null +++ b/android/app/src/googleDebug/res/values/strings.xml @@ -0,0 +1,4 @@ + + + CCC_G + diff --git a/android/app/src/huaweiDebug/res/values/strings.xml b/android/app/src/huaweiDebug/res/values/strings.xml new file mode 100644 index 0000000000..8906136e9c --- /dev/null +++ b/android/app/src/huaweiDebug/res/values/strings.xml @@ -0,0 +1,4 @@ + + + CCC_H + 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 49a085549f..2fb5fa644a 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 @@ -10,10 +10,9 @@ import android.os.StrictMode.ThreadPolicy import android.os.StrictMode.VmPolicy import co.touchlab.kermit.Logger import com.github.submob.logmob.ANRWatchDogHandler -import com.github.submob.logmob.initCrashlytics import com.github.submob.logmob.initLogger +import com.github.submob.logmob.setCrashlyticsCollection import com.oztechan.ccc.android.app.di.initKoin -import com.oztechan.ccc.android.core.ad.initAds import com.oztechan.ccc.client.core.analytics.initAnalytics class Application : Application() { @@ -21,8 +20,9 @@ class Application : Application() { override fun onCreate() { super.onCreate() + setCrashlyticsCollection(!BuildConfig.DEBUG) + if (!BuildConfig.DEBUG) { - initCrashlytics() initAnalytics(this) } @@ -38,8 +38,6 @@ class Application : Application() { Thread.setDefaultUncaughtExceptionHandler(ANRWatchDogHandler()) } - initAds(this) - initKoin(this) } diff --git a/android/core/ad/android-core-ad.gradle.kts b/android/core/ad/android-core-ad.gradle.kts index c6e48689a4..b6c876cb6d 100644 --- a/android/core/ad/android-core-ad.gradle.kts +++ b/android/core/ad/android-core-ad.gradle.kts @@ -72,6 +72,7 @@ dependencies { libs.android.apply { google.apply { DeviceFlavour.GOOGLE.implementation(googleAds) + DeviceFlavour.GOOGLE.implementation(ump) } huawei.apply { DeviceFlavour.HUAWEI.implementation(huaweiAds) diff --git a/android/core/ad/src/google/kotlin/com/oztechan/ccc/android/core/ad/AdManagerImpl.kt b/android/core/ad/src/google/kotlin/com/oztechan/ccc/android/core/ad/AdManagerImpl.kt index 7b52822cc2..9537b3277a 100644 --- a/android/core/ad/src/google/kotlin/com/oztechan/ccc/android/core/ad/AdManagerImpl.kt +++ b/android/core/ad/src/google/kotlin/com/oztechan/ccc/android/core/ad/AdManagerImpl.kt @@ -13,8 +13,17 @@ import com.google.android.gms.ads.interstitial.InterstitialAd import com.google.android.gms.ads.interstitial.InterstitialAdLoadCallback import com.google.android.gms.ads.rewarded.RewardedAd import com.google.android.gms.ads.rewarded.RewardedAdLoadCallback +import com.google.android.ump.ConsentInformation +import com.google.android.ump.ConsentRequestParameters +import com.google.android.ump.UserMessagingPlatform +import java.util.concurrent.atomic.AtomicBoolean -internal class AdManagerImpl : AdManager { +internal class AdManagerImpl(context: Context) : AdManager { + // Use an atomic boolean to initialize the Google Mobile Ads SDK and load ads once. + private val isMobileAdsInitializeCalled = AtomicBoolean(false) + + private val consentInformation: ConsentInformation = + UserMessagingPlatform.getConsentInformation(context) private val adRequest: AdRequest by lazy { AdRequest.Builder().build() @@ -22,8 +31,46 @@ internal class AdManagerImpl : AdManager { init { Logger.v { "AdManagerImpl init" } - MobileAds.setAppVolume(0.0f) - MobileAds.setAppMuted(true) + } + + override fun initAds(activity: Activity) { + Logger.v { "AdManagerImpl initAds" } + consentInformation.requestConsentInfoUpdate( + activity, + ConsentRequestParameters.Builder().build(), + { + UserMessagingPlatform.loadAndShowConsentFormIfRequired(activity) { + if (it != null) { + Logger.e { "Consent gathering failed: ${it.errorCode}: ${it.message}" } + } + + // Consent has been gathered. + if (consentInformation.canRequestAds()) { + activity.initializeMobileAdsSdk() + } + } + }, + { Logger.e { "Consent gathering failed: ${it.errorCode}: ${it.message}" } } + ) + + // Check if you can initialize the Google Mobile Ads SDK in parallel + // while checking for new consent information. Consent obtained in + // the previous session can be used to request ads. + if (consentInformation.canRequestAds()) { + activity.initializeMobileAdsSdk() + } + } + + override fun isPrivacyOptionsRequired() = + consentInformation.privacyOptionsRequirementStatus == + ConsentInformation.PrivacyOptionsRequirementStatus.REQUIRED + + override fun showConsentForm(activity: Activity) { + UserMessagingPlatform.showPrivacyOptionsForm(activity) { + if (it != null) { + Logger.e { "Showing consent form failed: ${it.errorCode}: ${it.message}" } + } + } } override fun getBannerAd( @@ -115,4 +162,17 @@ internal class AdManagerImpl : AdManager { } ) } + + private fun Activity.initializeMobileAdsSdk() { + Logger.v { "AdManagerImpl initializeMobileAdsSdk" } + + if (isMobileAdsInitializeCalled.getAndSet(true)) { + Logger.v { "AdManagerImpl initializeMobileAdsSdk is not called, already called" } + return + } + + MobileAds.initialize(this) + MobileAds.setAppVolume(0.0f) + MobileAds.setAppMuted(true) + } } diff --git a/android/core/ad/src/google/kotlin/com/oztechan/ccc/android/core/ad/Ads.kt b/android/core/ad/src/google/kotlin/com/oztechan/ccc/android/core/ad/Ads.kt deleted file mode 100644 index d1e3fd55ed..0000000000 --- a/android/core/ad/src/google/kotlin/com/oztechan/ccc/android/core/ad/Ads.kt +++ /dev/null @@ -1,10 +0,0 @@ -package com.oztechan.ccc.android.core.ad - -import android.content.Context -import co.touchlab.kermit.Logger -import com.google.android.gms.ads.MobileAds - -fun initAds(context: Context) { - Logger.v { "Ads initAds" } - MobileAds.initialize(context) -} diff --git a/android/core/ad/src/huawei/kotlin/com/oztechan/ccc/android/core/ad/AdManagerImpl.kt b/android/core/ad/src/huawei/kotlin/com/oztechan/ccc/android/core/ad/AdManagerImpl.kt index b313dfd167..e3d97bdd18 100644 --- a/android/core/ad/src/huawei/kotlin/com/oztechan/ccc/android/core/ad/AdManagerImpl.kt +++ b/android/core/ad/src/huawei/kotlin/com/oztechan/ccc/android/core/ad/AdManagerImpl.kt @@ -22,10 +22,19 @@ internal class AdManagerImpl : AdManager { init { Logger.v { "AdManagerImpl init" } + } + + override fun initAds(activity: Activity) { + Logger.v { "Ads initAds" } + HwAds.init(activity) HwAds.setVideoVolume(0f) HwAds.setVideoMuted(true) } + override fun isPrivacyOptionsRequired() = false + + override fun showConsentForm(activity: Activity) = Unit + override fun getBannerAd( context: Context, width: Int, diff --git a/android/core/ad/src/huawei/kotlin/com/oztechan/ccc/android/core/ad/Ads.kt b/android/core/ad/src/huawei/kotlin/com/oztechan/ccc/android/core/ad/Ads.kt deleted file mode 100644 index 82efeab88d..0000000000 --- a/android/core/ad/src/huawei/kotlin/com/oztechan/ccc/android/core/ad/Ads.kt +++ /dev/null @@ -1,10 +0,0 @@ -package com.oztechan.ccc.android.core.ad - -import android.content.Context -import co.touchlab.kermit.Logger -import com.huawei.hms.ads.HwAds - -fun initAds(context: Context) { - Logger.v { "Ads initAds" } - HwAds.init(context) -} diff --git a/android/core/ad/src/main/kotlin/com/oztechan/ccc/android/core/ad/AdManager.kt b/android/core/ad/src/main/kotlin/com/oztechan/ccc/android/core/ad/AdManager.kt index 96769b31f6..074490f01c 100644 --- a/android/core/ad/src/main/kotlin/com/oztechan/ccc/android/core/ad/AdManager.kt +++ b/android/core/ad/src/main/kotlin/com/oztechan/ccc/android/core/ad/AdManager.kt @@ -5,6 +5,12 @@ import android.content.Context interface AdManager { + fun initAds(activity: Activity) + + fun isPrivacyOptionsRequired(): Boolean + + fun showConsentForm(activity: Activity) + fun getBannerAd( context: Context, width: Int, diff --git a/android/ui/mobile/android-ui-mobile.gradle.kts b/android/ui/mobile/android-ui-mobile.gradle.kts index 3d1ebfe5a1..7b5f6dc3cd 100644 --- a/android/ui/mobile/android-ui-mobile.gradle.kts +++ b/android/ui/mobile/android-ui-mobile.gradle.kts @@ -66,6 +66,10 @@ dependencies { implementation(koinCompose) implementation(lifecycleRuntime) implementation(splashScreen) + + // todo can be removed when SearchView is removed: https://github.com/Oztechan/CCC/issues/3272 + implementation(appCompat) + implementation(appCompatResources) } android.google.apply { diff --git a/android/ui/mobile/src/main/kotlin/com/oztechan/ccc/android/ui/mobile/content/main/MainActivity.kt b/android/ui/mobile/src/main/kotlin/com/oztechan/ccc/android/ui/mobile/content/main/MainActivity.kt index 37acae6c32..9282e2c1a1 100755 --- a/android/ui/mobile/src/main/kotlin/com/oztechan/ccc/android/ui/mobile/content/main/MainActivity.kt +++ b/android/ui/mobile/src/main/kotlin/com/oztechan/ccc/android/ui/mobile/content/main/MainActivity.kt @@ -19,6 +19,7 @@ import com.oztechan.ccc.android.ui.mobile.BuildConfig import com.oztechan.ccc.android.ui.mobile.R import com.oztechan.ccc.android.ui.mobile.util.getThemeMode import com.oztechan.ccc.android.ui.mobile.util.requestAppReview +import com.oztechan.ccc.android.ui.mobile.util.resolveAndStartIntent import com.oztechan.ccc.android.ui.mobile.util.showDialog import com.oztechan.ccc.android.ui.mobile.util.updateBaseContextLocale import com.oztechan.ccc.client.viewmodel.main.MainEffect @@ -46,6 +47,7 @@ class MainActivity : BaseActivity() { super.onCreate(savedInstanceState) Logger.i { "MainActivity onCreate" } setContentView(R.layout.activity_main) + adManager.initAds(this) observeStates() observeEffects() } @@ -91,7 +93,7 @@ class MainActivity : BaseActivity() { positiveButton = R.string.update, cancelable = isCancelable ) { - startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(marketLink))) + resolveAndStartIntent(Intent(Intent.ACTION_VIEW, Uri.parse(marketLink))) } private fun setDestination(fragmentId: Int) = with(getNavigationController()) { 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 97a6522008..14a03309f2 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 @@ -16,6 +16,7 @@ import com.oztechan.ccc.android.core.billing.BillingManager import com.oztechan.ccc.android.ui.mobile.BuildConfig import com.oztechan.ccc.android.ui.mobile.R import com.oztechan.ccc.android.ui.mobile.databinding.BottomSheetPremiumBinding +import com.oztechan.ccc.android.ui.mobile.util.resolveAndStartIntent import com.oztechan.ccc.android.ui.mobile.util.showDialog import com.oztechan.ccc.android.ui.mobile.util.showSnack import com.oztechan.ccc.android.ui.mobile.util.toOldPurchaseList @@ -155,6 +156,6 @@ class PremiumBottomSheet : BaseVBBottomSheetDialogFragment() { with(itemDisableAds) { imgSettingsItem.setBackgroundResource(R.drawable.ic_premium) settingsItemTitle.text = getString(R.string.settings_item_premium_title) - settingsItemSubTitle.text = getString(R.string.settings_item_premium_sub_title_no_ads_and_widget) + settingsItemSubTitle.text = + getString(R.string.settings_item_premium_sub_title_no_ads_and_widget) } with(itemPrecision) { @@ -131,6 +133,12 @@ class SettingsFragment : BaseVBFragment() { settingsItemTitle.text = getString(R.string.settings_item_on_github_title) settingsItemSubTitle.text = getString(R.string.settings_item_on_github_sub_title) } + with(itemPrivacySettings) { + root.visibleIf(adManager.isPrivacyOptionsRequired()) + imgSettingsItem.setBackgroundResource(R.drawable.ic_privacy_settings) + settingsItemTitle.text = getString(R.string.settings_item_privacy_settings_title) + settingsItemSubTitle.text = getString(R.string.settings_item_privacy_settings_sub_title) + } with(itemVersion) { imgSettingsItem.setBackgroundResource(R.drawable.ic_version) settingsItemTitle.text = getString(R.string.settings_item_version_title) @@ -151,8 +159,15 @@ class SettingsFragment : BaseVBFragment() { itemDisableAds.settingsItemValue.text = when (val state = it.premiumStatus) { PremiumStatus.NeverActivated -> "" - is PremiumStatus.Active -> getString(R.string.settings_item_premium_value_will_expire, state.until) - is PremiumStatus.Expired -> getString(R.string.settings_item_premium_value_expired, state.at) + is PremiumStatus.Active -> getString( + R.string.settings_item_premium_value_will_expire, + state.until + ) + + is PremiumStatus.Expired -> getString( + R.string.settings_item_premium_value_expired, + state.at + ) } itemPrecision.settingsItemValue.text = requireContext().getString( @@ -185,10 +200,17 @@ class SettingsFragment : BaseVBFragment() { R.string.rate_and_support, R.string.rate ) { - startIntent(Intent(Intent.ACTION_VIEW, Uri.parse(viewEffect.marketLink))) + requireContext().resolveAndStartIntent( + Intent( + Intent.ACTION_VIEW, + Uri.parse(viewEffect.marketLink) + ) + ) } - SettingsEffect.OnGitHub -> startIntent( + SettingsEffect.PrivacySettings -> adManager.showConsentForm(requireActivity()) + + SettingsEffect.OnGitHub -> requireContext().resolveAndStartIntent( Intent( Intent.ACTION_VIEW, Uri.parse(getString(R.string.github_url)) @@ -210,7 +232,12 @@ class SettingsFragment : BaseVBFragment() { SettingsEffect.OnlyOneTimeSync -> view?.showSnack(R.string.txt_already_synced) SettingsEffect.AlreadyPremium -> view?.showSnack(R.string.txt_you_already_have_premium) SettingsEffect.SelectPrecision -> showPrecisionDialog() - SettingsEffect.OpenWatchers -> startActivity(Intent(context, ComposeMainActivity::class.java)) + SettingsEffect.OpenWatchers -> startActivity( + Intent( + context, + ComposeMainActivity::class.java + ) + ) } }.launchIn(viewLifecycleOwner.lifecycleScope) @@ -226,6 +253,7 @@ class SettingsFragment : BaseVBFragment() { itemFeedback.root.setOnClickListener { onFeedBackClick() } itemShare.root.setOnClickListener { onShareClick() } itemOnGithub.root.setOnClickListener { onOnGitHubClick() } + itemPrivacySettings.root.setOnClickListener { onPrivacySettingsClick() } itemPrecision.root.setOnClickListener { onPrecisionClick() } } @@ -258,10 +286,6 @@ class SettingsFragment : BaseVBFragment() { settingsViewModel.event.onPrecisionSelect(it) } - private fun startIntent(intent: Intent) = getBaseActivity()?.packageManager?.let { - intent.resolveActivity(it)?.let { startActivity(intent) } - } - private fun share(marketLink: String) = Intent(Intent.ACTION_SEND).apply { type = TEXT_TYPE putExtra(Intent.EXTRA_TEXT, marketLink) diff --git a/android/ui/mobile/src/main/kotlin/com/oztechan/ccc/android/ui/mobile/util/IntentUtil.kt b/android/ui/mobile/src/main/kotlin/com/oztechan/ccc/android/ui/mobile/util/IntentUtil.kt new file mode 100644 index 0000000000..55b62dacca --- /dev/null +++ b/android/ui/mobile/src/main/kotlin/com/oztechan/ccc/android/ui/mobile/util/IntentUtil.kt @@ -0,0 +1,11 @@ +package com.oztechan.ccc.android.ui.mobile.util + +import android.content.Context +import android.content.Intent +import co.touchlab.kermit.Logger + +fun Context.resolveAndStartIntent(intent: Intent) { + intent.resolveActivity(packageManager)?.let { + startActivity(intent) + } ?: Logger.w { "No activity found to handle the intent: $intent" } +} diff --git a/android/ui/mobile/src/main/res/drawable/ic_privacy_settings.xml b/android/ui/mobile/src/main/res/drawable/ic_privacy_settings.xml new file mode 100644 index 0000000000..dca83eb70f --- /dev/null +++ b/android/ui/mobile/src/main/res/drawable/ic_privacy_settings.xml @@ -0,0 +1,12 @@ + + + + + diff --git a/android/ui/mobile/src/main/res/layout/fragment_currencies.xml b/android/ui/mobile/src/main/res/layout/fragment_currencies.xml index b4efa72ec1..b95a692602 100755 --- a/android/ui/mobile/src/main/res/layout/fragment_currencies.xml +++ b/android/ui/mobile/src/main/res/layout/fragment_currencies.xml @@ -35,6 +35,7 @@ android:id="@+id/txt_select_currencies" style="@style/SelectYourCurrenciesStyle" app:layout_constraintBottom_toTopOf="@+id/ad_view_container" + app:layout_constraintEnd_toStartOf="@+id/btn_done" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/recycler_view_currencies" /> diff --git a/android/ui/mobile/src/main/res/layout/fragment_settings.xml b/android/ui/mobile/src/main/res/layout/fragment_settings.xml index 5a7d905c25..104e76fb56 100644 --- a/android/ui/mobile/src/main/res/layout/fragment_settings.xml +++ b/android/ui/mobile/src/main/res/layout/fragment_settings.xml @@ -123,12 +123,20 @@ app:layout_constraintTop_toBottomOf="@+id/item_share" /> + + diff --git a/android/ui/mobile/src/main/res/layout/item_currencies.xml b/android/ui/mobile/src/main/res/layout/item_currencies.xml index 31ab6e79ed..e210d3f676 100755 --- a/android/ui/mobile/src/main/res/layout/item_currencies.xml +++ b/android/ui/mobile/src/main/res/layout/item_currencies.xml @@ -4,7 +4,7 @@ + style="@style/CurrenciesItemLayout"> + app:layout_constraintStart_toEndOf="@+id/img_settings_item" + app:layout_constraintTop_toBottomOf="@+id/settings_item_title" /> - 11sp + 11sp + 12sp 15sp 17sp - 19sp + 20sp 22sp 25sp @@ -42,7 +43,7 @@ 48dp 200dp 20dp - 36dp + 36dp 256dp diff --git a/android/ui/mobile/src/main/res/values/styles.xml b/android/ui/mobile/src/main/res/values/styles.xml index 0abf7fe06d..1226a3bdb0 100755 --- a/android/ui/mobile/src/main/res/values/styles.xml +++ b/android/ui/mobile/src/main/res/values/styles.xml @@ -109,7 +109,6 @@ @@ -174,7 +173,7 @@ @@ -251,6 +250,8 @@ match_parent wrap_content @dimen/margin_eight + @dimen/margin_two + @dimen/margin_two @@ -294,7 +295,7 @@ @dimen/fit center @style/BaseTextStyle - @dimen/margin_eight + @dimen/margin_twelve @color/background @@ -317,6 +318,8 @@ @@ -324,7 +327,7 @@ @@ -337,24 +340,37 @@ @drawable/ic_search - + + - + + @@ -426,6 +435,7 @@ match_parent @dimen/height_settings_item_layout @drawable/selector_item + @dimen/margin_four true true @@ -454,14 +464,13 @@ @@ -487,9 +496,7 @@ @dimen/margin_two @dimen/margin_two @dimen/margin_four - @dimen/margin_four @dimen/margin_eight - @dimen/margin_eight end 1 @style/TextStyleSmall @@ -498,7 +505,6 @@