From d8f644c5b4081d44e76a53a60067b31cb7baadf5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Borcz?= Date: Sun, 26 Jun 2022 13:28:35 +0200 Subject: [PATCH] Add ads to dashboard (#1815) --- app/build.gradle | 3 + .../io/github/wulkanowy/utils/AdsHelper.kt | 28 +++++++ .../io/github/wulkanowy/utils/AdsHelper.kt | 28 +++++++ .../java/io/github/wulkanowy/WulkanowyApp.kt | 4 + .../repositories/PreferencesRepository.kt | 38 +++++++-- .../ui/modules/dashboard/DashboardFragment.kt | 9 +++ .../ui/modules/dashboard/DashboardItem.kt | 23 +++--- .../dashboard/DashboardItemMoveCallback.kt | 3 +- .../modules/dashboard/DashboardPresenter.kt | 35 +++++++- .../ui/modules/dashboard/DashboardView.kt | 4 +- .../{ => adapters}/DashboardAdapter.kt | 48 ++++++----- .../DashboardAnnouncementsAdapter.kt | 4 +- .../DashboardConferencesAdapter.kt | 4 +- .../{ => adapters}/DashboardExamsAdapter.kt | 4 +- .../{ => adapters}/DashboardGradesAdapter.kt | 2 +- .../DashboardHomeworkAdapter.kt | 4 +- .../wulkanowy/ui/modules/main/MainActivity.kt | 46 +++++++++++ .../ui/modules/main/MainPresenter.kt | 63 ++++++++++++--- .../wulkanowy/ui/modules/main/MainView.kt | 6 ++ .../wulkanowy/utils/ContextExtension.kt | 1 + .../main/res/layout/dialog_ads_consent.xml | 79 +++++++++++++++++++ .../main/res/layout/item_dashboard_ads.xml | 15 ++++ .../main/res/values/preferences_defaults.xml | 2 + app/src/main/res/values/preferences_keys.xml | 4 + app/src/main/res/values/strings.xml | 7 ++ .../ui/modules/settings/ads/AdsFragment.kt | 70 ++++++++++++++-- .../ui/modules/settings/ads/AdsPresenter.kt | 64 ++++++++++++--- .../ui/modules/settings/ads/AdsView.kt | 8 +- .../io/github/wulkanowy/utils/AdsHelper.kt | 59 ++++++++++++-- .../play/res/xml/scheme_preferences_ads.xml | 27 ++++++- .../ui/modules/main/MainPresenterTest.kt | 14 +++- 31 files changed, 621 insertions(+), 85 deletions(-) create mode 100644 app/src/fdroid/java/io/github/wulkanowy/utils/AdsHelper.kt create mode 100644 app/src/hms/java/io/github/wulkanowy/utils/AdsHelper.kt rename app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/{ => adapters}/DashboardAdapter.kt (96%) rename app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/{ => adapters}/DashboardAnnouncementsAdapter.kt (95%) rename app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/{ => adapters}/DashboardConferencesAdapter.kt (95%) rename app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/{ => adapters}/DashboardExamsAdapter.kt (97%) rename app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/{ => adapters}/DashboardGradesAdapter.kt (96%) rename app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/{ => adapters}/DashboardHomeworkAdapter.kt (97%) create mode 100644 app/src/main/res/layout/dialog_ads_consent.xml create mode 100644 app/src/main/res/layout/item_dashboard_ads.xml diff --git a/app/build.gradle b/app/build.gradle index 3e491206..29325abf 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -43,6 +43,7 @@ android { } buildConfigField "String", "SINGLE_SUPPORT_AD_ID", "null" + buildConfigField "String", "DASHBOARD_TILE_AD_ID", "null" if (System.env.SET_BUILD_TIMESTAMP) { buildConfigField "long", "BUILD_TIMESTAMP", String.valueOf(System.currentTimeMillis()) @@ -99,6 +100,8 @@ android { admob_project_id: System.getenv("ADMOB_PROJECT_ID") ?: "ca-app-pub-3940256099942544~3347511713" ] buildConfigField "String", "SINGLE_SUPPORT_AD_ID", "\"${System.getenv("SINGLE_SUPPORT_AD_ID") ?: "ca-app-pub-3940256099942544/5354046379"}\"" + buildConfigField "String", "DASHBOARD_TILE_AD_ID", "\"${System.getenv("DASHBOARD_TILE_AD_ID") ?: "ca-app-pub-3940256099942544/6300978111"}\"" + } fdroid { diff --git a/app/src/fdroid/java/io/github/wulkanowy/utils/AdsHelper.kt b/app/src/fdroid/java/io/github/wulkanowy/utils/AdsHelper.kt new file mode 100644 index 00000000..461d2995 --- /dev/null +++ b/app/src/fdroid/java/io/github/wulkanowy/utils/AdsHelper.kt @@ -0,0 +1,28 @@ +package io.github.wulkanowy.utils + +import android.content.Context +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 javax.inject.Inject + +@Suppress("unused") +class AdsHelper @Inject constructor( + @ApplicationContext private val context: Context, + private val preferencesRepository: PreferencesRepository +) { + + fun initialize() { + preferencesRepository.isAdsEnabled = false + preferencesRepository.isAgreeToProcessData = false + preferencesRepository.selectedDashboardTiles -= DashboardItem.Tile.ADS + } + + @Suppress("RedundantSuspendModifier", "UNUSED_PARAMETER") + suspend fun getDashboardTileAdBanner(width: Int): AdBanner { + throw IllegalStateException("Can't get ad banner (F-droid)") + } +} + +data class AdBanner(val view: View) diff --git a/app/src/hms/java/io/github/wulkanowy/utils/AdsHelper.kt b/app/src/hms/java/io/github/wulkanowy/utils/AdsHelper.kt new file mode 100644 index 00000000..0e922702 --- /dev/null +++ b/app/src/hms/java/io/github/wulkanowy/utils/AdsHelper.kt @@ -0,0 +1,28 @@ +package io.github.wulkanowy.utils + +import android.content.Context +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 javax.inject.Inject + +@Suppress("unused") +class AdsHelper @Inject constructor( + @ApplicationContext private val context: Context, + private val preferencesRepository: PreferencesRepository +) { + + fun initialize() { + preferencesRepository.isAdsEnabled = false + preferencesRepository.isAgreeToProcessData = false + preferencesRepository.selectedDashboardTiles -= DashboardItem.Tile.ADS + } + + @Suppress("RedundantSuspendModifier", "UNUSED_PARAMETER") + suspend fun getDashboardTileAdBanner(width: Int): AdBanner { + throw IllegalStateException("Can't get ad banner (HMS)") + } +} + +data class AdBanner(val view: View) diff --git a/app/src/main/java/io/github/wulkanowy/WulkanowyApp.kt b/app/src/main/java/io/github/wulkanowy/WulkanowyApp.kt index b5103e3e..7d2eeb1e 100644 --- a/app/src/main/java/io/github/wulkanowy/WulkanowyApp.kt +++ b/app/src/main/java/io/github/wulkanowy/WulkanowyApp.kt @@ -31,10 +31,14 @@ class WulkanowyApp : Application(), Configuration.Provider { @Inject lateinit var analyticsHelper: AnalyticsHelper + @Inject + lateinit var adsHelper: AdsHelper + override fun onCreate() { super.onCreate() initializeAppLanguage() themeManager.applyDefaultTheme() + adsHelper.initialize() initLogging() } diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/PreferencesRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/PreferencesRepository.kt index 4cd85586..237fb1a0 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/PreferencesRepository.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/PreferencesRepository.kt @@ -222,16 +222,14 @@ class PreferencesRepository @Inject constructor( get() = selectedDashboardTilesPreference.asFlow() .map { set -> set.map { DashboardItem.Tile.valueOf(it) } - .plus(DashboardItem.Tile.ACCOUNT) - .plus(DashboardItem.Tile.ADMIN_MESSAGE) + .plus(listOf(DashboardItem.Tile.ACCOUNT, DashboardItem.Tile.ADMIN_MESSAGE)) .toSet() } var selectedDashboardTiles: Set get() = selectedDashboardTilesPreference.get() .map { DashboardItem.Tile.valueOf(it) } - .plus(DashboardItem.Tile.ACCOUNT) - .plus(DashboardItem.Tile.ADMIN_MESSAGE) + .plus(listOf(DashboardItem.Tile.ACCOUNT, DashboardItem.Tile.ADMIN_MESSAGE)) .toSet() set(value) { val filteredValue = value.filterNot { it == DashboardItem.Tile.ACCOUNT } @@ -271,7 +269,33 @@ class PreferencesRepository @Inject constructor( var isAppReviewDone: Boolean get() = sharedPref.getBoolean(PREF_KEY_IN_APP_REVIEW_DONE, false) - set(value) = sharedPref.edit().putBoolean(PREF_KEY_IN_APP_REVIEW_DONE, value).apply() + set(value) = sharedPref.edit { putBoolean(PREF_KEY_IN_APP_REVIEW_DONE, value) } + + var isAppSupportShown: Boolean + 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) } + + var isAdsEnabled: Boolean + get() = getBoolean( + R.string.pref_key_ads_enabled, + R.bool.pref_default_ads_enabled + ) + set(value) = sharedPref.edit { + putBoolean(context.getString(R.string.pref_key_ads_enabled), value) + } private fun getLong(id: Int, default: Int) = getLong(context.getString(id), default) @@ -301,6 +325,10 @@ class PreferencesRepository @Inject constructor( 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" } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/DashboardFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/DashboardFragment.kt index 65832bdb..de0b4a6c 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/DashboardFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/DashboardFragment.kt @@ -18,6 +18,7 @@ import io.github.wulkanowy.ui.base.BaseFragment import io.github.wulkanowy.ui.modules.account.accountdetails.AccountDetailsFragment import io.github.wulkanowy.ui.modules.attendance.summary.AttendanceSummaryFragment import io.github.wulkanowy.ui.modules.conference.ConferenceFragment +import io.github.wulkanowy.ui.modules.dashboard.adapters.DashboardAdapter import io.github.wulkanowy.ui.modules.exam.ExamFragment import io.github.wulkanowy.ui.modules.grade.GradeFragment import io.github.wulkanowy.ui.modules.homework.HomeworkFragment @@ -47,6 +48,14 @@ class DashboardFragment : BaseFragment(R.layout.fragme override var subtitleString = LocalDate.now().toFormattedString("EEEE, d MMMM yyyy").capitalise() + override val tileWidth: Int + get() { + val recyclerWidth = binding.dashboardRecycler.width + val margin = requireContext().dpToPx(24f).toInt() + + return ((recyclerWidth - margin) / resources.displayMetrics.density).toInt() + } + companion object { fun newInstance() = DashboardFragment() diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/DashboardItem.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/DashboardItem.kt index c20bae7f..e220ae23 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/DashboardItem.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/DashboardItem.kt @@ -1,13 +1,9 @@ package io.github.wulkanowy.ui.modules.dashboard -import io.github.wulkanowy.data.db.entities.AdminMessage -import io.github.wulkanowy.data.db.entities.Conference -import io.github.wulkanowy.data.db.entities.Exam -import io.github.wulkanowy.data.db.entities.Grade -import io.github.wulkanowy.data.db.entities.SchoolAnnouncement -import io.github.wulkanowy.data.db.entities.Student +import io.github.wulkanowy.data.db.entities.* import io.github.wulkanowy.data.enums.GradeColorTheme import io.github.wulkanowy.data.pojos.TimetableFull +import io.github.wulkanowy.utils.AdBanner import io.github.wulkanowy.data.db.entities.Homework as EntitiesHomework sealed class DashboardItem(val type: Type) { @@ -106,17 +102,26 @@ sealed class DashboardItem(val type: Type) { override val isDataLoaded get() = conferences != null } + data class Ads( + val adBanner: AdBanner? = null, + override val error: Throwable? = null, + override val isLoading: Boolean = false + ) : DashboardItem(Type.ADS) { + + override val isDataLoaded get() = adBanner != null + } + enum class Type { ADMIN_MESSAGE, ACCOUNT, HORIZONTAL_GROUP, LESSONS, + ADS, GRADES, HOMEWORK, ANNOUNCEMENTS, EXAMS, CONFERENCES, - ADS } enum class Tile { @@ -126,12 +131,12 @@ sealed class DashboardItem(val type: Type) { MESSAGES, ATTENDANCE, LESSONS, + ADS, GRADES, HOMEWORK, ANNOUNCEMENTS, EXAMS, CONFERENCES, - ADS } } @@ -148,4 +153,4 @@ fun DashboardItem.Tile.toDashboardItemType() = when (this) { DashboardItem.Tile.EXAMS -> DashboardItem.Type.EXAMS DashboardItem.Tile.CONFERENCES -> DashboardItem.Type.CONFERENCES DashboardItem.Tile.ADS -> DashboardItem.Type.ADS -} \ No newline at end of file +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/DashboardItemMoveCallback.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/DashboardItemMoveCallback.kt index b9625570..9c15acc3 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/DashboardItemMoveCallback.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/DashboardItemMoveCallback.kt @@ -2,7 +2,8 @@ package io.github.wulkanowy.ui.modules.dashboard import androidx.recyclerview.widget.ItemTouchHelper import androidx.recyclerview.widget.RecyclerView -import java.util.Collections +import io.github.wulkanowy.ui.modules.dashboard.adapters.DashboardAdapter +import java.util.* class DashboardItemMoveCallback( private val dashboardAdapter: DashboardAdapter, diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/DashboardPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/DashboardPresenter.kt index c33955bc..e963a020 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/DashboardPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/DashboardPresenter.kt @@ -8,6 +8,7 @@ import io.github.wulkanowy.data.enums.MessageFolder import io.github.wulkanowy.data.repositories.* 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.* @@ -31,7 +32,8 @@ class DashboardPresenter @Inject constructor( private val conferenceRepository: ConferenceRepository, private val preferencesRepository: PreferencesRepository, private val schoolAnnouncementRepository: SchoolAnnouncementRepository, - private val adminMessageRepository: AdminMessageRepository + private val adminMessageRepository: AdminMessageRepository, + private val adsHelper: AdsHelper ) : BasePresenter(errorHandler, studentRepository) { private val dashboardItemLoadedList = mutableListOf() @@ -166,7 +168,7 @@ class DashboardPresenter @Inject constructor( DashboardItem.Type.CONFERENCES -> { loadConferences(student, forceRefresh) } - DashboardItem.Type.ADS -> TODO() + DashboardItem.Type.ADS -> loadAds(forceRefresh) DashboardItem.Type.ADMIN_MESSAGE -> loadAdminMessage(student, forceRefresh) } } @@ -595,6 +597,23 @@ class DashboardPresenter @Inject constructor( .launchWithUniqueRefreshJob("dashboard_admin_messages", forceRefresh) } + private fun loadAds(forceRefresh: Boolean) { + presenterScope.launch { + if (!forceRefresh) { + updateData(DashboardItem.Ads(), forceRefresh) + } + + val dashboardAdItem = + runCatching { + DashboardItem.Ads(adsHelper.getDashboardTileAdBanner(view!!.tileWidth)) + } + .onFailure { Timber.e(it) } + .getOrElse { DashboardItem.Ads(error = it) } + + updateData(dashboardAdItem, forceRefresh) + } + } + private fun updateData(dashboardItem: DashboardItem, forceRefresh: Boolean) { val isForceRefreshError = forceRefresh && dashboardItem.error != null val isFirstRunDataLoadedError = @@ -619,6 +638,18 @@ class DashboardPresenter @Inject constructor( } } + if (dashboardItem is DashboardItem.Ads) { + if (!dashboardItem.isDataLoaded) { + dashboardItemsToLoad = dashboardItemsToLoad - DashboardItem.Type.ADS + dashboardTileLoadedList = dashboardTileLoadedList - DashboardItem.Tile.ADS + + dashboardItemLoadedList.removeAll { it.type == DashboardItem.Type.ADS } + } else { + dashboardItemsToLoad = dashboardItemsToLoad + DashboardItem.Type.ADS + dashboardTileLoadedList = dashboardTileLoadedList + DashboardItem.Tile.ADS + } + } + if (forceRefresh) { updateForceRefreshData(dashboardItem) } else { diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/DashboardView.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/DashboardView.kt index 2cc2f1d2..76788543 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/DashboardView.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/DashboardView.kt @@ -4,6 +4,8 @@ import io.github.wulkanowy.ui.base.BaseView interface DashboardView : BaseView { + val tileWidth: Int + fun initView() fun updateData(data: List) @@ -27,4 +29,4 @@ interface DashboardView : BaseView { fun openNotificationsCenterView() fun openInternetBrowser(url: String) -} \ No newline at end of file +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/DashboardAdapter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/adapters/DashboardAdapter.kt similarity index 96% rename from app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/DashboardAdapter.kt rename to app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/adapters/DashboardAdapter.kt index 9191d43c..a3c423a8 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/DashboardAdapter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/adapters/DashboardAdapter.kt @@ -1,4 +1,4 @@ -package io.github.wulkanowy.ui.modules.dashboard +package io.github.wulkanowy.ui.modules.dashboard.adapters import android.annotation.SuppressLint import android.content.res.ColorStateList @@ -22,24 +22,15 @@ import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.db.entities.Timetable import io.github.wulkanowy.data.db.entities.TimetableHeader import io.github.wulkanowy.data.enums.GradeColorTheme -import io.github.wulkanowy.databinding.ItemDashboardAccountBinding -import io.github.wulkanowy.databinding.ItemDashboardAdminMessageBinding -import io.github.wulkanowy.databinding.ItemDashboardAnnouncementsBinding -import io.github.wulkanowy.databinding.ItemDashboardConferencesBinding -import io.github.wulkanowy.databinding.ItemDashboardExamsBinding -import io.github.wulkanowy.databinding.ItemDashboardGradesBinding -import io.github.wulkanowy.databinding.ItemDashboardHomeworkBinding -import io.github.wulkanowy.databinding.ItemDashboardHorizontalGroupBinding -import io.github.wulkanowy.databinding.ItemDashboardLessonsBinding -import io.github.wulkanowy.utils.createNameInitialsDrawable -import io.github.wulkanowy.utils.dpToPx -import io.github.wulkanowy.utils.getThemeAttrColor -import io.github.wulkanowy.utils.left -import io.github.wulkanowy.utils.nickOrName -import io.github.wulkanowy.utils.toFormattedString +import io.github.wulkanowy.databinding.* +import io.github.wulkanowy.ui.modules.dashboard.DashboardItem +import io.github.wulkanowy.utils.* import timber.log.Timber -import java.time.* -import java.util.Timer +import java.time.Duration +import java.time.Instant +import java.time.LocalDate +import java.time.LocalDateTime +import java.util.* import javax.inject.Inject import kotlin.concurrent.timer @@ -120,6 +111,9 @@ class DashboardAdapter @Inject constructor() : RecyclerView.Adapter AdminMessageViewHolder( ItemDashboardAdminMessageBinding.inflate(inflater, parent, false) ) + DashboardItem.Type.ADS.ordinal -> AdsViewHolder( + ItemDashboardAdsBinding.inflate(inflater, parent, false) + ) else -> throw IllegalArgumentException() } } @@ -135,6 +129,7 @@ class DashboardAdapter @Inject constructor() : RecyclerView.Adapter bindExamsViewHolder(holder, position) is ConferencesViewHolder -> bindConferencesViewHolder(holder, position) is AdminMessageViewHolder -> bindAdminMessage(holder, position) + is AdsViewHolder -> bindAdsViewHolder(holder, position) } } @@ -746,6 +741,20 @@ class DashboardAdapter @Inject constructor() : RecyclerView.Adapter, private val oldList: List diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/DashboardAnnouncementsAdapter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/adapters/DashboardAnnouncementsAdapter.kt similarity index 95% rename from app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/DashboardAnnouncementsAdapter.kt rename to app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/adapters/DashboardAnnouncementsAdapter.kt index 7a4c2b25..da588015 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/DashboardAnnouncementsAdapter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/adapters/DashboardAnnouncementsAdapter.kt @@ -1,4 +1,4 @@ -package io.github.wulkanowy.ui.modules.dashboard +package io.github.wulkanowy.ui.modules.dashboard.adapters import android.view.LayoutInflater import android.view.ViewGroup @@ -33,4 +33,4 @@ class DashboardAnnouncementsAdapter : class ViewHolder(val binding: SubitemDashboardAnnouncementsBinding) : RecyclerView.ViewHolder(binding.root) -} \ No newline at end of file +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/DashboardConferencesAdapter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/adapters/DashboardConferencesAdapter.kt similarity index 95% rename from app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/DashboardConferencesAdapter.kt rename to app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/adapters/DashboardConferencesAdapter.kt index 64cf599c..1244ff60 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/DashboardConferencesAdapter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/adapters/DashboardConferencesAdapter.kt @@ -1,4 +1,4 @@ -package io.github.wulkanowy.ui.modules.dashboard +package io.github.wulkanowy.ui.modules.dashboard.adapters import android.view.LayoutInflater import android.view.ViewGroup @@ -33,4 +33,4 @@ class DashboardConferencesAdapter : class ViewHolder(val binding: SubitemDashboardConferencesBinding) : RecyclerView.ViewHolder(binding.root) -} \ No newline at end of file +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/DashboardExamsAdapter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/adapters/DashboardExamsAdapter.kt similarity index 97% rename from app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/DashboardExamsAdapter.kt rename to app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/adapters/DashboardExamsAdapter.kt index 060f224b..583bf29d 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/DashboardExamsAdapter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/adapters/DashboardExamsAdapter.kt @@ -1,4 +1,4 @@ -package io.github.wulkanowy.ui.modules.dashboard +package io.github.wulkanowy.ui.modules.dashboard.adapters import android.annotation.SuppressLint import android.view.LayoutInflater @@ -56,4 +56,4 @@ class DashboardExamsAdapter : class ViewHolder(val binding: SubitemDashboardExamsBinding) : RecyclerView.ViewHolder(binding.root) -} \ No newline at end of file +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/DashboardGradesAdapter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/adapters/DashboardGradesAdapter.kt similarity index 96% rename from app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/DashboardGradesAdapter.kt rename to app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/adapters/DashboardGradesAdapter.kt index afffcc51..d00df9d4 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/DashboardGradesAdapter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/adapters/DashboardGradesAdapter.kt @@ -1,4 +1,4 @@ -package io.github.wulkanowy.ui.modules.dashboard +package io.github.wulkanowy.ui.modules.dashboard.adapters import android.view.LayoutInflater import android.view.ViewGroup diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/DashboardHomeworkAdapter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/adapters/DashboardHomeworkAdapter.kt similarity index 97% rename from app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/DashboardHomeworkAdapter.kt rename to app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/adapters/DashboardHomeworkAdapter.kt index 55ec9029..8105ff9c 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/DashboardHomeworkAdapter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/adapters/DashboardHomeworkAdapter.kt @@ -1,4 +1,4 @@ -package io.github.wulkanowy.ui.modules.dashboard +package io.github.wulkanowy.ui.modules.dashboard.adapters import android.annotation.SuppressLint import android.view.LayoutInflater @@ -53,4 +53,4 @@ class DashboardHomeworkAdapter : RecyclerView.Adapter(), MainVie inAppReviewHelper.showInAppReview(this) } + override fun showAppSupport() { + MaterialAlertDialogBuilder(this) + .setTitle(R.string.main_support_title) + .setMessage(R.string.main_support_description) + .setPositiveButton(R.string.main_support_positive) { _, _ -> presenter.onEnableAdsSelected() } + .setNegativeButton(android.R.string.cancel) { _, _ -> } + .setOnDismissListener { } + .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) diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainPresenter.kt index a07bdb37..8f457d92 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainPresenter.kt @@ -14,11 +14,15 @@ import io.github.wulkanowy.ui.base.ErrorHandler import io.github.wulkanowy.ui.modules.Destination import io.github.wulkanowy.ui.modules.account.AccountView import io.github.wulkanowy.ui.modules.account.accountdetails.AccountDetailsView +import io.github.wulkanowy.ui.modules.dashboard.DashboardItem import io.github.wulkanowy.ui.modules.grade.GradeView import io.github.wulkanowy.ui.modules.message.MessageView import io.github.wulkanowy.ui.modules.schoolandteachers.SchoolAndTeachersView import io.github.wulkanowy.ui.modules.studentinfo.StudentInfoView +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 @@ -29,10 +33,12 @@ import javax.inject.Inject class MainPresenter @Inject constructor( errorHandler: ErrorHandler, studentRepository: StudentRepository, - private val prefRepository: PreferencesRepository, + private val preferencesRepository: PreferencesRepository, private val syncManager: SyncManager, private val analytics: AnalyticsHelper, - private val json: Json + private val json: Json, + private val adsHelper: AdsHelper, + private val appInfo: AppInfo ) : BasePresenter(errorHandler, studentRepository) { private var studentsWitSemesters: List? = null @@ -47,7 +53,7 @@ class MainPresenter @Inject constructor( private val Destination?.startMenuIndex get() = when { - this == null -> prefRepository.startMenuIndex + this == null -> preferencesRepository.startMenuIndex destinationType in rootDestinationTypeList -> { rootDestinationTypeList.indexOf(destinationType) } @@ -71,6 +77,8 @@ class MainPresenter @Inject constructor( syncManager.startPeriodicSyncWorker() + checkAppSupport() + analytics.logEvent("app_open", "destination" to initDestination.toString()) Timber.i("Main view was initialized with $initDestination") } @@ -155,18 +163,53 @@ class MainPresenter @Inject constructor( } == true } - private fun checkInAppReview() { - prefRepository.inAppReviewCount++ + fun onEnableAdsSelected() { + view?.showPrivacyPolicyDialog() + } - if (prefRepository.inAppReviewDate == null) { - prefRepository.inAppReviewDate = Instant.now() + fun onPrivacyAgree(isPersonalizedAds: Boolean) { + preferencesRepository.isAdsEnabled = true + preferencesRepository.isAgreeToProcessData = true + preferencesRepository.isPersonalizedAdsEnabled = isPersonalizedAds + + adsHelper.initialize() + + preferencesRepository.selectedDashboardTiles += DashboardItem.Tile.ADS + } + + fun onPrivacySelected() { + view?.openPrivacyPolicy() + } + + private fun checkInAppReview() { + preferencesRepository.inAppReviewCount++ + + if (preferencesRepository.inAppReviewDate == null) { + preferencesRepository.inAppReviewDate = Instant.now() } - if (!prefRepository.isAppReviewDone && prefRepository.inAppReviewCount >= 50 && - Instant.now().minus(Duration.ofDays(14)).isAfter(prefRepository.inAppReviewDate) + if (!preferencesRepository.isAppReviewDone && preferencesRepository.inAppReviewCount >= 50 && + Instant.now().minus(Duration.ofDays(14)).isAfter(preferencesRepository.inAppReviewDate) ) { view?.showInAppReview() - prefRepository.isAppReviewDone = true + preferencesRepository.isAppReviewDone = true + } + } + + private fun checkAppSupport() { + if (!preferencesRepository.isAppSupportShown && !preferencesRepository.isAdsEnabled + && appInfo.buildFlavor == "play" + ) { + presenterScope.launch { + val student = runCatching { studentRepository.getCurrentStudent(false) } + .onFailure { Timber.e(it) } + .getOrElse { return@launch } + + if (Instant.now().minus(Duration.ofDays(28)).isAfter(student.registrationDate)) { + view?.showAppSupport() + preferencesRepository.isAppSupportShown = true + } + } } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainView.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainView.kt index 3a57fcc6..3d018e3d 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainView.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainView.kt @@ -41,6 +41,12 @@ interface MainView : BaseView { fun showInAppReview() + fun showAppSupport() + + fun showPrivacyPolicyDialog() + + fun openPrivacyPolicy() + fun openMoreDestination(destination: Destination) interface MainChildView { diff --git a/app/src/main/java/io/github/wulkanowy/utils/ContextExtension.kt b/app/src/main/java/io/github/wulkanowy/utils/ContextExtension.kt index 323e1e47..dd91d36d 100644 --- a/app/src/main/java/io/github/wulkanowy/utils/ContextExtension.kt +++ b/app/src/main/java/io/github/wulkanowy/utils/ContextExtension.kt @@ -13,6 +13,7 @@ import androidx.core.graphics.drawable.RoundedBitmapDrawable import androidx.core.graphics.drawable.RoundedBitmapDrawableFactory import androidx.core.graphics.drawable.toBitmap + @ColorInt fun Context.getThemeAttrColor(@AttrRes colorAttr: Int): Int { val array = obtainStyledAttributes(null, intArrayOf(colorAttr)) diff --git a/app/src/main/res/layout/dialog_ads_consent.xml b/app/src/main/res/layout/dialog_ads_consent.xml new file mode 100644 index 00000000..39510162 --- /dev/null +++ b/app/src/main/res/layout/dialog_ads_consent.xml @@ -0,0 +1,79 @@ + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/item_dashboard_ads.xml b/app/src/main/res/layout/item_dashboard_ads.xml new file mode 100644 index 00000000..b75bb27e --- /dev/null +++ b/app/src/main/res/layout/item_dashboard_ads.xml @@ -0,0 +1,15 @@ + + + + + + diff --git a/app/src/main/res/values/preferences_defaults.xml b/app/src/main/res/values/preferences_defaults.xml index deeb3696..9c092ec5 100644 --- a/app/src/main/res/values/preferences_defaults.xml +++ b/app/src/main/res/values/preferences_defaults.xml @@ -37,4 +37,6 @@ GRADES ANNOUNCEMENTS + false + false diff --git a/app/src/main/res/values/preferences_keys.xml b/app/src/main/res/values/preferences_keys.xml index 849d989e..f29080b3 100644 --- a/app/src/main/res/values/preferences_keys.xml +++ b/app/src/main/res/values/preferences_keys.xml @@ -37,4 +37,8 @@ notifications_piggyback notifications_piggyback_cancel_original single_ad_support + ads_enabled + ads_privacy_policy + ads_consent_data_processing + ads_over_eighteen diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 2ca516ad..db591309 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -85,6 +85,9 @@ Log in Session expired Session expired, log in again + Application support + Do you like this app? Support its development by enabling non-invasive ads that you can disable at any time + Enable ads @@ -715,6 +718,10 @@ Privacy policy Ad is loading Thank you for your support, come back later for more ads + Can we use your data to display ads? + 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 + Personalized ads + Non-personalized ads Advanced Appearance & Behavior diff --git a/app/src/play/java/io/github/wulkanowy/ui/modules/settings/ads/AdsFragment.kt b/app/src/play/java/io/github/wulkanowy/ui/modules/settings/ads/AdsFragment.kt index 8d31928b..48a6fc3e 100644 --- a/app/src/play/java/io/github/wulkanowy/ui/modules/settings/ads/AdsFragment.kt +++ b/app/src/play/java/io/github/wulkanowy/ui/modules/settings/ads/AdsFragment.kt @@ -2,12 +2,15 @@ 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.main.MainView @@ -36,6 +39,22 @@ class AdsFragment : PreferenceFragmentCompat(), MainView.TitledView, AdsView { presenter.onWatchSingleAdSelected() true } + + findPreference(getString(R.string.pref_key_ads_privacy_policy))?.setOnPreferenceClickListener { + presenter.onPrivacySelected() + true + } + + findPreference(getString(R.string.pref_key_ads_consent_data_processing)) + ?.setOnPreferenceChangeListener { _, newValue -> + presenter.onConsentSelected(newValue as Boolean) + true + } + + findPreference(getString(R.string.pref_key_ads_enabled))?.setOnPreferenceChangeListener { _, newValue -> + presenter.onAddEnabled(newValue as Boolean) + true + } } override fun showAd(ad: RewardedInterstitialAd) { @@ -45,13 +64,50 @@ class AdsFragment : PreferenceFragmentCompat(), MainView.TitledView, AdsView { } override fun showPrivacyPolicyDialog() { - MaterialAlertDialogBuilder(requireContext()) - .setTitle(getString(R.string.pref_ads_privacy_title)) - .setMessage(getString(R.string.pref_ads_privacy_description)) - .setPositiveButton(getString(R.string.pref_ads_privacy_agree)) { _, _ -> presenter.onAgreedPrivacy() } - .setNegativeButton(android.R.string.cancel) { _, _ -> } - .setNeutralButton(getString(R.string.pref_ads_privacy_link)) { _, _ -> presenter.onPrivacySelected() } + 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(getString(R.string.pref_key_ads_consent_data_processing)) + ?.summary = summaryText + } + + override fun setCheckedProcessingData(checked: Boolean) { + findPreference(getString(R.string.pref_key_ads_consent_data_processing)) + ?.isChecked = checked + } + + override fun setCheckedAdsEnabled(checked: Boolean) { + findPreference(getString(R.string.pref_key_ads_enabled)) + ?.isChecked = checked } override fun openPrivacyPolicy() { @@ -98,4 +154,4 @@ class AdsFragment : PreferenceFragmentCompat(), MainView.TitledView, AdsView { override fun showErrorDetailsDialog(error: Throwable) { ErrorDialog.newInstance(error).show(childFragmentManager, error.toString()) } -} \ No newline at end of file +} diff --git a/app/src/play/java/io/github/wulkanowy/ui/modules/settings/ads/AdsPresenter.kt b/app/src/play/java/io/github/wulkanowy/ui/modules/settings/ads/AdsPresenter.kt index 5ccbce1e..85c14c0a 100644 --- a/app/src/play/java/io/github/wulkanowy/ui/modules/settings/ads/AdsPresenter.kt +++ b/app/src/play/java/io/github/wulkanowy/ui/modules/settings/ads/AdsPresenter.kt @@ -1,8 +1,10 @@ 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 +import io.github.wulkanowy.ui.modules.dashboard.DashboardItem import io.github.wulkanowy.utils.AdsHelper import kotlinx.coroutines.launch import timber.log.Timber @@ -11,24 +13,22 @@ import javax.inject.Inject class AdsPresenter @Inject constructor( errorHandler: ErrorHandler, studentRepository: StudentRepository, - private val adsHelper: AdsHelper + private val adsHelper: AdsHelper, + private val preferencesRepository: PreferencesRepository ) : BasePresenter(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() { - view?.showPrivacyPolicyDialog() - } - - fun onPrivacySelected() { - view?.openPrivacyPolicy() - } - - fun onAgreedPrivacy() { view?.showLoadingSupportAd(true) presenterScope.launch { runCatching { adsHelper.getSupportAd() } @@ -41,4 +41,48 @@ class AdsPresenter @Inject constructor( } } } -} \ No newline at end of file + + fun onConsentSelected(isChecked: Boolean) { + if (isChecked) { + view?.showPrivacyPolicyDialog() + } else { + view?.showProcessingDataSummary(null) + view?.setCheckedAdsEnabled(false) + onAddEnabled(false) + } + } + + fun onPrivacySelected() { + view?.openPrivacyPolicy() + } + + fun onPrivacyDialogCanceled() { + view?.setCheckedProcessingData(false) + } + + 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 onAddEnabled(isEnabled: Boolean) { + if (isEnabled) { + preferencesRepository.selectedDashboardTiles += DashboardItem.Tile.ADS + } else { + preferencesRepository.selectedDashboardTiles -= DashboardItem.Tile.ADS + } + } +} diff --git a/app/src/play/java/io/github/wulkanowy/ui/modules/settings/ads/AdsView.kt b/app/src/play/java/io/github/wulkanowy/ui/modules/settings/ads/AdsView.kt index 89de7bd1..8de6e60d 100644 --- a/app/src/play/java/io/github/wulkanowy/ui/modules/settings/ads/AdsView.kt +++ b/app/src/play/java/io/github/wulkanowy/ui/modules/settings/ads/AdsView.kt @@ -16,4 +16,10 @@ interface AdsView : BaseView { fun showLoadingSupportAd(show: Boolean) fun showWatchAdOncePerVisit(show: Boolean) -} \ No newline at end of file + + fun setCheckedAdsEnabled(checked: Boolean) + + fun setCheckedProcessingData(checked: Boolean) + + fun showProcessingDataSummary(isPersonalized: Boolean?) +} diff --git a/app/src/play/java/io/github/wulkanowy/utils/AdsHelper.kt b/app/src/play/java/io/github/wulkanowy/utils/AdsHelper.kt index dde4d012..6be8e924 100644 --- a/app/src/play/java/io/github/wulkanowy/utils/AdsHelper.kt +++ b/app/src/play/java/io/github/wulkanowy/utils/AdsHelper.kt @@ -2,27 +2,39 @@ package io.github.wulkanowy.utils import android.content.Context import android.os.Bundle +import android.view.View import com.google.ads.mediation.admob.AdMobAdapter -import com.google.android.gms.ads.AdRequest -import com.google.android.gms.ads.LoadAdError -import com.google.android.gms.ads.MobileAds +import com.google.android.gms.ads.* import com.google.android.gms.ads.rewardedinterstitial.RewardedInterstitialAd import com.google.android.gms.ads.rewardedinterstitial.RewardedInterstitialAdLoadCallback import dagger.hilt.android.qualifiers.ApplicationContext import io.github.wulkanowy.BuildConfig +import io.github.wulkanowy.data.repositories.PreferencesRepository import javax.inject.Inject import kotlin.coroutines.resume import kotlin.coroutines.resumeWithException import kotlin.coroutines.suspendCoroutine -class AdsHelper @Inject constructor(@ApplicationContext private val context: Context) { + +class AdsHelper @Inject constructor( + @ApplicationContext private val context: Context, + private val preferencesRepository: PreferencesRepository +) { + + fun initialize() { + if (preferencesRepository.isAgreeToProcessData) { + MobileAds.initialize(context) + } + } suspend fun getSupportAd(): RewardedInterstitialAd? { - MobileAds.initialize(context) - val extra = Bundle().apply { putString("npa", "1") } val adRequest = AdRequest.Builder() - .addNetworkExtrasBundle(AdMobAdapter::class.java, extra) + .apply { + if (!preferencesRepository.isPersonalizedAdsEnabled) { + addNetworkExtrasBundle(AdMobAdapter::class.java, extra) + } + } .build() return suspendCoroutine { @@ -41,4 +53,35 @@ class AdsHelper @Inject constructor(@ApplicationContext private val context: Con }) } } -} \ No newline at end of file + + suspend fun getDashboardTileAdBanner(width: Int): AdBanner { + val extra = Bundle().apply { putString("npa", "1") } + val adRequest = AdRequest.Builder() + .apply { + if (!preferencesRepository.isPersonalizedAdsEnabled) { + addNetworkExtrasBundle(AdMobAdapter::class.java, extra) + } + } + .build() + + return suspendCoroutine { + val adView = AdView(context).apply { + adSize = AdSize.getPortraitAnchoredAdaptiveBannerAdSize(context, width) + adUnitId = BuildConfig.DASHBOARD_TILE_AD_ID + adListener = object : AdListener() { + override fun onAdFailedToLoad(loadAdError: LoadAdError) { + it.resumeWithException(IllegalArgumentException(loadAdError.message)) + } + + override fun onAdLoaded() { + it.resume(AdBanner(this@apply)) + } + } + } + + adView.loadAd(adRequest) + } + } +} + +data class AdBanner(val view: View) diff --git a/app/src/play/res/xml/scheme_preferences_ads.xml b/app/src/play/res/xml/scheme_preferences_ads.xml index 52a3df58..d5e93a35 100644 --- a/app/src/play/res/xml/scheme_preferences_ads.xml +++ b/app/src/play/res/xml/scheme_preferences_ads.xml @@ -2,11 +2,34 @@ + app:title="Agreements"> + + + + + - \ No newline at end of file + diff --git a/app/src/test/java/io/github/wulkanowy/ui/modules/main/MainPresenterTest.kt b/app/src/test/java/io/github/wulkanowy/ui/modules/main/MainPresenterTest.kt index 720239e6..6cfab199 100644 --- a/app/src/test/java/io/github/wulkanowy/ui/modules/main/MainPresenterTest.kt +++ b/app/src/test/java/io/github/wulkanowy/ui/modules/main/MainPresenterTest.kt @@ -4,7 +4,9 @@ import io.github.wulkanowy.data.repositories.PreferencesRepository import io.github.wulkanowy.data.repositories.StudentRepository import io.github.wulkanowy.services.sync.SyncManager import io.github.wulkanowy.ui.base.ErrorHandler +import io.github.wulkanowy.utils.AdsHelper import io.github.wulkanowy.utils.AnalyticsHelper +import io.github.wulkanowy.utils.AppInfo import io.mockk.* import io.mockk.impl.annotations.MockK import kotlinx.serialization.json.Json @@ -31,6 +33,12 @@ class MainPresenterTest { @MockK(relaxed = true) lateinit var analytics: AnalyticsHelper + @MockK(relaxed = true) + lateinit var appInfo: AppInfo + + @MockK(relaxed = true) + lateinit var adsHelper: AdsHelper + private lateinit var presenter: MainPresenter @Before @@ -42,10 +50,12 @@ class MainPresenterTest { presenter = MainPresenter( errorHandler = errorHandler, studentRepository = studentRepository, - prefRepository = prefRepository, + preferencesRepository = prefRepository, syncManager = syncManager, analytics = analytics, - json = Json + json = Json, + appInfo = appInfo, + adsHelper = adsHelper ) presenter.onAttachView(mainView, null) }