forked from github/wulkanowy-mirror
Add mobile access managment (#344)
This commit is contained in:
parent
5c70cd8b8c
commit
28f27db2b5
@ -7,7 +7,7 @@ references:
|
||||
|
||||
container_config: &container_config
|
||||
docker:
|
||||
- image: circleci/android:api-28
|
||||
- image: circleci/android@sha256:5cdc8626cc6f13efe5ed982cdcdb432b0472f8740fed8743a6461e025ad6cdfc
|
||||
working_directory: *workspace_root
|
||||
environment:
|
||||
environment:
|
||||
@ -93,6 +93,9 @@ jobs:
|
||||
<<: *container_config
|
||||
steps:
|
||||
- *attach_workspace
|
||||
- run:
|
||||
name: Accept licenses
|
||||
command: yes | sdkmanager --licenses && yes | sdkmanager --update
|
||||
- run:
|
||||
name: Setup emulator
|
||||
command: sdkmanager "system-images;android-19;default;armeabi-v7a" && echo "no" | avdmanager create avd -n test -k "system-images;android-19;default;armeabi-v7a"
|
||||
|
@ -85,7 +85,7 @@ play {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation 'com.github.wulkanowy:api:c84356f'
|
||||
implementation 'com.github.wulkanowy:api:d08b71b'
|
||||
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
|
||||
implementation "androidx.legacy:legacy-support-v4:1.0.0"
|
||||
|
1430
app/schemas/io.github.wulkanowy.data.db.AppDatabase/15.json
Normal file
1430
app/schemas/io.github.wulkanowy.data.db.AppDatabase/15.json
Normal file
File diff suppressed because it is too large
Load Diff
@ -25,7 +25,8 @@ abstract class AbstractMigrationTest {
|
||||
.addMigrations(
|
||||
Migration12(),
|
||||
Migration13(),
|
||||
Migration14()
|
||||
Migration14(),
|
||||
Migration15()
|
||||
)
|
||||
.build()
|
||||
// close the database and release any stream resources when the test finishes
|
||||
|
@ -132,4 +132,8 @@ internal class RepositoryModule {
|
||||
@Singleton
|
||||
@Provides
|
||||
fun provideRecipientDao(database: AppDatabase) = database.recipientDao
|
||||
|
||||
@Singleton
|
||||
@Provides
|
||||
fun provideMobileDevicesDao(database: AppDatabase) = database.mobileDeviceDao
|
||||
}
|
||||
|
@ -16,6 +16,7 @@ import io.github.wulkanowy.data.db.dao.GradeSummaryDao
|
||||
import io.github.wulkanowy.data.db.dao.HomeworkDao
|
||||
import io.github.wulkanowy.data.db.dao.LuckyNumberDao
|
||||
import io.github.wulkanowy.data.db.dao.MessagesDao
|
||||
import io.github.wulkanowy.data.db.dao.MobileDeviceDao
|
||||
import io.github.wulkanowy.data.db.dao.NoteDao
|
||||
import io.github.wulkanowy.data.db.dao.RecipientDao
|
||||
import io.github.wulkanowy.data.db.dao.ReportingUnitDao
|
||||
@ -33,6 +34,7 @@ import io.github.wulkanowy.data.db.entities.GradeSummary
|
||||
import io.github.wulkanowy.data.db.entities.Homework
|
||||
import io.github.wulkanowy.data.db.entities.LuckyNumber
|
||||
import io.github.wulkanowy.data.db.entities.Message
|
||||
import io.github.wulkanowy.data.db.entities.MobileDevice
|
||||
import io.github.wulkanowy.data.db.entities.Note
|
||||
import io.github.wulkanowy.data.db.entities.Recipient
|
||||
import io.github.wulkanowy.data.db.entities.ReportingUnit
|
||||
@ -45,6 +47,7 @@ import io.github.wulkanowy.data.db.migrations.Migration11
|
||||
import io.github.wulkanowy.data.db.migrations.Migration12
|
||||
import io.github.wulkanowy.data.db.migrations.Migration13
|
||||
import io.github.wulkanowy.data.db.migrations.Migration14
|
||||
import io.github.wulkanowy.data.db.migrations.Migration15
|
||||
import io.github.wulkanowy.data.db.migrations.Migration2
|
||||
import io.github.wulkanowy.data.db.migrations.Migration3
|
||||
import io.github.wulkanowy.data.db.migrations.Migration4
|
||||
@ -74,7 +77,8 @@ import javax.inject.Singleton
|
||||
LuckyNumber::class,
|
||||
CompletedLesson::class,
|
||||
ReportingUnit::class,
|
||||
Recipient::class
|
||||
Recipient::class,
|
||||
MobileDevice::class
|
||||
],
|
||||
version = AppDatabase.VERSION_SCHEMA,
|
||||
exportSchema = true
|
||||
@ -83,7 +87,7 @@ import javax.inject.Singleton
|
||||
abstract class AppDatabase : RoomDatabase() {
|
||||
|
||||
companion object {
|
||||
const val VERSION_SCHEMA = 14
|
||||
const val VERSION_SCHEMA = 15
|
||||
|
||||
fun newInstance(context: Context): AppDatabase {
|
||||
return Room.databaseBuilder(context, AppDatabase::class.java, "wulkanowy_database")
|
||||
@ -103,7 +107,8 @@ abstract class AppDatabase : RoomDatabase() {
|
||||
Migration11(),
|
||||
Migration12(),
|
||||
Migration13(),
|
||||
Migration14()
|
||||
Migration14(),
|
||||
Migration15()
|
||||
)
|
||||
.build()
|
||||
}
|
||||
@ -142,4 +147,6 @@ abstract class AppDatabase : RoomDatabase() {
|
||||
abstract val reportingUnitDao: ReportingUnitDao
|
||||
|
||||
abstract val recipientDao: RecipientDao
|
||||
|
||||
abstract val mobileDeviceDao: MobileDeviceDao
|
||||
}
|
||||
|
@ -0,0 +1,21 @@
|
||||
package io.github.wulkanowy.data.db.dao
|
||||
|
||||
import androidx.room.Dao
|
||||
import androidx.room.Delete
|
||||
import androidx.room.Insert
|
||||
import androidx.room.Query
|
||||
import io.github.wulkanowy.data.db.entities.MobileDevice
|
||||
import io.reactivex.Maybe
|
||||
|
||||
@Dao
|
||||
interface MobileDeviceDao {
|
||||
|
||||
@Insert
|
||||
fun insertAll(devices: List<MobileDevice>)
|
||||
|
||||
@Delete
|
||||
fun deleteAll(devices: List<MobileDevice>)
|
||||
|
||||
@Query("SELECT * FROM MobileDevices WHERE student_id = :studentId ORDER BY date DESC")
|
||||
fun loadAll(studentId: Int): Maybe<List<MobileDevice>>
|
||||
}
|
@ -0,0 +1,25 @@
|
||||
package io.github.wulkanowy.data.db.entities
|
||||
|
||||
import androidx.room.ColumnInfo
|
||||
import androidx.room.Entity
|
||||
import androidx.room.PrimaryKey
|
||||
import org.threeten.bp.LocalDateTime
|
||||
import java.io.Serializable
|
||||
|
||||
@Entity(tableName = "MobileDevices")
|
||||
data class MobileDevice(
|
||||
|
||||
@ColumnInfo(name = "student_id")
|
||||
val studentId: Int,
|
||||
|
||||
@ColumnInfo(name = "device_id")
|
||||
val deviceId: Int,
|
||||
|
||||
val name: String,
|
||||
|
||||
val date: LocalDateTime
|
||||
) : Serializable {
|
||||
|
||||
@PrimaryKey(autoGenerate = true)
|
||||
var id: Long = 0
|
||||
}
|
@ -0,0 +1,19 @@
|
||||
package io.github.wulkanowy.data.db.migrations
|
||||
|
||||
import androidx.room.migration.Migration
|
||||
import androidx.sqlite.db.SupportSQLiteDatabase
|
||||
|
||||
class Migration15 : Migration(14, 15) {
|
||||
|
||||
override fun migrate(database: SupportSQLiteDatabase) {
|
||||
database.execSQL("""
|
||||
CREATE TABLE IF NOT EXISTS MobileDevices (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
|
||||
student_id INTEGER NOT NULL,
|
||||
device_id INTEGER NOT NULL,
|
||||
name TEXT NOT NULL,
|
||||
date INTEGER NOT NULL
|
||||
)
|
||||
""")
|
||||
}
|
||||
}
|
@ -0,0 +1,12 @@
|
||||
package io.github.wulkanowy.data.pojos
|
||||
|
||||
data class MobileDeviceToken(
|
||||
|
||||
val token: String,
|
||||
|
||||
val symbol: String,
|
||||
|
||||
val pin: String,
|
||||
|
||||
val qr: String
|
||||
)
|
@ -0,0 +1,25 @@
|
||||
package io.github.wulkanowy.data.repositories.mobiledevice
|
||||
|
||||
import io.github.wulkanowy.data.db.dao.MobileDeviceDao
|
||||
import io.github.wulkanowy.data.db.entities.MobileDevice
|
||||
import io.github.wulkanowy.data.db.entities.Semester
|
||||
import io.github.wulkanowy.data.db.entities.Student
|
||||
import io.reactivex.Maybe
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
class MobileDeviceLocal @Inject constructor(private val mobileDb: MobileDeviceDao) {
|
||||
|
||||
fun saveDevices(devices: List<MobileDevice>) {
|
||||
mobileDb.insertAll(devices)
|
||||
}
|
||||
|
||||
fun deleteDevices(devices: List<MobileDevice>) {
|
||||
mobileDb.deleteAll(devices)
|
||||
}
|
||||
|
||||
fun getDevices(semester: Semester): Maybe<List<MobileDevice>> {
|
||||
return mobileDb.loadAll(semester.studentId).filter { it.isNotEmpty() }
|
||||
}
|
||||
}
|
@ -0,0 +1,47 @@
|
||||
package io.github.wulkanowy.data.repositories.mobiledevice
|
||||
|
||||
import io.github.wulkanowy.api.Api
|
||||
import io.github.wulkanowy.data.db.entities.MobileDevice
|
||||
import io.github.wulkanowy.data.db.entities.Semester
|
||||
import io.github.wulkanowy.data.pojos.MobileDeviceToken
|
||||
import io.github.wulkanowy.utils.toLocalDateTime
|
||||
import io.reactivex.Single
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
class MobileDeviceRemote @Inject constructor(private val api: Api) {
|
||||
|
||||
fun getDevices(semester: Semester): Single<List<MobileDevice>> {
|
||||
return Single.just(api.apply { diaryId = semester.diaryId })
|
||||
.flatMap { api.getRegisteredDevices() }
|
||||
.map { devices ->
|
||||
devices.map {
|
||||
MobileDevice(
|
||||
studentId = semester.studentId,
|
||||
date = it.date.toLocalDateTime(),
|
||||
deviceId = it.id,
|
||||
name = it.name
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun unregisterDevice(semester: Semester, device: MobileDevice): Single<Boolean> {
|
||||
return Single.just(api.apply { diaryId = semester.diaryId })
|
||||
.flatMap { api.unregisterDevice(device.deviceId) }
|
||||
}
|
||||
|
||||
fun getToken(semester: Semester): Single<MobileDeviceToken> {
|
||||
return Single.just(api.apply { diaryId = semester.diaryId })
|
||||
.flatMap { api.getToken() }
|
||||
.map {
|
||||
MobileDeviceToken(
|
||||
token = it.token,
|
||||
symbol = it.symbol,
|
||||
pin = it.pin,
|
||||
qr = it.qrCodeImage
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,44 @@
|
||||
package io.github.wulkanowy.data.repositories.mobiledevice
|
||||
|
||||
import com.github.pwittchen.reactivenetwork.library.rx2.ReactiveNetwork
|
||||
import com.github.pwittchen.reactivenetwork.library.rx2.internet.observing.InternetObservingSettings
|
||||
import io.github.wulkanowy.data.db.entities.MobileDevice
|
||||
import io.github.wulkanowy.data.db.entities.Semester
|
||||
import io.github.wulkanowy.data.pojos.MobileDeviceToken
|
||||
import io.github.wulkanowy.utils.uniqueSubtract
|
||||
import io.reactivex.Single
|
||||
import java.net.UnknownHostException
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
class MobileDeviceRepository @Inject constructor(
|
||||
private val settings: InternetObservingSettings,
|
||||
private val local: MobileDeviceLocal,
|
||||
private val remote: MobileDeviceRemote
|
||||
) {
|
||||
|
||||
fun getDevices(semester: Semester, forceRefresh: Boolean = false): Single<List<MobileDevice>> {
|
||||
return local.getDevices(semester).filter { !forceRefresh }
|
||||
.switchIfEmpty(ReactiveNetwork.checkInternetConnectivity(settings)
|
||||
.flatMap {
|
||||
if (it) remote.getDevices(semester)
|
||||
else Single.error(UnknownHostException())
|
||||
}.flatMap { new ->
|
||||
local.getDevices(semester).toSingle(emptyList())
|
||||
.doOnSuccess { old ->
|
||||
local.deleteDevices(old uniqueSubtract new)
|
||||
local.saveDevices(new uniqueSubtract old)
|
||||
}
|
||||
}
|
||||
).flatMap { local.getDevices(semester).toSingle(emptyList()) }
|
||||
}
|
||||
|
||||
fun unregisterDevice(semester: Semester, device: MobileDevice): Single<Boolean> {
|
||||
return remote.unregisterDevice(semester, device)
|
||||
}
|
||||
|
||||
fun getToken(semester: Semester): Single<MobileDeviceToken> {
|
||||
return remote.getToken(semester)
|
||||
}
|
||||
}
|
@ -19,6 +19,9 @@ import io.github.wulkanowy.ui.modules.luckynumber.LuckyNumberFragment
|
||||
import io.github.wulkanowy.ui.modules.message.MessageFragment
|
||||
import io.github.wulkanowy.ui.modules.message.MessageModule
|
||||
import io.github.wulkanowy.ui.modules.message.preview.MessagePreviewFragment
|
||||
import io.github.wulkanowy.ui.modules.mobiledevice.token.MobileDeviceTokenDialog
|
||||
import io.github.wulkanowy.ui.modules.mobiledevice.MobileDeviceFragment
|
||||
import io.github.wulkanowy.ui.modules.mobiledevice.MobileDeviceModule
|
||||
import io.github.wulkanowy.ui.modules.more.MoreFragment
|
||||
import io.github.wulkanowy.ui.modules.note.NoteFragment
|
||||
import io.github.wulkanowy.ui.modules.settings.SettingsFragment
|
||||
@ -97,4 +100,12 @@ abstract class MainModule {
|
||||
@PerFragment
|
||||
@ContributesAndroidInjector
|
||||
abstract fun bindAccountDialog(): AccountDialog
|
||||
|
||||
@PerFragment
|
||||
@ContributesAndroidInjector(modules = [MobileDeviceModule::class])
|
||||
abstract fun bindMobileDevices(): MobileDeviceFragment
|
||||
|
||||
@PerFragment
|
||||
@ContributesAndroidInjector
|
||||
abstract fun bindMobileDeviceDialog(): MobileDeviceTokenDialog
|
||||
}
|
||||
|
@ -0,0 +1,10 @@
|
||||
package io.github.wulkanowy.ui.modules.mobiledevice
|
||||
|
||||
import eu.davidea.flexibleadapter.FlexibleAdapter
|
||||
import eu.davidea.flexibleadapter.items.IFlexible
|
||||
import io.github.wulkanowy.data.db.entities.MobileDevice
|
||||
|
||||
class MobileDeviceAdapter<T : IFlexible<*>> : FlexibleAdapter<T>(null, null, true) {
|
||||
|
||||
var onDeviceUnregisterListener: (MobileDevice, position: Int) -> Unit = { _, _ -> }
|
||||
}
|
@ -0,0 +1,114 @@
|
||||
package io.github.wulkanowy.ui.modules.mobiledevice
|
||||
|
||||
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 eu.davidea.flexibleadapter.common.FlexibleItemDecoration
|
||||
import eu.davidea.flexibleadapter.common.SmoothScrollLinearLayoutManager
|
||||
import eu.davidea.flexibleadapter.helpers.EmptyViewHelper
|
||||
import eu.davidea.flexibleadapter.helpers.UndoHelper
|
||||
import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
|
||||
import io.github.wulkanowy.R
|
||||
import io.github.wulkanowy.ui.base.BaseFragment
|
||||
import io.github.wulkanowy.ui.modules.main.MainActivity
|
||||
import io.github.wulkanowy.ui.modules.main.MainView
|
||||
import io.github.wulkanowy.ui.modules.mobiledevice.token.MobileDeviceTokenDialog
|
||||
import kotlinx.android.synthetic.main.fragment_mobile_device.*
|
||||
import javax.inject.Inject
|
||||
|
||||
class MobileDeviceFragment : BaseFragment(), MobileDeviceView, MainView.TitledView {
|
||||
|
||||
@Inject
|
||||
lateinit var presenter: MobileDevicePresenter
|
||||
|
||||
@Inject
|
||||
lateinit var devicesAdapter: MobileDeviceAdapter<AbstractFlexibleItem<*>>
|
||||
|
||||
companion object {
|
||||
fun newInstance() = MobileDeviceFragment()
|
||||
}
|
||||
|
||||
override val titleStringId: Int
|
||||
get() = R.string.mobile_devices_title
|
||||
|
||||
override val isViewEmpty: Boolean
|
||||
get() = devicesAdapter.isEmpty
|
||||
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
||||
return inflater.inflate(R.layout.fragment_mobile_device, container, false)
|
||||
}
|
||||
|
||||
override fun onActivityCreated(savedInstanceState: Bundle?) {
|
||||
super.onActivityCreated(savedInstanceState)
|
||||
messageContainer = mobileDevicesRecycler
|
||||
presenter.onAttachView(this)
|
||||
}
|
||||
|
||||
override fun initView() {
|
||||
mobileDevicesRecycler.run {
|
||||
layoutManager = SmoothScrollLinearLayoutManager(context)
|
||||
adapter = devicesAdapter
|
||||
addItemDecoration(FlexibleItemDecoration(context)
|
||||
.withDefaultDivider()
|
||||
.withDrawDividerOnLastItem(false)
|
||||
)
|
||||
}
|
||||
EmptyViewHelper.create(devicesAdapter, mobileDevicesEmpty)
|
||||
mobileDevicesSwipe.setOnRefreshListener { presenter.onSwipeRefresh() }
|
||||
mobileDeviceAddButton.setOnClickListener { presenter.onRegisterDevice() }
|
||||
devicesAdapter.run {
|
||||
isPermanentDelete = false
|
||||
onDeviceUnregisterListener = { device, position ->
|
||||
val onActionListener = object : UndoHelper.OnActionListener {
|
||||
override fun onActionConfirmed(action: Int, event: Int) {
|
||||
presenter.onUnregister(device)
|
||||
}
|
||||
|
||||
override fun onActionCanceled(action: Int, positions: MutableList<Int>?) {
|
||||
devicesAdapter.restoreDeletedItems()
|
||||
}
|
||||
}
|
||||
UndoHelper(devicesAdapter, onActionListener)
|
||||
.withConsecutive(false)
|
||||
.withAction(UndoHelper.Action.REMOVE)
|
||||
.start(listOf(position), mobileDevicesRecycler, R.string.mobile_device_removed, R.string.all_undo, 3000)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun updateData(data: List<MobileDeviceItem>) {
|
||||
devicesAdapter.updateDataSet(data)
|
||||
}
|
||||
|
||||
override fun clearData() {
|
||||
devicesAdapter.clear()
|
||||
}
|
||||
|
||||
override fun hideRefresh() {
|
||||
mobileDevicesSwipe.isRefreshing = false
|
||||
}
|
||||
|
||||
override fun showProgress(show: Boolean) {
|
||||
mobileDevicesProgress.visibility = if (show) VISIBLE else GONE
|
||||
}
|
||||
|
||||
override fun enableSwipe(enable: Boolean) {
|
||||
mobileDevicesSwipe.isEnabled = enable
|
||||
}
|
||||
|
||||
override fun showContent(show: Boolean) {
|
||||
mobileDevicesRecycler.visibility = if (show) VISIBLE else GONE
|
||||
}
|
||||
|
||||
override fun showTokenDialog() {
|
||||
(activity as? MainActivity)?.showDialogFragment(MobileDeviceTokenDialog.newInstance())
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
presenter.onDetachView()
|
||||
super.onDestroyView()
|
||||
}
|
||||
}
|
@ -0,0 +1,53 @@
|
||||
package io.github.wulkanowy.ui.modules.mobiledevice
|
||||
|
||||
import android.view.View
|
||||
import eu.davidea.flexibleadapter.FlexibleAdapter
|
||||
import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
|
||||
import eu.davidea.flexibleadapter.items.IFlexible
|
||||
import eu.davidea.viewholders.FlexibleViewHolder
|
||||
import io.github.wulkanowy.R
|
||||
import io.github.wulkanowy.data.db.entities.MobileDevice
|
||||
import io.github.wulkanowy.utils.toFormattedString
|
||||
import kotlinx.android.extensions.LayoutContainer
|
||||
import kotlinx.android.synthetic.main.item_mobile_device.*
|
||||
|
||||
class MobileDeviceItem(val device: MobileDevice) : AbstractFlexibleItem<MobileDeviceItem.ViewHolder>() {
|
||||
|
||||
override fun getLayoutRes() = R.layout.item_mobile_device
|
||||
|
||||
override fun createViewHolder(view: View, adapter: FlexibleAdapter<IFlexible<*>>): ViewHolder {
|
||||
return ViewHolder(view, adapter)
|
||||
}
|
||||
|
||||
override fun bindViewHolder(adapter: FlexibleAdapter<IFlexible<*>>, holder: ViewHolder, position: Int, payloads: MutableList<Any>?) {
|
||||
holder.apply {
|
||||
mobileDeviceItemDate.text = device.date.toFormattedString("dd.MM.yyyy HH:mm:ss")
|
||||
mobileDeviceItemName.text = device.name
|
||||
mobileDeviceItemUnregister.setOnClickListener {
|
||||
(adapter as MobileDeviceAdapter).onDeviceUnregisterListener(device, holder.flexibleAdapterPosition)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (javaClass != other?.javaClass) return false
|
||||
|
||||
other as MobileDeviceItem
|
||||
|
||||
if (device.id != other.device.id) return false
|
||||
return true
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
var result = device.hashCode()
|
||||
result = 31 * result + device.id.toInt()
|
||||
return result
|
||||
}
|
||||
|
||||
class ViewHolder(val view: View, adapter: FlexibleAdapter<*>) : FlexibleViewHolder(view, adapter), LayoutContainer {
|
||||
|
||||
override val containerView: View
|
||||
get() = contentView
|
||||
}
|
||||
}
|
@ -0,0 +1,12 @@
|
||||
package io.github.wulkanowy.ui.modules.mobiledevice
|
||||
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
|
||||
|
||||
@Module
|
||||
class MobileDeviceModule {
|
||||
|
||||
@Provides
|
||||
fun provideMobileDeviceFlexibleAdapter() = MobileDeviceAdapter<AbstractFlexibleItem<*>>()
|
||||
}
|
@ -0,0 +1,94 @@
|
||||
package io.github.wulkanowy.ui.modules.mobiledevice
|
||||
|
||||
import io.github.wulkanowy.data.db.entities.MobileDevice
|
||||
import io.github.wulkanowy.data.repositories.mobiledevice.MobileDeviceRepository
|
||||
import io.github.wulkanowy.data.repositories.semester.SemesterRepository
|
||||
import io.github.wulkanowy.data.repositories.student.StudentRepository
|
||||
import io.github.wulkanowy.ui.base.BasePresenter
|
||||
import io.github.wulkanowy.ui.base.ErrorHandler
|
||||
import io.github.wulkanowy.utils.FirebaseAnalyticsHelper
|
||||
import io.github.wulkanowy.utils.SchedulersProvider
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
class MobileDevicePresenter @Inject constructor(
|
||||
schedulers: SchedulersProvider,
|
||||
errorHandler: ErrorHandler,
|
||||
studentRepository: StudentRepository,
|
||||
private val semesterRepository: SemesterRepository,
|
||||
private val mobileDeviceRepository: MobileDeviceRepository,
|
||||
private val analytics: FirebaseAnalyticsHelper
|
||||
) : BasePresenter<MobileDeviceView>(errorHandler, studentRepository, schedulers) {
|
||||
|
||||
override fun onAttachView(view: MobileDeviceView) {
|
||||
super.onAttachView(view)
|
||||
view.initView()
|
||||
Timber.i("Mobile device view was initialized")
|
||||
loadData()
|
||||
}
|
||||
|
||||
fun onSwipeRefresh() {
|
||||
loadData(true)
|
||||
}
|
||||
|
||||
private fun loadData(forceRefresh: Boolean = false) {
|
||||
Timber.i("Loading mobile devices data started")
|
||||
disposable.add(studentRepository.getCurrentStudent()
|
||||
.flatMap { semesterRepository.getCurrentSemester(it) }
|
||||
.flatMap { mobileDeviceRepository.getDevices(it, forceRefresh) }
|
||||
.map { items -> items.map { MobileDeviceItem(it) } }
|
||||
.subscribeOn(schedulers.backgroundThread)
|
||||
.observeOn(schedulers.mainThread)
|
||||
.doFinally {
|
||||
view?.run {
|
||||
hideRefresh()
|
||||
showProgress(false)
|
||||
enableSwipe(true)
|
||||
}
|
||||
}.subscribe({
|
||||
Timber.i("Loading mobile devices result: Success")
|
||||
view?.run {
|
||||
updateData(it)
|
||||
showContent(it.isNotEmpty())
|
||||
}
|
||||
analytics.logEvent("load_devices", "items" to it.size, "force_refresh" to forceRefresh)
|
||||
}) {
|
||||
Timber.i("Loading mobile devices result: An exception occurred")
|
||||
errorHandler.dispatch(it)
|
||||
})
|
||||
}
|
||||
|
||||
fun onRegisterDevice() {
|
||||
view?.showTokenDialog()
|
||||
}
|
||||
|
||||
fun onUnregister(device: MobileDevice) {
|
||||
Timber.i("Unregister device started")
|
||||
disposable.add(studentRepository.getCurrentStudent()
|
||||
.flatMap { semesterRepository.getCurrentSemester(it) }
|
||||
.flatMap { semester ->
|
||||
mobileDeviceRepository.unregisterDevice(semester, device)
|
||||
.flatMap { mobileDeviceRepository.getDevices(semester, it) }
|
||||
}
|
||||
.map { items -> items.map { MobileDeviceItem(it) } }
|
||||
.subscribeOn(schedulers.backgroundThread)
|
||||
.observeOn(schedulers.mainThread)
|
||||
.doFinally {
|
||||
view?.run {
|
||||
showProgress(false)
|
||||
enableSwipe(true)
|
||||
}
|
||||
}
|
||||
.subscribe({
|
||||
Timber.i("Unregister device result: Success")
|
||||
view?.run {
|
||||
updateData(it)
|
||||
showContent(it.isNotEmpty())
|
||||
}
|
||||
}) {
|
||||
Timber.i("Unregister device result: An exception occurred")
|
||||
errorHandler.dispatch(it)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
@ -0,0 +1,24 @@
|
||||
package io.github.wulkanowy.ui.modules.mobiledevice
|
||||
|
||||
import io.github.wulkanowy.ui.base.BaseView
|
||||
|
||||
interface MobileDeviceView : BaseView {
|
||||
|
||||
val isViewEmpty: Boolean
|
||||
|
||||
fun initView()
|
||||
|
||||
fun updateData(data: List<MobileDeviceItem>)
|
||||
|
||||
fun hideRefresh()
|
||||
|
||||
fun clearData()
|
||||
|
||||
fun showProgress(show: Boolean)
|
||||
|
||||
fun enableSwipe(enable: Boolean)
|
||||
|
||||
fun showContent(show: Boolean)
|
||||
|
||||
fun showTokenDialog()
|
||||
}
|
@ -0,0 +1,89 @@
|
||||
package io.github.wulkanowy.ui.modules.mobiledevice.token
|
||||
|
||||
import android.graphics.BitmapFactory
|
||||
import android.os.Bundle
|
||||
import android.util.Base64
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.View.GONE
|
||||
import android.view.View.VISIBLE
|
||||
import android.view.ViewGroup
|
||||
import android.widget.Toast
|
||||
import dagger.android.support.DaggerDialogFragment
|
||||
import io.github.wulkanowy.R
|
||||
import io.github.wulkanowy.data.pojos.MobileDeviceToken
|
||||
import io.github.wulkanowy.data.repositories.mobiledevice.MobileDeviceRemote
|
||||
import io.github.wulkanowy.ui.base.BaseActivity
|
||||
import kotlinx.android.synthetic.main.dialog_mobile_device.*
|
||||
import javax.inject.Inject
|
||||
|
||||
class MobileDeviceTokenDialog : DaggerDialogFragment(), MobileDeviceTokenVIew {
|
||||
|
||||
@Inject
|
||||
lateinit var presenter: MobileDeviceTokenPresenter
|
||||
|
||||
companion object {
|
||||
fun newInstance(): MobileDeviceTokenDialog = MobileDeviceTokenDialog()
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setStyle(STYLE_NO_TITLE, 0)
|
||||
}
|
||||
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
||||
return inflater.inflate(R.layout.dialog_mobile_device, container, false)
|
||||
}
|
||||
|
||||
override fun onActivityCreated(savedInstanceState: Bundle?) {
|
||||
super.onActivityCreated(savedInstanceState)
|
||||
presenter.onAttachView(this)
|
||||
}
|
||||
|
||||
override fun initView() {
|
||||
mobileDeviceDialogClose.setOnClickListener { dismiss() }
|
||||
}
|
||||
|
||||
override fun updateData(token: MobileDeviceToken) {
|
||||
mobileDeviceDialogToken.text = token.token
|
||||
mobileDeviceDialogSymbol.text = token.symbol
|
||||
mobileDeviceDialogPin.text = token.pin
|
||||
|
||||
mobileDeviceQr.setImageBitmap(Base64.decode(token.qr, Base64.DEFAULT).let {
|
||||
BitmapFactory.decodeByteArray(it, 0, it.size)
|
||||
})
|
||||
}
|
||||
|
||||
override fun hideLoading() {
|
||||
mobileDeviceDialogProgress.visibility = GONE
|
||||
}
|
||||
|
||||
override fun showContent() {
|
||||
mobileDeviceDialogContent.visibility = VISIBLE
|
||||
}
|
||||
|
||||
override fun closeDialog() {
|
||||
dismiss()
|
||||
}
|
||||
|
||||
override fun showError(text: String, error: Throwable) {
|
||||
showMessage(text)
|
||||
}
|
||||
|
||||
override fun showMessage(text: String) {
|
||||
Toast.makeText(context, text, Toast.LENGTH_LONG).show()
|
||||
}
|
||||
|
||||
override fun showExpiredDialog() {
|
||||
(activity as? BaseActivity<*>)?.showExpiredDialog()
|
||||
}
|
||||
|
||||
override fun openClearLoginView() {
|
||||
(activity as? BaseActivity<*>)?.openClearLoginView()
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
presenter.onDetachView()
|
||||
super.onDestroyView()
|
||||
}
|
||||
}
|
@ -0,0 +1,51 @@
|
||||
package io.github.wulkanowy.ui.modules.mobiledevice.token
|
||||
|
||||
import io.github.wulkanowy.data.repositories.mobiledevice.MobileDeviceRepository
|
||||
import io.github.wulkanowy.data.repositories.semester.SemesterRepository
|
||||
import io.github.wulkanowy.data.repositories.student.StudentRepository
|
||||
import io.github.wulkanowy.ui.base.BasePresenter
|
||||
import io.github.wulkanowy.ui.base.ErrorHandler
|
||||
import io.github.wulkanowy.utils.FirebaseAnalyticsHelper
|
||||
import io.github.wulkanowy.utils.SchedulersProvider
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
class MobileDeviceTokenPresenter @Inject constructor(
|
||||
schedulers: SchedulersProvider,
|
||||
errorHandler: ErrorHandler,
|
||||
studentRepository: StudentRepository,
|
||||
private val semesterRepository: SemesterRepository,
|
||||
private val mobileDeviceRepository: MobileDeviceRepository,
|
||||
private val analytics: FirebaseAnalyticsHelper
|
||||
) : BasePresenter<MobileDeviceTokenVIew>(errorHandler, studentRepository, schedulers) {
|
||||
|
||||
override fun onAttachView(view: MobileDeviceTokenVIew) {
|
||||
super.onAttachView(view)
|
||||
view.initView()
|
||||
Timber.i("Mobile device view was initialized")
|
||||
loadData()
|
||||
}
|
||||
|
||||
private fun loadData() {
|
||||
Timber.i("Mobile device registration data started")
|
||||
disposable.add(studentRepository.getCurrentStudent()
|
||||
.flatMap { semesterRepository.getCurrentSemester(it) }
|
||||
.flatMap { mobileDeviceRepository.getToken(it) }
|
||||
.subscribeOn(schedulers.backgroundThread)
|
||||
.observeOn(schedulers.mainThread)
|
||||
.doFinally { view?.hideLoading() }
|
||||
.subscribe({
|
||||
Timber.i("Mobile device registration result: Success")
|
||||
view?.run {
|
||||
updateData(it)
|
||||
showContent()
|
||||
}
|
||||
analytics.logEvent("device_register", "symbol" to it.token.substring(0, 3))
|
||||
}) {
|
||||
Timber.i("Mobile device registration result: An exception occurred")
|
||||
view?.closeDialog()
|
||||
errorHandler.dispatch(it)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
@ -0,0 +1,17 @@
|
||||
package io.github.wulkanowy.ui.modules.mobiledevice.token
|
||||
|
||||
import io.github.wulkanowy.data.pojos.MobileDeviceToken
|
||||
import io.github.wulkanowy.ui.base.BaseView
|
||||
|
||||
interface MobileDeviceTokenVIew : BaseView {
|
||||
|
||||
fun initView()
|
||||
|
||||
fun hideLoading()
|
||||
|
||||
fun showContent()
|
||||
|
||||
fun closeDialog()
|
||||
|
||||
fun updateData(token: MobileDeviceToken)
|
||||
}
|
@ -17,6 +17,7 @@ import io.github.wulkanowy.ui.modules.luckynumber.LuckyNumberFragment
|
||||
import io.github.wulkanowy.ui.modules.main.MainActivity
|
||||
import io.github.wulkanowy.ui.modules.main.MainView
|
||||
import io.github.wulkanowy.ui.modules.message.MessageFragment
|
||||
import io.github.wulkanowy.ui.modules.mobiledevice.MobileDeviceFragment
|
||||
import io.github.wulkanowy.ui.modules.note.NoteFragment
|
||||
import io.github.wulkanowy.ui.modules.settings.SettingsFragment
|
||||
import io.github.wulkanowy.utils.setOnItemClickListener
|
||||
@ -69,6 +70,14 @@ class MoreFragment : BaseFragment(), MoreView, MainView.TitledView, MainView.Mai
|
||||
}
|
||||
}
|
||||
|
||||
override val mobileDevicesRes: Pair<String, Drawable?>?
|
||||
get() {
|
||||
return context?.run {
|
||||
getString(R.string.mobile_devices_title) to
|
||||
ContextCompat.getDrawable(this, R.drawable.ic_menu_main_mobile_devices_24dp)
|
||||
}
|
||||
}
|
||||
|
||||
override val settingsRes: Pair<String, Drawable?>?
|
||||
get() {
|
||||
return context?.run {
|
||||
@ -127,6 +136,10 @@ class MoreFragment : BaseFragment(), MoreView, MainView.TitledView, MainView.Mai
|
||||
(activity as? MainActivity)?.pushView(LuckyNumberFragment.newInstance())
|
||||
}
|
||||
|
||||
override fun openMobileDevicesView() {
|
||||
(activity as? MainActivity)?.pushView(MobileDeviceFragment.newInstance())
|
||||
}
|
||||
|
||||
override fun openSettingsView() {
|
||||
(activity as? MainActivity)?.pushView(SettingsFragment.newInstance())
|
||||
}
|
||||
|
@ -30,6 +30,7 @@ class MorePresenter @Inject constructor(
|
||||
homeworkRes?.first -> openHomeworkView()
|
||||
noteRes?.first -> openNoteView()
|
||||
luckyNumberRes?.first -> openLuckyNumberView()
|
||||
mobileDevicesRes?.first -> openMobileDevicesView()
|
||||
settingsRes?.first -> openSettingsView()
|
||||
aboutRes?.first -> openAboutView()
|
||||
}
|
||||
@ -50,6 +51,7 @@ class MorePresenter @Inject constructor(
|
||||
homeworkRes?.let { MoreItem(it.first, it.second) },
|
||||
noteRes?.let { MoreItem(it.first, it.second) },
|
||||
luckyNumberRes?.let { MoreItem(it.first, it.second) },
|
||||
mobileDevicesRes?.let { MoreItem(it.first, it.second) },
|
||||
settingsRes?.let { MoreItem(it.first, it.second) },
|
||||
aboutRes?.let { MoreItem(it.first, it.second) })
|
||||
)
|
||||
|
@ -13,6 +13,8 @@ interface MoreView : BaseView {
|
||||
|
||||
val luckyNumberRes: Pair<String, Drawable?>?
|
||||
|
||||
val mobileDevicesRes: Pair<String, Drawable?>?
|
||||
|
||||
val settingsRes: Pair<String, Drawable?>?
|
||||
|
||||
val aboutRes: Pair<String, Drawable?>?
|
||||
@ -34,4 +36,6 @@ interface MoreView : BaseView {
|
||||
fun openNoteView()
|
||||
|
||||
fun openLuckyNumberView()
|
||||
|
||||
fun openMobileDevicesView()
|
||||
}
|
||||
|
5
app/src/main/res/drawable/ic_all_add_24dp.xml
Normal file
5
app/src/main/res/drawable/ic_all_add_24dp.xml
Normal file
@ -0,0 +1,5 @@
|
||||
<vector android:height="24dp" android:tint="#FFFFFF"
|
||||
android:viewportHeight="24.0" android:viewportWidth="24.0"
|
||||
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillColor="#FF000000" android:pathData="M19,13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z"/>
|
||||
</vector>
|
@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24.0"
|
||||
android:viewportHeight="24.0">
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M4,6h18L22,4L4,4c-1.1,0 -2,0.9 -2,2v11L0,17v3h14v-3L4,17L4,6zM23,8h-6c-0.55,0 -1,0.45 -1,1v10c0,0.55 0.45,1 1,1h6c0.55,0 1,-0.45 1,-1L24,9c0,-0.55 -0.45,-1 -1,-1zM22,17h-4v-7h4v7z"/>
|
||||
</vector>
|
@ -1,10 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:tint="#FFFFFF"
|
||||
android:viewportWidth="24.0"
|
||||
android:viewportHeight="24.0">
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:fillColor="#000"
|
||||
android:pathData="M6,19c0,1.1 0.9,2 2,2h8c1.1,0 2,-0.9 2,-2V7H6v12zM19,4h-3.5l-1,-1h-5l-1,1H5v2h14V4z" />
|
||||
</vector>
|
||||
|
98
app/src/main/res/layout/dialog_mobile_device.xml
Normal file
98
app/src/main/res/layout/dialog_mobile_device.xml
Normal file
@ -0,0 +1,98 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<ScrollView 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"
|
||||
tools:context=".ui.modules.mobiledevice.token.MobileDeviceTokenDialog">
|
||||
|
||||
<FrameLayout
|
||||
android:layout_width="300dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:padding="20dp">
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/mobileDeviceDialogContent"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:visibility="invisible"
|
||||
tools:visibility="visible">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/mobileDeviceQr"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:adjustViewBounds="true"
|
||||
android:layout_gravity="center"
|
||||
android:contentDescription="@string/mobile_device_qr"
|
||||
android:layout_marginBottom="12dp" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/mobile_device_token"
|
||||
android:textSize="17sp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/mobileDeviceDialogToken"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="3dp"
|
||||
android:text="@string/all_no_data"
|
||||
android:textIsSelectable="true"
|
||||
android:textSize="12sp" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="10dp"
|
||||
android:text="@string/mobile_device_symbol"
|
||||
android:textSize="17sp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/mobileDeviceDialogSymbol"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="3dp"
|
||||
android:text="@string/all_no_data"
|
||||
android:textIsSelectable="true"
|
||||
android:textSize="12sp" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="10dp"
|
||||
android:text="@string/mobile_device_pin"
|
||||
android:textSize="17sp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/mobileDeviceDialogPin"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="3dp"
|
||||
android:text="@string/all_no_data"
|
||||
android:textIsSelectable="true"
|
||||
android:textSize="12sp" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/mobileDeviceDialogClose"
|
||||
style="@style/Widget.AppCompat.Button.Borderless"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="end"
|
||||
android:layout_marginTop="15dp"
|
||||
android:text="@string/all_close"
|
||||
android:textAllCaps="true"
|
||||
android:textSize="15sp" />
|
||||
</LinearLayout>
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/mobileDeviceDialogProgress"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:indeterminate="true"
|
||||
tools:visibility="invisible" />
|
||||
</FrameLayout>
|
||||
</ScrollView>
|
65
app/src/main/res/layout/fragment_mobile_device.xml
Normal file
65
app/src/main/res/layout/fragment_mobile_device.xml
Normal file
@ -0,0 +1,65 @@
|
||||
<androidx.coordinatorlayout.widget.CoordinatorLayout 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"
|
||||
tools:context=".ui.modules.mobiledevice.MobileDeviceFragment">
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/mobileDevicesProgress"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:indeterminate="true"
|
||||
tools:visibility="invisible" />
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/mobileDevicesEmpty"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:gravity="center"
|
||||
android:orientation="vertical"
|
||||
android:padding="10dp"
|
||||
android:alpha="0.0">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="20dp"
|
||||
android:gravity="center"
|
||||
android:text="@string/mobile_devices_no_items"
|
||||
android:textSize="20sp" />
|
||||
|
||||
<ImageView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:minWidth="100dp"
|
||||
android:minHeight="100dp"
|
||||
app:srcCompat="@drawable/ic_menu_main_mobile_devices_24dp"
|
||||
app:tint="?android:attr/textColorPrimary"
|
||||
tools:ignore="contentDescription" />
|
||||
</LinearLayout>
|
||||
|
||||
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
|
||||
android:id="@+id/mobileDevicesSwipe"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/mobileDevicesRecycler"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
tools:listitem="@layout/item_mobile_device" />
|
||||
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
|
||||
|
||||
<com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||
android:id="@+id/mobileDeviceAddButton"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="bottom|end"
|
||||
android:layout_margin="16dp"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
android:tint="#FFFFFF"
|
||||
app:srcCompat="@drawable/ic_all_add_24dp" />
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
52
app/src/main/res/layout/item_mobile_device.xml
Normal file
52
app/src/main/res/layout/item_mobile_device.xml
Normal file
@ -0,0 +1,52 @@
|
||||
<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:id="@+id/mobileDevice_subitem_container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="64dp"
|
||||
tools:context=".ui.modules.mobiledevice.MobileDeviceItem">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/mobileDeviceItemName"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="32dp"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginLeft="16dp"
|
||||
android:layout_toStartOf="@id/mobileDeviceItemUnregister"
|
||||
android:layout_toLeftOf="@id/mobileDeviceItemUnregister"
|
||||
android:ellipsize="end"
|
||||
android:gravity="bottom"
|
||||
android:maxLines="1"
|
||||
android:textSize="16sp"
|
||||
tools:text="@tools:sample/lorem/random" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/mobileDeviceItemDate"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="20dp"
|
||||
android:layout_below="@id/mobileDeviceItemName"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginLeft="16dp"
|
||||
android:layout_toStartOf="@id/mobileDeviceItemUnregister"
|
||||
android:layout_toLeftOf="@id/mobileDeviceItemUnregister"
|
||||
android:gravity="bottom"
|
||||
android:textColor="?android:textColorSecondary"
|
||||
android:textSize="14sp"
|
||||
tools:text="@tools:sample/date/ddmmyy" />
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/mobileDeviceItemUnregister"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:layout_alignParentRight="true"
|
||||
android:layout_centerVertical="true"
|
||||
android:layout_marginStart="4dp"
|
||||
android:layout_marginLeft="4dp"
|
||||
android:layout_marginEnd="4dp"
|
||||
android:layout_marginRight="4dp"
|
||||
android:background="?selectableItemBackgroundBorderless"
|
||||
android:contentDescription="@string/mobile_devices_unregister"
|
||||
app:srcCompat="@drawable/ic_message_delete_24dp"
|
||||
app:tint="?android:textColorSecondary" />
|
||||
</RelativeLayout>
|
@ -204,6 +204,17 @@
|
||||
<string name="lucky_number_notify_new_item">Dziś szczęśliwym numerkiem jest: %d</string>
|
||||
|
||||
|
||||
<!--Mobile devices-->
|
||||
<string name="mobile_devices_title">Dostęp mobilny</string>
|
||||
<string name="mobile_devices_no_items">Brak urządzeń</string>
|
||||
<string name="mobile_devices_unregister">Wyrejestruj</string>
|
||||
<string name="mobile_device_removed">Urządzenie usunięte</string>
|
||||
<string name="mobile_device_qr">Kod QR</string>
|
||||
<string name="mobile_device_token">Token</string>
|
||||
<string name="mobile_device_symbol">Symbol</string>
|
||||
<string name="mobile_device_pin">PIN</string>
|
||||
|
||||
|
||||
<!--Account-->
|
||||
<string name="account_add_new">Dodaj konto</string>
|
||||
<string name="account_logout">Wyloguj</string>
|
||||
@ -279,6 +290,7 @@
|
||||
|
||||
<!--Others-->
|
||||
<string name="all_copied">Skopiowano</string>
|
||||
<string name="all_undo">Cofnij</string>
|
||||
|
||||
|
||||
<!--Errors-->
|
||||
|
@ -188,6 +188,16 @@
|
||||
<string name="lucky_number_notify_new_item_title">Lucky number for today</string>
|
||||
<string name="lucky_number_notify_new_item">Today\'s lucky number is: %d</string>
|
||||
|
||||
<!--Mobile devices-->
|
||||
<string name="mobile_devices_title">Mobile devices</string>
|
||||
<string name="mobile_devices_no_items">No devices</string>
|
||||
<string name="mobile_devices_unregister">Unregister</string>
|
||||
<string name="mobile_device_removed">Device removed</string>
|
||||
<string name="mobile_device_qr">QR code</string>
|
||||
<string name="mobile_device_token">Token</string>
|
||||
<string name="mobile_device_symbol">Symbol</string>
|
||||
<string name="mobile_device_pin">PIN</string>
|
||||
|
||||
|
||||
<!--Account-->
|
||||
<string name="account_add_new">Add account</string>
|
||||
@ -264,6 +274,7 @@
|
||||
|
||||
<!--Others-->
|
||||
<string name="all_copied">Copied</string>
|
||||
<string name="all_undo">Undo</string>
|
||||
|
||||
|
||||
<!--Errors-->
|
||||
|
@ -0,0 +1,64 @@
|
||||
package io.github.wulkanowy.data.repositories.mobiledevice
|
||||
|
||||
import com.github.pwittchen.reactivenetwork.library.rx2.internet.observing.InternetObservingSettings
|
||||
import io.github.wulkanowy.data.db.entities.MobileDevice
|
||||
import io.github.wulkanowy.data.db.entities.Semester
|
||||
import io.github.wulkanowy.data.repositories.UnitTestInternetObservingStrategy
|
||||
import io.reactivex.Maybe
|
||||
import io.reactivex.Single
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.mockito.Mock
|
||||
import org.mockito.Mockito.doReturn
|
||||
import org.mockito.Mockito.verify
|
||||
import org.mockito.MockitoAnnotations
|
||||
import org.threeten.bp.LocalDateTime.of
|
||||
|
||||
class MobileDeviceRepositoryTest {
|
||||
|
||||
@Mock
|
||||
private lateinit var semester: Semester
|
||||
|
||||
@Mock
|
||||
private lateinit var mobileDeviceRemote: MobileDeviceRemote
|
||||
|
||||
@Mock
|
||||
private lateinit var mobileDeviceLocal: MobileDeviceLocal
|
||||
|
||||
private lateinit var mobileDeviceRepository: MobileDeviceRepository
|
||||
|
||||
private val settings = InternetObservingSettings.builder()
|
||||
.strategy(UnitTestInternetObservingStrategy())
|
||||
.build()
|
||||
|
||||
@Before
|
||||
fun initTest() {
|
||||
MockitoAnnotations.initMocks(this)
|
||||
mobileDeviceRepository = MobileDeviceRepository(settings, mobileDeviceLocal, mobileDeviceRemote)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun getDevices() {
|
||||
val devices = listOf(
|
||||
getDeviceEntity(1),
|
||||
getDeviceEntity(2)
|
||||
)
|
||||
|
||||
doReturn(Maybe.empty<MobileDevice>()).`when`(mobileDeviceLocal).getDevices(semester)
|
||||
doReturn(Single.just(devices)).`when`(mobileDeviceRemote).getDevices(semester)
|
||||
|
||||
mobileDeviceRepository.getDevices(semester).blockingGet()
|
||||
|
||||
verify(mobileDeviceLocal).deleteDevices(emptyList())
|
||||
verify(mobileDeviceLocal).saveDevices(devices)
|
||||
}
|
||||
|
||||
private fun getDeviceEntity(day: Int): MobileDevice {
|
||||
return MobileDevice(
|
||||
studentId = 1,
|
||||
deviceId = 1,
|
||||
name = "",
|
||||
date = of(2019, 5, day, 0, 0, 0)
|
||||
)
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user