forked from github/wulkanowy-mirror
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.github.wulkanowy:material-chips-input:2.1.1"
|
||||
implementation "com.github.PhilJay:MPAndroidChart:v3.1.0"
|
||||
implementation 'com.mikhaellopez:circularimageview:4.2.0'
|
||||
|
||||
implementation "androidx.work:work-runtime-ktx:$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 io.github.wulkanowy.data.db.AppDatabase
|
||||
import io.github.wulkanowy.data.db.SharedPrefProvider
|
||||
import io.github.wulkanowy.utils.AppInfo
|
||||
import org.junit.Rule
|
||||
|
||||
abstract class AbstractMigrationTest {
|
||||
@ -24,12 +25,16 @@ abstract class AbstractMigrationTest {
|
||||
|
||||
fun getMigratedRoomDatabase(): AppDatabase {
|
||||
val context = ApplicationProvider.getApplicationContext<Context>()
|
||||
val database = Room.databaseBuilder(ApplicationProvider.getApplicationContext(),
|
||||
AppDatabase::class.java, dbName)
|
||||
.addMigrations(*AppDatabase.getMigrations(SharedPrefProvider(PreferenceManager
|
||||
.getDefaultSharedPreferences(context)))
|
||||
val database = Room.databaseBuilder(
|
||||
ApplicationProvider.getApplicationContext(),
|
||||
AppDatabase::class.java,
|
||||
dbName
|
||||
).addMigrations(
|
||||
*AppDatabase.getMigrations(
|
||||
SharedPrefProvider(PreferenceManager.getDefaultSharedPreferences(context)),
|
||||
AppInfo()
|
||||
)
|
||||
.build()
|
||||
).build()
|
||||
// close the database and release any stream resources when the test finishes
|
||||
helper.closeWhenFinished(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
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.Application
|
||||
import android.content.Context
|
||||
import android.util.Log.DEBUG
|
||||
import android.util.Log.INFO
|
||||
import android.util.Log.VERBOSE
|
||||
import android.webkit.WebView
|
||||
import androidx.fragment.app.FragmentManager
|
||||
import androidx.hilt.work.HiltWorkerFactory
|
||||
import androidx.multidex.MultiDex
|
||||
import androidx.work.Configuration
|
||||
@ -46,8 +48,10 @@ class WulkanowyApp : Application(), Configuration.Provider {
|
||||
MultiDex.install(this)
|
||||
}
|
||||
|
||||
@SuppressLint("UnsafeOptInUsageWarning")
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
FragmentManager.enableNewStateManager(false)
|
||||
|
||||
initializeAppLanguage()
|
||||
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.repositories.PreferencesRepository
|
||||
import io.github.wulkanowy.sdk.Sdk
|
||||
import io.github.wulkanowy.utils.AppInfo
|
||||
import timber.log.Timber
|
||||
import javax.inject.Singleton
|
||||
|
||||
@ -60,7 +61,8 @@ internal class RepositoryModule {
|
||||
fun provideDatabase(
|
||||
@ApplicationContext context: Context,
|
||||
sharedPrefProvider: SharedPrefProvider,
|
||||
) = AppDatabase.newInstance(context, sharedPrefProvider)
|
||||
appInfo: AppInfo
|
||||
) = AppDatabase.newInstance(context, sharedPrefProvider, appInfo)
|
||||
|
||||
@Singleton
|
||||
@Provides
|
||||
|
@ -6,7 +6,6 @@ import androidx.room.Room
|
||||
import androidx.room.RoomDatabase
|
||||
import androidx.room.RoomDatabase.JournalMode.TRUNCATE
|
||||
import androidx.room.TypeConverters
|
||||
import androidx.room.migration.Migration
|
||||
import io.github.wulkanowy.data.db.dao.AttendanceDao
|
||||
import io.github.wulkanowy.data.db.dao.AttendanceSummaryDao
|
||||
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.Migration33
|
||||
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.Migration5
|
||||
import io.github.wulkanowy.data.db.migrations.Migration6
|
||||
import io.github.wulkanowy.data.db.migrations.Migration7
|
||||
import io.github.wulkanowy.data.db.migrations.Migration8
|
||||
import io.github.wulkanowy.data.db.migrations.Migration9
|
||||
import io.github.wulkanowy.utils.AppInfo
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
@ -131,54 +132,55 @@ import javax.inject.Singleton
|
||||
abstract class AppDatabase : RoomDatabase() {
|
||||
|
||||
companion object {
|
||||
const val VERSION_SCHEMA = 34
|
||||
const val VERSION_SCHEMA = 35
|
||||
|
||||
fun getMigrations(sharedPrefProvider: SharedPrefProvider): Array<Migration> {
|
||||
return arrayOf(
|
||||
Migration2(),
|
||||
Migration3(),
|
||||
Migration4(),
|
||||
Migration5(),
|
||||
Migration6(),
|
||||
Migration7(),
|
||||
Migration8(),
|
||||
Migration9(),
|
||||
Migration10(),
|
||||
Migration11(),
|
||||
Migration12(),
|
||||
Migration13(),
|
||||
Migration14(),
|
||||
Migration15(),
|
||||
Migration16(),
|
||||
Migration17(),
|
||||
Migration18(),
|
||||
Migration19(sharedPrefProvider),
|
||||
Migration20(),
|
||||
Migration21(),
|
||||
Migration22(),
|
||||
Migration23(),
|
||||
Migration24(),
|
||||
Migration25(),
|
||||
Migration26(),
|
||||
Migration27(),
|
||||
Migration28(),
|
||||
Migration29(),
|
||||
Migration30(),
|
||||
Migration31(),
|
||||
Migration32(),
|
||||
Migration33(),
|
||||
Migration34()
|
||||
)
|
||||
}
|
||||
fun getMigrations(sharedPrefProvider: SharedPrefProvider, appInfo: AppInfo) = arrayOf(
|
||||
Migration2(),
|
||||
Migration3(),
|
||||
Migration4(),
|
||||
Migration5(),
|
||||
Migration6(),
|
||||
Migration7(),
|
||||
Migration8(),
|
||||
Migration9(),
|
||||
Migration10(),
|
||||
Migration11(),
|
||||
Migration12(),
|
||||
Migration13(),
|
||||
Migration14(),
|
||||
Migration15(),
|
||||
Migration16(),
|
||||
Migration17(),
|
||||
Migration18(),
|
||||
Migration19(sharedPrefProvider),
|
||||
Migration20(),
|
||||
Migration21(),
|
||||
Migration22(),
|
||||
Migration23(),
|
||||
Migration24(),
|
||||
Migration25(),
|
||||
Migration26(),
|
||||
Migration27(),
|
||||
Migration28(),
|
||||
Migration29(),
|
||||
Migration30(),
|
||||
Migration31(),
|
||||
Migration32(),
|
||||
Migration33(),
|
||||
Migration34(),
|
||||
Migration35(appInfo)
|
||||
)
|
||||
|
||||
fun newInstance(context: Context, sharedPrefProvider: SharedPrefProvider): AppDatabase {
|
||||
return Room.databaseBuilder(context, AppDatabase::class.java, "wulkanowy_database")
|
||||
.setJournalMode(TRUNCATE)
|
||||
.fallbackToDestructiveMigrationFrom(VERSION_SCHEMA + 1)
|
||||
.fallbackToDestructiveMigrationOnDowngrade()
|
||||
.addMigrations(*getMigrations(sharedPrefProvider))
|
||||
.build()
|
||||
}
|
||||
fun newInstance(
|
||||
context: Context,
|
||||
sharedPrefProvider: SharedPrefProvider,
|
||||
appInfo: AppInfo
|
||||
) = Room.databaseBuilder(context, AppDatabase::class.java, "wulkanowy_database")
|
||||
.setJournalMode(TRUNCATE)
|
||||
.fallbackToDestructiveMigrationFrom(VERSION_SCHEMA + 1)
|
||||
.fallbackToDestructiveMigrationOnDowngrade()
|
||||
.addMigrations(*getMigrations(sharedPrefProvider, appInfo))
|
||||
.build()
|
||||
}
|
||||
|
||||
abstract val studentDao: StudentDao
|
||||
|
@ -8,7 +8,7 @@ import androidx.room.Query
|
||||
import androidx.room.Transaction
|
||||
import androidx.room.Update
|
||||
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 javax.inject.Singleton
|
||||
|
||||
@ -23,13 +23,13 @@ interface StudentDao {
|
||||
suspend fun delete(student: Student)
|
||||
|
||||
@Update(entity = Student::class)
|
||||
suspend fun update(studentNick: StudentNick)
|
||||
suspend fun update(studentNickAndAvatar: StudentNickAndAvatar)
|
||||
|
||||
@Query("SELECT * FROM Students WHERE is_current = 1")
|
||||
suspend fun loadCurrent(): Student?
|
||||
|
||||
@Query("SELECT * FROM Students WHERE id = :id")
|
||||
suspend fun loadById(id: Int): Student?
|
||||
suspend fun loadById(id: Long): Student?
|
||||
|
||||
@Query("SELECT * FROM Students")
|
||||
suspend fun loadAll(): List<Student>
|
||||
|
@ -10,7 +10,7 @@ import java.time.LocalDateTime
|
||||
data class Message(
|
||||
|
||||
@ColumnInfo(name = "student_id")
|
||||
val studentId: Int,
|
||||
val studentId: Long,
|
||||
|
||||
@ColumnInfo(name = "real_id")
|
||||
val realId: Int,
|
||||
|
@ -81,4 +81,7 @@ data class Student(
|
||||
var id: Long = 0
|
||||
|
||||
var nick = ""
|
||||
|
||||
@ColumnInfo(name = "avatar_color")
|
||||
var avatarColor = 0L
|
||||
}
|
||||
|
@ -1,13 +1,17 @@
|
||||
package io.github.wulkanowy.data.db.entities
|
||||
|
||||
import androidx.room.ColumnInfo
|
||||
import androidx.room.Entity
|
||||
import androidx.room.PrimaryKey
|
||||
import java.io.Serializable
|
||||
|
||||
@Entity
|
||||
data class StudentNick(
|
||||
data class StudentNickAndAvatar(
|
||||
|
||||
val nick: String
|
||||
val nick: String,
|
||||
|
||||
@ColumnInfo(name = "avatar_color")
|
||||
var avatarColor: Long
|
||||
|
||||
) : 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.Recipient
|
||||
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 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 {
|
||||
Message(
|
||||
studentId = student.id.toInt(),
|
||||
studentId = student.id,
|
||||
realId = it.id ?: 0,
|
||||
messageId = it.messageId ?: 0,
|
||||
sender = it.sender?.name.orEmpty(),
|
||||
|
@ -5,7 +5,7 @@ import io.github.wulkanowy.data.db.entities.StudentWithSemesters
|
||||
import java.time.LocalDateTime
|
||||
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(
|
||||
student = Student(
|
||||
email = it.email,
|
||||
@ -28,8 +28,10 @@ fun List<SdkStudent>.mapToEntities(password: String = "") = map {
|
||||
mobileBaseUrl = it.mobileBaseUrl,
|
||||
privateKey = it.privateKey,
|
||||
certificateKey = it.certificateKey,
|
||||
loginMode = it.loginMode.name
|
||||
),
|
||||
loginMode = it.loginMode.name,
|
||||
).apply {
|
||||
avatarColor = colors.random()
|
||||
},
|
||||
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.StudentDao
|
||||
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.exceptions.NoCurrentStudentException
|
||||
import io.github.wulkanowy.data.mappers.mapToEntities
|
||||
import io.github.wulkanowy.sdk.Sdk
|
||||
import io.github.wulkanowy.utils.AppInfo
|
||||
import io.github.wulkanowy.utils.DispatchersProvider
|
||||
import io.github.wulkanowy.utils.security.decrypt
|
||||
import io.github.wulkanowy.utils.security.encrypt
|
||||
@ -23,7 +24,8 @@ class StudentRepository @Inject constructor(
|
||||
private val dispatchers: DispatchersProvider,
|
||||
private val studentDb: StudentDao,
|
||||
private val semesterDb: SemesterDao,
|
||||
private val sdk: Sdk
|
||||
private val sdk: Sdk,
|
||||
private val appInfo: AppInfo
|
||||
) {
|
||||
|
||||
suspend fun isStudentSaved() = getSavedStudents(false).isNotEmpty()
|
||||
@ -35,7 +37,8 @@ class StudentRepository @Inject constructor(
|
||||
symbol: String,
|
||||
token: String
|
||||
): List<StudentWithSemesters> =
|
||||
sdk.getStudentsFromMobileApi(token, pin, symbol, "").mapToEntities()
|
||||
sdk.getStudentsFromMobileApi(token, pin, symbol, "")
|
||||
.mapToEntities(colors = appInfo.defaultColorsForAvatar)
|
||||
|
||||
suspend fun getStudentsScrapper(
|
||||
email: String,
|
||||
@ -44,7 +47,7 @@ class StudentRepository @Inject constructor(
|
||||
symbol: String
|
||||
): List<StudentWithSemesters> =
|
||||
sdk.getStudentsFromScrapper(email, password, scrapperBaseUrl, symbol)
|
||||
.mapToEntities(password)
|
||||
.mapToEntities(password, appInfo.defaultColorsForAvatar)
|
||||
|
||||
suspend fun getStudentsHybrid(
|
||||
email: String,
|
||||
@ -52,46 +55,58 @@ class StudentRepository @Inject constructor(
|
||||
scrapperBaseUrl: String,
|
||||
symbol: String
|
||||
): 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) =
|
||||
withContext(dispatchers.backgroundThread) {
|
||||
studentDb.loadStudentsWithSemesters().map {
|
||||
studentDb.loadStudentsWithSemesters()
|
||||
.map {
|
||||
it.apply {
|
||||
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) {
|
||||
studentDb.loadById(id)?.apply {
|
||||
if (Sdk.Mode.valueOf(loginMode) != Sdk.Mode.API) {
|
||||
password = decrypt(password)
|
||||
suspend fun getStudentById(id: Long, decryptPass: Boolean = true): Student {
|
||||
val student = studentDb.loadById(id) ?: throw NoCurrentStudentException()
|
||||
|
||||
if (decryptPass && Sdk.Mode.valueOf(student.loginMode) != Sdk.Mode.API) {
|
||||
student.password = withContext(dispatchers.backgroundThread) {
|
||||
decrypt(student.password)
|
||||
}
|
||||
}
|
||||
} ?: throw NoCurrentStudentException()
|
||||
return student
|
||||
}
|
||||
|
||||
suspend fun getCurrentStudent(decryptPass: Boolean = true) =
|
||||
withContext(dispatchers.backgroundThread) {
|
||||
studentDb.loadCurrent()?.apply {
|
||||
if (decryptPass && Sdk.Mode.valueOf(loginMode) != Sdk.Mode.API) {
|
||||
password = decrypt(password)
|
||||
}
|
||||
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)
|
||||
}
|
||||
} ?: throw NoCurrentStudentException()
|
||||
}
|
||||
return student
|
||||
}
|
||||
|
||||
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 }
|
||||
.map {
|
||||
it.apply {
|
||||
if (Sdk.Mode.valueOf(it.loginMode) != Sdk.Mode.API) {
|
||||
password = withContext(dispatchers.backgroundThread) {
|
||||
encrypt(password, context)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return withContext(dispatchers.backgroundThread) {
|
||||
studentDb.insertAll(studentsWithSemesters.map { it.student }.map {
|
||||
if (Sdk.Mode.valueOf(it.loginMode) != Sdk.Mode.API) {
|
||||
it.copy(password = encrypt(it.password, context))
|
||||
} else it
|
||||
})
|
||||
}
|
||||
semesterDb.insertSemesters(semesters)
|
||||
return studentDb.insertAll(students)
|
||||
}
|
||||
|
||||
suspend fun switchStudent(studentWithSemesters: StudentWithSemesters) {
|
||||
@ -103,5 +118,6 @@ class StudentRepository @Inject constructor(
|
||||
|
||||
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
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.graphics.PorterDuff
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View.GONE
|
||||
import android.view.View.VISIBLE
|
||||
import android.view.ViewGroup
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import io.github.wulkanowy.R
|
||||
import io.github.wulkanowy.data.db.entities.StudentWithSemesters
|
||||
import io.github.wulkanowy.databinding.HeaderAccountBinding
|
||||
import io.github.wulkanowy.databinding.ItemAccountBinding
|
||||
import io.github.wulkanowy.utils.createNameInitialsDrawable
|
||||
import io.github.wulkanowy.utils.getThemeAttrColor
|
||||
import io.github.wulkanowy.utils.nickOrName
|
||||
import javax.inject.Inject
|
||||
@ -72,9 +73,13 @@ class AccountAdapter @Inject constructor() : RecyclerView.Adapter<RecyclerView.V
|
||||
binding: ItemAccountBinding,
|
||||
studentWithSemesters: StudentWithSemesters
|
||||
) {
|
||||
val context = binding.root.context
|
||||
val student = studentWithSemesters.student
|
||||
val semesters = studentWithSemesters.semesters
|
||||
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 {
|
||||
if (it.value !is StudentWithSemesters) return@filter false
|
||||
val studentToCompare = it.value.student
|
||||
@ -87,15 +92,17 @@ class AccountAdapter @Inject constructor() : RecyclerView.Adapter<RecyclerView.V
|
||||
with(binding) {
|
||||
accountItemName.text = "${student.nickOrName} ${diary?.diaryName.orEmpty()}"
|
||||
accountItemSchool.text = studentWithSemesters.student.schoolName
|
||||
accountItemAccountType.setText(if (student.isParent) R.string.account_type_parent else R.string.account_type_student)
|
||||
accountItemAccountType.visibility = if (isDuplicatedStudent) VISIBLE else GONE
|
||||
accountItemImage.setImageDrawable(avatar)
|
||||
|
||||
with(accountItemImage) {
|
||||
val colorImage =
|
||||
if (student.isCurrent) context.getThemeAttrColor(R.attr.colorPrimary)
|
||||
else context.getThemeAttrColor(R.attr.colorOnSurface, 153)
|
||||
with(accountItemAccountType) {
|
||||
setText(if (student.isParent) R.string.account_type_parent else R.string.account_type_student)
|
||||
isVisible = isDuplicatedStudent
|
||||
}
|
||||
|
||||
setColorFilter(colorImage, PorterDuff.Mode.SRC_IN)
|
||||
with(accountItemCheck) {
|
||||
isVisible = student.isCurrent
|
||||
borderColor = checkBackgroundColor
|
||||
circleColor = checkBackgroundColor
|
||||
}
|
||||
|
||||
root.setOnClickListener { onClickListener(studentWithSemesters) }
|
||||
|
@ -36,23 +36,20 @@ class AccountFragment : BaseFragment<FragmentAccountBinding>(R.layout.fragment_a
|
||||
|
||||
override var subtitleString = ""
|
||||
|
||||
override val isViewEmpty get() = accountAdapter.items.isEmpty()
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setHasOptionsMenu(true)
|
||||
}
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
binding = FragmentAccountBinding.bind(view)
|
||||
presenter.onAttachView(this)
|
||||
}
|
||||
|
||||
override fun initView() {
|
||||
binding.accountErrorRetry.setOnClickListener { presenter.onRetry() }
|
||||
binding.accountErrorDetails.setOnClickListener { presenter.onDetailsClick() }
|
||||
|
||||
binding.accountRecycler.apply {
|
||||
layoutManager = LinearLayoutManager(context)
|
||||
adapter = accountAdapter
|
||||
@ -60,9 +57,7 @@ class AccountFragment : BaseFragment<FragmentAccountBinding>(R.layout.fragment_a
|
||||
|
||||
accountAdapter.onClickListener = presenter::onItemSelected
|
||||
|
||||
with(binding) {
|
||||
accountAdd.setOnClickListener { presenter.onAddSelected() }
|
||||
}
|
||||
binding.accountAdd.setOnClickListener { presenter.onAddSelected() }
|
||||
}
|
||||
|
||||
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
|
||||
@ -84,28 +79,7 @@ class AccountFragment : BaseFragment<FragmentAccountBinding>(R.layout.fragment_a
|
||||
|
||||
override fun openAccountDetailsView(studentWithSemesters: StudentWithSemesters) {
|
||||
(activity as? MainActivity)?.pushView(
|
||||
AccountDetailsFragment.newInstance(
|
||||
studentWithSemesters
|
||||
)
|
||||
AccountDetailsFragment.newInstance(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.ui.base.BasePresenter
|
||||
import io.github.wulkanowy.ui.base.ErrorHandler
|
||||
import io.github.wulkanowy.utils.afterLoading
|
||||
import io.github.wulkanowy.utils.flowWithResource
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import timber.log.Timber
|
||||
@ -16,28 +15,13 @@ class AccountPresenter @Inject constructor(
|
||||
studentRepository: StudentRepository,
|
||||
) : BasePresenter<AccountView>(errorHandler, studentRepository) {
|
||||
|
||||
private lateinit var lastError: Throwable
|
||||
|
||||
override fun onAttachView(view: AccountView) {
|
||||
super.onAttachView(view)
|
||||
view.initView()
|
||||
Timber.i("Account view was initialized")
|
||||
errorHandler.showErrorMessage = ::showErrorViewOnError
|
||||
loadData()
|
||||
}
|
||||
|
||||
fun onRetry() {
|
||||
view?.run {
|
||||
showErrorView(false)
|
||||
showProgress(true)
|
||||
}
|
||||
loadData()
|
||||
}
|
||||
|
||||
fun onDetailsClick() {
|
||||
view?.showErrorDetailsDialog(lastError)
|
||||
}
|
||||
|
||||
fun onAddSelected() {
|
||||
Timber.i("Select add account")
|
||||
view?.openLoginView()
|
||||
@ -47,6 +31,24 @@ class AccountPresenter @Inject constructor(
|
||||
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<*>> {
|
||||
return items.groupBy {
|
||||
Account("${it.student.userName} (${it.student.email})", it.student.isParent)
|
||||
@ -60,45 +62,4 @@ class AccountPresenter @Inject constructor(
|
||||
}
|
||||
.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 {
|
||||
|
||||
val isViewEmpty: Boolean
|
||||
|
||||
fun initView()
|
||||
|
||||
fun updateData(data: List<AccountItem<*>>)
|
||||
@ -14,13 +12,4 @@ interface AccountView : BaseView {
|
||||
fun openLoginView()
|
||||
|
||||
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 androidx.appcompat.app.AlertDialog
|
||||
import androidx.core.view.get
|
||||
import androidx.core.view.isVisible
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import io.github.wulkanowy.R
|
||||
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.studentinfo.StudentInfoFragment
|
||||
import io.github.wulkanowy.ui.modules.studentinfo.StudentInfoView
|
||||
import io.github.wulkanowy.utils.createNameInitialsDrawable
|
||||
import io.github.wulkanowy.utils.nickOrName
|
||||
import javax.inject.Inject
|
||||
|
||||
@ -88,8 +90,15 @@ class AccountDetailsFragment :
|
||||
|
||||
override fun showAccountData(student: Student) {
|
||||
with(binding) {
|
||||
accountDetailsCheck.isVisible = student.isCurrent
|
||||
accountDetailsName.text = student.nickOrName
|
||||
accountDetailsSchool.text = student.schoolName
|
||||
accountDetailsAvatar.setImageDrawable(
|
||||
requireContext().createNameInitialsDrawable(
|
||||
student.nickOrName,
|
||||
student.avatarColor
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -21,7 +21,7 @@ class AccountDetailsPresenter @Inject constructor(
|
||||
private val syncManager: SyncManager
|
||||
) : BasePresenter<AccountDetailsView>(errorHandler, studentRepository) {
|
||||
|
||||
private lateinit var studentWithSemesters: StudentWithSemesters
|
||||
private var studentWithSemesters: StudentWithSemesters? = null
|
||||
|
||||
private lateinit var lastError: Throwable
|
||||
|
||||
@ -69,10 +69,10 @@ class AccountDetailsPresenter @Inject constructor(
|
||||
}
|
||||
Status.SUCCESS -> {
|
||||
Timber.i("Loading account details view result: Success")
|
||||
studentWithSemesters = it.data!!
|
||||
studentWithSemesters = it.data
|
||||
view?.run {
|
||||
showAccountData(studentWithSemesters.student)
|
||||
enableSelectStudentButton(!studentWithSemesters.student.isCurrent)
|
||||
showAccountData(studentWithSemesters!!.student)
|
||||
enableSelectStudentButton(!studentWithSemesters!!.student.isCurrent)
|
||||
showContent(true)
|
||||
showErrorView(false)
|
||||
}
|
||||
@ -88,17 +88,23 @@ class AccountDetailsPresenter @Inject constructor(
|
||||
}
|
||||
|
||||
fun onAccountEditSelected() {
|
||||
view?.showAccountEditDetailsDialog(studentWithSemesters.student)
|
||||
studentWithSemesters?.let {
|
||||
view?.showAccountEditDetailsDialog(it.student)
|
||||
}
|
||||
}
|
||||
|
||||
fun onStudentInfoSelected(infoType: StudentInfoView.Type) {
|
||||
view?.openStudentInfoView(infoType, studentWithSemesters)
|
||||
studentWithSemesters?.let {
|
||||
view?.openStudentInfoView(infoType, it)
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
when (it.status) {
|
||||
Status.LOADING -> Timber.i("Attempt to change a student")
|
||||
@ -122,8 +128,10 @@ class AccountDetailsPresenter @Inject constructor(
|
||||
}
|
||||
|
||||
fun onLogoutConfirm() {
|
||||
if (studentWithSemesters == null) return
|
||||
|
||||
flowWithResource {
|
||||
val studentToLogout = studentWithSemesters.student
|
||||
val studentToLogout = studentWithSemesters!!.student
|
||||
|
||||
studentRepository.logoutStudent(studentToLogout)
|
||||
val students = studentRepository.getSavedStudents(false)
|
||||
@ -143,7 +151,7 @@ class AccountDetailsPresenter @Inject constructor(
|
||||
syncManager.stopSyncWorker()
|
||||
openClearLoginView()
|
||||
}
|
||||
studentWithSemesters.student.isCurrent -> {
|
||||
studentWithSemesters!!.student.isCurrent -> {
|
||||
Timber.i("Logout result: Logout student and switch to another")
|
||||
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.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.recyclerview.widget.GridLayoutManager
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import io.github.wulkanowy.data.db.entities.Student
|
||||
import io.github.wulkanowy.databinding.DialogAccountEditBinding
|
||||
@ -16,6 +17,9 @@ class AccountEditDialog : BaseDialogFragment<DialogAccountEditBinding>(), Accoun
|
||||
@Inject
|
||||
lateinit var presenter: AccountEditPresenter
|
||||
|
||||
@Inject
|
||||
lateinit var accountEditColorAdapter: AccountEditColorAdapter
|
||||
|
||||
companion object {
|
||||
|
||||
private const val ARGUMENT_KEY = "student_with_semesters"
|
||||
@ -48,8 +52,30 @@ class AccountEditDialog : BaseDialogFragment<DialogAccountEditBinding>(), Accoun
|
||||
with(binding) {
|
||||
accountEditDetailsCancel.setOnClickListener { dismiss() }
|
||||
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.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.ui.base.BasePresenter
|
||||
import io.github.wulkanowy.ui.base.ErrorHandler
|
||||
import io.github.wulkanowy.utils.AppInfo
|
||||
import io.github.wulkanowy.utils.afterLoading
|
||||
import io.github.wulkanowy.utils.flowWithResource
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
@ -13,12 +14,15 @@ import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
class AccountEditPresenter @Inject constructor(
|
||||
private val appInfo: AppInfo,
|
||||
errorHandler: ErrorHandler,
|
||||
studentRepository: StudentRepository
|
||||
) : BasePresenter<AccountEditView>(errorHandler, studentRepository) {
|
||||
|
||||
lateinit var student: Student
|
||||
|
||||
private val colors = appInfo.defaultColorsForAvatar.map { it.toInt() }
|
||||
|
||||
fun onAttachView(view: AccountEditView, student: Student) {
|
||||
super.onAttachView(view)
|
||||
this.student = student
|
||||
@ -28,27 +32,49 @@ class AccountEditPresenter @Inject constructor(
|
||||
showCurrentNick(student.nick.trim())
|
||||
}
|
||||
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 {
|
||||
val studentNick =
|
||||
StudentNick(nick = nick.trim()).apply { id = student.id }
|
||||
studentRepository.updateStudentNick(studentNick)
|
||||
StudentNickAndAvatar(nick = nick.trim(), avatarColor = avatarColor.toLong())
|
||||
.apply { id = student.id }
|
||||
studentRepository.updateStudentNickAndAvatar(studentNick)
|
||||
}.onEach {
|
||||
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 -> {
|
||||
Timber.i("Change a student nick result: Success")
|
||||
Timber.i("Change a student nick and avatar result: Success")
|
||||
view?.recreateMainView()
|
||||
}
|
||||
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!!)
|
||||
}
|
||||
}
|
||||
}
|
||||
.afterLoading { view?.popView() }
|
||||
.launch()
|
||||
.launch("update_student")
|
||||
}
|
||||
}
|
||||
|
@ -11,4 +11,8 @@ interface AccountEditView : BaseView {
|
||||
fun recreateMainView()
|
||||
|
||||
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 androidx.recyclerview.widget.LinearLayoutManager
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import io.github.wulkanowy.data.db.entities.StudentWithSemesters
|
||||
import io.github.wulkanowy.databinding.DialogAccountQuickBinding
|
||||
import io.github.wulkanowy.ui.base.BaseDialogFragment
|
||||
import io.github.wulkanowy.ui.modules.account.AccountAdapter
|
||||
@ -24,7 +25,15 @@ class AccountQuickDialog : BaseDialogFragment<DialogAccountQuickBinding>(), Acco
|
||||
lateinit var presenter: AccountQuickPresenter
|
||||
|
||||
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?) {
|
||||
@ -38,8 +47,12 @@ class AccountQuickDialog : BaseDialogFragment<DialogAccountQuickBinding>(), Acco
|
||||
savedInstanceState: Bundle?
|
||||
) = DialogAccountQuickBinding.inflate(inflater).apply { binding = this }.root
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
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() {
|
||||
|
@ -17,11 +17,15 @@ class AccountQuickPresenter @Inject constructor(
|
||||
studentRepository: 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)
|
||||
this.studentsWithSemesters = studentsWithSemesters
|
||||
|
||||
view.initView()
|
||||
Timber.i("Account quick dialog view was initialized")
|
||||
loadData()
|
||||
view.updateData(createAccountItems(studentsWithSemesters))
|
||||
}
|
||||
|
||||
fun onManagerSelected() {
|
||||
@ -57,22 +61,6 @@ class AccountQuickPresenter @Inject constructor(
|
||||
.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 {
|
||||
AccountItem(it, AccountItem.ViewType.ITEM)
|
||||
}
|
||||
|
@ -28,6 +28,8 @@ import com.ncapdevi.fragnav.FragNavController
|
||||
import com.ncapdevi.fragnav.FragNavController.Companion.HIDE
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
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.ui.base.BaseActivity
|
||||
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.AppInfo
|
||||
import io.github.wulkanowy.utils.UpdateHelper
|
||||
import io.github.wulkanowy.utils.createNameInitialsDrawable
|
||||
import io.github.wulkanowy.utils.dpToPx
|
||||
import io.github.wulkanowy.utils.getThemeAttrColor
|
||||
import io.github.wulkanowy.utils.nickOrName
|
||||
import io.github.wulkanowy.utils.safelyPopFragments
|
||||
import io.github.wulkanowy.utils.setOnViewChangeListener
|
||||
import timber.log.Timber
|
||||
@ -65,6 +69,8 @@ class MainActivity : BaseActivity<MainPresenter, ActivityMainBinding>(), MainVie
|
||||
@Inject
|
||||
lateinit var appInfo: AppInfo
|
||||
|
||||
private var accountMenu: MenuItem? = null
|
||||
|
||||
private val overlayProvider by lazy { ElevationOverlayProvider(this) }
|
||||
|
||||
private val navController =
|
||||
@ -192,6 +198,9 @@ class MainActivity : BaseActivity<MainPresenter, ActivityMainBinding>(), MainVie
|
||||
|
||||
override fun onCreateOptionsMenu(menu: Menu?): Boolean {
|
||||
menuInflater.inflate(R.menu.action_menu_main, menu)
|
||||
accountMenu = menu?.findItem(R.id.mainMenuAccount)
|
||||
|
||||
presenter.onActionMenuCreated()
|
||||
return true
|
||||
}
|
||||
|
||||
@ -288,8 +297,8 @@ class MainActivity : BaseActivity<MainPresenter, ActivityMainBinding>(), MainVie
|
||||
supportActionBar?.setDisplayHomeAsUpEnabled(show)
|
||||
}
|
||||
|
||||
override fun showAccountPicker() {
|
||||
navController.showDialogFragment(AccountQuickDialog.newInstance())
|
||||
override fun showAccountPicker(studentWithSemesters: List<StudentWithSemesters>) {
|
||||
navController.showDialogFragment(AccountQuickDialog.newInstance(studentWithSemesters))
|
||||
}
|
||||
|
||||
override fun showActionBarElevation(show: Boolean) {
|
||||
@ -323,6 +332,13 @@ class MainActivity : BaseActivity<MainPresenter, ActivityMainBinding>(), MainVie
|
||||
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) {
|
||||
super.onSaveInstanceState(outState)
|
||||
navController.onSaveInstanceState(outState)
|
||||
|
@ -1,5 +1,7 @@
|
||||
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.StudentRepository
|
||||
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.SCHOOL
|
||||
import io.github.wulkanowy.utils.AnalyticsHelper
|
||||
import io.github.wulkanowy.utils.flowWithResource
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
@ -17,9 +21,11 @@ class MainPresenter @Inject constructor(
|
||||
studentRepository: StudentRepository,
|
||||
private val prefRepository: PreferencesRepository,
|
||||
private val syncManager: SyncManager,
|
||||
private val analytics: AnalyticsHelper
|
||||
private val analytics: AnalyticsHelper,
|
||||
) : BasePresenter<MainView>(errorHandler, studentRepository) {
|
||||
|
||||
var studentsWitSemesters: List<StudentWithSemesters>? = null
|
||||
|
||||
fun onAttachView(view: MainView, initMenu: MainView.Section?) {
|
||||
super.onAttachView(view)
|
||||
view.apply {
|
||||
@ -35,6 +41,28 @@ class MainPresenter @Inject constructor(
|
||||
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?) {
|
||||
view?.apply {
|
||||
showActionBarElevation(section != GRADE && section != MESSAGE && section != SCHOOL)
|
||||
@ -48,8 +76,10 @@ class MainPresenter @Inject constructor(
|
||||
}
|
||||
|
||||
fun onAccountManagerSelected(): Boolean {
|
||||
if (studentsWitSemesters.isNullOrEmpty()) return true
|
||||
|
||||
Timber.i("Select account manager")
|
||||
view?.showAccountPicker()
|
||||
view?.showAccountPicker(studentsWitSemesters!!)
|
||||
return true
|
||||
}
|
||||
|
||||
@ -81,6 +111,13 @@ class MainPresenter @Inject constructor(
|
||||
} == true
|
||||
}
|
||||
|
||||
private fun showCurrentStudentAvatar() {
|
||||
val currentStudent =
|
||||
studentsWitSemesters!!.single { it.student.isCurrent }.student
|
||||
|
||||
view?.showStudentAvatar(currentStudent)
|
||||
}
|
||||
|
||||
private fun getProperViewIndexes(initMenu: MainView.Section?): Pair<Int, Int> {
|
||||
return when (initMenu?.id) {
|
||||
in 0..3 -> initMenu!!.id to -1
|
||||
|
@ -1,5 +1,7 @@
|
||||
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
|
||||
|
||||
interface MainView : BaseView {
|
||||
@ -22,7 +24,7 @@ interface MainView : BaseView {
|
||||
|
||||
fun showHomeArrow(show: Boolean)
|
||||
|
||||
fun showAccountPicker()
|
||||
fun showAccountPicker(studentWithSemesters: List<StudentWithSemesters>)
|
||||
|
||||
fun showActionBarElevation(show: Boolean)
|
||||
|
||||
@ -36,6 +38,8 @@ interface MainView : BaseView {
|
||||
|
||||
fun popView(depth: Int = 1)
|
||||
|
||||
fun showStudentAvatar(student: Student)
|
||||
|
||||
interface MainChildView {
|
||||
|
||||
fun onFragmentReselected()
|
||||
|
@ -35,8 +35,8 @@ open class AppInfo @Inject constructor() {
|
||||
open val systemLanguage: String
|
||||
get() = Resources.getSystem().configuration.locale.language
|
||||
|
||||
open val defaultColorsForAvatar = listOf(
|
||||
0xe57373, 0xf06292, 0xba68c8, 0x9575cd, 0x7986cb, 0x64b5f6, 0x4fc3f7, 0x4dd0e1, 0x4db6ac,
|
||||
0x81c784, 0xaed581, 0xff8a65, 0xd4e157, 0xffd54f, 0xffb74d, 0xa1887f, 0x90a4ae
|
||||
)
|
||||
val defaultColorsForAvatar = listOf(
|
||||
0xd32f2f, 0xE64A19, 0xFFA000, 0xAFB42B, 0x689F38, 0x388E3C, 0x00796B, 0x0097A7,
|
||||
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
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
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.text.TextPaint
|
||||
import android.util.DisplayMetrics.DENSITY_DEFAULT
|
||||
import androidx.annotation.AttrRes
|
||||
import androidx.annotation.ColorInt
|
||||
@ -10,6 +17,9 @@ import androidx.annotation.ColorRes
|
||||
import androidx.annotation.DrawableRes
|
||||
import androidx.core.content.ContextCompat
|
||||
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
|
||||
|
||||
@ColorInt
|
||||
@ -30,7 +40,8 @@ fun Context.getThemeAttrColor(@AttrRes colorAttr: Int, alpha: Int): Int {
|
||||
@ColorInt
|
||||
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) {
|
||||
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 {
|
||||
putExtra(Intent.EXTRA_EMAIL, arrayOf(email))
|
||||
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
|
||||
|
||||
@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,87 +1,123 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<View
|
||||
android:layout_width="280dp"
|
||||
android:layout_height="1dp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/accountEditDetailsHeader"
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="24dp"
|
||||
android:layout_marginTop="24dp"
|
||||
android:layout_marginEnd="24dp"
|
||||
android:text="@string/account_edit_header"
|
||||
android:textSize="21sp"
|
||||
android:textStyle="bold"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintHorizontal_bias="0"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:id="@+id/accountEditDetailsNick"
|
||||
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="24dp"
|
||||
android:layout_marginTop="28dp"
|
||||
android:layout_marginEnd="24dp"
|
||||
android:hint="@string/account_edit_nick_hint"
|
||||
app:endIconMode="clear_text"
|
||||
app:errorEnabled="true"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/accountEditDetailsHeader">
|
||||
<View
|
||||
android:layout_width="280dp"
|
||||
android:layout_height="1dp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<requestFocus />
|
||||
<TextView
|
||||
android:id="@+id/accountEditDetailsHeader"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="24dp"
|
||||
android:layout_marginTop="24dp"
|
||||
android:layout_marginEnd="24dp"
|
||||
android:text="@string/account_edit_header"
|
||||
android:textSize="21sp"
|
||||
android:textStyle="bold"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintHorizontal_bias="0"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:id="@+id/accountEditDetailsNickText"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:inputType="textPersonName"
|
||||
android:maxLength="20" />
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:id="@+id/accountEditDetailsNick"
|
||||
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="24dp"
|
||||
android:layout_marginTop="28dp"
|
||||
android:layout_marginEnd="24dp"
|
||||
android:hint="@string/account_edit_nick_hint"
|
||||
app:endIconMode="clear_text"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/accountEditDetailsHeader">
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/accountEditDetailsSave"
|
||||
style="@style/Widget.MaterialComponents.Button.TextButton.Dialog"
|
||||
android:layout_width="88dp"
|
||||
android:layout_height="36dp"
|
||||
android:layout_marginTop="36dp"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:insetLeft="0dp"
|
||||
android:insetTop="0dp"
|
||||
android:insetRight="0dp"
|
||||
android:insetBottom="0dp"
|
||||
android:text="@string/all_save"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/accountEditDetailsNick" />
|
||||
<requestFocus />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/accountEditDetailsCancel"
|
||||
style="@style/Widget.MaterialComponents.Button.TextButton.Dialog"
|
||||
android:layout_width="88dp"
|
||||
android:layout_height="36dp"
|
||||
android:layout_marginTop="36dp"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:insetLeft="0dp"
|
||||
android:insetTop="0dp"
|
||||
android:insetRight="0dp"
|
||||
android:insetBottom="0dp"
|
||||
android:text="@android:string/cancel"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@id/accountEditDetailsSave"
|
||||
app:layout_constraintTop_toBottomOf="@id/accountEditDetailsNick" />
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:id="@+id/accountEditDetailsNickText"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:inputType="textPersonName"
|
||||
android:maxLength="20" />
|
||||
</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
|
||||
android:id="@+id/accountEditDetailsSave"
|
||||
style="@style/Widget.MaterialComponents.Button.TextButton.Dialog"
|
||||
android:layout_width="88dp"
|
||||
android:layout_height="36dp"
|
||||
android:layout_marginTop="36dp"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:insetLeft="0dp"
|
||||
android:insetTop="0dp"
|
||||
android:insetRight="0dp"
|
||||
android:insetBottom="0dp"
|
||||
android:text="@string/all_save"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/account_edit_colors" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/accountEditDetailsCancel"
|
||||
style="@style/Widget.MaterialComponents.Button.TextButton.Dialog"
|
||||
android:layout_width="88dp"
|
||||
android:layout_height="36dp"
|
||||
android:layout_marginTop="36dp"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:insetLeft="0dp"
|
||||
android:insetTop="0dp"
|
||||
android:insetRight="0dp"
|
||||
android:insetBottom="0dp"
|
||||
android:text="@android:string/cancel"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@id/accountEditDetailsSave"
|
||||
app:layout_constraintTop_toBottomOf="@id/account_edit_colors" />
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
</ScrollView>
|
||||
|
@ -2,7 +2,7 @@
|
||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="300dp"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<LinearLayout
|
||||
|
@ -5,16 +5,6 @@
|
||||
android:layout_width="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
|
||||
android:id="@+id/accountRecycler"
|
||||
android:layout_width="match_parent"
|
||||
@ -36,55 +26,4 @@
|
||||
android:insetBottom="0dp"
|
||||
android:text="@string/account_add_new"
|
||||
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>
|
||||
|
@ -21,9 +21,9 @@
|
||||
android:layout_height="match_parent"
|
||||
android:gravity="center"
|
||||
android:orientation="vertical"
|
||||
android:visibility="invisible"
|
||||
android:visibility="gone"
|
||||
tools:ignore="UseCompoundDrawables"
|
||||
tools:visibility="visible">
|
||||
tools:visibility="gone">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="100dp"
|
||||
@ -69,7 +69,9 @@
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:id="@+id/account_details_content"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
android:layout_height="match_parent"
|
||||
android:visibility="gone"
|
||||
tools:visibility="visible">
|
||||
|
||||
<ScrollView
|
||||
android:layout_width="match_parent"
|
||||
@ -90,9 +92,22 @@
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:srcCompat="@drawable/ic_all_account"
|
||||
app:tint="?colorPrimary"
|
||||
tools:ignore="ContentDescription" />
|
||||
tools:ignore="ContentDescription"
|
||||
tools:src="@tools:sample/avatars" />
|
||||
|
||||
<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
|
||||
android:id="@+id/accountDetailsName"
|
||||
|
@ -18,15 +18,27 @@
|
||||
android:layout_height="40dp"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:srcCompat="@drawable/ic_all_account"
|
||||
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
|
||||
android:id="@+id/accountItemName"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="1"
|
||||
android:textSize="16sp"
|
||||
@ -41,6 +53,7 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginTop="3dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="1"
|
||||
android:textColor="?android:textColorSecondary"
|
||||
@ -56,6 +69,7 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginTop="3dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="1"
|
||||
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:background="?android:windowBackground"
|
||||
tools:context=".ui.modules.grade.statistics.GradeStatisticsAdapter" />
|
||||
|
||||
</LinearLayout>
|
||||
|
@ -1,11 +1,11 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<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
|
||||
android:id="@+id/mainMenuAccount"
|
||||
android:icon="@drawable/ic_all_account"
|
||||
android:orderInCategory="2"
|
||||
android:title="@string/main_account_picker"
|
||||
app:iconTint="@color/material_on_surface_emphasis_medium"
|
||||
app:showAsAction="always" />
|
||||
app:showAsAction="always"
|
||||
tools:ignore="MenuTitle" />
|
||||
</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.sdk.Sdk
|
||||
import io.github.wulkanowy.sdk.pojo.Student
|
||||
import io.github.wulkanowy.utils.AppInfo
|
||||
import io.mockk.MockKAnnotations
|
||||
import io.mockk.coEvery
|
||||
import io.mockk.impl.annotations.MockK
|
||||
@ -30,7 +31,14 @@ class StudentTest {
|
||||
@Before
|
||||
fun initApi() {
|
||||
MockKAnnotations.init(this)
|
||||
studentRepository = StudentRepository(mockk(), TestDispatchersProvider(), studentDb, semesterDb, mockSdk)
|
||||
studentRepository = StudentRepository(
|
||||
mockk(),
|
||||
TestDispatchersProvider(),
|
||||
studentDb,
|
||||
semesterDb,
|
||||
mockSdk,
|
||||
AppInfo()
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
|
2
gradle/wrapper/gradle-wrapper.properties
vendored
2
gradle/wrapper/gradle-wrapper.properties
vendored
@ -1,5 +1,5 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
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
|
||||
zipStorePath=wrapper/dists
|
||||
|
Loading…
x
Reference in New Issue
Block a user