Split login form for two fragments (#230)

This commit is contained in:
Rafał Borcz 2019-02-11 02:04:24 +01:00 committed by Mikołaj Pich
parent 2f87779647
commit 1b1f2ae3bb
47 changed files with 597 additions and 378 deletions

View File

@ -19,7 +19,6 @@
<option name="CONTINUATION_INDENT_IN_SUPERTYPE_LISTS" value="false" /> <option name="CONTINUATION_INDENT_IN_SUPERTYPE_LISTS" value="false" />
<option name="CONTINUATION_INDENT_IN_IF_CONDITIONS" value="false" /> <option name="CONTINUATION_INDENT_IN_IF_CONDITIONS" value="false" />
<option name="CONTINUATION_INDENT_IN_ELVIS" value="false" /> <option name="CONTINUATION_INDENT_IN_ELVIS" value="false" />
<option name="WRAP_EXPRESSION_BODY_FUNCTIONS" value="1" />
</JetCodeStyleSettings> </JetCodeStyleSettings>
<Objective-C-extensions> <Objective-C-extensions>
<file> <file>

View File

@ -73,7 +73,7 @@ play {
dependencies { dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
implementation "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version" implementation "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
implementation('io.github.wulkanowy:api:0.6.6') { exclude module: "threetenbp" } implementation('com.github.wulkanowy:api:f941c4b1c7') { exclude module: "threetenbp" }
implementation "androidx.legacy:legacy-support-v4:1.0.0" implementation "androidx.legacy:legacy-support-v4:1.0.0"
implementation "androidx.appcompat:appcompat:1.0.2" implementation "androidx.appcompat:appcompat:1.0.2"
@ -82,20 +82,18 @@ dependencies {
implementation 'androidx.multidex:multidex:2.0.1' implementation 'androidx.multidex:multidex:2.0.1'
implementation 'com.takisoft.preferencex:preferencex:1.0.0' implementation 'com.takisoft.preferencex:preferencex:1.0.0'
implementation "com.mikepenz:aboutlibraries:6.2.0"
implementation "com.firebase:firebase-jobdispatcher:0.8.5" implementation "com.firebase:firebase-jobdispatcher:0.8.5"
implementation "com.google.dagger:dagger-android-support:2.21" implementation "com.google.dagger:dagger-android-support:2.21"
kapt "com.google.dagger:dagger-compiler:2.21" kapt "com.google.dagger:dagger-compiler:2.21"
kapt "com.google.dagger:dagger-android-processor:2.21" kapt "com.google.dagger:dagger-android-processor:2.21"
implementation "androidx.room:room-runtime:2.1.0-alpha03" implementation "androidx.room:room-runtime:2.1.0-alpha04"
implementation "androidx.room:room-rxjava2:2.1.0-alpha03" implementation "androidx.room:room-rxjava2:2.1.0-alpha04"
kapt "androidx.room:room-compiler:2.1.0-alpha03" kapt "androidx.room:room-compiler:2.1.0-alpha04"
implementation "eu.davidea:flexible-adapter:5.1.0" implementation "eu.davidea:flexible-adapter:5.1.0"
implementation "eu.davidea:flexible-adapter-ui:1.0.0" implementation "eu.davidea:flexible-adapter-ui:1.0.0"
implementation "com.aurelhubert:ahbottomnavigation:2.3.4" implementation "com.aurelhubert:ahbottomnavigation:2.3.4"
implementation 'com.ncapdevi:frag-nav:3.1.0' implementation 'com.ncapdevi:frag-nav:3.1.0'
@ -104,16 +102,16 @@ dependencies {
implementation "io.reactivex.rxjava2:rxjava:2.2.5" implementation "io.reactivex.rxjava2:rxjava:2.2.5"
implementation "com.jakewharton.threetenabp:threetenabp:1.1.1" implementation "com.jakewharton.threetenabp:threetenabp:1.1.1"
implementation "com.jakewharton.timber:timber:4.7.1" implementation "com.jakewharton.timber:timber:4.7.1"
implementation "at.favre.lib:slf4j-timber:1.0.1" implementation "at.favre.lib:slf4j-timber:1.0.1"
implementation "com.mikepenz:aboutlibraries:6.2.0"
implementation 'com.google.firebase:firebase-core:16.0.6' implementation 'com.google.firebase:firebase-core:16.0.7'
implementation 'com.crashlytics.sdk.android:crashlytics:2.9.8' implementation 'com.crashlytics.sdk.android:crashlytics:2.9.9'
releaseImplementation 'fr.o80.chucker:library-no-op:2.0.3' releaseImplementation 'fr.o80.chucker:library-no-op:2.0.3'
debugImplementation 'fr.o80.chucker:library:2.0.3'
debugImplementation 'fr.o80.chucker:library:2.0.3'
debugImplementation "com.amitshekhar.android:debug-db:1.0.4" debugImplementation "com.amitshekhar.android:debug-db:1.0.4"
testImplementation "junit:junit:4.12" testImplementation "junit:junit:4.12"

View File

@ -14,10 +14,10 @@ class ApiHelper @Inject constructor(private val api: Api) {
symbol = student.symbol symbol = student.symbol
schoolSymbol = student.schoolSymbol schoolSymbol = student.schoolSymbol
studentId = student.studentId studentId = student.studentId
useNewStudent = false
host = URL(student.endpoint).run { host + ":$port".removeSuffix(":-1") } host = URL(student.endpoint).run { host + ":$port".removeSuffix(":-1") }
ssl = student.endpoint.startsWith("https") ssl = student.endpoint.startsWith("https")
loginType = Api.LoginType.valueOf(student.loginType) loginType = Api.LoginType.valueOf(student.loginType)
useNewStudent = true
} }
} }

View File

@ -4,6 +4,7 @@ import androidx.room.ColumnInfo
import androidx.room.Entity import androidx.room.Entity
import androidx.room.Index import androidx.room.Index
import androidx.room.PrimaryKey import androidx.room.PrimaryKey
import java.io.Serializable
@Entity(tableName = "Students", indices = [Index(value = ["email", "symbol", "student_id", "school_id"], unique = true)]) @Entity(tableName = "Students", indices = [Index(value = ["email", "symbol", "student_id", "school_id"], unique = true)])
data class Student( data class Student(
@ -35,4 +36,4 @@ data class Student(
@ColumnInfo(name = "is_current") @ColumnInfo(name = "is_current")
var isCurrent: Boolean = false var isCurrent: Boolean = false
) ) : Serializable

View File

@ -24,17 +24,13 @@ class StudentRepository @Inject constructor(
val isStudentSaved val isStudentSaved
get() = local.isStudentSaved get() = local.isStudentSaved
lateinit var cachedStudents: Single<List<Student>> fun getStudents(email: String, password: String, endpoint: String, symbol: String = ""): Single<List<Student>> {
private set return ReactiveNetwork.checkInternetConnectivity(settings)
fun getStudents(email: String, password: String, symbol: String, endpoint: String): Single<List<Student>> {
cachedStudents = ReactiveNetwork.checkInternetConnectivity(settings)
.flatMap { .flatMap {
apiHelper.initApi(email, password, symbol, endpoint) apiHelper.initApi(email, password, symbol, endpoint)
if (it) remote.getStudents(email, password, endpoint) if (it) remote.getStudents(email, password, endpoint)
else Single.error(UnknownHostException("No internet connection")) else Single.error(UnknownHostException("No internet connection"))
}.doOnSuccess { cachedStudents = Single.just(it) } }
return cachedStudents
} }
fun getSavedStudents(decryptPass: Boolean = true): Single<List<Student>> { fun getSavedStudents(decryptPass: Boolean = true): Single<List<Student>> {

View File

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

View File

@ -10,18 +10,18 @@ import javax.inject.Singleton
class StudentRemote @Inject constructor(private val api: Api) { class StudentRemote @Inject constructor(private val api: Api) {
fun getStudents(email: String, password: String, endpoint: String): Single<List<Student>> { fun getStudents(email: String, password: String, endpoint: String): Single<List<Student>> {
return api.getPupils().map { students -> return api.getStudents().map { students ->
students.map { pupil -> students.map { student ->
Student( Student(
email = email, email = email,
password = password, password = password,
symbol = pupil.symbol, symbol = student.symbol,
studentId = pupil.studentId, studentId = student.studentId,
studentName = pupil.studentName, studentName = student.studentName,
schoolSymbol = pupil.schoolSymbol, schoolSymbol = student.schoolSymbol,
schoolName = pupil.description, schoolName = student.schoolName,
endpoint = endpoint, endpoint = endpoint,
loginType = pupil.loginType.name loginType = student.loginType.name
) )
} }
} }

View File

@ -8,9 +8,11 @@ import dagger.Module
import dagger.Provides import dagger.Provides
import eu.davidea.flexibleadapter.FlexibleAdapter import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.davidea.flexibleadapter.items.AbstractFlexibleItem import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
import io.github.wulkanowy.BuildConfig.DEBUG
import io.github.wulkanowy.WulkanowyApp import io.github.wulkanowy.WulkanowyApp
import io.github.wulkanowy.utils.FirebaseAnalyticsHelper import io.github.wulkanowy.utils.FirebaseAnalyticsHelper
import io.github.wulkanowy.utils.SchedulersProvider import io.github.wulkanowy.utils.SchedulersProvider
import javax.inject.Named
import javax.inject.Singleton import javax.inject.Singleton
@Module @Module
@ -34,4 +36,9 @@ internal class AppModule {
@Singleton @Singleton
@Provides @Provides
fun provideFirebaseAnalyticsHelper(context: Context) = FirebaseAnalyticsHelper(FirebaseAnalytics.getInstance(context)) fun provideFirebaseAnalyticsHelper(context: Context) = FirebaseAnalyticsHelper(FirebaseAnalytics.getInstance(context))
@Singleton
@Named("isDebug")
@Provides
fun provideIsDebug() = DEBUG
} }

View File

@ -25,12 +25,12 @@ class AboutPresenter @Inject constructor(
when (type) { when (type) {
SPECIAL1 -> { SPECIAL1 -> {
Timber.i("Opening github page") Timber.i("Opening github page")
analytics.logEvent("open_page", mapOf("name" to "github")) analytics.logEvent("open_page", "name" to "github")
openSourceWebView() openSourceWebView()
} }
SPECIAL2 -> { SPECIAL2 -> {
Timber.i("Opening issues page") Timber.i("Opening issues page")
analytics.logEvent("open_page", mapOf("name" to "issues")) analytics.logEvent("open_page", "name" to "issues")
openIssuesWebView() openIssuesWebView()
} }
SPECIAL3 -> { SPECIAL3 -> {

View File

@ -114,7 +114,7 @@ class AttendancePresenter @Inject constructor(
showEmpty(it.isEmpty()) showEmpty(it.isEmpty())
showContent(it.isNotEmpty()) showContent(it.isNotEmpty())
} }
analytics.logEvent("load_attendance", mapOf("items" to it.size, "force_refresh" to forceRefresh, START_DATE to currentDate.toFormattedString("yyyy-MM-dd"))) analytics.logEvent("load_attendance", "items" to it.size, "force_refresh" to forceRefresh, START_DATE to currentDate.toFormattedString("yyyy-MM-dd"))
}) { }) {
Timber.i("Loading attendance result: An exception occurred") Timber.i("Loading attendance result: An exception occurred")
view?.run { showEmpty(isViewEmpty) } view?.run { showEmpty(isViewEmpty) }

View File

@ -83,7 +83,7 @@ class AttendanceSummaryPresenter @Inject constructor(
showContent(it.first.isNotEmpty()) showContent(it.first.isNotEmpty())
updateDataSet(it.first, it.second) updateDataSet(it.first, it.second)
} }
analytics.logEvent("load_attendance_summary", mapOf("items" to it.first.size, "force_refresh" to forceRefresh, "item_id" to subjectId)) analytics.logEvent("load_attendance_summary", "items" to it.first.size, "force_refresh" to forceRefresh, "item_id" to subjectId)
}) { }) {
Timber.i("Loading attendance summary result: An exception occurred") Timber.i("Loading attendance summary result: An exception occurred")
view?.run { showEmpty(isViewEmpty) } view?.run { showEmpty(isViewEmpty) }

View File

@ -101,7 +101,7 @@ class ExamPresenter @Inject constructor(
showEmpty(it.isEmpty()) showEmpty(it.isEmpty())
showContent(it.isNotEmpty()) showContent(it.isNotEmpty())
} }
analytics.logEvent("load_exam", mapOf("items" to it.size, "force_refresh" to forceRefresh, START_DATE to currentDate.toFormattedString("yyyy-MM-dd"))) analytics.logEvent("load_exam", "items" to it.size, "force_refresh" to forceRefresh, START_DATE to currentDate.toFormattedString("yyyy-MM-dd"))
}) { }) {
Timber.i("Loading exam result: An exception occurred") Timber.i("Loading exam result: An exception occurred")
view?.run { showEmpty(isViewEmpty) } view?.run { showEmpty(isViewEmpty) }

View File

@ -57,7 +57,7 @@ class GradePresenter @Inject constructor(
notifyChildrenSemesterChange() notifyChildrenSemesterChange()
loadChild(it.currentPageIndex) loadChild(it.currentPageIndex)
} }
analytics.logEvent("changed_semester", mapOf("number" to index + 1)) analytics.logEvent("changed_semester", "number" to index + 1)
} }
} }

