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
|
container_config: &container_config
|
||||||
docker:
|
docker:
|
||||||
- image: circleci/android:api-28
|
- image: circleci/android@sha256:5cdc8626cc6f13efe5ed982cdcdb432b0472f8740fed8743a6461e025ad6cdfc
|
||||||
working_directory: *workspace_root
|
working_directory: *workspace_root
|
||||||
environment:
|
environment:
|
||||||
environment:
|
environment:
|
||||||
@ -93,6 +93,9 @@ jobs:
|
|||||||
<<: *container_config
|
<<: *container_config
|
||||||
steps:
|
steps:
|
||||||
- *attach_workspace
|
- *attach_workspace
|
||||||
|
- run:
|
||||||
|
name: Accept licenses
|
||||||
|
command: yes | sdkmanager --licenses && yes | sdkmanager --update
|
||||||
- run:
|
- run:
|
||||||
name: Setup emulator
|
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"
|
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 {
|
dependencies {
|
||||||
implementation 'com.github.wulkanowy:api:c84356f'
|
implementation 'com.github.wulkanowy:api:d08b71b'
|
||||||
|
|
||||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
|
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
|
||||||
implementation "androidx.legacy:legacy-support-v4:1.0.0"
|
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(
|
.addMigrations(
|
||||||
Migration12(),
|
Migration12(),
|
||||||
Migration13(),
|
Migration13(),
|
||||||
Migration14()
|
Migration14(),
|
||||||
|
Migration15()
|
||||||
)
|
)
|
||||||
.build()
|
.build()
|
||||||
// close the database and release any stream resources when the test finishes
|
// close the database and release any stream resources when the test finishes
|
||||||
|
@ -132,4 +132,8 @@ internal class RepositoryModule {
|
|||||||
@Singleton
|
@Singleton
|
||||||
@Provides
|
@Provides
|
||||||
fun provideRecipientDao(database: AppDatabase) = database.recipientDao
|
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.HomeworkDao
|
||||||
import io.github.wulkanowy.data.db.dao.LuckyNumberDao
|
import io.github.wulkanowy.data.db.dao.LuckyNumberDao
|
||||||
import io.github.wulkanowy.data.db.dao.MessagesDao
|
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.NoteDao
|
||||||
import io.github.wulkanowy.data.db.dao.RecipientDao
|
import io.github.wulkanowy.data.db.dao.RecipientDao
|
||||||
import io.github.wulkanowy.data.db.dao.ReportingUnitDao
|
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.Homework
|
||||||
import io.github.wulkanowy.data.db.entities.LuckyNumber
|
import io.github.wulkanowy.data.db.entities.LuckyNumber
|
||||||
import io.github.wulkanowy.data.db.entities.Message
|
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.Note
|
||||||
import io.github.wulkanowy.data.db.entities.Recipient
|
import io.github.wulkanowy.data.db.entities.Recipient
|
||||||
import io.github.wulkanowy.data.db.entities.ReportingUnit
|
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.Migration12
|
||||||
import io.github.wulkanowy.data.db.migrations.Migration13
|
import io.github.wulkanowy.data.db.migrations.Migration13
|
||||||
import io.github.wulkanowy.data.db.migrations.Migration14
|
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.Migration2
|
||||||
import io.github.wulkanowy.data.db.migrations.Migration3
|
import io.github.wulkanowy.data.db.migrations.Migration3
|
||||||
import io.github.wulkanowy.data.db.migrations.Migration4
|
import io.github.wulkanowy.data.db.migrations.Migration4
|
||||||
@ -74,7 +77,8 @@ import javax.inject.Singleton
|
|||||||
LuckyNumber::class,
|
LuckyNumber::class,
|
||||||
CompletedLesson::class,
|
CompletedLesson::class,
|
||||||
ReportingUnit::class,
|
ReportingUnit::class,
|
||||||
Recipient::class
|
Recipient::class,
|
||||||
|
MobileDevice::class
|
||||||
],
|
],
|
||||||
version = AppDatabase.VERSION_SCHEMA,
|
version = AppDatabase.VERSION_SCHEMA,
|
||||||
exportSchema = true
|
exportSchema = true
|
||||||
@ -83,7 +87,7 @@ import javax.inject.Singleton
|
|||||||
abstract class AppDatabase : RoomDatabase() {
|
abstract class AppDatabase : RoomDatabase() {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val VERSION_SCHEMA = 14
|
const val VERSION_SCHEMA = 15
|
||||||
|
|
||||||
fun newInstance(context: Context): AppDatabase {
|
fun newInstance(context: Context): AppDatabase {
|
||||||
return Room.databaseBuilder(context, AppDatabase::class.java, "wulkanowy_database")
|
return Room.databaseBuilder(context, AppDatabase::class.java, "wulkanowy_database")
|
||||||
@ -103,7 +107,8 @@ abstract class AppDatabase : RoomDatabase() {
|
|||||||
Migration11(),
|
Migration11(),
|
||||||
Migration12(),
|
Migration12(),
|
||||||
Migration13(),
|
Migration13(),
|
||||||
Migration14()
|
Migration14(),
|
||||||
|
Migration15()
|
||||||
)
|
)
|
||||||
.build()
|
.build()
|
||||||
}
|
}
|
||||||
@ -142,4 +147,6 @@ abstract class AppDatabase : RoomDatabase() {
|
|||||||
abstract val reportingUnitDao: ReportingUnitDao
|
abstract val reportingUnitDao: ReportingUnitDao
|
||||||
|
|
||||||
abstract val recipientDao: RecipientDao
|
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.MessageFragment
|
||||||
import io.github.wulkanowy.ui.modules.message.MessageModule
|
import io.github.wulkanowy.ui.modules.message.MessageModule
|
||||||
import io.github.wulkanowy.ui.modules.message.preview.MessagePreviewFragment
|
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.more.MoreFragment
|
||||||
import io.github.wulkanowy.ui.modules.note.NoteFragment
|
import io.github.wulkanowy.ui.modules.note.NoteFragment
|
||||||
import io.github.wulkanowy.ui.modules.settings.SettingsFragment
|
import io.github.wulkanowy.ui.modules.settings.SettingsFragment
|
||||||
@ -97,4 +100,12 @@ abstract class MainModule {
|
|||||||
@PerFragment
|
@PerFragment
|
||||||
@ContributesAndroidInjector
|
@ContributesAndroidInjector
|
||||||
abstract fun bindAccountDialog(): AccountDialog
|
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.MainActivity
|
||||||
import io.github.wulkanowy.ui.modules.main.MainView
|
import io.github.wulkanowy.ui.modules.main.MainView
|
||||||
import io.github.wulkanowy.ui.modules.message.MessageFragment
|
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.note.NoteFragment
|
||||||
import io.github.wulkanowy.ui.modules.settings.SettingsFragment
|
import io.github.wulkanowy.ui.modules.settings.SettingsFragment
|
||||||
import io.github.wulkanowy.utils.setOnItemClickListener
|
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?>?
|
override val settingsRes: Pair<String, Drawable?>?
|
||||||
get() {
|
get() {
|
||||||
return context?.run {
|
return context?.run {
|
||||||
@ -127,6 +136,10 @@ class MoreFragment : BaseFragment(), MoreView, MainView.TitledView, MainView.Mai
|
|||||||
(activity as? MainActivity)?.pushView(LuckyNumberFragment.newInstance())
|
(activity as? MainActivity)?.pushView(LuckyNumberFragment.newInstance())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun openMobileDevicesView() {
|
||||||
|
(activity as? MainActivity)?.pushView(MobileDeviceFragment.newInstance())
|
||||||
|
}
|
||||||
|
|
||||||
override fun openSettingsView() {
|
override fun openSettingsView() {
|
||||||
(activity as? MainActivity)?.pushView(SettingsFragment.newInstance())
|
(activity as? MainActivity)?.pushView(SettingsFragment.newInstance())
|
||||||
}
|
}
|
||||||
|
@ -30,6 +30,7 @@ class MorePresenter @Inject constructor(
|
|||||||
homeworkRes?.first -> openHomeworkView()
|
homeworkRes?.first -> openHomeworkView()
|
||||||
noteRes?.first -> openNoteView()
|
noteRes?.first -> openNoteView()
|
||||||
luckyNumberRes?.first -> openLuckyNumberView()
|
luckyNumberRes?.first -> openLuckyNumberView()
|
||||||
|
mobileDevicesRes?.first -> openMobileDevicesView()
|
||||||
settingsRes?.first -> openSettingsView()
|
settingsRes?.first -> openSettingsView()
|
||||||
aboutRes?.first -> openAboutView()
|
aboutRes?.first -> openAboutView()
|
||||||
}
|
}
|
||||||
@ -50,6 +51,7 @@ class MorePresenter @Inject constructor(
|
|||||||
homeworkRes?.let { MoreItem(it.first, it.second) },
|
homeworkRes?.let { MoreItem(it.first, it.second) },
|
||||||
noteRes?.let { MoreItem(it.first, it.second) },
|
noteRes?.let { MoreItem(it.first, it.second) },
|
||||||
luckyNumberRes?.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) },
|
settingsRes?.let { MoreItem(it.first, it.second) },
|
||||||
aboutRes?.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 luckyNumberRes: Pair<String, Drawable?>?
|
||||||
|
|
||||||
|
val mobileDevicesRes: Pair<String, Drawable?>?
|
||||||
|
|
||||||
val settingsRes: Pair<String, Drawable?>?
|
val settingsRes: Pair<String, Drawable?>?
|
||||||
|
|
||||||
val aboutRes: Pair<String, Drawable?>?
|
val aboutRes: Pair<String, Drawable?>?
|
||||||
@ -34,4 +36,6 @@ interface MoreView : BaseView {
|
|||||||
fun openNoteView()
|
fun openNoteView()
|
||||||
|
|
||||||
fun openLuckyNumberView()
|
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"
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:width="24dp"
|
android:width="24dp"
|
||||||
android:height="24dp"
|
android:height="24dp"
|
||||||
android:tint="#FFFFFF"
|
|
||||||
android:viewportWidth="24.0"
|
android:viewportWidth="24.0"
|
||||||
android:viewportHeight="24.0">
|
android:viewportHeight="24.0">
|
||||||
<path
|
<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" />
|
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>
|
</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>
|
<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-->
|
<!--Account-->
|
||||||
<string name="account_add_new">Dodaj konto</string>
|
<string name="account_add_new">Dodaj konto</string>
|
||||||
<string name="account_logout">Wyloguj</string>
|
<string name="account_logout">Wyloguj</string>
|
||||||
@ -279,6 +290,7 @@
|
|||||||
|
|
||||||
<!--Others-->
|
<!--Others-->
|
||||||
<string name="all_copied">Skopiowano</string>
|
<string name="all_copied">Skopiowano</string>
|
||||||
|
<string name="all_undo">Cofnij</string>
|
||||||
|
|
||||||
|
|
||||||
<!--Errors-->
|
<!--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_title">Lucky number for today</string>
|
||||||
<string name="lucky_number_notify_new_item">Today\'s lucky number is: %d</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-->
|
<!--Account-->
|
||||||
<string name="account_add_new">Add account</string>
|
<string name="account_add_new">Add account</string>
|
||||||
@ -264,6 +274,7 @@
|
|||||||
|
|
||||||
<!--Others-->
|
<!--Others-->
|
||||||
<string name="all_copied">Copied</string>
|
<string name="all_copied">Copied</string>
|
||||||
|
<string name="all_undo">Undo</string>
|
||||||
|
|
||||||
|
|
||||||
<!--Errors-->
|
<!--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