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

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_IF_CONDITIONS" value="false" />
<option name="CONTINUATION_INDENT_IN_ELVIS" value="false" />
<option name="WRAP_EXPRESSION_BODY_FUNCTIONS" value="1" />
</JetCodeStyleSettings>
<Objective-C-extensions>
<file>

View File

@ -73,7 +73,7 @@ play {
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$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.appcompat:appcompat:1.0.2"
@ -82,20 +82,18 @@ dependencies {
implementation 'androidx.multidex:multidex:2.0.1'
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.google.dagger:dagger-android-support:2.21"
kapt "com.google.dagger:dagger-compiler:2.21"
kapt "com.google.dagger:dagger-android-processor:2.21"
implementation "androidx.room:room-runtime:2.1.0-alpha03"
implementation "androidx.room:room-rxjava2:2.1.0-alpha03"
kapt "androidx.room:room-compiler:2.1.0-alpha03"
implementation "androidx.room:room-runtime:2.1.0-alpha04"
implementation "androidx.room:room-rxjava2:2.1.0-alpha04"
kapt "androidx.room:room-compiler:2.1.0-alpha04"
implementation "eu.davidea:flexible-adapter:5.1.0"
implementation "eu.davidea:flexible-adapter-ui:1.0.0"
implementation "com.aurelhubert:ahbottomnavigation:2.3.4"
implementation 'com.ncapdevi:frag-nav:3.1.0'
@ -104,16 +102,16 @@ dependencies {
implementation "io.reactivex.rxjava2:rxjava:2.2.5"
implementation "com.jakewharton.threetenabp:threetenabp:1.1.1"
implementation "com.jakewharton.timber:timber:4.7.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.crashlytics.sdk.android:crashlytics:2.9.8'
implementation 'com.google.firebase:firebase-core:16.0.7'
implementation 'com.crashlytics.sdk.android:crashlytics:2.9.9'
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"
testImplementation "junit:junit:4.12"

View File

@ -14,10 +14,10 @@ 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)
useNewStudent = true
}
}

View File

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

View File