View File

@ -129,7 +129,7 @@ class GradeDetailsPresenter @Inject constructor(
showContent(it.isNotEmpty()) showContent(it.isNotEmpty())
updateData(it) updateData(it)
} }
analytics.logEvent("load_grade_details", mapOf("items" to it.size, "force_refresh" to forceRefresh)) analytics.logEvent("load_grade_details", "items" to it.size, "force_refresh" to forceRefresh)
}) { }) {
Timber.i("Loading grade details result: An exception occurred") Timber.i("Loading grade details result: An exception occurred")
view?.run { showEmpty(isViewEmpty) } view?.run { showEmpty(isViewEmpty) }

View File

@ -72,7 +72,7 @@ class GradeSummaryPresenter @Inject constructor(
showContent(it.first.isNotEmpty()) showContent(it.first.isNotEmpty())
updateData(it.first, it.second) updateData(it.first, it.second)
} }
analytics.logEvent("load_grade_summary", mapOf("items" to it.first.size, "force_refresh" to forceRefresh)) analytics.logEvent("load_grade_summary", "items" to it.first.size, "force_refresh" to forceRefresh)
}) { }) {
Timber.i("Loading grade summary result: An exception occurred") Timber.i("Loading grade summary result: An exception occurred")
view?.run { showEmpty(isViewEmpty) } view?.run { showEmpty(isViewEmpty) }

View File

@ -86,7 +86,7 @@ class HomeworkPresenter @Inject constructor(
showEmpty(it.isEmpty()) showEmpty(it.isEmpty())
showContent(it.isNotEmpty()) showContent(it.isNotEmpty())
} }
analytics.logEvent("load_homework", mapOf("items" to it.size, "force_refresh" to forceRefresh, START_DATE to currentDate.toFormattedString("yyyy-MM-dd"))) analytics.logEvent("load_homework", "items" to it.size, "force_refresh" to forceRefresh, START_DATE to currentDate.toFormattedString("yyyy-MM-dd"))
}) { }) {
Timber.i("Loading homework result: An exception occurred") Timber.i("Loading homework result: An exception occurred")
view?.run { showEmpty(isViewEmpty()) } view?.run { showEmpty(isViewEmpty()) }

View File

@ -4,10 +4,12 @@ import android.content.Context
import android.content.Intent import android.content.Intent
import android.os.Bundle import android.os.Bundle
import io.github.wulkanowy.R import io.github.wulkanowy.R
import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.ui.base.BaseActivity import io.github.wulkanowy.ui.base.BaseActivity
import io.github.wulkanowy.ui.base.BaseFragmentPagerAdapter import io.github.wulkanowy.ui.base.BaseFragmentPagerAdapter
import io.github.wulkanowy.ui.modules.login.form.LoginFormFragment import io.github.wulkanowy.ui.modules.login.form.LoginFormFragment
import io.github.wulkanowy.ui.modules.login.options.LoginOptionsFragment import io.github.wulkanowy.ui.modules.login.studentselect.LoginStudentSelectFragment
import io.github.wulkanowy.ui.modules.login.symbol.LoginSymbolFragment
import io.github.wulkanowy.utils.setOnSelectPageListener import io.github.wulkanowy.utils.setOnSelectPageListener
import kotlinx.android.synthetic.main.activity_login.* import kotlinx.android.synthetic.main.activity_login.*
import javax.inject.Inject import javax.inject.Inject
@ -38,12 +40,14 @@ class LoginActivity : BaseActivity(), LoginView {
override fun initAdapter() { override fun initAdapter() {
loginAdapter.addFragments(listOf( loginAdapter.addFragments(listOf(
LoginFormFragment.newInstance(), LoginFormFragment.newInstance(),
LoginOptionsFragment.newInstance() LoginSymbolFragment.newInstance(),
LoginStudentSelectFragment.newInstance()
)) ))
loginViewpager.run { loginViewpager.run {
offscreenPageLimit = 2
adapter = loginAdapter adapter = loginAdapter
setOnSelectPageListener { presenter.onPageSelected(it) } setOnSelectPageListener { presenter.onViewSelected(it) }
} }
} }
@ -51,22 +55,30 @@ class LoginActivity : BaseActivity(), LoginView {
loginViewpager.setCurrentItem(index, false) loginViewpager.setCurrentItem(index, false)
} }
override fun notifyOptionsViewLoadData() { override fun showActionBar(show: Boolean) {
(loginAdapter.getFragmentInstance(1) as? LoginOptionsFragment)?.onParentLoadData() supportActionBar?.apply { if (show) show() else hide() }
}
fun onChildFragmentSwitchOptions() {
presenter.onChildViewSwitchOptions()
}
override fun hideActionBar() {
supportActionBar?.hide()
} }
override fun onBackPressed() { override fun onBackPressed() {
presenter.onBackPressed { super.onBackPressed() } presenter.onBackPressed { super.onBackPressed() }
} }
override fun notifyInitSymbolFragment(loginData: Triple<String, String, String>) {
(loginAdapter.getFragmentInstance(1) as? LoginSymbolFragment)?.onParentInitSymbolFragment(loginData)
}
override fun notifyInitStudentSelectFragment(students: List<Student>) {
(loginAdapter.getFragmentInstance(2) as? LoginStudentSelectFragment)?.onParentInitStudentSelectFragment(students)
}
fun onFormFragmentAccountLogged(students: List<Student>, loginData: Triple<String, String, String>) {
presenter.onFormViewAccountLogged(students, loginData)
}
fun onSymbolFragmentAccountLogged(students: List<Student>) {
presenter.onSymbolViewAccountLogged(students)
}
public override fun onDestroy() { public override fun onDestroy() {
presenter.onDetachView() presenter.onDetachView()
super.onDestroy() super.onDestroy()

View File

@ -7,7 +7,8 @@ import io.github.wulkanowy.di.scopes.PerActivity
import io.github.wulkanowy.di.scopes.PerFragment import io.github.wulkanowy.di.scopes.PerFragment
import io.github.wulkanowy.ui.base.BaseFragmentPagerAdapter import io.github.wulkanowy.ui.base.BaseFragmentPagerAdapter
import io.github.wulkanowy.ui.modules.login.form.LoginFormFragment import io.github.wulkanowy.ui.modules.login.form.LoginFormFragment
import io.github.wulkanowy.ui.modules.login.options.LoginOptionsFragment import io.github.wulkanowy.ui.modules.login.studentselect.LoginStudentSelectFragment
import io.github.wulkanowy.ui.modules.login.symbol.LoginSymbolFragment
@Module @Module
internal abstract class LoginModule { internal abstract class LoginModule {
@ -22,10 +23,14 @@ internal abstract class LoginModule {
} }
@PerFragment @PerFragment
@ContributesAndroidInjector() @ContributesAndroidInjector
abstract fun bindLoginFormFragment(): LoginFormFragment abstract fun bindLoginFormFragment(): LoginFormFragment
@PerFragment @PerFragment
@ContributesAndroidInjector() @ContributesAndroidInjector
abstract fun bindLoginOptionsFragment(): LoginOptionsFragment abstract fun bindLoginSymbolFragment(): LoginSymbolFragment
@PerFragment
@ContributesAndroidInjector
abstract fun bindLoginSelectStudentFragment(): LoginStudentSelectFragment
} }

View File

@ -1,5 +1,6 @@
package io.github.wulkanowy.ui.modules.login package io.github.wulkanowy.ui.modules.login
import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.BasePresenter
import io.github.wulkanowy.ui.base.ErrorHandler import io.github.wulkanowy.ui.base.ErrorHandler
import timber.log.Timber import timber.log.Timber
@ -11,26 +12,49 @@ class LoginPresenter @Inject constructor(errorHandler: ErrorHandler) : BasePrese
super.onAttachView(view) super.onAttachView(view)
view.run { view.run {
initAdapter() initAdapter()
hideActionBar() showActionBar(false)
} }
Timber.i("Login view is attached") Timber.i("Login view is attached")
} }
fun onPageSelected(index: Int) { fun onFormViewAccountLogged(students: List<Student>, loginData: Triple<String, String, String>) {
if (index == 1) view?.notifyOptionsViewLoadData() view?.apply {
if (students.isEmpty()) {
Timber.i("Switch to symbol form")
notifyInitSymbolFragment(loginData)
switchView(1)
} else {
Timber.i("Switch to student select")
notifyInitStudentSelectFragment(students)
switchView(2)
}
}
} }
fun onChildViewSwitchOptions() { fun onSymbolViewAccountLogged(students: List<Student>) {
view?.switchView(1) view?.apply {
Timber.i("Switch to student select")
notifyInitStudentSelectFragment(students)
switchView(2)
}
}
fun onViewSelected(index: Int) {
view?.apply {
when (index) {
0, 1 -> showActionBar(false)
2 -> showActionBar(true)
}
}
} }
fun onBackPressed(default: () -> Unit) { fun onBackPressed(default: () -> Unit) {
Timber.i("Back pressed in login view") Timber.i("Back pressed in login view")
view?.run { view?.apply {
if (currentViewIndex == 1) { when (currentViewIndex) {
switchView(0) 1, 2 -> switchView(0)
hideActionBar() else -> default()
} else default() }
} }
} }
} }

