mirror of
https://github.com/wulkanowy/wulkanowy.git
synced 2025-01-31 11:48:20 +01:00
Add admin messages (#1553)
This commit is contained in:
parent
d6918077bf
commit
e3122127c0
@ -66,12 +66,14 @@ android {
|
|||||||
shrinkResources true
|
shrinkResources true
|
||||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||||
signingConfig signingConfigs.release
|
signingConfig signingConfigs.release
|
||||||
|
buildConfigField "String", "MESSAGES_BASE_URL", "\"https://messages.wulkanowy.net.pl\""
|
||||||
}
|
}
|
||||||
debug {
|
debug {
|
||||||
resValue "string", "app_name", "Wulkanowy DEV " + defaultConfig.versionCode
|
resValue "string", "app_name", "Wulkanowy DEV " + defaultConfig.versionCode
|
||||||
applicationIdSuffix ".dev"
|
applicationIdSuffix ".dev"
|
||||||
versionNameSuffix "-dev"
|
versionNameSuffix "-dev"
|
||||||
ext.enableCrashlytics = project.hasProperty("enableFirebase")
|
ext.enableCrashlytics = project.hasProperty("enableFirebase")
|
||||||
|
buildConfigField "String", "MESSAGES_BASE_URL", "\"https://messages.wulkanowy.net.pl\""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -170,7 +172,7 @@ ext {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
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'
|
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.ncapdevi:FragNav:3.3.0'
|
||||||
implementation "com.github.YarikSOffice:lingver:1.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 "com.jakewharton.timber:timber:5.0.1"
|
||||||
implementation "at.favre.lib:slf4j-timber:1.0.1"
|
implementation "at.favre.lib:slf4j-timber:1.0.1"
|
||||||
implementation 'com.github.bastienpaulfr:Treessence:1.0.5'
|
implementation 'com.github.bastienpaulfr:Treessence:1.0.5'
|
||||||
@ -234,7 +240,7 @@ dependencies {
|
|||||||
releaseImplementation "com.github.ChuckerTeam.Chucker:library-no-op:$chucker"
|
releaseImplementation "com.github.ChuckerTeam.Chucker:library-no-op:$chucker"
|
||||||
|
|
||||||
debugImplementation "com.github.ChuckerTeam.Chucker:library:$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 "junit:junit:4.13.2"
|
||||||
testImplementation "io.mockk:mockk:$mockk"
|
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.Context
|
||||||
import android.content.SharedPreferences
|
import android.content.SharedPreferences
|
||||||
import android.content.res.AssetManager
|
|
||||||
import android.content.res.Resources
|
|
||||||
import androidx.preference.PreferenceManager
|
import androidx.preference.PreferenceManager
|
||||||
import com.chuckerteam.chucker.api.ChuckerCollector
|
import com.chuckerteam.chucker.api.ChuckerCollector
|
||||||
import com.chuckerteam.chucker.api.ChuckerInterceptor
|
import com.chuckerteam.chucker.api.ChuckerInterceptor
|
||||||
import com.chuckerteam.chucker.api.RetentionManager
|
import com.chuckerteam.chucker.api.RetentionManager
|
||||||
import com.fredporciuncula.flow.preferences.FlowSharedPreferences
|
import com.fredporciuncula.flow.preferences.FlowSharedPreferences
|
||||||
|
import com.jakewharton.retrofit2.converter.kotlinx.serialization.asConverterFactory
|
||||||
import dagger.Module
|
import dagger.Module
|
||||||
import dagger.Provides
|
import dagger.Provides
|
||||||
import dagger.hilt.InstallIn
|
import dagger.hilt.InstallIn
|
||||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||||
import dagger.hilt.components.SingletonComponent
|
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.AppDatabase
|
||||||
import io.github.wulkanowy.data.db.SharedPrefProvider
|
import io.github.wulkanowy.data.db.SharedPrefProvider
|
||||||
import io.github.wulkanowy.data.repositories.PreferencesRepository
|
import io.github.wulkanowy.data.repositories.PreferencesRepository
|
||||||
import io.github.wulkanowy.sdk.Sdk
|
import io.github.wulkanowy.sdk.Sdk
|
||||||
import io.github.wulkanowy.utils.AppInfo
|
import io.github.wulkanowy.utils.AppInfo
|
||||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||||
|
import kotlinx.serialization.ExperimentalSerializationApi
|
||||||
import kotlinx.serialization.json.Json
|
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 timber.log.Timber
|
||||||
|
import java.util.concurrent.TimeUnit
|
||||||
import javax.inject.Singleton
|
import javax.inject.Singleton
|
||||||
|
|
||||||
@Module
|
@Module
|
||||||
@InstallIn(SingletonComponent::class)
|
@InstallIn(SingletonComponent::class)
|
||||||
internal class RepositoryModule {
|
internal class DataModule {
|
||||||
|
|
||||||
@Singleton
|
@Singleton
|
||||||
@Provides
|
@Provides
|
||||||
fun provideSdk(chuckerCollector: ChuckerCollector, @ApplicationContext context: Context): Sdk {
|
fun provideSdk(chuckerInterceptor: ChuckerInterceptor) =
|
||||||
return Sdk().apply {
|
Sdk().apply {
|
||||||
androidVersion = android.os.Build.VERSION.RELEASE
|
androidVersion = android.os.Build.VERSION.RELEASE
|
||||||
buildTag = android.os.Build.MODEL
|
buildTag = android.os.Build.MODEL
|
||||||
setSimpleHttpLogger { Timber.d(it) }
|
setSimpleHttpLogger { Timber.d(it) }
|
||||||
|
|
||||||
// for debug only
|
// for debug only
|
||||||
addInterceptor(
|
addInterceptor(chuckerInterceptor, network = true)
|
||||||
ChuckerInterceptor.Builder(context)
|
|
||||||
.collector(chuckerCollector)
|
|
||||||
.alwaysReadResponseBody(true)
|
|
||||||
.build(), network = true
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Singleton
|
@Singleton
|
||||||
@ -51,13 +52,50 @@ internal class RepositoryModule {
|
|||||||
fun provideChuckerCollector(
|
fun provideChuckerCollector(
|
||||||
@ApplicationContext context: Context,
|
@ApplicationContext context: Context,
|
||||||
prefRepository: PreferencesRepository
|
prefRepository: PreferencesRepository
|
||||||
): ChuckerCollector {
|
) = ChuckerCollector(
|
||||||
return ChuckerCollector(
|
|
||||||
context = context,
|
context = context,
|
||||||
showNotification = prefRepository.isDebugNotificationEnable,
|
showNotification = prefRepository.isDebugNotificationEnable,
|
||||||
retentionPeriod = RetentionManager.Period.ONE_HOUR
|
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
|
@Singleton
|
||||||
@Provides
|
@Provides
|
||||||
@ -67,14 +105,6 @@ internal class RepositoryModule {
|
|||||||
appInfo: AppInfo
|
appInfo: AppInfo
|
||||||
) = AppDatabase.newInstance(context, sharedPrefProvider, 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
|
@Singleton
|
||||||
@Provides
|
@Provides
|
||||||
fun provideSharedPref(@ApplicationContext context: Context): SharedPreferences =
|
fun provideSharedPref(@ApplicationContext context: Context): SharedPreferences =
|
||||||
@ -208,4 +238,8 @@ internal class RepositoryModule {
|
|||||||
@Singleton
|
@Singleton
|
||||||
@Provides
|
@Provides
|
||||||
fun provideNotificationDao(database: AppDatabase) = database.notificationDao
|
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
|
||||||
import androidx.room.RoomDatabase.JournalMode.TRUNCATE
|
import androidx.room.RoomDatabase.JournalMode.TRUNCATE
|
||||||
import androidx.room.TypeConverters
|
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.AttendanceDao
|
||||||
import io.github.wulkanowy.data.db.dao.AttendanceSummaryDao
|
import io.github.wulkanowy.data.db.dao.AttendanceSummaryDao
|
||||||
import io.github.wulkanowy.data.db.dao.CompletedLessonsDao
|
import io.github.wulkanowy.data.db.dao.CompletedLessonsDao
|
||||||
@ -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.TimetableAdditionalDao
|
||||||
import io.github.wulkanowy.data.db.dao.TimetableDao
|
import io.github.wulkanowy.data.db.dao.TimetableDao
|
||||||
import io.github.wulkanowy.data.db.dao.TimetableHeaderDao
|
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.Attendance
|
||||||
import io.github.wulkanowy.data.db.entities.AttendanceSummary
|
import io.github.wulkanowy.data.db.entities.AttendanceSummary
|
||||||
import io.github.wulkanowy.data.db.entities.CompletedLesson
|
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.Migration38
|
||||||
import io.github.wulkanowy.data.db.migrations.Migration39
|
import io.github.wulkanowy.data.db.migrations.Migration39
|
||||||
import io.github.wulkanowy.data.db.migrations.Migration4
|
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.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.Migration5
|
||||||
import io.github.wulkanowy.data.db.migrations.Migration6
|
import io.github.wulkanowy.data.db.migrations.Migration6
|
||||||
import io.github.wulkanowy.data.db.migrations.Migration7
|
import io.github.wulkanowy.data.db.migrations.Migration7
|
||||||
@ -138,7 +141,8 @@ import javax.inject.Singleton
|
|||||||
StudentInfo::class,
|
StudentInfo::class,
|
||||||
TimetableHeader::class,
|
TimetableHeader::class,
|
||||||
SchoolAnnouncement::class,
|
SchoolAnnouncement::class,
|
||||||
Notification::class
|
Notification::class,
|
||||||
|
AdminMessage::class
|
||||||
],
|
],
|
||||||
version = AppDatabase.VERSION_SCHEMA,
|
version = AppDatabase.VERSION_SCHEMA,
|
||||||
exportSchema = true
|
exportSchema = true
|
||||||
@ -147,7 +151,7 @@ import javax.inject.Singleton
|
|||||||
abstract class AppDatabase : RoomDatabase() {
|
abstract class AppDatabase : RoomDatabase() {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val VERSION_SCHEMA = 41
|
const val VERSION_SCHEMA = 42
|
||||||
|
|
||||||
fun getMigrations(sharedPrefProvider: SharedPrefProvider, appInfo: AppInfo) = arrayOf(
|
fun getMigrations(sharedPrefProvider: SharedPrefProvider, appInfo: AppInfo) = arrayOf(
|
||||||
Migration2(),
|
Migration2(),
|
||||||
@ -189,7 +193,8 @@ abstract class AppDatabase : RoomDatabase() {
|
|||||||
Migration38(),
|
Migration38(),
|
||||||
Migration39(),
|
Migration39(),
|
||||||
Migration40(),
|
Migration40(),
|
||||||
Migration41()
|
Migration41(),
|
||||||
|
Migration42()
|
||||||
)
|
)
|
||||||
|
|
||||||
fun newInstance(
|
fun newInstance(
|
||||||
@ -261,4 +266,6 @@ abstract class AppDatabase : RoomDatabase() {
|
|||||||
abstract val schoolAnnouncementDao: SchoolAnnouncementDao
|
abstract val schoolAnnouncementDao: SchoolAnnouncementDao
|
||||||
|
|
||||||
abstract val notificationDao: NotificationDao
|
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
|
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.data.pojos.Contributor
|
||||||
import io.github.wulkanowy.utils.DispatchersProvider
|
import io.github.wulkanowy.utils.DispatchersProvider
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
@ -12,7 +13,7 @@ import javax.inject.Singleton
|
|||||||
|
|
||||||
@Singleton
|
@Singleton
|
||||||
class AppCreatorRepository @Inject constructor(
|
class AppCreatorRepository @Inject constructor(
|
||||||
private val assets: AssetManager,
|
@ApplicationContext private val context: Context,
|
||||||
private val dispatchers: DispatchersProvider,
|
private val dispatchers: DispatchersProvider,
|
||||||
private val json: Json,
|
private val json: Json,
|
||||||
) {
|
) {
|
||||||
@ -20,7 +21,7 @@ class AppCreatorRepository @Inject constructor(
|
|||||||
@OptIn(ExperimentalSerializationApi::class)
|
@OptIn(ExperimentalSerializationApi::class)
|
||||||
@Suppress("BlockingMethodInNonBlockingContext")
|
@Suppress("BlockingMethodInNonBlockingContext")
|
||||||
suspend fun getAppCreators() = withContext(dispatchers.backgroundThread) {
|
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)
|
json.decodeFromStream<List<Contributor>>(inputStream)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -209,6 +209,7 @@ class PreferencesRepository @Inject constructor(
|
|||||||
.map { set ->
|
.map { set ->
|
||||||
set.map { DashboardItem.Tile.valueOf(it) }
|
set.map { DashboardItem.Tile.valueOf(it) }
|
||||||
.plus(DashboardItem.Tile.ACCOUNT)
|
.plus(DashboardItem.Tile.ACCOUNT)
|
||||||
|
.plus(DashboardItem.Tile.ADMIN_MESSAGE)
|
||||||
.toSet()
|
.toSet()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -216,6 +217,7 @@ class PreferencesRepository @Inject constructor(
|
|||||||
get() = selectedDashboardTilesPreference.get()
|
get() = selectedDashboardTilesPreference.get()
|
||||||
.map { DashboardItem.Tile.valueOf(it) }
|
.map { DashboardItem.Tile.valueOf(it) }
|
||||||
.plus(DashboardItem.Tile.ACCOUNT)
|
.plus(DashboardItem.Tile.ACCOUNT)
|
||||||
|
.plus(DashboardItem.Tile.ADMIN_MESSAGE)
|
||||||
.toSet()
|
.toSet()
|
||||||
set(value) {
|
set(value) {
|
||||||
val filteredValue = value.filterNot { it == DashboardItem.Tile.ACCOUNT }
|
val filteredValue = value.filterNot { it == DashboardItem.Tile.ACCOUNT }
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package io.github.wulkanowy.ui.base
|
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.data.exceptions.NoCurrentStudentException
|
||||||
import io.github.wulkanowy.sdk.scrapper.login.BadCredentialsException
|
import io.github.wulkanowy.sdk.scrapper.login.BadCredentialsException
|
||||||
import io.github.wulkanowy.sdk.scrapper.login.PasswordChangeRequiredException
|
import io.github.wulkanowy.sdk.scrapper.login.PasswordChangeRequiredException
|
||||||
@ -9,7 +10,7 @@ import io.github.wulkanowy.utils.security.ScramblerException
|
|||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import javax.inject.Inject
|
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 = { _, _ -> }
|
var showErrorMessage: (String, Throwable) -> Unit = { _, _ -> }
|
||||||
|
|
||||||
@ -25,7 +26,7 @@ open class ErrorHandler @Inject constructor(protected val resources: Resources)
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected open fun proceed(error: Throwable) {
|
protected open fun proceed(error: Throwable) {
|
||||||
showErrorMessage(resources.getString(error), error)
|
showErrorMessage(context.resources.getString(error), error)
|
||||||
when (error) {
|
when (error) {
|
||||||
is PasswordChangeRequiredException -> onPasswordChangeRequired(error.redirectUrl)
|
is PasswordChangeRequiredException -> onPasswordChangeRequired(error.redirectUrl)
|
||||||
is ScramblerException, is BadCredentialsException -> onSessionExpired()
|
is ScramblerException, is BadCredentialsException -> onSessionExpired()
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
package io.github.wulkanowy.ui.modules.dashboard
|
package io.github.wulkanowy.ui.modules.dashboard
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
|
import android.content.res.ColorStateList
|
||||||
|
import android.graphics.Color
|
||||||
import android.graphics.Typeface
|
import android.graphics.Typeface
|
||||||
import android.os.Handler
|
import android.os.Handler
|
||||||
import android.os.Looper
|
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.Timetable
|
||||||
import io.github.wulkanowy.data.db.entities.TimetableHeader
|
import io.github.wulkanowy.data.db.entities.TimetableHeader
|
||||||
import io.github.wulkanowy.databinding.ItemDashboardAccountBinding
|
import io.github.wulkanowy.databinding.ItemDashboardAccountBinding
|
||||||
|
import io.github.wulkanowy.databinding.ItemDashboardAdminMessageBinding
|
||||||
import io.github.wulkanowy.databinding.ItemDashboardAnnouncementsBinding
|
import io.github.wulkanowy.databinding.ItemDashboardAnnouncementsBinding
|
||||||
import io.github.wulkanowy.databinding.ItemDashboardConferencesBinding
|
import io.github.wulkanowy.databinding.ItemDashboardConferencesBinding
|
||||||
import io.github.wulkanowy.databinding.ItemDashboardExamsBinding
|
import io.github.wulkanowy.databinding.ItemDashboardExamsBinding
|
||||||
@ -63,6 +66,8 @@ class DashboardAdapter @Inject constructor() : RecyclerView.Adapter<RecyclerView
|
|||||||
|
|
||||||
var onConferencesTileClickListener: () -> Unit = {}
|
var onConferencesTileClickListener: () -> Unit = {}
|
||||||
|
|
||||||
|
var onAdminMessageClickListener: (String?) -> Unit = {}
|
||||||
|
|
||||||
val items = mutableListOf<DashboardItem>()
|
val items = mutableListOf<DashboardItem>()
|
||||||
|
|
||||||
fun submitList(newItems: List<DashboardItem>) {
|
fun submitList(newItems: List<DashboardItem>) {
|
||||||
@ -109,6 +114,9 @@ class DashboardAdapter @Inject constructor() : RecyclerView.Adapter<RecyclerView
|
|||||||
DashboardItem.Type.CONFERENCES.ordinal -> ConferencesViewHolder(
|
DashboardItem.Type.CONFERENCES.ordinal -> ConferencesViewHolder(
|
||||||
ItemDashboardConferencesBinding.inflate(inflater, parent, false)
|
ItemDashboardConferencesBinding.inflate(inflater, parent, false)
|
||||||
)
|
)
|
||||||
|
DashboardItem.Type.ADMIN_MESSAGE.ordinal -> AdminMessageViewHolder(
|
||||||
|
ItemDashboardAdminMessageBinding.inflate(inflater, parent, false)
|
||||||
|
)
|
||||||
else -> throw IllegalArgumentException()
|
else -> throw IllegalArgumentException()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -123,6 +131,7 @@ class DashboardAdapter @Inject constructor() : RecyclerView.Adapter<RecyclerView
|
|||||||
is AnnouncementsViewHolder -> bindAnnouncementsViewHolder(holder, position)
|
is AnnouncementsViewHolder -> bindAnnouncementsViewHolder(holder, position)
|
||||||
is ExamsViewHolder -> bindExamsViewHolder(holder, position)
|
is ExamsViewHolder -> bindExamsViewHolder(holder, position)
|
||||||
is ConferencesViewHolder -> bindConferencesViewHolder(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) :
|
class AccountViewHolder(val binding: ItemDashboardAccountBinding) :
|
||||||
RecyclerView.ViewHolder(binding.root)
|
RecyclerView.ViewHolder(binding.root)
|
||||||
|
|
||||||
@ -736,6 +773,9 @@ class DashboardAdapter @Inject constructor() : RecyclerView.Adapter<RecyclerView
|
|||||||
val adapter by lazy { DashboardConferencesAdapter() }
|
val adapter by lazy { DashboardConferencesAdapter() }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class AdminMessageViewHolder(val binding: ItemDashboardAdminMessageBinding) :
|
||||||
|
RecyclerView.ViewHolder(binding.root)
|
||||||
|
|
||||||
private class DiffCallback(
|
private class DiffCallback(
|
||||||
private val newList: List<DashboardItem>,
|
private val newList: List<DashboardItem>,
|
||||||
private val oldList: 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.DefaultItemAnimator
|
||||||
import androidx.recyclerview.widget.ItemTouchHelper
|
import androidx.recyclerview.widget.ItemTouchHelper
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import dagger.hilt.android.AndroidEntryPoint
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
import io.github.wulkanowy.R
|
import io.github.wulkanowy.R
|
||||||
import io.github.wulkanowy.databinding.FragmentDashboardBinding
|
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.ui.modules.timetable.TimetableFragment
|
||||||
import io.github.wulkanowy.utils.capitalise
|
import io.github.wulkanowy.utils.capitalise
|
||||||
import io.github.wulkanowy.utils.getThemeAttrColor
|
import io.github.wulkanowy.utils.getThemeAttrColor
|
||||||
|
import io.github.wulkanowy.utils.openInternetBrowser
|
||||||
import io.github.wulkanowy.utils.toFormattedString
|
import io.github.wulkanowy.utils.toFormattedString
|
||||||
import java.time.LocalDate
|
import java.time.LocalDate
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
@ -97,6 +99,13 @@ class DashboardFragment : BaseFragment<FragmentDashboardBinding>(R.layout.fragme
|
|||||||
onConferencesTileClickListener = {
|
onConferencesTileClickListener = {
|
||||||
mainActivity.pushView(ConferenceFragment.newInstance())
|
mainActivity.pushView(ConferenceFragment.newInstance())
|
||||||
}
|
}
|
||||||
|
onAdminMessageClickListener = presenter::onAdminMessageSelected
|
||||||
|
|
||||||
|
registerAdapterDataObserver(object : RecyclerView.AdapterDataObserver() {
|
||||||
|
override fun onItemRangeInserted(positionStart: Int, itemCount: Int) {
|
||||||
|
binding.dashboardRecycler.scrollToPosition(0)
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
with(binding) {
|
with(binding) {
|
||||||
@ -188,6 +197,10 @@ class DashboardFragment : BaseFragment<FragmentDashboardBinding>(R.layout.fragme
|
|||||||
(requireActivity() as MainActivity).pushView(NotificationsCenterFragment.newInstance())
|
(requireActivity() as MainActivity).pushView(NotificationsCenterFragment.newInstance())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun openInternetBrowser(url: String) {
|
||||||
|
requireContext().openInternetBrowser(url)
|
||||||
|
}
|
||||||
|
|
||||||
override fun onDestroyView() {
|
override fun onDestroyView() {
|
||||||
dashboardAdapter.clearTimers()
|
dashboardAdapter.clearTimers()
|
||||||
presenter.onDetachView()
|
presenter.onDetachView()
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
package io.github.wulkanowy.ui.modules.dashboard
|
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.Conference
|
||||||
import io.github.wulkanowy.data.db.entities.Exam
|
import io.github.wulkanowy.data.db.entities.Exam
|
||||||
import io.github.wulkanowy.data.db.entities.Grade
|
import io.github.wulkanowy.data.db.entities.Grade
|
||||||
@ -16,6 +17,15 @@ sealed class DashboardItem(val type: Type) {
|
|||||||
|
|
||||||
abstract val isDataLoaded: Boolean
|
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(
|
data class Account(
|
||||||
val student: Student? = null,
|
val student: Student? = null,
|
||||||
override val error: Throwable? = null,
|
override val error: Throwable? = null,
|
||||||
@ -96,6 +106,7 @@ sealed class DashboardItem(val type: Type) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
enum class Type {
|
enum class Type {
|
||||||
|
ADMIN_MESSAGE,
|
||||||
ACCOUNT,
|
ACCOUNT,
|
||||||
HORIZONTAL_GROUP,
|
HORIZONTAL_GROUP,
|
||||||
LESSONS,
|
LESSONS,
|
||||||
@ -108,6 +119,7 @@ sealed class DashboardItem(val type: Type) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
enum class Tile {
|
enum class Tile {
|
||||||
|
ADMIN_MESSAGE,
|
||||||
ACCOUNT,
|
ACCOUNT,
|
||||||
LUCKY_NUMBER,
|
LUCKY_NUMBER,
|
||||||
MESSAGES,
|
MESSAGES,
|
||||||
@ -123,6 +135,7 @@ sealed class DashboardItem(val type: Type) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun DashboardItem.Tile.toDashboardItemType() = when (this) {
|
fun DashboardItem.Tile.toDashboardItemType() = when (this) {
|
||||||
|
DashboardItem.Tile.ADMIN_MESSAGE -> DashboardItem.Type.ADMIN_MESSAGE
|
||||||
DashboardItem.Tile.ACCOUNT -> DashboardItem.Type.ACCOUNT
|
DashboardItem.Tile.ACCOUNT -> DashboardItem.Type.ACCOUNT
|
||||||
DashboardItem.Tile.LUCKY_NUMBER -> DashboardItem.Type.HORIZONTAL_GROUP
|
DashboardItem.Tile.LUCKY_NUMBER -> DashboardItem.Type.HORIZONTAL_GROUP
|
||||||
DashboardItem.Tile.MESSAGES -> DashboardItem.Type.HORIZONTAL_GROUP
|
DashboardItem.Tile.MESSAGES -> DashboardItem.Type.HORIZONTAL_GROUP
|
||||||
|
@ -21,7 +21,7 @@ class DashboardItemMoveCallback(
|
|||||||
recyclerView: RecyclerView,
|
recyclerView: RecyclerView,
|
||||||
viewHolder: RecyclerView.ViewHolder
|
viewHolder: RecyclerView.ViewHolder
|
||||||
): Int {
|
): Int {
|
||||||
val dragFlags = if (viewHolder.bindingAdapterPosition != 0) {
|
val dragFlags = if (!viewHolder.isAdminMessageOrAccountItem) {
|
||||||
ItemTouchHelper.UP or ItemTouchHelper.DOWN
|
ItemTouchHelper.UP or ItemTouchHelper.DOWN
|
||||||
} else 0
|
} else 0
|
||||||
|
|
||||||
@ -32,7 +32,7 @@ class DashboardItemMoveCallback(
|
|||||||
recyclerView: RecyclerView,
|
recyclerView: RecyclerView,
|
||||||
current: RecyclerView.ViewHolder,
|
current: RecyclerView.ViewHolder,
|
||||||
target: RecyclerView.ViewHolder
|
target: RecyclerView.ViewHolder
|
||||||
) = target.bindingAdapterPosition != 0
|
) = !target.isAdminMessageOrAccountItem
|
||||||
|
|
||||||
override fun onMove(
|
override fun onMove(
|
||||||
recyclerView: RecyclerView,
|
recyclerView: RecyclerView,
|
||||||
@ -52,4 +52,7 @@ class DashboardItemMoveCallback(
|
|||||||
|
|
||||||
onUserInteractionEndListener(dashboardAdapter.items.toList())
|
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.LuckyNumber
|
||||||
import io.github.wulkanowy.data.db.entities.Student
|
import io.github.wulkanowy.data.db.entities.Student
|
||||||
import io.github.wulkanowy.data.enums.MessageFolder
|
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.AttendanceSummaryRepository
|
||||||
import io.github.wulkanowy.data.repositories.ConferenceRepository
|
import io.github.wulkanowy.data.repositories.ConferenceRepository
|
||||||
import io.github.wulkanowy.data.repositories.ExamRepository
|
import io.github.wulkanowy.data.repositories.ExamRepository
|
||||||
@ -50,7 +51,8 @@ class DashboardPresenter @Inject constructor(
|
|||||||
private val examRepository: ExamRepository,
|
private val examRepository: ExamRepository,
|
||||||
private val conferenceRepository: ConferenceRepository,
|
private val conferenceRepository: ConferenceRepository,
|
||||||
private val preferencesRepository: PreferencesRepository,
|
private val preferencesRepository: PreferencesRepository,
|
||||||
private val schoolAnnouncementRepository: SchoolAnnouncementRepository
|
private val schoolAnnouncementRepository: SchoolAnnouncementRepository,
|
||||||
|
private val adminMessageRepository: AdminMessageRepository
|
||||||
) : BasePresenter<DashboardView>(errorHandler, studentRepository) {
|
) : BasePresenter<DashboardView>(errorHandler, studentRepository) {
|
||||||
|
|
||||||
private val dashboardItemLoadedList = mutableListOf<DashboardItem>()
|
private val dashboardItemLoadedList = mutableListOf<DashboardItem>()
|
||||||
@ -179,6 +181,7 @@ class DashboardPresenter @Inject constructor(
|
|||||||
loadConferences(student, forceRefresh)
|
loadConferences(student, forceRefresh)
|
||||||
}
|
}
|
||||||
DashboardItem.Type.ADS -> TODO()
|
DashboardItem.Type.ADS -> TODO()
|
||||||
|
DashboardItem.Type.ADMIN_MESSAGE -> loadAdminMessage(student, forceRefresh)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -225,6 +228,10 @@ class DashboardPresenter @Inject constructor(
|
|||||||
}.toSet()
|
}.toSet()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun onAdminMessageSelected(url: String?) {
|
||||||
|
url?.let { view?.openInternetBrowser(it) }
|
||||||
|
}
|
||||||
|
|
||||||
private fun loadHorizontalGroup(student: Student, forceRefresh: Boolean) {
|
private fun loadHorizontalGroup(student: Student, forceRefresh: Boolean) {
|
||||||
flow {
|
flow {
|
||||||
val semester = semesterRepository.getCurrentSemester(student)
|
val semester = semesterRepository.getCurrentSemester(student)
|
||||||
@ -567,6 +574,38 @@ class DashboardPresenter @Inject constructor(
|
|||||||
}.launch("dashboard_conferences")
|
}.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) {
|
private fun updateData(dashboardItem: DashboardItem, forceRefresh: Boolean) {
|
||||||
val isForceRefreshError = forceRefresh && dashboardItem.error != null
|
val isForceRefreshError = forceRefresh && dashboardItem.error != null
|
||||||
val isFirstRunDataLoadedError =
|
val isFirstRunDataLoadedError =
|
||||||
@ -579,6 +618,11 @@ class DashboardPresenter @Inject constructor(
|
|||||||
|
|
||||||
sortDashboardItems()
|
sortDashboardItems()
|
||||||
|
|
||||||
|
if (dashboardItem is DashboardItem.AdminMessages && !dashboardItem.isDataLoaded) {
|
||||||
|
dashboardItemsToLoad = dashboardItemsToLoad - DashboardItem.Type.ADMIN_MESSAGE
|
||||||
|
dashboardItemLoadedList.removeAll { it.type == DashboardItem.Type.ADMIN_MESSAGE }
|
||||||
|
}
|
||||||
|
|
||||||
if (forceRefresh) {
|
if (forceRefresh) {
|
||||||
updateForceRefreshData(dashboardItem)
|
updateForceRefreshData(dashboardItem)
|
||||||
} else {
|
} else {
|
||||||
@ -644,7 +688,9 @@ class DashboardPresenter @Inject constructor(
|
|||||||
itemsLoadedList: List<DashboardItem>,
|
itemsLoadedList: List<DashboardItem>,
|
||||||
forceRefresh: Boolean
|
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 =
|
val isAccountItemError =
|
||||||
itemsLoadedList.find { it.type == DashboardItem.Type.ACCOUNT }?.error != null
|
itemsLoadedList.find { it.type == DashboardItem.Type.ACCOUNT }?.error != null
|
||||||
val isGeneralError =
|
val isGeneralError =
|
||||||
@ -676,10 +722,13 @@ class DashboardPresenter @Inject constructor(
|
|||||||
val dashboardItemsPosition = preferencesRepository.dashboardItemsPosition
|
val dashboardItemsPosition = preferencesRepository.dashboardItemsPosition
|
||||||
|
|
||||||
dashboardItemLoadedList.sortBy { tile ->
|
dashboardItemLoadedList.sortBy { tile ->
|
||||||
dashboardItemsPosition?.getOrDefault(
|
val defaultPosition = if (tile is DashboardItem.AdminMessages) {
|
||||||
tile.type,
|
-1
|
||||||
|
} else {
|
||||||
tile.type.ordinal + 100
|
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 popViewToRoot()
|
||||||
|
|
||||||
fun openNotificationsCenterView()
|
fun openNotificationsCenterView()
|
||||||
|
|
||||||
|
fun openInternetBrowser(url: String)
|
||||||
}
|
}
|
@ -1,7 +1,8 @@
|
|||||||
package io.github.wulkanowy.ui.modules.login
|
package io.github.wulkanowy.ui.modules.login
|
||||||
|
|
||||||
import android.content.res.Resources
|
import android.content.Context
|
||||||
import android.database.sqlite.SQLiteConstraintException
|
import android.database.sqlite.SQLiteConstraintException
|
||||||
|
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||||
import io.github.wulkanowy.R
|
import io.github.wulkanowy.R
|
||||||
import io.github.wulkanowy.sdk.mobile.exception.InvalidPinException
|
import io.github.wulkanowy.sdk.mobile.exception.InvalidPinException
|
||||||
import io.github.wulkanowy.sdk.mobile.exception.InvalidSymbolException
|
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 io.github.wulkanowy.ui.base.ErrorHandler
|
||||||
import javax.inject.Inject
|
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 = {}
|
var onBadCredentials: (String?) -> Unit = {}
|
||||||
|
|
||||||
@ -24,6 +26,7 @@ class LoginErrorHandler @Inject constructor(resources: Resources) : ErrorHandler
|
|||||||
var onStudentDuplicate: (String) -> Unit = {}
|
var onStudentDuplicate: (String) -> Unit = {}
|
||||||
|
|
||||||
override fun proceed(error: Throwable) {
|
override fun proceed(error: Throwable) {
|
||||||
|
val resources = context.resources
|
||||||
when (error) {
|
when (error) {
|
||||||
is BadCredentialsException -> onBadCredentials(error.message)
|
is BadCredentialsException -> onBadCredentials(error.message)
|
||||||
is SQLiteConstraintException -> onStudentDuplicate(resources.getString(R.string.login_duplicate_student))
|
is SQLiteConstraintException -> onStudentDuplicate(resources.getString(R.string.login_duplicate_student))
|
||||||
|
@ -1,13 +1,15 @@
|
|||||||
package io.github.wulkanowy.ui.modules.login.recover
|
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.InvalidCaptchaException
|
||||||
import io.github.wulkanowy.sdk.scrapper.exception.InvalidEmailException
|
import io.github.wulkanowy.sdk.scrapper.exception.InvalidEmailException
|
||||||
import io.github.wulkanowy.sdk.scrapper.exception.NoAccountFoundException
|
import io.github.wulkanowy.sdk.scrapper.exception.NoAccountFoundException
|
||||||
import io.github.wulkanowy.ui.base.ErrorHandler
|
import io.github.wulkanowy.ui.base.ErrorHandler
|
||||||
import javax.inject.Inject
|
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 = {}
|
var onInvalidUsername: (String) -> Unit = {}
|
||||||
|
|
||||||
@ -15,7 +17,8 @@ class RecoverErrorHandler @Inject constructor(resources: Resources) : ErrorHandl
|
|||||||
|
|
||||||
override fun proceed(error: Throwable) {
|
override fun proceed(error: Throwable) {
|
||||||
when (error) {
|
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)
|
is InvalidCaptchaException -> onInvalidCaptcha(error.localizedMessage.orEmpty(), error)
|
||||||
else -> super.proceed(error)
|
else -> super.proceed(error)
|
||||||
}
|
}
|
||||||
|
@ -1,11 +1,13 @@
|
|||||||
package io.github.wulkanowy.ui.modules.timetable.completed
|
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.sdk.scrapper.exception.FeatureDisabledException
|
||||||
import io.github.wulkanowy.ui.base.ErrorHandler
|
import io.github.wulkanowy.ui.base.ErrorHandler
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class CompletedLessonsErrorHandler @Inject constructor(resources: Resources) : ErrorHandler(resources) {
|
class CompletedLessonsErrorHandler @Inject constructor(@ApplicationContext context: Context) :
|
||||||
|
ErrorHandler(context) {
|
||||||
|
|
||||||
var onFeatureDisabled: () -> Unit = {}
|
var onFeatureDisabled: () -> Unit = {}
|
||||||
|
|
||||||
|
@ -1,35 +1,31 @@
|
|||||||
package io.github.wulkanowy.utils
|
package io.github.wulkanowy.utils
|
||||||
|
|
||||||
import android.content.res.Resources
|
import android.content.res.Resources
|
||||||
import android.os.Build.MANUFACTURER
|
import android.os.Build
|
||||||
import android.os.Build.MODEL
|
import io.github.wulkanowy.BuildConfig
|
||||||
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 javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import javax.inject.Singleton
|
import javax.inject.Singleton
|
||||||
|
|
||||||
@Singleton
|
@Singleton
|
||||||
open class AppInfo @Inject constructor() {
|
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")
|
@Suppress("DEPRECATION")
|
||||||
open val systemLanguage: String
|
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="colorError">@color/colorErrorLight</item>
|
||||||
<item name="colorDivider">@color/colorDividerInverse</item>
|
<item name="colorDivider">@color/colorDividerInverse</item>
|
||||||
<item name="colorSwipeRefresh">@color/colorSwipeRefreshDark</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:windowBackground">?colorSurface</item>
|
||||||
<item name="android:textColor">?android:textColorPrimary</item>
|
<item name="android:textColor">?android:textColorPrimary</item>
|
||||||
<item name="android:navigationBarColor">@color/colorNavigationBarLight</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>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>
|
||||||
<string-array name="hosts_symbols">
|
<string-array name="hosts_symbols">
|
||||||
<item>Default</item>
|
<item>Default</item>
|
||||||
|
@ -3,4 +3,5 @@
|
|||||||
<attr name="colorDivider" format="color" />
|
<attr name="colorDivider" format="color" />
|
||||||
<attr name="colorSwipeRefresh" format="color" />
|
<attr name="colorSwipeRefresh" format="color" />
|
||||||
<attr name="colorTimetableChange" format="color" />
|
<attr name="colorTimetableChange" format="color" />
|
||||||
|
<attr name="colorMessageMedium" format="color" />
|
||||||
</resources>
|
</resources>
|
||||||
|
@ -12,6 +12,9 @@
|
|||||||
<color name="colorStatusBarLight">#1C1C1C</color>
|
<color name="colorStatusBarLight">#1C1C1C</color>
|
||||||
<color name="colorStatusBarBlack">#0D0D0D</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_light">#ffd54f</color>
|
||||||
<color name="timetable_change_dark">#ff8f00</color>
|
<color name="timetable_change_dark">#ff8f00</color>
|
||||||
|
|
||||||
|
@ -11,6 +11,7 @@
|
|||||||
<item name="colorError">@color/colorError</item>
|
<item name="colorError">@color/colorError</item>
|
||||||
<item name="colorDivider">@color/colorDivider</item>
|
<item name="colorDivider">@color/colorDivider</item>
|
||||||
<item name="colorSwipeRefresh">@color/colorSwipeRefresh</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:statusBarColor">@android:color/darker_gray</item>
|
||||||
<item name="android:textColor">?android:textColorPrimary</item>
|
<item name="android:textColor">?android:textColorPrimary</item>
|
||||||
<item name="android:preferenceStyle">@style/PreferenceThemeOverlay</item>
|
<item name="android:preferenceStyle">@style/PreferenceThemeOverlay</item>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user