Compare commits
22 Commits
Author | SHA1 | Date | |
---|---|---|---|
f893170dec | |||
cff08d6322 | |||
9dee7f01f6 | |||
8324a9cac3 | |||
5316e3e1bf | |||
81e80181f2 | |||
6ee38e9259 | |||
40df80371c | |||
a3596c35b8 | |||
66b7ea4cb4 | |||
770749e158 | |||
0aa83b020e | |||
4d1218d1d3 | |||
0ea6cbc8ed | |||
eb31f9578f | |||
f69d50d2c1 | |||
8a424ee6a4 | |||
7dfa48bbe3 | |||
d811cdb919 | |||
e2f2e21081 | |||
c812310497 | |||
7f6475cf35 |
@ -27,8 +27,8 @@ android {
|
||||
testApplicationId "io.github.tests.wulkanowy"
|
||||
minSdkVersion 21
|
||||
targetSdkVersion 34
|
||||
versionCode 139
|
||||
versionName "2.2.7"
|
||||
versionCode 142
|
||||
versionName "2.3.2"
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
|
||||
resValue "string", "app_name", "Wulkanowy"
|
||||
@ -163,7 +163,7 @@ play {
|
||||
track = 'production'
|
||||
releaseStatus = ReleaseStatus.IN_PROGRESS
|
||||
userFraction = 0.99d
|
||||
updatePriority = 5
|
||||
updatePriority = 3
|
||||
enabled.set(false)
|
||||
}
|
||||
|
||||
@ -188,12 +188,12 @@ ext {
|
||||
android_hilt = "1.1.0"
|
||||
room = "2.6.1"
|
||||
chucker = "4.0.0"
|
||||
mockk = "1.13.8"
|
||||
mockk = "1.13.9"
|
||||
coroutines = "1.7.3"
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation 'io.github.wulkanowy:sdk:2.2.7'
|
||||
implementation 'io.github.wulkanowy:sdk:2.3.4'
|
||||
|
||||
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.0.4'
|
||||
|
||||
@ -240,7 +240,7 @@ dependencies {
|
||||
implementation "com.squareup.okhttp3:logging-interceptor:4.12.0"
|
||||
|
||||
implementation "com.jakewharton.timber:timber:5.0.1"
|
||||
implementation "at.favre.lib:slf4j-timber:1.0.1"
|
||||
implementation 'com.github.Faierbel:slf4j-timber:2.0'
|
||||
implementation 'com.github.bastienpaulfr:Treessence:1.1.2'
|
||||
implementation "com.mikepenz:aboutlibraries-core:$about_libraries"
|
||||
implementation 'io.coil-kt:coil:2.5.0'
|
||||
@ -250,17 +250,19 @@ dependencies {
|
||||
implementation 'org.apache.commons:commons-text:1.11.0'
|
||||
|
||||
playImplementation platform('com.google.firebase:firebase-bom:32.7.0')
|
||||
playImplementation 'com.google.firebase:firebase-analytics-ktx'
|
||||
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.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.302'
|
||||
hmsImplementation 'com.huawei.agconnect:agconnect-crash:1.9.1.303'
|
||||
|
||||
releaseImplementation "com.github.chuckerteam.chucker:library-no-op:$chucker"
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
@ -12,7 +12,6 @@ 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.ActivityLifecycleLogger
|
||||
import io.github.wulkanowy.utils.AdsHelper
|
||||
import io.github.wulkanowy.utils.AnalyticsHelper
|
||||
import io.github.wulkanowy.utils.AppInfo
|
||||
import io.github.wulkanowy.utils.CrashLogExceptionTree
|
||||
@ -37,9 +36,6 @@ class WulkanowyApp : Application(), Configuration.Provider {
|
||||
@Inject
|
||||
lateinit var analyticsHelper: AnalyticsHelper
|
||||
|
||||
@Inject
|
||||
lateinit var adsHelper: AdsHelper
|
||||
|
||||
@Inject
|
||||
lateinit var remoteConfigHelper: RemoteConfigHelper
|
||||
|
||||
@ -56,7 +52,6 @@ class WulkanowyApp : Application(), Configuration.Provider {
|
||||
super.onCreate()
|
||||
initializeAppLanguage()
|
||||
themeManager.applyDefaultTheme()
|
||||
adsHelper.initialize()
|
||||
remoteConfigHelper.initialize()
|
||||
initLogging()
|
||||
}
|
||||
|
@ -1,11 +1,16 @@
|
||||
package io.github.wulkanowy.data.db.dao
|
||||
|
||||
import androidx.room.*
|
||||
import androidx.room.Dao
|
||||
import androidx.room.Delete
|
||||
import androidx.room.Insert
|
||||
import androidx.room.OnConflictStrategy
|
||||
import androidx.room.Query
|
||||
import androidx.room.Transaction
|
||||
import androidx.room.Update
|
||||
import io.github.wulkanowy.data.db.entities.Semester
|
||||
import io.github.wulkanowy.data.db.entities.Student
|
||||
import io.github.wulkanowy.data.db.entities.StudentName
|
||||
import io.github.wulkanowy.data.db.entities.StudentNickAndAvatar
|
||||
import io.github.wulkanowy.data.db.entities.StudentWithSemesters
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
@ -47,6 +52,9 @@ abstract class StudentDao {
|
||||
@Query("UPDATE Students SET is_current = 0")
|
||||
abstract suspend fun resetCurrent()
|
||||
|
||||
@Query("DELETE FROM Students WHERE email = :email AND user_name = :userName")
|
||||
abstract suspend fun deleteByEmailAndUserName(email: String, userName: String)
|
||||
|
||||
@Transaction
|
||||
open suspend fun switchCurrent(id: Long) {
|
||||
resetCurrent()
|
||||
|
@ -35,12 +35,15 @@ class LuckyNumberRepository @Inject constructor(
|
||||
fetch = {
|
||||
sdk.init(student).getLuckyNumber(student.schoolShortName)?.mapToEntity(student)
|
||||
},
|
||||
saveFetchResult = { old, new ->
|
||||
if (new != old) {
|
||||
old?.let { luckyNumberDb.deleteAll(listOfNotNull(it)) }
|
||||
luckyNumberDb.insertAll(listOfNotNull((new?.apply {
|
||||
if (notify) isNotified = false
|
||||
})))
|
||||
saveFetchResult = { oldLuckyNumber, newLuckyNumber ->
|
||||
newLuckyNumber ?: return@networkBoundResource
|
||||
|
||||
if (newLuckyNumber != oldLuckyNumber) {
|
||||
val updatedLuckNumberList =
|
||||
listOf(newLuckyNumber.apply { if (notify) isNotified = false })
|
||||
|
||||
oldLuckyNumber?.let { luckyNumberDb.deleteAll(listOfNotNull(it)) }
|
||||
luckyNumberDb.insertAll(updatedLuckNumberList)
|
||||
}
|
||||
}
|
||||
)
|
||||
|
@ -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
|
||||
@ -18,7 +23,7 @@ import kotlinx.coroutines.flow.map
|
||||
import kotlinx.serialization.encodeToString
|
||||
import kotlinx.serialization.json.Json
|
||||
import java.time.Instant
|
||||
import java.util.*
|
||||
import java.util.UUID
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@ -303,19 +308,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)
|
||||
@ -398,7 +390,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"
|
||||
}
|
||||
}
|
||||
|
@ -1,8 +1,6 @@
|
||||
package io.github.wulkanowy.data.repositories
|
||||
|
||||
import android.content.Context
|
||||
import androidx.room.withTransaction
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import io.github.wulkanowy.data.db.AppDatabase
|
||||
import io.github.wulkanowy.data.db.dao.SemesterDao
|
||||
import io.github.wulkanowy.data.db.dao.StudentDao
|
||||
@ -17,20 +15,19 @@ import io.github.wulkanowy.data.pojos.RegisterUser
|
||||
import io.github.wulkanowy.sdk.Sdk
|
||||
import io.github.wulkanowy.utils.DispatchersProvider
|
||||
import io.github.wulkanowy.utils.init
|
||||
import io.github.wulkanowy.utils.security.decrypt
|
||||
import io.github.wulkanowy.utils.security.encrypt
|
||||
import io.github.wulkanowy.utils.security.Scrambler
|
||||
import kotlinx.coroutines.withContext
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
class StudentRepository @Inject constructor(
|
||||
@ApplicationContext private val context: Context,
|
||||
private val dispatchers: DispatchersProvider,
|
||||
private val studentDb: StudentDao,
|
||||
private val semesterDb: SemesterDao,
|
||||
private val sdk: Sdk,
|
||||
private val appDatabase: AppDatabase
|
||||
private val appDatabase: AppDatabase,
|
||||
private val scrambler: Scrambler,
|
||||
) {
|
||||
|
||||
suspend fun isCurrentStudentSet() = studentDb.loadCurrent()?.isCurrent ?: false
|
||||
@ -68,7 +65,7 @@ class StudentRepository @Inject constructor(
|
||||
student = student.apply {
|
||||
if (decryptPass && Sdk.Mode.valueOf(student.loginMode) != Sdk.Mode.HEBE) {
|
||||
student.password = withContext(dispatchers.io) {
|
||||
decrypt(student.password)
|
||||
scrambler.decrypt(student.password)
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -86,7 +83,7 @@ class StudentRepository @Inject constructor(
|
||||
}.apply {
|
||||
if (decryptPass && Sdk.Mode.valueOf(student.loginMode) != Sdk.Mode.HEBE) {
|
||||
student.password = withContext(dispatchers.io) {
|
||||
decrypt(student.password)
|
||||
scrambler.decrypt(student.password)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -96,7 +93,7 @@ class StudentRepository @Inject constructor(
|
||||
|
||||
if (decryptPass && Sdk.Mode.valueOf(student.loginMode) != Sdk.Mode.HEBE) {
|
||||
student.password = withContext(dispatchers.io) {
|
||||
decrypt(student.password)
|
||||
scrambler.decrypt(student.password)
|
||||
}
|
||||
}
|
||||
return student
|
||||
@ -107,7 +104,7 @@ class StudentRepository @Inject constructor(
|
||||
|
||||
if (decryptPass && Sdk.Mode.valueOf(student.loginMode) != Sdk.Mode.HEBE) {
|
||||
student.password = withContext(dispatchers.io) {
|
||||
decrypt(student.password)
|
||||
scrambler.decrypt(student.password)
|
||||
}
|
||||
}
|
||||
return student
|
||||
@ -120,7 +117,7 @@ class StudentRepository @Inject constructor(
|
||||
it.apply {
|
||||
if (Sdk.Mode.valueOf(it.loginMode) != Sdk.Mode.HEBE) {
|
||||
password = withContext(dispatchers.io) {
|
||||
encrypt(password, context)
|
||||
scrambler.encrypt(password)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -166,4 +163,15 @@ class StudentRepository @Inject constructor(
|
||||
|
||||
studentDb.update(studentName)
|
||||
}
|
||||
|
||||
suspend fun deleteStudentsAssociatedWithAccount(student: Student) {
|
||||
studentDb.deleteByEmailAndUserName(student.email, student.userName)
|
||||
}
|
||||
|
||||
suspend fun clearAll() {
|
||||
withContext(dispatchers.io) {
|
||||
scrambler.clearKeyPair()
|
||||
appDatabase.clearAllTables()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -68,11 +68,20 @@ abstract class BaseActivity<T : BasePresenter<out BaseView>, VB : ViewBinding> :
|
||||
} else Toast.makeText(this, text, Toast.LENGTH_LONG).show()
|
||||
}
|
||||
|
||||
override fun showExpiredDialog() {
|
||||
override fun showExpiredCredentialsDialog() {
|
||||
MaterialAlertDialogBuilder(this)
|
||||
.setTitle(R.string.main_expired_credentials_title)
|
||||
.setMessage(R.string.main_expired_credentials_description)
|
||||
.setPositiveButton(R.string.main_log_in) { _, _ -> presenter.onConfirmExpiredCredentialsSelected() }
|
||||
.setNegativeButton(android.R.string.cancel) { _, _ -> }
|
||||
.show()
|
||||
}
|
||||
|
||||
override fun showDecryptionFailedDialog() {
|
||||
MaterialAlertDialogBuilder(this)
|
||||
.setTitle(R.string.main_session_expired)
|
||||
.setMessage(R.string.main_session_relogin)
|
||||
.setPositiveButton(R.string.main_log_in) { _, _ -> presenter.onExpiredLoginSelected() }
|
||||
.setPositiveButton(R.string.main_log_in) { _, _ -> presenter.onConfirmDecryptionFailedSelected() }
|
||||
.setNegativeButton(android.R.string.cancel) { _, _ -> }
|
||||
.show()
|
||||
}
|
||||
|
@ -28,8 +28,12 @@ abstract class BaseDialogFragment<VB : ViewBinding> : DialogFragment(), BaseView
|
||||
Toast.makeText(context, text, Toast.LENGTH_LONG).show()
|
||||
}
|
||||
|
||||
override fun showExpiredDialog() {
|
||||
(activity as? BaseActivity<*, *>)?.showExpiredDialog()
|
||||
override fun showExpiredCredentialsDialog() {
|
||||
(activity as? BaseActivity<*, *>)?.showExpiredCredentialsDialog()
|
||||
}
|
||||
|
||||
override fun showDecryptionFailedDialog() {
|
||||
(activity as? BaseActivity<*, *>)?.showDecryptionFailedDialog()
|
||||
}
|
||||
|
||||
override fun openClearLoginView() {
|
||||
|
@ -39,8 +39,12 @@ abstract class BaseFragment<VB : ViewBinding>(@LayoutRes layoutId: Int) : Fragme
|
||||
}
|
||||
}
|
||||
|
||||
override fun showExpiredDialog() {
|
||||
(activity as? BaseActivity<*, *>)?.showExpiredDialog()
|
||||
override fun showExpiredCredentialsDialog() {
|
||||
(activity as? BaseActivity<*, *>)?.showExpiredCredentialsDialog()
|
||||
}
|
||||
|
||||
override fun showDecryptionFailedDialog() {
|
||||
(activity as? BaseActivity<*, *>)?.showDecryptionFailedDialog()
|
||||
}
|
||||
|
||||
override fun showAuthDialog() {
|
||||
|
@ -28,20 +28,37 @@ open class BasePresenter<T : BaseView>(
|
||||
this.view = view
|
||||
errorHandler.apply {
|
||||
showErrorMessage = view::showError
|
||||
onSessionExpired = view::showExpiredDialog
|
||||
onExpiredCredentials = view::showExpiredCredentialsDialog
|
||||
onDecryptionFailed = view::showDecryptionFailedDialog
|
||||
onNoCurrentStudent = view::openClearLoginView
|
||||
onPasswordChangeRequired = view::showChangePasswordSnackbar
|
||||
onAuthorizationRequired = view::showAuthDialog
|
||||
}
|
||||
}
|
||||
|
||||
fun onExpiredLoginSelected() {
|
||||
Timber.i("Attempt to switch the student after the session expires")
|
||||
fun onConfirmDecryptionFailedSelected() {
|
||||
Timber.i("Attempt to clear all data")
|
||||
|
||||
presenterScope.launch {
|
||||
runCatching { studentRepository.clearAll() }
|
||||
.onFailure {
|
||||
Timber.i("Clear data result: An exception occurred")
|
||||
errorHandler.dispatch(it)
|
||||
}
|
||||
.onSuccess {
|
||||
Timber.i("Clear data result: Open login view")
|
||||
view?.openClearLoginView()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun onConfirmExpiredCredentialsSelected() {
|
||||
Timber.i("Attempt to delete students associated with the account and switch to new student")
|
||||
|
||||
presenterScope.launch {
|
||||
runCatching {
|
||||
val student = studentRepository.getCurrentStudent(false)
|
||||
studentRepository.logoutStudent(student)
|
||||
studentRepository.deleteStudentsAssociatedWithAccount(student)
|
||||
|
||||
val students = studentRepository.getSavedStudents(false)
|
||||
if (students.isNotEmpty()) {
|
||||
@ -50,11 +67,11 @@ open class BasePresenter<T : BaseView>(
|
||||
}
|
||||
}
|
||||
.onFailure {
|
||||
Timber.i("Switch student result: An exception occurred")
|
||||
Timber.i("Delete students result: An exception occurred")
|
||||
errorHandler.dispatch(it)
|
||||
}
|
||||
.onSuccess {
|
||||
Timber.i("Switch student result: Open login view")
|
||||
Timber.i("Delete students result: Open login view")
|
||||
view?.openClearLoginView()
|
||||
}
|
||||
}
|
||||
|
@ -6,7 +6,9 @@ interface BaseView {
|
||||
|
||||
fun showMessage(text: String)
|
||||
|
||||
fun showExpiredDialog()
|
||||
fun showExpiredCredentialsDialog()
|
||||
|
||||
fun showDecryptionFailedDialog()
|
||||
|
||||
fun showAuthDialog()
|
||||
|
||||
|
@ -15,7 +15,9 @@ open class ErrorHandler @Inject constructor(@ApplicationContext protected val co
|
||||
|
||||
var showErrorMessage: (String, Throwable) -> Unit = { _, _ -> }
|
||||
|
||||
var onSessionExpired: () -> Unit = {}
|
||||
var onExpiredCredentials: () -> Unit = {}
|
||||
|
||||
var onDecryptionFailed: () -> Unit = {}
|
||||
|
||||
var onNoCurrentStudent: () -> Unit = {}
|
||||
|
||||
@ -32,7 +34,8 @@ open class ErrorHandler @Inject constructor(@ApplicationContext protected val co
|
||||
showErrorMessage(context.resources.getErrorString(error), error)
|
||||
when (error) {
|
||||
is PasswordChangeRequiredException -> onPasswordChangeRequired(error.redirectUrl)
|
||||
is ScramblerException, is BadCredentialsException -> onSessionExpired()
|
||||
is ScramblerException -> onDecryptionFailed()
|
||||
is BadCredentialsException -> onExpiredCredentials()
|
||||
is NoCurrentStudentException -> onNoCurrentStudent()
|
||||
is AuthorizationRequiredException -> onAuthorizationRequired()
|
||||
}
|
||||
@ -40,7 +43,8 @@ open class ErrorHandler @Inject constructor(@ApplicationContext protected val co
|
||||
|
||||
open fun clear() {
|
||||
showErrorMessage = { _, _ -> }
|
||||
onSessionExpired = {}
|
||||
onExpiredCredentials = {}
|
||||
onDecryptionFailed = {}
|
||||
onNoCurrentStudent = {}
|
||||
onPasswordChangeRequired = {}
|
||||
onAuthorizationRequired = {}
|
||||
|
@ -30,7 +30,12 @@ import io.github.wulkanowy.ui.modules.message.MessageFragment
|
||||
import io.github.wulkanowy.ui.modules.notificationscenter.NotificationsCenterFragment
|
||||
import io.github.wulkanowy.ui.modules.schoolannouncement.SchoolAnnouncementFragment
|
||||
import io.github.wulkanowy.ui.modules.timetable.TimetableFragment
|
||||
import io.github.wulkanowy.utils.*
|
||||
import io.github.wulkanowy.utils.capitalise
|
||||
import io.github.wulkanowy.utils.dpToPx
|
||||
import io.github.wulkanowy.utils.getErrorString
|
||||
import io.github.wulkanowy.utils.getThemeAttrColor
|
||||
import io.github.wulkanowy.utils.openInternetBrowser
|
||||
import io.github.wulkanowy.utils.toFormattedString
|
||||
import java.time.LocalDate
|
||||
import javax.inject.Inject
|
||||
|
||||
|
@ -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>) {
|
||||
@ -187,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() {
|
||||
@ -195,7 +236,7 @@ class DashboardPresenter @Inject constructor(
|
||||
showErrorView(false)
|
||||
showProgress(true)
|
||||
}
|
||||
loadData(preferencesRepository.selectedDashboardTiles, forceRefresh = true)
|
||||
loadData(selectedDashboardTiles, forceRefresh = true)
|
||||
}
|
||||
|
||||
fun onViewReselected() {
|
||||
@ -216,7 +257,7 @@ class DashboardPresenter @Inject constructor(
|
||||
}
|
||||
|
||||
fun onDashboardTileSettingsSelected(): Boolean {
|
||||
view?.showDashboardTileSettings(preferencesRepository.selectedDashboardTiles.toList())
|
||||
view?.showDashboardTileSettings(selectedDashboardTiles.toList())
|
||||
return true
|
||||
}
|
||||
|
||||
@ -232,7 +273,7 @@ class DashboardPresenter @Inject constructor(
|
||||
|
||||
private fun loadHorizontalGroup(student: Student, forceRefresh: Boolean) {
|
||||
flow {
|
||||
val selectedTiles = preferencesRepository.selectedDashboardTiles
|
||||
val selectedTiles = selectedDashboardTiles
|
||||
val flowSuccess = flowOf(Resource.Success(null))
|
||||
|
||||
val luckyNumberFlow = luckyNumberRepository.getLuckyNumber(student, forceRefresh)
|
||||
|
@ -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)
|
||||
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -46,10 +46,6 @@ interface MainView : BaseView {
|
||||
|
||||
fun showAppSupport()
|
||||
|
||||
fun showPrivacyPolicyDialog()
|
||||
|
||||
fun openPrivacyPolicy()
|
||||
|
||||
fun openMoreDestination(destination: Destination)
|
||||
|
||||
interface MainChildView {
|
||||
|
@ -24,7 +24,9 @@ class SettingsFragment : PreferenceFragmentCompat(), MainView.TitledView, Settin
|
||||
|
||||
override fun showMessage(text: String) {}
|
||||
|
||||
override fun showExpiredDialog() {}
|
||||
override fun showExpiredCredentialsDialog() {}
|
||||
|
||||
override fun showDecryptionFailedDialog() {}
|
||||
|
||||
override fun openClearLoginView() {}
|
||||
|
||||
|
@ -47,8 +47,12 @@ class AdvancedFragment : PreferenceFragmentCompat(),
|
||||
(activity as? BaseActivity<*, *>)?.showMessage(text)
|
||||
}
|
||||
|
||||
override fun showExpiredDialog() {
|
||||
(activity as? BaseActivity<*, *>)?.showExpiredDialog()
|
||||
override fun showExpiredCredentialsDialog() {
|
||||
(activity as? BaseActivity<*, *>)?.showExpiredCredentialsDialog()
|
||||
}
|
||||
|
||||
override fun showDecryptionFailedDialog() {
|
||||
(activity as? BaseActivity<*, *>)?.showDecryptionFailedDialog()
|
||||
}
|
||||
|
||||
override fun showChangePasswordSnackbar(redirectUrl: String) {
|
||||
|
@ -63,8 +63,12 @@ class AppearanceFragment : PreferenceFragmentCompat(),
|
||||
(activity as? BaseActivity<*, *>)?.showMessage(text)
|
||||
}
|
||||
|
||||
override fun showExpiredDialog() {
|
||||
(activity as? BaseActivity<*, *>)?.showExpiredDialog()
|
||||
override fun showExpiredCredentialsDialog() {
|
||||
(activity as? BaseActivity<*, *>)?.showExpiredCredentialsDialog()
|
||||
}
|
||||
|
||||
override fun showDecryptionFailedDialog() {
|
||||
(activity as? BaseActivity<*, *>)?.showDecryptionFailedDialog()
|
||||
}
|
||||
|
||||
override fun showChangePasswordSnackbar(redirectUrl: String) {
|
||||
|
@ -133,8 +133,12 @@ class NotificationsFragment : PreferenceFragmentCompat(),
|
||||
(activity as? BaseActivity<*, *>)?.showMessage(text)
|
||||
}
|
||||
|
||||
override fun showExpiredDialog() {
|
||||
(activity as? BaseActivity<*, *>)?.showExpiredDialog()
|
||||
override fun showExpiredCredentialsDialog() {
|
||||
(activity as? BaseActivity<*, *>)?.showExpiredCredentialsDialog()
|
||||
}
|
||||
|
||||
override fun showDecryptionFailedDialog() {
|
||||
(activity as? BaseActivity<*, *>)?.showDecryptionFailedDialog()
|
||||
}
|
||||
|
||||
override fun showChangePasswordSnackbar(redirectUrl: String) {
|
||||
|
@ -84,8 +84,12 @@ class SyncFragment : PreferenceFragmentCompat(),
|
||||
}
|
||||
}
|
||||
|
||||
override fun showExpiredDialog() {
|
||||
(activity as? BaseActivity<*, *>)?.showExpiredDialog()
|
||||
override fun showExpiredCredentialsDialog() {
|
||||
(activity as? BaseActivity<*, *>)?.showExpiredCredentialsDialog()
|
||||
}
|
||||
|
||||
override fun showDecryptionFailedDialog() {
|
||||
(activity as? BaseActivity<*, *>)?.showDecryptionFailedDialog()
|
||||
}
|
||||
|
||||
override fun showChangePasswordSnackbar(redirectUrl: String) {
|
||||
|
@ -11,6 +11,7 @@ fun Sdk.init(student: Student): Sdk {
|
||||
schoolSymbol = student.schoolSymbol
|
||||
studentId = student.studentId
|
||||
classId = student.classId
|
||||
emptyCookieJarInterceptor = true
|
||||
|
||||
if (Sdk.Mode.valueOf(student.loginMode) == Sdk.Mode.HEBE) {
|
||||
mobileBaseUrl = student.mobileBaseUrl
|
||||
|
@ -16,6 +16,7 @@ import android.util.Base64.DEFAULT
|
||||
import android.util.Base64.decode
|
||||
import android.util.Base64.encode
|
||||
import android.util.Base64.encodeToString
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import timber.log.Timber
|
||||
import java.io.ByteArrayInputStream
|
||||
import java.io.ByteArrayOutputStream
|
||||
@ -33,108 +34,124 @@ import javax.crypto.CipherInputStream
|
||||
import javax.crypto.CipherOutputStream
|
||||
import javax.crypto.spec.OAEPParameterSpec
|
||||
import javax.crypto.spec.PSource.PSpecified
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
import javax.security.auth.x500.X500Principal
|
||||
|
||||
private const val KEYSTORE_NAME = "AndroidKeyStore"
|
||||
@Singleton
|
||||
class Scrambler @Inject constructor(
|
||||
@ApplicationContext private val context: Context,
|
||||
) {
|
||||
private val keyCharset = Charset.forName("UTF-8")
|
||||
|
||||
private const val KEY_ALIAS = "wulkanowy_password"
|
||||
private val isKeyPairExists: Boolean
|
||||
get() = keyStore.getKey(KEY_ALIAS, null) != null
|
||||
|
||||
private val KEY_CHARSET = Charset.forName("UTF-8")
|
||||
private val keyStore: KeyStore
|
||||
get() = KeyStore.getInstance(KEYSTORE_NAME).apply { load(null) }
|
||||
|
||||
private val isKeyPairExists: Boolean
|
||||
get() = keyStore.getKey(KEY_ALIAS, null) != null
|
||||
private val cipher: Cipher
|
||||
get() {
|
||||
return if (SDK_INT >= M) Cipher.getInstance(
|
||||
"RSA/ECB/OAEPWithSHA-256AndMGF1Padding",
|
||||
"AndroidKeyStoreBCWorkaround"
|
||||
)
|
||||
else Cipher.getInstance("RSA/ECB/PKCS1Padding", "AndroidOpenSSL")
|
||||
}
|
||||
|
||||
private val keyStore: KeyStore
|
||||
get() = KeyStore.getInstance(KEYSTORE_NAME).apply { load(null) }
|
||||
fun encrypt(plainText: String): String {
|
||||
if (plainText.isEmpty()) throw ScramblerException("Text to be encrypted is empty")
|
||||
|
||||
private val cipher: Cipher
|
||||
get() {
|
||||
return if (SDK_INT >= M) Cipher.getInstance(
|
||||
"RSA/ECB/OAEPWithSHA-256AndMGF1Padding",
|
||||
"AndroidKeyStoreBCWorkaround"
|
||||
)
|
||||
else Cipher.getInstance("RSA/ECB/PKCS1Padding", "AndroidOpenSSL")
|
||||
return try {
|
||||
if (!isKeyPairExists) generateKeyPair()
|
||||
|
||||
cipher.let {
|
||||
if (SDK_INT >= M) {
|
||||
OAEPParameterSpec("SHA-256", "MGF1", SHA1, PSpecified.DEFAULT).let { spec ->
|
||||
it.init(ENCRYPT_MODE, keyStore.getCertificate(KEY_ALIAS).publicKey, spec)
|
||||
}
|
||||
} else it.init(ENCRYPT_MODE, keyStore.getCertificate(KEY_ALIAS).publicKey)
|
||||
|
||||
ByteArrayOutputStream().let { output ->
|
||||
CipherOutputStream(output, it).apply {
|
||||
write(plainText.toByteArray(keyCharset))
|
||||
close()
|
||||
}
|
||||
encodeToString(output.toByteArray(), DEFAULT)
|
||||
}
|
||||
}
|
||||
} catch (exception: Exception) {
|
||||
Timber.e(exception, "An error occurred while encrypting text")
|
||||
String(encode(plainText.toByteArray(keyCharset), DEFAULT), keyCharset)
|
||||
}
|
||||
}
|
||||
|
||||
fun encrypt(plainText: String, context: Context): String {
|
||||
if (plainText.isEmpty()) throw ScramblerException("Text to be encrypted is empty")
|
||||
fun decrypt(cipherText: String): String {
|
||||
if (cipherText.isEmpty()) throw ScramblerException("Text to be encrypted is empty")
|
||||
|
||||
return try {
|
||||
if (!isKeyPairExists) generateKeyPair(context)
|
||||
return try {
|
||||
if (!isKeyPairExists) throw ScramblerException("KeyPair doesn't exist")
|
||||
|
||||
cipher.let {
|
||||
if (SDK_INT >= M) {
|
||||
OAEPParameterSpec("SHA-256", "MGF1", SHA1, PSpecified.DEFAULT).let { spec ->
|
||||
it.init(ENCRYPT_MODE, keyStore.getCertificate(KEY_ALIAS).publicKey, spec)
|
||||
cipher.let {
|
||||
if (SDK_INT >= M) {
|
||||
OAEPParameterSpec("SHA-256", "MGF1", SHA1, PSpecified.DEFAULT).let { spec ->
|
||||
it.init(DECRYPT_MODE, keyStore.getKey(KEY_ALIAS, null), spec)
|
||||
}
|
||||
} else it.init(DECRYPT_MODE, keyStore.getKey(KEY_ALIAS, null))
|
||||
|
||||
CipherInputStream(
|
||||
ByteArrayInputStream(decode(cipherText, DEFAULT)),
|
||||
it
|
||||
).let { input ->
|
||||
val values = ArrayList<Byte>()
|
||||
var nextByte: Int
|
||||
while (run { nextByte = input.read(); nextByte } != -1) {
|
||||
values.add(nextByte.toByte())
|
||||
}
|
||||
val bytes = ByteArray(values.size)
|
||||
for (i in bytes.indices) {
|
||||
bytes[i] = values[i]
|
||||
}
|
||||
String(bytes, 0, bytes.size, keyCharset)
|
||||
}
|
||||
} else it.init(ENCRYPT_MODE, keyStore.getCertificate(KEY_ALIAS).publicKey)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
throw ScramblerException("An error occurred while decrypting text", e)
|
||||
}
|
||||
}
|
||||
|
||||
ByteArrayOutputStream().let { output ->
|
||||
CipherOutputStream(output, it).apply {
|
||||
write(plainText.toByteArray(KEY_CHARSET))
|
||||
close()
|
||||
}
|
||||
encodeToString(output.toByteArray(), DEFAULT)
|
||||
private fun generateKeyPair() {
|
||||
(if (SDK_INT >= M) {
|
||||
KeyGenParameterSpec.Builder(KEY_ALIAS, PURPOSE_DECRYPT or PURPOSE_ENCRYPT)
|
||||
.setDigests(DIGEST_SHA256, DIGEST_SHA512)
|
||||
.setEncryptionPaddings(ENCRYPTION_PADDING_RSA_OAEP)
|
||||
.setCertificateSerialNumber(BigInteger.TEN)
|
||||
.setCertificateSubject(X500Principal("CN=Wulkanowy"))
|
||||
.build()
|
||||
} else {
|
||||
KeyPairGeneratorSpec.Builder(context)
|
||||
.setAlias(KEY_ALIAS)
|
||||
.setSubject(X500Principal("CN=Wulkanowy"))
|
||||
.setSerialNumber(BigInteger.TEN)
|
||||
.setStartDate(Calendar.getInstance().time)
|
||||
.setEndDate(Calendar.getInstance().apply { add(YEAR, 99) }.time)
|
||||
.build()
|
||||
}).let {
|
||||
KeyPairGenerator.getInstance("RSA", KEYSTORE_NAME).apply {
|
||||
initialize(it)
|
||||
genKeyPair()
|
||||
}
|
||||
}
|
||||
} catch (exception: Exception) {
|
||||
Timber.e(exception, "An error occurred while encrypting text")
|
||||
String(encode(plainText.toByteArray(KEY_CHARSET), DEFAULT), KEY_CHARSET)
|
||||
Timber.i("A new KeyPair has been generated")
|
||||
}
|
||||
|
||||
fun clearKeyPair() {
|
||||
keyStore.deleteEntry(KEY_ALIAS)
|
||||
Timber.i("KeyPair has been cleared")
|
||||
}
|
||||
|
||||
private companion object {
|
||||
private const val KEYSTORE_NAME = "AndroidKeyStore"
|
||||
private const val KEY_ALIAS = "wulkanowy_password"
|
||||
}
|
||||
}
|
||||
|
||||
fun decrypt(cipherText: String): String {
|
||||
if (cipherText.isEmpty()) throw ScramblerException("Text to be encrypted is empty")
|
||||
|
||||
return try {
|
||||
if (!isKeyPairExists) throw ScramblerException("KeyPair doesn't exist")
|
||||
|
||||
cipher.let {
|
||||
if (SDK_INT >= M) {
|
||||
OAEPParameterSpec("SHA-256", "MGF1", SHA1, PSpecified.DEFAULT).let { spec ->
|
||||
it.init(DECRYPT_MODE, keyStore.getKey(KEY_ALIAS, null), spec)
|
||||
}
|
||||
} else it.init(DECRYPT_MODE, keyStore.getKey(KEY_ALIAS, null))
|
||||
|
||||
CipherInputStream(ByteArrayInputStream(decode(cipherText, DEFAULT)), it).let { input ->
|
||||
val values = ArrayList<Byte>()
|
||||
var nextByte: Int
|
||||
while (run { nextByte = input.read(); nextByte } != -1) {
|
||||
values.add(nextByte.toByte())
|
||||
}
|
||||
val bytes = ByteArray(values.size)
|
||||
for (i in bytes.indices) {
|
||||
bytes[i] = values[i]
|
||||
}
|
||||
String(bytes, 0, bytes.size, KEY_CHARSET)
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
throw ScramblerException("An error occurred while decrypting text", e)
|
||||
}
|
||||
}
|
||||
|
||||
private fun generateKeyPair(context: Context) {
|
||||
(if (SDK_INT >= M) {
|
||||
KeyGenParameterSpec.Builder(KEY_ALIAS, PURPOSE_DECRYPT or PURPOSE_ENCRYPT)
|
||||
.setDigests(DIGEST_SHA256, DIGEST_SHA512)
|
||||
.setEncryptionPaddings(ENCRYPTION_PADDING_RSA_OAEP)
|
||||
.setCertificateSerialNumber(BigInteger.TEN)
|
||||
.setCertificateSubject(X500Principal("CN=Wulkanowy"))
|
||||
.build()
|
||||
} else {
|
||||
KeyPairGeneratorSpec.Builder(context)
|
||||
.setAlias(KEY_ALIAS)
|
||||
.setSubject(X500Principal("CN=Wulkanowy"))
|
||||
.setSerialNumber(BigInteger.TEN)
|
||||
.setStartDate(Calendar.getInstance().time)
|
||||
.setEndDate(Calendar.getInstance().apply { add(YEAR, 99) }.time)
|
||||
.build()
|
||||
}).let {
|
||||
KeyPairGenerator.getInstance("RSA", KEYSTORE_NAME).apply {
|
||||
initialize(it)
|
||||
genKeyPair()
|
||||
}
|
||||
}
|
||||
Timber.i("A new KeyPair has been generated")
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
Wersja 2.2.7
|
||||
Wersja 2.3.2
|
||||
|
||||
— naprawiliśmy logowanie do aplikacji i odświeżanie danych na odmianie standardowej i podobnych
|
||||
— poprawiliśmy kolejne usterki przy odświeżaniu danych (teraz to powinno działać już dużo lepiej)
|
||||
|
||||
Pełna lista zmian: https://github.com/wulkanowy/wulkanowy/releases
|
||||
|
@ -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>
|
@ -96,6 +96,8 @@
|
||||
<string name="main_log_in">Přihlásit se</string>
|
||||
<string name="main_session_expired">Relace vypršela</string>
|
||||
<string name="main_session_relogin">Relace vypršela. Přihlaste se prosím znovu</string>
|
||||
<string name="main_expired_credentials_description">Heslo k vašemu účtu bylo změněno. Musíte se znovu přihlásit do Wulkanového</string>
|
||||
<string name="main_expired_credentials_title">Heslo bylo změněno</string>
|
||||
<string name="main_support_title">Podpora aplikace</string>
|
||||
<string name="main_support_description">Líbí se Vám tato aplikace? Podpořte její vývoj tím, že povolíte neinvazivní reklamy, které můžete kdykoliv vypnout</string>
|
||||
<string name="main_support_positive">Zapnout reklamy</string>
|
||||
@ -760,7 +762,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">Zobrazit souhlas se zpracováním údajů</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 +771,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>
|
||||
|
@ -96,6 +96,8 @@
|
||||
<string name="main_log_in">Log in</string>
|
||||
<string name="main_session_expired">Session expired</string>
|
||||
<string name="main_session_relogin">Session expired, log in again</string>
|
||||
<string name="main_expired_credentials_description">Your account password has been changed. You need to log in to Wulkanowy again</string>
|
||||
<string name="main_expired_credentials_title">Password changed</string>
|
||||
<string name="main_support_title">Application support</string>
|
||||
<string name="main_support_description">Do you like this app? Support its development by enabling non-invasive ads that you can disable at any time</string>
|
||||
<string name="main_support_positive">Enable ads</string>
|
||||
@ -670,7 +672,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 +681,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 & Behavior</string>
|
||||
<string name="pref_settings_notifications_title">Notifications</string>
|
||||
|
@ -96,6 +96,8 @@
|
||||
<string name="main_log_in">Anmelden</string>
|
||||
<string name="main_session_expired">Die Sitzung ist abgelaufen</string>
|
||||
<string name="main_session_relogin">Die Sitzung ist abgelaufen, bitte loggen Sie sich erneut ein</string>
|
||||
<string name="main_expired_credentials_description">Your account password has been changed. You need to log in to Wulkanowy again</string>
|
||||
<string name="main_expired_credentials_title">Password changed</string>
|
||||
<string name="main_support_title">Anwendungsunterstützung</string>
|
||||
<string name="main_support_description">Gefällt Ihnen diese App? Unterstützen Sie ihre Entwicklung, indem Sie nicht-invasive Werbung aktivieren, die Sie jederzeit deaktivieren können</string>
|
||||
<string name="main_support_positive">Werbung aktivieren</string>
|
||||
@ -670,7 +672,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 +681,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 & Verhalten</string>
|
||||
<string name="pref_settings_notifications_title">Benachrichtigungen</string>
|
||||
|
@ -96,6 +96,8 @@
|
||||
<string name="main_log_in">Log in</string>
|
||||
<string name="main_session_expired">Session expired</string>
|
||||
<string name="main_session_relogin">Session expired, log in again</string>
|
||||
<string name="main_expired_credentials_description">Your account password has been changed. You need to log in to Wulkanowy again</string>
|
||||
<string name="main_expired_credentials_title">Password changed</string>
|
||||
<string name="main_support_title">Application support</string>
|
||||
<string name="main_support_description">Do you like this app? Support its development by enabling non-invasive ads that you can disable at any time</string>
|
||||
<string name="main_support_positive">Enable ads</string>
|
||||
@ -670,7 +672,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 +681,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 & Behavior</string>
|
||||
<string name="pref_settings_notifications_title">Notifications</string>
|
||||
|
@ -96,6 +96,8 @@
|
||||
<string name="main_log_in">Log in</string>
|
||||
<string name="main_session_expired">Session expired</string>
|
||||
<string name="main_session_relogin">Session expired, log in again</string>
|
||||
<string name="main_expired_credentials_description">Your account password has been changed. You need to log in to Wulkanowy again</string>
|
||||
<string name="main_expired_credentials_title">Password changed</string>
|
||||
<string name="main_support_title">Application support</string>
|
||||
<string name="main_support_description">Do you like this app? Support its development by enabling non-invasive ads that you can disable at any time</string>
|
||||
<string name="main_support_positive">Enable ads</string>
|
||||
@ -670,7 +672,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 +681,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 & Behavior</string>
|
||||
<string name="pref_settings_notifications_title">Notifications</string>
|
||||
|
@ -96,6 +96,8 @@
|
||||
<string name="main_log_in">Zaloguj się</string>
|
||||
<string name="main_session_expired">Sesja wygasła</string>
|
||||
<string name="main_session_relogin">Sesja wygasła, zaloguj się ponownie</string>
|
||||
<string name="main_expired_credentials_description">Hasło do Twojego konta zostało zmienione. Musisz zalogować się ponownie do Wulkanowego</string>
|
||||
<string name="main_expired_credentials_title">Hasło zostało zmienione</string>
|
||||
<string name="main_support_title">Wparcie aplikacji</string>
|
||||
<string name="main_support_description">Podoba Ci się ta aplikacja? Wspieraj jej rozwój poprzez włączenie nieinwazyjnych reklam, które możesz wyłączyć w dowolnym momencie</string>
|
||||
<string name="main_support_positive">Włącz reklamy</string>
|
||||
@ -760,7 +762,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 +771,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>
|
||||
|
@ -96,6 +96,8 @@
|
||||
<string name="main_log_in">Войти</string>
|
||||
<string name="main_session_expired">Сеанс истёк</string>
|
||||
<string name="main_session_relogin">Сеанс истёк, авторизуйтесь снова</string>
|
||||
<string name="main_expired_credentials_description">Your account password has been changed. You need to log in to Wulkanowy again</string>
|
||||
<string name="main_expired_credentials_title">Password changed</string>
|
||||
<string name="main_support_title">Поддержка приложения</string>
|
||||
<string name="main_support_description">Вам нравится это приложение? Поддержите его разработку, включив неинвазивную рекламу, которую можно отключить в любое время</string>
|
||||
<string name="main_support_positive">Включить рекламу</string>
|
||||
@ -760,7 +762,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 +771,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>
|
||||
|
@ -96,6 +96,8 @@
|
||||
<string name="main_log_in">Prihlásiť sa</string>
|
||||
<string name="main_session_expired">Relácia vypršala</string>
|
||||
<string name="main_session_relogin">Relácia vypršala. Prihláste sa prosím znovu</string>
|
||||
<string name="main_expired_credentials_description">Heslo k vášmu účtu bolo zmenené. Musíte sa znovu prihlásiť do Wulkanového</string>
|
||||
<string name="main_expired_credentials_title">Heslo bolo zmenené</string>
|
||||
<string name="main_support_title">Podpora aplikácie</string>
|
||||
<string name="main_support_description">Páči sa Vám táto aplikácia? Podporte jej vývoj tým, že povolíte neinvazívne reklamy, ktoré môžete kedykoľvek vypnúť</string>
|
||||
<string name="main_support_positive">Zapnúť reklamy</string>
|
||||
@ -760,7 +762,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">Zobraziť súhlas so spracovaním údajov</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 +771,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>
|
||||
|
@ -96,6 +96,8 @@
|
||||
<string name="main_log_in">Увійти</string>
|
||||
<string name="main_session_expired">Минув термін дії сесії</string>
|
||||
<string name="main_session_relogin">Минув термін дії сесії, авторизуйтеся знову</string>
|
||||
<string name="main_expired_credentials_description">Your account password has been changed. You need to log in to Wulkanowy again</string>
|
||||
<string name="main_expired_credentials_title">Password changed</string>
|
||||
<string name="main_support_title">Підтримка додатку</string>
|
||||
<string name="main_support_description">Вам подобається цей додаток? Підтримайте його розвиток, увімкнувши неінвазивну рекламу, яку ви можете відключити в будь-який час</string>
|
||||
<string name="main_support_positive">Увімкнути рекламу</string>
|
||||
@ -760,7 +762,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 +771,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>
|
||||
|
@ -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>
|
||||
|
@ -107,6 +107,8 @@
|
||||
<string name="main_log_in">Log in</string>
|
||||
<string name="main_session_expired">Session expired</string>
|
||||
<string name="main_session_relogin">Session expired, log in again</string>
|
||||
<string name="main_expired_credentials_description">Your account password has been changed. You need to log in to Wulkanowy again</string>
|
||||
<string name="main_expired_credentials_title">Password changed</string>
|
||||
<string name="main_support_title">Application support</string>
|
||||
<string name="main_support_description">Do you like this app? Support its development by enabling non-invasive ads that you can disable at any time</string>
|
||||
<string name="main_support_positive">Enable ads</string>
|
||||
@ -749,7 +751,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 +760,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 & Behavior</string>
|
||||
<string name="pref_settings_notifications_title">Notifications</string>
|
||||
|
@ -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
|
||||
@ -135,8 +101,12 @@ class AdsFragment : PreferenceFragmentCompat(), MainView.TitledView, AdsView {
|
||||
(activity as? BaseActivity<*, *>)?.showMessage(text)
|
||||
}
|
||||
|
||||
override fun showExpiredDialog() {
|
||||
(activity as? BaseActivity<*, *>)?.showExpiredDialog()
|
||||
override fun showExpiredCredentialsDialog() {
|
||||
(activity as? BaseActivity<*, *>)?.showExpiredCredentialsDialog()
|
||||
}
|
||||
|
||||
override fun showDecryptionFailedDialog() {
|
||||
(activity as? BaseActivity<*, *>)?.showDecryptionFailedDialog()
|
||||
}
|
||||
|
||||
override fun showChangePasswordSnackbar(redirectUrl: String) {
|
||||
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
@ -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?)
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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) {
|
||||
|
@ -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"
|
||||
|
@ -1,7 +1,7 @@
|
||||
buildscript {
|
||||
ext {
|
||||
kotlin_version = '1.9.22'
|
||||
about_libraries = '10.9.2'
|
||||
about_libraries = '10.10.0'
|
||||
hilt_version = '2.50'
|
||||
}
|
||||
repositories {
|
||||
@ -14,10 +14,10 @@ buildscript {
|
||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
||||
classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version"
|
||||
classpath "com.google.devtools.ksp:com.google.devtools.ksp.gradle.plugin:$kotlin_version-1.0.16"
|
||||
classpath 'com.android.tools.build:gradle:8.2.0'
|
||||
classpath 'com.android.tools.build:gradle:8.2.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.302'
|
||||
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.2"
|
||||
@ -29,6 +29,7 @@ buildscript {
|
||||
|
||||
allprojects {
|
||||
repositories {
|
||||
mavenLocal()
|
||||
mavenCentral()
|
||||
google()
|
||||
maven { url "https://jitpack.io" }
|
||||
|
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Binary file not shown.
2
gradle/wrapper/gradle-wrapper.properties
vendored
2
gradle/wrapper/gradle-wrapper.properties
vendored
@ -1,6 +1,6 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-all.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-all.zip
|
||||
networkTimeout=10000
|
||||
validateDistributionUrl=true
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
|
17
gradlew
vendored
17
gradlew
vendored
@ -83,7 +83,8 @@ done
|
||||
# This is normally unused
|
||||
# shellcheck disable=SC2034
|
||||
APP_BASE_NAME=${0##*/}
|
||||
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
|
||||
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
|
||||
APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit
|
||||
|
||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||
MAX_FD=maximum
|
||||
@ -144,7 +145,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
|
||||
case $MAX_FD in #(
|
||||
max*)
|
||||
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
|
||||
# shellcheck disable=SC3045
|
||||
# shellcheck disable=SC2039,SC3045
|
||||
MAX_FD=$( ulimit -H -n ) ||
|
||||
warn "Could not query maximum file descriptor limit"
|
||||
esac
|
||||
@ -152,7 +153,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
|
||||
'' | soft) :;; #(
|
||||
*)
|
||||
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
|
||||
# shellcheck disable=SC3045
|
||||
# shellcheck disable=SC2039,SC3045
|
||||
ulimit -n "$MAX_FD" ||
|
||||
warn "Could not set maximum file descriptor limit to $MAX_FD"
|
||||
esac
|
||||
@ -201,11 +202,11 @@ fi
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||
|
||||
# Collect all arguments for the java command;
|
||||
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
|
||||
# shell script including quotes and variable substitutions, so put them in
|
||||
# double quotes to make sure that they get re-expanded; and
|
||||
# * put everything else in single quotes, so that it's not re-expanded.
|
||||
# Collect all arguments for the java command:
|
||||
# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
|
||||
# and any embedded shellness will be escaped.
|
||||
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
|
||||
# treated as '${Hostname}' itself on the command line.
|
||||
|
||||
set -- \
|
||||
"-Dorg.gradle.appname=$APP_BASE_NAME" \
|
||||
|
Reference in New Issue
Block a user