View File

@ -1,5 +1,6 @@
package io.github.wulkanowy.ui.modules.login package io.github.wulkanowy.ui.modules.login
import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.ui.base.BaseView import io.github.wulkanowy.ui.base.BaseView
interface LoginView : BaseView { interface LoginView : BaseView {
@ -8,9 +9,11 @@ interface LoginView : BaseView {
fun initAdapter() fun initAdapter()
fun hideActionBar()
fun switchView(index: Int) fun switchView(index: Int)
fun notifyOptionsViewLoadData() fun showActionBar(show: Boolean)
fun notifyInitSymbolFragment(loginData: Triple<String, String, String>)
fun notifyInitStudentSelectFragment(students: List<Student>)
} }

View File

@ -10,9 +10,9 @@ import android.view.ViewGroup
import android.view.inputmethod.EditorInfo.IME_ACTION_DONE import android.view.inputmethod.EditorInfo.IME_ACTION_DONE
import android.view.inputmethod.EditorInfo.IME_NULL import android.view.inputmethod.EditorInfo.IME_NULL
import android.widget.ArrayAdapter import android.widget.ArrayAdapter
import io.github.wulkanowy.BuildConfig.DEBUG
import io.github.wulkanowy.BuildConfig.VERSION_NAME import io.github.wulkanowy.BuildConfig.VERSION_NAME
import io.github.wulkanowy.R import io.github.wulkanowy.R
import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.ui.base.BaseFragment import io.github.wulkanowy.ui.base.BaseFragment
import io.github.wulkanowy.ui.modules.login.LoginActivity import io.github.wulkanowy.ui.modules.login.LoginActivity
import io.github.wulkanowy.utils.hideSoftInput import io.github.wulkanowy.utils.hideSoftInput
@ -29,8 +29,6 @@ class LoginFormFragment : BaseFragment(), LoginFormView {
fun newInstance() = LoginFormFragment() fun newInstance() = LoginFormFragment()
} }
override val isDebug = DEBUG
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.fragment_login_form, container, false) return inflater.inflate(R.layout.fragment_login_form, container, false)
} }
@ -41,95 +39,52 @@ class LoginFormFragment : BaseFragment(), LoginFormView {
} }
override fun initView() { override fun initView() {
loginSignButton.setOnClickListener { loginFormSignIn.setOnClickListener {
presenter.attemptLogin( presenter.attemptLogin(
loginNicknameEdit.text.toString(), loginFormName.text.toString(),
loginPassEdit.text.toString(), loginFormPass.text.toString(),
loginSymbolEdit.text.toString(), resources.getStringArray(R.array.endpoints_values)[loginFormHost.selectedItemPosition]
resources.getStringArray(R.array.endpoints_values)[loginHostEdit.selectedItemPosition]
) )
} }
loginPassEdit.setOnEditorActionListener { _, id, _ -> onEditAction(id) } loginFormPass.setOnEditorActionListener { _, id, _ ->
if (id == IME_ACTION_DONE || id == IME_NULL) loginFormSignIn.callOnClick() else false
}
loginHostEdit.apply { context?.let {
adapter = ArrayAdapter.createFromResource(context, R.array.endpoints_keys, android.R.layout.simple_spinner_item) loginFormHost.adapter = ArrayAdapter.createFromResource(it, R.array.endpoints_keys, android.R.layout.simple_spinner_item)
.apply { setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item) } .apply { setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item) }
} }
loginSymbolEdit.run {
setAdapter(ArrayAdapter(context, android.R.layout.simple_list_item_1, resources.getStringArray(R.array.symbols_values)))
setOnEditorActionListener { _, id, _ -> onEditAction(id) }
}
} }
override fun showSymbolInput() { override fun setErrorNameRequired() {
loginHeader.text = getString(R.string.login_header_symbol) loginFormName.run {
loginMainForm.visibility = GONE
loginSymbolInput.visibility = VISIBLE
loginSymbolEdit.requestFocus()
showSoftKeyboard()
}
@SuppressLint("SetTextI18n")
override fun showVersion() {
loginVersion.apply {
visibility = VISIBLE
text = "${getString(R.string.app_name)} $VERSION_NAME"
}
}
override fun switchOptionsView() {
(activity as? LoginActivity)?.onChildFragmentSwitchOptions()
}
override fun setErrorNicknameRequired() {
loginNicknameEdit.run {
requestFocus() requestFocus()
error = getString(R.string.login_field_required) error = getString(R.string.login_field_required)
} }
} }
override fun setErrorPassRequired(focus: Boolean) { override fun setErrorPassRequired(focus: Boolean) {
loginPassEdit.run { loginFormPass.run {
if (focus) requestFocus() if (focus) requestFocus()
error = getString(R.string.login_field_required) error = getString(R.string.login_field_required)
} }
} }
override fun setErrorPassInvalid(focus: Boolean) { override fun setErrorPassInvalid(focus: Boolean) {
loginPassEdit.run { loginFormPass.run {
if (focus) requestFocus() if (focus) requestFocus()
error = getString(R.string.login_invalid_password) error = getString(R.string.login_invalid_password)
} }
} }
override fun setErrorSymbolRequire() {
loginSymbolEdit.run {
requestFocus()
error = getString(R.string.login_field_required)
}
}
override fun setErrorPassIncorrect() { override fun setErrorPassIncorrect() {
loginPassEdit.run { loginFormPass.run {
requestFocus() requestFocus()
error = getString(R.string.login_incorrect_password) error = getString(R.string.login_incorrect_password)
} }
} }
override fun setErrorSymbolIncorrect() {
loginSymbolEdit.run {
requestFocus()
error = getString(R.string.login_incorrect_symbol)
}
}
override fun resetViewErrors() {
loginNicknameEdit.error = null
loginPassEdit.error = null
}
override fun showSoftKeyboard() { override fun showSoftKeyboard() {
activity?.showSoftInput() activity?.showSoftInput()
} }
@ -146,13 +101,22 @@ class LoginFormFragment : BaseFragment(), LoginFormView {
loginFormContainer.visibility = if (show) VISIBLE else GONE loginFormContainer.visibility = if (show) VISIBLE else GONE
} }
private fun onEditAction(actionId: Int): Boolean { @SuppressLint("SetTextI18n")
return when (actionId) { override fun showVersion() {
IME_ACTION_DONE, IME_NULL -> loginSignButton.callOnClick() loginFormVersion.apply {
else -> false visibility = VISIBLE
text = "${getString(R.string.app_name)} $VERSION_NAME"
} }
} }
override fun notifyParentAccountLogged(students: List<Student>) {
(activity as? LoginActivity)?.onFormFragmentAccountLogged(students, Triple(
loginFormName.text.toString(),
loginFormPass.text.toString(),
resources.getStringArray(R.array.endpoints_values)[loginFormHost.selectedItemPosition]
))
}
override fun onDestroyView() { override fun onDestroyView() {
super.onDestroyView() super.onDestroyView()
presenter.onDetachView() presenter.onDetachView()

View File

@ -1,7 +1,5 @@
package io.github.wulkanowy.ui.modules.login.form 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 com.google.firebase.analytics.FirebaseAnalytics.Param.SUCCESS
import io.github.wulkanowy.data.repositories.StudentRepository import io.github.wulkanowy.data.repositories.StudentRepository
import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.BasePresenter
@ -10,16 +8,16 @@ import io.github.wulkanowy.utils.FirebaseAnalyticsHelper
import io.github.wulkanowy.utils.SchedulersProvider import io.github.wulkanowy.utils.SchedulersProvider
import timber.log.Timber import timber.log.Timber
import javax.inject.Inject import javax.inject.Inject
import javax.inject.Named
class LoginFormPresenter @Inject constructor( class LoginFormPresenter @Inject constructor(
private val schedulers: SchedulersProvider, private val schedulers: SchedulersProvider,
private val errorHandler: LoginErrorHandler, private val errorHandler: LoginErrorHandler,
private val studentRepository: StudentRepository, private val studentRepository: StudentRepository,
private val analytics: FirebaseAnalyticsHelper private val analytics: FirebaseAnalyticsHelper,
@param:Named("isDebug") private val isDebug: Boolean
) : BasePresenter<LoginFormView>(errorHandler) { ) : BasePresenter<LoginFormView>(errorHandler) {
private var wasEmpty = false
override fun onAttachView(view: LoginFormView) { override fun onAttachView(view: LoginFormView) {
super.onAttachView(view) super.onAttachView(view)
view.run { view.run {
@ -33,10 +31,10 @@ class LoginFormPresenter @Inject constructor(
} }
} }
fun attemptLogin(email: String, password: String, symbol: String, endpoint: String) { fun attemptLogin(email: String, password: String, endpoint: String) {
if (!validateCredentials(email, password, symbol)) return if (!validateCredentials(email, password)) return
disposable.add(studentRepository.getStudents(email, password, symbol, endpoint) disposable.add(studentRepository.getStudents(email, password, endpoint)
.subscribeOn(schedulers.backgroundThread) .subscribeOn(schedulers.backgroundThread)
.observeOn(schedulers.mainThread) .observeOn(schedulers.mainThread)
.doOnSubscribe { .doOnSubscribe {
@ -54,35 +52,21 @@ class LoginFormPresenter @Inject constructor(
} }
} }
.subscribe({ .subscribe({
view?.run {
if (it.isEmpty() && !wasEmpty) {
showSymbolInput()
wasEmpty = true
analytics.logEvent("sign_up_send", mapOf(SUCCESS to false, "students" to 0, "endpoint" to endpoint, GROUP_ID to symbol.ifEmpty { "null" }))
Timber.i("Login result: Empty student list")
} else if (it.isEmpty() && wasEmpty) {
showSymbolInput()
setErrorSymbolIncorrect()
analytics.logEvent("sign_up_send", mapOf(SUCCESS to false, "students" to it.size, "endpoint" to endpoint, GROUP_ID to symbol.ifEmpty { "null" }))
Timber.i("Login result: Wrong symbol")
} else {
analytics.logEvent("sign_up_send", mapOf(SUCCESS to true, "students" to it.size, "endpoint" to endpoint, GROUP_ID to symbol))
Timber.i("Login result: Success") Timber.i("Login result: Success")
switchOptionsView() analytics.logEvent("registration_form", SUCCESS to true, "students" to it.size, "endpoint" to endpoint, "error" to "No error")
} view?.notifyParentAccountLogged(it)
}
}, { }, {
analytics.logEvent(SIGN_UP, mapOf(SUCCESS to false, "endpoint" to endpoint, "message" to it.localizedMessage, GROUP_ID to symbol.ifEmpty { "null" }))
Timber.i("Login result: An exception occurred") Timber.i("Login result: An exception occurred")
analytics.logEvent("registration_form", SUCCESS to false, "students" to -1, "endpoint" to endpoint, "error" to it.localizedMessage)
errorHandler.dispatch(it) errorHandler.dispatch(it)
})) }))
} }
private fun validateCredentials(login: String, password: String, symbol: String): Boolean { private fun validateCredentials(login: String, password: String): Boolean {
var isCorrect = true var isCorrect = true
if (login.isEmpty()) { if (login.isEmpty()) {
view?.setErrorNicknameRequired() view?.setErrorNameRequired()
isCorrect = false isCorrect = false
} }
@ -91,11 +75,6 @@ class LoginFormPresenter @Inject constructor(
isCorrect = false isCorrect = false
} }
if (symbol.isEmpty() && wasEmpty) {
view?.setErrorSymbolRequire()
isCorrect = false
}
if (password.length < 6 && password.isNotEmpty()) { if (password.length < 6 && password.isNotEmpty()) {
view?.setErrorPassInvalid(focus = isCorrect) view?.setErrorPassInvalid(focus = isCorrect)
isCorrect = false isCorrect = false

View File

@ -1,31 +1,20 @@
package io.github.wulkanowy.ui.modules.login.form package io.github.wulkanowy.ui.modules.login.form
import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.ui.base.BaseView import io.github.wulkanowy.ui.base.BaseView
interface LoginFormView : BaseView { interface LoginFormView : BaseView {
val isDebug: Boolean
fun initView() fun initView()
fun switchOptionsView() fun setErrorNameRequired()
fun setErrorNicknameRequired()
fun setErrorPassRequired(focus: Boolean) fun setErrorPassRequired(focus: Boolean)
fun setErrorSymbolRequire()
fun setErrorPassInvalid(focus: Boolean) fun setErrorPassInvalid(focus: Boolean)
fun setErrorPassIncorrect() fun setErrorPassIncorrect()
fun setErrorSymbolIncorrect()
fun resetViewErrors()
fun showVersion()
fun showSoftKeyboard() fun showSoftKeyboard()
fun hideSoftKeyboard() fun hideSoftKeyboard()
@ -34,5 +23,7 @@ interface LoginFormView : BaseView {
fun showContent(show: Boolean) fun showContent(show: Boolean)
fun showSymbolInput() fun showVersion()
fun notifyParentAccountLogged(students: List<Student>)
} }

View File

@ -1,4 +1,4 @@
package io.github.wulkanowy.ui.modules.login.options package io.github.wulkanowy.ui.modules.login.studentselect
import android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK import android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK
import android.content.Intent.FLAG_ACTIVITY_NEW_TASK import android.content.Intent.FLAG_ACTIVITY_NEW_TASK
@ -13,31 +13,35 @@ import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.davidea.flexibleadapter.common.SmoothScrollLinearLayoutManager import eu.davidea.flexibleadapter.common.SmoothScrollLinearLayoutManager
import eu.davidea.flexibleadapter.items.AbstractFlexibleItem import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
import io.github.wulkanowy.R import io.github.wulkanowy.R
import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.ui.base.BaseFragment import io.github.wulkanowy.ui.base.BaseFragment
import io.github.wulkanowy.ui.modules.main.MainActivity import io.github.wulkanowy.ui.modules.main.MainActivity
import io.github.wulkanowy.utils.setOnItemClickListener import io.github.wulkanowy.utils.setOnItemClickListener
import kotlinx.android.synthetic.main.fragment_login_options.* import kotlinx.android.synthetic.main.fragment_login_student_select.*
import java.io.Serializable
import javax.inject.Inject import javax.inject.Inject
class LoginOptionsFragment : BaseFragment(), LoginOptionsView { class LoginStudentSelectFragment : BaseFragment(), LoginStudentSelectView {
@Inject @Inject
lateinit var presenter: LoginOptionsPresenter lateinit var presenter: LoginStudentSelectPresenter
@Inject @Inject
lateinit var loginAdapter: FlexibleAdapter<AbstractFlexibleItem<*>> lateinit var loginAdapter: FlexibleAdapter<AbstractFlexibleItem<*>>
companion object { companion object {
fun newInstance() = LoginOptionsFragment() const val SAVED_STUDENTS = "STUDENTS"
fun newInstance() = LoginStudentSelectFragment()
} }
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.fragment_login_options, container, false) return inflater.inflate(R.layout.fragment_login_student_select, container, false)
} }
override fun onActivityCreated(savedInstanceState: Bundle?) { override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState) super.onActivityCreated(savedInstanceState)
presenter.onAttachView(this) presenter.onAttachView(this, savedInstanceState?.getSerializable(SAVED_STUDENTS))
} }
override fun initView() { override fun initView() {
@ -49,11 +53,7 @@ class LoginOptionsFragment : BaseFragment(), LoginOptionsView {
} }
} }
fun onParentLoadData() { override fun updateData(data: List<LoginStudentSelectItem>) {
presenter.onParentViewLoadData()
}
override fun updateData(data: List<LoginOptionsItem>) {
loginAdapter.updateDataSet(data, true) loginAdapter.updateDataSet(data, true)
} }
@ -76,6 +76,15 @@ class LoginOptionsFragment : BaseFragment(), LoginOptionsView {
(activity as? AppCompatActivity)?.supportActionBar?.run { if (show) show() else hide() } (activity as? AppCompatActivity)?.supportActionBar?.run { if (show) show() else hide() }
} }
fun onParentInitStudentSelectFragment(students: List<Student>) {
presenter.onParentInitStudentSelectView(students)
}
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
outState.putSerializable(SAVED_STUDENTS, presenter.students as Serializable)
}
override fun onDestroyView() { override fun onDestroyView() {
presenter.onDetachView() presenter.onDetachView()
super.onDestroyView() super.onDestroyView()

View File

@ -1,4 +1,4 @@
package io.github.wulkanowy.ui.modules.login.options package io.github.wulkanowy.ui.modules.login.studentselect
import android.view.View import android.view.View
import eu.davidea.flexibleadapter.FlexibleAdapter import eu.davidea.flexibleadapter.FlexibleAdapter
@ -10,7 +10,7 @@ import io.github.wulkanowy.data.db.entities.Student
import kotlinx.android.extensions.LayoutContainer import kotlinx.android.extensions.LayoutContainer
import kotlinx.android.synthetic.main.item_login_options.view.* import kotlinx.android.synthetic.main.item_login_options.view.*
class LoginOptionsItem(val student: Student) : AbstractFlexibleItem<LoginOptionsItem.ItemViewHolder>() { class LoginStudentSelectItem(val student: Student) : AbstractFlexibleItem<LoginStudentSelectItem.ItemViewHolder>() {
override fun getLayoutRes(): Int = R.layout.item_login_options override fun getLayoutRes(): Int = R.layout.item_login_options
@ -27,7 +27,7 @@ class LoginOptionsItem(val student: Student) : AbstractFlexibleItem<LoginOptions
if (this === other) return true if (this === other) return true
if (javaClass != other?.javaClass) return false if (javaClass != other?.javaClass) return false
other as LoginOptionsItem other as LoginStudentSelectItem
if (student != other.student) return false if (student != other.student) return false

View File

@ -1,7 +1,5 @@
package io.github.wulkanowy.ui.modules.login.options package io.github.wulkanowy.ui.modules.login.studentselect
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 com.google.firebase.analytics.FirebaseAnalytics.Param.SUCCESS
import eu.davidea.flexibleadapter.items.AbstractFlexibleItem import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.db.entities.Student
@ -13,17 +11,20 @@ import io.github.wulkanowy.utils.FirebaseAnalyticsHelper
import io.github.wulkanowy.utils.SchedulersProvider import io.github.wulkanowy.utils.SchedulersProvider
import io.reactivex.Single import io.reactivex.Single
import timber.log.Timber import timber.log.Timber
import java.io.Serializable
import javax.inject.Inject import javax.inject.Inject
class LoginOptionsPresenter @Inject constructor( class LoginStudentSelectPresenter @Inject constructor(
private val errorHandler: LoginErrorHandler, private val errorHandler: LoginErrorHandler,
private val studentRepository: StudentRepository, private val studentRepository: StudentRepository,
private val semesterRepository: SemesterRepository, private val semesterRepository: SemesterRepository,
private val schedulers: SchedulersProvider, private val schedulers: SchedulersProvider,
private val analytics: FirebaseAnalyticsHelper private val analytics: FirebaseAnalyticsHelper
) : BasePresenter<LoginOptionsView>(errorHandler) { ) : BasePresenter<LoginStudentSelectView>(errorHandler) {
override fun onAttachView(view: LoginOptionsView) { var students = emptyList<Student>()
fun onAttachView(view: LoginStudentSelectView, students: Serializable?) {
super.onAttachView(view) super.onAttachView(view)
view.run { view.run {
initView() initView()
@ -32,22 +33,29 @@ class LoginOptionsPresenter @Inject constructor(
Timber.i("The student already registered in the app was selected") Timber.i("The student already registered in the app was selected")
} }
} }
if (students is List<*> && students.isNotEmpty()) {
loadData(students.filterIsInstance<Student>())
}
} }
fun onParentViewLoadData() { fun onParentInitStudentSelectView(students: List<Student>) {
disposable.add(studentRepository.cachedStudents loadData(students)
.observeOn(schedulers.mainThread)
.subscribeOn(schedulers.backgroundThread)
.doOnSubscribe { view?.showActionBar(true) }
.subscribe({ view?.updateData(it.map { student -> LoginOptionsItem(student) }) }, { errorHandler.dispatch(it) }))
} }
fun onItemSelected(item: AbstractFlexibleItem<*>?) { fun onItemSelected(item: AbstractFlexibleItem<*>?) {
if (item is LoginOptionsItem) { if (item is LoginStudentSelectItem) {
registerStudent(item.student) registerStudent(item.student)
} }
} }
private fun loadData(students: List<Student>) {
this.students = students
view?.apply {
updateData(students.map { LoginStudentSelectItem(it) })
}
}
private fun registerStudent(student: Student) { private fun registerStudent(student: Student) {
disposable.add(studentRepository.saveStudent(student) disposable.add(studentRepository.saveStudent(student)
.map { student.apply { id = it } } .map { student.apply { id = it } }
@ -65,10 +73,11 @@ class LoginOptionsPresenter @Inject constructor(
Timber.i("Registration started") Timber.i("Registration started")
} }
.subscribe({ .subscribe({
analytics.logEvent(SIGN_UP, mapOf(SUCCESS to true, "endpoint" to student.endpoint, "message" to "Success", GROUP_ID to student.symbol)) analytics.logEvent("registration_student_select", SUCCESS to true, "endpoint" to student.endpoint, "symbol" to student.symbol, "error" to "No error")
Timber.i("Registration result: Success") Timber.i("Registration result: Success")
view?.openMainView() view?.openMainView()
}, { }, {
analytics.logEvent("registration_student_select", SUCCESS to false, "endpoint" to student.endpoint, "symbol" to student.symbol, "error" to it.localizedMessage)
Timber.i("Registration result: An exception occurred ") Timber.i("Registration result: An exception occurred ")
errorHandler.dispatch(it) errorHandler.dispatch(it)
view?.apply { view?.apply {

View File

@ -1,12 +1,12 @@
package io.github.wulkanowy.ui.modules.login.options package io.github.wulkanowy.ui.modules.login.studentselect
import io.github.wulkanowy.ui.base.BaseView import io.github.wulkanowy.ui.base.BaseView
interface LoginOptionsView : BaseView { interface LoginStudentSelectView : BaseView {
fun initView() fun initView()
fun updateData(data: List<LoginOptionsItem>) fun updateData(data: List<LoginStudentSelectItem>)
fun openMainView() fun openMainView()

View File

@ -0,0 +1,106 @@
package io.github.wulkanowy.ui.modules.login.symbol
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.View.GONE
import android.view.View.VISIBLE
import android.view.ViewGroup
import android.view.inputmethod.EditorInfo.IME_ACTION_DONE
import android.view.inputmethod.EditorInfo.IME_NULL
import android.widget.ArrayAdapter
import io.github.wulkanowy.R
import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.ui.base.BaseFragment
import io.github.wulkanowy.ui.modules.login.LoginActivity
import io.github.wulkanowy.utils.hideSoftInput
import io.github.wulkanowy.utils.showSoftInput
import kotlinx.android.synthetic.main.fragment_login_symbol.*
import javax.inject.Inject
class LoginSymbolFragment : BaseFragment(), LoginSymbolView {
@Inject
lateinit var presenter: LoginSymbolPresenter
companion object {
private const val SAVED_LOGIN_DATA = "LOGIN_DATA"
fun newInstance() = LoginSymbolFragment()
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.fragment_login_symbol, container, false)
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
presenter.onAttachView(this, savedInstanceState?.getSerializable(SAVED_LOGIN_DATA))
}
override fun initView() {
loginSymbolSignIn.setOnClickListener { presenter.attemptLogin(loginSymbolName.text.toString()) }
loginSymbolName.apply {
setOnEditorActionListener { _, id, _ ->
if (id == IME_ACTION_DONE || id == IME_NULL) loginSymbolSignIn.callOnClick() else false
}
setAdapter(ArrayAdapter(context, android.R.layout.simple_list_item_1, resources.getStringArray(R.array.symbols_values)))
}
}
fun onParentInitSymbolFragment(loginData: Triple<String, String, String>) {
presenter.onParentInitSymbolView(loginData)
}
override fun setErrorSymbolIncorrect() {
loginSymbolName.apply {
requestFocus()
error = getString(R.string.login_incorrect_symbol)
}
}
override fun setErrorSymbolRequire() {
loginSymbolName.apply {
requestFocus()
error = getString(R.string.login_field_required)
}
}
override fun clearAndFocusSymbol() {
loginSymbolName.apply {
text = null
requestFocus()
}
}
override fun showSoftKeyboard() {
activity?.showSoftInput()
}
override fun hideSoftKeyboard() {
activity?.hideSoftInput()
}
override fun showProgress(show: Boolean) {
loginSymbolProgressContainer.visibility = if (show) VISIBLE else GONE
}
override fun showContent(show: Boolean) {
loginSymbolContainer.visibility = if (show) VISIBLE else GONE
}
override fun notifyParentAccountLogged(students: List<Student>) {
(activity as? LoginActivity)?.onSymbolFragmentAccountLogged(students)
}
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
outState.putSerializable(SAVED_LOGIN_DATA, presenter.loginData)
}
override fun onDestroyView() {
super.onDestroyView()
presenter.onDetachView()
}
}

View File

@ -0,0 +1,82 @@
package io.github.wulkanowy.ui.modules.login.symbol
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.reactivex.Single
import timber.log.Timber
import java.io.Serializable
import javax.inject.Inject
class LoginSymbolPresenter @Inject constructor(
private val studentRepository: StudentRepository,
private val errorHandler: LoginErrorHandler,
private val schedulers: SchedulersProvider,
private val analytics: FirebaseAnalyticsHelper
) : BasePresenter<LoginSymbolView>(errorHandler) {
var loginData: Triple<String, String, String>? = null
@Suppress("UNCHECKED_CAST")
fun onAttachView(view: LoginSymbolView, savedLoginData: Serializable?) {
super.onAttachView(view)
view.initView()
if (savedLoginData is Triple<*, *, *>) {
loginData = savedLoginData as Triple<String, String, String>
}
}
fun attemptLogin(symbol: String) {
if (symbol.isBlank()) {
view?.setErrorSymbolRequire()
return
}
disposable.add(
Single.fromCallable { if (loginData == null) throw IllegalArgumentException("Login data is null") else loginData }
.flatMap { studentRepository.getStudents(it.first, it.second, it.third, symbol) }
.subscribeOn(schedulers.backgroundThread)
.observeOn(schedulers.mainThread)
.doOnSubscribe {
view?.apply {
hideSoftKeyboard()
showProgress(true)
showContent(false)
}
Timber.i("Login with symbol started")
}
.doFinally {
view?.apply {
showProgress(false)
showContent(true)
}
}
.subscribe({
analytics.logEvent("registration_symbol", SUCCESS to true, "students" to it.size, "endpoint" to loginData?.third, "symbol" to symbol, "error" to "No error")
view?.apply {
if (it.isEmpty()) {
Timber.i("Login with symbol result: Empty student list")
setErrorSymbolIncorrect()
} else {
Timber.i("Login with symbol result: Success")
notifyParentAccountLogged(it)
}
}
}, {
Timber.i("Login with symbol result: An exception occurred")
analytics.logEvent("registration_symbol", SUCCESS to false, "students" to -1, "endpoint" to loginData?.third, "symbol" to symbol, "error" to it.localizedMessage)
errorHandler.dispatch(it)
}))
}
fun onParentInitSymbolView(loginData: Triple<String, String, String>) {
this.loginData = loginData
view?.apply {
clearAndFocusSymbol()
showSoftKeyboard()
}
}
}

View File

@ -0,0 +1,25 @@
package io.github.wulkanowy.ui.modules.login.symbol
import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.ui.base.BaseView
interface LoginSymbolView : BaseView {
fun initView()
fun setErrorSymbolIncorrect()
fun setErrorSymbolRequire()
fun clearAndFocusSymbol()
fun showSoftKeyboard()
fun hideSoftKeyboard()
fun showProgress(show: Boolean)
fun showContent(show: Boolean)
fun notifyParentAccountLogged(students: List<Student>)
}

View File

@ -7,7 +7,6 @@ import io.github.wulkanowy.ui.base.BasePresenter
import io.github.wulkanowy.ui.base.session.SessionErrorHandler import io.github.wulkanowy.ui.base.session.SessionErrorHandler
import io.github.wulkanowy.utils.FirebaseAnalyticsHelper import io.github.wulkanowy.utils.FirebaseAnalyticsHelper
import io.github.wulkanowy.utils.SchedulersProvider import io.github.wulkanowy.utils.SchedulersProvider
import io.reactivex.MaybeSource
import javax.inject.Inject import javax.inject.Inject
class LuckyNumberPresenter @Inject constructor( class LuckyNumberPresenter @Inject constructor(
@ -45,7 +44,7 @@ class LuckyNumberPresenter @Inject constructor(
showContent(true) showContent(true)
showEmpty(false) showEmpty(false)
} }
analytics.logEvent("load_lucky_number", mapOf("lucky_number" to it.luckyNumber, "force_refresh" to forceRefresh)) analytics.logEvent("load_lucky_number", "lucky_number" to it.luckyNumber, "force_refresh" to forceRefresh)
}, { }, {
view?.run { showEmpty(isViewEmpty()) } view?.run { showEmpty(isViewEmpty()) }
errorHandler.dispatch(it) errorHandler.dispatch(it)

View File

@ -32,12 +32,12 @@ class MainPresenter @Inject constructor(
} }
serviceHelper.startFullSyncService() serviceHelper.startFullSyncService()
analytics.logEvent(APP_OPEN, mapOf(DESTINATION to when (initMenuIndex) { analytics.logEvent(APP_OPEN, DESTINATION to when (initMenuIndex) {
1 -> "Grades" 1 -> "Grades"
3 -> "Timetable" 3 -> "Timetable"
4 -> "More" 4 -> "More"
else -> "User action" else -> "User action"
})) })
} }
fun onViewChange() { fun onViewChange() {

View File

@ -48,9 +48,9 @@ class MessagePreviewPresenter @Inject constructor(
else setSender(it.sender) else setSender(it.sender)
} }
} }
analytics.logEvent("load_message_preview", mapOf(START_DATE to message.date?.toFormattedString("yyyy.MM.dd"), "lenght" to message.content?.length)) analytics.logEvent("load_message_preview", START_DATE to message.date?.toFormattedString("yyyy.MM.dd"), "lenght" to message.content?.length)
}) { }) {
Timber.i("Loading message $id preview result: An excception occurred ") Timber.i("Loading message $id preview result: An exception occurred ")
view?.showMessageError() view?.showMessageError()
errorHandler.dispatch(it) errorHandler.dispatch(it)
}) })

View File

@ -56,7 +56,7 @@ class MessageTabPresenter @Inject constructor(
showContent(it.isNotEmpty()) showContent(it.isNotEmpty())
updateData(it) updateData(it)
} }
analytics.logEvent("load_messages", mapOf("items" to it.size, "folder" to folder.name)) analytics.logEvent("load_messages", "items" to it.size, "folder" to folder.name)
}) { }) {
Timber.i("Loading $folder message result: An exception occurred") Timber.i("Loading $folder message result: An exception occurred")
view?.run { showEmpty(isViewEmpty) } view?.run { showEmpty(isViewEmpty) }

View File

@ -54,7 +54,7 @@ class NotePresenter @Inject constructor(
showEmpty(it.isEmpty()) showEmpty(it.isEmpty())
showContent(it.isNotEmpty()) showContent(it.isNotEmpty())
} }
analytics.logEvent("load_note", mapOf("items" to it.size, "force_refresh" to forceRefresh)) analytics.logEvent("load_note", "items" to it.size, "force_refresh" to forceRefresh)
}, { }, {
Timber.i("Loading note result: An exception occurred") Timber.i("Loading note result: An exception occurred")
view?.run { showEmpty(isViewEmpty) } view?.run { showEmpty(isViewEmpty) }

View File

@ -46,6 +46,6 @@ class SettingsPresenter @Inject constructor(
chuckCollector.showNotification(preferencesRepository.isShowChuckerNotification) chuckCollector.showNotification(preferencesRepository.isShowChuckerNotification)
} }
} }
analytics.logEvent("setting_changed", mapOf("name" to key)) analytics.logEvent("setting_changed", "name" to key)
} }
} }

