forked from github/wulkanowy-mirror
Add admin messages (#1553)
This commit is contained in:
parent
d6918077bf
commit
e3122127c0
@ -66,12 +66,14 @@ android {
|
||||
shrinkResources true
|
||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||
signingConfig signingConfigs.release
|
||||
buildConfigField "String", "MESSAGES_BASE_URL", "\"https://messages.wulkanowy.net.pl\""
|
||||
}
|
||||
debug {
|
||||
resValue "string", "app_name", "Wulkanowy DEV " + defaultConfig.versionCode
|
||||
applicationIdSuffix ".dev"
|
||||
versionNameSuffix "-dev"
|
||||
ext.enableCrashlytics = project.hasProperty("enableFirebase")
|
||||
buildConfigField "String", "MESSAGES_BASE_URL", "\"https://messages.wulkanowy.net.pl\""
|
||||
}
|
||||
}
|
||||
|
||||
@ -170,7 +172,7 @@ ext {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation "io.github.wulkanowy:sdk:1.3.0"
|
||||
implementation "io.github.wulkanowy:sdk:4efd64264b"
|
||||
|
||||
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.5'
|
||||
|
||||
@ -212,6 +214,10 @@ dependencies {
|
||||
implementation 'com.github.ncapdevi:FragNav:3.3.0'
|
||||
implementation "com.github.YarikSOffice:lingver:1.3.0"
|
||||
|
||||
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
|
||||
implementation "com.jakewharton.retrofit:retrofit2-kotlinx-serialization-converter:0.8.0"
|
||||
implementation "com.squareup.okhttp3:logging-interceptor:4.9.2"
|
||||
|
||||
implementation "com.jakewharton.timber:timber:5.0.1"
|
||||
implementation "at.favre.lib:slf4j-timber:1.0.1"
|
||||
implementation 'com.github.bastienpaulfr:Treessence:1.0.5'
|
||||
@ -234,7 +240,7 @@ dependencies {
|
||||
releaseImplementation "com.github.ChuckerTeam.Chucker:library-no-op:$chucker"
|
||||
|
||||
debugImplementation "com.github.ChuckerTeam.Chucker:library:$chucker"
|
||||
debugImplementation 'com.github.amitshekhariitbhu.Android-Debug-Database:debug-db:v1.0.6'
|
||||
debugImplementation 'com.github.amitshekhariitbhu.Android-Debug-Database:debug-db:1.0.6'
|
||||
|
||||
testImplementation "junit:junit:4.13.2"
|
||||
testImplementation "io.mockk:mockk:$mockk"
|
||||
|
2396
app/schemas/io.github.wulkanowy.data.db.AppDatabase/42.json
Normal file
2396
app/schemas/io.github.wulkanowy.data.db.AppDatabase/42.json
Normal file
File diff suppressed because it is too large
Load Diff
@ -2,48 +2,49 @@ package io.github.wulkanowy.data
|
||||
|
||||
import android.content.Context
|
||||
import android.content.SharedPreferences
|
||||
import android.content.res.AssetManager
|
||||
import android.content.res.Resources
|
||||
import androidx.preference.PreferenceManager
|
||||
import com.chuckerteam.chucker.api.ChuckerCollector
|
||||
import com.chuckerteam.chucker.api.ChuckerInterceptor
|
||||
import com.chuckerteam.chucker.api.RetentionManager
|
||||
import com.fredporciuncula.flow.preferences.FlowSharedPreferences
|
||||
import com.jakewharton.retrofit2.converter.kotlinx.serialization.asConverterFactory
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import io.github.wulkanowy.data.api.AdminMessageService
|
||||
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 kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.serialization.ExperimentalSerializationApi
|
||||
import kotlinx.serialization.json.Json
|
||||
import okhttp3.MediaType.Companion.toMediaType
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.logging.HttpLoggingInterceptor
|
||||
import retrofit2.Retrofit
|
||||
import retrofit2.create
|
||||
import timber.log.Timber
|
||||
import java.util.concurrent.TimeUnit
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
internal class RepositoryModule {
|
||||
internal class DataModule {
|
||||
|
||||
@Singleton
|
||||
@Provides
|
||||
fun provideSdk(chuckerCollector: ChuckerCollector, @ApplicationContext context: Context): Sdk {
|
||||
return Sdk().apply {
|
||||
fun provideSdk(chuckerInterceptor: ChuckerInterceptor) =
|
||||
Sdk().apply {
|
||||
androidVersion = android.os.Build.VERSION.RELEASE
|
||||
buildTag = android.os.Build.MODEL
|
||||
setSimpleHttpLogger { Timber.d(it) }
|
||||
|
||||
// for debug only
|
||||
addInterceptor(
|
||||
ChuckerInterceptor.Builder(context)
|
||||
.collector(chuckerCollector)
|
||||
.alwaysReadResponseBody(true)
|
||||
.build(), network = true
|
||||
)
|
||||
}
|
||||
addInterceptor(chuckerInterceptor, network = true)
|
||||
}
|
||||
|
||||
@Singleton
|
||||
@ -51,13 +52,50 @@ internal class RepositoryModule {
|
||||
fun provideChuckerCollector(
|
||||
@ApplicationContext context: Context,
|
||||
prefRepository: PreferencesRepository
|
||||
): ChuckerCollector {
|
||||
return ChuckerCollector(
|
||||
) = ChuckerCollector(
|
||||
context = context,
|
||||
showNotification = prefRepository.isDebugNotificationEnable,
|
||||
retentionPeriod = RetentionManager.Period.ONE_HOUR
|
||||
)
|
||||
}
|
||||
|
||||
@Singleton
|
||||
@Provides
|
||||
fun provideChuckerInterceptor(
|
||||
@ApplicationContext context: Context,
|
||||
chuckerCollector: ChuckerCollector
|
||||
) = ChuckerInterceptor.Builder(context)
|
||||
.collector(chuckerCollector)
|
||||
.alwaysReadResponseBody(true)
|
||||
.build()
|
||||
|
||||
@Singleton
|
||||
@Provides
|
||||
fun provideOkHttpClient(chuckerInterceptor: ChuckerInterceptor): OkHttpClient =
|
||||
OkHttpClient.Builder()
|
||||
.addNetworkInterceptor(chuckerInterceptor)
|
||||
.addInterceptor(HttpLoggingInterceptor().apply {
|
||||
level = HttpLoggingInterceptor.Level.BASIC
|
||||
})
|
||||
.connectTimeout(30, TimeUnit.SECONDS)
|
||||
.readTimeout(30, TimeUnit.SECONDS)
|
||||
.build()
|
||||
|
||||
@OptIn(ExperimentalSerializationApi::class)
|
||||
@Singleton
|
||||
@Provides
|
||||
fun provideRetrofit(
|
||||
okHttpClient: OkHttpClient,
|
||||
json: Json,
|
||||
appInfo: AppInfo
|
||||
): Retrofit = Retrofit.Builder()
|
||||
.baseUrl(appInfo.messagesBaseUrl)
|
||||
.client(okHttpClient)
|
||||
.addConverterFactory(json.asConverterFactory("application/json".toMediaType()))
|
||||
.build()
|
||||
|
||||
@Singleton
|
||||
@Provides
|
||||
fun provideAdminMessageService(retrofit: Retrofit): AdminMessageService = retrofit.create()
|
||||
|
||||
@Singleton
|
||||
@Provides
|
||||
@ -67,14 +105,6 @@ internal class RepositoryModule {
|
||||
appInfo: AppInfo
|
||||
) = AppDatabase.newInstance(context, sharedPrefProvider, appInfo)
|
||||
|
||||
@Singleton
|
||||
@Provides
|
||||
fun provideResources(@ApplicationContext context: Context): Resources = context.resources
|
||||
|
||||
@Singleton
|
||||
@Provides
|
||||
fun provideAssets(@ApplicationContext context: Context): AssetManager = context.assets
|
||||
|
||||
@Singleton
|
||||
@Provides
|
||||
fun provideSharedPref(@ApplicationContext context: Context): SharedPreferences =
|
||||
@ -208,4 +238,8 @@ internal class RepositoryModule {
|
||||
@Singleton
|
||||
@Provides
|
||||
fun provideNotificationDao(database: AppDatabase) = database.notificationDao
|
||||
|
||||
@Singleton
|
||||
@Provides
|
||||
fun provideAdminMessageDao(database: AppDatabase) = database.adminMessagesDao
|
||||
}
|
@ -0,0 +1,12 @@
|
||||
package io.github.wulkanowy.data.api
|
||||
|
||||
import io.github.wulkanowy.data.db.entities.AdminMessage
|
||||
import retrofit2.http.GET
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
interface AdminMessageService {
|
||||
|
||||
@GET("/v1.json")
|
||||
suspend fun getAdminMessages(): List<AdminMessage>
|
||||
}
|
@ -6,6 +6,7 @@ import androidx.room.Room
|
||||
import androidx.room.RoomDatabase
|
||||
import androidx.room.RoomDatabase.JournalMode.TRUNCATE
|
||||
import androidx.room.TypeConverters
|
||||
import io.github.wulkanowy.data.db.dao.AdminMessageDao
|
||||
import io.github.wulkanowy.data.db.dao.AttendanceDao
|
||||
import io.github.wulkanowy.data.db.dao.AttendanceSummaryDao
|
||||
import io.github.wulkanowy.data.db.dao.CompletedLessonsDao
|
||||
@ -35,6 +36,7 @@ import io.github.wulkanowy.data.db.dao.TeacherDao
|
||||
import io.github.wulkanowy.data.db.dao.TimetableAdditionalDao
|
||||
import io.github.wulkanowy.data.db.dao.TimetableDao
|
||||
import io.github.wulkanowy.data.db.dao.TimetableHeaderDao
|
||||
import io.github.wulkanowy.data.db.entities.AdminMessage
|
||||
import io.github.wulkanowy.data.db.entities.Attendance
|
||||
import io.github.wulkanowy.data.db.entities.AttendanceSummary
|
||||
import io.github.wulkanowy.data.db.entities.CompletedLesson
|
||||
@ -97,8 +99,9 @@ import io.github.wulkanowy.data.db.migrations.Migration37
|
||||
import io.github.wulkanowy.data.db.migrations.Migration38
|
||||
import io.github.wulkanowy.data.db.migrations.Migration39
|
||||
import io.github.wulkanowy.data.db.migrations.Migration4
|
||||
import io.github.wulkanowy.data.db.migrations.Migration41
|
||||
import io.github.wulkanowy.data.db.migrations.Migration40
|
||||
import io.github.wulkanowy.data.db.migrations.Migration41
|
||||
import io.github.wulkanowy.data.db.migrations.Migration42
|
||||
import io.github.wulkanowy.data.db.migrations.Migration5
|
||||
import io.github.wulkanowy.data.db.migrations.Migration6
|
||||
import io.github.wulkanowy.data.db.migrations.Migration7
|
||||
@ -138,7 +141,8 @@ import javax.inject.Singleton
|
||||
StudentInfo::class,
|
||||
TimetableHeader::class,
|
||||
SchoolAnnouncement::class,
|
||||
Notification::class
|
||||
Notification::class,
|
||||
AdminMessage::class
|
||||
],
|
||||
version = AppDatabase.VERSION_SCHEMA,
|
||||
exportSchema = true
|
||||
@ -147,7 +151,7 @@ import javax.inject.Singleton
|
||||
abstract class AppDatabase : RoomDatabase() {
|
||||
|
||||
companion object {
|
||||
const val VERSION_SCHEMA = 41
|
||||
const val VERSION_SCHEMA = 42
|
||||
|
||||
fun getMigrations(sharedPrefProvider: SharedPrefProvider, appInfo: AppInfo) = arrayOf(
|
||||
Migration2(),
|
||||
@ -189,7 +193,8 @@ abstract class AppDatabase : RoomDatabase() {
|
||||
Migration38(),
|
||||
Migration39(),
|
||||
Migration40(),
|
||||
Migration41()
|
||||
Migration41(),
|
||||
Migration42()
|
||||
)
|
||||
|
||||
fun newInstance(
|
||||
@ -261,4 +266,6 @@ abstract class AppDatabase : RoomDatabase() {
|
||||
abstract val schoolAnnouncementDao: SchoolAnnouncementDao
|
||||
|
||||
abstract val notificationDao: NotificationDao
|
||||
|
||||
abstract val adminMessagesDao: AdminMessageDao
|
||||
}
|
||||
|
@ -0,0 +1,25 @@
|
||||
package io.github.wulkanowy.data.db.dao
|
||||
|
||||
import androidx.room.Dao
|
||||
import androidx.room.Query
|
||||
import androidx.room.Transaction
|
||||
import io.github.wulkanowy.data.db.entities.AdminMessage
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
@Dao
|
||||
abstract class AdminMessageDao : BaseDao<AdminMessage> {
|
||||
|
||||
@Query("SELECT * FROM AdminMessages")
|
||||
abstract fun loadAll(): Flow<List<AdminMessage>>
|
||||
|
||||
@Transaction
|
||||
open suspend fun removeOldAndSaveNew(
|
||||
oldMessages: List<AdminMessage>,
|
||||
newMessages: List<AdminMessage>
|
||||
) {
|
||||
deleteAll(oldMessages)
|
||||
insertAll(newMessages)
|
||||
}
|
||||
}
|
@ -0,0 +1,37 @@
|
||||
package io.github.wulkanowy.data.db.entities
|
||||
|
||||
import androidx.room.ColumnInfo
|
||||
import androidx.room.Entity
|
||||
import androidx.room.PrimaryKey
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
@Entity(tableName = "AdminMessages")
|
||||
data class AdminMessage(
|
||||
|
||||
@PrimaryKey
|
||||
val id: Int,
|
||||
|
||||
val title: String,
|
||||
|
||||
val content: String,
|
||||
|
||||
@ColumnInfo(name = "version_name")
|
||||
val versionMin: Int? = null,
|
||||
|
||||
@ColumnInfo(name = "version_max")
|
||||
val versionMax: Int? = null,
|
||||
|
||||
@ColumnInfo(name = "target_register_host")
|
||||
val targetRegisterHost: String? = null,
|
||||
|
||||
@ColumnInfo(name = "target_flavor")
|
||||
val targetFlavor: String? = null,
|
||||
|
||||
@ColumnInfo(name = "destination_url")
|
||||
val destinationUrl: String? = null,
|
||||
|
||||
val priority: String,
|
||||
|
||||
val type: String
|
||||
)
|
@ -0,0 +1,24 @@
|
||||
package io.github.wulkanowy.data.db.migrations
|
||||
|
||||
import androidx.room.migration.Migration
|
||||
import androidx.sqlite.db.SupportSQLiteDatabase
|
||||
|
||||
class Migration42 : Migration(41, 42) {
|
||||
|
||||
override fun migrate(database: SupportSQLiteDatabase) {
|
||||
database.execSQL(
|
||||
"""CREATE TABLE IF NOT EXISTS `AdminMessages` (
|
||||
`id` INTEGER NOT NULL,
|
||||
`title` TEXT NOT NULL,
|
||||
`content` TEXT NOT NULL,
|
||||
`version_name` INTEGER,
|
||||
`version_max` INTEGER,
|
||||
`target_register_host` TEXT,
|
||||
`target_flavor` TEXT,
|
||||
`destination_url` TEXT,
|
||||
`priority` TEXT NOT NULL,
|
||||
`type` TEXT NOT NULL,
|
||||
PRIMARY KEY(`id`))"""
|
||||
)
|
||||
}
|
||||
}
|
@ -0,0 +1,53 @@
|
||||
package io.github.wulkanowy.data.repositories
|
||||
|
||||
import io.github.wulkanowy.data.api.AdminMessageService
|
||||
import io.github.wulkanowy.data.db.dao.AdminMessageDao
|
||||
import io.github.wulkanowy.data.db.entities.Student
|
||||
import io.github.wulkanowy.utils.AppInfo
|
||||
import io.github.wulkanowy.utils.AutoRefreshHelper
|
||||
import io.github.wulkanowy.utils.getRefreshKey
|
||||
import io.github.wulkanowy.utils.networkBoundResource
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
class AdminMessageRepository @Inject constructor(
|
||||
private val adminMessageService: AdminMessageService,
|
||||
private val adminMessageDao: AdminMessageDao,
|
||||
private val appInfo: AppInfo,
|
||||
private val refreshHelper: AutoRefreshHelper,
|
||||
) {
|
||||
private val saveFetchResultMutex = Mutex()
|
||||
|
||||
private val cacheKey = "admin_messages"
|
||||
|
||||
suspend fun getAdminMessages(student: Student, forceRefresh: Boolean) = networkBoundResource(
|
||||
mutex = saveFetchResultMutex,
|
||||
query = { adminMessageDao.loadAll() },
|
||||
fetch = { adminMessageService.getAdminMessages() },
|
||||
shouldFetch = {
|
||||
refreshHelper.shouldBeRefreshed(getRefreshKey(cacheKey, student)) || forceRefresh
|
||||
},
|
||||
saveFetchResult = { oldItems, newItems ->
|
||||
adminMessageDao.removeOldAndSaveNew(oldItems, newItems)
|
||||
refreshHelper.updateLastRefreshTimestamp(cacheKey)
|
||||
},
|
||||
showSavedOnLoading = false,
|
||||
mapResult = { adminMessages ->
|
||||
adminMessages.filter { adminMessage ->
|
||||
val isCorrectRegister = adminMessage.targetRegisterHost?.let {
|
||||
student.scrapperBaseUrl.contains(it, true)
|
||||
} ?: true
|
||||
val isCorrectFlavor =
|
||||
adminMessage.targetFlavor?.equals(appInfo.buildFlavor, true) ?: true
|
||||
val isCorrectMaxVersion =
|
||||
adminMessage.versionMax?.let { it >= appInfo.versionCode } ?: true
|
||||
val isCorrectMinVersion =
|
||||
adminMessage.versionMin?.let { it <= appInfo.versionCode } ?: true
|
||||
|
||||
isCorrectRegister && isCorrectFlavor && isCorrectMaxVersion && isCorrectMinVersion
|
||||
}.maxByOrNull { it.id }
|
||||
}
|
||||
)
|
||||
}
|
@ -1,6 +1,7 @@
|
||||
package io.github.wulkanowy.data.repositories
|
||||
|
||||
import android.content.res.AssetManager
|
||||
import android.content.Context
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import io.github.wulkanowy.data.pojos.Contributor
|
||||
import io.github.wulkanowy.utils.DispatchersProvider
|
||||
import kotlinx.coroutines.withContext
|
||||
@ -12,7 +13,7 @@ import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
class AppCreatorRepository @Inject constructor(
|
||||
private val assets: AssetManager,
|
||||
@ApplicationContext private val context: Context,
|
||||
private val dispatchers: DispatchersProvider,
|
||||
private val json: Json,
|
||||
) {
|
||||
@ -20,7 +21,7 @@ class AppCreatorRepository @Inject constructor(
|
||||
@OptIn(ExperimentalSerializationApi::class)
|
||||
@Suppress("BlockingMethodInNonBlockingContext")
|
||||
suspend fun getAppCreators() = withContext(dispatchers.backgroundThread) {
|
||||
val inputStream = assets.open("contributors.json").buffered()
|
||||
val inputStream = context.assets.open("contributors.json").buffered()
|
||||
json.decodeFromStream<List<Contributor>>(inputStream)
|
||||
}
|
||||
}
|
||||
|
@ -209,6 +209,7 @@ class PreferencesRepository @Inject constructor(
|
||||
.map { set ->
|
||||
set.map { DashboardItem.Tile.valueOf(it) }
|
||||
.plus(DashboardItem.Tile.ACCOUNT)
|
||||
.plus(DashboardItem.Tile.ADMIN_MESSAGE)
|
||||
.toSet()
|
||||
}
|
||||
|
||||
@ -216,6 +217,7 @@ class PreferencesRepository @Inject constructor(
|
||||
get() = selectedDashboardTilesPreference.get()
|
||||
.map { DashboardItem.Tile.valueOf(it) }
|
||||
.plus(DashboardItem.Tile.ACCOUNT)
|
||||
.plus(DashboardItem.Tile.ADMIN_MESSAGE)
|
||||
.toSet()
|
||||
set(value) {
|
||||
val filteredValue = value.filterNot { it == DashboardItem.Tile.ACCOUNT }
|
||||
|
@ -1,6 +1,7 @@
|
||||
package io.github.wulkanowy.ui.base
|
||||
|
||||
import android.content.res.Resources
|
||||
import android.content.Context
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import io.github.wulkanowy.data.exceptions.NoCurrentStudentException
|
||||
import io.github.wulkanowy.sdk.scrapper.login.BadCredentialsException
|
||||
import io.github.wulkanowy.sdk.scrapper.login.PasswordChangeRequiredException
|
||||
@ -9,7 +10,7 @@ import io.github.wulkanowy.utils.security.ScramblerException
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
open class ErrorHandler @Inject constructor(protected val resources: Resources) {
|
||||
open class ErrorHandler @Inject constructor(@ApplicationContext protected val context: Context) {
|
||||
|
||||
var showErrorMessage: (String, Throwable) -> Unit = { _, _ -> }
|
||||
|
||||
@ -25,7 +26,7 @@ open class ErrorHandler @Inject constructor(protected val resources: Resources)
|
||||
}
|
||||
|
||||
protected open fun proceed(error: Throwable) {
|
||||
showErrorMessage(resources.getString(error), error)
|
||||
showErrorMessage(context.resources.getString(error), error)
|
||||
when (error) {
|
||||
is PasswordChangeRequiredException -> onPasswordChangeRequired(error.redirectUrl)
|
||||
is ScramblerException, is BadCredentialsException -> onSessionExpired()
|
||||
|
@ -1,6 +1,8 @@
|
||||
package io.github.wulkanowy.ui.modules.dashboard
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.res.ColorStateList
|
||||
import android.graphics.Color
|
||||
import android.graphics.Typeface
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
@ -18,6 +20,7 @@ import io.github.wulkanowy.data.db.entities.Student
|
||||
import io.github.wulkanowy.data.db.entities.Timetable
|
||||
import io.github.wulkanowy.data.db.entities.TimetableHeader
|
||||
import io.github.wulkanowy.databinding.ItemDashboardAccountBinding
|
||||
import io.github.wulkanowy.databinding.ItemDashboardAdminMessageBinding
|
||||
import io.github.wulkanowy.databinding.ItemDashboardAnnouncementsBinding
|
||||
import io.github.wulkanowy.databinding.ItemDashboardConferencesBinding
|
||||
import io.github.wulkanowy.databinding.ItemDashboardExamsBinding
|
||||
@ -63,6 +66,8 @@ class DashboardAdapter @Inject constructor() : RecyclerView.Adapter<RecyclerView
|
||||
|
||||
var onConferencesTileClickListener: () -> Unit = {}
|
||||
|
||||
var onAdminMessageClickListener: (String?) -> Unit = {}
|
||||
|
||||
val items = mutableListOf<DashboardItem>()
|
||||
|
||||
fun submitList(newItems: List<DashboardItem>) {
|
||||
@ -109,6 +114,9 @@ class DashboardAdapter @Inject constructor() : RecyclerView.Adapter<RecyclerView
|
||||
DashboardItem.Type.CONFERENCES.ordinal -> ConferencesViewHolder(
|
||||
ItemDashboardConferencesBinding.inflate(inflater, parent, false)
|
||||
)
|
||||
DashboardItem.Type.ADMIN_MESSAGE.ordinal -> AdminMessageViewHolder(
|
||||
ItemDashboardAdminMessageBinding.inflate(inflater, parent, false)
|
||||
)
|
||||
else -> throw IllegalArgumentException()
|
||||
}
|
||||
}
|
||||
@ -123,6 +131,7 @@ class DashboardAdapter @Inject constructor() : RecyclerView.Adapter<RecyclerView
|
||||
is AnnouncementsViewHolder -> bindAnnouncementsViewHolder(holder, position)
|
||||
is ExamsViewHolder -> bindExamsViewHolder(holder, position)
|
||||
is ConferencesViewHolder -> bindConferencesViewHolder(holder, position)
|
||||
is AdminMessageViewHolder -> bindAdminMessage(holder, position)
|
||||
}
|
||||
}
|
||||
|
||||
@ -697,6 +706,34 @@ class DashboardAdapter @Inject constructor() : RecyclerView.Adapter<RecyclerView
|
||||
}
|
||||
}
|
||||
|
||||
private fun bindAdminMessage(adminMessageViewHolder: AdminMessageViewHolder, position: Int) {
|
||||
val item = (items[position] as DashboardItem.AdminMessages).adminMessage ?: return
|
||||
val context = adminMessageViewHolder.binding.root.context
|
||||
val (backgroundColor, textColor) = when (item.priority) {
|
||||
"HIGH" -> {
|
||||
context.getThemeAttrColor(R.attr.colorPrimary) to
|
||||
context.getThemeAttrColor(R.attr.colorOnPrimary)
|
||||
}
|
||||
"MEDIUM" -> {
|
||||
context.getThemeAttrColor(R.attr.colorMessageMedium) to Color.BLACK
|
||||
}
|
||||
else -> null to context.getThemeAttrColor(R.attr.colorOnSurface)
|
||||
}
|
||||
|
||||
with(adminMessageViewHolder.binding) {
|
||||
dashboardAdminMessageItemTitle.text = item.title
|
||||
dashboardAdminMessageItemTitle.setTextColor(textColor)
|
||||
dashboardAdminMessageItemDescription.text = item.content
|
||||
dashboardAdminMessageItemDescription.setTextColor(textColor)
|
||||
dashboardAdminMessageItemIcon.setColorFilter(textColor)
|
||||
|
||||
root.setCardBackgroundColor(backgroundColor?.let { ColorStateList.valueOf(it) })
|
||||
item.destinationUrl?.let { url ->
|
||||
root.setOnClickListener { onAdminMessageClickListener(url) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class AccountViewHolder(val binding: ItemDashboardAccountBinding) :
|
||||
RecyclerView.ViewHolder(binding.root)
|
||||
|
||||
@ -736,6 +773,9 @@ class DashboardAdapter @Inject constructor() : RecyclerView.Adapter<RecyclerView
|
||||
val adapter by lazy { DashboardConferencesAdapter() }
|
||||
}
|
||||
|
||||
class AdminMessageViewHolder(val binding: ItemDashboardAdminMessageBinding) :
|
||||
RecyclerView.ViewHolder(binding.root)
|
||||
|
||||
private class DiffCallback(
|
||||
private val newList: List<DashboardItem>,
|
||||
private val oldList: List<DashboardItem>
|
||||
|
@ -10,6 +10,7 @@ import androidx.core.view.isVisible
|
||||
import androidx.recyclerview.widget.DefaultItemAnimator
|
||||
import androidx.recyclerview.widget.ItemTouchHelper
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import io.github.wulkanowy.R
|
||||
import io.github.wulkanowy.databinding.FragmentDashboardBinding
|
||||
@ -29,6 +30,7 @@ import io.github.wulkanowy.ui.modules.schoolannouncement.SchoolAnnouncementFragm
|
||||
import io.github.wulkanowy.ui.modules.timetable.TimetableFragment
|
||||
import io.github.wulkanowy.utils.capitalise
|
||||
import io.github.wulkanowy.utils.getThemeAttrColor
|
||||
import io.github.wulkanowy.utils.openInternetBrowser
|
||||
import io.github.wulkanowy.utils.toFormattedString
|
||||
import java.time.LocalDate
|
||||
import javax.inject.Inject
|
||||
@ -97,6 +99,13 @@ class DashboardFragment : BaseFragment<FragmentDashboardBinding>(R.layout.fragme
|
||||
onConferencesTileClickListener = {
|
||||
mainActivity.pushView(ConferenceFragment.newInstance())
|
||||
}
|
||||
onAdminMessageClickListener = presenter::onAdminMessageSelected
|
||||
|
||||
registerAdapterDataObserver(object : RecyclerView.AdapterDataObserver() {
|
||||
override fun onItemRangeInserted(positionStart: Int, itemCount: Int) {
|
||||
binding.dashboardRecycler.scrollToPosition(0)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
with(binding) {
|
||||
@ -188,6 +197,10 @@ class DashboardFragment : BaseFragment<FragmentDashboardBinding>(R.layout.fragme
|
||||
(requireActivity() as MainActivity).pushView(NotificationsCenterFragment.newInstance())
|
||||
}
|
||||
|
||||
override fun openInternetBrowser(url: String) {
|
||||
requireContext().openInternetBrowser(url)
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
dashboardAdapter.clearTimers()
|
||||
presenter.onDetachView()
|
||||
|
@ -1,5 +1,6 @@
|
||||
package io.github.wulkanowy.ui.modules.dashboard
|
||||
|
||||
import io.github.wulkanowy.data.db.entities.AdminMessage
|
||||
import io.github.wulkanowy.data.db.entities.Conference
|
||||
import io.github.wulkanowy.data.db.entities.Exam
|
||||
import io.github.wulkanowy.data.db.entities.Grade
|
||||
@ -16,6 +17,15 @@ sealed class DashboardItem(val type: Type) {
|
||||
|
||||
abstract val isDataLoaded: Boolean
|
||||
|
||||
data class AdminMessages(
|
||||
val adminMessage: AdminMessage? = null,
|
||||
override val error: Throwable? = null,
|
||||
override val isLoading: Boolean = false
|
||||
) : DashboardItem(Type.ADMIN_MESSAGE) {
|
||||
|
||||
override val isDataLoaded get() = adminMessage != null
|
||||
}
|
||||
|
||||
data class Account(
|
||||
val student: Student? = null,
|
||||
override val error: Throwable? = null,
|
||||
@ -96,6 +106,7 @@ sealed class DashboardItem(val type: Type) {
|
||||
}
|
||||
|
||||
enum class Type {
|
||||
ADMIN_MESSAGE,
|
||||
ACCOUNT,
|
||||
HORIZONTAL_GROUP,
|
||||
LESSONS,
|
||||
@ -108,6 +119,7 @@ sealed class DashboardItem(val type: Type) {
|
||||
}
|
||||
|
||||
enum class Tile {
|
||||
ADMIN_MESSAGE,
|
||||
ACCOUNT,
|
||||
LUCKY_NUMBER,
|
||||
MESSAGES,
|
||||
@ -123,6 +135,7 @@ sealed class DashboardItem(val type: Type) {
|
||||
}
|
||||
|
||||
fun DashboardItem.Tile.toDashboardItemType() = when (this) {
|
||||
DashboardItem.Tile.ADMIN_MESSAGE -> DashboardItem.Type.ADMIN_MESSAGE
|
||||
DashboardItem.Tile.ACCOUNT -> DashboardItem.Type.ACCOUNT
|
||||
DashboardItem.Tile.LUCKY_NUMBER -> DashboardItem.Type.HORIZONTAL_GROUP
|
||||
DashboardItem.Tile.MESSAGES -> DashboardItem.Type.HORIZONTAL_GROUP
|
||||
|
@ -21,7 +21,7 @@ class DashboardItemMoveCallback(
|
||||
recyclerView: RecyclerView,
|
||||
viewHolder: RecyclerView.ViewHolder
|
||||
): Int {
|
||||
val dragFlags = if (viewHolder.bindingAdapterPosition != 0) {
|
||||
val dragFlags = if (!viewHolder.isAdminMessageOrAccountItem) {
|
||||
ItemTouchHelper.UP or ItemTouchHelper.DOWN
|
||||
} else 0
|
||||
|
||||
@ -32,7 +32,7 @@ class DashboardItemMoveCallback(
|
||||
recyclerView: RecyclerView,
|
||||
current: RecyclerView.ViewHolder,
|
||||
target: RecyclerView.ViewHolder
|
||||
) = target.bindingAdapterPosition != 0
|
||||
) = !target.isAdminMessageOrAccountItem
|
||||
|
||||
override fun onMove(
|
||||
recyclerView: RecyclerView,
|
||||
@ -52,4 +52,7 @@ class DashboardItemMoveCallback(
|
||||
|
||||
onUserInteractionEndListener(dashboardAdapter.items.toList())
|
||||
}
|
||||
|
||||
private val RecyclerView.ViewHolder.isAdminMessageOrAccountItem: Boolean
|
||||
get() = this is DashboardAdapter.AdminMessageViewHolder || this is DashboardAdapter.AccountViewHolder
|
||||
}
|
||||
|
@ -5,6 +5,7 @@ import io.github.wulkanowy.data.Status
|
||||
import io.github.wulkanowy.data.db.entities.LuckyNumber
|
||||
import io.github.wulkanowy.data.db.entities.Student
|
||||
import io.github.wulkanowy.data.enums.MessageFolder
|
||||
import io.github.wulkanowy.data.repositories.AdminMessageRepository
|
||||
import io.github.wulkanowy.data.repositories.AttendanceSummaryRepository
|
||||
import io.github.wulkanowy.data.repositories.ConferenceRepository
|
||||
import io.github.wulkanowy.data.repositories.ExamRepository
|
||||
@ -50,7 +51,8 @@ class DashboardPresenter @Inject constructor(
|
||||
private val examRepository: ExamRepository,
|
||||
private val conferenceRepository: ConferenceRepository,
|
||||
private val preferencesRepository: PreferencesRepository,
|
||||
private val schoolAnnouncementRepository: SchoolAnnouncementRepository
|
||||
private val schoolAnnouncementRepository: SchoolAnnouncementRepository,
|
||||
private val adminMessageRepository: AdminMessageRepository
|
||||
) : BasePresenter<DashboardView>(errorHandler, studentRepository) {
|
||||
|
||||
private val dashboardItemLoadedList = mutableListOf<DashboardItem>()
|
||||
@ -179,6 +181,7 @@ class DashboardPresenter @Inject constructor(
|
||||
loadConferences(student, forceRefresh)
|
||||
}
|
||||
DashboardItem.Type.ADS -> TODO()
|
||||
DashboardItem.Type.ADMIN_MESSAGE -> loadAdminMessage(student, forceRefresh)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -225,6 +228,10 @@ class DashboardPresenter @Inject constructor(
|
||||
}.toSet()
|
||||
}
|
||||
|
||||
fun onAdminMessageSelected(url: String?) {
|
||||
url?.let { view?.openInternetBrowser(it) }
|
||||
}
|
||||
|
||||
private fun loadHorizontalGroup(student: Student, forceRefresh: Boolean) {
|
||||
flow {
|
||||
val semester = semesterRepository.getCurrentSemester(student)
|
||||
@ -567,6 +574,38 @@ class DashboardPresenter @Inject constructor(
|
||||
}.launch("dashboard_conferences")
|
||||
}
|
||||
|
||||
private fun loadAdminMessage(student: Student, forceRefresh: Boolean) {
|
||||
flowWithResourceIn { adminMessageRepository.getAdminMessages(student, forceRefresh) }
|
||||
.onEach {
|
||||
when (it.status) {
|
||||
Status.LOADING -> {
|
||||
Timber.i("Loading dashboard admin message data started")
|
||||
if (forceRefresh) return@onEach
|
||||
updateData(DashboardItem.AdminMessages(), forceRefresh)
|
||||
}
|
||||
Status.SUCCESS -> {
|
||||
Timber.i("Loading dashboard admin message result: Success")
|
||||
updateData(
|
||||
dashboardItem = DashboardItem.AdminMessages(adminMessage = it.data),
|
||||
forceRefresh = forceRefresh
|
||||
)
|
||||
}
|
||||
Status.ERROR -> {
|
||||
Timber.i("Loading dashboard admin message result: An exception occurred")
|
||||
errorHandler.dispatch(it.error!!)
|
||||
updateData(
|
||||
dashboardItem = DashboardItem.AdminMessages(
|
||||
adminMessage = it.data,
|
||||
error = it.error
|
||||
),
|
||||
forceRefresh = forceRefresh
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
.launch("dashboard_admin_messages")
|
||||
}
|
||||
|
||||
private fun updateData(dashboardItem: DashboardItem, forceRefresh: Boolean) {
|
||||
val isForceRefreshError = forceRefresh && dashboardItem.error != null
|
||||
val isFirstRunDataLoadedError =
|
||||
@ -579,6 +618,11 @@ class DashboardPresenter @Inject constructor(
|
||||
|
||||
sortDashboardItems()
|
||||
|
||||
if (dashboardItem is DashboardItem.AdminMessages && !dashboardItem.isDataLoaded) {
|
||||
dashboardItemsToLoad = dashboardItemsToLoad - DashboardItem.Type.ADMIN_MESSAGE
|
||||
dashboardItemLoadedList.removeAll { it.type == DashboardItem.Type.ADMIN_MESSAGE }
|
||||
}
|
||||
|
||||
if (forceRefresh) {
|
||||
updateForceRefreshData(dashboardItem)
|
||||
} else {
|
||||
@ -644,7 +688,9 @@ class DashboardPresenter @Inject constructor(
|
||||
itemsLoadedList: List<DashboardItem>,
|
||||
forceRefresh: Boolean
|
||||
) {
|
||||
val filteredItems = itemsLoadedList.filterNot { it.type == DashboardItem.Type.ACCOUNT }
|
||||
val filteredItems = itemsLoadedList.filterNot {
|
||||
it.type == DashboardItem.Type.ACCOUNT || it.type == DashboardItem.Type.ADMIN_MESSAGE
|
||||
}
|
||||
val isAccountItemError =
|
||||
itemsLoadedList.find { it.type == DashboardItem.Type.ACCOUNT }?.error != null
|
||||
val isGeneralError =
|
||||
@ -676,10 +722,13 @@ class DashboardPresenter @Inject constructor(
|
||||
val dashboardItemsPosition = preferencesRepository.dashboardItemsPosition
|
||||
|
||||
dashboardItemLoadedList.sortBy { tile ->
|
||||
dashboardItemsPosition?.getOrDefault(
|
||||
tile.type,
|
||||
val defaultPosition = if (tile is DashboardItem.AdminMessages) {
|
||||
-1
|
||||
} else {
|
||||
tile.type.ordinal + 100
|
||||
) ?: tile.type.ordinal
|
||||
}
|
||||
|
||||
dashboardItemsPosition?.getOrDefault(tile.type, defaultPosition) ?: tile.type.ordinal
|
||||
}
|
||||
}
|
||||
}
|
@ -25,4 +25,6 @@ interface DashboardView : BaseView {
|
||||
fun popViewToRoot()
|
||||
|
||||
fun openNotificationsCenterView()
|
||||
|
||||
fun openInternetBrowser(url: String)
|
||||
}
|
@ -1,7 +1,8 @@
|
||||
package io.github.wulkanowy.ui.modules.login
|
||||
|
||||
import android.content.res.Resources
|
||||
import android.content.Context
|
||||
import android.database.sqlite.SQLiteConstraintException
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import io.github.wulkanowy.R
|
||||
import io.github.wulkanowy.sdk.mobile.exception.InvalidPinException
|
||||
import io.github.wulkanowy.sdk.mobile.exception.InvalidSymbolException
|
||||
@ -11,7 +12,8 @@ import io.github.wulkanowy.sdk.scrapper.login.BadCredentialsException
|
||||
import io.github.wulkanowy.ui.base.ErrorHandler
|
||||
import javax.inject.Inject
|
||||
|
||||
class LoginErrorHandler @Inject constructor(resources: Resources) : ErrorHandler(resources) {
|
||||
class LoginErrorHandler @Inject constructor(@ApplicationContext context: Context) :
|
||||
ErrorHandler(context) {
|
||||
|
||||
var onBadCredentials: (String?) -> Unit = {}
|
||||
|
||||
@ -24,6 +26,7 @@ class LoginErrorHandler @Inject constructor(resources: Resources) : ErrorHandler
|
||||
var onStudentDuplicate: (String) -> Unit = {}
|
||||
|
||||
override fun proceed(error: Throwable) {
|
||||
val resources = context.resources
|
||||
when (error) {
|
||||
is BadCredentialsException -> onBadCredentials(error.message)
|
||||
is SQLiteConstraintException -> onStudentDuplicate(resources.getString(R.string.login_duplicate_student))
|
||||
|
@ -1,13 +1,15 @@
|
||||
package io.github.wulkanowy.ui.modules.login.recover
|
||||
|
||||
import android.content.res.Resources
|
||||
import android.content.Context
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import io.github.wulkanowy.sdk.scrapper.exception.InvalidCaptchaException
|
||||
import io.github.wulkanowy.sdk.scrapper.exception.InvalidEmailException
|
||||
import io.github.wulkanowy.sdk.scrapper.exception.NoAccountFoundException
|
||||
import io.github.wulkanowy.ui.base.ErrorHandler
|
||||
import javax.inject.Inject
|
||||
|
||||
class RecoverErrorHandler @Inject constructor(resources: Resources) : ErrorHandler(resources) {
|
||||
class RecoverErrorHandler @Inject constructor(@ApplicationContext context: Context) :
|
||||
ErrorHandler(context) {
|
||||
|
||||
var onInvalidUsername: (String) -> Unit = {}
|
||||
|
||||
@ -15,7 +17,8 @@ class RecoverErrorHandler @Inject constructor(resources: Resources) : ErrorHandl
|
||||
|
||||
override fun proceed(error: Throwable) {
|
||||
when (error) {
|
||||
is InvalidEmailException, is NoAccountFoundException -> onInvalidUsername(error.localizedMessage.orEmpty())
|
||||
is InvalidEmailException,
|
||||
is NoAccountFoundException -> onInvalidUsername(error.localizedMessage.orEmpty())
|
||||
is InvalidCaptchaException -> onInvalidCaptcha(error.localizedMessage.orEmpty(), error)
|
||||
else -> super.proceed(error)
|
||||
}
|
||||
|
@ -1,11 +1,13 @@
|
||||
package io.github.wulkanowy.ui.modules.timetable.completed
|
||||
|
||||
import android.content.res.Resources
|
||||
import android.content.Context
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import io.github.wulkanowy.sdk.scrapper.exception.FeatureDisabledException
|
||||
import io.github.wulkanowy.ui.base.ErrorHandler
|
||||
import javax.inject.Inject
|
||||
|
||||
class CompletedLessonsErrorHandler @Inject constructor(resources: Resources) : ErrorHandler(resources) {
|
||||
class CompletedLessonsErrorHandler @Inject constructor(@ApplicationContext context: Context) :
|
||||
ErrorHandler(context) {
|
||||
|
||||
var onFeatureDisabled: () -> Unit = {}
|
||||
|
||||
|
@ -1,35 +1,31 @@
|
||||
package io.github.wulkanowy.utils
|
||||
|
||||
import android.content.res.Resources
|
||||
import android.os.Build.MANUFACTURER
|
||||
import android.os.Build.MODEL
|
||||
import android.os.Build.VERSION.SDK_INT
|
||||
import io.github.wulkanowy.BuildConfig.BUILD_TIMESTAMP
|
||||
import io.github.wulkanowy.BuildConfig.DEBUG
|
||||
import io.github.wulkanowy.BuildConfig.FLAVOR
|
||||
import io.github.wulkanowy.BuildConfig.VERSION_CODE
|
||||
import io.github.wulkanowy.BuildConfig.VERSION_NAME
|
||||
import android.os.Build
|
||||
import io.github.wulkanowy.BuildConfig
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
open class AppInfo @Inject constructor() {
|
||||
|
||||
open val isDebug get() = DEBUG
|
||||
open val isDebug get() = BuildConfig.DEBUG
|
||||
|
||||
open val versionCode get() = VERSION_CODE
|
||||
open val versionCode get() = BuildConfig.VERSION_CODE
|
||||
|
||||
open val buildTimestamp get() = BUILD_TIMESTAMP
|
||||
open val buildTimestamp get() = BuildConfig.BUILD_TIMESTAMP
|
||||
|
||||
open val buildFlavor get() = FLAVOR
|
||||
open val buildFlavor get() = BuildConfig.FLAVOR
|
||||
|
||||
open val versionName get() = VERSION_NAME
|
||||
open val versionName get() = BuildConfig.VERSION_NAME
|
||||
|
||||
open val systemVersion get() = SDK_INT
|
||||
open val systemVersion get() = Build.VERSION.SDK_INT
|
||||
|
||||
open val systemManufacturer: String get() = MANUFACTURER
|
||||
open val systemManufacturer: String get() = Build.MANUFACTURER
|
||||
|
||||
open val systemModel: String get() = MODEL
|
||||
open val systemModel: String get() = Build.MODEL
|
||||
|
||||
open val messagesBaseUrl = BuildConfig.MESSAGES_BASE_URL
|
||||
|
||||
@Suppress("DEPRECATION")
|
||||
open val systemLanguage: String
|
||||
|
10
app/src/main/res/drawable/ic_dashboard_warning.xml
Normal file
10
app/src/main/res/drawable/ic_dashboard_warning.xml
Normal file
@ -0,0 +1,10 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24"
|
||||
android:tint="?attr/colorControlNormal">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M12,5.99L19.53,19L4.47,19L12,5.99M2.74,18c-0.77,1.33 0.19,3 1.73,3h15.06c1.54,0 2.5,-1.67 1.73,-3L13.73,4.99c-0.77,-1.33 -2.69,-1.33 -3.46,0L2.74,18zM11,11v2c0,0.55 0.45,1 1,1s1,-0.45 1,-1v-2c0,-0.55 -0.45,-1 -1,-1s-1,0.45 -1,1zM11,16h2v2h-2z" />
|
||||
</vector>
|
60
app/src/main/res/layout/item_dashboard_admin_message.xml
Normal file
60
app/src/main/res/layout/item_dashboard_admin_message.xml
Normal file
@ -0,0 +1,60 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<com.google.android.material.card.MaterialCardView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginHorizontal="12dp"
|
||||
android:layout_marginVertical="6dp"
|
||||
app:cardElevation="4dp">
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:id="@+id/dashboard_admin_message_item_content"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/dashboard_admin_message_item_icon"
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:src="@drawable/ic_error"
|
||||
app:layout_constraintBottom_toBottomOf="@id/dashboard_admin_message_item_title"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="@id/dashboard_admin_message_item_title"
|
||||
tools:ignore="ContentDescription"
|
||||
tools:tint="@android:color/black" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/dashboard_admin_message_item_title"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="8dp"
|
||||
android:layout_marginTop="16dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:textStyle="bold"
|
||||
android:textSize="18sp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@id/dashboard_admin_message_item_icon"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:text="@tools:sample/lorem" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/dashboard_admin_message_item_description"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginTop="8dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:textSize="14sp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/dashboard_admin_message_item_title"
|
||||
app:lineHeight="20dp"
|
||||
tools:maxLines="5"
|
||||
tools:text="@tools:sample/lorem/random" />
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
</com.google.android.material.card.MaterialCardView>
|
@ -8,6 +8,7 @@
|
||||
<item name="colorError">@color/colorErrorLight</item>
|
||||
<item name="colorDivider">@color/colorDividerInverse</item>
|
||||
<item name="colorSwipeRefresh">@color/colorSwipeRefreshDark</item>
|
||||
<item name="colorMessageMedium">@color/dashboard_message_medium_light</item>
|
||||
<item name="android:windowBackground">?colorSurface</item>
|
||||
<item name="android:textColor">?android:textColorPrimary</item>
|
||||
<item name="android:navigationBarColor">@color/colorNavigationBarLight</item>
|
||||
|
@ -40,7 +40,7 @@
|
||||
<item>https://vulcan.net.pl/?login</item>
|
||||
<item>https://vulcan.net.pl/?login</item>
|
||||
<item>https://vulcan.net.pl/?login</item>
|
||||
<item>http://fakelog.tk/?email</item>
|
||||
<item>http://fakelog.cf/?email</item>
|
||||
</string-array>
|
||||
<string-array name="hosts_symbols">
|
||||
<item>Default</item>
|
||||
|
@ -3,4 +3,5 @@
|
||||
<attr name="colorDivider" format="color" />
|
||||
<attr name="colorSwipeRefresh" format="color" />
|
||||
<attr name="colorTimetableChange" format="color" />
|
||||
<attr name="colorMessageMedium" format="color" />
|
||||
</resources>
|
||||
|
@ -12,6 +12,9 @@
|
||||
<color name="colorStatusBarLight">#1C1C1C</color>
|
||||
<color name="colorStatusBarBlack">#0D0D0D</color>
|
||||
|
||||
<color name="dashboard_message_medium_light">#FFD980</color>
|
||||
<color name="dashboard_message_medium_dark">#ffd54f</color>
|
||||
|
||||
<color name="timetable_change_light">#ffd54f</color>
|
||||
<color name="timetable_change_dark">#ff8f00</color>
|
||||
|
||||
|
@ -11,6 +11,7 @@
|
||||
<item name="colorError">@color/colorError</item>
|
||||
<item name="colorDivider">@color/colorDivider</item>
|
||||
<item name="colorSwipeRefresh">@color/colorSwipeRefresh</item>
|
||||
<item name="colorMessageMedium">@color/dashboard_message_medium_dark</item>
|
||||
<item name="android:statusBarColor">@android:color/darker_gray</item>
|
||||
<item name="android:textColor">?android:textColorPrimary</item>
|
||||
<item name="android:preferenceStyle">@style/PreferenceThemeOverlay</item>
|
||||
|
Loading…
x
Reference in New Issue
Block a user