Merge remote-tracking branch 'origin/develop' into develop

# Conflicts:
#	app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/DashboardPresenter.kt
This commit is contained in:
sadorowo 2024-01-03 19:25:38 +01:00
commit ee906d02ae
31 changed files with 258 additions and 431 deletions

View File

@ -27,8 +27,8 @@ android {
testApplicationId "io.github.tests.wulkanowy"
minSdkVersion 21
targetSdkVersion 34
versionCode 138
versionName "2.2.6"
versionCode 140
versionName "2.3.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
resValue "string", "app_name", "Wulkanowy"
@ -113,6 +113,7 @@ android {
buildFeatures {
viewBinding true
buildConfig true
}
bundle {
@ -162,8 +163,8 @@ play {
defaultToAppBundles = false
track = 'production'
releaseStatus = ReleaseStatus.IN_PROGRESS
userFraction = 0.99d
updatePriority = 5
userFraction = 0.15d
updatePriority = 3
enabled.set(false)
}
@ -184,16 +185,16 @@ huaweiPublish {
}
ext {
work_manager = "2.8.1"
work_manager = "2.9.0"
android_hilt = "1.1.0"
room = "2.6.1"
chucker = "3.5.2"
chucker = "4.0.0"
mockk = "1.13.8"
coroutines = "1.7.3"
}
dependencies {
implementation 'io.github.wulkanowy:sdk:2.2.7-SNAPSHOT'
implementation 'io.github.wulkanowy:sdk:2.3.1'
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.0.4'
@ -202,10 +203,10 @@ dependencies {
implementation 'androidx.core:core-ktx:1.12.0'
implementation 'androidx.core:core-splashscreen:1.0.1'
implementation "androidx.activity:activity-ktx:1.8.1"
implementation "androidx.activity:activity-ktx:1.8.2"
implementation "androidx.appcompat:appcompat:1.6.1"
implementation "androidx.fragment:fragment-ktx:1.6.2"
implementation "androidx.annotation:annotation:1.7.0"
implementation "androidx.annotation:annotation:1.7.1"
implementation "androidx.preference:preference-ktx:1.2.1"
implementation "androidx.recyclerview:recyclerview:1.3.2"
@ -218,7 +219,7 @@ dependencies {
implementation "com.github.PhilJay:MPAndroidChart:v3.1.0"
implementation 'com.github.lopspower:CircularImageView:4.3.0'
implementation "androidx.work:work-runtime-ktx:$work_manager"
implementation "androidx.work:work-runtime:$work_manager"
playImplementation "androidx.work:work-gcm:$work_manager"
implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.6.2"
@ -249,22 +250,24 @@ dependencies {
implementation 'com.fredporciuncula:flow-preferences:1.9.1'
implementation 'org.apache.commons:commons-text:1.11.0'
playImplementation platform('com.google.firebase:firebase-bom:32.6.0')
playImplementation 'com.google.firebase:firebase-analytics-ktx'
playImplementation 'com.google.firebase:firebase-messaging:'
playImplementation platform('com.google.firebase:firebase-bom:32.7.0')
playImplementation 'com.google.firebase:firebase-analytics'
playImplementation 'com.google.firebase:firebase-messaging'
playImplementation 'com.google.firebase:firebase-crashlytics:'
playImplementation 'com.google.firebase:firebase-config-ktx'
playImplementation 'com.google.android.gms:play-services-ads:22.5.0'
playImplementation 'com.google.firebase:firebase-config'
playImplementation 'com.google.android.gms:play-services-ads:22.6.0'
playImplementation "com.google.android.play:integrity:1.3.0"
playImplementation 'com.google.android.play:app-update-ktx:2.1.0'
playImplementation 'com.google.android.play:review-ktx:2.0.1'
playImplementation "com.google.android.ump:user-messaging-platform:2.1.0"
hmsImplementation 'com.huawei.hms:hianalytics:6.12.0.300'
hmsImplementation 'com.huawei.agconnect:agconnect-crash:1.9.1.301'
hmsImplementation 'com.huawei.agconnect:agconnect-crash:1.9.1.303'
releaseImplementation "com.github.ChuckerTeam.Chucker:library-no-op:$chucker"
releaseImplementation "com.github.chuckerteam.chucker:library-no-op:$chucker"
debugImplementation "com.github.ChuckerTeam.Chucker:library:$chucker"
debugImplementation "com.github.chuckerteam.chucker:library:$chucker"
debugImplementation 'com.github.amitshekhariitbhu.Android-Debug-Database:debug-db:1.0.6'
debugImplementation 'com.github.haroldadmin:WhatTheStack:1.0.0-alpha04'

View File

@ -1,7 +1,7 @@
apply plugin: "jacoco"
jacoco {
toolVersion "0.8.10"
toolVersion "0.8.11"
reportsDirectory.set(file("$buildDir/reports"))
}

View File

@ -5,6 +5,7 @@ import android.view.View
import dagger.hilt.android.qualifiers.ApplicationContext
import io.github.wulkanowy.data.repositories.PreferencesRepository
import io.github.wulkanowy.ui.modules.dashboard.DashboardItem
import kotlinx.coroutines.flow.MutableStateFlow
import javax.inject.Inject
@Suppress("unused")
@ -13,9 +14,11 @@ class AdsHelper @Inject constructor(
private val preferencesRepository: PreferencesRepository
) {
val isMobileAdsSdkInitialized = MutableStateFlow(false)
val canShowAd = false
fun initialize() {
preferencesRepository.isAdsEnabled = false
preferencesRepository.isAgreeToProcessData = false
preferencesRepository.selectedDashboardTiles -= DashboardItem.Tile.ADS
}

View File

@ -5,6 +5,7 @@ import android.view.View
import dagger.hilt.android.qualifiers.ApplicationContext
import io.github.wulkanowy.data.repositories.PreferencesRepository
import io.github.wulkanowy.ui.modules.dashboard.DashboardItem
import kotlinx.coroutines.flow.MutableStateFlow
import javax.inject.Inject
@Suppress("unused")
@ -12,10 +13,11 @@ class AdsHelper @Inject constructor(
@ApplicationContext private val context: Context,
private val preferencesRepository: PreferencesRepository
) {
val isMobileAdsSdkInitialized = MutableStateFlow(false)
val canShowAd = false
fun initialize() {
preferencesRepository.isAdsEnabled = false
preferencesRepository.isAgreeToProcessData = false
preferencesRepository.selectedDashboardTiles -= DashboardItem.Tile.ADS
}

View File

@ -1,7 +1,9 @@
package io.github.wulkanowy
import android.app.Application
import android.util.Log.*
import android.util.Log.DEBUG
import android.util.Log.INFO
import android.util.Log.VERBOSE
import androidx.hilt.work.HiltWorkerFactory
import androidx.work.Configuration
import com.yariksoffice.lingver.Lingver
@ -9,16 +11,19 @@ import dagger.hilt.android.HiltAndroidApp
import fr.bipi.treessence.file.FileLoggerTree
import io.github.wulkanowy.data.repositories.PreferencesRepository
import io.github.wulkanowy.ui.base.ThemeManager
import io.github.wulkanowy.utils.*
import io.github.wulkanowy.utils.ActivityLifecycleLogger
import io.github.wulkanowy.utils.AnalyticsHelper
import io.github.wulkanowy.utils.AppInfo
import io.github.wulkanowy.utils.CrashLogExceptionTree
import io.github.wulkanowy.utils.CrashLogTree
import io.github.wulkanowy.utils.DebugLogTree
import io.github.wulkanowy.utils.RemoteConfigHelper
import timber.log.Timber
import javax.inject.Inject
@HiltAndroidApp
class WulkanowyApp : Application(), Configuration.Provider {
@Inject
lateinit var workerFactory: HiltWorkerFactory
@Inject
lateinit var themeManager: ThemeManager
@ -32,16 +37,21 @@ class WulkanowyApp : Application(), Configuration.Provider {
lateinit var analyticsHelper: AnalyticsHelper
@Inject
lateinit var adsHelper: AdsHelper
lateinit var remoteConfigHelper: RemoteConfigHelper
@Inject
lateinit var remoteConfigHelper: RemoteConfigHelper
lateinit var workerFactory: HiltWorkerFactory
override val workManagerConfiguration: Configuration
get() = Configuration.Builder()
.setWorkerFactory(workerFactory)
.setMinimumLoggingLevel(if (appInfo.isDebug) VERBOSE else INFO)
.build()
override fun onCreate() {
super.onCreate()
initializeAppLanguage()
themeManager.applyDefaultTheme()
adsHelper.initialize()
remoteConfigHelper.initialize()
initLogging()
}
@ -74,9 +84,4 @@ class WulkanowyApp : Application(), Configuration.Provider {
analyticsHelper.logEvent("language", "startup" to preferencesRepository.appLanguage)
}
}
override fun getWorkManagerConfiguration() = Configuration.Builder()
.setWorkerFactory(workerFactory)
.setMinimumLoggingLevel(if (appInfo.isDebug) VERBOSE else INFO)
.build()
}

View File

@ -9,7 +9,12 @@ import com.fredporciuncula.flow.preferences.Preference
import com.fredporciuncula.flow.preferences.Serializer
import dagger.hilt.android.qualifiers.ApplicationContext
import io.github.wulkanowy.R
import io.github.wulkanowy.data.enums.*
import io.github.wulkanowy.data.enums.AppTheme
import io.github.wulkanowy.data.enums.GradeColorTheme
import io.github.wulkanowy.data.enums.GradeExpandMode
import io.github.wulkanowy.data.enums.GradeSortingMode
import io.github.wulkanowy.data.enums.TimetableGapsMode
import io.github.wulkanowy.data.enums.TimetableMode
import io.github.wulkanowy.ui.modules.dashboard.DashboardItem
import io.github.wulkanowy.ui.modules.grade.GradeAverageMode
import io.github.wulkanowy.ui.modules.settings.appearance.menuorder.AppMenuItem
@ -19,7 +24,7 @@ import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import okhttp3.internal.notifyAll
import java.time.Instant
import java.util.*
import java.util.UUID
import javax.inject.Inject
import javax.inject.Singleton
@ -323,19 +328,6 @@ class PreferencesRepository @Inject constructor(
get() = sharedPref.getBoolean(PREF_KEY_APP_SUPPORT_SHOWN, false)
set(value) = sharedPref.edit { putBoolean(PREF_KEY_APP_SUPPORT_SHOWN, value) }
var isAgreeToProcessData: Boolean
get() = getBoolean(
R.string.pref_key_ads_consent_data_processing,
R.bool.pref_default_ads_consent_data_processing
)
set(value) = sharedPref.edit {
putBoolean(context.getString(R.string.pref_key_ads_consent_data_processing), value)
}
var isPersonalizedAdsEnabled: Boolean
get() = sharedPref.getBoolean(PREF_KEY_PERSONALIZED_ADS_ENABLED, false)
set(value) = sharedPref.edit { putBoolean(PREF_KEY_PERSONALIZED_ADS_ENABLED, value) }
val isAdsEnabledFlow = flowSharedPref.getBoolean(
context.getString(R.string.pref_key_ads_enabled),
context.resources.getBoolean(R.bool.pref_default_ads_enabled)
@ -418,7 +410,6 @@ class PreferencesRepository @Inject constructor(
private const val PREF_KEY_IN_APP_REVIEW_DATE = "in_app_review_date"
private const val PREF_KEY_IN_APP_REVIEW_DONE = "in_app_review_done"
private const val PREF_KEY_APP_SUPPORT_SHOWN = "app_support_shown"
private const val PREF_KEY_PERSONALIZED_ADS_ENABLED = "personalized_ads_enabled"
private const val PREF_KEY_ADMIN_DISMISSED_MESSAGE_IDS = "admin_message_dismissed_ids"
}
}

View File

@ -1,19 +1,46 @@
package io.github.wulkanowy.ui.modules.dashboard
import io.github.wulkanowy.data.*
import io.github.wulkanowy.data.Resource
import io.github.wulkanowy.data.dataOrNull
import io.github.wulkanowy.data.db.entities.AdminMessage
import io.github.wulkanowy.data.db.entities.LuckyNumber
import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.enums.MessageFolder
import io.github.wulkanowy.data.enums.MessageType
import io.github.wulkanowy.data.repositories.*
import io.github.wulkanowy.data.errorOrNull
import io.github.wulkanowy.data.flatResourceFlow
import io.github.wulkanowy.data.mapResourceData
import io.github.wulkanowy.data.onResourceError
import io.github.wulkanowy.data.repositories.AttendanceSummaryRepository
import io.github.wulkanowy.data.repositories.ConferenceRepository
import io.github.wulkanowy.data.repositories.ExamRepository
import io.github.wulkanowy.data.repositories.GradeRepository
import io.github.wulkanowy.data.repositories.HomeworkRepository
import io.github.wulkanowy.data.repositories.LuckyNumberRepository
import io.github.wulkanowy.data.repositories.MessageRepository
import io.github.wulkanowy.data.repositories.PreferencesRepository
import io.github.wulkanowy.data.repositories.SchoolAnnouncementRepository
import io.github.wulkanowy.data.repositories.SemesterRepository
import io.github.wulkanowy.data.repositories.StudentRepository
import io.github.wulkanowy.data.repositories.TimetableRepository
import io.github.wulkanowy.domain.adminmessage.GetAppropriateAdminMessageUseCase
import io.github.wulkanowy.ui.base.BasePresenter
import io.github.wulkanowy.ui.base.ErrorHandler
import io.github.wulkanowy.utils.AdsHelper
import io.github.wulkanowy.utils.calculatePercentage
import io.github.wulkanowy.utils.nextOrSameSchoolDay
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.emitAll
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.filterNot
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.merge
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch
import timber.log.Timber
import java.time.Instant
@ -48,6 +75,11 @@ class DashboardPresenter @Inject constructor(
private val firstLoadedItemList = mutableListOf<DashboardItem.Type>()
private val selectedDashboardTiles
get() = preferencesRepository.selectedDashboardTiles
.filterNot { it == DashboardItem.Tile.ADS && !adsHelper.canShowAd }
.toSet()
private lateinit var lastError: Throwable
override fun onAttachView(view: DashboardView) {
@ -59,10 +91,19 @@ class DashboardPresenter @Inject constructor(
showContent(false)
}
val selectedDashboardTilesFlow = preferencesRepository.selectedDashboardTilesFlow
.map { selectedDashboardTiles }
val isAdsEnabledFlow = preferencesRepository.isAdsEnabledFlow
.filter { (adsHelper.canShowAd && it) || !it }
.map { selectedDashboardTiles }
val isMobileAdsSdkInitializedFlow = adsHelper.isMobileAdsSdkInitialized
.filter { it }
.map { selectedDashboardTiles }
merge(
preferencesRepository.selectedDashboardTilesFlow,
preferencesRepository.isAdsEnabledFlow
.map { preferencesRepository.selectedDashboardTiles }
selectedDashboardTilesFlow,
isAdsEnabledFlow,
isMobileAdsSdkInitializedFlow
)
.onEach { loadData(tilesToLoad = it) }
.launch("dashboard_pref")
@ -71,7 +112,7 @@ class DashboardPresenter @Inject constructor(
fun onAdminMessageDismissed(adminMessage: AdminMessage) {
preferencesRepository.dismissedAdminMessageIds += adminMessage.id
loadData(preferencesRepository.selectedDashboardTiles)
loadData(selectedDashboardTiles)
}
fun onDragAndDropEnd(list: List<DashboardItem>) {
@ -120,27 +161,14 @@ class DashboardPresenter @Inject constructor(
dashboardItemLoadedList.find { it is DashboardItem.HorizontalGroup } as DashboardItem.HorizontalGroup?
if (horizontalGroup != null) {
val fakeAttendance = preferencesRepository
.selectedHiddenSettingTiles
.contains(DashboardItem.HiddenSettingTile.ATTENDANCE)
val isLuckyNumberToLoad = DashboardItem.Tile.LUCKY_NUMBER in tilesToLoad
val isMessagesToLoad = DashboardItem.Tile.MESSAGES in tilesToLoad
val isAttendanceToLoad = DashboardItem.Tile.ATTENDANCE in tilesToLoad
val horizontalGroupIndex = dashboardItemLoadedList.indexOf(horizontalGroup)
// Fake 100% attendance if tweak is enabled
val newHorizontalGroup = horizontalGroup.copy(
attendancePercentage = if (fakeAttendance) {
DashboardItem.HorizontalGroup.Cell(
data = 100.0,
error = false,
isLoading = false,
)
} else {
horizontalGroup.attendancePercentage.takeIf { isAttendanceToLoad }
},
attendancePercentage = horizontalGroup.attendancePercentage.takeIf { isAttendanceToLoad },
unreadMessagesCount = horizontalGroup.unreadMessagesCount.takeIf { isMessagesToLoad },
luckyNumber = horizontalGroup.luckyNumber.takeIf { isLuckyNumberToLoad }
)
@ -200,7 +228,7 @@ class DashboardPresenter @Inject constructor(
fun onSwipeRefresh() {
Timber.i("Force refreshing the dashboard")
loadData(preferencesRepository.selectedDashboardTiles, forceRefresh = true)
loadData(selectedDashboardTiles, forceRefresh = true)
}
fun onRetry() {
@ -208,7 +236,7 @@ class DashboardPresenter @Inject constructor(
showErrorView(false)
showProgress(true)
}
loadData(preferencesRepository.selectedDashboardTiles, forceRefresh = true)
loadData(selectedDashboardTiles, forceRefresh = true)
}
fun onViewReselected() {
@ -229,7 +257,7 @@ class DashboardPresenter @Inject constructor(
}
fun onDashboardTileSettingsSelected(): Boolean {
view?.showDashboardTileSettings(preferencesRepository.selectedDashboardTiles.toList())
view?.showDashboardTileSettings(selectedDashboardTiles.toList())
return true
}
@ -245,11 +273,7 @@ class DashboardPresenter @Inject constructor(
private fun loadHorizontalGroup(student: Student, forceRefresh: Boolean) {
flow {
val fake = preferencesRepository
.selectedHiddenSettingTiles
.contains(DashboardItem.HiddenSettingTile.ATTENDANCE)
val selectedTiles = preferencesRepository.selectedDashboardTiles
val selectedTiles = selectedDashboardTiles
val flowSuccess = flowOf(Resource.Success(null))
val luckyNumberFlow = luckyNumberRepository.getLuckyNumber(student, forceRefresh)
@ -300,7 +324,7 @@ class DashboardPresenter @Inject constructor(
} else null
},
attendancePercentage = DashboardItem.HorizontalGroup.Cell(
data = attendanceResource.dataOrNull?.calculatePercentage(fake),
data = attendanceResource.dataOrNull?.calculatePercentage(),
error = attendanceResource.errorOrNull != null,
isLoading = attendanceResource is Resource.Loading,
),

View File

@ -9,7 +9,11 @@ import android.view.MenuItem
import android.view.ViewGroup.MarginLayoutParams
import androidx.activity.OnBackPressedCallback
import androidx.activity.addCallback
import androidx.core.view.*
import androidx.core.view.ViewCompat
import androidx.core.view.WindowCompat
import androidx.core.view.WindowInsetsCompat
import androidx.core.view.isVisible
import androidx.core.view.updateLayoutParams
import androidx.fragment.app.DialogFragment
import androidx.fragment.app.Fragment
import androidx.preference.Preference
@ -23,12 +27,19 @@ import io.github.wulkanowy.R
import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.db.entities.StudentWithSemesters
import io.github.wulkanowy.databinding.ActivityMainBinding
import io.github.wulkanowy.databinding.DialogAdsConsentBinding
import io.github.wulkanowy.ui.base.BaseActivity
import io.github.wulkanowy.ui.modules.Destination
import io.github.wulkanowy.ui.modules.account.accountquick.AccountQuickDialog
import io.github.wulkanowy.ui.modules.settings.appearance.menuorder.AppMenuItem
import io.github.wulkanowy.utils.*
import io.github.wulkanowy.utils.AnalyticsHelper
import io.github.wulkanowy.utils.AppInfo
import io.github.wulkanowy.utils.InAppReviewHelper
import io.github.wulkanowy.utils.InAppUpdateHelper
import io.github.wulkanowy.utils.createNameInitialsDrawable
import io.github.wulkanowy.utils.dpToPx
import io.github.wulkanowy.utils.nickOrName
import io.github.wulkanowy.utils.safelyPopFragments
import io.github.wulkanowy.utils.setOnViewChangeListener
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import timber.log.Timber
@ -312,40 +323,6 @@ class MainActivity : BaseActivity<MainPresenter, ActivityMainBinding>(), MainVie
.show()
}
override fun showPrivacyPolicyDialog() {
val dialogAdsConsentBinding = DialogAdsConsentBinding.inflate(layoutInflater)
val dialog = MaterialAlertDialogBuilder(this)
.setTitle(R.string.pref_ads_consent_title)
.setMessage(R.string.pref_ads_consent_description)
.setView(dialogAdsConsentBinding.root)
.show()
dialogAdsConsentBinding.adsConsentOver.setOnCheckedChangeListener { _, isChecked ->
dialogAdsConsentBinding.adsConsentPersonalised.isEnabled = isChecked
}
dialogAdsConsentBinding.adsConsentPersonalised.setOnClickListener {
presenter.onPrivacyAgree(true)
dialog.dismiss()
}
dialogAdsConsentBinding.adsConsentNonPersonalised.setOnClickListener {
presenter.onPrivacyAgree(false)
dialog.dismiss()
}
dialogAdsConsentBinding.adsConsentPrivacy.setOnClickListener { presenter.onPrivacySelected() }
dialogAdsConsentBinding.adsConsentCancel.setOnClickListener { dialog.cancel() }
}
override fun openPrivacyPolicy() {
openInternetBrowser(
"https://wulkanowy.github.io/polityka-prywatnosci.html",
::showMessage
)
}
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
navController.onSaveInstanceState(outState)

View File

@ -19,7 +19,6 @@ import io.github.wulkanowy.utils.AdsHelper
import io.github.wulkanowy.utils.AnalyticsHelper
import io.github.wulkanowy.utils.AppInfo
import kotlinx.coroutines.launch
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.json.Json
import timber.log.Timber
import java.time.Duration
@ -52,6 +51,7 @@ class MainPresenter @Inject constructor(
destinationType in rootDestinationTypeList -> {
rootDestinationTypeList.indexOf(destinationType)
}
else -> 4
}
@ -110,6 +110,7 @@ class MainPresenter @Inject constructor(
is AccountView,
is StudentInfoView,
is AccountDetailsView -> false
else -> true
}
@ -148,20 +149,8 @@ class MainPresenter @Inject constructor(
}
fun onEnableAdsSelected() {
view?.showPrivacyPolicyDialog()
}
fun onPrivacyAgree(isPersonalizedAds: Boolean) {
preferencesRepository.isAgreeToProcessData = true
preferencesRepository.isPersonalizedAdsEnabled = isPersonalizedAds
adsHelper.initialize()
preferencesRepository.isAdsEnabled = true
}
fun onPrivacySelected() {
view?.openPrivacyPolicy()
adsHelper.initialize()
}
private fun checkInAppReview() {
@ -189,8 +178,8 @@ class MainPresenter @Inject constructor(
.getOrElse { return@launch }
if (Instant.now().minus(Duration.ofDays(28)).isAfter(student.registrationDate)) {
view?.showAppSupport()
preferencesRepository.isAppSupportShown = true
view?.showAppSupport()
}
}
}

View File

@ -46,10 +46,6 @@ interface MainView : BaseView {
fun showAppSupport()
fun showPrivacyPolicyDialog()
fun openPrivacyPolicy()
fun openMoreDestination(destination: Destination)
interface MainChildView {

View File

@ -1,5 +1,6 @@
Wersja 2.2.6
Wersja 2.3.0
— naprawiliśmy logowanie do aplikacji (tym razem musi się udać) na odmianie standardowej i podobnych
— poprawiliśmy kilka usterek przy odświeżaniu danych (ale pewnie nie wszystkie)
— zaktualizowaliśmy sposób pytania o zgodę na personalizowane reklamy
Pełna lista zmian: https://github.com/wulkanowy/wulkanowy/releases

View File

@ -1,79 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.google.android.material.button.MaterialButton
android:id="@+id/ads_consent_privacy"
style="@style/Widget.MaterialComponents.Button.TextButton"
android:layout_width="wrap_content"
android:layout_height="24dp"
android:layout_marginHorizontal="24dp"
android:insetLeft="0dp"
android:insetTop="0dp"
android:insetRight="0dp"
android:insetBottom="0dp"
android:padding="0dp"
android:text="@string/pref_ads_privacy_policy"
android:textAllCaps="false"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<com.google.android.material.checkbox.MaterialCheckBox
android:id="@+id/ads_consent_over"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="17dp"
android:layout_marginTop="8dp"
android:text="@string/pref_ads_over_18_years_old"
android:textColor="?android:textColorSecondary"
android:textSize="14sp"
app:layout_constraintTop_toBottomOf="@id/ads_consent_privacy" />
<com.google.android.material.button.MaterialButton
android:id="@+id/ads_consent_personalised"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="24dp"
android:layout_marginTop="24dp"
android:enabled="false"
android:insetLeft="0dp"
android:insetTop="0dp"
android:insetRight="0dp"
android:insetBottom="0dp"
android:text="@string/pref_ads_option_personalized"
app:layout_constraintTop_toBottomOf="@id/ads_consent_over" />
<com.google.android.material.button.MaterialButton
android:id="@+id/ads_consent_non_personalised"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="24dp"
android:layout_marginTop="24dp"
android:layout_marginBottom="24dp"
android:insetLeft="0dp"
android:insetTop="0dp"
android:insetRight="0dp"
android:insetBottom="0dp"
android:text="@string/pref_ads_option_non_personalized"
app:layout_constraintBottom_toTopOf="@id/ads_consent_cancel"
app:layout_constraintTop_toBottomOf="@id/ads_consent_personalised"
app:layout_constraintVertical_bias="0" />
<com.google.android.material.button.MaterialButton
android:id="@+id/ads_consent_cancel"
style="@style/Widget.Material3.Button.OutlinedButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="24dp"
android:layout_marginTop="24dp"
android:layout_marginBottom="24dp"
android:insetLeft="0dp"
android:insetTop="0dp"
android:insetRight="0dp"
android:insetBottom="0dp"
android:text="@android:string/cancel"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintVertical_bias="0" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -760,7 +760,7 @@
<string name="pref_ads_support_category_name">Podpora</string>
<string name="pref_ads_privacy_policy">Ochrana osobních údajů</string>
<string name="pref_ads_agreements">Souhlasy</string>
<string name="pref_ads_consent">Souhlas se zpracováním údajů souvisejících s reklamami</string>
<string name="pref_ads_consent">Show consent to data processing</string>
<string name="pref_ads_show_in_app">Zobrazit reklamy v aplikaci</string>
<string name="pref_ads_support">Podívejte se na jednu reklamu pro podporu projektu</string>
<string name="pref_ads_privacy_title">Souhlas se zpracováním dat</string>
@ -769,13 +769,6 @@
<string name="pref_ads_privacy_link">Ochrana osobních údajů</string>
<string name="pref_ads_loading">Reklama se načítá</string>
<string name="pref_ads_once_per_visit">Děkujeme za vaši podporu, vraťte se později pro více reklam</string>
<string name="pref_ads_consent_title">Můžeme použít Vaše data k zobrazení reklam?</string>
<string name="pref_ads_consent_description">Volbu můžete kdykoliv změnit v nastavení aplikace. Můžeme použít Vaše data k zobrazení reklam šitých pro vás nebo pomocí méně vašich dat zobrazovat nepřizpůsobené reklamy. Podrobnosti naleznete v našich Zásadách ochrany osobních údajů</string>
<string name="pref_ads_summary_personalized">Přizpůsobené reklamy</string>
<string name="pref_ads_summary_non_personalized">Nepřizpůsobené reklamy</string>
<string name="pref_ads_over_18_years_old">Je mi více než 18 let</string>
<string name="pref_ads_option_personalized">Ano, přizpůsobené reklamy</string>
<string name="pref_ads_option_non_personalized">Ano, nepřizpůsobené reklamy</string>
<string name="pref_settings_advanced_title">Pokročilé</string>
<string name="pref_settings_appearance_title">Vzhled a chování</string>
<string name="pref_settings_notifications_title">Oznámení</string>

View File

@ -670,7 +670,7 @@
<string name="pref_ads_support_category_name">Support</string>
<string name="pref_ads_privacy_policy">Privacy Policy</string>
<string name="pref_ads_agreements">Agreements</string>
<string name="pref_ads_consent">Consent to processing of data related to ads</string>
<string name="pref_ads_consent">Show consent to data processing</string>
<string name="pref_ads_show_in_app">Show ads in app</string>
<string name="pref_ads_support">Watch single ad to support project</string>
<string name="pref_ads_privacy_title">Consent to data processing</string>
@ -679,13 +679,6 @@
<string name="pref_ads_privacy_link">Privacy policy</string>
<string name="pref_ads_loading">Ad is loading</string>
<string name="pref_ads_once_per_visit">Thank you for your support, come back later for more ads</string>
<string name="pref_ads_consent_title">Can we use your data to display ads?</string>
<string name="pref_ads_consent_description">You can change your choice anytime in the app settings. We may use your data to display ads tailored to you or, using less of your data, display non-personalized ads. Please see our Privacy Policy for details</string>
<string name="pref_ads_summary_personalized">Personalized ads</string>
<string name="pref_ads_summary_non_personalized">Non-personalized ads</string>
<string name="pref_ads_over_18_years_old">I am over 18 years old</string>
<string name="pref_ads_option_personalized">Yes, personalized ads</string>
<string name="pref_ads_option_non_personalized">Yes, non-personalized ads</string>
<string name="pref_settings_advanced_title">Advanced</string>
<string name="pref_settings_appearance_title">Appearance &amp; Behavior</string>
<string name="pref_settings_notifications_title">Notifications</string>

View File

@ -670,7 +670,7 @@
<string name="pref_ads_support_category_name">Unterstützung</string>
<string name="pref_ads_privacy_policy">Datenschutz-Bestimmungen</string>
<string name="pref_ads_agreements">Vereinbarungen</string>
<string name="pref_ads_consent">Zustimmung zur Verarbeitung von Daten im Zusammenhang mit Anzeigen</string>
<string name="pref_ads_consent">Show consent to data processing</string>
<string name="pref_ads_show_in_app">Anzeigen in der App anzeigen</string>
<string name="pref_ads_support">Einzelanzeige ansehen, um Projekt zu unterstützen</string>
<string name="pref_ads_privacy_title">Einwilligung in die Datenverarbeitung</string>
@ -679,13 +679,6 @@
<string name="pref_ads_privacy_link">Datenschutzerklärung</string>
<string name="pref_ads_loading">Anzeige wird geladen</string>
<string name="pref_ads_once_per_visit">Vielen Dank für Ihre Unterstützung, kommen Sie später wieder für weitere Anzeigen</string>
<string name="pref_ads_consent_title">Können wir Ihre Daten zur Anzeige von Werbung verwenden?</string>
<string name="pref_ads_consent_description">Sie können Ihre Wahl jederzeit in den App-Einstellungen ändern. Wir verwenden Ihre Daten, um auf Sie zugeschnittene Anzeigen anzuzeigen oder unter Verwendung weniger Ihrer Daten nicht personalisierte Werbung anzuzeigen. Bitte lesen Sie unsere Datenschutzerklärung für Details</string>
<string name="pref_ads_summary_personalized">Personalisierte Werbung</string>
<string name="pref_ads_summary_non_personalized">keine personalisierte Werbung</string>
<string name="pref_ads_over_18_years_old">Ich bin über 18 Jahre alt</string>
<string name="pref_ads_option_personalized">Ja, personalisierte Werbung</string>
<string name="pref_ads_option_non_personalized">Ja, nicht personalisierte Werbung</string>
<string name="pref_settings_advanced_title">Erweitert</string>
<string name="pref_settings_appearance_title">Aussehen &amp; Verhalten</string>
<string name="pref_settings_notifications_title">Benachrichtigungen</string>

View File

@ -670,7 +670,7 @@
<string name="pref_ads_support_category_name">Support</string>
<string name="pref_ads_privacy_policy">Privacy Policy</string>
<string name="pref_ads_agreements">Agreements</string>
<string name="pref_ads_consent">Consent to processing of data related to ads</string>
<string name="pref_ads_consent">Show consent to data processing</string>
<string name="pref_ads_show_in_app">Show ads in app</string>
<string name="pref_ads_support">Watch single ad to support project</string>
<string name="pref_ads_privacy_title">Consent to data processing</string>
@ -679,13 +679,6 @@
<string name="pref_ads_privacy_link">Privacy policy</string>
<string name="pref_ads_loading">Ad is loading</string>
<string name="pref_ads_once_per_visit">Thank you for your support, come back later for more ads</string>
<string name="pref_ads_consent_title">Can we use your data to display ads?</string>
<string name="pref_ads_consent_description">You can change your choice anytime in the app settings. We may use your data to display ads tailored to you or, using less of your data, display non-personalized ads. Please see our Privacy Policy for details</string>
<string name="pref_ads_summary_personalized">Personalized ads</string>
<string name="pref_ads_summary_non_personalized">Non-personalized ads</string>
<string name="pref_ads_over_18_years_old">I am over 18 years old</string>
<string name="pref_ads_option_personalized">Yes, personalized ads</string>
<string name="pref_ads_option_non_personalized">Yes, non-personalized ads</string>
<string name="pref_settings_advanced_title">Advanced</string>
<string name="pref_settings_appearance_title">Appearance &amp; Behavior</string>
<string name="pref_settings_notifications_title">Notifications</string>

View File

@ -670,7 +670,7 @@
<string name="pref_ads_support_category_name">Support</string>
<string name="pref_ads_privacy_policy">Privacy Policy</string>
<string name="pref_ads_agreements">Agreements</string>
<string name="pref_ads_consent">Consent to processing of data related to ads</string>
<string name="pref_ads_consent">Show consent to data processing</string>
<string name="pref_ads_show_in_app">Show ads in app</string>
<string name="pref_ads_support">Watch single ad to support project</string>
<string name="pref_ads_privacy_title">Consent to data processing</string>
@ -679,13 +679,6 @@
<string name="pref_ads_privacy_link">Privacy policy</string>
<string name="pref_ads_loading">Ad is loading</string>
<string name="pref_ads_once_per_visit">Thank you for your support, come back later for more ads</string>
<string name="pref_ads_consent_title">Can we use your data to display ads?</string>
<string name="pref_ads_consent_description">You can change your choice anytime in the app settings. We may use your data to display ads tailored to you or, using less of your data, display non-personalized ads. Please see our Privacy Policy for details</string>
<string name="pref_ads_summary_personalized">Personalized ads</string>
<string name="pref_ads_summary_non_personalized">Non-personalized ads</string>
<string name="pref_ads_over_18_years_old">I am over 18 years old</string>
<string name="pref_ads_option_personalized">Yes, personalized ads</string>
<string name="pref_ads_option_non_personalized">Yes, non-personalized ads</string>
<string name="pref_settings_advanced_title">Advanced</string>
<string name="pref_settings_appearance_title">Appearance &amp; Behavior</string>
<string name="pref_settings_notifications_title">Notifications</string>

View File

@ -760,7 +760,7 @@
<string name="pref_ads_support_category_name">Wsparcie</string>
<string name="pref_ads_privacy_policy">Polityka prywatności</string>
<string name="pref_ads_agreements">Zgody</string>
<string name="pref_ads_consent">Zgoda na przetwarzanie danych związanych z reklamami</string>
<string name="pref_ads_consent">Pokaż zgodę na przetwarzanie danych</string>
<string name="pref_ads_show_in_app">Pokazuj reklamy w aplikacji</string>
<string name="pref_ads_support">Obejrzyj pojedynczą reklamę, aby wesprzeć projekt</string>
<string name="pref_ads_privacy_title">Zgoda na przetwarzanie danych</string>
@ -769,13 +769,6 @@
<string name="pref_ads_privacy_link">Polityka prywatności</string>
<string name="pref_ads_loading">Ładowanie reklamy</string>
<string name="pref_ads_once_per_visit">Dziękujemy za wsparcie, wróć później po więcej reklam</string>
<string name="pref_ads_consent_title">Czy możemy używać Twoich danych do wyświetlania reklam?</string>
<string name="pref_ads_consent_description">Możesz zmienić swój wybór w dowolnym momencie w ustawieniach aplikacji. Możemy wykorzystać Twoje dane do wyświetlania reklam dostosowanych do Ciebie lub, przy użyciu mniejszej ilości danych, wyświetlić niepersonalizowane reklamy. Zobacz naszą Politykę Prywatności, aby uzyskać więcej informacji</string>
<string name="pref_ads_summary_personalized">Spersonalizowane reklamy</string>
<string name="pref_ads_summary_non_personalized">Niespersonalizowane reklamy</string>
<string name="pref_ads_over_18_years_old">Mam ukończone 18 lat</string>
<string name="pref_ads_option_personalized">Tak, spersonalizowane reklamy</string>
<string name="pref_ads_option_non_personalized">Tak, niespersonalizowane reklamy</string>
<string name="pref_settings_advanced_title">Zaawansowane</string>
<string name="pref_settings_appearance_title">Wygląd i zachowanie</string>
<string name="pref_settings_notifications_title">Powiadomienia</string>

View File

@ -760,7 +760,7 @@
<string name="pref_ads_support_category_name">Поддержка</string>
<string name="pref_ads_privacy_policy">Политика приватности</string>
<string name="pref_ads_agreements">Соглашения</string>
<string name="pref_ads_consent">Согласие на обработку данных, связанных с объявлениями</string>
<string name="pref_ads_consent">Show consent to data processing</string>
<string name="pref_ads_show_in_app">Показать рекламу в приложении</string>
<string name="pref_ads_support">Посмотреть рекламу для поддержки проекта</string>
<string name="pref_ads_privacy_title">Согласие на обработку данных</string>
@ -769,13 +769,6 @@
<string name="pref_ads_privacy_link">Политика конфиденциальности</string>
<string name="pref_ads_loading">Реклама загружается</string>
<string name="pref_ads_once_per_visit">Спасибо за вашу поддержку, возвращайтесь позже для дополнительной рекламы</string>
<string name="pref_ads_consent_title">Можем ли мы использовать ваши данные для показа рекламы?</string>
<string name="pref_ads_consent_description">Вы можете изменить свой выбор в любое время в настройках приложения. Мы можем использовать ваши данные для показа объявлений в соответствии с вашими пожеланиями или, используя меньше данных, отображать неперсональную рекламу. Пожалуйста, ознакомьтесь с нашей политикой конфиденциальности для подробностей</string>
<string name="pref_ads_summary_personalized">Персонализированная реклама</string>
<string name="pref_ads_summary_non_personalized">Неперсонализированная реклама</string>
<string name="pref_ads_over_18_years_old">Я старше 18 лет</string>
<string name="pref_ads_option_personalized">Да, персонализировать рекламу</string>
<string name="pref_ads_option_non_personalized">Да, не персонализировать рекламу</string>
<string name="pref_settings_advanced_title">Расширенные</string>
<string name="pref_settings_appearance_title">Внешний вид и поведение</string>
<string name="pref_settings_notifications_title">Уведомления</string>

View File

@ -760,7 +760,7 @@
<string name="pref_ads_support_category_name">Podpora</string>
<string name="pref_ads_privacy_policy">Ochrana osobných údajov</string>
<string name="pref_ads_agreements">Súhlasy</string>
<string name="pref_ads_consent">Súhlas so spracovaním údajov súvisiacich s reklamami</string>
<string name="pref_ads_consent">Show consent to data processing</string>
<string name="pref_ads_show_in_app">Zobraziť reklamy v aplikácii</string>
<string name="pref_ads_support">Pozrite sa na jednu reklamu pre podporu projektu</string>
<string name="pref_ads_privacy_title">Súhlas so spracovaním dát</string>
@ -769,13 +769,6 @@
<string name="pref_ads_privacy_link">Ochrana osobných údajov</string>
<string name="pref_ads_loading">Reklama sa načítava</string>
<string name="pref_ads_once_per_visit">Ďakujeme za vašu podporu, vráťte sa neskôr pre viac reklám</string>
<string name="pref_ads_consent_title">Môžeme použiť Vaše údaje na zobrazenie reklám?</string>
<string name="pref_ads_consent_description">Voľbu môžete kedykoľvek zmeniť v nastavení aplikácie. Môžeme použiť vaše údaje na zobrazenie reklám šitých pre vás alebo pomocou menej vašich dát zobrazovať neprispôsobené reklamy. Podrobnosti nájdete v našich Zásadách ochrany osobných údajov</string>
<string name="pref_ads_summary_personalized">Prispôsobené reklamy</string>
<string name="pref_ads_summary_non_personalized">Neprispôsobené reklamy</string>
<string name="pref_ads_over_18_years_old">Mám viac ako 18 rokov</string>
<string name="pref_ads_option_personalized">Áno, prispôsobené reklamy</string>
<string name="pref_ads_option_non_personalized">Áno, neprispôsobené reklamy</string>
<string name="pref_settings_advanced_title">Pokročilé</string>
<string name="pref_settings_appearance_title">Vzhľad a správanie</string>
<string name="pref_settings_notifications_title">Oznámenia</string>

View File

@ -760,7 +760,7 @@
<string name="pref_ads_support_category_name">Підтримка</string>
<string name="pref_ads_privacy_policy">Політика конфіденційності</string>
<string name="pref_ads_agreements">Угоди</string>
<string name="pref_ads_consent">Згода на обробку даних, пов\'язаних з рекламою</string>
<string name="pref_ads_consent">Показати згоду на обробку даних</string>
<string name="pref_ads_show_in_app">Показувати рекламу в додатку</string>
<string name="pref_ads_support">Подивіться одну рекламу для підтримки проєкту</string>
<string name="pref_ads_privacy_title">Згода в обробці даних</string>
@ -769,13 +769,6 @@
<string name="pref_ads_privacy_link">Політика конфіденційності</string>
<string name="pref_ads_loading">Реклама завантажується</string>
<string name="pref_ads_once_per_visit">Дякуємо за вашу підтримку, повертайтеся пізніше для більшої кількості реклам</string>
<string name="pref_ads_consent_title">Чи можемо ми використовувати ваші дані для висвітлювання реклами?</string>
<string name="pref_ads_consent_description">Ви можете змінити свій вибір в будь-який час в налаштуваннях додатку. Ми можемо використовувати ваші дані для висвітлювання реклами, адаптованої до вас або, використовуючи менше ваших даних, висвітлювати неперсоналізовану рекламу. Перегляньте нашу Політику конфіденційності для подробиць</string>
<string name="pref_ads_summary_personalized">Персоналізована реклама</string>
<string name="pref_ads_summary_non_personalized">Неперсоналізована реклама</string>
<string name="pref_ads_over_18_years_old">Мені більше 18 років</string>
<string name="pref_ads_option_personalized">Так, персоналізована реклама</string>
<string name="pref_ads_option_non_personalized">Так, неперсоналізована реклама</string>
<string name="pref_settings_advanced_title">Додатково</string>
<string name="pref_settings_appearance_title">Вигляд та поведінка</string>
<string name="pref_settings_notifications_title">Сповіщення</string>

View File

@ -37,8 +37,7 @@
<string name="pref_key_ads_single_support">single_ad_support</string>
<string name="pref_key_ads_enabled">ads_enabled</string>
<string name="pref_key_ads_privacy_policy">ads_privacy_policy</string>
<string name="pref_key_ads_consent_data_processing">ads_consent_data_processing</string>
<string name="pref_key_ads_over_eighteen">ads_over_eighteen</string>
<string name="pref_key_ads_ump_agreements">ads_ump_agreements</string>
<string name="pref_key_incognito_moge">incognito_mode</string>
<string name="pref_key_menu_order">appearance_menu_order</string>
</resources>

View File

@ -749,7 +749,7 @@
<string name="pref_ads_support_category_name">Support</string>
<string name="pref_ads_privacy_policy">Privacy Policy</string>
<string name="pref_ads_agreements">Agreements</string>
<string name="pref_ads_consent">Consent to processing of data related to ads</string>
<string name="pref_ads_consent">Show consent to data processing</string>
<string name="pref_ads_show_in_app">Show ads in app</string>
<string name="pref_ads_support">Watch single ad to support project</string>
<string name="pref_ads_privacy_title">Consent to data processing</string>
@ -758,13 +758,6 @@
<string name="pref_ads_privacy_link">Privacy policy</string>
<string name="pref_ads_loading">Ad is loading</string>
<string name="pref_ads_once_per_visit">Thank you for your support, come back later for more ads</string>
<string name="pref_ads_consent_title">Can we use your data to display ads?</string>
<string name="pref_ads_consent_description">You can change your choice anytime in the app settings. We may use your data to display ads tailored to you or, using less of your data, display non-personalized ads. Please see our Privacy Policy for details</string>
<string name="pref_ads_summary_personalized">Personalized ads</string>
<string name="pref_ads_summary_non_personalized">Non-personalized ads</string>
<string name="pref_ads_over_18_years_old">I am over 18 years old</string>
<string name="pref_ads_option_personalized">Yes, personalized ads</string>
<string name="pref_ads_option_non_personalized">Yes, non-personalized ads</string>
<string name="pref_settings_advanced_title">Advanced</string>
<string name="pref_settings_appearance_title">Appearance &amp; Behavior</string>
<string name="pref_settings_notifications_title">Notifications</string>

View File

@ -2,19 +2,17 @@ package io.github.wulkanowy.ui.modules.settings.ads
import android.os.Bundle
import android.view.View
import androidx.preference.CheckBoxPreference
import androidx.preference.Preference
import androidx.preference.PreferenceFragmentCompat
import androidx.preference.SwitchPreferenceCompat
import com.google.android.gms.ads.rewardedinterstitial.RewardedInterstitialAd
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import dagger.hilt.android.AndroidEntryPoint
import io.github.wulkanowy.R
import io.github.wulkanowy.databinding.DialogAdsConsentBinding
import io.github.wulkanowy.ui.base.BaseActivity
import io.github.wulkanowy.ui.base.ErrorDialog
import io.github.wulkanowy.ui.modules.auth.AuthDialog
import io.github.wulkanowy.ui.modules.main.MainView
import io.github.wulkanowy.utils.AdsHelper
import io.github.wulkanowy.utils.openInternetBrowser
import javax.inject.Inject
@ -24,6 +22,9 @@ class AdsFragment : PreferenceFragmentCompat(), MainView.TitledView, AdsView {
@Inject
lateinit var presenter: AdsPresenter
@Inject
lateinit var adsHelper: AdsHelper
override val titleStringId = R.string.pref_settings_ads_title
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
@ -46,11 +47,18 @@ class AdsFragment : PreferenceFragmentCompat(), MainView.TitledView, AdsView {
true
}
findPreference<CheckBoxPreference>(getString(R.string.pref_key_ads_consent_data_processing))
?.setOnPreferenceChangeListener { _, newValue ->
presenter.onConsentSelected(newValue as Boolean)
true
}
findPreference<Preference>(getString(R.string.pref_key_ads_ump_agreements))?.setOnPreferenceClickListener {
presenter.onUmpAgreementsSelected()
true
}
findPreference<Preference>(getString(R.string.pref_key_ads_single_support))
?.isEnabled = adsHelper.canShowAd
findPreference<SwitchPreferenceCompat>(getString(R.string.pref_key_ads_enabled))?.setOnPreferenceChangeListener { _, newValue ->
presenter.onAdsEnabledSelected(newValue as Boolean)
true
}
}
override fun showAd(ad: RewardedInterstitialAd) {
@ -59,48 +67,6 @@ class AdsFragment : PreferenceFragmentCompat(), MainView.TitledView, AdsView {
}
}
override fun showPrivacyPolicyDialog() {
val dialogAdsConsentBinding = DialogAdsConsentBinding.inflate(layoutInflater)
val dialog = MaterialAlertDialogBuilder(requireContext())
.setTitle(R.string.pref_ads_consent_title)
.setMessage(R.string.pref_ads_consent_description)
.setView(dialogAdsConsentBinding.root)
.setOnCancelListener { presenter.onPrivacyDialogCanceled() }
.show()
dialogAdsConsentBinding.adsConsentOver.setOnCheckedChangeListener { _, isChecked ->
dialogAdsConsentBinding.adsConsentPersonalised.isEnabled = isChecked
}
dialogAdsConsentBinding.adsConsentPersonalised.setOnClickListener {
presenter.onPersonalizedAgree()
dialog.dismiss()
}
dialogAdsConsentBinding.adsConsentNonPersonalised.setOnClickListener {
presenter.onNonPersonalizedAgree()
dialog.dismiss()
}
dialogAdsConsentBinding.adsConsentPrivacy.setOnClickListener { presenter.onPrivacySelected() }
dialogAdsConsentBinding.adsConsentCancel.setOnClickListener { dialog.cancel() }
}
override fun showProcessingDataSummary(isPersonalized: Boolean?) {
val summaryText = isPersonalized?.let {
getString(if (it) R.string.pref_ads_summary_personalized else R.string.pref_ads_summary_non_personalized)
}
findPreference<CheckBoxPreference>(getString(R.string.pref_key_ads_consent_data_processing))
?.summary = summaryText
}
override fun setCheckedProcessingData(checked: Boolean) {
findPreference<CheckBoxPreference>(getString(R.string.pref_key_ads_consent_data_processing))
?.isChecked = checked
}
override fun setCheckedAdsEnabled(checked: Boolean) {
findPreference<SwitchPreferenceCompat>(getString(R.string.pref_key_ads_enabled))
?.isChecked = checked

View File

@ -1,6 +1,5 @@
package io.github.wulkanowy.ui.modules.settings.ads
import io.github.wulkanowy.data.repositories.PreferencesRepository
import io.github.wulkanowy.data.repositories.StudentRepository
import io.github.wulkanowy.ui.base.BasePresenter
import io.github.wulkanowy.ui.base.ErrorHandler
@ -13,18 +12,12 @@ class AdsPresenter @Inject constructor(
errorHandler: ErrorHandler,
studentRepository: StudentRepository,
private val adsHelper: AdsHelper,
private val preferencesRepository: PreferencesRepository
) : BasePresenter<AdsView>(errorHandler, studentRepository) {
override fun onAttachView(view: AdsView) {
super.onAttachView(view)
view.initView()
Timber.i("Settings ads view was initialized")
view.showProcessingDataSummary(
preferencesRepository.isPersonalizedAdsEnabled.takeIf {
preferencesRepository.isAgreeToProcessData
})
}
fun onWatchSingleAdSelected() {
@ -50,38 +43,17 @@ class AdsPresenter @Inject constructor(
}
}
fun onConsentSelected(isChecked: Boolean) {
if (isChecked) {
view?.showPrivacyPolicyDialog()
} else {
view?.showProcessingDataSummary(null)
view?.setCheckedAdsEnabled(false)
}
}
fun onPrivacySelected() {
view?.openPrivacyPolicy()
}
fun onPrivacyDialogCanceled() {
view?.setCheckedProcessingData(false)
fun onAdsEnabledSelected(newValue: Boolean) {
if (newValue) {
adsHelper.initialize()
}
}
fun onNonPersonalizedAgree() {
preferencesRepository.isPersonalizedAdsEnabled = false
adsHelper.initialize()
view?.setCheckedProcessingData(true)
view?.showProcessingDataSummary(false)
}
fun onPersonalizedAgree() {
preferencesRepository.isPersonalizedAdsEnabled = true
adsHelper.initialize()
view?.setCheckedProcessingData(true)
view?.showProcessingDataSummary(true)
fun onUmpAgreementsSelected() {
adsHelper.openAdsUmpAgreements()
}
}

View File

@ -9,8 +9,6 @@ interface AdsView : BaseView {
fun showAd(ad: RewardedInterstitialAd)
fun showPrivacyPolicyDialog()
fun openPrivacyPolicy()
fun showLoadingSupportAd(show: Boolean)
@ -18,8 +16,4 @@ interface AdsView : BaseView {
fun showWatchAdOncePerVisit(show: Boolean)
fun setCheckedAdsEnabled(checked: Boolean)
fun setCheckedProcessingData(checked: Boolean)
fun showProcessingDataSummary(isPersonalized: Boolean?)
}

View File

@ -1,49 +1,110 @@
package io.github.wulkanowy.utils
import android.app.Activity
import android.content.Context
import android.net.ConnectivityManager
import android.net.NetworkCapabilities
import android.os.Build
import android.os.Bundle
import android.view.View
import androidx.core.content.getSystemService
import com.google.ads.mediation.admob.AdMobAdapter
import com.google.android.gms.ads.*
import com.google.android.gms.ads.AdListener
import com.google.android.gms.ads.AdRequest
import com.google.android.gms.ads.AdSize
import com.google.android.gms.ads.AdView
import com.google.android.gms.ads.LoadAdError
import com.google.android.gms.ads.MobileAds
import com.google.android.gms.ads.rewardedinterstitial.RewardedInterstitialAd
import com.google.android.gms.ads.rewardedinterstitial.RewardedInterstitialAdLoadCallback
import com.google.android.ump.ConsentInformation
import com.google.android.ump.ConsentRequestParameters
import com.google.android.ump.UserMessagingPlatform
import dagger.hilt.android.qualifiers.ApplicationContext
import dagger.hilt.android.scopes.ActivityScoped
import io.github.wulkanowy.BuildConfig
import io.github.wulkanowy.data.repositories.PreferencesRepository
import kotlinx.coroutines.flow.MutableStateFlow
import timber.log.Timber
import java.net.UnknownHostException
import java.util.concurrent.atomic.AtomicBoolean
import javax.inject.Inject
import kotlin.coroutines.resume
import kotlin.coroutines.resumeWithException
import kotlin.coroutines.suspendCoroutine
@ActivityScoped
class AdsHelper @Inject constructor(
private val activity: Activity,
@ApplicationContext private val context: Context,
private val preferencesRepository: PreferencesRepository
preferencesRepository: PreferencesRepository
) {
private var isMobileAdsInitializeCalled = AtomicBoolean(false)
private var consentInformation: ConsentInformation? = null
private val canRequestAd get() = consentInformation?.canRequestAds() == true
val isMobileAdsSdkInitialized = MutableStateFlow(false)
val canShowAd get() = isMobileAdsSdkInitialized.value && canRequestAd
init {
if (preferencesRepository.isAdsEnabled) {
initialize()
}
}
fun initialize() {
if (preferencesRepository.isAgreeToProcessData) {
MobileAds.initialize(context)
val consentRequestParameters = ConsentRequestParameters.Builder()
.build()
consentInformation = UserMessagingPlatform.getConsentInformation(context)
consentInformation?.requestConsentInfoUpdate(
activity,
consentRequestParameters,
{
UserMessagingPlatform.loadAndShowConsentFormIfRequired(
activity
) { loadAndShowError ->
if (loadAndShowError != null) {
Timber.e(IllegalStateException("${loadAndShowError.errorCode}: ${loadAndShowError.message}"))
}
if (canRequestAd) {
initializeMobileAds()
}
}
},
{ requestConsentError ->
Timber.e(IllegalStateException("${requestConsentError.errorCode}: ${requestConsentError.message}"))
})
if (canRequestAd) {
initializeMobileAds()
}
}
fun openAdsUmpAgreements() {
UserMessagingPlatform.showPrivacyOptionsForm(activity) {
if (it != null) {
Timber.e(IllegalStateException("${it.errorCode}: ${it.message}"))
}
}
}
private fun initializeMobileAds() {
if (isMobileAdsInitializeCalled.getAndSet(true)) return
MobileAds.initialize(context) {
isMobileAdsSdkInitialized.value = true
}
}
suspend fun getSupportAd(): RewardedInterstitialAd? {
if (!canRequestAd) return null
if (!context.isInternetConnected()) {
throw UnknownHostException()
}
val extra = Bundle().apply { putString("npa", "1") }
val adRequest = AdRequest.Builder()
.apply {
if (!preferencesRepository.isPersonalizedAdsEnabled) {
addNetworkExtrasBundle(AdMobAdapter::class.java, extra)
}
}
.build()
return suspendCoroutine {
@ -64,13 +125,8 @@ class AdsHelper @Inject constructor(
}
suspend fun getDashboardTileAdBanner(width: Int): AdBanner {
val extra = Bundle().apply { putString("npa", "1") }
if (!canShowAd) throw IllegalStateException("Cannot show ad")
val adRequest = AdRequest.Builder()
.apply {
if (!preferencesRepository.isPersonalizedAdsEnabled) {
addNetworkExtrasBundle(AdMobAdapter::class.java, extra)
}
}
.build()
return suspendCoroutine {

View File

@ -1,25 +1,24 @@
package io.github.wulkanowy.utils
import android.app.Activity
import android.content.Context
import android.os.Bundle
import com.google.firebase.Firebase
import com.google.firebase.analytics.FirebaseAnalytics
import com.google.firebase.crashlytics.FirebaseCrashlytics
import dagger.hilt.android.qualifiers.ApplicationContext
import com.google.firebase.analytics.analytics
import com.google.firebase.crashlytics.crashlytics
import io.github.wulkanowy.data.repositories.PreferencesRepository
import javax.inject.Inject
import javax.inject.Singleton
@Singleton
class AnalyticsHelper @Inject constructor(
@ApplicationContext private val context: Context,
preferencesRepository: PreferencesRepository,
appInfo: AppInfo,
) {
private val analytics by lazy { FirebaseAnalytics.getInstance(context) }
private val analytics by lazy { Firebase.analytics }
private val crashlytics by lazy { FirebaseCrashlytics.getInstance() }
private val crashlytics by lazy { Firebase.crashlytics }
init {
if (!appInfo.isDebug) {

View File

@ -1,5 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen xmlns:app="http://schemas.android.com/apk/res-auto">
<SwitchPreferenceCompat
app:defaultValue="@string/pref_key_ads_enabled"
app:iconSpaceReserved="false"
app:key="@string/pref_key_ads_enabled"
app:singleLineTitle="false"
app:title="@string/pref_ads_show_in_app" />
<PreferenceCategory
app:iconSpaceReserved="false"
app:title="@string/pref_ads_agreements">
@ -8,25 +14,17 @@
app:key="@string/pref_key_ads_privacy_policy"
app:singleLineTitle="false"
app:title="@string/pref_ads_privacy_policy" />
<CheckBoxPreference
app:defaultValue="@bool/pref_default_ads_consent_data_processing"
<Preference
app:dependency="@string/pref_key_ads_enabled"
app:iconSpaceReserved="false"
app:key="@string/pref_key_ads_consent_data_processing"
app:key="@string/pref_key_ads_ump_agreements"
app:singleLineTitle="false"
app:title="@string/pref_ads_consent" />
</PreferenceCategory>
<PreferenceCategory
app:iconSpaceReserved="false"
app:title="@string/pref_ads_support_category_name">
<SwitchPreferenceCompat
app:defaultValue="@string/pref_key_ads_enabled"
app:dependency="@string/pref_key_ads_consent_data_processing"
app:iconSpaceReserved="false"
app:key="@string/pref_key_ads_enabled"
app:singleLineTitle="false"
app:title="@string/pref_ads_show_in_app" />
<Preference
app:dependency="@string/pref_key_ads_consent_data_processing"
app:iconSpaceReserved="false"
app:key="@string/pref_key_ads_single_support"
app:singleLineTitle="false"

View File

@ -1,8 +1,8 @@
buildscript {
ext {
kotlin_version = '1.9.21'
about_libraries = '10.9.2'
hilt_version = '2.49'
kotlin_version = '1.9.22'
about_libraries = '10.10.0'
hilt_version = '2.50'
}
repositories {
mavenCentral()
@ -13,15 +13,17 @@ buildscript {
dependencies {
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.9.20-Beta2"
classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version"
classpath "com.google.devtools.ksp:com.google.devtools.ksp.gradle.plugin:$kotlin_version-1.0.15"
// NOTE: Version downgraded due to https://github.com/appwrite/sdk-for-android/issues/42.
classpath 'com.android.tools.build:gradle:8.1.1'
classpath "com.google.dagger:hilt-android-gradle-plugin:$hilt_version"
classpath 'com.google.gms:google-services:4.4.0'
classpath 'com.huawei.agconnect:agcp:1.9.1.301'
classpath 'com.huawei.agconnect:agcp:1.9.1.303'
classpath 'com.google.firebase:firebase-crashlytics-gradle:2.9.9'
classpath "com.github.triplet.gradle:play-publisher:3.8.4"
classpath "ru.cian:huawei-publish-gradle-plugin:1.4.0"
classpath "ru.cian:huawei-publish-gradle-plugin:1.4.2"
classpath "org.sonarsource.scanner.gradle:sonarqube-gradle-plugin:4.4.1.3373"
classpath "gradle.plugin.com.star-zero.gradle:githook:1.2.0"
classpath "com.mikepenz.aboutlibraries.plugin:aboutlibraries-plugin:$about_libraries"

View File

@ -6,7 +6,6 @@ kotlin.code.style=official
android.useAndroidX=true
android.enableJetifier=true
android.nonTransitiveRClass=false
android.defaults.buildfeatures.buildconfig=true
#
# https://developer.huawei.com/consumer/en/doc/development/AppGallery-connect-Guides/agc-common-faq-0000001063210244#section17273113244910
apmsInstrumentationEnabled=false