@ -24,17 +24,13 @@ class StudentRepository @Inject constructor(
val isStudentSaved
get() = local.isStudentSaved
lateinit var cachedStudents: Single<List<Student>>
private set
fun getStudents(email: String, password: String, symbol: String, endpoint: String): Single<List<Student>> {
cachedStudents = ReactiveNetwork.checkInternetConnectivity(settings)
fun getStudents(email: String, password: String, endpoint: String, symbol: String = ""): Single<List<Student>> {
return ReactiveNetwork.checkInternetConnectivity(settings)
.flatMap {
apiHelper.initApi(email, password, symbol, endpoint)
if (it) remote.getStudents(email, password, endpoint)
else Single.error(UnknownHostException("No internet connection"))
}.doOnSuccess { cachedStudents = Single.just(it) }
return cachedStudents
}
}
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,
comment = it.comment,
color = it.color,
gradeSymbol = it.symbol ?: "",
description = it.description,
gradeSymbol = it.symbol.orEmpty(),
description = it.description.orEmpty(),
weight = it.weight,
weightValue = it.weightValue,
date = it.date.toLocalDate(),

View File

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

View File

@ -8,9 +8,11 @@ import dagger.Module
import dagger.Provides
import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
import io.github.wulkanowy.BuildConfig.DEBUG
import io.github.wulkanowy.WulkanowyApp
import io.github.wulkanowy.utils.FirebaseAnalyticsHelper
import io.github.wulkanowy.utils.SchedulersProvider
import javax.inject.Named
import javax.inject.Singleton
@Module
@ -34,4 +36,9 @@ internal class AppModule {
@Singleton
@Provides
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) {
SPECIAL1 -> {
Timber.i("Opening github page")
analytics.logEvent("open_page", mapOf("name" to "github"))
analytics.logEvent("open_page", "name" to "github")
openSourceWebView()
}
SPECIAL2 -> {
Timber.i("Opening issues page")
analytics.logEvent("open_page", mapOf("name" to "issues"))
analytics.logEvent("open_page", "name" to "issues")
openIssuesWebView()
}
SPECIAL3 -> {

View File

@ -114,7 +114,7 @@ class AttendancePresenter @Inject constructor(
showEmpty(it.isEmpty())
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")
view?.run { showEmpty(isViewEmpty) }

View File

@ -83,7 +83,7 @@ class AttendanceSummaryPresenter @Inject constructor(
showContent(it.first.isNotEmpty())
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")
view?.run { showEmpty(isViewEmpty) }

View File

@ -101,7 +101,7 @@ class ExamPresenter @Inject constructor(
showEmpty(it.isEmpty())
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")
view?.run { showEmpty(isViewEmpty) }

View File

@ -57,7 +57,7 @@ class GradePresenter @Inject constructor(
notifyChildrenSemesterChange()
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())
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")
view?.run { showEmpty(isViewEmpty) }

View File

@ -72,7 +72,7 @@ class GradeSummaryPresenter @Inject constructor(
showContent(it.first.isNotEmpty())
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")
view?.run { showEmpty(isViewEmpty) }

View File

@ -86,7 +86,7 @@ class HomeworkPresenter @Inject constructor(
showEmpty(it.isEmpty())
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")
view?.run { showEmpty(isViewEmpty()) }

View File

@ -4,10 +4,12 @@ import android.content.Context
import android.content.Intent
import android.os.Bundle
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.BaseFragmentPagerAdapter
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 kotlinx.android.synthetic.main.activity_login.*
import javax.inject.Inject
@ -38,12 +40,14 @@ class LoginActivity : BaseActivity(), LoginView {
override fun initAdapter() {
loginAdapter.addFragments(listOf(
LoginFormFragment.newInstance(),
LoginOptionsFragment.newInstance()
LoginSymbolFragment.newInstance(),
LoginStudentSelectFragment.newInstance()
))
loginViewpager.run {
offscreenPageLimit = 2
adapter = loginAdapter
setOnSelectPageListener { presenter.onPageSelected(it) }
setOnSelectPageListener { presenter.onViewSelected(it) }
}
}
@ -51,22 +55,30 @@ class LoginActivity : BaseActivity(), LoginView {
loginViewpager.setCurrentItem(index, false)
}
override fun notifyOptionsViewLoadData() {
(loginAdapter.getFragmentInstance(1) as? LoginOptionsFragment)?.onParentLoadData()
}
fun onChildFragmentSwitchOptions() {
presenter.onChildViewSwitchOptions()
}
override fun hideActionBar() {
supportActionBar?.hide()
override fun showActionBar(show: Boolean) {
supportActionBar?.apply { if (show) show() else hide() }
}
override fun 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() {
presenter.onDetachView()
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.ui.base.BaseFragmentPagerAdapter
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
internal abstract class LoginModule {
@ -22,10 +23,14 @@ internal abstract class LoginModule {
}
@PerFragment
@ContributesAndroidInjector()
@ContributesAndroidInjector
abstract fun bindLoginFormFragment(): LoginFormFragment
@PerFragment
@ContributesAndroidInjector()
abstract fun bindLoginOptionsFragment(): LoginOptionsFragment
@ContributesAndroidInjector
abstract fun bindLoginSymbolFragment(): LoginSymbolFragment
@PerFragment
@ContributesAndroidInjector
abstract fun bindLoginSelectStudentFragment(): LoginStudentSelectFragment
}

View File

@ -1,5 +1,6 @@
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.ErrorHandler
import timber.log.Timber
@ -11,26 +12,49 @@ class LoginPresenter @Inject constructor(errorHandler: ErrorHandler) : BasePrese
super.onAttachView(view)
view.run {
initAdapter()
hideActionBar()
showActionBar(false)
}
Timber.i("Login view is attached")
}
fun onPageSelected(index: Int) {
if (index == 1) view?.notifyOptionsViewLoadData()
fun onFormViewAccountLogged(students: List<Student>, loginData: Triple<String, String, String>) {
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() {
view?.switchView(1)
fun onSymbolViewAccountLogged(students: List<Student>) {
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) {
Timber.i("Back pressed in login view")
view?.run {
if (currentViewIndex == 1) {
switchView(0)
hideActionBar()
} else default()
view?.apply {
when (currentViewIndex) {
1, 2 -> switchView(0)
else -> default()
}
}
}
}

View File

@ -1,5 +1,6 @@
package io.github.wulkanowy.ui.modules.login
import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.ui.base.BaseView
interface LoginView : BaseView {
@ -8,9 +9,11 @@ interface LoginView : BaseView {
fun initAdapter()
fun hideActionBar()
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_NULL
import android.widget.ArrayAdapter
import io.github.wulkanowy.BuildConfig.DEBUG
import io.github.wulkanowy.BuildConfig.VERSION_NAME
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
@ -29,8 +29,6 @@ class LoginFormFragment : BaseFragment(), LoginFormView {
fun newInstance() = LoginFormFragment()
}
override val isDebug = DEBUG
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.fragment_login_form, container, false)
}
@ -41,95 +39,52 @@ class LoginFormFragment : BaseFragment(), LoginFormView {
}
override fun initView() {
loginSignButton.setOnClickListener {
loginFormSignIn.setOnClickListener {
presenter.attemptLogin(
loginNicknameEdit.text.toString(),
loginPassEdit.text.toString(),
loginSymbolEdit.text.toString(),
resources.getStringArray(R.array.endpoints_values)[loginHostEdit.selectedItemPosition]
loginFormName.text.toString(),
loginFormPass.text.toString(),
resources.getStringArray(R.array.endpoints_values)[loginFormHost.selectedItemPosition]
)
}
loginPassEdit.setOnEditorActionListener { _, id, _ -> onEditAction(id) }
loginFormPass.setOnEditorActionListener { _, id, _ ->
if (id == IME_ACTION_DONE || id == IME_NULL) loginFormSignIn.callOnClick() else false
}
loginHostEdit.apply {
adapter = ArrayAdapter.createFromResource(context, R.array.endpoints_keys, android.R.layout.simple_spinner_item)
context?.let {
loginFormHost.adapter = ArrayAdapter.createFromResource(it, R.array.endpoints_keys, android.R.layout.simple_spinner_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() {
loginHeader.text = getString(R.string.login_header_symbol)
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 {
override fun setErrorNameRequired() {
loginFormName.run {
requestFocus()
error = getString(R.string.login_field_required)
}
}
override fun setErrorPassRequired(focus: Boolean) {
loginPassEdit.run {
loginFormPass.run {
if (focus) requestFocus()
error = getString(R.string.login_field_required)
}
}
override fun setErrorPassInvalid(focus: Boolean) {
loginPassEdit.run {
loginFormPass.run {
if (focus) requestFocus()
error = getString(R.string.login_invalid_password)
}
}
override fun setErrorSymbolRequire() {
loginSymbolEdit.run {
requestFocus()
error = getString(R.string.login_field_required)
}
}
override fun setErrorPassIncorrect() {
loginPassEdit.run {
loginFormPass.run {
requestFocus()
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() {
activity?.showSoftInput()
}
@ -146,13 +101,22 @@ class LoginFormFragment : BaseFragment(), LoginFormView {
loginFormContainer.visibility = if (show) VISIBLE else GONE
}
private fun onEditAction(actionId: Int): Boolean {
return when (actionId) {
IME_ACTION_DONE, IME_NULL -> loginSignButton.callOnClick()
else -> false
@SuppressLint("SetTextI18n")
override fun showVersion() {
loginFormVersion.apply {
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() {
super.onDestroyView()
presenter.onDetachView()

View File

@ -1,7 +1,5 @@
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
@ -10,16 +8,16 @@ import io.github.wulkanowy.utils.FirebaseAnalyticsHelper
import io.github.wulkanowy.utils.SchedulersProvider
import timber.log.Timber
import javax.inject.Inject
import javax.inject.Named
class LoginFormPresenter @Inject constructor(
private val schedulers: SchedulersProvider,
private val errorHandler: LoginErrorHandler,
private val studentRepository: StudentRepository,
private val analytics: FirebaseAnalyticsHelper
private val analytics: FirebaseAnalyticsHelper,
@param:Named("isDebug") private val isDebug: Boolean
) : BasePresenter<LoginFormView>(errorHandler) {
private var wasEmpty = false
override fun onAttachView(view: LoginFormView) {
super.onAttachView(view)
view.run {
@ -33,10 +31,10 @@ class LoginFormPresenter @Inject constructor(
}
}
fun attemptLogin(email: String, password: String, symbol: String, endpoint: String) {
if (!validateCredentials(email, password, symbol)) return
fun attemptLogin(email: String, password: String, endpoint: String) {
if (!validateCredentials(email, password)) return
disposable.add(studentRepository.getStudents(email, password, symbol, endpoint)
disposable.add(studentRepository.getStudents(email, password, endpoint)
.subscribeOn(schedulers.backgroundThread)
.observeOn(schedulers.mainThread)
.doOnSubscribe {
@ -54,35 +52,21 @@ class LoginFormPresenter @Inject constructor(
}
}
.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")
switchOptionsView()
}
}
Timber.i("Login result: Success")
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")
analytics.logEvent("registration_form", SUCCESS to false, "students" to -1, "endpoint" to endpoint, "error" to it.localizedMessage)
errorHandler.dispatch(it)
}))
}
private fun validateCredentials(login: String, password: String, symbol: String): Boolean {
private fun validateCredentials(login: String, password: String): Boolean {
var isCorrect = true
if (login.isEmpty()) {
view?.setErrorNicknameRequired()
view?.setErrorNameRequired()
isCorrect = false
}
@ -91,11 +75,6 @@ class LoginFormPresenter @Inject constructor(
isCorrect = false
}
if (symbol.isEmpty() && wasEmpty) {
view?.setErrorSymbolRequire()
isCorrect = false
}
if (password.length < 6 && password.isNotEmpty()) {
view?.setErrorPassInvalid(focus = isCorrect)
isCorrect = false

View File

@ -1,31 +1,20 @@
package io.github.wulkanowy.ui.modules.login.form
import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.ui.base.BaseView
interface LoginFormView : BaseView {
val isDebug: Boolean
fun initView()
fun switchOptionsView()
fun setErrorNicknameRequired()
fun setErrorNameRequired()
fun setErrorPassRequired(focus: Boolean)
fun setErrorSymbolRequire()
fun setErrorPassInvalid(focus: Boolean)
fun setErrorPassIncorrect()
fun setErrorSymbolIncorrect()
fun resetViewErrors()
fun showVersion()
fun showSoftKeyboard()
fun hideSoftKeyboard()
@ -34,5 +23,7 @@ interface LoginFormView : BaseView {
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_NEW_TASK
@ -13,31 +13,35 @@ import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.davidea.flexibleadapter.common.SmoothScrollLinearLayoutManager
import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
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.main.MainActivity
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
class LoginOptionsFragment : BaseFragment(), LoginOptionsView {
class LoginStudentSelectFragment : BaseFragment(), LoginStudentSelectView {
@Inject
lateinit var presenter: LoginOptionsPresenter
lateinit var presenter: LoginStudentSelectPresenter
@Inject
lateinit var loginAdapter: FlexibleAdapter<AbstractFlexibleItem<*>>
companion object {
fun newInstance() = LoginOptionsFragment()
const val SAVED_STUDENTS = "STUDENTS"
fun newInstance() = LoginStudentSelectFragment()
}
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?) {
super.onActivityCreated(savedInstanceState)
presenter.onAttachView(this)
presenter.onAttachView(this, savedInstanceState?.getSerializable(SAVED_STUDENTS))
}
override fun initView() {
@ -49,11 +53,7 @@ class LoginOptionsFragment : BaseFragment(), LoginOptionsView {
}
}
fun onParentLoadData() {
presenter.onParentViewLoadData()
}
override fun updateData(data: List<LoginOptionsItem>) {
override fun updateData(data: List<LoginStudentSelectItem>) {
loginAdapter.updateDataSet(data, true)
}
@ -76,6 +76,15 @@ class LoginOptionsFragment : BaseFragment(), LoginOptionsView {
(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() {
presenter.onDetachView()
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 eu.davidea.flexibleadapter.FlexibleAdapter
@ -10,7 +10,7 @@ import io.github.wulkanowy.data.db.entities.Student
import kotlinx.android.extensions.LayoutContainer
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
@ -27,7 +27,7 @@ class LoginOptionsItem(val student: Student) : AbstractFlexibleItem<LoginOptions
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as LoginOptionsItem
other as LoginStudentSelectItem
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 eu.davidea.flexibleadapter.items.AbstractFlexibleItem
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.reactivex.Single
import timber.log.Timber
import java.io.Serializable
import javax.inject.Inject
class LoginOptionsPresenter @Inject constructor(
class LoginStudentSelectPresenter @Inject constructor(
private val errorHandler: LoginErrorHandler,
private val studentRepository: StudentRepository,
private val semesterRepository: SemesterRepository,
private val schedulers: SchedulersProvider,
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)
view.run {
initView()
@ -32,22 +33,29 @@ class LoginOptionsPresenter @Inject constructor(
Timber.i("The student already registered in the app was selected")
}
}
if (students is List<*> && students.isNotEmpty()) {
loadData(students.filterIsInstance<Student>())
}
}
fun onParentViewLoadData() {
disposable.add(studentRepository.cachedStudents
.observeOn(schedulers.mainThread)
.subscribeOn(schedulers.backgroundThread)
.doOnSubscribe { view?.showActionBar(true) }
.subscribe({ view?.updateData(it.map { student -> LoginOptionsItem(student) }) }, { errorHandler.dispatch(it) }))
fun onParentInitStudentSelectView(students: List<Student>) {
loadData(students)
}
fun onItemSelected(item: AbstractFlexibleItem<*>?) {
if (item is LoginOptionsItem) {
if (item is LoginStudentSelectItem) {
registerStudent(item.student)
}
}
private fun loadData(students: List<Student>) {
this.students = students
view?.apply {
updateData(students.map { LoginStudentSelectItem(it) })
}
}
private fun registerStudent(student: Student) {
disposable.add(studentRepository.saveStudent(student)
.map { student.apply { id = it } }
@ -65,10 +73,11 @@ class LoginOptionsPresenter @Inject constructor(
Timber.i("Registration started")
}
.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")
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 ")
errorHandler.dispatch(it)
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
interface LoginOptionsView : BaseView {
interface LoginStudentSelectView : BaseView {
fun initView()
fun updateData(data: List<LoginOptionsItem>)
fun updateData(data: List<LoginStudentSelectItem>)
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.utils.FirebaseAnalyticsHelper
import io.github.wulkanowy.utils.SchedulersProvider
import io.reactivex.MaybeSource
import javax.inject.Inject
class LuckyNumberPresenter @Inject constructor(
@ -45,7 +44,7 @@ class LuckyNumberPresenter @Inject constructor(
showContent(true)
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()) }
errorHandler.dispatch(it)

View File

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

View File

@ -48,9 +48,9 @@ class MessagePreviewPresenter @Inject constructor(
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()
errorHandler.dispatch(it)
})

View File

@ -56,7 +56,7 @@ class MessageTabPresenter @Inject constructor(
showContent(it.isNotEmpty())
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")
view?.run { showEmpty(isViewEmpty) }

View File

@ -54,7 +54,7 @@ class NotePresenter @Inject constructor(
showEmpty(it.isEmpty())
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")
view?.run { showEmpty(isViewEmpty) }

View File

@ -46,6 +46,6 @@ class SettingsPresenter @Inject constructor(
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())
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")
view?.run { showEmpty(isViewEmpty) }

View File

@ -96,7 +96,7 @@ class TimetableWidgetProvider : AppWidgetProvider() {
}
button?.also { btn ->
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
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 {
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)
if (it.second == null) return@forEach
when (it.second) {
is String, is String? -> putString(it.first, it.second as String)
is Int, is Int? -> putInt(it.first, it.second as Int)
is Boolean, is Boolean? -> putBoolean(it.first, it.second as Boolean)
}
}
analytics.logEvent(name, this)

View File

@ -1,6 +1,5 @@
<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">
@ -34,7 +33,6 @@
</RelativeLayout>
<ScrollView
android:id="@+id/loginScroll"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scrollbars="none">
@ -49,7 +47,7 @@
android:orientation="vertical">
<TextView
android:id="@+id/loginHeader"
android:id="@+id/loginFormHeader"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
@ -57,72 +55,48 @@
android:text="@string/login_header_default"
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
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="15dp"
android:hint="@string/login_nickname_hint">
<androidx.appcompat.widget.AppCompatEditText
android:id="@+id/loginNicknameEdit"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="textEmailAddress"
android:maxLines="1" />
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="15dp"
android:hint="@string/login_password_hint">
<androidx.appcompat.widget.AppCompatEditText
android:id="@+id/loginPassEdit"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:imeActionLabel="@string/login_sign_in"
android:imeOptions="actionDone"
android:inputType="textPassword"
android:maxLines="1"
app:fontFamily="sans-serif" />
</com.google.android.material.textfield.TextInputLayout>
<androidx.appcompat.widget.AppCompatSpinner
android:id="@+id/loginHostEdit"
android:layout_width="match_parent"
android:layout_height="wrap_content"
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">
android:layout_marginBottom="15dp"
android:hint="@string/login_nickname_hint">
<AutoCompleteTextView
android:id="@+id/loginSymbolEdit"
<androidx.appcompat.widget.AppCompatEditText
android:id="@+id/loginFormName"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="textEmailAddress"
android:maxLines="1" />
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="15dp"
android:hint="@string/login_password_hint">
<androidx.appcompat.widget.AppCompatEditText
android:id="@+id/loginFormPass"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:imeActionLabel="@string/login_sign_in"
android:imeOptions="actionDone"
android:inputType="textAutoComplete"
android:inputType="textPassword"
android:maxLines="1"
tools:ignore="LabelFor" />
app:fontFamily="sans-serif" />
</com.google.android.material.textfield.TextInputLayout>
<androidx.appcompat.widget.AppCompatSpinner
android:id="@+id/loginFormHost"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/login_host_hint" />
<androidx.appcompat.widget.AppCompatButton
android:id="@+id/loginSignButton"
android:id="@+id/loginFormSignIn"
style="?android:textAppearanceSmall"
android:layout_width="match_parent"
android:layout_height="wrap_content"
@ -134,12 +108,11 @@
app:backgroundTint="@color/colorPrimary" />
<TextView
android:id="@+id/loginVersion"
android:id="@+id/loginFormVersion"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_marginBottom="30dp"
android:text="@string/app_name"
android:textColor="@color/second_text"
android:visibility="gone" />
</LinearLayout>

View File

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

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
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 org.junit.Assert.assertEquals
import org.junit.Before
@ -22,8 +22,8 @@ class StudentRemoteTest {
@Test
fun testRemoteAll() {
doReturn(Single.just(listOf(Pupil("", "", 1, "test", "", "", "", Api.LoginType.AUTO))))
.`when`(mockApi).getPupils()
doReturn(Single.just(listOf(Student("", "", 1, "test", "", "", Api.LoginType.AUTO))))
.`when`(mockApi).getStudents()
val students = StudentRemote(mockApi).getStudents("", "", "").blockingGet()
assertEquals(1, students.size)

View File

@ -6,7 +6,6 @@ import org.junit.Test
import org.mockito.Mock
import org.mockito.Mockito.clearInvocations
import org.mockito.Mockito.doReturn
import org.mockito.Mockito.never
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
@ -32,25 +31,7 @@ class LoginPresenterTest {
@Test
fun initViewTest() {
verify(loginView).initAdapter()
verify(loginView).hideActionBar()
}
@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)
verify(loginView).showActionBar(false)
}
@Test
@ -59,7 +40,6 @@ class LoginPresenterTest {
doReturn(1).`when`(loginView).currentViewIndex
presenter.onBackPressed { }
verify(loginView).switchView(0)
verify(loginView).hideActionBar()
}
@Test

View File

@ -37,7 +37,7 @@ class LoginFormPresenterTest {
fun initPresenter() {
MockitoAnnotations.initMocks(this)
clearInvocations(repository, loginFormView)
presenter = LoginFormPresenter(TestSchedulersProvider(), errorHandler, repository, analytics)
presenter = LoginFormPresenter(TestSchedulersProvider(), errorHandler, repository, analytics, false)
presenter.onAttachView(loginFormView)
}
@ -48,87 +48,70 @@ class LoginFormPresenterTest {
@Test
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()).setErrorSymbolRequire()
verify(loginFormView, never()).setErrorPassInvalid(false)
}
@Test
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, never()).setErrorSymbolRequire()
verify(loginFormView, never()).setErrorPassInvalid(false)
}
@Test
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()).setErrorSymbolRequire()
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
fun loginTest() {
val studentTest = Student(email = "test@", password = "123", endpoint = "https://fakelog.cf", loginType = "AUTO")
doReturn(Single.just(listOf(studentTest)))
.`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).showProgress(true)
verify(loginFormView).showProgress(false)
verify(loginFormView).showContent(false)
verify(loginFormView).showContent(true)
verify(loginFormView).switchOptionsView()
}
@Test
fun loginEmptyTest() {
doReturn(Single.just(emptyList<Student>()))
.`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).showProgress(true)
verify(loginFormView).showProgress(false)
verify(loginFormView).showContent(false)
verify(loginFormView).showContent(true)
verify(loginFormView).showSymbolInput()
}
@Test
fun loginEmptyTwiceTest() {
doReturn(Single.just(emptyList<Student>()))
.`when`(repository).getStudents(anyString(), anyString(), anyString(), anyString())
presenter.attemptLogin("@", "123456", "", "https://fakelog.cf")
presenter.attemptLogin("@", "123456", "test", "https://fakelog.cf")
presenter.attemptLogin("@", "123456", "https://fakelog.cf")
presenter.attemptLogin("@", "123456", "https://fakelog.cf")
verify(loginFormView, times(2)).hideSoftKeyboard()
verify(loginFormView, times(2)).showProgress(true)
verify(loginFormView, times(2)).showProgress(false)
verify(loginFormView, times(2)).showContent(false)
verify(loginFormView, times(2)).showContent(true)
verify(loginFormView, times(2)).showSymbolInput()
verify(loginFormView).setErrorSymbolIncorrect()
}
@Test
@ -136,7 +119,7 @@ class LoginFormPresenterTest {
val testException = RuntimeException("test")
doReturn(Single.error<List<Student>>(testException))
.`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).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.data.db.entities.Semester
@ -17,13 +17,13 @@ import org.mockito.Mockito.doReturn
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
class LoginOptionsPresenterTest {
class LoginStudentSelectPresenterTest {
@Mock
lateinit var errorHandler: LoginErrorHandler
@Mock
lateinit var loginOptionsView: LoginOptionsView
lateinit var loginStudentSelectView: LoginStudentSelectView
@Mock
lateinit var studentRepository: StudentRepository
@ -34,7 +34,7 @@ class LoginOptionsPresenterTest {
@Mock
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") }
@ -43,31 +43,15 @@ class LoginOptionsPresenterTest {
@Before
fun initPresenter() {
MockitoAnnotations.initMocks(this)
clearInvocations(studentRepository, loginOptionsView)
clearInvocations(semesterRepository, loginOptionsView)
presenter = LoginOptionsPresenter(errorHandler, studentRepository, semesterRepository, TestSchedulersProvider(), analytics)
presenter.onAttachView(loginOptionsView)
clearInvocations(studentRepository, loginStudentSelectView)
clearInvocations(semesterRepository, loginStudentSelectView)
presenter = LoginStudentSelectPresenter(errorHandler, studentRepository, semesterRepository, TestSchedulersProvider(), analytics)
presenter.onAttachView(loginStudentSelectView, null)
}
@Test
fun initViewTest() {
verify(loginOptionsView).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)
verify(loginStudentSelectView).initView()
}
@Test
@ -75,10 +59,10 @@ class LoginOptionsPresenterTest {
doReturn(Single.just(1L)).`when`(studentRepository).saveStudent(testStudent)
doReturn(Single.just(emptyList<Semester>())).`when`(semesterRepository).getSemesters(testStudent, true)
doReturn(Completable.complete()).`when`(studentRepository).switchStudent(testStudent)
presenter.onItemSelected(LoginOptionsItem(testStudent))
verify(loginOptionsView).showContent(false)
verify(loginOptionsView).showProgress(true)
verify(loginOptionsView).openMainView()
presenter.onItemSelected(LoginStudentSelectItem(testStudent))
verify(loginStudentSelectView).showContent(false)
verify(loginStudentSelectView).showProgress(true)
verify(loginStudentSelectView).openMainView()
}
@Test
@ -86,9 +70,9 @@ class LoginOptionsPresenterTest {
doReturn(Single.error<Student>(testException)).`when`(studentRepository).saveStudent(testStudent)
doReturn(Single.just(emptyList<Semester>())).`when`(semesterRepository).getSemesters(testStudent, true)
doReturn(Completable.complete()).`when`(studentRepository).logoutStudent(testStudent)
presenter.onItemSelected(LoginOptionsItem(testStudent))
verify(loginOptionsView).showContent(false)
verify(loginOptionsView).showProgress(true)
presenter.onItemSelected(LoginStudentSelectItem(testStudent))
verify(loginStudentSelectView).showContent(false)
verify(loginStudentSelectView).showProgress(true)
verify(errorHandler).dispatch(testException)
}
}

View File

@ -1,5 +1,5 @@
buildscript {
ext.kotlin_version = '1.3.20'
ext.kotlin_version = '1.3.21'
repositories {
mavenCentral()
google()
@ -9,12 +9,11 @@ buildscript {
}
dependencies {
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 "io.fabric.tools:gradle:1.27.0"
classpath "com.github.triplet.gradle:play-publisher:2.1.0"
classpath "org.sonarsource.scanner.gradle:sonarqube-gradle-plugin:2.7"
classpath 'com.android.tools.build.jetifier:jetifier-processor:1.0.0-beta02'
}
}