View File

@ -99,7 +99,7 @@ class TimetablePresenter @Inject constructor(
showEmpty(it.isEmpty()) showEmpty(it.isEmpty())
showContent(it.isNotEmpty()) showContent(it.isNotEmpty())
} }
analytics.logEvent("load_timetable", mapOf("items" to it.size, "force_refresh" to forceRefresh, START_DATE to currentDate.toFormattedString("yyyy-MM-dd"))) analytics.logEvent("load_timetable", "items" to it.size, "force_refresh" to forceRefresh, START_DATE to currentDate.toFormattedString("yyyy-MM-dd"))
}) { }) {
Timber.i("Loading timetable result: An exception occurred") Timber.i("Loading timetable result: An exception occurred")
view?.run { showEmpty(isViewEmpty) } view?.run { showEmpty(isViewEmpty) }

View File

@ -96,7 +96,7 @@ class TimetableWidgetProvider : AppWidgetProvider() {
} }
button?.also { btn -> button?.also { btn ->
if (btn.isNotBlank()) { if (btn.isNotBlank()) {
analytics.logEvent("changed_timetable_widget_day", mapOf("button" to button)) analytics.logEvent("changed_timetable_widget_day", "button" to button)
} }
} }
} }

View File

@ -7,14 +7,14 @@ import javax.inject.Singleton
@Singleton @Singleton
class FirebaseAnalyticsHelper(private val analytics: FirebaseAnalytics) { class FirebaseAnalyticsHelper(private val analytics: FirebaseAnalytics) {
fun logEvent(name: String, params: Map<String, Any?>) { fun logEvent(name: String, vararg params: Pair<String, Any?>) {
Bundle().apply { Bundle().apply {
params.forEach { params.forEach {
if (it.value == null) return@forEach if (it.second == null) return@forEach
when (it.value) { when (it.second) {
is String, is String? -> putString(it.key, it.value as String) is String, is String? -> putString(it.first, it.second as String)
is Int, is Int? -> putInt(it.key, it.value as Int) is Int, is Int? -> putInt(it.first, it.second as Int)
is Boolean, is Boolean? -> putBoolean(it.key, it.value as Boolean) is Boolean, is Boolean? -> putBoolean(it.first, it.second as Boolean)
} }
} }
analytics.logEvent(name, this) analytics.logEvent(name, this)

View File

@ -1,6 +1,5 @@
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent"> android:layout_height="match_parent">
@ -34,7 +33,6 @@
</RelativeLayout> </RelativeLayout>
<ScrollView <ScrollView
android:id="@+id/loginScroll"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:scrollbars="none"> android:scrollbars="none">
@ -49,7 +47,7 @@
android:orientation="vertical"> android:orientation="vertical">
<TextView <TextView
android:id="@+id/loginHeader" android:id="@+id/loginFormHeader"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="20dp" android:layout_marginTop="20dp"
@ -57,11 +55,6 @@
android:text="@string/login_header_default" android:text="@string/login_header_default"
android:textSize="16sp" /> android:textSize="16sp" />
<LinearLayout
android:id="@+id/loginMainForm"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<com.google.android.material.textfield.TextInputLayout <com.google.android.material.textfield.TextInputLayout
android:layout_width="match_parent" android:layout_width="match_parent"
@ -70,7 +63,7 @@
android:hint="@string/login_nickname_hint"> android:hint="@string/login_nickname_hint">
<androidx.appcompat.widget.AppCompatEditText <androidx.appcompat.widget.AppCompatEditText
android:id="@+id/loginNicknameEdit" android:id="@+id/loginFormName"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:inputType="textEmailAddress" android:inputType="textEmailAddress"
@ -85,7 +78,7 @@
android:hint="@string/login_password_hint"> android:hint="@string/login_password_hint">
<androidx.appcompat.widget.AppCompatEditText <androidx.appcompat.widget.AppCompatEditText
android:id="@+id/loginPassEdit" android:id="@+id/loginFormPass"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:imeActionLabel="@string/login_sign_in" android:imeActionLabel="@string/login_sign_in"
@ -97,32 +90,13 @@
</com.google.android.material.textfield.TextInputLayout> </com.google.android.material.textfield.TextInputLayout>
<androidx.appcompat.widget.AppCompatSpinner <androidx.appcompat.widget.AppCompatSpinner
android:id="@+id/loginHostEdit" android:id="@+id/loginFormHost"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:hint="@string/login_host_hint" /> android:hint="@string/login_host_hint" />
</LinearLayout>
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/loginSymbolInput"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/login_symbol_hint"
android:visibility="gone">
<AutoCompleteTextView
android:id="@+id/loginSymbolEdit"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:imeActionLabel="@string/login_sign_in"
android:imeOptions="actionDone"
android:inputType="textAutoComplete"
android:maxLines="1"
tools:ignore="LabelFor" />
</com.google.android.material.textfield.TextInputLayout>
<androidx.appcompat.widget.AppCompatButton <androidx.appcompat.widget.AppCompatButton
android:id="@+id/loginSignButton" android:id="@+id/loginFormSignIn"
style="?android:textAppearanceSmall" style="?android:textAppearanceSmall"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
@ -134,12 +108,11 @@
app:backgroundTint="@color/colorPrimary" /> app:backgroundTint="@color/colorPrimary" />
<TextView <TextView
android:id="@+id/loginVersion" android:id="@+id/loginFormVersion"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="center_horizontal" android:layout_gravity="center_horizontal"
android:layout_marginBottom="30dp" android:layout_marginBottom="30dp"
android:text="@string/app_name"
android:textColor="@color/second_text" android:textColor="@color/second_text"
android:visibility="gone" /> android:visibility="gone" />
</LinearLayout> </LinearLayout>

View File

@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:orientation="vertical"> android:orientation="vertical">
@ -18,8 +19,8 @@
android:layout_below="@id/loginOptionsProgressText" android:layout_below="@id/loginOptionsProgressText"
android:layout_centerHorizontal="true" android:layout_centerHorizontal="true"
android:indeterminate="true" android:indeterminate="true"
android:minHeight="30dp" android:minWidth="220dp"
android:minWidth="220dp" /> android:minHeight="30dp" />
<TextView <TextView
android:id="@+id/loginOptionsProgressText" android:id="@+id/loginOptionsProgressText"
@ -33,5 +34,7 @@
<androidx.recyclerview.widget.RecyclerView <androidx.recyclerview.widget.RecyclerView
android:id="@+id/loginOptionsRecycler" android:id="@+id/loginOptionsRecycler"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" /> android:layout_height="match_parent"
tools:itemCount="5"
tools:listitem="@layout/item_login_options" />
</LinearLayout> </LinearLayout>

View File

@ -0,0 +1,88 @@
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
<RelativeLayout
android:id="@+id/loginSymbolProgressContainer"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_alignParentStart="true"
android:layout_alignParentLeft="true"
android:layout_alignParentTop="true"
android:gravity="center"
android:visibility="gone">
<ProgressBar
style="?android:attr/progressBarStyleHorizontal"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/loginSymbolProgressText"
android:layout_centerHorizontal="true"
android:indeterminate="true"
android:minWidth="220dp"
android:minHeight="30dp" />
<TextView
android:id="@+id/loginSymbolProgressText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:layout_marginBottom="15dp"
android:text="@string/login_progress" />
</RelativeLayout>
<ScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scrollbars="none">
<LinearLayout
android:id="@+id/loginSymbolContainer"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="16dp"
android:layout_marginTop="16dp"
android:layout_marginRight="16dp"
android:orientation="vertical">
<TextView
android:id="@+id/loginSymbolHeader"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:layout_marginBottom="24dp"
android:text="@string/login_header_symbol"
android:textSize="16sp" />
<com.google.android.material.textfield.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/login_symbol_hint">
<AutoCompleteTextView
android:id="@+id/loginSymbolName"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:imeActionLabel="@string/login_sign_in"
android:imeOptions="actionDone"
android:inputType="textAutoComplete"
android:maxLines="1"
tools:ignore="LabelFor" />
</com.google.android.material.textfield.TextInputLayout>
<androidx.appcompat.widget.AppCompatButton
android:id="@+id/loginSymbolSignIn"
style="?android:textAppearanceSmall"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:layout_marginBottom="30dp"
android:text="@string/login_sign_in"
android:textColor="@android:color/white"
android:textStyle="bold"
app:backgroundTint="@color/colorPrimary" />
</LinearLayout>
</ScrollView>
</RelativeLayout>

View File

@ -1,7 +1,7 @@
package io.github.wulkanowy.data.repositories.remote package io.github.wulkanowy.data.repositories.remote
import io.github.wulkanowy.api.Api import io.github.wulkanowy.api.Api
import io.github.wulkanowy.api.register.Pupil import io.github.wulkanowy.api.register.Student
import io.reactivex.Single import io.reactivex.Single
import org.junit.Assert.assertEquals import org.junit.Assert.assertEquals
import org.junit.Before import org.junit.Before
@ -22,8 +22,8 @@ class StudentRemoteTest {
@Test @Test
fun testRemoteAll() { fun testRemoteAll() {
doReturn(Single.just(listOf(Pupil("", "", 1, "test", "", "", "", Api.LoginType.AUTO)))) doReturn(Single.just(listOf(Student("", "", 1, "test", "", "", Api.LoginType.AUTO))))
.`when`(mockApi).getPupils() .`when`(mockApi).getStudents()
val students = StudentRemote(mockApi).getStudents("", "", "").blockingGet() val students = StudentRemote(mockApi).getStudents("", "", "").blockingGet()
assertEquals(1, students.size) assertEquals(1, students.size)

View File

@ -6,7 +6,6 @@ import org.junit.Test
import org.mockito.Mock import org.mockito.Mock
import org.mockito.Mockito.clearInvocations import org.mockito.Mockito.clearInvocations
import org.mockito.Mockito.doReturn import org.mockito.Mockito.doReturn
import org.mockito.Mockito.never
import org.mockito.Mockito.verify import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations import org.mockito.MockitoAnnotations
@ -32,25 +31,7 @@ class LoginPresenterTest {
@Test @Test
fun initViewTest() { fun initViewTest() {
verify(loginView).initAdapter() verify(loginView).initAdapter()
verify(loginView).hideActionBar() verify(loginView).showActionBar(false)
}
@Test
fun onPageSelectedTest() {
presenter.onPageSelected(1)
verify(loginView).notifyOptionsViewLoadData()
}
@Test
fun onPageSelectedNeverTest() {
presenter.onPageSelected(0)
verify(loginView, never()).notifyOptionsViewLoadData()
}
@Test
fun onSwitchFragmentTest() {
presenter.onChildViewSwitchOptions()
verify(loginView).switchView(1)
} }
@Test @Test
@ -59,7 +40,6 @@ class LoginPresenterTest {
doReturn(1).`when`(loginView).currentViewIndex doReturn(1).`when`(loginView).currentViewIndex
presenter.onBackPressed { } presenter.onBackPressed { }
verify(loginView).switchView(0) verify(loginView).switchView(0)
verify(loginView).hideActionBar()
} }
@Test @Test

View File

@ -37,7 +37,7 @@ class LoginFormPresenterTest {
fun initPresenter() { fun initPresenter() {
MockitoAnnotations.initMocks(this) MockitoAnnotations.initMocks(this)
clearInvocations(repository, loginFormView) clearInvocations(repository, loginFormView)
presenter = LoginFormPresenter(TestSchedulersProvider(), errorHandler, repository, analytics) presenter = LoginFormPresenter(TestSchedulersProvider(), errorHandler, repository, analytics, false)
presenter.onAttachView(loginFormView) presenter.onAttachView(loginFormView)
} }
@ -48,87 +48,70 @@ class LoginFormPresenterTest {
@Test @Test
fun emptyNicknameLoginTest() { fun emptyNicknameLoginTest() {
presenter.attemptLogin("", "test123", "test", "https://fakelog.cf") presenter.attemptLogin("", "test123", "https://fakelog.cf")
verify(loginFormView).setErrorNicknameRequired() verify(loginFormView).setErrorNameRequired()
verify(loginFormView, never()).setErrorPassRequired(false) verify(loginFormView, never()).setErrorPassRequired(false)
verify(loginFormView, never()).setErrorSymbolRequire()
verify(loginFormView, never()).setErrorPassInvalid(false) verify(loginFormView, never()).setErrorPassInvalid(false)
} }
@Test @Test
fun emptyPassLoginTest() { fun emptyPassLoginTest() {
presenter.attemptLogin("@", "", "test", "https://fakelog.cf") presenter.attemptLogin("@", "", "https://fakelog.cf")
verify(loginFormView, never()).setErrorNicknameRequired() verify(loginFormView, never()).setErrorNameRequired()
verify(loginFormView).setErrorPassRequired(true) verify(loginFormView).setErrorPassRequired(true)
verify(loginFormView, never()).setErrorSymbolRequire()
verify(loginFormView, never()).setErrorPassInvalid(false) verify(loginFormView, never()).setErrorPassInvalid(false)
} }
@Test @Test
fun invalidPassLoginTest() { fun invalidPassLoginTest() {
presenter.attemptLogin("@", "123", "test", "https://fakelog.cf") presenter.attemptLogin("@", "123", "https://fakelog.cf")
verify(loginFormView, never()).setErrorNicknameRequired() verify(loginFormView, never()).setErrorNameRequired()
verify(loginFormView, never()).setErrorPassRequired(true) verify(loginFormView, never()).setErrorPassRequired(true)
verify(loginFormView, never()).setErrorSymbolRequire()
verify(loginFormView).setErrorPassInvalid(true) verify(loginFormView).setErrorPassInvalid(true)
} }
@Test
fun emptySymbolLoginTest() {
doReturn(Single.just(emptyList<Student>()))
.`when`(repository).getStudents(anyString(), anyString(), anyString(), anyString())
presenter.attemptLogin("@", "123456", "", "https://fakelog.cf")
presenter.attemptLogin("@", "123456", "", "https://fakelog.cf")
verify(loginFormView).setErrorSymbolRequire()
}
@Test @Test
fun loginTest() { fun loginTest() {
val studentTest = Student(email = "test@", password = "123", endpoint = "https://fakelog.cf", loginType = "AUTO") val studentTest = Student(email = "test@", password = "123", endpoint = "https://fakelog.cf", loginType = "AUTO")
doReturn(Single.just(listOf(studentTest))) doReturn(Single.just(listOf(studentTest)))
.`when`(repository).getStudents(anyString(), anyString(), anyString(), anyString()) .`when`(repository).getStudents(anyString(), anyString(), anyString(), anyString())
presenter.attemptLogin("@", "123456", "test", "https://fakelog.cf") presenter.attemptLogin("@", "123456", "https://fakelog.cf")
verify(loginFormView).hideSoftKeyboard() verify(loginFormView).hideSoftKeyboard()
verify(loginFormView).showProgress(true) verify(loginFormView).showProgress(true)
verify(loginFormView).showProgress(false) verify(loginFormView).showProgress(false)
verify(loginFormView).showContent(false) verify(loginFormView).showContent(false)
verify(loginFormView).showContent(true) verify(loginFormView).showContent(true)
verify(loginFormView).switchOptionsView()
} }
@Test @Test
fun loginEmptyTest() { fun loginEmptyTest() {
doReturn(Single.just(emptyList<Student>())) doReturn(Single.just(emptyList<Student>()))
.`when`(repository).getStudents(anyString(), anyString(), anyString(), anyString()) .`when`(repository).getStudents(anyString(), anyString(), anyString(), anyString())
presenter.attemptLogin("@", "123456", "test", "https://fakelog.cf") presenter.attemptLogin("@", "123456", "https://fakelog.cf")
verify(loginFormView).hideSoftKeyboard() verify(loginFormView).hideSoftKeyboard()
verify(loginFormView).showProgress(true) verify(loginFormView).showProgress(true)
verify(loginFormView).showProgress(false) verify(loginFormView).showProgress(false)
verify(loginFormView).showContent(false) verify(loginFormView).showContent(false)
verify(loginFormView).showContent(true) verify(loginFormView).showContent(true)
verify(loginFormView).showSymbolInput()
} }
@Test @Test
fun loginEmptyTwiceTest() { fun loginEmptyTwiceTest() {
doReturn(Single.just(emptyList<Student>())) doReturn(Single.just(emptyList<Student>()))
.`when`(repository).getStudents(anyString(), anyString(), anyString(), anyString()) .`when`(repository).getStudents(anyString(), anyString(), anyString(), anyString())
presenter.attemptLogin("@", "123456", "", "https://fakelog.cf") presenter.attemptLogin("@", "123456", "https://fakelog.cf")
presenter.attemptLogin("@", "123456", "test", "https://fakelog.cf") presenter.attemptLogin("@", "123456", "https://fakelog.cf")
verify(loginFormView, times(2)).hideSoftKeyboard() verify(loginFormView, times(2)).hideSoftKeyboard()
verify(loginFormView, times(2)).showProgress(true) verify(loginFormView, times(2)).showProgress(true)
verify(loginFormView, times(2)).showProgress(false) verify(loginFormView, times(2)).showProgress(false)
verify(loginFormView, times(2)).showContent(false) verify(loginFormView, times(2)).showContent(false)
verify(loginFormView, times(2)).showContent(true) verify(loginFormView, times(2)).showContent(true)
verify(loginFormView, times(2)).showSymbolInput()
verify(loginFormView).setErrorSymbolIncorrect()
} }
@Test @Test
@ -136,7 +119,7 @@ class LoginFormPresenterTest {
val testException = RuntimeException("test") val testException = RuntimeException("test")
doReturn(Single.error<List<Student>>(testException)) doReturn(Single.error<List<Student>>(testException))
.`when`(repository).getStudents(anyString(), anyString(), anyString(), anyString()) .`when`(repository).getStudents(anyString(), anyString(), anyString(), anyString())
presenter.attemptLogin("@", "123456", "test", "https://fakelog.cf") presenter.attemptLogin("@", "123456", "https://fakelog.cf")
verify(loginFormView).hideSoftKeyboard() verify(loginFormView).hideSoftKeyboard()
verify(loginFormView).showProgress(true) verify(loginFormView).showProgress(true)

View File

@ -1,4 +1,4 @@
package io.github.wulkanowy.ui.modules.login.options package io.github.wulkanowy.ui.modules.login.studentselect
import io.github.wulkanowy.TestSchedulersProvider import io.github.wulkanowy.TestSchedulersProvider
import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Semester
@ -17,13 +17,13 @@ import org.mockito.Mockito.doReturn
import org.mockito.Mockito.verify import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations import org.mockito.MockitoAnnotations
class LoginOptionsPresenterTest { class LoginStudentSelectPresenterTest {
@Mock @Mock
lateinit var errorHandler: LoginErrorHandler lateinit var errorHandler: LoginErrorHandler
@Mock @Mock
lateinit var loginOptionsView: LoginOptionsView lateinit var loginStudentSelectView: LoginStudentSelectView
@Mock @Mock
lateinit var studentRepository: StudentRepository lateinit var studentRepository: StudentRepository
@ -34,7 +34,7 @@ class LoginOptionsPresenterTest {
@Mock @Mock
lateinit var analytics: FirebaseAnalyticsHelper lateinit var analytics: FirebaseAnalyticsHelper
private lateinit var presenter: LoginOptionsPresenter private lateinit var presenter: LoginStudentSelectPresenter
private val testStudent by lazy { Student(email = "test", password = "test123", endpoint = "https://fakelog.cf", loginType = "AUTO") } private val testStudent by lazy { Student(email = "test", password = "test123", endpoint = "https://fakelog.cf", loginType = "AUTO") }
@ -43,31 +43,15 @@ class LoginOptionsPresenterTest {
@Before @Before
fun initPresenter() { fun initPresenter() {
MockitoAnnotations.initMocks(this) MockitoAnnotations.initMocks(this)
clearInvocations(studentRepository, loginOptionsView) clearInvocations(studentRepository, loginStudentSelectView)
clearInvocations(semesterRepository, loginOptionsView) clearInvocations(semesterRepository, loginStudentSelectView)
presenter = LoginOptionsPresenter(errorHandler, studentRepository, semesterRepository, TestSchedulersProvider(), analytics) presenter = LoginStudentSelectPresenter(errorHandler, studentRepository, semesterRepository, TestSchedulersProvider(), analytics)
presenter.onAttachView(loginOptionsView) presenter.onAttachView(loginStudentSelectView, null)
} }
@Test @Test
fun initViewTest() { fun initViewTest() {
verify(loginOptionsView).initView() verify(loginStudentSelectView).initView()
}
@Test
fun refreshDataTest() {
doReturn(Single.just(listOf(testStudent))).`when`(studentRepository).cachedStudents
presenter.onParentViewLoadData()
verify(loginOptionsView).showActionBar(true)
verify(loginOptionsView).updateData(listOf(LoginOptionsItem(testStudent)))
}
@Test
fun refreshDataErrorTest() {
doReturn(Single.error<List<Student>>(testException)).`when`(studentRepository).cachedStudents
presenter.onParentViewLoadData()
verify(loginOptionsView).showActionBar(true)
verify(errorHandler).dispatch(testException)
} }
@Test @Test
@ -75,10 +59,10 @@ class LoginOptionsPresenterTest {
doReturn(Single.just(1L)).`when`(studentRepository).saveStudent(testStudent) doReturn(Single.just(1L)).`when`(studentRepository).saveStudent(testStudent)
doReturn(Single.just(emptyList<Semester>())).`when`(semesterRepository).getSemesters(testStudent, true) doReturn(Single.just(emptyList<Semester>())).`when`(semesterRepository).getSemesters(testStudent, true)
doReturn(Completable.complete()).`when`(studentRepository).switchStudent(testStudent) doReturn(Completable.complete()).`when`(studentRepository).switchStudent(testStudent)
presenter.onItemSelected(LoginOptionsItem(testStudent)) presenter.onItemSelected(LoginStudentSelectItem(testStudent))
verify(loginOptionsView).showContent(false) verify(loginStudentSelectView).showContent(false)
verify(loginOptionsView).showProgress(true) verify(loginStudentSelectView).showProgress(true)
verify(loginOptionsView).openMainView() verify(loginStudentSelectView).openMainView()
} }
@Test @Test
@ -86,9 +70,9 @@ class LoginOptionsPresenterTest {
doReturn(Single.error<Student>(testException)).`when`(studentRepository).saveStudent(testStudent) doReturn(Single.error<Student>(testException)).`when`(studentRepository).saveStudent(testStudent)
doReturn(Single.just(emptyList<Semester>())).`when`(semesterRepository).getSemesters(testStudent, true) doReturn(Single.just(emptyList<Semester>())).`when`(semesterRepository).getSemesters(testStudent, true)
doReturn(Completable.complete()).`when`(studentRepository).logoutStudent(testStudent) doReturn(Completable.complete()).`when`(studentRepository).logoutStudent(testStudent)
presenter.onItemSelected(LoginOptionsItem(testStudent)) presenter.onItemSelected(LoginStudentSelectItem(testStudent))
verify(loginOptionsView).showContent(false) verify(loginStudentSelectView).showContent(false)
verify(loginOptionsView).showProgress(true) verify(loginStudentSelectView).showProgress(true)
verify(errorHandler).dispatch(testException) verify(errorHandler).dispatch(testException)
} }
} }

View File

@ -1,5 +1,5 @@
buildscript { buildscript {
ext.kotlin_version = '1.3.20' ext.kotlin_version = '1.3.21'
repositories { repositories {
mavenCentral() mavenCentral()
google() google()
@ -9,12 +9,11 @@ buildscript {
} }
dependencies { dependencies {
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath 'com.android.tools.build:gradle:3.3.0' classpath 'com.android.tools.build:gradle:3.3.1'
classpath 'com.google.gms:google-services:4.2.0' classpath 'com.google.gms:google-services:4.2.0'
classpath "io.fabric.tools:gradle:1.27.0" classpath "io.fabric.tools:gradle:1.27.0"
classpath "com.github.triplet.gradle:play-publisher:2.1.0" classpath "com.github.triplet.gradle:play-publisher:2.1.0"
classpath "org.sonarsource.scanner.gradle:sonarqube-gradle-plugin:2.7" classpath "org.sonarsource.scanner.gradle:sonarqube-gradle-plugin:2.7"
classpath 'com.android.tools.build.jetifier:jetifier-processor:1.0.0-beta02'
} }
} }