1
0
mirror of https://github.com/wulkanowy/wulkanowy.git synced 2024-09-19 20:39:08 -05:00

Migrate to firebase (#196)

This commit is contained in:
Mikołaj Pich 2018-12-14 00:20:54 +01:00 committed by Rafał Borcz
parent 229bfe8d22
commit 5ee979447f
37 changed files with 255 additions and 151 deletions

View File

@ -113,7 +113,7 @@ jobs:
adb shell input keyevent 82
- run:
name: Run instrumented tests
command: ./gradlew clean createDebugCoverageReport jacocoTestReport --no-daemon --stacktrace --console=plain -PdisablePreDex -PdisableCrashlytics
command: ./gradlew clean createDebugCoverageReport jacocoTestReport --no-daemon --stacktrace --console=plain -PdisablePreDex
- run:
name: Collect logs from emulator
command: adb logcat -d > ./app/build/reports/logcat_emulator.txt
@ -153,11 +153,12 @@ jobs:
- run:
name: Decrypt keys
command: |
gpg --yes --batch --passphrase=$SERVICES_ENCRYPT_KEY ./app/src/release/google-services.json.gpg
openssl aes-256-cbc -d -in ./app/key-encrypted.p12 -k $ENCRYPT_KEY >> ./app/key.p12
openssl aes-256-cbc -d -in ./app/upload-key-encrypted.jks -k $ENCRYPT_KEY >> ./app/upload-key.jks
- run:
name: Publish release
command: ./gradlew publishRelease --no-daemon --stacktrace --console=plain -PdisablePreDex
command: ./gradlew publishRelease --no-daemon --stacktrace --console=plain -PenableCrashlytics -PdisablePreDex
workflows:
version: 2

View File

@ -49,17 +49,18 @@ script:
- fossa --no-ansi || true
- ./gradlew lint -x fabricGenerateResourcesRelease --stacktrace --daemon
- ./gradlew test -x fabricGenerateResourcesRelease --stacktrace --daemon
- ./gradlew createDebugCoverageReport --stacktrace -PdisableCrashlytics --daemon
- ./gradlew createDebugCoverageReport --stacktrace --daemon
- ./gradlew jacocoTestReport --stacktrace --daemon
- if [ "$TRAVIS_PULL_REQUEST" != "false" ] || [ "$TRAVIS_BRANCH" == "master" ]; then
git fetch --unshallow;
./gradlew sonarqube -x test -x lint -x fabricGenerateResourcesRelease -Dsonar.host.url=$SONAR_HOST -Dsonar.organization=$SONAR_ORG -Dsonar.login=$SONAR_KEY -Dsonar.branch.name=${TRAVIS_PULL_REQUEST_BRANCH:-$TRAVIS_BRANCH} -PdisableCrashlytics --stacktrace --daemon;
./gradlew sonarqube -x test -x lint -x fabricGenerateResourcesRelease -Dsonar.host.url=$SONAR_HOST -Dsonar.organization=$SONAR_ORG -Dsonar.login=$SONAR_KEY -Dsonar.branch.name=${TRAVIS_PULL_REQUEST_BRANCH:-$TRAVIS_BRANCH} --stacktrace --daemon;
fi
- |
if [ $TRAVIS_TAG ]; then
gpg --yes --batch --passphrase=$SERVICES_ENCRYPT_KEY ./app/src/release/google-services.json.gpg;
openssl aes-256-cbc -d -in ./app/key-encrypted.p12 -k $ENCRYPT_KEY >> ./app/key.p12;
openssl aes-256-cbc -d -in ./app/upload-key-encrypted.jks -k $ENCRYPT_KEY >> ./app/upload-key.jks;
./gradlew publishRelease --stacktrace;
./gradlew publishRelease -PenableCrashlytics --stacktrace;
fi
after_success:

View File

@ -7,8 +7,6 @@ apply plugin: 'com.github.triplet.play'
apply from: 'jacoco.gradle'
apply from: 'sonarqube.gradle'
def fabricApiKey = System.getenv("FABRIC_API_KEY") ?: "null"
android {
compileSdkVersion 28
buildToolsVersion '28.0.3'
@ -31,7 +29,7 @@ android {
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables.useSupportLibrary = true
playAccountConfig = playAccountConfigs.defaultAccountConfig
manifestPlaceholders = [fabricApiKey: fabricApiKey]
manifestPlaceholders = [crashlytics_enabled: project.hasProperty("enableCrashlytics")]
}
signingConfigs {
@ -45,17 +43,17 @@ android {
buildTypes {
release {
buildConfigField "boolean", "FABRIC_ENABLED", "true"
buildConfigField "boolean", "CRASHLYTICS_ENABLED", "true"
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
signingConfig signingConfigs.release
}
debug {
buildConfigField "boolean", "FABRIC_ENABLED", fabricApiKey != "null" && !project.hasProperty("disableCrashlytics") ? "true" : "false"
buildConfigField "boolean", "CRASHLYTICS_ENABLED", project.hasProperty("enableCrashlytics") ? "true" : "false"
applicationIdSuffix ".dev"
versionNameSuffix "-dev"
testCoverageEnabled = true
ext.enableCrashlytics = fabricApiKey != "null" && !project.hasProperty("disableCrashlytics")
ext.enableCrashlytics = project.hasProperty("enableCrashlytics")
multiDexKeepProguard file('proguard-multidex-rules.pro')
}
}
@ -81,7 +79,7 @@ configurations.all {
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
implementation "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
implementation('com.github.wulkanowy:api:6a73b0e') { exclude module: "threetenbp" }
implementation('com.github.wulkanowy:api:88e279e') { exclude module: "threetenbp" }
implementation "androidx.legacy:legacy-support-v4:1.0.0"
implementation "androidx.appcompat:appcompat:1.0.2"
@ -115,12 +113,9 @@ dependencies {
implementation "com.jakewharton.timber:timber:4.7.1"
implementation "at.favre.lib:slf4j-timber:1.0.1"
implementation("com.crashlytics.sdk.android:crashlytics:2.9.7@aar") {
transitive = true
}
implementation("com.crashlytics.sdk.android:answers:1.4.5@aar") {
transitive = true
}
implementation 'com.google.firebase:firebase-core:16.0.6'
implementation 'com.crashlytics.sdk.android:crashlytics:2.9.7'
debugImplementation "com.amitshekhar.android:debug-db:1.0.4"
@ -135,3 +130,5 @@ dependencies {
androidTestImplementation "org.mockito:mockito-android:2.23.4"
androidTestImplementation "org.jetbrains.kotlin:kotlin-test:$kotlin_version"
}
apply plugin: 'com.google.gms.google-services'

View File

@ -0,0 +1,42 @@
{
"project_info": {
"project_number": "",
"firebase_url": "",
"project_id": "",
"storage_bucket": ""
},
"client": [
{
"client_info": {
"mobilesdk_app_id": "1:1091101852179:android:b558a25f65d088b1",
"android_client_info": {
"package_name": "io.github.wulkanowy.dev"
}
},
"oauth_client": [
{
"client_id": "",
"client_type": 3
}
],
"api_key": [
{
"current_key": ""
}
],
"services": {
"analytics_service": {
"status": 1
},
"appinvite_service": {
"status": 1,
"other_platform_oauth_client": []
},
"ads_service": {
"status": 2
}
}
}
],
"configuration_version": "1"
}

Binary file not shown.

View File

@ -63,7 +63,7 @@
</receiver>
<meta-data
android:name="io.fabric.ApiKey"
android:value="${fabricApiKey}" />
android:name="firebase_crashlytics_collection_enabled"
android:value="${crashlytics_enabled}" />
</application>
</manifest>

View File

@ -4,7 +4,6 @@ import android.content.Context
import androidx.appcompat.app.AppCompatDelegate
import androidx.multidex.MultiDex
import com.crashlytics.android.Crashlytics
import com.crashlytics.android.answers.Answers
import com.crashlytics.android.core.CrashlyticsCore
import com.jakewharton.threetenabp.AndroidThreeTen
import dagger.android.AndroidInjector
@ -45,8 +44,7 @@ class WulkanowyApp : DaggerApplication() {
private fun initializeFabric() {
Fabric.with(Fabric.Builder(this).kits(
Crashlytics.Builder().core(CrashlyticsCore.Builder().disabled(!BuildConfig.FABRIC_ENABLED).build()).build(),
Answers()
Crashlytics.Builder().core(CrashlyticsCore.Builder().disabled(!BuildConfig.CRASHLYTICS_ENABLED).build()).build()
).debuggable(BuildConfig.DEBUG).build())
Timber.plant(CrashlyticsTree())
}

View File

@ -14,6 +14,7 @@ class ApiHelper @Inject constructor(private val api: Api) {
symbol = student.symbol
schoolSymbol = student.schoolSymbol
studentId = student.studentId
useNewStudent = false
host = URL(student.endpoint).run { host + ":$port".removeSuffix(":-1") }
ssl = student.endpoint.startsWith("https")
loginType = Api.LoginType.valueOf(student.loginType)
@ -24,4 +25,3 @@ class ApiHelper @Inject constructor(private val api: Api) {
initApi(Student(email = email, password = password, symbol = symbol, endpoint = endpoint, loginType = "AUTO"))
}
}

View File

@ -25,7 +25,7 @@ class GradeRemote @Inject constructor(private val api: Api) {
modifier = it.modifier,
comment = it.comment,
color = it.color,
gradeSymbol = it.symbol,
gradeSymbol = it.symbol ?: "",
description = it.description,
weight = it.weight,
weightValue = it.weightValue,

View File

@ -3,11 +3,13 @@ package io.github.wulkanowy.di
import android.content.Context
import com.firebase.jobdispatcher.FirebaseJobDispatcher
import com.firebase.jobdispatcher.GooglePlayDriver
import com.google.firebase.analytics.FirebaseAnalytics
import dagger.Module
import dagger.Provides
import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
import io.github.wulkanowy.WulkanowyApp
import io.github.wulkanowy.utils.FirebaseAnalyticsHelper
import io.github.wulkanowy.utils.SchedulersProvider
import javax.inject.Singleton
@ -28,4 +30,8 @@ internal class AppModule {
@Singleton
@Provides
fun provideJobDispatcher(context: Context) = FirebaseJobDispatcher(GooglePlayDriver(context))
@Singleton
@Provides
fun provideFirebaseAnalyticsHelper(context: Context) = FirebaseAnalyticsHelper(FirebaseAnalytics.getInstance(context))
}

View File

@ -6,23 +6,31 @@ import com.mikepenz.aboutlibraries.Libs.SpecialButton.SPECIAL2
import com.mikepenz.aboutlibraries.Libs.SpecialButton.SPECIAL3
import io.github.wulkanowy.data.ErrorHandler
import io.github.wulkanowy.ui.base.BasePresenter
import io.github.wulkanowy.utils.FirebaseAnalyticsHelper
import timber.log.Timber
import javax.inject.Inject
class AboutPresenter @Inject constructor(errorHandler: ErrorHandler) : BasePresenter<AboutView>(errorHandler) {
class AboutPresenter @Inject constructor(
errorHandler: ErrorHandler,
private val analytics: FirebaseAnalyticsHelper
) : BasePresenter<AboutView>(errorHandler) {
fun onExtraSelect(type: Libs.SpecialButton?) {
view?.run {
when (type) {
SPECIAL1 -> {
Timber.i("Opening github page")
analytics.logEvent("open_page", mapOf("name" to "github"))
openSourceWebView()
}
SPECIAL2 -> {
Timber.i("Opening issues page")
analytics.logEvent("open_page", mapOf("name" to "issues"))
openIssuesWebView()
}
SPECIAL3 -> { }
SPECIAL3 -> {
//empty for now
}
}
}
}

View File

@ -1,5 +1,6 @@
package io.github.wulkanowy.ui.modules.attendance
import com.google.firebase.analytics.FirebaseAnalytics.Param.START_DATE
import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
import io.github.wulkanowy.data.repositories.AttendanceRepository
import io.github.wulkanowy.data.repositories.PreferencesRepository
@ -7,9 +8,9 @@ import io.github.wulkanowy.data.repositories.SemesterRepository
import io.github.wulkanowy.data.repositories.StudentRepository
import io.github.wulkanowy.ui.base.BasePresenter
import io.github.wulkanowy.ui.modules.main.MainErrorHandler
import io.github.wulkanowy.utils.FirebaseAnalyticsHelper
import io.github.wulkanowy.utils.SchedulersProvider
import io.github.wulkanowy.utils.isHolidays
import io.github.wulkanowy.utils.logEvent
import io.github.wulkanowy.utils.nextSchoolDay
import io.github.wulkanowy.utils.previousOrSameSchoolDay
import io.github.wulkanowy.utils.previousSchoolDay
@ -26,7 +27,8 @@ class AttendancePresenter @Inject constructor(
private val attendanceRepository: AttendanceRepository,
private val studentRepository: StudentRepository,
private val semesterRepository: SemesterRepository,
private val prefRepository: PreferencesRepository
private val prefRepository: PreferencesRepository,
private val analytics: FirebaseAnalyticsHelper
) : BasePresenter<AttendanceView>(errorHandler) {
lateinit var currentDate: LocalDate
@ -42,13 +44,11 @@ class AttendancePresenter @Inject constructor(
fun onPreviousDay() {
loadData(currentDate.previousSchoolDay)
reloadView()
logEvent("Attendance day changed", mapOf("button" to "prev", "date" to currentDate.toFormattedString()))
}
fun onNextDay() {
loadData(currentDate.nextSchoolDay)
reloadView()
logEvent("Attendance day changed", mapOf("button" to "next", "date" to currentDate.toFormattedString()))
}
fun onSwipeRefresh() {
@ -105,7 +105,7 @@ class AttendancePresenter @Inject constructor(
showEmpty(it.isEmpty())
showContent(it.isNotEmpty())
}
logEvent("Attendance load", mapOf("items" to it.size, "forceRefresh" to forceRefresh, "date" to currentDate.toFormattedString()))
analytics.logEvent("load_attendance", mapOf("items" to it.size, "force_refresh" to forceRefresh, START_DATE to currentDate.toFormattedString("yyyy-MM-dd")))
}) {
view?.run { showEmpty(isViewEmpty) }
errorHandler.dispatch(it)

View File

@ -8,10 +8,10 @@ import io.github.wulkanowy.data.repositories.SemesterRepository
import io.github.wulkanowy.data.repositories.StudentRepository
import io.github.wulkanowy.data.repositories.SubjectRepostory
import io.github.wulkanowy.ui.base.BasePresenter
import io.github.wulkanowy.utils.FirebaseAnalyticsHelper
import io.github.wulkanowy.utils.SchedulersProvider
import io.github.wulkanowy.utils.calculatePercentage
import io.github.wulkanowy.utils.getFormattedName
import io.github.wulkanowy.utils.logEvent
import java.lang.String.format
import java.util.Locale.FRANCE
import java.util.concurrent.TimeUnit.MILLISECONDS
@ -23,7 +23,8 @@ class AttendanceSummaryPresenter @Inject constructor(
private val subjectRepository: SubjectRepostory,
private val studentRepository: StudentRepository,
private val semesterRepository: SemesterRepository,
private val schedulers: SchedulersProvider
private val schedulers: SchedulersProvider,
private val analytics: FirebaseAnalyticsHelper
) : BasePresenter<AttendanceSummaryView>(errorHandler) {
private var subjects = emptyList<Subject>()
@ -74,7 +75,7 @@ class AttendanceSummaryPresenter @Inject constructor(
showContent(it.first.isNotEmpty())
updateDataSet(it.first, it.second)
}
logEvent("Attendance load", mapOf("forceRefresh" to forceRefresh))
analytics.logEvent("load_attendance_summary", mapOf("items" to it.first.size, "force_refresh" to forceRefresh, "item_id" to subjectId))
}) {
view?.run { showEmpty(isViewEmpty) }
errorHandler.dispatch(it)

View File

@ -1,5 +1,6 @@
package io.github.wulkanowy.ui.modules.exam
import com.google.firebase.analytics.FirebaseAnalytics.Param.START_DATE
import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
import io.github.wulkanowy.data.db.entities.Exam
import io.github.wulkanowy.data.repositories.ExamRepository
@ -7,10 +8,10 @@ import io.github.wulkanowy.data.repositories.SemesterRepository
import io.github.wulkanowy.data.repositories.StudentRepository
import io.github.wulkanowy.ui.base.BasePresenter
import io.github.wulkanowy.ui.modules.main.MainErrorHandler
import io.github.wulkanowy.utils.FirebaseAnalyticsHelper
import io.github.wulkanowy.utils.SchedulersProvider
import io.github.wulkanowy.utils.friday
import io.github.wulkanowy.utils.isHolidays
import io.github.wulkanowy.utils.logEvent
import io.github.wulkanowy.utils.monday
import io.github.wulkanowy.utils.nextOrSameSchoolDay
import io.github.wulkanowy.utils.toFormattedString
@ -25,7 +26,8 @@ class ExamPresenter @Inject constructor(
private val schedulers: SchedulersProvider,
private val examRepository: ExamRepository,
private val studentRepository: StudentRepository,
private val semesterRepository: SemesterRepository
private val semesterRepository: SemesterRepository,
private val analytics: FirebaseAnalyticsHelper
) : BasePresenter<ExamView>(errorHandler) {
lateinit var currentDate: LocalDate
@ -41,13 +43,11 @@ class ExamPresenter @Inject constructor(
fun onPreviousWeek() {
loadData(currentDate.minusDays(7))
reloadView()
logEvent("Exam week changed", mapOf("button" to "prev", "date" to currentDate.toFormattedString()))
}
fun onNextWeek() {
loadData(currentDate.plusDays(7))
reloadView()
logEvent("Exam week changed", mapOf("button" to "next", "date" to currentDate.toFormattedString()))
}
fun onSwipeRefresh() {
@ -92,7 +92,7 @@ class ExamPresenter @Inject constructor(
showEmpty(it.isEmpty())
showContent(it.isNotEmpty())
}
logEvent("Exam load", mapOf("items" to it.size, "forceRefresh" to forceRefresh, "date" to currentDate.toFormattedString()))
analytics.logEvent("load_exam", mapOf("items" to it.size, "force_refresh" to forceRefresh, START_DATE to currentDate.toFormattedString("yyyy-MM-dd")))
}) {
view?.run { showEmpty(isViewEmpty) }
errorHandler.dispatch(it)

View File

@ -5,8 +5,8 @@ import io.github.wulkanowy.data.repositories.SemesterRepository
import io.github.wulkanowy.data.repositories.StudentRepository
import io.github.wulkanowy.ui.base.BasePresenter
import io.github.wulkanowy.ui.modules.main.MainErrorHandler
import io.github.wulkanowy.utils.FirebaseAnalyticsHelper
import io.github.wulkanowy.utils.SchedulersProvider
import io.github.wulkanowy.utils.logEvent
import io.reactivex.Completable
import java.util.concurrent.TimeUnit.MILLISECONDS
import javax.inject.Inject
@ -15,7 +15,8 @@ class GradePresenter @Inject constructor(
private val errorHandler: MainErrorHandler,
private val schedulers: SchedulersProvider,
private val studentRepository: StudentRepository,
private val semesterRepository: SemesterRepository
private val semesterRepository: SemesterRepository,
private val analytics: FirebaseAnalyticsHelper
) : BasePresenter<GradeView>(errorHandler) {
var selectedIndex = 0
@ -52,7 +53,7 @@ class GradePresenter @Inject constructor(
notifyChildrenSemesterChange()
loadChild(it.currentPageIndex)
}
logEvent("Semester changed", mapOf("number" to index + 1))
analytics.logEvent("changed_semester", mapOf("number" to index + 1))
}
}

View File

@ -8,10 +8,10 @@ import io.github.wulkanowy.data.repositories.SemesterRepository
import io.github.wulkanowy.data.repositories.StudentRepository
import io.github.wulkanowy.ui.base.BasePresenter
import io.github.wulkanowy.ui.modules.main.MainErrorHandler
import io.github.wulkanowy.utils.FirebaseAnalyticsHelper
import io.github.wulkanowy.utils.SchedulersProvider
import io.github.wulkanowy.utils.calcAverage
import io.github.wulkanowy.utils.changeModifier
import io.github.wulkanowy.utils.logEvent
import io.github.wulkanowy.utils.valueColor
import timber.log.Timber
import javax.inject.Inject
@ -22,7 +22,8 @@ class GradeDetailsPresenter @Inject constructor(
private val gradeRepository: GradeRepository,
private val studentRepository: StudentRepository,
private val semesterRepository: SemesterRepository,
private val preferencesRepository: PreferencesRepository
private val preferencesRepository: PreferencesRepository,
private val analytics: FirebaseAnalyticsHelper
) : BasePresenter<GradeDetailsView>(errorHandler) {
private var currentSemesterId = 0
@ -113,7 +114,7 @@ class GradeDetailsPresenter @Inject constructor(
showContent(it.isNotEmpty())
updateData(it)
}
logEvent("Grade details load", mapOf("items" to it.size, "forceRefresh" to forceRefresh))
analytics.logEvent("load_grade_details", mapOf("items" to it.size, "force_refresh" to forceRefresh))
}) {
view?.run { showEmpty(isViewEmpty) }
errorHandler.dispatch(it)

View File

@ -8,10 +8,10 @@ import io.github.wulkanowy.data.repositories.SemesterRepository
import io.github.wulkanowy.data.repositories.StudentRepository
import io.github.wulkanowy.ui.base.BasePresenter
import io.github.wulkanowy.ui.modules.main.MainErrorHandler
import io.github.wulkanowy.utils.FirebaseAnalyticsHelper
import io.github.wulkanowy.utils.SchedulersProvider
import io.github.wulkanowy.utils.calcAverage
import io.github.wulkanowy.utils.changeModifier
import io.github.wulkanowy.utils.logEvent
import java.lang.String.format
import java.util.Locale.FRANCE
import javax.inject.Inject
@ -23,7 +23,8 @@ class GradeSummaryPresenter @Inject constructor(
private val studentRepository: StudentRepository,
private val semesterRepository: SemesterRepository,
private val preferencesRepository: PreferencesRepository,
private val schedulers: SchedulersProvider
private val schedulers: SchedulersProvider,
private val analytics: FirebaseAnalyticsHelper
) : BasePresenter<GradeSummaryView>(errorHandler) {
override fun onAttachView(view: GradeSummaryView) {
@ -68,7 +69,7 @@ class GradeSummaryPresenter @Inject constructor(
showContent(it.first.isNotEmpty())
updateData(it.first, it.second)
}
logEvent("Grade summary load", mapOf("items" to it.first.size, "forceRefresh" to forceRefresh))
analytics.logEvent("load_grade_summary", mapOf("items" to it.first.size, "force_refresh" to forceRefresh))
}) {
view?.run { showEmpty(isViewEmpty) }
errorHandler.dispatch(it)

View File

@ -1,14 +1,15 @@
package io.github.wulkanowy.ui.modules.homework
import com.google.firebase.analytics.FirebaseAnalytics.Param.START_DATE
import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
import io.github.wulkanowy.data.repositories.HomeworkRepository
import io.github.wulkanowy.data.repositories.SemesterRepository
import io.github.wulkanowy.data.repositories.StudentRepository
import io.github.wulkanowy.ui.base.BasePresenter
import io.github.wulkanowy.ui.modules.main.MainErrorHandler
import io.github.wulkanowy.utils.FirebaseAnalyticsHelper
import io.github.wulkanowy.utils.SchedulersProvider
import io.github.wulkanowy.utils.isHolidays
import io.github.wulkanowy.utils.logEvent
import io.github.wulkanowy.utils.nextOrSameSchoolDay
import io.github.wulkanowy.utils.nextSchoolDay
import io.github.wulkanowy.utils.previousSchoolDay
@ -22,7 +23,8 @@ class HomeworkPresenter @Inject constructor(
private val schedulers: SchedulersProvider,
private val homeworkRepository: HomeworkRepository,
private val studentRepository: StudentRepository,
private val semesterRepository: SemesterRepository
private val semesterRepository: SemesterRepository,
private val analytics: FirebaseAnalyticsHelper
) : BasePresenter<HomeworkView>(errorHandler) {
lateinit var currentDate: LocalDate
@ -38,13 +40,11 @@ class HomeworkPresenter @Inject constructor(
fun onPreviousDay() {
loadData(currentDate.previousSchoolDay)
reloadView()
logEvent("Homework day changed", mapOf("button" to "prev", "date" to currentDate.toFormattedString()))
}
fun onNextDay() {
loadData(currentDate.nextSchoolDay)
reloadView()
logEvent("Homework day changed", mapOf("button" to "next", "date" to currentDate.toFormattedString()))
}
fun onSwipeRefresh() {
@ -78,7 +78,7 @@ class HomeworkPresenter @Inject constructor(
showEmpty(it.isEmpty())
showContent(it.isNotEmpty())
}
logEvent("Homework load", mapOf("items" to it.size, "forceRefresh" to forceRefresh, "date" to currentDate.toFormattedString()))
analytics.logEvent("load_homework", mapOf("items" to it.size, "force_refresh" to forceRefresh, START_DATE to currentDate.toFormattedString("yyyy-MM-dd")))
}) {
view?.run { showEmpty(isViewEmpty()) }
errorHandler.dispatch(it)

View File

@ -1,18 +1,21 @@
package io.github.wulkanowy.ui.modules.login.form
import com.google.firebase.analytics.FirebaseAnalytics.Event.SIGN_UP
import com.google.firebase.analytics.FirebaseAnalytics.Param.GROUP_ID
import com.google.firebase.analytics.FirebaseAnalytics.Param.SUCCESS
import io.github.wulkanowy.data.repositories.StudentRepository
import io.github.wulkanowy.ui.base.BasePresenter
import io.github.wulkanowy.ui.modules.login.LoginErrorHandler
import io.github.wulkanowy.utils.FirebaseAnalyticsHelper
import io.github.wulkanowy.utils.SchedulersProvider
import io.github.wulkanowy.utils.logEvent
import io.github.wulkanowy.utils.logRegister
import timber.log.Timber
import javax.inject.Inject
class LoginFormPresenter @Inject constructor(
private val schedulers: SchedulersProvider,
private val errorHandler: LoginErrorHandler,
private val studentRepository: StudentRepository
private val studentRepository: StudentRepository,
private val analytics: FirebaseAnalyticsHelper
) : BasePresenter<LoginFormView>(errorHandler) {
private var wasEmpty = false
@ -57,15 +60,14 @@ class LoginFormPresenter @Inject constructor(
} else if (it.isEmpty() && wasEmpty) {
showSymbolInput()
setErrorSymbolIncorrect()
logRegister("No student found", false, if (symbol.isEmpty()) "nil" else symbol, endpoint)
analytics.logEvent(SIGN_UP, mapOf(SUCCESS to false, "students" to it.size, "endpoint" to endpoint, GROUP_ID to symbol.ifEmpty { "nil" }))
} else {
switchOptionsView()
logEvent("Found students", mapOf("students" to it.size, "symbol" to it.joinToString { student -> student.symbol }, "endpoint" to endpoint))
}
}
}, {
analytics.logEvent(SIGN_UP, mapOf(SUCCESS to true, "endpoint" to endpoint, GROUP_ID to symbol.ifEmpty { "nil" }))
errorHandler.dispatch(it)
logRegister(it.localizedMessage, false, if (symbol.isEmpty()) "nil" else symbol, endpoint)
}))
}

View File

@ -1,13 +1,16 @@
package io.github.wulkanowy.ui.modules.login.options
import com.google.firebase.analytics.FirebaseAnalytics.Event.SIGN_UP
import com.google.firebase.analytics.FirebaseAnalytics.Param.GROUP_ID
import com.google.firebase.analytics.FirebaseAnalytics.Param.SUCCESS
import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.repositories.SemesterRepository
import io.github.wulkanowy.data.repositories.StudentRepository
import io.github.wulkanowy.ui.base.BasePresenter
import io.github.wulkanowy.ui.modules.login.LoginErrorHandler
import io.github.wulkanowy.utils.FirebaseAnalyticsHelper
import io.github.wulkanowy.utils.SchedulersProvider
import io.github.wulkanowy.utils.logRegister
import io.reactivex.Single
import javax.inject.Inject
@ -15,7 +18,8 @@ class LoginOptionsPresenter @Inject constructor(
private val errorHandler: LoginErrorHandler,
private val studentRepository: StudentRepository,
private val semesterRepository: SemesterRepository,
private val schedulers: SchedulersProvider
private val schedulers: SchedulersProvider,
private val analytics: FirebaseAnalyticsHelper
) : BasePresenter<LoginOptionsView>(errorHandler) {
override fun onAttachView(view: LoginOptionsView) {
@ -56,7 +60,7 @@ class LoginOptionsPresenter @Inject constructor(
}
}
.subscribe({
logRegister("Success", true, student.symbol, student.endpoint)
analytics.logEvent(SIGN_UP, mapOf(SUCCESS to true, "students" to 1, "endpoint" to student.endpoint, GROUP_ID to student.symbol))
view?.openMainView()
}, {
errorHandler.dispatch(it)

View File

@ -1,11 +1,13 @@
package io.github.wulkanowy.ui.modules.main
import com.google.firebase.analytics.FirebaseAnalytics.Event.APP_OPEN
import com.google.firebase.analytics.FirebaseAnalytics.Param.DESTINATION
import io.github.wulkanowy.data.repositories.PreferencesRepository
import io.github.wulkanowy.data.repositories.StudentRepository
import io.github.wulkanowy.services.job.ServiceHelper
import io.github.wulkanowy.ui.base.BasePresenter
import io.github.wulkanowy.utils.FirebaseAnalyticsHelper
import io.github.wulkanowy.utils.SchedulersProvider
import io.github.wulkanowy.utils.logLogin
import io.reactivex.Completable
import javax.inject.Inject
@ -14,7 +16,8 @@ class MainPresenter @Inject constructor(
private val studentRepository: StudentRepository,
private val prefRepository: PreferencesRepository,
private val schedulers: SchedulersProvider,
private val serviceHelper: ServiceHelper
private val serviceHelper: ServiceHelper,
private val analytics: FirebaseAnalyticsHelper
) : BasePresenter<MainView>(errorHandler) {
fun onAttachView(view: MainView, initMenuIndex: Int) {
@ -27,11 +30,12 @@ class MainPresenter @Inject constructor(
}
serviceHelper.startFullSyncService()
when (initMenuIndex) {
1 -> logLogin("Grades")
3 -> logLogin("Timetable")
4 -> logLogin("More")
}
analytics.logEvent(APP_OPEN, mapOf(DESTINATION to when (initMenuIndex) {
1 -> "Grades"
3 -> "Timetable"
4 -> "More"
else -> "User action"
}))
}
fun onViewStart() {

View File

@ -1,11 +1,12 @@
package io.github.wulkanowy.ui.modules.message.preview
import com.google.firebase.analytics.FirebaseAnalytics.Param.START_DATE
import io.github.wulkanowy.data.ErrorHandler
import io.github.wulkanowy.data.repositories.MessagesRepository
import io.github.wulkanowy.data.repositories.StudentRepository
import io.github.wulkanowy.ui.base.BasePresenter
import io.github.wulkanowy.utils.FirebaseAnalyticsHelper
import io.github.wulkanowy.utils.SchedulersProvider
import io.github.wulkanowy.utils.logEvent
import io.github.wulkanowy.utils.toFormattedString
import javax.inject.Inject
@ -13,7 +14,8 @@ class MessagePreviewPresenter @Inject constructor(
private val errorHandler: ErrorHandler,
private val schedulers: SchedulersProvider,
private val messagesRepository: MessagesRepository,
private val studentRepository: StudentRepository
private val studentRepository: StudentRepository,
private val analytics: FirebaseAnalyticsHelper
) : BasePresenter<MessagePreviewView>(errorHandler) {
var messageId: Int = 0
@ -32,9 +34,9 @@ class MessagePreviewPresenter @Inject constructor(
.subscribeOn(schedulers.backgroundThread)
.observeOn(schedulers.mainThread)
.doFinally { view?.showProgress(false) }
.subscribe({ messages ->
.subscribe({ message ->
view?.run {
messages.let {
message.let {
setSubject(if (it.subject.isNotBlank()) it.subject else noSubjectString)
setDate(it.date?.toFormattedString("yyyy-MM-dd HH:mm:ss"))
setContent(it.content)
@ -43,7 +45,7 @@ class MessagePreviewPresenter @Inject constructor(
else setSender(it.sender)
}
}
logEvent("Message load", mapOf("length" to messages.content?.length))
analytics.logEvent("load_attendance", mapOf(START_DATE to message.date?.toFormattedString("yyyy.MM.dd"), "lenght" to message.content?.length))
}) {
view?.showMessageError()
errorHandler.dispatch(it)

View File

@ -7,8 +7,8 @@ import io.github.wulkanowy.data.repositories.MessagesRepository
import io.github.wulkanowy.data.repositories.StudentRepository
import io.github.wulkanowy.ui.base.BasePresenter
import io.github.wulkanowy.ui.modules.message.MessageItem
import io.github.wulkanowy.utils.FirebaseAnalyticsHelper
import io.github.wulkanowy.utils.SchedulersProvider
import io.github.wulkanowy.utils.logEvent
import timber.log.Timber
import javax.inject.Inject
@ -16,7 +16,8 @@ class MessageTabPresenter @Inject constructor(
private val errorHandler: ErrorHandler,
private val schedulers: SchedulersProvider,
private val messagesRepository: MessagesRepository,
private val studentRepository: StudentRepository
private val studentRepository: StudentRepository,
private val analytics: FirebaseAnalyticsHelper
) : BasePresenter<MessageTabView>(errorHandler) {
lateinit var folder: MessagesRepository.MessageFolder
@ -52,7 +53,7 @@ class MessageTabPresenter @Inject constructor(
showContent(it.isNotEmpty())
updateData(it)
}
logEvent("Message tab load", mapOf("items" to it.size, "forceRefresh" to forceRefresh))
analytics.logEvent("load_messages", mapOf("items" to it.size, "folder" to folder.name))
}) {
view?.run { showEmpty(isViewEmpty) }
errorHandler.dispatch(it)

View File

@ -7,8 +7,8 @@ import io.github.wulkanowy.data.repositories.SemesterRepository
import io.github.wulkanowy.data.repositories.StudentRepository
import io.github.wulkanowy.ui.base.BasePresenter
import io.github.wulkanowy.ui.modules.main.MainErrorHandler
import io.github.wulkanowy.utils.FirebaseAnalyticsHelper
import io.github.wulkanowy.utils.SchedulersProvider
import io.github.wulkanowy.utils.logEvent
import timber.log.Timber
import javax.inject.Inject
@ -17,7 +17,8 @@ class NotePresenter @Inject constructor(
private val schedulers: SchedulersProvider,
private val studentRepository: StudentRepository,
private val noteRepository: NoteRepository,
private val semesterRepository: SemesterRepository
private val semesterRepository: SemesterRepository,
private val analytics: FirebaseAnalyticsHelper
) : BasePresenter<NoteView>(errorHandler) {
override fun onAttachView(view: NoteView) {
@ -49,7 +50,7 @@ class NotePresenter @Inject constructor(
showEmpty(it.isEmpty())
showContent(it.isNotEmpty())
}
logEvent("Note load", mapOf("items" to it.size, "forceRefresh" to forceRefresh))
analytics.logEvent("load_note", mapOf("items" to it.size, "force_refresh" to forceRefresh))
}, {
view?.run { showEmpty(isViewEmpty) }
errorHandler.dispatch(it)

View File

@ -4,15 +4,16 @@ import io.github.wulkanowy.data.ErrorHandler
import io.github.wulkanowy.data.repositories.PreferencesRepository
import io.github.wulkanowy.services.job.ServiceHelper
import io.github.wulkanowy.ui.base.BasePresenter
import io.github.wulkanowy.utils.FirebaseAnalyticsHelper
import io.github.wulkanowy.utils.isHolidays
import io.github.wulkanowy.utils.logEvent
import org.threeten.bp.LocalDate.now
import javax.inject.Inject
class SettingsPresenter @Inject constructor(
errorHandler: ErrorHandler,
private val preferencesRepository: PreferencesRepository,
private val serviceHelper: ServiceHelper
private val serviceHelper: ServiceHelper,
private val analytics: FirebaseAnalyticsHelper
) : BasePresenter<SettingsView>(errorHandler) {
override fun onAttachView(view: SettingsView) {
@ -38,6 +39,6 @@ class SettingsPresenter @Inject constructor(
}
}
logEvent("Setting changed", mapOf("name" to key))
analytics.logEvent("setting_changed", mapOf("name" to key))
}
}

View File

@ -3,7 +3,6 @@ package io.github.wulkanowy.ui.modules.splash
import io.github.wulkanowy.data.ErrorHandler
import io.github.wulkanowy.data.repositories.StudentRepository
import io.github.wulkanowy.ui.base.BasePresenter
import io.github.wulkanowy.utils.logLogin
import javax.inject.Inject
class SplashPresenter @Inject constructor(
@ -14,10 +13,8 @@ class SplashPresenter @Inject constructor(
override fun onAttachView(view: SplashView) {
super.onAttachView(view)
view.run {
if (studentRepository.isStudentSaved) {
logLogin("Open app")
openMainView()
} else openLoginView()
if (studentRepository.isStudentSaved) openMainView()
else openLoginView()
}
}
}

View File

@ -1,14 +1,15 @@
package io.github.wulkanowy.ui.modules.timetable
import com.google.firebase.analytics.FirebaseAnalytics.Param.START_DATE
import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
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.ui.base.BasePresenter
import io.github.wulkanowy.ui.modules.main.MainErrorHandler
import io.github.wulkanowy.utils.FirebaseAnalyticsHelper
import io.github.wulkanowy.utils.SchedulersProvider
import io.github.wulkanowy.utils.isHolidays
import io.github.wulkanowy.utils.logEvent
import io.github.wulkanowy.utils.nextOrSameSchoolDay
import io.github.wulkanowy.utils.nextSchoolDay
import io.github.wulkanowy.utils.previousSchoolDay
@ -24,7 +25,8 @@ class TimetablePresenter @Inject constructor(
private val schedulers: SchedulersProvider,
private val timetableRepository: TimetableRepository,
private val studentRepository: StudentRepository,
private val semesterRepository: SemesterRepository
private val semesterRepository: SemesterRepository,
private val analytics: FirebaseAnalyticsHelper
) : BasePresenter<TimetableView>(errorHandler) {
lateinit var currentDate: LocalDate
@ -40,13 +42,11 @@ class TimetablePresenter @Inject constructor(
fun onPreviousDay() {
loadData(currentDate.previousSchoolDay)
reloadView()
logEvent("Timetable day changed", mapOf("button" to "prev", "date" to currentDate.toFormattedString()))
}
fun onNextDay() {
loadData(currentDate.nextSchoolDay)
reloadView()
logEvent("Timetable day changed", mapOf("button" to "next", "date" to currentDate.toFormattedString()))
}
fun onSwipeRefresh() {
@ -90,7 +90,7 @@ class TimetablePresenter @Inject constructor(
showEmpty(it.isEmpty())
showContent(it.isNotEmpty())
}
logEvent("Timetable load", mapOf("items" to it.size, "forceRefresh" to forceRefresh, "date" to currentDate.toFormattedString()))
analytics.logEvent("load_attendance", mapOf("items" to it.size, "force_refresh" to forceRefresh, START_DATE to currentDate.toFormattedString("yyyy-MM-dd")))
}) {
view?.run { showEmpty(isViewEmpty) }
errorHandler.dispatch(it)

View File

@ -15,7 +15,7 @@ import io.github.wulkanowy.data.db.SharedPrefHelper
import io.github.wulkanowy.services.widgets.TimetableWidgetService
import io.github.wulkanowy.ui.modules.main.MainActivity
import io.github.wulkanowy.ui.modules.main.MainActivity.Companion.EXTRA_START_MENU_INDEX
import io.github.wulkanowy.utils.logEvent
import io.github.wulkanowy.utils.FirebaseAnalyticsHelper
import io.github.wulkanowy.utils.nextOrSameSchoolDay
import io.github.wulkanowy.utils.nextSchoolDay
import io.github.wulkanowy.utils.previousSchoolDay
@ -29,6 +29,9 @@ class TimetableWidgetProvider : AppWidgetProvider() {
@Inject
lateinit var sharedPref: SharedPrefHelper
@Inject
lateinit var analytics: FirebaseAnalyticsHelper
companion object {
const val EXTRA_TOGGLED_WIDGET_ID = "extraToggledWidget"
@ -91,7 +94,11 @@ class TimetableWidgetProvider : AppWidgetProvider() {
}
BUTTON_RESET -> sharedPref.putLong(widgetKey, LocalDate.now().nextOrSameSchoolDay.toEpochDay(), true)
}
button?.also { btn -> if (btn.isNotBlank()) logEvent("Widget day changed", mapOf("button" to button)) }
button?.also { btn ->
if (btn.isNotBlank()) {
analytics.logEvent("changed_timetable_widget_day", mapOf("button" to button))
}
}
}
}
super.onReceive(context, intent)

View File

@ -1,49 +0,0 @@
package io.github.wulkanowy.utils
import com.crashlytics.android.answers.Answers
import com.crashlytics.android.answers.CustomEvent
import com.crashlytics.android.answers.LoginEvent
import com.crashlytics.android.answers.SignUpEvent
import timber.log.Timber
import kotlin.math.min
fun logLogin(method: String) {
try {
Answers.getInstance().logLogin(LoginEvent().putMethod(method))
} catch (e: Throwable) {
Timber.d(e)
}
}
fun logRegister(message: String, result: Boolean, symbol: String, endpoint: String) {
try {
Answers.getInstance().logSignUp(SignUpEvent()
.putMethod("Login activity")
.putSuccess(result)
.putCustomAttribute("symbol", symbol)
.putCustomAttribute("message", message.substring(0, min(message.length, 100)))
.putCustomAttribute("endpoint", endpoint)
)
} catch (e: Throwable) {
Timber.d(e)
}
}
fun <T> logEvent(name: String, params: Map<String, T>) {
try {
Answers.getInstance().logCustom(CustomEvent(name)
.apply {
params.forEach {
when {
it.value is String -> putCustomAttribute(it.key, it.value as String)
it.value is Number -> putCustomAttribute(it.key, it.value as Number)
it.value is Boolean -> putCustomAttribute(it.key, if ((it.value as Boolean)) "true" else "false")
else -> Timber.w("logEvent() unknown value type: ${it.value}")
}
}
}
)
} catch (e: Throwable) {
Timber.d(e)
}
}

View File

@ -0,0 +1,23 @@
package io.github.wulkanowy.utils
import android.os.Bundle
import com.google.firebase.analytics.FirebaseAnalytics
import javax.inject.Singleton
@Singleton
class FirebaseAnalyticsHelper(private val analytics: FirebaseAnalytics) {
fun logEvent(name: String, params: Map<String, Any?>) {
Bundle().apply {
params.forEach {
if (it.value == null) return@forEach
when (it.value) {
is String, is String? -> putString(it.key, it.value as String)
is Int, is Int? -> putInt(it.key, it.value as Int)
is Boolean, is Boolean? -> putBoolean(it.key, it.value as Boolean)
}
}
analytics.logEvent(name, this)
}
}
}

View File

@ -0,0 +1,42 @@
{
"project_info": {
"project_number": "",
"firebase_url": "",
"project_id": "",
"storage_bucket": ""
},
"client": [
{
"client_info": {
"mobilesdk_app_id": "1:1091101852179:android:b558a25f65d088b1",
"android_client_info": {
"package_name": "io.github.wulkanowy"
}
},
"oauth_client": [
{
"client_id": "",
"client_type": 3
}
],
"api_key": [
{
"current_key": ""
}
],
"services": {
"analytics_service": {
"status": 1
},
"appinvite_service": {
"status": 1,
"other_platform_oauth_client": []
},
"ads_service": {
"status": 2
}
}
}
],
"configuration_version": "1"
}

Binary file not shown.

View File

@ -22,7 +22,7 @@ class StudentRemoteTest {
@Test
fun testRemoteAll() {
doReturn(Single.just(listOf(Pupil("", "", 1, "test", "", "", Api.LoginType.AUTO))))
doReturn(Single.just(listOf(Pupil("", "", 1, "test", "", "", "", Api.LoginType.AUTO))))
.`when`(mockApi).getPupils()
val students = StudentRemote(mockApi).getStudents("", "", "").blockingGet()

View File

@ -4,6 +4,7 @@ import io.github.wulkanowy.TestSchedulersProvider
import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.repositories.StudentRepository
import io.github.wulkanowy.ui.modules.login.LoginErrorHandler
import io.github.wulkanowy.utils.FirebaseAnalyticsHelper
import io.reactivex.Single
import org.junit.Before
import org.junit.Test
@ -27,13 +28,16 @@ class LoginFormPresenterTest {
@Mock
lateinit var errorHandler: LoginErrorHandler
@Mock
lateinit var analytics: FirebaseAnalyticsHelper
private lateinit var presenter: LoginFormPresenter
@Before
fun initPresenter() {
MockitoAnnotations.initMocks(this)
clearInvocations(repository, loginFormView)
presenter = LoginFormPresenter(TestSchedulersProvider(), errorHandler, repository)
presenter = LoginFormPresenter(TestSchedulersProvider(), errorHandler, repository, analytics)
presenter.onAttachView(loginFormView)
}
@ -125,7 +129,6 @@ class LoginFormPresenterTest {
verify(loginFormView, times(2)).showContent(true)
verify(loginFormView, times(2)).showSymbolInput()
verify(loginFormView).setErrorSymbolIncorrect()
}
@Test

View File

@ -6,6 +6,7 @@ import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.repositories.SemesterRepository
import io.github.wulkanowy.data.repositories.StudentRepository
import io.github.wulkanowy.ui.modules.login.LoginErrorHandler
import io.github.wulkanowy.utils.FirebaseAnalyticsHelper
import io.reactivex.Completable
import io.reactivex.Single
import org.junit.Before
@ -30,6 +31,9 @@ class LoginOptionsPresenterTest {
@Mock
lateinit var semesterRepository: SemesterRepository
@Mock
lateinit var analytics: FirebaseAnalyticsHelper
private lateinit var presenter: LoginOptionsPresenter
private val testStudent by lazy { Student(email = "test", password = "test123", endpoint = "https://fakelog.cf", loginType = "AUTO") }
@ -41,7 +45,7 @@ class LoginOptionsPresenterTest {
MockitoAnnotations.initMocks(this)
clearInvocations(studentRepository, loginOptionsView)
clearInvocations(semesterRepository, loginOptionsView)
presenter = LoginOptionsPresenter(errorHandler, studentRepository, semesterRepository, TestSchedulersProvider())
presenter = LoginOptionsPresenter(errorHandler, studentRepository, semesterRepository, TestSchedulersProvider(), analytics)
presenter.onAttachView(loginOptionsView)
}

View File

@ -4,6 +4,7 @@ import io.github.wulkanowy.TestSchedulersProvider
import io.github.wulkanowy.data.repositories.PreferencesRepository
import io.github.wulkanowy.data.repositories.StudentRepository
import io.github.wulkanowy.services.job.ServiceHelper
import io.github.wulkanowy.utils.FirebaseAnalyticsHelper
import org.junit.Before
import org.junit.Test
import org.mockito.Mock
@ -28,6 +29,9 @@ class MainPresenterTest {
@Mock
lateinit var mainView: MainView
@Mock
lateinit var analytics: FirebaseAnalyticsHelper
private lateinit var presenter: MainPresenter
@Before
@ -35,7 +39,7 @@ class MainPresenterTest {
MockitoAnnotations.initMocks(this)
clearInvocations(mainView)
presenter = MainPresenter(errorHandler, studentRepository, prefRepository, TestSchedulersProvider(), serviceHelper)
presenter = MainPresenter(errorHandler, studentRepository, prefRepository, TestSchedulersProvider(), serviceHelper, analytics)
presenter.onAttachView(mainView, -1)
}

View File

@ -10,7 +10,8 @@ buildscript {
dependencies {
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath 'com.android.tools.build:gradle:3.2.1'
classpath "io.fabric.tools:gradle:1.26.0"
classpath 'com.google.gms:google-services:4.2.0'
classpath "io.fabric.tools:gradle:1.26.1"
classpath "com.github.triplet.gradle:play-publisher:1.2.2"
classpath "org.sonarsource.scanner.gradle:sonarqube-gradle-plugin:2.6.2"
classpath 'com.android.tools.build.jetifier:jetifier-processor:1.0.0-beta02'