mirror of
https://github.com/wulkanowy/wulkanowy.git
synced 2025-01-31 11:48:20 +01:00
Add avatars (#1146)
This commit is contained in:
parent
412057b512
commit
9e2985864a
@ -166,6 +166,7 @@ dependencies {
|
|||||||
implementation "com.google.android.material:material:1.3.0"
|
implementation "com.google.android.material:material:1.3.0"
|
||||||
implementation "com.github.wulkanowy:material-chips-input:2.1.1"
|
implementation "com.github.wulkanowy:material-chips-input:2.1.1"
|
||||||
implementation "com.github.PhilJay:MPAndroidChart:v3.1.0"
|
implementation "com.github.PhilJay:MPAndroidChart:v3.1.0"
|
||||||
|
implementation 'com.mikhaellopez:circularimageview:4.2.0'
|
||||||
|
|
||||||
implementation "androidx.work:work-runtime-ktx:$work_manager"
|
implementation "androidx.work:work-runtime-ktx:$work_manager"
|
||||||
playImplementation "androidx.work:work-gcm:$work_manager"
|
playImplementation "androidx.work:work-gcm:$work_manager"
|
||||||
|
2148
app/schemas/io.github.wulkanowy.data.db.AppDatabase/35.json
Normal file
2148
app/schemas/io.github.wulkanowy.data.db.AppDatabase/35.json
Normal file
File diff suppressed because it is too large
Load Diff
@ -9,6 +9,7 @@ import androidx.test.core.app.ApplicationProvider
|
|||||||
import androidx.test.platform.app.InstrumentationRegistry
|
import androidx.test.platform.app.InstrumentationRegistry
|
||||||
import io.github.wulkanowy.data.db.AppDatabase
|
import io.github.wulkanowy.data.db.AppDatabase
|
||||||
import io.github.wulkanowy.data.db.SharedPrefProvider
|
import io.github.wulkanowy.data.db.SharedPrefProvider
|
||||||
|
import io.github.wulkanowy.utils.AppInfo
|
||||||
import org.junit.Rule
|
import org.junit.Rule
|
||||||
|
|
||||||
abstract class AbstractMigrationTest {
|
abstract class AbstractMigrationTest {
|
||||||
@ -24,12 +25,16 @@ abstract class AbstractMigrationTest {
|
|||||||
|
|
||||||
fun getMigratedRoomDatabase(): AppDatabase {
|
fun getMigratedRoomDatabase(): AppDatabase {
|
||||||
val context = ApplicationProvider.getApplicationContext<Context>()
|
val context = ApplicationProvider.getApplicationContext<Context>()
|
||||||
val database = Room.databaseBuilder(ApplicationProvider.getApplicationContext(),
|
val database = Room.databaseBuilder(
|
||||||
AppDatabase::class.java, dbName)
|
ApplicationProvider.getApplicationContext(),
|
||||||
.addMigrations(*AppDatabase.getMigrations(SharedPrefProvider(PreferenceManager
|
AppDatabase::class.java,
|
||||||
.getDefaultSharedPreferences(context)))
|
dbName
|
||||||
|
).addMigrations(
|
||||||
|
*AppDatabase.getMigrations(
|
||||||
|
SharedPrefProvider(PreferenceManager.getDefaultSharedPreferences(context)),
|
||||||
|
AppInfo()
|
||||||
)
|
)
|
||||||
.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
|
||||||
helper.closeWhenFinished(database)
|
helper.closeWhenFinished(database)
|
||||||
return database
|
return database
|
||||||
|
@ -0,0 +1,60 @@
|
|||||||
|
package io.github.wulkanowy.data.db.migrations
|
||||||
|
|
||||||
|
import android.content.ContentValues
|
||||||
|
import android.database.sqlite.SQLiteDatabase
|
||||||
|
import androidx.sqlite.db.SupportSQLiteDatabase
|
||||||
|
import io.github.wulkanowy.utils.AppInfo
|
||||||
|
import kotlinx.coroutines.runBlocking
|
||||||
|
import org.junit.Test
|
||||||
|
import kotlin.random.Random
|
||||||
|
import kotlin.test.assertEquals
|
||||||
|
import kotlin.test.assertTrue
|
||||||
|
|
||||||
|
class Migration35Test : AbstractMigrationTest() {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun addRandomAvatarColorsForStudents() {
|
||||||
|
with(helper.createDatabase(dbName, 34)) {
|
||||||
|
createStudent(this, 1)
|
||||||
|
createStudent(this, 2)
|
||||||
|
}
|
||||||
|
|
||||||
|
helper.runMigrationsAndValidate(dbName, 35, true, Migration35(AppInfo()))
|
||||||
|
|
||||||
|
val db = getMigratedRoomDatabase()
|
||||||
|
val students = runBlocking { db.studentDao.loadAll() }
|
||||||
|
|
||||||
|
assertEquals(2, students.size)
|
||||||
|
|
||||||
|
assertTrue { students[0].avatarColor in AppInfo().defaultColorsForAvatar }
|
||||||
|
assertTrue { students[1].avatarColor in AppInfo().defaultColorsForAvatar }
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createStudent(db: SupportSQLiteDatabase, id: Long) {
|
||||||
|
db.insert("Students", SQLiteDatabase.CONFLICT_FAIL, ContentValues().apply {
|
||||||
|
put("id", id)
|
||||||
|
put("scrapper_base_url", "https://fakelog.cf")
|
||||||
|
put("mobile_base_url", "")
|
||||||
|
put("login_mode", "SCRAPPER")
|
||||||
|
put("login_type", "STANDARD")
|
||||||
|
put("certificate_key", "")
|
||||||
|
put("private_key", "")
|
||||||
|
put("is_parent", false)
|
||||||
|
put("email", "jan@fakelog.cf")
|
||||||
|
put("password", "******")
|
||||||
|
put("symbol", "Default")
|
||||||
|
put("school_short", "")
|
||||||
|
put("class_name", "")
|
||||||
|
put("student_id", Random.nextInt())
|
||||||
|
put("class_id", Random.nextInt())
|
||||||
|
put("school_id", "123")
|
||||||
|
put("school_name", "Wulkan first class school")
|
||||||
|
put("is_current", false)
|
||||||
|
put("registration_date", "0")
|
||||||
|
put("user_login_id", Random.nextInt())
|
||||||
|
put("student_name", "")
|
||||||
|
put("user_name", "")
|
||||||
|
put("nick", "")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
@ -1,11 +1,13 @@
|
|||||||
package io.github.wulkanowy
|
package io.github.wulkanowy
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
import android.app.Application
|
import android.app.Application
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.util.Log.DEBUG
|
import android.util.Log.DEBUG
|
||||||
import android.util.Log.INFO
|
import android.util.Log.INFO
|
||||||
import android.util.Log.VERBOSE
|
import android.util.Log.VERBOSE
|
||||||
import android.webkit.WebView
|
import android.webkit.WebView
|
||||||
|
import androidx.fragment.app.FragmentManager
|
||||||
import androidx.hilt.work.HiltWorkerFactory
|
import androidx.hilt.work.HiltWorkerFactory
|
||||||
import androidx.multidex.MultiDex
|
import androidx.multidex.MultiDex
|
||||||
import androidx.work.Configuration
|
import androidx.work.Configuration
|
||||||
@ -46,8 +48,10 @@ class WulkanowyApp : Application(), Configuration.Provider {
|
|||||||
MultiDex.install(this)
|
MultiDex.install(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressLint("UnsafeOptInUsageWarning")
|
||||||
override fun onCreate() {
|
override fun onCreate() {
|
||||||
super.onCreate()
|
super.onCreate()
|
||||||
|
FragmentManager.enableNewStateManager(false)
|
||||||
|
|
||||||
initializeAppLanguage()
|
initializeAppLanguage()
|
||||||
themeManager.applyDefaultTheme()
|
themeManager.applyDefaultTheme()
|
||||||
|
@ -17,6 +17,7 @@ import io.github.wulkanowy.data.db.AppDatabase
|
|||||||
import io.github.wulkanowy.data.db.SharedPrefProvider
|
import io.github.wulkanowy.data.db.SharedPrefProvider
|
||||||
import io.github.wulkanowy.data.repositories.PreferencesRepository
|
import io.github.wulkanowy.data.repositories.PreferencesRepository
|
||||||
import io.github.wulkanowy.sdk.Sdk
|
import io.github.wulkanowy.sdk.Sdk
|
||||||
|
import io.github.wulkanowy.utils.AppInfo
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import javax.inject.Singleton
|
import javax.inject.Singleton
|
||||||
|
|
||||||
@ -60,7 +61,8 @@ internal class RepositoryModule {
|
|||||||
fun provideDatabase(
|
fun provideDatabase(
|
||||||
@ApplicationContext context: Context,
|
@ApplicationContext context: Context,
|
||||||
sharedPrefProvider: SharedPrefProvider,
|
sharedPrefProvider: SharedPrefProvider,
|
||||||
) = AppDatabase.newInstance(context, sharedPrefProvider)
|
appInfo: AppInfo
|
||||||
|
) = AppDatabase.newInstance(context, sharedPrefProvider, appInfo)
|
||||||
|
|
||||||
@Singleton
|
@Singleton
|
||||||
@Provides
|
@Provides
|
||||||
|
@ -6,7 +6,6 @@ import androidx.room.Room
|
|||||||
import androidx.room.RoomDatabase
|
import androidx.room.RoomDatabase
|
||||||
import androidx.room.RoomDatabase.JournalMode.TRUNCATE
|
import androidx.room.RoomDatabase.JournalMode.TRUNCATE
|
||||||
import androidx.room.TypeConverters
|
import androidx.room.TypeConverters
|
||||||
import androidx.room.migration.Migration
|
|
||||||
import io.github.wulkanowy.data.db.dao.AttendanceDao
|
import io.github.wulkanowy.data.db.dao.AttendanceDao
|
||||||
import io.github.wulkanowy.data.db.dao.AttendanceSummaryDao
|
import io.github.wulkanowy.data.db.dao.AttendanceSummaryDao
|
||||||
import io.github.wulkanowy.data.db.dao.CompletedLessonsDao
|
import io.github.wulkanowy.data.db.dao.CompletedLessonsDao
|
||||||
@ -86,12 +85,14 @@ import io.github.wulkanowy.data.db.migrations.Migration31
|
|||||||
import io.github.wulkanowy.data.db.migrations.Migration32
|
import io.github.wulkanowy.data.db.migrations.Migration32
|
||||||
import io.github.wulkanowy.data.db.migrations.Migration33
|
import io.github.wulkanowy.data.db.migrations.Migration33
|
||||||
import io.github.wulkanowy.data.db.migrations.Migration34
|
import io.github.wulkanowy.data.db.migrations.Migration34
|
||||||
|
import io.github.wulkanowy.data.db.migrations.Migration35
|
||||||
import io.github.wulkanowy.data.db.migrations.Migration4
|
import io.github.wulkanowy.data.db.migrations.Migration4
|
||||||
import io.github.wulkanowy.data.db.migrations.Migration5
|
import io.github.wulkanowy.data.db.migrations.Migration5
|
||||||
import io.github.wulkanowy.data.db.migrations.Migration6
|
import io.github.wulkanowy.data.db.migrations.Migration6
|
||||||
import io.github.wulkanowy.data.db.migrations.Migration7
|
import io.github.wulkanowy.data.db.migrations.Migration7
|
||||||
import io.github.wulkanowy.data.db.migrations.Migration8
|
import io.github.wulkanowy.data.db.migrations.Migration8
|
||||||
import io.github.wulkanowy.data.db.migrations.Migration9
|
import io.github.wulkanowy.data.db.migrations.Migration9
|
||||||
|
import io.github.wulkanowy.utils.AppInfo
|
||||||
import javax.inject.Singleton
|
import javax.inject.Singleton
|
||||||
|
|
||||||
@Singleton
|
@Singleton
|
||||||
@ -131,10 +132,9 @@ import javax.inject.Singleton
|
|||||||
abstract class AppDatabase : RoomDatabase() {
|
abstract class AppDatabase : RoomDatabase() {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val VERSION_SCHEMA = 34
|
const val VERSION_SCHEMA = 35
|
||||||
|
|
||||||
fun getMigrations(sharedPrefProvider: SharedPrefProvider): Array<Migration> {
|
fun getMigrations(sharedPrefProvider: SharedPrefProvider, appInfo: AppInfo) = arrayOf(
|
||||||
return arrayOf(
|
|
||||||
Migration2(),
|
Migration2(),
|
||||||
Migration3(),
|
Migration3(),
|
||||||
Migration4(),
|
Migration4(),
|
||||||
@ -167,19 +167,21 @@ abstract class AppDatabase : RoomDatabase() {
|
|||||||
Migration31(),
|
Migration31(),
|
||||||
Migration32(),
|
Migration32(),
|
||||||
Migration33(),
|
Migration33(),
|
||||||
Migration34()
|
Migration34(),
|
||||||
|
Migration35(appInfo)
|
||||||
)
|
)
|
||||||
}
|
|
||||||
|
|
||||||
fun newInstance(context: Context, sharedPrefProvider: SharedPrefProvider): AppDatabase {
|
fun newInstance(
|
||||||
return Room.databaseBuilder(context, AppDatabase::class.java, "wulkanowy_database")
|
context: Context,
|
||||||
|
sharedPrefProvider: SharedPrefProvider,
|
||||||
|
appInfo: AppInfo
|
||||||
|
) = Room.databaseBuilder(context, AppDatabase::class.java, "wulkanowy_database")
|
||||||
.setJournalMode(TRUNCATE)
|
.setJournalMode(TRUNCATE)
|
||||||
.fallbackToDestructiveMigrationFrom(VERSION_SCHEMA + 1)
|
.fallbackToDestructiveMigrationFrom(VERSION_SCHEMA + 1)
|
||||||
.fallbackToDestructiveMigrationOnDowngrade()
|
.fallbackToDestructiveMigrationOnDowngrade()
|
||||||
.addMigrations(*getMigrations(sharedPrefProvider))
|
.addMigrations(*getMigrations(sharedPrefProvider, appInfo))
|
||||||
.build()
|
.build()
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
abstract val studentDao: StudentDao
|
abstract val studentDao: StudentDao
|
||||||
|
|
||||||
|
@ -8,7 +8,7 @@ import androidx.room.Query
|
|||||||
import androidx.room.Transaction
|
import androidx.room.Transaction
|
||||||
import androidx.room.Update
|
import androidx.room.Update
|
||||||
import io.github.wulkanowy.data.db.entities.Student
|
import io.github.wulkanowy.data.db.entities.Student
|
||||||
import io.github.wulkanowy.data.db.entities.StudentNick
|
import io.github.wulkanowy.data.db.entities.StudentNickAndAvatar
|
||||||
import io.github.wulkanowy.data.db.entities.StudentWithSemesters
|
import io.github.wulkanowy.data.db.entities.StudentWithSemesters
|
||||||
import javax.inject.Singleton
|
import javax.inject.Singleton
|
||||||
|
|
||||||
@ -23,13 +23,13 @@ interface StudentDao {
|
|||||||
suspend fun delete(student: Student)
|
suspend fun delete(student: Student)
|
||||||
|
|
||||||
@Update(entity = Student::class)
|
@Update(entity = Student::class)
|
||||||
suspend fun update(studentNick: StudentNick)
|
suspend fun update(studentNickAndAvatar: StudentNickAndAvatar)
|
||||||
|
|
||||||
@Query("SELECT * FROM Students WHERE is_current = 1")
|
@Query("SELECT * FROM Students WHERE is_current = 1")
|
||||||
suspend fun loadCurrent(): Student?
|
suspend fun loadCurrent(): Student?
|
||||||
|
|
||||||
@Query("SELECT * FROM Students WHERE id = :id")
|
@Query("SELECT * FROM Students WHERE id = :id")
|
||||||
suspend fun loadById(id: Int): Student?
|
suspend fun loadById(id: Long): Student?
|
||||||
|
|
||||||
@Query("SELECT * FROM Students")
|
@Query("SELECT * FROM Students")
|
||||||
suspend fun loadAll(): List<Student>
|
suspend fun loadAll(): List<Student>
|
||||||
|
@ -10,7 +10,7 @@ import java.time.LocalDateTime
|
|||||||
data class Message(
|
data class Message(
|
||||||
|
|
||||||
@ColumnInfo(name = "student_id")
|
@ColumnInfo(name = "student_id")
|
||||||
val studentId: Int,
|
val studentId: Long,
|
||||||
|
|
||||||
@ColumnInfo(name = "real_id")
|
@ColumnInfo(name = "real_id")
|
||||||
val realId: Int,
|
val realId: Int,
|
||||||
|
@ -81,4 +81,7 @@ data class Student(
|
|||||||
var id: Long = 0
|
var id: Long = 0
|
||||||
|
|
||||||
var nick = ""
|
var nick = ""
|
||||||
|
|
||||||
|
@ColumnInfo(name = "avatar_color")
|
||||||
|
var avatarColor = 0L
|
||||||
}
|
}
|
||||||
|
@ -1,13 +1,17 @@
|
|||||||
package io.github.wulkanowy.data.db.entities
|
package io.github.wulkanowy.data.db.entities
|
||||||
|
|
||||||
|
import androidx.room.ColumnInfo
|
||||||
import androidx.room.Entity
|
import androidx.room.Entity
|
||||||
import androidx.room.PrimaryKey
|
import androidx.room.PrimaryKey
|
||||||
import java.io.Serializable
|
import java.io.Serializable
|
||||||
|
|
||||||
@Entity
|
@Entity
|
||||||
data class StudentNick(
|
data class StudentNickAndAvatar(
|
||||||
|
|
||||||
val nick: String
|
val nick: String,
|
||||||
|
|
||||||
|
@ColumnInfo(name = "avatar_color")
|
||||||
|
var avatarColor: Long
|
||||||
|
|
||||||
) : Serializable {
|
) : Serializable {
|
||||||
|
|
@ -0,0 +1,24 @@
|
|||||||
|
package io.github.wulkanowy.data.db.migrations
|
||||||
|
|
||||||
|
import androidx.core.database.getLongOrNull
|
||||||
|
import androidx.room.migration.Migration
|
||||||
|
import androidx.sqlite.db.SupportSQLiteDatabase
|
||||||
|
import io.github.wulkanowy.utils.AppInfo
|
||||||
|
|
||||||
|
class Migration35(private val appInfo: AppInfo) : Migration(34, 35) {
|
||||||
|
|
||||||
|
override fun migrate(database: SupportSQLiteDatabase) {
|
||||||
|
database.execSQL("ALTER TABLE Students ADD COLUMN `avatar_color` INTEGER NOT NULL DEFAULT 0")
|
||||||
|
|
||||||
|
val studentsCursor = database.query("SELECT * FROM Students")
|
||||||
|
|
||||||
|
while (studentsCursor.moveToNext()) {
|
||||||
|
val studentId = studentsCursor.getLongOrNull(0)
|
||||||
|
database.execSQL(
|
||||||
|
"""UPDATE Students
|
||||||
|
SET avatar_color = ${appInfo.defaultColorsForAvatar.random()}
|
||||||
|
WHERE id = $studentId"""
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -4,14 +4,14 @@ import io.github.wulkanowy.data.db.entities.Message
|
|||||||
import io.github.wulkanowy.data.db.entities.MessageAttachment
|
import io.github.wulkanowy.data.db.entities.MessageAttachment
|
||||||
import io.github.wulkanowy.data.db.entities.Recipient
|
import io.github.wulkanowy.data.db.entities.Recipient
|
||||||
import io.github.wulkanowy.data.db.entities.Student
|
import io.github.wulkanowy.data.db.entities.Student
|
||||||
import io.github.wulkanowy.sdk.pojo.Recipient as SdkRecipient
|
|
||||||
import io.github.wulkanowy.sdk.pojo.MessageAttachment as SdkMessageAttachment
|
|
||||||
import java.time.LocalDateTime
|
import java.time.LocalDateTime
|
||||||
import io.github.wulkanowy.sdk.pojo.Message as SdkMessage
|
import io.github.wulkanowy.sdk.pojo.Message as SdkMessage
|
||||||
|
import io.github.wulkanowy.sdk.pojo.MessageAttachment as SdkMessageAttachment
|
||||||
|
import io.github.wulkanowy.sdk.pojo.Recipient as SdkRecipient
|
||||||
|
|
||||||
fun List<SdkMessage>.mapToEntities(student: Student) = map {
|
fun List<SdkMessage>.mapToEntities(student: Student) = map {
|
||||||
Message(
|
Message(
|
||||||
studentId = student.id.toInt(),
|
studentId = student.id,
|
||||||
realId = it.id ?: 0,
|
realId = it.id ?: 0,
|
||||||
messageId = it.messageId ?: 0,
|
messageId = it.messageId ?: 0,
|
||||||
sender = it.sender?.name.orEmpty(),
|
sender = it.sender?.name.orEmpty(),
|
||||||
|
@ -5,7 +5,7 @@ import io.github.wulkanowy.data.db.entities.StudentWithSemesters
|
|||||||
import java.time.LocalDateTime
|
import java.time.LocalDateTime
|
||||||
import io.github.wulkanowy.sdk.pojo.Student as SdkStudent
|
import io.github.wulkanowy.sdk.pojo.Student as SdkStudent
|
||||||
|
|
||||||
fun List<SdkStudent>.mapToEntities(password: String = "") = map {
|
fun List<SdkStudent>.mapToEntities(password: String = "", colors: List<Long>) = map {
|
||||||
StudentWithSemesters(
|
StudentWithSemesters(
|
||||||
student = Student(
|
student = Student(
|
||||||
email = it.email,
|
email = it.email,
|
||||||
@ -28,8 +28,10 @@ fun List<SdkStudent>.mapToEntities(password: String = "") = map {
|
|||||||
mobileBaseUrl = it.mobileBaseUrl,
|
mobileBaseUrl = it.mobileBaseUrl,
|
||||||
privateKey = it.privateKey,
|
privateKey = it.privateKey,
|
||||||
certificateKey = it.certificateKey,
|
certificateKey = it.certificateKey,
|
||||||
loginMode = it.loginMode.name
|
loginMode = it.loginMode.name,
|
||||||
),
|
).apply {
|
||||||
|
avatarColor = colors.random()
|
||||||
|
},
|
||||||
semesters = it.semesters.mapToEntities(it.studentId)
|
semesters = it.semesters.mapToEntities(it.studentId)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -5,11 +5,12 @@ import dagger.hilt.android.qualifiers.ApplicationContext
|
|||||||
import io.github.wulkanowy.data.db.dao.SemesterDao
|
import io.github.wulkanowy.data.db.dao.SemesterDao
|
||||||
import io.github.wulkanowy.data.db.dao.StudentDao
|
import io.github.wulkanowy.data.db.dao.StudentDao
|
||||||
import io.github.wulkanowy.data.db.entities.Student
|
import io.github.wulkanowy.data.db.entities.Student
|
||||||
import io.github.wulkanowy.data.db.entities.StudentNick
|
import io.github.wulkanowy.data.db.entities.StudentNickAndAvatar
|
||||||
import io.github.wulkanowy.data.db.entities.StudentWithSemesters
|
import io.github.wulkanowy.data.db.entities.StudentWithSemesters
|
||||||
import io.github.wulkanowy.data.exceptions.NoCurrentStudentException
|
import io.github.wulkanowy.data.exceptions.NoCurrentStudentException
|
||||||
import io.github.wulkanowy.data.mappers.mapToEntities
|
import io.github.wulkanowy.data.mappers.mapToEntities
|
||||||
import io.github.wulkanowy.sdk.Sdk
|
import io.github.wulkanowy.sdk.Sdk
|
||||||
|
import io.github.wulkanowy.utils.AppInfo
|
||||||
import io.github.wulkanowy.utils.DispatchersProvider
|
import io.github.wulkanowy.utils.DispatchersProvider
|
||||||
import io.github.wulkanowy.utils.security.decrypt
|
import io.github.wulkanowy.utils.security.decrypt
|
||||||
import io.github.wulkanowy.utils.security.encrypt
|
import io.github.wulkanowy.utils.security.encrypt
|
||||||
@ -23,7 +24,8 @@ class StudentRepository @Inject constructor(
|
|||||||
private val dispatchers: DispatchersProvider,
|
private val dispatchers: DispatchersProvider,
|
||||||
private val studentDb: StudentDao,
|
private val studentDb: StudentDao,
|
||||||
private val semesterDb: SemesterDao,
|
private val semesterDb: SemesterDao,
|
||||||
private val sdk: Sdk
|
private val sdk: Sdk,
|
||||||
|
private val appInfo: AppInfo
|
||||||
) {
|
) {
|
||||||
|
|
||||||
suspend fun isStudentSaved() = getSavedStudents(false).isNotEmpty()
|
suspend fun isStudentSaved() = getSavedStudents(false).isNotEmpty()
|
||||||
@ -35,7 +37,8 @@ class StudentRepository @Inject constructor(
|
|||||||
symbol: String,
|
symbol: String,
|
||||||
token: String
|
token: String
|
||||||
): List<StudentWithSemesters> =
|
): List<StudentWithSemesters> =
|
||||||
sdk.getStudentsFromMobileApi(token, pin, symbol, "").mapToEntities()
|
sdk.getStudentsFromMobileApi(token, pin, symbol, "")
|
||||||
|
.mapToEntities(colors = appInfo.defaultColorsForAvatar)
|
||||||
|
|
||||||
suspend fun getStudentsScrapper(
|
suspend fun getStudentsScrapper(
|
||||||
email: String,
|
email: String,
|
||||||
@ -44,7 +47,7 @@ class StudentRepository @Inject constructor(
|
|||||||
symbol: String
|
symbol: String
|
||||||
): List<StudentWithSemesters> =
|
): List<StudentWithSemesters> =
|
||||||
sdk.getStudentsFromScrapper(email, password, scrapperBaseUrl, symbol)
|
sdk.getStudentsFromScrapper(email, password, scrapperBaseUrl, symbol)
|
||||||
.mapToEntities(password)
|
.mapToEntities(password, appInfo.defaultColorsForAvatar)
|
||||||
|
|
||||||
suspend fun getStudentsHybrid(
|
suspend fun getStudentsHybrid(
|
||||||
email: String,
|
email: String,
|
||||||
@ -52,47 +55,59 @@ class StudentRepository @Inject constructor(
|
|||||||
scrapperBaseUrl: String,
|
scrapperBaseUrl: String,
|
||||||
symbol: String
|
symbol: String
|
||||||
): List<StudentWithSemesters> =
|
): List<StudentWithSemesters> =
|
||||||
sdk.getStudentsHybrid(email, password, scrapperBaseUrl, "", symbol).mapToEntities(password)
|
sdk.getStudentsHybrid(email, password, scrapperBaseUrl, "", symbol)
|
||||||
|
.mapToEntities(password, appInfo.defaultColorsForAvatar)
|
||||||
|
|
||||||
suspend fun getSavedStudents(decryptPass: Boolean = true) =
|
suspend fun getSavedStudents(decryptPass: Boolean = true) =
|
||||||
withContext(dispatchers.backgroundThread) {
|
studentDb.loadStudentsWithSemesters()
|
||||||
studentDb.loadStudentsWithSemesters().map {
|
.map {
|
||||||
it.apply {
|
it.apply {
|
||||||
if (decryptPass && Sdk.Mode.valueOf(student.loginMode) != Sdk.Mode.API) {
|
if (decryptPass && Sdk.Mode.valueOf(student.loginMode) != Sdk.Mode.API) {
|
||||||
student.password = decrypt(student.password)
|
student.password = withContext(dispatchers.backgroundThread) {
|
||||||
|
decrypt(student.password)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun getStudentById(id: Int) = withContext(dispatchers.backgroundThread) {
|
suspend fun getStudentById(id: Long, decryptPass: Boolean = true): Student {
|
||||||
studentDb.loadById(id)?.apply {
|
val student = studentDb.loadById(id) ?: throw NoCurrentStudentException()
|
||||||
if (Sdk.Mode.valueOf(loginMode) != Sdk.Mode.API) {
|
|
||||||
password = decrypt(password)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} ?: throw NoCurrentStudentException()
|
|
||||||
|
|
||||||
suspend fun getCurrentStudent(decryptPass: Boolean = true) =
|
if (decryptPass && Sdk.Mode.valueOf(student.loginMode) != Sdk.Mode.API) {
|
||||||
withContext(dispatchers.backgroundThread) {
|
student.password = withContext(dispatchers.backgroundThread) {
|
||||||
studentDb.loadCurrent()?.apply {
|
decrypt(student.password)
|
||||||
if (decryptPass && Sdk.Mode.valueOf(loginMode) != Sdk.Mode.API) {
|
|
||||||
password = decrypt(password)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} ?: throw NoCurrentStudentException()
|
return student
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun getCurrentStudent(decryptPass: Boolean = true): Student {
|
||||||
|
val student = studentDb.loadCurrent() ?: throw NoCurrentStudentException()
|
||||||
|
|
||||||
|
if (decryptPass && Sdk.Mode.valueOf(student.loginMode) != Sdk.Mode.API) {
|
||||||
|
student.password = withContext(dispatchers.backgroundThread) {
|
||||||
|
decrypt(student.password)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return student
|
||||||
|
}
|
||||||
|
|
||||||
suspend fun saveStudents(studentsWithSemesters: List<StudentWithSemesters>): List<Long> {
|
suspend fun saveStudents(studentsWithSemesters: List<StudentWithSemesters>): List<Long> {
|
||||||
semesterDb.insertSemesters(studentsWithSemesters.flatMap { it.semesters })
|
val semesters = studentsWithSemesters.flatMap { it.semesters }
|
||||||
|
val students = studentsWithSemesters.map { it.student }
|
||||||
return withContext(dispatchers.backgroundThread) {
|
.map {
|
||||||
studentDb.insertAll(studentsWithSemesters.map { it.student }.map {
|
it.apply {
|
||||||
if (Sdk.Mode.valueOf(it.loginMode) != Sdk.Mode.API) {
|
if (Sdk.Mode.valueOf(it.loginMode) != Sdk.Mode.API) {
|
||||||
it.copy(password = encrypt(it.password, context))
|
password = withContext(dispatchers.backgroundThread) {
|
||||||
} else it
|
encrypt(password, context)
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
semesterDb.insertSemesters(semesters)
|
||||||
|
return studentDb.insertAll(students)
|
||||||
|
}
|
||||||
|
|
||||||
suspend fun switchStudent(studentWithSemesters: StudentWithSemesters) {
|
suspend fun switchStudent(studentWithSemesters: StudentWithSemesters) {
|
||||||
with(studentDb) {
|
with(studentDb) {
|
||||||
@ -103,5 +118,6 @@ class StudentRepository @Inject constructor(
|
|||||||
|
|
||||||
suspend fun logoutStudent(student: Student) = studentDb.delete(student)
|
suspend fun logoutStudent(student: Student) = studentDb.delete(student)
|
||||||
|
|
||||||
suspend fun updateStudentNick(studentNick: StudentNick) = studentDb.update(studentNick)
|
suspend fun updateStudentNickAndAvatar(studentNickAndAvatar: StudentNickAndAvatar) =
|
||||||
|
studentDb.update(studentNickAndAvatar)
|
||||||
}
|
}
|
||||||
|
@ -1,16 +1,17 @@
|
|||||||
package io.github.wulkanowy.ui.modules.account
|
package io.github.wulkanowy.ui.modules.account
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import android.graphics.PorterDuff
|
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View.GONE
|
import android.view.View.GONE
|
||||||
import android.view.View.VISIBLE
|
import android.view.View.VISIBLE
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
|
import androidx.core.view.isVisible
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import io.github.wulkanowy.R
|
import io.github.wulkanowy.R
|
||||||
import io.github.wulkanowy.data.db.entities.StudentWithSemesters
|
import io.github.wulkanowy.data.db.entities.StudentWithSemesters
|
||||||
import io.github.wulkanowy.databinding.HeaderAccountBinding
|
import io.github.wulkanowy.databinding.HeaderAccountBinding
|
||||||
import io.github.wulkanowy.databinding.ItemAccountBinding
|
import io.github.wulkanowy.databinding.ItemAccountBinding
|
||||||
|
import io.github.wulkanowy.utils.createNameInitialsDrawable
|
||||||
import io.github.wulkanowy.utils.getThemeAttrColor
|
import io.github.wulkanowy.utils.getThemeAttrColor
|
||||||
import io.github.wulkanowy.utils.nickOrName
|
import io.github.wulkanowy.utils.nickOrName
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
@ -72,9 +73,13 @@ class AccountAdapter @Inject constructor() : RecyclerView.Adapter<RecyclerView.V
|
|||||||
binding: ItemAccountBinding,
|
binding: ItemAccountBinding,
|
||||||
studentWithSemesters: StudentWithSemesters
|
studentWithSemesters: StudentWithSemesters
|
||||||
) {
|
) {
|
||||||
|
val context = binding.root.context
|
||||||
val student = studentWithSemesters.student
|
val student = studentWithSemesters.student
|
||||||
val semesters = studentWithSemesters.semesters
|
val semesters = studentWithSemesters.semesters
|
||||||
val diary = semesters.maxByOrNull { it.semesterId }
|
val diary = semesters.maxByOrNull { it.semesterId }
|
||||||
|
val avatar = context.createNameInitialsDrawable(student.nickOrName, student.avatarColor)
|
||||||
|
val checkBackgroundColor =
|
||||||
|
context.getThemeAttrColor(if (isAccountQuickDialogMode) R.attr.colorBackgroundFloating else R.attr.colorSurface)
|
||||||
val isDuplicatedStudent = items.filter {
|
val isDuplicatedStudent = items.filter {
|
||||||
if (it.value !is StudentWithSemesters) return@filter false
|
if (it.value !is StudentWithSemesters) return@filter false
|
||||||
val studentToCompare = it.value.student
|
val studentToCompare = it.value.student
|
||||||
@ -87,15 +92,17 @@ class AccountAdapter @Inject constructor() : RecyclerView.Adapter<RecyclerView.V
|
|||||||
with(binding) {
|
with(binding) {
|
||||||
accountItemName.text = "${student.nickOrName} ${diary?.diaryName.orEmpty()}"
|
accountItemName.text = "${student.nickOrName} ${diary?.diaryName.orEmpty()}"
|
||||||
accountItemSchool.text = studentWithSemesters.student.schoolName
|
accountItemSchool.text = studentWithSemesters.student.schoolName
|
||||||
accountItemAccountType.setText(if (student.isParent) R.string.account_type_parent else R.string.account_type_student)
|
accountItemImage.setImageDrawable(avatar)
|
||||||
accountItemAccountType.visibility = if (isDuplicatedStudent) VISIBLE else GONE
|
|
||||||
|
|
||||||
with(accountItemImage) {
|
with(accountItemAccountType) {
|
||||||
val colorImage =
|
setText(if (student.isParent) R.string.account_type_parent else R.string.account_type_student)
|
||||||
if (student.isCurrent) context.getThemeAttrColor(R.attr.colorPrimary)
|
isVisible = isDuplicatedStudent
|
||||||
else context.getThemeAttrColor(R.attr.colorOnSurface, 153)
|
}
|
||||||
|
|
||||||
setColorFilter(colorImage, PorterDuff.Mode.SRC_IN)
|
with(accountItemCheck) {
|
||||||
|
isVisible = student.isCurrent
|
||||||
|
borderColor = checkBackgroundColor
|
||||||
|
circleColor = checkBackgroundColor
|
||||||
}
|
}
|
||||||
|
|
||||||
root.setOnClickListener { onClickListener(studentWithSemesters) }
|
root.setOnClickListener { onClickListener(studentWithSemesters) }
|
||||||
|
@ -36,23 +36,20 @@ class AccountFragment : BaseFragment<FragmentAccountBinding>(R.layout.fragment_a
|
|||||||
|
|
||||||
override var subtitleString = ""
|
override var subtitleString = ""
|
||||||
|
|
||||||
override val isViewEmpty get() = accountAdapter.items.isEmpty()
|
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
setHasOptionsMenu(true)
|
setHasOptionsMenu(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
super.onViewCreated(view, savedInstanceState)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
|
||||||
binding = FragmentAccountBinding.bind(view)
|
binding = FragmentAccountBinding.bind(view)
|
||||||
presenter.onAttachView(this)
|
presenter.onAttachView(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun initView() {
|
override fun initView() {
|
||||||
binding.accountErrorRetry.setOnClickListener { presenter.onRetry() }
|
|
||||||
binding.accountErrorDetails.setOnClickListener { presenter.onDetailsClick() }
|
|
||||||
|
|
||||||
binding.accountRecycler.apply {
|
binding.accountRecycler.apply {
|
||||||
layoutManager = LinearLayoutManager(context)
|
layoutManager = LinearLayoutManager(context)
|
||||||
adapter = accountAdapter
|
adapter = accountAdapter
|
||||||
@ -60,9 +57,7 @@ class AccountFragment : BaseFragment<FragmentAccountBinding>(R.layout.fragment_a
|
|||||||
|
|
||||||
accountAdapter.onClickListener = presenter::onItemSelected
|
accountAdapter.onClickListener = presenter::onItemSelected
|
||||||
|
|
||||||
with(binding) {
|
binding.accountAdd.setOnClickListener { presenter.onAddSelected() }
|
||||||
accountAdd.setOnClickListener { presenter.onAddSelected() }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
|
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
|
||||||
@ -84,28 +79,7 @@ class AccountFragment : BaseFragment<FragmentAccountBinding>(R.layout.fragment_a
|
|||||||
|
|
||||||
override fun openAccountDetailsView(studentWithSemesters: StudentWithSemesters) {
|
override fun openAccountDetailsView(studentWithSemesters: StudentWithSemesters) {
|
||||||
(activity as? MainActivity)?.pushView(
|
(activity as? MainActivity)?.pushView(
|
||||||
AccountDetailsFragment.newInstance(
|
AccountDetailsFragment.newInstance(studentWithSemesters)
|
||||||
studentWithSemesters
|
|
||||||
)
|
)
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun showErrorView(show: Boolean) {
|
|
||||||
binding.accountError.visibility = if (show) View.VISIBLE else View.GONE
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun setErrorDetails(message: String) {
|
|
||||||
binding.accountErrorMessage.text = message
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun showProgress(show: Boolean) {
|
|
||||||
binding.accountProgress.visibility = if (show) View.VISIBLE else View.GONE
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun showContent(show: Boolean) {
|
|
||||||
with(binding) {
|
|
||||||
accountRecycler.visibility = if (show) View.VISIBLE else View.GONE
|
|
||||||
accountAdd.visibility = if (show) View.VISIBLE else View.GONE
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,7 +5,6 @@ import io.github.wulkanowy.data.db.entities.StudentWithSemesters
|
|||||||
import io.github.wulkanowy.data.repositories.StudentRepository
|
import io.github.wulkanowy.data.repositories.StudentRepository
|
||||||
import io.github.wulkanowy.ui.base.BasePresenter
|
import io.github.wulkanowy.ui.base.BasePresenter
|
||||||
import io.github.wulkanowy.ui.base.ErrorHandler
|
import io.github.wulkanowy.ui.base.ErrorHandler
|
||||||
import io.github.wulkanowy.utils.afterLoading
|
|
||||||
import io.github.wulkanowy.utils.flowWithResource
|
import io.github.wulkanowy.utils.flowWithResource
|
||||||
import kotlinx.coroutines.flow.onEach
|
import kotlinx.coroutines.flow.onEach
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
@ -16,28 +15,13 @@ class AccountPresenter @Inject constructor(
|
|||||||
studentRepository: StudentRepository,
|
studentRepository: StudentRepository,
|
||||||
) : BasePresenter<AccountView>(errorHandler, studentRepository) {
|
) : BasePresenter<AccountView>(errorHandler, studentRepository) {
|
||||||
|
|
||||||
private lateinit var lastError: Throwable
|
|
||||||
|
|
||||||
override fun onAttachView(view: AccountView) {
|
override fun onAttachView(view: AccountView) {
|
||||||
super.onAttachView(view)
|
super.onAttachView(view)
|
||||||
view.initView()
|
view.initView()
|
||||||
Timber.i("Account view was initialized")
|
Timber.i("Account view was initialized")
|
||||||
errorHandler.showErrorMessage = ::showErrorViewOnError
|
|
||||||
loadData()
|
loadData()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onRetry() {
|
|
||||||
view?.run {
|
|
||||||
showErrorView(false)
|
|
||||||
showProgress(true)
|
|
||||||
}
|
|
||||||
loadData()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun onDetailsClick() {
|
|
||||||
view?.showErrorDetailsDialog(lastError)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun onAddSelected() {
|
fun onAddSelected() {
|
||||||
Timber.i("Select add account")
|
Timber.i("Select add account")
|
||||||
view?.openLoginView()
|
view?.openLoginView()
|
||||||
@ -47,6 +31,24 @@ class AccountPresenter @Inject constructor(
|
|||||||
view?.openAccountDetailsView(studentWithSemesters)
|
view?.openAccountDetailsView(studentWithSemesters)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun loadData() {
|
||||||
|
flowWithResource { studentRepository.getSavedStudents(false) }
|
||||||
|
.onEach {
|
||||||
|
when (it.status) {
|
||||||
|
Status.LOADING -> Timber.i("Loading account data started")
|
||||||
|
Status.SUCCESS -> {
|
||||||
|
Timber.i("Loading account result: Success")
|
||||||
|
view?.updateData(createAccountItems(it.data!!))
|
||||||
|
}
|
||||||
|
Status.ERROR -> {
|
||||||
|
Timber.i("Loading account result: An exception occurred")
|
||||||
|
errorHandler.dispatch(it.error!!)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.launch("load")
|
||||||
|
}
|
||||||
|
|
||||||
private fun createAccountItems(items: List<StudentWithSemesters>): List<AccountItem<*>> {
|
private fun createAccountItems(items: List<StudentWithSemesters>): List<AccountItem<*>> {
|
||||||
return items.groupBy {
|
return items.groupBy {
|
||||||
Account("${it.student.userName} (${it.student.email})", it.student.isParent)
|
Account("${it.student.userName} (${it.student.email})", it.student.isParent)
|
||||||
@ -60,45 +62,4 @@ class AccountPresenter @Inject constructor(
|
|||||||
}
|
}
|
||||||
.flatten()
|
.flatten()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun loadData() {
|
|
||||||
flowWithResource { studentRepository.getSavedStudents(false) }
|
|
||||||
.onEach {
|
|
||||||
when (it.status) {
|
|
||||||
Status.LOADING -> {
|
|
||||||
Timber.i("Loading account data started")
|
|
||||||
view?.run {
|
|
||||||
showProgress(true)
|
|
||||||
showContent(false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Status.SUCCESS -> {
|
|
||||||
Timber.i("Loading account result: Success")
|
|
||||||
view?.updateData(createAccountItems(it.data!!))
|
|
||||||
view?.run {
|
|
||||||
showContent(true)
|
|
||||||
showErrorView(false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Status.ERROR -> {
|
|
||||||
Timber.i("Loading account result: An exception occurred")
|
|
||||||
errorHandler.dispatch(it.error!!)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.afterLoading { view?.showProgress(false) }
|
|
||||||
.launch()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun showErrorViewOnError(message: String, error: Throwable) {
|
|
||||||
view?.run {
|
|
||||||
if (isViewEmpty) {
|
|
||||||
lastError = error
|
|
||||||
setErrorDetails(message)
|
|
||||||
showErrorView(true)
|
|
||||||
showContent(false)
|
|
||||||
showProgress(false)
|
|
||||||
} else showError(message, error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -5,8 +5,6 @@ import io.github.wulkanowy.ui.base.BaseView
|
|||||||
|
|
||||||
interface AccountView : BaseView {
|
interface AccountView : BaseView {
|
||||||
|
|
||||||
val isViewEmpty: Boolean
|
|
||||||
|
|
||||||
fun initView()
|
fun initView()
|
||||||
|
|
||||||
fun updateData(data: List<AccountItem<*>>)
|
fun updateData(data: List<AccountItem<*>>)
|
||||||
@ -14,13 +12,4 @@ interface AccountView : BaseView {
|
|||||||
fun openLoginView()
|
fun openLoginView()
|
||||||
|
|
||||||
fun openAccountDetailsView(studentWithSemesters: StudentWithSemesters)
|
fun openAccountDetailsView(studentWithSemesters: StudentWithSemesters)
|
||||||
|
|
||||||
fun showErrorView(show: Boolean)
|
|
||||||
|
|
||||||
fun setErrorDetails(message: String)
|
|
||||||
|
|
||||||
fun showProgress(show: Boolean)
|
|
||||||
|
|
||||||
fun showContent(show: Boolean)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -7,6 +7,7 @@ import android.view.MenuItem
|
|||||||
import android.view.View
|
import android.view.View
|
||||||
import androidx.appcompat.app.AlertDialog
|
import androidx.appcompat.app.AlertDialog
|
||||||
import androidx.core.view.get
|
import androidx.core.view.get
|
||||||
|
import androidx.core.view.isVisible
|
||||||
import dagger.hilt.android.AndroidEntryPoint
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
import io.github.wulkanowy.R
|
import io.github.wulkanowy.R
|
||||||
import io.github.wulkanowy.data.db.entities.Student
|
import io.github.wulkanowy.data.db.entities.Student
|
||||||
@ -18,6 +19,7 @@ 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.studentinfo.StudentInfoFragment
|
import io.github.wulkanowy.ui.modules.studentinfo.StudentInfoFragment
|
||||||
import io.github.wulkanowy.ui.modules.studentinfo.StudentInfoView
|
import io.github.wulkanowy.ui.modules.studentinfo.StudentInfoView
|
||||||
|
import io.github.wulkanowy.utils.createNameInitialsDrawable
|
||||||
import io.github.wulkanowy.utils.nickOrName
|
import io.github.wulkanowy.utils.nickOrName
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
@ -88,8 +90,15 @@ class AccountDetailsFragment :
|
|||||||
|
|
||||||
override fun showAccountData(student: Student) {
|
override fun showAccountData(student: Student) {
|
||||||
with(binding) {
|
with(binding) {
|
||||||
|
accountDetailsCheck.isVisible = student.isCurrent
|
||||||
accountDetailsName.text = student.nickOrName
|
accountDetailsName.text = student.nickOrName
|
||||||
accountDetailsSchool.text = student.schoolName
|
accountDetailsSchool.text = student.schoolName
|
||||||
|
accountDetailsAvatar.setImageDrawable(
|
||||||
|
requireContext().createNameInitialsDrawable(
|
||||||
|
student.nickOrName,
|
||||||
|
student.avatarColor
|
||||||
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -21,7 +21,7 @@ class AccountDetailsPresenter @Inject constructor(
|
|||||||
private val syncManager: SyncManager
|
private val syncManager: SyncManager
|
||||||
) : BasePresenter<AccountDetailsView>(errorHandler, studentRepository) {
|
) : BasePresenter<AccountDetailsView>(errorHandler, studentRepository) {
|
||||||
|
|
||||||
private lateinit var studentWithSemesters: StudentWithSemesters
|
private var studentWithSemesters: StudentWithSemesters? = null
|
||||||
|
|
||||||
private lateinit var lastError: Throwable
|
private lateinit var lastError: Throwable
|
||||||
|
|
||||||
@ -69,10 +69,10 @@ class AccountDetailsPresenter @Inject constructor(
|
|||||||
}
|
}
|
||||||
Status.SUCCESS -> {
|
Status.SUCCESS -> {
|
||||||
Timber.i("Loading account details view result: Success")
|
Timber.i("Loading account details view result: Success")
|
||||||
studentWithSemesters = it.data!!
|
studentWithSemesters = it.data
|
||||||
view?.run {
|
view?.run {
|
||||||
showAccountData(studentWithSemesters.student)
|
showAccountData(studentWithSemesters!!.student)
|
||||||
enableSelectStudentButton(!studentWithSemesters.student.isCurrent)
|
enableSelectStudentButton(!studentWithSemesters!!.student.isCurrent)
|
||||||
showContent(true)
|
showContent(true)
|
||||||
showErrorView(false)
|
showErrorView(false)
|
||||||
}
|
}
|
||||||
@ -88,17 +88,23 @@ class AccountDetailsPresenter @Inject constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun onAccountEditSelected() {
|
fun onAccountEditSelected() {
|
||||||
view?.showAccountEditDetailsDialog(studentWithSemesters.student)
|
studentWithSemesters?.let {
|
||||||
|
view?.showAccountEditDetailsDialog(it.student)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onStudentInfoSelected(infoType: StudentInfoView.Type) {
|
fun onStudentInfoSelected(infoType: StudentInfoView.Type) {
|
||||||
view?.openStudentInfoView(infoType, studentWithSemesters)
|
studentWithSemesters?.let {
|
||||||
|
view?.openStudentInfoView(infoType, it)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onStudentSelect() {
|
fun onStudentSelect() {
|
||||||
Timber.i("Select student ${studentWithSemesters.student.id}")
|
if (studentWithSemesters == null) return
|
||||||
|
|
||||||
flowWithResource { studentRepository.switchStudent(studentWithSemesters) }
|
Timber.i("Select student ${studentWithSemesters!!.student.id}")
|
||||||
|
|
||||||
|
flowWithResource { studentRepository.switchStudent(studentWithSemesters!!) }
|
||||||
.onEach {
|
.onEach {
|
||||||
when (it.status) {
|
when (it.status) {
|
||||||
Status.LOADING -> Timber.i("Attempt to change a student")
|
Status.LOADING -> Timber.i("Attempt to change a student")
|
||||||
@ -122,8 +128,10 @@ class AccountDetailsPresenter @Inject constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun onLogoutConfirm() {
|
fun onLogoutConfirm() {
|
||||||
|
if (studentWithSemesters == null) return
|
||||||
|
|
||||||
flowWithResource {
|
flowWithResource {
|
||||||
val studentToLogout = studentWithSemesters.student
|
val studentToLogout = studentWithSemesters!!.student
|
||||||
|
|
||||||
studentRepository.logoutStudent(studentToLogout)
|
studentRepository.logoutStudent(studentToLogout)
|
||||||
val students = studentRepository.getSavedStudents(false)
|
val students = studentRepository.getSavedStudents(false)
|
||||||
@ -143,7 +151,7 @@ class AccountDetailsPresenter @Inject constructor(
|
|||||||
syncManager.stopSyncWorker()
|
syncManager.stopSyncWorker()
|
||||||
openClearLoginView()
|
openClearLoginView()
|
||||||
}
|
}
|
||||||
studentWithSemesters.student.isCurrent -> {
|
studentWithSemesters!!.student.isCurrent -> {
|
||||||
Timber.i("Logout result: Logout student and switch to another")
|
Timber.i("Logout result: Logout student and switch to another")
|
||||||
recreateMainView()
|
recreateMainView()
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,90 @@
|
|||||||
|
package io.github.wulkanowy.ui.modules.account.accountedit
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
|
import android.content.res.ColorStateList
|
||||||
|
import android.graphics.Color
|
||||||
|
import android.graphics.drawable.ColorDrawable
|
||||||
|
import android.graphics.drawable.Drawable
|
||||||
|
import android.graphics.drawable.GradientDrawable
|
||||||
|
import android.graphics.drawable.RippleDrawable
|
||||||
|
import android.graphics.drawable.StateListDrawable
|
||||||
|
import android.os.Build
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import androidx.core.view.isVisible
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import io.github.wulkanowy.databinding.ItemAccountEditColorBinding
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class AccountEditColorAdapter @Inject constructor() :
|
||||||
|
RecyclerView.Adapter<AccountEditColorAdapter.ViewHolder>() {
|
||||||
|
|
||||||
|
var items = listOf<Int>()
|
||||||
|
|
||||||
|
var selectedColor = 0
|
||||||
|
|
||||||
|
override fun getItemCount() = items.size
|
||||||
|
|
||||||
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = ViewHolder(
|
||||||
|
ItemAccountEditColorBinding.inflate(LayoutInflater.from(parent.context), parent, false)
|
||||||
|
)
|
||||||
|
|
||||||
|
@SuppressLint("RestrictedApi", "NewApi")
|
||||||
|
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
||||||
|
val item = items[position]
|
||||||
|
|
||||||
|
with(holder.binding) {
|
||||||
|
accountEditItemColor.setImageDrawable(GradientDrawable().apply {
|
||||||
|
shape = GradientDrawable.OVAL
|
||||||
|
setColor(item)
|
||||||
|
})
|
||||||
|
|
||||||
|
accountEditItemColorContainer.foreground = item.createForegroundDrawable()
|
||||||
|
accountEditCheck.isVisible = selectedColor == item
|
||||||
|
|
||||||
|
root.setOnClickListener {
|
||||||
|
val oldSelectedPosition = items.indexOf(selectedColor)
|
||||||
|
selectedColor = item
|
||||||
|
|
||||||
|
notifyItemChanged(oldSelectedPosition)
|
||||||
|
notifyItemChanged(position)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun Int.createForegroundDrawable(): Drawable =
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||||
|
val mask = GradientDrawable().apply {
|
||||||
|
shape = GradientDrawable.OVAL
|
||||||
|
setColor(Color.BLACK)
|
||||||
|
}
|
||||||
|
RippleDrawable(ColorStateList.valueOf(this.rippleColor), null, mask)
|
||||||
|
} else {
|
||||||
|
val foreground = StateListDrawable().apply {
|
||||||
|
alpha = 80
|
||||||
|
setEnterFadeDuration(250)
|
||||||
|
setExitFadeDuration(250)
|
||||||
|
}
|
||||||
|
|
||||||
|
val mask = GradientDrawable().apply {
|
||||||
|
shape = GradientDrawable.OVAL
|
||||||
|
setColor(this@createForegroundDrawable.rippleColor)
|
||||||
|
}
|
||||||
|
|
||||||
|
foreground.apply {
|
||||||
|
addState(intArrayOf(android.R.attr.state_pressed), mask)
|
||||||
|
addState(intArrayOf(), ColorDrawable(Color.TRANSPARENT))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private inline val Int.rippleColor: Int
|
||||||
|
get() {
|
||||||
|
val hsv = FloatArray(3)
|
||||||
|
Color.colorToHSV(this, hsv)
|
||||||
|
hsv[2] = hsv[2] * 0.5f
|
||||||
|
return Color.HSVToColor(hsv)
|
||||||
|
}
|
||||||
|
|
||||||
|
class ViewHolder(val binding: ItemAccountEditColorBinding) :
|
||||||
|
RecyclerView.ViewHolder(binding.root)
|
||||||
|
}
|
@ -4,6 +4,7 @@ import android.os.Bundle
|
|||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
|
import androidx.recyclerview.widget.GridLayoutManager
|
||||||
import dagger.hilt.android.AndroidEntryPoint
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
import io.github.wulkanowy.data.db.entities.Student
|
import io.github.wulkanowy.data.db.entities.Student
|
||||||
import io.github.wulkanowy.databinding.DialogAccountEditBinding
|
import io.github.wulkanowy.databinding.DialogAccountEditBinding
|
||||||
@ -16,6 +17,9 @@ class AccountEditDialog : BaseDialogFragment<DialogAccountEditBinding>(), Accoun
|
|||||||
@Inject
|
@Inject
|
||||||
lateinit var presenter: AccountEditPresenter
|
lateinit var presenter: AccountEditPresenter
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
lateinit var accountEditColorAdapter: AccountEditColorAdapter
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
||||||
private const val ARGUMENT_KEY = "student_with_semesters"
|
private const val ARGUMENT_KEY = "student_with_semesters"
|
||||||
@ -48,8 +52,30 @@ class AccountEditDialog : BaseDialogFragment<DialogAccountEditBinding>(), Accoun
|
|||||||
with(binding) {
|
with(binding) {
|
||||||
accountEditDetailsCancel.setOnClickListener { dismiss() }
|
accountEditDetailsCancel.setOnClickListener { dismiss() }
|
||||||
accountEditDetailsSave.setOnClickListener {
|
accountEditDetailsSave.setOnClickListener {
|
||||||
presenter.changeStudentNick(binding.accountEditDetailsNickText.text.toString())
|
presenter.changeStudentNickAndAvatar(
|
||||||
|
binding.accountEditDetailsNickText.text.toString(),
|
||||||
|
accountEditColorAdapter.selectedColor
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
with(binding.accountEditColors) {
|
||||||
|
layoutManager = GridLayoutManager(context, 4)
|
||||||
|
adapter = accountEditColorAdapter
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun updateSelectedColorData(color: Int) {
|
||||||
|
with(accountEditColorAdapter) {
|
||||||
|
selectedColor = color
|
||||||
|
notifyDataSetChanged()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun updateColorsData(colors: List<Int>) {
|
||||||
|
with(accountEditColorAdapter) {
|
||||||
|
items = colors
|
||||||
|
notifyDataSetChanged()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,10 +2,11 @@ package io.github.wulkanowy.ui.modules.account.accountedit
|
|||||||
|
|
||||||
import io.github.wulkanowy.data.Status
|
import io.github.wulkanowy.data.Status
|
||||||
import io.github.wulkanowy.data.db.entities.Student
|
import io.github.wulkanowy.data.db.entities.Student
|
||||||
import io.github.wulkanowy.data.db.entities.StudentNick
|
import io.github.wulkanowy.data.db.entities.StudentNickAndAvatar
|
||||||
import io.github.wulkanowy.data.repositories.StudentRepository
|
import io.github.wulkanowy.data.repositories.StudentRepository
|
||||||
import io.github.wulkanowy.ui.base.BasePresenter
|
import io.github.wulkanowy.ui.base.BasePresenter
|
||||||
import io.github.wulkanowy.ui.base.ErrorHandler
|
import io.github.wulkanowy.ui.base.ErrorHandler
|
||||||
|
import io.github.wulkanowy.utils.AppInfo
|
||||||
import io.github.wulkanowy.utils.afterLoading
|
import io.github.wulkanowy.utils.afterLoading
|
||||||
import io.github.wulkanowy.utils.flowWithResource
|
import io.github.wulkanowy.utils.flowWithResource
|
||||||
import kotlinx.coroutines.flow.onEach
|
import kotlinx.coroutines.flow.onEach
|
||||||
@ -13,12 +14,15 @@ import timber.log.Timber
|
|||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class AccountEditPresenter @Inject constructor(
|
class AccountEditPresenter @Inject constructor(
|
||||||
|
private val appInfo: AppInfo,
|
||||||
errorHandler: ErrorHandler,
|
errorHandler: ErrorHandler,
|
||||||
studentRepository: StudentRepository
|
studentRepository: StudentRepository
|
||||||
) : BasePresenter<AccountEditView>(errorHandler, studentRepository) {
|
) : BasePresenter<AccountEditView>(errorHandler, studentRepository) {
|
||||||
|
|
||||||
lateinit var student: Student
|
lateinit var student: Student
|
||||||
|
|
||||||
|
private val colors = appInfo.defaultColorsForAvatar.map { it.toInt() }
|
||||||
|
|
||||||
fun onAttachView(view: AccountEditView, student: Student) {
|
fun onAttachView(view: AccountEditView, student: Student) {
|
||||||
super.onAttachView(view)
|
super.onAttachView(view)
|
||||||
this.student = student
|
this.student = student
|
||||||
@ -28,27 +32,49 @@ class AccountEditPresenter @Inject constructor(
|
|||||||
showCurrentNick(student.nick.trim())
|
showCurrentNick(student.nick.trim())
|
||||||
}
|
}
|
||||||
Timber.i("Account edit dialog view was initialized")
|
Timber.i("Account edit dialog view was initialized")
|
||||||
|
loadData()
|
||||||
|
|
||||||
|
view.updateColorsData(colors)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun changeStudentNick(nick: String) {
|
private fun loadData() {
|
||||||
|
flowWithResource {
|
||||||
|
studentRepository.getStudentById(student.id, false).avatarColor
|
||||||
|
}.onEach { resource ->
|
||||||
|
when (resource.status) {
|
||||||
|
Status.LOADING -> Timber.i("Attempt to load student")
|
||||||
|
Status.SUCCESS -> {
|
||||||
|
view?.updateSelectedColorData(resource.data?.toInt()!!)
|
||||||
|
Timber.i("Attempt to load student: Success")
|
||||||
|
}
|
||||||
|
Status.ERROR -> {
|
||||||
|
Timber.i("Attempt to load student: An exception occurred")
|
||||||
|
errorHandler.dispatch(resource.error!!)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}.launch("load_data")
|
||||||
|
}
|
||||||
|
|
||||||
|
fun changeStudentNickAndAvatar(nick: String, avatarColor: Int) {
|
||||||
flowWithResource {
|
flowWithResource {
|
||||||
val studentNick =
|
val studentNick =
|
||||||
StudentNick(nick = nick.trim()).apply { id = student.id }
|
StudentNickAndAvatar(nick = nick.trim(), avatarColor = avatarColor.toLong())
|
||||||
studentRepository.updateStudentNick(studentNick)
|
.apply { id = student.id }
|
||||||
|
studentRepository.updateStudentNickAndAvatar(studentNick)
|
||||||
}.onEach {
|
}.onEach {
|
||||||
when (it.status) {
|
when (it.status) {
|
||||||
Status.LOADING -> Timber.i("Attempt to change a student nick")
|
Status.LOADING -> Timber.i("Attempt to change a student nick and avatar")
|
||||||
Status.SUCCESS -> {
|
Status.SUCCESS -> {
|
||||||
Timber.i("Change a student nick result: Success")
|
Timber.i("Change a student nick and avatar result: Success")
|
||||||
view?.recreateMainView()
|
view?.recreateMainView()
|
||||||
}
|
}
|
||||||
Status.ERROR -> {
|
Status.ERROR -> {
|
||||||
Timber.i("Change a student result: An exception occurred")
|
Timber.i("Change a student nick and avatar result: An exception occurred")
|
||||||
errorHandler.dispatch(it.error!!)
|
errorHandler.dispatch(it.error!!)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.afterLoading { view?.popView() }
|
.afterLoading { view?.popView() }
|
||||||
.launch()
|
.launch("update_student")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -11,4 +11,8 @@ interface AccountEditView : BaseView {
|
|||||||
fun recreateMainView()
|
fun recreateMainView()
|
||||||
|
|
||||||
fun showCurrentNick(nick: String)
|
fun showCurrentNick(nick: String)
|
||||||
|
|
||||||
|
fun updateSelectedColorData(color: Int)
|
||||||
|
|
||||||
|
fun updateColorsData(colors: List<Int>)
|
||||||
}
|
}
|
||||||
|
@ -6,6 +6,7 @@ import android.view.View
|
|||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
import dagger.hilt.android.AndroidEntryPoint
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
|
import io.github.wulkanowy.data.db.entities.StudentWithSemesters
|
||||||
import io.github.wulkanowy.databinding.DialogAccountQuickBinding
|
import io.github.wulkanowy.databinding.DialogAccountQuickBinding
|
||||||
import io.github.wulkanowy.ui.base.BaseDialogFragment
|
import io.github.wulkanowy.ui.base.BaseDialogFragment
|
||||||
import io.github.wulkanowy.ui.modules.account.AccountAdapter
|
import io.github.wulkanowy.ui.modules.account.AccountAdapter
|
||||||
@ -24,7 +25,15 @@ class AccountQuickDialog : BaseDialogFragment<DialogAccountQuickBinding>(), Acco
|
|||||||
lateinit var presenter: AccountQuickPresenter
|
lateinit var presenter: AccountQuickPresenter
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
fun newInstance() = AccountQuickDialog()
|
|
||||||
|
private const val STUDENTS_ARGUMENT_KEY = "students"
|
||||||
|
|
||||||
|
fun newInstance(studentsWithSemesters: List<StudentWithSemesters>) =
|
||||||
|
AccountQuickDialog().apply {
|
||||||
|
arguments = Bundle().apply {
|
||||||
|
putSerializable(STUDENTS_ARGUMENT_KEY, studentsWithSemesters.toTypedArray())
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
@ -38,8 +47,12 @@ class AccountQuickDialog : BaseDialogFragment<DialogAccountQuickBinding>(), Acco
|
|||||||
savedInstanceState: Bundle?
|
savedInstanceState: Bundle?
|
||||||
) = DialogAccountQuickBinding.inflate(inflater).apply { binding = this }.root
|
) = DialogAccountQuickBinding.inflate(inflater).apply { binding = this }.root
|
||||||
|
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
presenter.onAttachView(this)
|
val studentsWithSemesters =
|
||||||
|
(requireArguments()[STUDENTS_ARGUMENT_KEY] as Array<StudentWithSemesters>).toList()
|
||||||
|
|
||||||
|
presenter.onAttachView(this, studentsWithSemesters)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun initView() {
|
override fun initView() {
|
||||||
|
@ -17,11 +17,15 @@ class AccountQuickPresenter @Inject constructor(
|
|||||||
studentRepository: StudentRepository
|
studentRepository: StudentRepository
|
||||||
) : BasePresenter<AccountQuickView>(errorHandler, studentRepository) {
|
) : BasePresenter<AccountQuickView>(errorHandler, studentRepository) {
|
||||||
|
|
||||||
override fun onAttachView(view: AccountQuickView) {
|
private lateinit var studentsWithSemesters: List<StudentWithSemesters>
|
||||||
|
|
||||||
|
fun onAttachView(view: AccountQuickView, studentsWithSemesters: List<StudentWithSemesters>) {
|
||||||
super.onAttachView(view)
|
super.onAttachView(view)
|
||||||
|
this.studentsWithSemesters = studentsWithSemesters
|
||||||
|
|
||||||
view.initView()
|
view.initView()
|
||||||
Timber.i("Account quick dialog view was initialized")
|
Timber.i("Account quick dialog view was initialized")
|
||||||
loadData()
|
view.updateData(createAccountItems(studentsWithSemesters))
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onManagerSelected() {
|
fun onManagerSelected() {
|
||||||
@ -57,22 +61,6 @@ class AccountQuickPresenter @Inject constructor(
|
|||||||
.launch("switch")
|
.launch("switch")
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun loadData() {
|
|
||||||
flowWithResource { studentRepository.getSavedStudents(false) }.onEach {
|
|
||||||
when (it.status) {
|
|
||||||
Status.LOADING -> Timber.i("Loading account data started")
|
|
||||||
Status.SUCCESS -> {
|
|
||||||
Timber.i("Loading account result: Success")
|
|
||||||
view?.updateData(createAccountItems(it.data!!))
|
|
||||||
}
|
|
||||||
Status.ERROR -> {
|
|
||||||
Timber.i("Loading account result: An exception occurred")
|
|
||||||
errorHandler.dispatch(it.error!!)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}.launch()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun createAccountItems(items: List<StudentWithSemesters>) = items.map {
|
private fun createAccountItems(items: List<StudentWithSemesters>) = items.map {
|
||||||
AccountItem(it, AccountItem.ViewType.ITEM)
|
AccountItem(it, AccountItem.ViewType.ITEM)
|
||||||
}
|
}
|
||||||
|
@ -28,6 +28,8 @@ import com.ncapdevi.fragnav.FragNavController
|
|||||||
import com.ncapdevi.fragnav.FragNavController.Companion.HIDE
|
import com.ncapdevi.fragnav.FragNavController.Companion.HIDE
|
||||||
import dagger.hilt.android.AndroidEntryPoint
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
import io.github.wulkanowy.R
|
import io.github.wulkanowy.R
|
||||||
|
import io.github.wulkanowy.data.db.entities.Student
|
||||||
|
import io.github.wulkanowy.data.db.entities.StudentWithSemesters
|
||||||
import io.github.wulkanowy.databinding.ActivityMainBinding
|
import io.github.wulkanowy.databinding.ActivityMainBinding
|
||||||
import io.github.wulkanowy.ui.base.BaseActivity
|
import io.github.wulkanowy.ui.base.BaseActivity
|
||||||
import io.github.wulkanowy.ui.modules.account.accountquick.AccountQuickDialog
|
import io.github.wulkanowy.ui.modules.account.accountquick.AccountQuickDialog
|
||||||
@ -43,8 +45,10 @@ import io.github.wulkanowy.ui.modules.timetable.TimetableFragment
|
|||||||
import io.github.wulkanowy.utils.AnalyticsHelper
|
import io.github.wulkanowy.utils.AnalyticsHelper
|
||||||
import io.github.wulkanowy.utils.AppInfo
|
import io.github.wulkanowy.utils.AppInfo
|
||||||
import io.github.wulkanowy.utils.UpdateHelper
|
import io.github.wulkanowy.utils.UpdateHelper
|
||||||
|
import io.github.wulkanowy.utils.createNameInitialsDrawable
|
||||||
import io.github.wulkanowy.utils.dpToPx
|
import io.github.wulkanowy.utils.dpToPx
|
||||||
import io.github.wulkanowy.utils.getThemeAttrColor
|
import io.github.wulkanowy.utils.getThemeAttrColor
|
||||||
|
import io.github.wulkanowy.utils.nickOrName
|
||||||
import io.github.wulkanowy.utils.safelyPopFragments
|
import io.github.wulkanowy.utils.safelyPopFragments
|
||||||
import io.github.wulkanowy.utils.setOnViewChangeListener
|
import io.github.wulkanowy.utils.setOnViewChangeListener
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
@ -65,6 +69,8 @@ class MainActivity : BaseActivity<MainPresenter, ActivityMainBinding>(), MainVie
|
|||||||
@Inject
|
@Inject
|
||||||
lateinit var appInfo: AppInfo
|
lateinit var appInfo: AppInfo
|
||||||
|
|
||||||
|
private var accountMenu: MenuItem? = null
|
||||||
|
|
||||||
private val overlayProvider by lazy { ElevationOverlayProvider(this) }
|
private val overlayProvider by lazy { ElevationOverlayProvider(this) }
|
||||||
|
|
||||||
private val navController =
|
private val navController =
|
||||||
@ -192,6 +198,9 @@ class MainActivity : BaseActivity<MainPresenter, ActivityMainBinding>(), MainVie
|
|||||||
|
|
||||||
override fun onCreateOptionsMenu(menu: Menu?): Boolean {
|
override fun onCreateOptionsMenu(menu: Menu?): Boolean {
|
||||||
menuInflater.inflate(R.menu.action_menu_main, menu)
|
menuInflater.inflate(R.menu.action_menu_main, menu)
|
||||||
|
accountMenu = menu?.findItem(R.id.mainMenuAccount)
|
||||||
|
|
||||||
|
presenter.onActionMenuCreated()
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -288,8 +297,8 @@ class MainActivity : BaseActivity<MainPresenter, ActivityMainBinding>(), MainVie
|
|||||||
supportActionBar?.setDisplayHomeAsUpEnabled(show)
|
supportActionBar?.setDisplayHomeAsUpEnabled(show)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun showAccountPicker() {
|
override fun showAccountPicker(studentWithSemesters: List<StudentWithSemesters>) {
|
||||||
navController.showDialogFragment(AccountQuickDialog.newInstance())
|
navController.showDialogFragment(AccountQuickDialog.newInstance(studentWithSemesters))
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun showActionBarElevation(show: Boolean) {
|
override fun showActionBarElevation(show: Boolean) {
|
||||||
@ -323,6 +332,13 @@ class MainActivity : BaseActivity<MainPresenter, ActivityMainBinding>(), MainVie
|
|||||||
presenter.onBackPressed { super.onBackPressed() }
|
presenter.onBackPressed { super.onBackPressed() }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun showStudentAvatar(student: Student) {
|
||||||
|
accountMenu?.run {
|
||||||
|
icon = createNameInitialsDrawable(student.nickOrName, student.avatarColor, 0.44f)
|
||||||
|
title = getString(R.string.main_account_picker)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun onSaveInstanceState(outState: Bundle) {
|
override fun onSaveInstanceState(outState: Bundle) {
|
||||||
super.onSaveInstanceState(outState)
|
super.onSaveInstanceState(outState)
|
||||||
navController.onSaveInstanceState(outState)
|
navController.onSaveInstanceState(outState)
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
package io.github.wulkanowy.ui.modules.main
|
package io.github.wulkanowy.ui.modules.main
|
||||||
|
|
||||||
|
import io.github.wulkanowy.data.Status
|
||||||
|
import io.github.wulkanowy.data.db.entities.StudentWithSemesters
|
||||||
import io.github.wulkanowy.data.repositories.PreferencesRepository
|
import io.github.wulkanowy.data.repositories.PreferencesRepository
|
||||||
import io.github.wulkanowy.data.repositories.StudentRepository
|
import io.github.wulkanowy.data.repositories.StudentRepository
|
||||||
import io.github.wulkanowy.services.sync.SyncManager
|
import io.github.wulkanowy.services.sync.SyncManager
|
||||||
@ -9,6 +11,8 @@ import io.github.wulkanowy.ui.modules.main.MainView.Section.GRADE
|
|||||||
import io.github.wulkanowy.ui.modules.main.MainView.Section.MESSAGE
|
import io.github.wulkanowy.ui.modules.main.MainView.Section.MESSAGE
|
||||||
import io.github.wulkanowy.ui.modules.main.MainView.Section.SCHOOL
|
import io.github.wulkanowy.ui.modules.main.MainView.Section.SCHOOL
|
||||||
import io.github.wulkanowy.utils.AnalyticsHelper
|
import io.github.wulkanowy.utils.AnalyticsHelper
|
||||||
|
import io.github.wulkanowy.utils.flowWithResource
|
||||||
|
import kotlinx.coroutines.flow.onEach
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
@ -17,9 +21,11 @@ class MainPresenter @Inject constructor(
|
|||||||
studentRepository: StudentRepository,
|
studentRepository: StudentRepository,
|
||||||
private val prefRepository: PreferencesRepository,
|
private val prefRepository: PreferencesRepository,
|
||||||
private val syncManager: SyncManager,
|
private val syncManager: SyncManager,
|
||||||
private val analytics: AnalyticsHelper
|
private val analytics: AnalyticsHelper,
|
||||||
) : BasePresenter<MainView>(errorHandler, studentRepository) {
|
) : BasePresenter<MainView>(errorHandler, studentRepository) {
|
||||||
|
|
||||||
|
var studentsWitSemesters: List<StudentWithSemesters>? = null
|
||||||
|
|
||||||
fun onAttachView(view: MainView, initMenu: MainView.Section?) {
|
fun onAttachView(view: MainView, initMenu: MainView.Section?) {
|
||||||
super.onAttachView(view)
|
super.onAttachView(view)
|
||||||
view.apply {
|
view.apply {
|
||||||
@ -35,6 +41,28 @@ class MainPresenter @Inject constructor(
|
|||||||
analytics.logEvent("app_open", "destination" to initMenu?.name)
|
analytics.logEvent("app_open", "destination" to initMenu?.name)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun onActionMenuCreated() {
|
||||||
|
if (!studentsWitSemesters.isNullOrEmpty()) {
|
||||||
|
showCurrentStudentAvatar()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
flowWithResource { studentRepository.getSavedStudents(false) }
|
||||||
|
.onEach { resource ->
|
||||||
|
when (resource.status) {
|
||||||
|
Status.LOADING -> Timber.i("Loading student avatar data started")
|
||||||
|
Status.SUCCESS -> {
|
||||||
|
studentsWitSemesters = resource.data
|
||||||
|
showCurrentStudentAvatar()
|
||||||
|
}
|
||||||
|
Status.ERROR -> {
|
||||||
|
Timber.i("Loading student avatar result: An exception occurred")
|
||||||
|
errorHandler.dispatch(resource.error!!)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}.launch("avatar")
|
||||||
|
}
|
||||||
|
|
||||||
fun onViewChange(section: MainView.Section?) {
|
fun onViewChange(section: MainView.Section?) {
|
||||||
view?.apply {
|
view?.apply {
|
||||||
showActionBarElevation(section != GRADE && section != MESSAGE && section != SCHOOL)
|
showActionBarElevation(section != GRADE && section != MESSAGE && section != SCHOOL)
|
||||||
@ -48,8 +76,10 @@ class MainPresenter @Inject constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun onAccountManagerSelected(): Boolean {
|
fun onAccountManagerSelected(): Boolean {
|
||||||
|
if (studentsWitSemesters.isNullOrEmpty()) return true
|
||||||
|
|
||||||
Timber.i("Select account manager")
|
Timber.i("Select account manager")
|
||||||
view?.showAccountPicker()
|
view?.showAccountPicker(studentsWitSemesters!!)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -81,6 +111,13 @@ class MainPresenter @Inject constructor(
|
|||||||
} == true
|
} == true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun showCurrentStudentAvatar() {
|
||||||
|
val currentStudent =
|
||||||
|
studentsWitSemesters!!.single { it.student.isCurrent }.student
|
||||||
|
|
||||||
|
view?.showStudentAvatar(currentStudent)
|
||||||
|
}
|
||||||
|
|
||||||
private fun getProperViewIndexes(initMenu: MainView.Section?): Pair<Int, Int> {
|
private fun getProperViewIndexes(initMenu: MainView.Section?): Pair<Int, Int> {
|
||||||
return when (initMenu?.id) {
|
return when (initMenu?.id) {
|
||||||
in 0..3 -> initMenu!!.id to -1
|
in 0..3 -> initMenu!!.id to -1
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
package io.github.wulkanowy.ui.modules.main
|
package io.github.wulkanowy.ui.modules.main
|
||||||
|
|
||||||
|
import io.github.wulkanowy.data.db.entities.Student
|
||||||
|
import io.github.wulkanowy.data.db.entities.StudentWithSemesters
|
||||||
import io.github.wulkanowy.ui.base.BaseView
|
import io.github.wulkanowy.ui.base.BaseView
|
||||||
|
|
||||||
interface MainView : BaseView {
|
interface MainView : BaseView {
|
||||||
@ -22,7 +24,7 @@ interface MainView : BaseView {
|
|||||||
|
|
||||||
fun showHomeArrow(show: Boolean)
|
fun showHomeArrow(show: Boolean)
|
||||||
|
|
||||||
fun showAccountPicker()
|
fun showAccountPicker(studentWithSemesters: List<StudentWithSemesters>)
|
||||||
|
|
||||||
fun showActionBarElevation(show: Boolean)
|
fun showActionBarElevation(show: Boolean)
|
||||||
|
|
||||||
@ -36,6 +38,8 @@ interface MainView : BaseView {
|
|||||||
|
|
||||||
fun popView(depth: Int = 1)
|
fun popView(depth: Int = 1)
|
||||||
|
|
||||||
|
fun showStudentAvatar(student: Student)
|
||||||
|
|
||||||
interface MainChildView {
|
interface MainChildView {
|
||||||
|
|
||||||
fun onFragmentReselected()
|
fun onFragmentReselected()
|
||||||
|
@ -35,8 +35,8 @@ open class AppInfo @Inject constructor() {
|
|||||||
open val systemLanguage: String
|
open val systemLanguage: String
|
||||||
get() = Resources.getSystem().configuration.locale.language
|
get() = Resources.getSystem().configuration.locale.language
|
||||||
|
|
||||||
open val defaultColorsForAvatar = listOf(
|
val defaultColorsForAvatar = listOf(
|
||||||
0xe57373, 0xf06292, 0xba68c8, 0x9575cd, 0x7986cb, 0x64b5f6, 0x4fc3f7, 0x4dd0e1, 0x4db6ac,
|
0xd32f2f, 0xE64A19, 0xFFA000, 0xAFB42B, 0x689F38, 0x388E3C, 0x00796B, 0x0097A7,
|
||||||
0x81c784, 0xaed581, 0xff8a65, 0xd4e157, 0xffd54f, 0xffb74d, 0xa1887f, 0x90a4ae
|
0x1976D2, 0x3647b5, 0x6236c9, 0x9225c1, 0xC2185B, 0x616161, 0x455A64, 0x7a5348
|
||||||
)
|
).map { (it and 0x00ffffff or (255 shl 24)).toLong() }
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,15 @@
|
|||||||
package io.github.wulkanowy.utils
|
package io.github.wulkanowy.utils
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
|
import android.graphics.Bitmap
|
||||||
|
import android.graphics.Color
|
||||||
|
import android.graphics.Paint
|
||||||
|
import android.graphics.Rect
|
||||||
|
import android.graphics.Typeface
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
|
import android.text.TextPaint
|
||||||
import android.util.DisplayMetrics.DENSITY_DEFAULT
|
import android.util.DisplayMetrics.DENSITY_DEFAULT
|
||||||
import androidx.annotation.AttrRes
|
import androidx.annotation.AttrRes
|
||||||
import androidx.annotation.ColorInt
|
import androidx.annotation.ColorInt
|
||||||
@ -10,6 +17,9 @@ import androidx.annotation.ColorRes
|
|||||||
import androidx.annotation.DrawableRes
|
import androidx.annotation.DrawableRes
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.core.graphics.ColorUtils
|
import androidx.core.graphics.ColorUtils
|
||||||
|
import androidx.core.graphics.applyCanvas
|
||||||
|
import androidx.core.graphics.drawable.RoundedBitmapDrawable
|
||||||
|
import androidx.core.graphics.drawable.RoundedBitmapDrawableFactory
|
||||||
import io.github.wulkanowy.BuildConfig.APPLICATION_ID
|
import io.github.wulkanowy.BuildConfig.APPLICATION_ID
|
||||||
|
|
||||||
@ColorInt
|
@ColorInt
|
||||||
@ -30,7 +40,8 @@ fun Context.getThemeAttrColor(@AttrRes colorAttr: Int, alpha: Int): Int {
|
|||||||
@ColorInt
|
@ColorInt
|
||||||
fun Context.getCompatColor(@ColorRes colorRes: Int) = ContextCompat.getColor(this, colorRes)
|
fun Context.getCompatColor(@ColorRes colorRes: Int) = ContextCompat.getColor(this, colorRes)
|
||||||
|
|
||||||
fun Context.getCompatDrawable(@DrawableRes drawableRes: Int) = ContextCompat.getDrawable(this, drawableRes)
|
fun Context.getCompatDrawable(@DrawableRes drawableRes: Int) =
|
||||||
|
ContextCompat.getDrawable(this, drawableRes)
|
||||||
|
|
||||||
fun Context.openInternetBrowser(uri: String, onActivityNotFound: (uri: String) -> Unit) {
|
fun Context.openInternetBrowser(uri: String, onActivityNotFound: (uri: String) -> Unit) {
|
||||||
Intent.parseUri(uri, 0).let {
|
Intent.parseUri(uri, 0).let {
|
||||||
@ -45,7 +56,13 @@ fun Context.openAppInMarket(onActivityNotFound: (uri: String) -> Unit) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun Context.openEmailClient(chooserTitle: String, email: String, subject: String, body: String, onActivityNotFound: () -> Unit = {}) {
|
fun Context.openEmailClient(
|
||||||
|
chooserTitle: String,
|
||||||
|
email: String,
|
||||||
|
subject: String,
|
||||||
|
body: String,
|
||||||
|
onActivityNotFound: () -> Unit = {}
|
||||||
|
) {
|
||||||
val intent = Intent(Intent.ACTION_SENDTO, Uri.parse("mailto:")).apply {
|
val intent = Intent(Intent.ACTION_SENDTO, Uri.parse("mailto:")).apply {
|
||||||
putExtra(Intent.EXTRA_EMAIL, arrayOf(email))
|
putExtra(Intent.EXTRA_EMAIL, arrayOf(email))
|
||||||
putExtra(Intent.EXTRA_SUBJECT, subject)
|
putExtra(Intent.EXTRA_SUBJECT, subject)
|
||||||
@ -85,3 +102,39 @@ fun Context.shareText(text: String, subject: String?) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun Context.dpToPx(dp: Float) = dp * resources.displayMetrics.densityDpi / DENSITY_DEFAULT
|
fun Context.dpToPx(dp: Float) = dp * resources.displayMetrics.densityDpi / DENSITY_DEFAULT
|
||||||
|
|
||||||
|
@SuppressLint("DefaultLocale")
|
||||||
|
fun Context.createNameInitialsDrawable(
|
||||||
|
text: String,
|
||||||
|
backgroundColor: Long,
|
||||||
|
scaleFactory: Float = 1f
|
||||||
|
): RoundedBitmapDrawable {
|
||||||
|
val words = text.split(" ")
|
||||||
|
val firstCharFirstWord = words.getOrNull(0)?.firstOrNull() ?: ""
|
||||||
|
val firstCharSecondWord = words.getOrNull(1)?.firstOrNull() ?: ""
|
||||||
|
|
||||||
|
val initials = "$firstCharFirstWord$firstCharSecondWord".toUpperCase()
|
||||||
|
|
||||||
|
val bounds = Rect()
|
||||||
|
val dimension = this.dpToPx(64f * scaleFactory).toInt()
|
||||||
|
val textPaint = TextPaint().apply {
|
||||||
|
typeface = Typeface.SANS_SERIF
|
||||||
|
color = Color.WHITE
|
||||||
|
textAlign = Paint.Align.CENTER
|
||||||
|
isAntiAlias = true
|
||||||
|
textSize = this@createNameInitialsDrawable.dpToPx(30f * scaleFactory)
|
||||||
|
getTextBounds(initials, 0, initials.length, bounds)
|
||||||
|
}
|
||||||
|
|
||||||
|
val xCoordinate = (dimension / 2).toFloat()
|
||||||
|
val yCoordinate = (dimension / 2 + (bounds.bottom - bounds.top) / 2).toFloat()
|
||||||
|
|
||||||
|
val bitmap = Bitmap.createBitmap(dimension, dimension, Bitmap.Config.ARGB_8888)
|
||||||
|
.applyCanvas {
|
||||||
|
drawColor(backgroundColor.toInt())
|
||||||
|
drawText(initials, 0, initials.length, xCoordinate, yCoordinate, textPaint)
|
||||||
|
}
|
||||||
|
|
||||||
|
return RoundedBitmapDrawableFactory.create(this.resources, bitmap)
|
||||||
|
.apply { isCircular = true }
|
||||||
|
}
|
||||||
|
9
app/src/main/res/drawable/ic_all_round_check.xml
Normal file
9
app/src/main/res/drawable/ic_all_round_check.xml
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24">
|
||||||
|
<path
|
||||||
|
android:fillColor="@android:color/white"
|
||||||
|
android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM10,17l-5,-5 1.41,-1.41L10,14.17l7.59,-7.59L19,8l-9,9z" />
|
||||||
|
</vector>
|
@ -1,6 +1,10 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<ScrollView 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="wrap_content"
|
||||||
|
android:layout_height="wrap_content">
|
||||||
|
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content">
|
android:layout_height="wrap_content">
|
||||||
|
|
||||||
@ -36,7 +40,6 @@
|
|||||||
android:layout_marginEnd="24dp"
|
android:layout_marginEnd="24dp"
|
||||||
android:hint="@string/account_edit_nick_hint"
|
android:hint="@string/account_edit_nick_hint"
|
||||||
app:endIconMode="clear_text"
|
app:endIconMode="clear_text"
|
||||||
app:errorEnabled="true"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toBottomOf="@id/accountEditDetailsHeader">
|
app:layout_constraintTop_toBottomOf="@id/accountEditDetailsHeader">
|
||||||
@ -51,6 +54,38 @@
|
|||||||
android:maxLength="20" />
|
android:maxLength="20" />
|
||||||
</com.google.android.material.textfield.TextInputLayout>
|
</com.google.android.material.textfield.TextInputLayout>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/accountEditDetailsSecondHeader"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="24dp"
|
||||||
|
android:layout_marginTop="28dp"
|
||||||
|
android:layout_marginEnd="24dp"
|
||||||
|
android:text="Wybierz kolor avatara"
|
||||||
|
android:textSize="21sp"
|
||||||
|
android:textStyle="bold"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintHorizontal_bias="0"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/accountEditDetailsNick" />
|
||||||
|
|
||||||
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
|
android:id="@+id/account_edit_colors"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="24dp"
|
||||||
|
android:layout_marginTop="14dp"
|
||||||
|
android:layout_marginEnd="24dp"
|
||||||
|
android:overScrollMode="never"
|
||||||
|
android:scrollbars="none"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/accountEditDetailsSecondHeader"
|
||||||
|
tools:itemCount="12"
|
||||||
|
tools:layoutManager="androidx.recyclerview.widget.GridLayoutManager"
|
||||||
|
tools:listitem="@layout/item_account_edit_color"
|
||||||
|
tools:spanCount="4" />
|
||||||
|
|
||||||
<com.google.android.material.button.MaterialButton
|
<com.google.android.material.button.MaterialButton
|
||||||
android:id="@+id/accountEditDetailsSave"
|
android:id="@+id/accountEditDetailsSave"
|
||||||
style="@style/Widget.MaterialComponents.Button.TextButton.Dialog"
|
style="@style/Widget.MaterialComponents.Button.TextButton.Dialog"
|
||||||
@ -66,7 +101,7 @@
|
|||||||
android:text="@string/all_save"
|
android:text="@string/all_save"
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintTop_toBottomOf="@id/accountEditDetailsNick" />
|
app:layout_constraintTop_toBottomOf="@id/account_edit_colors" />
|
||||||
|
|
||||||
<com.google.android.material.button.MaterialButton
|
<com.google.android.material.button.MaterialButton
|
||||||
android:id="@+id/accountEditDetailsCancel"
|
android:id="@+id/accountEditDetailsCancel"
|
||||||
@ -83,5 +118,6 @@
|
|||||||
android:text="@android:string/cancel"
|
android:text="@android:string/cancel"
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
app:layout_constraintEnd_toStartOf="@id/accountEditDetailsSave"
|
app:layout_constraintEnd_toStartOf="@id/accountEditDetailsSave"
|
||||||
app:layout_constraintTop_toBottomOf="@id/accountEditDetailsNick" />
|
app:layout_constraintTop_toBottomOf="@id/account_edit_colors" />
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
</ScrollView>
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:layout_width="300dp"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content">
|
android:layout_height="wrap_content">
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
|
@ -5,16 +5,6 @@
|
|||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent">
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
<com.google.android.material.progressindicator.CircularProgressIndicator
|
|
||||||
android:id="@+id/account_progress"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:indeterminate="true"
|
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
|
||||||
app:layout_constraintTop_toTopOf="parent" />
|
|
||||||
|
|
||||||
<androidx.recyclerview.widget.RecyclerView
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
android:id="@+id/accountRecycler"
|
android:id="@+id/accountRecycler"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
@ -36,55 +26,4 @@
|
|||||||
android:insetBottom="0dp"
|
android:insetBottom="0dp"
|
||||||
android:text="@string/account_add_new"
|
android:text="@string/account_add_new"
|
||||||
app:layout_constraintBottom_toBottomOf="parent" />
|
app:layout_constraintBottom_toBottomOf="parent" />
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:id="@+id/account_error"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:gravity="center"
|
|
||||||
android:orientation="vertical"
|
|
||||||
android:visibility="invisible"
|
|
||||||
tools:ignore="UseCompoundDrawables"
|
|
||||||
tools:visibility="gone">
|
|
||||||
|
|
||||||
<ImageView
|
|
||||||
android:layout_width="100dp"
|
|
||||||
android:layout_height="100dp"
|
|
||||||
app:srcCompat="@drawable/ic_all_account"
|
|
||||||
app:tint="?colorOnBackground"
|
|
||||||
tools:ignore="contentDescription" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/account_error_message"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginTop="20dp"
|
|
||||||
android:gravity="center"
|
|
||||||
android:padding="8dp"
|
|
||||||
android:text="@string/error_unknown"
|
|
||||||
android:textSize="20sp" />
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginTop="16dp"
|
|
||||||
android:gravity="center"
|
|
||||||
android:orientation="horizontal">
|
|
||||||
|
|
||||||
<com.google.android.material.button.MaterialButton
|
|
||||||
android:id="@+id/account_error_details"
|
|
||||||
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginEnd="8dp"
|
|
||||||
android:layout_marginRight="8dp"
|
|
||||||
android:text="@string/all_details" />
|
|
||||||
|
|
||||||
<com.google.android.material.button.MaterialButton
|
|
||||||
android:id="@+id/account_error_retry"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:text="@string/all_retry" />
|
|
||||||
</LinearLayout>
|
|
||||||
</LinearLayout>
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
@ -21,9 +21,9 @@
|
|||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:gravity="center"
|
android:gravity="center"
|
||||||
android:orientation="vertical"
|
android:orientation="vertical"
|
||||||
android:visibility="invisible"
|
android:visibility="gone"
|
||||||
tools:ignore="UseCompoundDrawables"
|
tools:ignore="UseCompoundDrawables"
|
||||||
tools:visibility="visible">
|
tools:visibility="gone">
|
||||||
|
|
||||||
<ImageView
|
<ImageView
|
||||||
android:layout_width="100dp"
|
android:layout_width="100dp"
|
||||||
@ -69,7 +69,9 @@
|
|||||||
<androidx.constraintlayout.widget.ConstraintLayout
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
android:id="@+id/account_details_content"
|
android:id="@+id/account_details_content"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent">
|
android:layout_height="match_parent"
|
||||||
|
android:visibility="gone"
|
||||||
|
tools:visibility="visible">
|
||||||
|
|
||||||
<ScrollView
|
<ScrollView
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
@ -90,9 +92,22 @@
|
|||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toTopOf="parent"
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
app:srcCompat="@drawable/ic_all_account"
|
tools:ignore="ContentDescription"
|
||||||
app:tint="?colorPrimary"
|
tools:src="@tools:sample/avatars" />
|
||||||
tools:ignore="ContentDescription" />
|
|
||||||
|
<com.mikhaellopez.circularimageview.CircularImageView
|
||||||
|
android:id="@+id/account_details_check"
|
||||||
|
android:layout_width="40dp"
|
||||||
|
android:layout_height="40dp"
|
||||||
|
android:layout_marginStart="70dp"
|
||||||
|
android:layout_marginTop="70dp"
|
||||||
|
app:civ_border_color="?colorSurface"
|
||||||
|
app:civ_border_width="1dp"
|
||||||
|
app:civ_circle_color="?colorSurface"
|
||||||
|
app:layout_constraintStart_toStartOf="@id/accountDetailsAvatar"
|
||||||
|
app:layout_constraintTop_toTopOf="@id/accountDetailsAvatar"
|
||||||
|
app:srcCompat="@drawable/ic_all_round_check"
|
||||||
|
app:tint="?colorPrimary" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/accountDetailsName"
|
android:id="@+id/accountDetailsName"
|
||||||
|
@ -18,15 +18,27 @@
|
|||||||
android:layout_height="40dp"
|
android:layout_height="40dp"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toTopOf="parent"
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
app:srcCompat="@drawable/ic_all_account"
|
|
||||||
tools:ignore="ContentDescription"
|
tools:ignore="ContentDescription"
|
||||||
tools:tint="@color/colorPrimary" />
|
tools:src="@tools:sample/avatars" />
|
||||||
|
|
||||||
|
<com.mikhaellopez.circularimageview.CircularImageView
|
||||||
|
android:id="@+id/account_item_check"
|
||||||
|
android:layout_width="20dp"
|
||||||
|
android:layout_height="20dp"
|
||||||
|
android:layout_marginStart="28dp"
|
||||||
|
android:layout_marginTop="28dp"
|
||||||
|
app:civ_border_width="1dp"
|
||||||
|
app:layout_constraintStart_toStartOf="@id/accountItemImage"
|
||||||
|
app:layout_constraintTop_toTopOf="@id/accountItemImage"
|
||||||
|
app:srcCompat="@drawable/ic_all_round_check"
|
||||||
|
app:tint="?colorPrimary" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/accountItemName"
|
android:id="@+id/accountItemName"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginStart="16dp"
|
android:layout_marginStart="16dp"
|
||||||
|
android:layout_marginEnd="16dp"
|
||||||
android:ellipsize="end"
|
android:ellipsize="end"
|
||||||
android:maxLines="1"
|
android:maxLines="1"
|
||||||
android:textSize="16sp"
|
android:textSize="16sp"
|
||||||
@ -41,6 +53,7 @@
|
|||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginStart="16dp"
|
android:layout_marginStart="16dp"
|
||||||
android:layout_marginTop="3dp"
|
android:layout_marginTop="3dp"
|
||||||
|
android:layout_marginEnd="16dp"
|
||||||
android:ellipsize="end"
|
android:ellipsize="end"
|
||||||
android:maxLines="1"
|
android:maxLines="1"
|
||||||
android:textColor="?android:textColorSecondary"
|
android:textColor="?android:textColorSecondary"
|
||||||
@ -56,6 +69,7 @@
|
|||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginStart="16dp"
|
android:layout_marginStart="16dp"
|
||||||
android:layout_marginTop="3dp"
|
android:layout_marginTop="3dp"
|
||||||
|
android:layout_marginEnd="16dp"
|
||||||
android:ellipsize="end"
|
android:ellipsize="end"
|
||||||
android:maxLines="1"
|
android:maxLines="1"
|
||||||
android:textColor="?android:textColorSecondary"
|
android:textColor="?android:textColorSecondary"
|
||||||
|
27
app/src/main/res/layout/item_account_edit_color.xml
Normal file
27
app/src/main/res/layout/item_account_edit_color.xml
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
<FrameLayout 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/account_edit_item_color_container"
|
||||||
|
android:layout_width="48dp"
|
||||||
|
android:layout_height="48dp"
|
||||||
|
android:layout_marginTop="10dp"
|
||||||
|
android:layout_marginEnd="10dp"
|
||||||
|
android:clickable="true"
|
||||||
|
android:focusable="true">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/account_edit_item_color"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
tools:ignore="ContentDescription"
|
||||||
|
tools:src="@tools:sample/avatars" />
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/account_edit_check"
|
||||||
|
android:layout_width="32dp"
|
||||||
|
android:layout_height="32dp"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
app:srcCompat="@drawable/ic_check"
|
||||||
|
app:tint="@android:color/white"
|
||||||
|
tools:ignore="ContentDescription" />
|
||||||
|
</FrameLayout>
|
@ -23,5 +23,4 @@
|
|||||||
android:layout_margin="10dp"
|
android:layout_margin="10dp"
|
||||||
android:background="?android:windowBackground"
|
android:background="?android:windowBackground"
|
||||||
tools:context=".ui.modules.grade.statistics.GradeStatisticsAdapter" />
|
tools:context=".ui.modules.grade.statistics.GradeStatisticsAdapter" />
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools">
|
||||||
|
|
||||||
<item
|
<item
|
||||||
android:id="@+id/mainMenuAccount"
|
android:id="@+id/mainMenuAccount"
|
||||||
android:icon="@drawable/ic_all_account"
|
|
||||||
android:orderInCategory="2"
|
android:orderInCategory="2"
|
||||||
android:title="@string/main_account_picker"
|
app:showAsAction="always"
|
||||||
app:iconTint="@color/material_on_surface_emphasis_medium"
|
tools:ignore="MenuTitle" />
|
||||||
app:showAsAction="always" />
|
|
||||||
</menu>
|
</menu>
|
||||||
|
@ -5,6 +5,7 @@ import io.github.wulkanowy.data.db.dao.SemesterDao
|
|||||||
import io.github.wulkanowy.data.db.dao.StudentDao
|
import io.github.wulkanowy.data.db.dao.StudentDao
|
||||||
import io.github.wulkanowy.sdk.Sdk
|
import io.github.wulkanowy.sdk.Sdk
|
||||||
import io.github.wulkanowy.sdk.pojo.Student
|
import io.github.wulkanowy.sdk.pojo.Student
|
||||||
|
import io.github.wulkanowy.utils.AppInfo
|
||||||
import io.mockk.MockKAnnotations
|
import io.mockk.MockKAnnotations
|
||||||
import io.mockk.coEvery
|
import io.mockk.coEvery
|
||||||
import io.mockk.impl.annotations.MockK
|
import io.mockk.impl.annotations.MockK
|
||||||
@ -30,7 +31,14 @@ class StudentTest {
|
|||||||
@Before
|
@Before
|
||||||
fun initApi() {
|
fun initApi() {
|
||||||
MockKAnnotations.init(this)
|
MockKAnnotations.init(this)
|
||||||
studentRepository = StudentRepository(mockk(), TestDispatchersProvider(), studentDb, semesterDb, mockSdk)
|
studentRepository = StudentRepository(
|
||||||
|
mockk(),
|
||||||
|
TestDispatchersProvider(),
|
||||||
|
studentDb,
|
||||||
|
semesterDb,
|
||||||
|
mockSdk,
|
||||||
|
AppInfo()
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
2
gradle/wrapper/gradle-wrapper.properties
vendored
2
gradle/wrapper/gradle-wrapper.properties
vendored
@ -1,5 +1,5 @@
|
|||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-6.8.2-all.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-6.8.3-all.zip
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
|
Loading…
x
Reference in New Issue
Block a user