[Structure] Refactor App class to Kotlin. Rewrite SzkolnyTask and posting notifications. Remove dependency on AppConfig. Update libraries and gradle.

This commit is contained in:
Kuba Szczodrzyński 2020-01-19 19:07:27 +01:00
parent 55c6e40d6d
commit b7fc6fcc38
131 changed files with 2429 additions and 2880 deletions

View File

@ -2,7 +2,7 @@ apply plugin: 'com.android.library'
//apply plugin: 'me.tatarka.retrolambda'
android {
compileSdkVersion rootProject.ext.compileSdkVersion
compileSdkVersion setup.compileSdk
android {
lintOptions {
@ -12,7 +12,7 @@ android {
defaultConfig {
minSdkVersion 14
targetSdkVersion rootProject.ext.targetSdkVersion
targetSdkVersion setup.targetSdk
versionCode 1
versionName "1.0"
}
@ -43,9 +43,9 @@ android {
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
// Google libraries
implementation "androidx.appcompat:appcompat:${androidXAppCompat}"
implementation "androidx.recyclerview:recyclerview:${androidXRecyclerView}"
implementation "com.google.android.material:material:${googleMaterial}"
implementation "androidx.appcompat:appcompat:${versions.appcompat}"
implementation "androidx.recyclerview:recyclerview:${versions.recyclerView}"
implementation "com.google.android.material:material:${versions.material}"
// other libraries
//implementation 'se.emilsjolander:stickylistheaders:2.7.0'

View File

@ -1,13 +1,14 @@
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android-extensions'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-kapt'
apply plugin: 'kotlin-android-extensions'
apply plugin: 'com.google.gms.google-services'
apply plugin: 'io.fabric'
android {
signingConfigs {
}
compileSdkVersion rootProject.ext.compileSdkVersion
compileSdkVersion setup.compileSdk
defaultConfig {
applicationId 'pl.szczodrzynski.edziennik'
minSdkVersion setup.minSdk
@ -103,7 +104,7 @@ tasks.whenTaskAdded { task ->
dependencies {
implementation fileTree(include: ['*.jar'], dir: 'libs')
annotationProcessor "androidx.room:room-compiler:${versions.room}"
kapt "androidx.room:room-compiler:${versions.room}"
debugImplementation "com.amitshekhar.android:debug-db:1.0.5"
implementation "android.arch.navigation:navigation-fragment-ktx:${versions.navigationFragment}"

View File

@ -28,7 +28,7 @@
-keepclassmembers class pl.szczodrzynski.edziennik.ui.widgets.WidgetConfig { public *; }
-keepnames class pl.szczodrzynski.edziennik.ui.widgets.timetable.WidgetTimetableProvider
-keepnames class pl.szczodrzynski.edziennik.ui.widgets.notifications.WidgetNotificationsProvider
-keepnames class pl.szczodrzynski.edziennik.widgets.luckynumber.WidgetLuckyNumber
-keepnames class pl.szczodrzynski.edziennik.ui.widgets.luckynumber.WidgetLuckyNumberProvider
-keep class .R
-keep class **.R$* {

View File

@ -80,6 +80,7 @@
<service android:name=".ui.widgets.timetable.WidgetTimetableService"
android:permission="android.permission.BIND_REMOTEVIEWS" />
<activity android:name=".ui.widgets.LessonDialogActivity"
android:label=""
android:configChanges="orientation|keyboardHidden"
android:excludeFromRecents="true"
android:noHistory="true"
@ -141,12 +142,8 @@
android:label="@string/app_name"
android:theme="@style/AppTheme" />
<activity android:name=".ui.modules.settings.SettingsLicenseActivity"
android:configChanges="orientation|keyboardHidden"
android:theme="@style/AppTheme" />
<activity android:name=".ui.modules.webpush.WebPushConfigActivity"
android:configChanges="orientation|keyboardHidden"
android:theme="@style/AppTheme.Dark" />
<activity android:name=".ui.modules.webpush.QrScannerActivity" />
<activity android:name="com.theartofdev.edmodo.cropper.CropImageActivity"
android:configChanges="orientation|keyboardHidden"
@ -165,20 +162,11 @@
<action android:name="android.intent.action.USER_PRESENT" />
</intent-filter>
</receiver>
<receiver android:name=".receivers.BootReceiver">
<receiver android:name=".sync.UpdateDownloaderService$DownloadProgressReceiver">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
<action android:name="android.intent.action.DOWNLOAD_COMPLETE" />
<action android:name="android.intent.action.QUICKBOOT_POWERON" />
</intent-filter>
</receiver>
<!--<receiver android:name=".sync.FirebaseBroadcastReceiver"
android:exported="true"
android:permission="com.google.android.c2dm.permission.SEND">
<intent-filter>
<action android:name="com.google.android.c2dm.intent.RECEIVE" />
</intent-filter>
</receiver>-->
<receiver android:name=".receivers.SzkolnyReceiver"
android:exported="true">
<intent-filter>
@ -200,8 +188,6 @@
<action android:name="com.google.firebase.MESSAGING_EVENT" />
</intent-filter>
</service>-->
<service android:name=".receivers.BootReceiver$NotificationActionService" />
<service android:name=".Notifier$GetDataRetryService" />
<service android:name=".data.api.ApiService" />
<service android:name=".data.firebase.MyFirebaseService"
android:exported="false">
@ -210,6 +196,7 @@
<action android:name="com.google.firebase.INSTANCE_ID_EVENT" />
</intent-filter>
</service>
<service android:name=".sync.UpdateDownloaderService" />
<!--
_____ _ _

View File

@ -1,4 +1,4 @@
<h3>Wersja 4.0-beta.3, 2020-01-10</h3>
<h3>Wersja 4.0-beta.4, 2020-01-19</h3>
<ul>
<li><b>Przebudowaliśmy cały moduł synchronizacji</b>, co oznacza większą stabilność aplikacji, szybkosć oraz poprawność pobieranych danych.</li>
<li><b><u>Wysyłanie wiadomości</u></b> - funkcja, na którą czekał każdy. Od teraz w Szkolnym można wysyłać oraz odpowiadać na wiadomości do nauczycieli &#x1F44F;</li>
@ -6,6 +6,7 @@
<li>Nowa <b>Strona główna</b> - ładniejszy wygląd oraz możliwość przestawiania kart na każdym profilu</li>
<li>Nowy <b>Plan lekcji</b> - z doskonałą obsługą lekcji przesuniętych oraz dwóch lekcji o tej samej godzinie</li>
<li>Nowe okienka informacji o wydarzeniach oraz lekcjach</li>
<li>Nowe, przyjemniejsze powiadomienia</li>
<li>Łatwiejsze dodawanie własnych wydarzeń</li>
<li>Dużo poprawek w widoku <b>Wiadomości</b> oraz <b>Ogłoszeń</b></li>
<li>Częściowa <b>Obsługa dziennika EduDziennik</b></li>
@ -14,7 +15,9 @@
<li>Librus: obsługa Zadań domowych bez posiadania Mobilnych dodatków (przez system Synergia)</li>
<li>Lepsze <b>przekazywanie powiadomień na komputer</b> oraz łatwiejsze parowanie</li>
<li>Poprawiliśmy synchronizację w tle na niektórych telefonach</li>
<li>Usunąłem denerwujący brak zaznaczenia w lewym menu</li>
<li>Znaczna ilość błędów z poprzednich wersji już nie występuje</li>
<li><strike>Występują natomiast nowe błędy, dlatego proszę o ich zgłaszanie :)</strike></li>
</ul>
<br>
<br>

View File

@ -4,20 +4,93 @@
package pl.szczodrzynski.edziennik
import android.app.NotificationChannel
import android.app.NotificationManager
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.content.pm.ShortcutInfo
import android.content.pm.ShortcutManager
import android.graphics.drawable.Icon
import android.os.Build
import android.provider.Settings
import android.util.Log
import androidx.appcompat.app.AppCompatDelegate
import androidx.multidex.MultiDexApplication
import androidx.work.Configuration
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import cat.ereza.customactivityoncrash.config.CaocConfig
import com.chuckerteam.chucker.api.ChuckerCollector
import com.chuckerteam.chucker.api.ChuckerInterceptor
import com.chuckerteam.chucker.api.RetentionManager
import com.google.firebase.FirebaseApp
import com.google.firebase.FirebaseOptions
import com.google.firebase.iid.FirebaseInstanceId
import com.google.firebase.messaging.FirebaseMessaging
import com.google.gson.Gson
import com.hypertrack.hyperlog.HyperLog
import com.mikepenz.iconics.Iconics
import com.mikepenz.iconics.typeface.library.szkolny.font.SzkolnyFont
import im.wangchao.mhttp.MHttp
import im.wangchao.mhttp.internal.cookie.PersistentCookieJar
import im.wangchao.mhttp.internal.cookie.cache.SetCookieCache
import im.wangchao.mhttp.internal.cookie.persistence.SharedPrefsCookiePersistor
import kotlinx.coroutines.*
import me.leolin.shortcutbadger.ShortcutBadger
import okhttp3.OkHttpClient
import org.greenrobot.eventbus.EventBus
import pl.szczodrzynski.edziennik.config.Config
import pl.szczodrzynski.edziennik.data.api.events.ProfileListEmptyEvent
import pl.szczodrzynski.edziennik.data.api.szkolny.interceptor.Signing
import pl.szczodrzynski.edziennik.data.db.AppDb
import pl.szczodrzynski.edziennik.data.db.entity.Profile
import pl.szczodrzynski.edziennik.network.NetworkUtils
import pl.szczodrzynski.edziennik.sync.SyncWorker
import pl.szczodrzynski.edziennik.sync.UpdateWorker
import pl.szczodrzynski.edziennik.ui.modules.base.CrashActivity
import pl.szczodrzynski.edziennik.utils.*
import java.util.concurrent.TimeUnit
import kotlin.coroutines.CoroutineContext
class Szkolny : /*MultiDexApplication(),*/ Configuration.Provider, CoroutineScope {
class App : MultiDexApplication(), Configuration.Provider, CoroutineScope {
companion object {
@Volatile
lateinit var db: AppDb
val config: Config by lazy { Config(db) }
var profile: Profile by mutableLazy { Profile(0, 0, 0, "") }
val profileId
get() = profile.id
var devMode = false
}
//lateinit var db: AppDb
//val config by lazy { Config(db); // TODO migrate }
val notifications by lazy { Notifications() }
inner class Notifications {
val syncId = 1
val syncKey = "pl.szczodrzynski.edziennik.SYNC"
val syncChannelName: String by lazy { getString(R.string.notification_channel_get_data_name) }
val syncChannelDesc: String by lazy { getString(R.string.notification_channel_get_data_desc) }
val dataId = 50
val dataKey = "pl.szczodrzynski.edziennik.DATA"
val dataChannelName: String by lazy { getString(R.string.notification_channel_notifications_name) }
val dataChannelDesc: String by lazy { getString(R.string.notification_channel_notifications_desc) }
val dataQuietId = 60
val dataQuietKey = "pl.szczodrzynski.edziennik.DATA_QUIET"
val dataQuietChannelName: String by lazy { getString(R.string.notification_channel_notifications_quiet_name) }
val dataQuietChannelDesc: String by lazy { getString(R.string.notification_channel_notifications_quiet_desc) }
val updatesId = 100
val updatesKey = "pl.szczodrzynski.edziennik.UPDATES"
val updatesChannelName: String by lazy { getString(R.string.notification_channel_updates_name) }
val updatesChannelDesc: String by lazy { getString(R.string.notification_channel_updates_desc) }
}
val db
get() = App.db
val config
get() = App.config
val profile
get() = App.profile
val profileId
get() = App.profileId
private val job = Job()
override val coroutineContext: CoroutineContext
@ -26,11 +99,10 @@ class Szkolny : /*MultiDexApplication(),*/ Configuration.Provider, CoroutineScop
.setMinimumLoggingLevel(Log.VERBOSE)
.build()
/*val preferences by lazy { getSharedPreferences(getString(R.string.preference_file), Context.MODE_PRIVATE) }
val notifier by lazy { Notifier(this) }
val preferences by lazy { getSharedPreferences(getString(R.string.preference_file), Context.MODE_PRIVATE) }
val permissionChecker by lazy { PermissionChecker(this) }
lateinit var profile: ProfileFull
val networkUtils by lazy { NetworkUtils(this) }
val gson by lazy { Gson() }
/* _ _ _______ _______ _____
| | | |__ __|__ __| __ \
@ -48,13 +120,13 @@ class Szkolny : /*MultiDexApplication(),*/ Configuration.Provider, CoroutineScop
.connectTimeout(20, TimeUnit.SECONDS)
.writeTimeout(5, TimeUnit.SECONDS)
.readTimeout(10, TimeUnit.SECONDS)
builder.installHttpsSupport()
builder.installHttpsSupport(this)
if (devMode || BuildConfig.DEBUG) {
HyperLog.initialize(this)
HyperLog.setLogLevel(Log.VERBOSE)
HyperLog.setLogFormat(DebugLogFormat(this))
val chuckerCollector = ChuckerCollector(this, true, Period.ONE_HOUR)
val chuckerCollector = ChuckerCollector(this, true, RetentionManager.Period.ONE_HOUR)
val chuckerInterceptor = ChuckerInterceptor(this, chuckerCollector)
builder.addInterceptor(chuckerInterceptor)
}
@ -77,22 +149,7 @@ class Szkolny : /*MultiDexApplication(),*/ Configuration.Provider, CoroutineScop
|_____/|_|\__, |_| |_|\__,_|\__|\__,_|_| \___|
__/ |
|__*/
private val deviceId: String by lazy { Settings.Secure.getString(contentResolver, Settings.Secure.ANDROID_ID) ?: "" }
private val signature: String by lazy {
var str = ""
try {
val packageInfo: PackageInfo = packageManager.getPackageInfo(packageName, PackageManager.GET_SIGNATURES)
for (signature in packageInfo.signatures) {
val signatureBytes = signature.toByteArray()
val md = MessageDigest.getInstance("SHA")
md.update(signatureBytes)
str = Base64.encodeToString(md.digest(), Base64.DEFAULT)
}
} catch (e: Exception) {
e.printStackTrace()
}
str
}
val deviceId: String by lazy { Settings.Secure.getString(contentResolver, Settings.Secure.ANDROID_ID) ?: "" }
private var unreadBadgesAvailable = true
/* _____ _
@ -118,193 +175,214 @@ class Szkolny : /*MultiDexApplication(),*/ Configuration.Provider, CoroutineScop
.apply()
Iconics.init(applicationContext)
Iconics.registerFont(SzkolnyFont)
db = AppDb.getDatabase(this)
App.db = AppDb(this)
Themes.themeInt = config.ui.theme
MHttp.instance().customOkHttpClient(http)
if (!profileLoadById(config.lastProfileId)) {
db.profileDao().firstId?.let { profileLoadById(it) }
}
devMode = "f054761fbdb6a238" == deviceId || BuildConfig.DEBUG
if (config.devModePassword != null)
checkDevModePassword()
Signing.getCert(this)
launch { async(Dispatchers.Default) {
if (config.sync.enabled) {
scheduleNext(this@App, false)
} else {
cancelNext(this@App)
launch {
withContext(Dispatchers.Default) {
config.migrate(this@App)
if (config.devModePassword != null)
checkDevModePassword()
if (config.sync.enabled)
SyncWorker.scheduleNext(this@App, false)
else
SyncWorker.cancelNext(this@App)
if (config.sync.notifyAboutUpdates)
UpdateWorker.scheduleNext(this@App, false)
else
UpdateWorker.cancelNext(this@App)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1) {
val shortcutManager = getSystemService(ShortcutManager::class.java)
val shortcutTimetable = ShortcutInfo.Builder(this@App, "item_timetable")
.setShortLabel(getString(R.string.shortcut_timetable)).setLongLabel(getString(R.string.shortcut_timetable))
.setIcon(Icon.createWithResource(this@App, R.mipmap.ic_shortcut_timetable))
.setIntent(Intent(Intent.ACTION_MAIN, null, this@App, MainActivity::class.java)
.putExtra("fragmentId", MainActivity.DRAWER_ITEM_TIMETABLE))
.build()
val shortcutAgenda = ShortcutInfo.Builder(this@App, "item_agenda")
.setShortLabel(getString(R.string.shortcut_agenda)).setLongLabel(getString(R.string.shortcut_agenda))
.setIcon(Icon.createWithResource(this@App, R.mipmap.ic_shortcut_agenda))
.setIntent(Intent(Intent.ACTION_MAIN, null, this@App, MainActivity::class.java)
.putExtra("fragmentId", MainActivity.DRAWER_ITEM_AGENDA))
.build()
val shortcutGrades = ShortcutInfo.Builder(this@App, "item_grades")
.setShortLabel(getString(R.string.shortcut_grades)).setLongLabel(getString(R.string.shortcut_grades))
.setIcon(Icon.createWithResource(this@App, R.mipmap.ic_shortcut_grades))
.setIntent(Intent(Intent.ACTION_MAIN, null, this@App, MainActivity::class.java)
.putExtra("fragmentId", MainActivity.DRAWER_ITEM_GRADES))
.build()
val shortcutHomework = ShortcutInfo.Builder(this@App, "item_homeworks")
.setShortLabel(getString(R.string.shortcut_homework)).setLongLabel(getString(R.string.shortcut_homework))
.setIcon(Icon.createWithResource(this@App, R.mipmap.ic_shortcut_homework))
.setIntent(Intent(Intent.ACTION_MAIN, null, this@App, MainActivity::class.java)
.putExtra("fragmentId", MainActivity.DRAWER_ITEM_HOMEWORK))
.build()
val shortcutMessages = ShortcutInfo.Builder(this@App, "item_messages")
.setShortLabel(getString(R.string.shortcut_messages)).setLongLabel(getString(R.string.shortcut_messages))
.setIcon(Icon.createWithResource(this@App, R.mipmap.ic_shortcut_messages))
.setIntent(Intent(Intent.ACTION_MAIN, null, this@App, MainActivity::class.java)
.putExtra("fragmentId", MainActivity.DRAWER_ITEM_MESSAGES))
.build()
shortcutManager.dynamicShortcuts = listOf(
shortcutTimetable,
shortcutAgenda,
shortcutGrades,
shortcutHomework,
shortcutMessages
)
} // shortcuts - end
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val notificationManager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager
notificationManager.createNotificationChannel(
NotificationChannel(notifications.syncKey, notifications.syncChannelName, NotificationManager.IMPORTANCE_MIN).apply {
description = notifications.syncChannelDesc
})
notificationManager.createNotificationChannel(
NotificationChannel(notifications.dataKey, notifications.dataChannelName, NotificationManager.IMPORTANCE_HIGH).apply {
description = notifications.dataChannelDesc
enableLights(true)
lightColor = 0xff2196f3.toInt()
})
notificationManager.createNotificationChannel(
NotificationChannel(notifications.dataQuietKey, notifications.dataQuietChannelName, NotificationManager.IMPORTANCE_LOW).apply {
description = notifications.dataQuietChannelDesc
setSound(null, null)
enableVibration(false)
})
notificationManager.createNotificationChannel(
NotificationChannel(notifications.updatesKey, notifications.updatesChannelName, NotificationManager.IMPORTANCE_DEFAULT).apply {
description = notifications.updatesChannelDesc
})
}
if (config.appInstalledTime == 0L)
try {
config.appInstalledTime = packageManager.getPackageInfo(packageName, 0).firstInstallTime
config.appRateSnackbarTime = config.appInstalledTime + 7 * DAY * MS
} catch (e: PackageManager.NameNotFoundException) {
e.printStackTrace()
}
val pushMobidziennikApp = FirebaseApp.initializeApp(
this@App,
FirebaseOptions.Builder()
.setApiKey("AIzaSyCi5LmsZ5BBCQnGtrdvWnp1bWLCNP8OWQE")
.setApplicationId("1:747285019373:android:f6341bf7b158621d")
.build(),
"Mobidziennik2"
)
val pushLibrusApp = FirebaseApp.initializeApp(
this@App,
FirebaseOptions.Builder()
.setApiKey("AIzaSyDfTuEoYPKdv4aceEws1CO3n0-HvTndz-o")
.setApplicationId("1:513056078587:android:1e29083b760af544")
.build(),
"Librus"
)
val pushVulcanApp = FirebaseApp.initializeApp(
this@App,
FirebaseOptions.Builder()
.setApiKey("AIzaSyDW8MUtanHy64_I0oCpY6cOxB3jrvJd_iA")
.setApplicationId("1:987828170337:android:ac97431a0a4578c3")
.build(),
"Vulcan"
)
try {
FirebaseInstanceId.getInstance().instanceId.addOnSuccessListener { instanceIdResult ->
val token = instanceIdResult.token
config.sync.tokenApp = token
}
FirebaseInstanceId.getInstance(pushMobidziennikApp).instanceId.addOnSuccessListener { instanceIdResult ->
val token = instanceIdResult.token
if (token != config.sync.tokenMobidziennik) {
config.sync.tokenMobidziennik = token
config.sync.tokenMobidziennikList = listOf()
}
}
FirebaseInstanceId.getInstance(pushLibrusApp).instanceId.addOnSuccessListener { instanceIdResult ->
val token = instanceIdResult.token
if (token != config.sync.tokenLibrus) {
config.sync.tokenLibrus = token
config.sync.tokenLibrusList = listOf()
}
}
FirebaseInstanceId.getInstance(pushVulcanApp).instanceId.addOnSuccessListener { instanceIdResult ->
val token = instanceIdResult.token
if (token != config.sync.tokenVulcan) {
config.sync.tokenVulcan = token
config.sync.tokenVulcanList = listOf()
}
}
FirebaseMessaging.getInstance().subscribeToTopic(packageName)
} catch (e: IllegalStateException) {
e.printStackTrace()
}
}
db.metadataDao().countUnseen().observeForever { count: Int ->
if (unreadBadgesAvailable)
unreadBadgesAvailable = ShortcutBadger.applyCount(this@App, count)
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1) {
val shortcutManager = getSystemService(ShortcutManager::class.java)
val shortcutTimetable = ShortcutInfo.Builder(this@App, "item_timetable")
.setShortLabel(getString(R.string.shortcut_timetable)).setLongLabel(getString(R.string.shortcut_timetable))
.setIcon(Icon.createWithResource(this@App, R.mipmap.ic_shortcut_timetable))
.setIntent(Intent(Intent.ACTION_MAIN, null, this@App, MainActivity::class.java)
.putExtra("fragmentId", MainActivity.DRAWER_ITEM_TIMETABLE))
.build()
val shortcutAgenda = ShortcutInfo.Builder(this@App, "item_agenda")
.setShortLabel(getString(R.string.shortcut_agenda)).setLongLabel(getString(R.string.shortcut_agenda))
.setIcon(Icon.createWithResource(this@App, R.mipmap.ic_shortcut_agenda))
.setIntent(Intent(Intent.ACTION_MAIN, null, this@App, MainActivity::class.java)
.putExtra("fragmentId", MainActivity.DRAWER_ITEM_AGENDA))
.build()
val shortcutGrades = ShortcutInfo.Builder(this@App, "item_grades")
.setShortLabel(getString(R.string.shortcut_grades)).setLongLabel(getString(R.string.shortcut_grades))
.setIcon(Icon.createWithResource(this@App, R.mipmap.ic_shortcut_grades))
.setIntent(Intent(Intent.ACTION_MAIN, null, this@App, MainActivity::class.java)
.putExtra("fragmentId", MainActivity.DRAWER_ITEM_GRADES))
.build()
val shortcutHomework = ShortcutInfo.Builder(this@App, "item_homeworks")
.setShortLabel(getString(R.string.shortcut_homework)).setLongLabel(getString(R.string.shortcut_homework))
.setIcon(Icon.createWithResource(this@App, R.mipmap.ic_shortcut_homework))
.setIntent(Intent(Intent.ACTION_MAIN, null, this@App, MainActivity::class.java)
.putExtra("fragmentId", MainActivity.DRAWER_ITEM_HOMEWORK))
.build()
val shortcutMessages = ShortcutInfo.Builder(this@App, "item_messages")
.setShortLabel(getString(R.string.shortcut_messages)).setLongLabel(getString(R.string.shortcut_messages))
.setIcon(Icon.createWithResource(this@App, R.mipmap.ic_shortcut_messages))
.setIntent(Intent(Intent.ACTION_MAIN, null, this@App, MainActivity::class.java)
.putExtra("fragmentId", MainActivity.DRAWER_ITEM_MESSAGES))
.build()
shortcutManager.dynamicShortcuts = listOf(
shortcutTimetable,
shortcutAgenda,
shortcutGrades,
shortcutHomework,
shortcutMessages
)
} // shortcuts - end
if (config.appInstalledTime == 0L)
try {
config.appInstalledTime = packageManager.getPackageInfo(packageName, 0).firstInstallTime
config.appRateSnackbarTime = config.appInstalledTime + 7*DAY*MS
} catch (e: NameNotFoundException) {
e.printStackTrace()
}
val pushMobidziennikApp = FirebaseApp.initializeApp(
this@App,
FirebaseOptions.Builder()
.setApiKey("AIzaSyCi5LmsZ5BBCQnGtrdvWnp1bWLCNP8OWQE")
.setApplicationId("1:747285019373:android:f6341bf7b158621d")
.build(),
"Mobidziennik2"
)
val pushLibrusApp = FirebaseApp.initializeApp(
this@App,
FirebaseOptions.Builder()
.setApiKey("AIzaSyDfTuEoYPKdv4aceEws1CO3n0-HvTndz-o")
.setApplicationId("1:513056078587:android:1e29083b760af544")
.build(),
"Librus"
)
val pushVulcanApp = FirebaseApp.initializeApp(
this@App,
FirebaseOptions.Builder()
.setApiKey("AIzaSyDW8MUtanHy64_I0oCpY6cOxB3jrvJd_iA")
.setApplicationId("1:987828170337:android:ac97431a0a4578c3")
.build(),
"Vulcan"
)
try {
FirebaseInstanceId.getInstance().instanceId.addOnSuccessListener { instanceIdResult ->
val token = instanceIdResult.token
config.sync.tokenApp = token
}
FirebaseInstanceId.getInstance(pushMobidziennikApp).instanceId.addOnSuccessListener { instanceIdResult ->
val token = instanceIdResult.token
if (token != config.sync.tokenMobidziennik) {
config.sync.tokenMobidziennik = token
config.sync.tokenMobidziennikList = listOf()
}
}
FirebaseInstanceId.getInstance(pushLibrusApp).instanceId.addOnSuccessListener { instanceIdResult ->
val token = instanceIdResult.token
if (token != config.sync.tokenLibrus) {
config.sync.tokenLibrus = token
config.sync.tokenLibrusList = listOf()
}
}
FirebaseInstanceId.getInstance(pushVulcanApp).instanceId.addOnSuccessListener { instanceIdResult ->
val token = instanceIdResult.token
if (token != config.sync.tokenVulcan) {
config.sync.tokenVulcan = token
config.sync.tokenVulcanList = listOf()
}
}
FirebaseMessaging.getInstance().subscribeToTopic(packageName)
} catch (e: IllegalStateException) {
e.printStackTrace()
}
}}
}
private fun profileLoad(profileId: Int) {
db.profileDao().getFullByIdNow(profileId)?.also {
profile = it
} ?: run {
if (!::profile.isInitialized) {
profile = ProfileFull(-1, "", "", -1)
}
}
}
fun profileLoad(profileId: Int, onSuccess: (profile: ProfileFull) -> Unit) {
private fun profileLoadById(profileId: Int): Boolean {
db.profileDao().getByIdNow(profileId)?.also {
App.profile = it
App.config.lastProfileId = it.id
return true
}
return false
}
fun profileLoad(profileId: Int, onSuccess: (profile: Profile) -> Unit) {
launch {
val deferred = async(Dispatchers.Default) {
profileLoad(profileId)
val success = withContext(Dispatchers.Default) {
profileLoadById(profileId)
}
deferred.await()
onSuccess(profile)
if (success)
onSuccess(profile)
else
profileLoadLast(onSuccess)
}
}
private fun OkHttpClient.Builder.installHttpsSupport() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN && Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP_MR1) {
try {
try {
ProviderInstaller.installIfNeeded(this@App)
} catch (e: Exception) {
Log.e("OkHttpTLSCompat", "Play Services not found or outdated")
val trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm())
trustManagerFactory.init(null as KeyStore?)
val x509TrustManager = trustManagerFactory.trustManagers.singleOrNull { it is X509TrustManager } as X509TrustManager?
?: return
val sc = SSLContext.getInstance("TLSv1.2")
sc.init(null, null, null)
sslSocketFactory(TLSSocketFactory(sc.socketFactory), x509TrustManager)
val cs: ConnectionSpec = ConnectionSpec.Builder(ConnectionSpec.MODERN_TLS)
.tlsVersions(TlsVersion.TLS_1_0)
.tlsVersions(TlsVersion.TLS_1_1)
.tlsVersions(TlsVersion.TLS_1_2)
.build()
val specs: MutableList<ConnectionSpec> = ArrayList()
specs.add(cs)
specs.add(ConnectionSpec.COMPATIBLE_TLS)
specs.add(ConnectionSpec.CLEARTEXT)
connectionSpecs(specs)
}
} catch (exc: Exception) {
Log.e("OkHttpTLSCompat", "Error while setting TLS 1.2", exc)
fun profileLoadLast(onSuccess: (profile: Profile) -> Unit) {
launch {
val success = withContext(Dispatchers.Default) {
profileLoadById(db.profileDao().lastId ?: return@withContext false)
}
if (!success) {
EventBus.getDefault().post(ProfileListEmptyEvent())
}
}
}
fun profileSave() = profileSave(profile)
fun profileSave(profile: Profile) {
launch(Dispatchers.Default) {
App.db.profileDao().add(profile)
}
}
@ -315,5 +393,5 @@ class Szkolny : /*MultiDexApplication(),*/ Configuration.Provider, CoroutineScop
e.printStackTrace()
false
}
}*/
}
}

View File

@ -1,91 +1,10 @@
/*
* Copyright (c) Kuba Szczodrzyński 2020-1-19.
*/
package pl.szczodrzynski.edziennik;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.ShortcutInfo;
import android.content.pm.ShortcutManager;
import android.content.pm.Signature;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.drawable.Icon;
import android.os.AsyncTask;
import android.os.Build;
import android.os.Handler;
import android.provider.Settings;
import android.util.Base64;
import android.util.Log;
import androidx.annotation.RequiresApi;
import androidx.appcompat.app.AppCompatDelegate;
import androidx.work.Configuration;
import com.chuckerteam.chucker.api.ChuckerCollector;
import com.chuckerteam.chucker.api.ChuckerInterceptor;
import com.chuckerteam.chucker.api.RetentionManager;
import com.google.android.gms.security.ProviderInstaller;
import com.google.firebase.FirebaseApp;
import com.google.firebase.FirebaseOptions;
import com.google.firebase.iid.FirebaseInstanceId;
import com.google.firebase.messaging.FirebaseMessaging;
import com.google.gson.Gson;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonSyntaxException;
import com.hypertrack.hyperlog.HyperLog;
import com.mikepenz.iconics.Iconics;
import com.mikepenz.iconics.IconicsColor;
import com.mikepenz.iconics.IconicsDrawable;
import com.mikepenz.iconics.IconicsSize;
import com.mikepenz.iconics.typeface.IIcon;
import com.mikepenz.iconics.typeface.library.szkolny.font.SzkolnyFont;
import java.lang.reflect.Field;
import java.security.KeyStore;
import java.security.MessageDigest;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.ConcurrentModificationException;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.X509TrustManager;
import cat.ereza.customactivityoncrash.config.CaocConfig;
import im.wangchao.mhttp.MHttp;
import im.wangchao.mhttp.internal.cookie.PersistentCookieJar;
import im.wangchao.mhttp.internal.cookie.cache.SetCookieCache;
import im.wangchao.mhttp.internal.cookie.persistence.SharedPrefsCookiePersistor;
import me.leolin.shortcutbadger.ShortcutBadger;
import okhttp3.ConnectionSpec;
import okhttp3.OkHttpClient;
import okhttp3.TlsVersion;
import pl.szczodrzynski.edziennik.config.Config;
import pl.szczodrzynski.edziennik.data.api.szkolny.interceptor.Signing;
import pl.szczodrzynski.edziennik.data.api.task.EdziennikTask;
import pl.szczodrzynski.edziennik.data.db.AppDb;
import pl.szczodrzynski.edziennik.data.db.entity.DebugLog;
import pl.szczodrzynski.edziennik.data.db.entity.Profile;
import pl.szczodrzynski.edziennik.network.NetworkUtils;
import pl.szczodrzynski.edziennik.network.TLSSocketFactory;
import pl.szczodrzynski.edziennik.sync.SyncWorker;
import pl.szczodrzynski.edziennik.ui.modules.base.CrashActivity;
import pl.szczodrzynski.edziennik.utils.DebugLogFormat;
import pl.szczodrzynski.edziennik.utils.PermissionChecker;
import pl.szczodrzynski.edziennik.utils.Themes;
import pl.szczodrzynski.edziennik.utils.Utils;
import pl.szczodrzynski.edziennik.utils.models.AppConfig;
import static pl.szczodrzynski.edziennik.data.db.entity.LoginStore.LOGIN_TYPE_MOBIDZIENNIK;
public class App extends androidx.multidex.MultiDexApplication implements Configuration.Provider {
/*public class AppOld extends androidx.multidex.MultiDexApplication implements Configuration.Provider {
private static final String TAG = "App";
public static int profileId = -1;
private Context mContext;
@ -379,36 +298,7 @@ public class App extends androidx.multidex.MultiDexApplication implements Config
}
}
/*Task<CapabilityInfo> capabilityInfoTask =
Wearable.getCapabilityClient(this)
.getCapability("edziennik_wear_app", CapabilityClient.FILTER_REACHABLE);
capabilityInfoTask.addOnCompleteListener((task) -> {
if (task.isSuccessful()) {
CapabilityInfo capabilityInfo = task.getResult();
assert capabilityInfo != null;
Set<Node> nodes;
nodes = capabilityInfo.getNodes();
Log.d(TAG, "Nodes "+nodes);
if (nodes.size() > 0) {
Wearable.getMessageClient(this).sendMessage(
nodes.toArray(new Node[]{})[0].getId(), "/ping", "Hello world".getBytes());
}
} else {
Log.d(TAG, "Capability request failed to return any results.");
}
});
Wearable.getDataClient(this).addListener(dataEventBuffer -> {
Log.d(TAG, "onDataChanged(): " + dataEventBuffer);
for (DataEvent event : dataEventBuffer) {
if (event.getType() == DataEvent.TYPE_CHANGED) {
String path = event.getDataItem().getUri().getPath();
Log.d(TAG, "Data "+path+ " :: "+Arrays.toString(event.getDataItem().getData()));
}
}
});*/
FirebaseApp pushMobidziennikApp = FirebaseApp.initializeApp(
this,
@ -498,14 +388,7 @@ public class App extends androidx.multidex.MultiDexApplication implements Config
if (appSharedPrefs.contains("profiles")) {
SharedPreferences.Editor appSharedPrefsEditor = appSharedPrefs.edit();
/*List<Integer> appProfileIds = gson.fromJson(appSharedPrefs.getString("profiles", ""), new TypeToken<List<Integer>>(){}.getType());
for (int id: appProfileIds) {
AppProfile appProfile = gson.fromJson(appSharedPrefs.getString("profile"+id, ""), AppProfile.class);
if (appProfile != null) {
appConfig.profiles.add(appProfile);
}
appSharedPrefsEditor.remove("profile"+id);
}*/
appSharedPrefsEditor.remove("profiles");
appSharedPrefsEditor.apply();
//profilesSave();
@ -539,16 +422,6 @@ public class App extends androidx.multidex.MultiDexApplication implements Config
}
}
/*if (appConfig.lastAppVersion > BuildConfig.VERSION_CODE) {
BootReceiver br = new BootReceiver();
Intent i = new Intent();
//i.putExtra("UserChecked", true);
br.onReceive(getContext(), i);
Toast.makeText(mContext, R.string.warning_older_version_running, Toast.LENGTH_LONG).show();
//Toast.makeText(mContext, "Zaktualizuj aplikację.", Toast.LENGTH_LONG).show();
//System.exit(0);
}*/
if (appConfig == null) {
appConfig = new AppConfig(this);
}
@ -564,15 +437,7 @@ public class App extends androidx.multidex.MultiDexApplication implements Config
for (Map.Entry<String, JsonElement> entry : appConfigJson.entrySet()) {
String jsonObj;
jsonObj = entry.getValue().toString();
/*if (entry.getValue().isJsonObject()) {
jsonObj = entry.getValue().getAsJsonObject().toString();
}
else if (entry.getValue().isJsonArray()) {
jsonObj = entry.getValue().getAsJsonArray().toString();
}
else {
jsonObj = entry.getValue().toString();
}*/
appSharedPrefsEditor.putString("app.appConfig." + entry.getKey(), jsonObj);
}
@ -628,18 +493,10 @@ public class App extends androidx.multidex.MultiDexApplication implements Config
}
public void profileLoadById(int id, boolean loadedLast) {
//Log.d(TAG, "Loading ID "+id);
/*if (profile == null) {
profile = profileNew();
AppDb.profileId = profile.id;
appSharedPrefs.edit().putInt("current_profile_id", profile.id).apply();
return;
}*/
if (profile == null || profile.getId() != id) {
profile = db.profileDao().getByIdNow(id);
/*if (profile == null) {
profileLoadById(id);
return;
}*/
if (profile != null) {
MainActivity.Companion.setUseOldMessages(profile.getLoginStoreType() == LOGIN_TYPE_MOBIDZIENNIK && appConfig.mobidziennikOldMessages == 1);
profileId = profile.getId();
@ -655,44 +512,6 @@ public class App extends androidx.multidex.MultiDexApplication implements Config
}
}
/*public void profileRemove(int id)
{
Profile profile = db.profileDao().getFullByIdNow(id);
if (profile.id == profile.loginStoreId) {
// this profile is the owner of the login store
// we need to check if any other profile is using it
List<Integer> transferProfileIds = db.profileDao().getIdsByLoginStoreIdNow(profile.loginStoreId);
if (transferProfileIds.size() == 1) {
// this login store is free of users, remove it along with the profile
db.loginStoreDao().remove(profile.loginStoreId);
// the current store is removed, we are ready to remove the profile
}
else if (transferProfileIds.size() > 1) {
transferProfileIds.remove(transferProfileIds.indexOf(profile.id));
// someone is using the store
// we need to transfer it to the firstProfileId
db.loginStoreDao().changeId(profile.loginStoreId, transferProfileIds.get(0));
db.profileDao().changeStoreId(profile.loginStoreId, transferProfileIds.get(0));
// the current store is removed, we are ready to remove the profile
}
}
// else, the profile uses a store that it doesn't own
// leave the store and go on with removing
Log.d(TAG, "Before removal: "+db.profileDao().getAllNow().toString());
db.profileDao().remove(profile.id);
Log.d(TAG, "After removal: "+db.profileDao().getAllNow().toString());
*//*int newId = 1;
if (appConfig.profiles.size() > 0) {
newId = appConfig.profiles.get(appConfig.profiles.size() - 1).id;
}
Log.d(TAG, "New ID: "+newId);
//Toast.makeText(mContext, "selected new id "+newId, Toast.LENGTH_SHORT).show();
profileLoadById(newId);*//*
}*/
public int profileFirstId() {
Integer id = db.profileDao().getFirstId();
return id == null ? 1 : id;
@ -719,4 +538,4 @@ public class App extends androidx.multidex.MultiDexApplication implements Config
}
}
}
}*/

View File

@ -35,6 +35,7 @@ import androidx.core.util.forEach
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.LiveData
import androidx.lifecycle.Observer
import com.google.android.gms.security.ProviderInstaller
import com.google.gson.JsonArray
import com.google.gson.JsonElement
import com.google.gson.JsonObject
@ -42,23 +43,31 @@ import im.wangchao.mhttp.Response
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import okhttp3.ConnectionSpec
import okhttp3.OkHttpClient
import okhttp3.RequestBody
import okhttp3.TlsVersion
import okio.Buffer
import pl.szczodrzynski.edziennik.data.db.entity.Notification
import pl.szczodrzynski.edziennik.data.db.entity.Profile
import pl.szczodrzynski.edziennik.data.db.entity.Teacher
import pl.szczodrzynski.edziennik.data.db.entity.Team
import pl.szczodrzynski.edziennik.network.TLSSocketFactory
import pl.szczodrzynski.edziennik.utils.models.Time
import java.io.PrintWriter
import java.io.StringWriter
import java.math.BigInteger
import java.nio.charset.Charset
import java.security.KeyStore
import java.security.MessageDigest
import java.text.SimpleDateFormat
import java.util.*
import java.util.zip.CRC32
import javax.crypto.Mac
import javax.crypto.spec.SecretKeySpec
import javax.net.ssl.SSLContext
import javax.net.ssl.TrustManagerFactory
import javax.net.ssl.X509TrustManager
import kotlin.Pair
@ -643,6 +652,12 @@ fun Bundle(vararg properties: Pair<String, Any?>): Bundle {
}
}
}
fun Intent(action: String? = null, vararg properties: Pair<String, Any?>): Intent {
return Intent(action).putExtras(Bundle(*properties))
}
fun Intent(packageContext: Context, cls: Class<*>, vararg properties: Pair<String, Any?>): Intent {
return Intent(packageContext, cls).putExtras(Bundle(*properties))
}
fun Bundle.toJsonObject(): JsonObject {
val json = JsonObject()
@ -957,6 +972,8 @@ fun Context.getNotificationTitle(type: Int): String {
Notification.TYPE_NEW_EVENT -> R.string.notification_type_new_event
Notification.TYPE_NEW_HOMEWORK -> R.string.notification_type_new_homework
Notification.TYPE_NEW_SHARED_EVENT -> R.string.notification_type_new_shared_event
Notification.TYPE_NEW_SHARED_HOMEWORK -> R.string.notification_type_new_shared_homework
Notification.TYPE_REMOVED_SHARED_EVENT -> R.string.notification_type_removed_shared_event
Notification.TYPE_NEW_MESSAGE -> R.string.notification_type_new_message
Notification.TYPE_NEW_NOTICE -> R.string.notification_type_notice
Notification.TYPE_NEW_ATTENDANCE -> R.string.notification_type_attendance
@ -973,3 +990,37 @@ fun Context.getNotificationTitle(type: Int): String {
fun Cursor?.getString(columnName: String) = this?.getStringOrNull(getColumnIndex(columnName))
fun Cursor?.getInt(columnName: String) = this?.getIntOrNull(getColumnIndex(columnName))
fun Cursor?.getLong(columnName: String) = this?.getLongOrNull(getColumnIndex(columnName))
fun OkHttpClient.Builder.installHttpsSupport(context: Context) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN && Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP_MR1) {
try {
try {
ProviderInstaller.installIfNeeded(context)
} catch (e: Exception) {
Log.e("OkHttpTLSCompat", "Play Services not found or outdated")
val trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm())
trustManagerFactory.init(null as KeyStore?)
val x509TrustManager = trustManagerFactory.trustManagers.singleOrNull { it is X509TrustManager } as X509TrustManager?
?: return
val sc = SSLContext.getInstance("TLSv1.2")
sc.init(null, null, null)
sslSocketFactory(TLSSocketFactory(sc.socketFactory), x509TrustManager)
val cs: ConnectionSpec = ConnectionSpec.Builder(ConnectionSpec.MODERN_TLS)
.tlsVersions(TlsVersion.TLS_1_0)
.tlsVersions(TlsVersion.TLS_1_1)
.tlsVersions(TlsVersion.TLS_1_2)
.build()
val specs: MutableList<ConnectionSpec> = ArrayList()
specs.add(cs)
specs.add(ConnectionSpec.COMPATIBLE_TLS)
specs.add(ConnectionSpec.CLEARTEXT)
connectionSpecs(specs)
}
} catch (exc: Exception) {
Log.e("OkHttpTLSCompat", "Error while setting TLS 1.2", exc)
}
}
}

View File

@ -1,6 +1,5 @@
package pl.szczodrzynski.edziennik
import android.app.Activity
import android.app.ActivityManager
import android.content.BroadcastReceiver
import android.content.Context
@ -9,7 +8,6 @@ import android.content.IntentFilter
import android.content.pm.PackageManager
import android.graphics.BitmapFactory
import android.graphics.drawable.BitmapDrawable
import android.os.AsyncTask
import android.os.Build
import android.os.Bundle
import android.os.Environment
@ -41,15 +39,16 @@ import org.greenrobot.eventbus.EventBus
import org.greenrobot.eventbus.Subscribe
import org.greenrobot.eventbus.ThreadMode
import pl.droidsonroids.gif.GifDrawable
import pl.szczodrzynski.edziennik.data.api.edziennik.EdziennikTask
import pl.szczodrzynski.edziennik.data.api.events.*
import pl.szczodrzynski.edziennik.data.api.models.ApiError
import pl.szczodrzynski.edziennik.data.api.szkolny.interceptor.Signing
import pl.szczodrzynski.edziennik.data.api.task.EdziennikTask
import pl.szczodrzynski.edziennik.data.db.entity.LoginStore
import pl.szczodrzynski.edziennik.data.db.entity.Metadata.*
import pl.szczodrzynski.edziennik.databinding.ActivitySzkolnyBinding
import pl.szczodrzynski.edziennik.sync.AppManagerDetectedEvent
import pl.szczodrzynski.edziennik.sync.SyncWorker
import pl.szczodrzynski.edziennik.sync.UpdateWorker
import pl.szczodrzynski.edziennik.ui.dialogs.changelog.ChangelogDialog
import pl.szczodrzynski.edziennik.ui.dialogs.settings.ProfileRemoveDialog
import pl.szczodrzynski.edziennik.ui.dialogs.sync.SyncViewListDialog
@ -269,12 +268,21 @@ class MainActivity : AppCompatActivity(), CoroutineScope {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
d(TAG, "Activity created")
setTheme(Themes.appTheme)
app.config.ui.language?.let {
setLanguage(it)
}
if (App.profileId == 0) {
onProfileListEmptyEvent(ProfileListEmptyEvent())
return
}
d(TAG, "Profile is valid, inflating views")
setContentView(b.root)
Log.d(TAG, Signing.appPassword)
@ -344,8 +352,7 @@ class MainActivity : AppCompatActivity(), CoroutineScope {
setAccountHeaderBackground(app.config.ui.headerBackground)
drawerProfileListEmptyListener = {
app.config.loginFinished = false
profileListEmptyListener()
onProfileListEmptyEvent(ProfileListEmptyEvent())
}
drawerItemSelectedListener = { id, position, drawerItem ->
loadTarget(id)
@ -374,30 +381,19 @@ class MainActivity : AppCompatActivity(), CoroutineScope {
navTarget = navTargetList[0]
var profileListEmpty = drawer.profileListEmpty
if (savedInstanceState != null) {
intent?.putExtras(savedInstanceState)
savedInstanceState.clear()
}
if (!profileListEmpty) {
handleIntent(intent?.extras)
}
app.db.profileDao().all.observe(this, Observer { profiles ->
drawer.setProfileList(profiles.filter { it.id >= 0 }.toMutableList())
if (profileListEmpty) {
profileListEmpty = false
handleIntent(intent?.extras)
}
else if (app.profile != null) {
drawer.currentProfile = app.profile.id
}
drawer.currentProfile = App.profileId
})
// if null, getAllFull will load a profile and update drawerItems
if (app.profile != null)
setDrawerItems()
setDrawerItems()
handleIntent(intent?.extras)
app.db.metadataDao().unreadCounts.observe(this, Observer { unreadCounters ->
unreadCounters.map {
@ -417,6 +413,7 @@ class MainActivity : AppCompatActivity(), CoroutineScope {
isStoragePermissionGranted()
SyncWorker.scheduleNext(app)
UpdateWorker.scheduleNext(app)
// APP BACKGROUND
if (app.config.ui.appBackground != null) {
@ -521,13 +518,10 @@ class MainActivity : AppCompatActivity(), CoroutineScope {
}
}
var profileListEmptyListener = {
startActivityForResult(Intent(this, LoginActivity::class.java), REQUEST_LOGIN_ACTIVITY)
}
private var profileSettingClickListener = { id: Int, view: View? ->
when (id) {
DRAWER_PROFILE_ADD_NEW -> {
profileListEmptyListener()
startActivityForResult(Intent(this, LoginActivity::class.java), REQUEST_LOGIN_ACTIVITY)
}
DRAWER_PROFILE_SYNC_ALL -> {
EdziennikTask.sync().enqueue(this)
@ -587,6 +581,13 @@ class MainActivity : AppCompatActivity(), CoroutineScope {
}
}
@Subscribe(threadMode = ThreadMode.MAIN)
fun onProfileListEmptyEvent(event: ProfileListEmptyEvent) {
d(TAG, "Profile list is empty. Launch LoginActivity.")
app.config.loginFinished = false
startActivity(Intent(this, LoginActivity::class.java))
finish()
}
@Subscribe(threadMode = ThreadMode.MAIN)
fun onApiTaskProgressEvent(event: ApiTaskProgressEvent) {
if (event.profileId == App.profileId) {
navView.toolbar.apply {
@ -630,7 +631,7 @@ class MainActivity : AppCompatActivity(), CoroutineScope {
@Subscribe(threadMode = ThreadMode.MAIN, sticky = true)
fun onAppManagerDetectedEvent(event: AppManagerDetectedEvent) {
EventBus.getDefault().removeStickyEvent(event)
if (app.appConfig.dontShowAppManagerDialog)
if (app.config.sync.dontShowAppManagerDialog)
return
MaterialAlertDialogBuilder(this)
.setTitle(R.string.app_manager_dialog_title)
@ -652,8 +653,7 @@ class MainActivity : AppCompatActivity(), CoroutineScope {
}
}
.setNeutralButton(R.string.dont_ask_again) { dialog, which ->
app.appConfig.dontShowAppManagerDialog = true
app.saveConfig("dontShowAppManagerDialog")
app.config.sync.dontShowAppManagerDialog = true
}
.setCancelable(false)
.show()
@ -698,7 +698,7 @@ class MainActivity : AppCompatActivity(), CoroutineScope {
if (extras?.containsKey("reloadProfileId") == true) {
val reloadProfileId = extras.getInt("reloadProfileId", -1)
extras.remove("reloadProfileId")
if (reloadProfileId == -1 || (app.profile != null && app.profile.id == reloadProfileId)) {
if (reloadProfileId == -1 || app.profile.id == reloadProfileId) {
reloadTarget()
return
}
@ -728,9 +728,9 @@ class MainActivity : AppCompatActivity(), CoroutineScope {
}
when {
app.profile == null || app.profile.id == -1 -> {
app.profile.id == 0 -> {
if (intentProfileId == -1)
intentProfileId = app.appSharedPrefs.getInt("current_profile_id", 1)
intentProfileId = app.config.lastProfileId
loadProfile(intentProfileId, intentTargetId, extras)
}
intentProfileId != -1 -> {
@ -769,7 +769,16 @@ class MainActivity : AppCompatActivity(), CoroutineScope {
startActivity(intent)
}
override fun onStart() {
d(TAG, "Activity started")
super.onStart()
}
override fun onStop() {
d(TAG, "Activity stopped")
super.onStop()
}
override fun onResume() {
d(TAG, "Activity resumed")
val filter = IntentFilter()
filter.addAction(Intent.ACTION_MAIN)
registerReceiver(intentReceiver, filter)
@ -777,10 +786,15 @@ class MainActivity : AppCompatActivity(), CoroutineScope {
super.onResume()
}
override fun onPause() {
d(TAG, "Activity paused")
unregisterReceiver(intentReceiver)
EventBus.getDefault().unregister(this)
super.onPause()
}
override fun onDestroy() {
d(TAG, "Activity destroyed")
super.onDestroy()
}
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
@ -794,15 +808,10 @@ class MainActivity : AppCompatActivity(), CoroutineScope {
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (requestCode == REQUEST_LOGIN_ACTIVITY) {
if (resultCode == Activity.RESULT_CANCELED && false) {
if (!app.config.loginFinished)
finish()
}
else {
if (!app.config.loginFinished)
finish()
else {
handleIntent(data?.extras)
}
handleIntent(data?.extras)
}
}
}
@ -823,34 +832,23 @@ class MainActivity : AppCompatActivity(), CoroutineScope {
fun loadProfile(id: Int) = loadProfile(id, navTargetId)
fun loadProfile(id: Int, arguments: Bundle?) = loadProfile(id, navTargetId, arguments)
fun loadProfile(id: Int, drawerSelection: Int, arguments: Bundle? = null) {
//d("NavDebug", "loadProfile(id = $id, drawerSelection = $drawerSelection)")
if (app.profile != null && App.profileId == id) {
if (App.profileId == id) {
drawer.currentProfile = app.profile.id
loadTarget(drawerSelection, arguments)
return
}
AsyncTask.execute {
app.profileLoadById(id)
app.profileLoad(id) {
MessagesFragment.pageSelection = -1
MessagesListFragment.tapPositions = intArrayOf(RecyclerView.NO_POSITION, RecyclerView.NO_POSITION)
MessagesListFragment.topPositions = intArrayOf(RecyclerView.NO_POSITION, RecyclerView.NO_POSITION)
MessagesListFragment.bottomPositions = intArrayOf(RecyclerView.NO_POSITION, RecyclerView.NO_POSITION)
this.runOnUiThread {
if (app.profile == null) {
if (app.config.loginFinished) {
// this shouldn't run
profileListEmptyListener()
}
} else {
setDrawerItems()
// the drawer profile is updated automatically when the drawer item is clicked
// update it manually when switching profiles from other source
//if (drawer.currentProfile != app.profile.id)
drawer.currentProfile = app.profile.id
loadTarget(drawerSelection, arguments)
}
}
setDrawerItems()
// the drawer profile is updated automatically when the drawer item is clicked
// update it manually when switching profiles from other source
//if (drawer.currentProfile != app.profile.id)
drawer.currentProfile = app.profileId
loadTarget(drawerSelection, arguments)
}
}
fun loadTarget(id: Int, arguments: Bundle? = null) {
@ -995,7 +993,7 @@ class MainActivity : AppCompatActivity(), CoroutineScope {
* that something has changed in the bottom sheet.
*/
fun gainAttention() {
if (app.config.ui.bottomSheetOpened || true)
if (app.config.ui.bottomSheetOpened)
return
b.navView.postDelayed({
navView.gainAttentionOnBottomBar()
@ -1044,12 +1042,11 @@ class MainActivity : AppCompatActivity(), CoroutineScope {
}
fun setDrawerItems() {
d("NavDebug", "setDrawerItems() app.profile = ${app.profile ?: "null"}")
d("NavDebug", "setDrawerItems() app.profile = ${app.profile}")
val drawerItems = arrayListOf<IDrawerItem<*>>()
val drawerProfiles = arrayListOf<ProfileSettingDrawerItem>()
val supportedFragments = if (app.profile == null) arrayListOf<Int>()
else app.profile.supportedFragments
val supportedFragments = app.profile.supportedFragments
targetPopToHomeList.clear()
@ -1113,7 +1110,7 @@ class MainActivity : AppCompatActivity(), CoroutineScope {
private var targetHomeId: Int = -1
override fun onBackPressed() {
if (!b.navView.onBackPressed()) {
if (App.getConfig().ui.openDrawerOnBackPressed) {
if (App.config.ui.openDrawerOnBackPressed) {
b.navView.drawer.toggle()
} else {
navigateUp()

View File

@ -1,350 +0,0 @@
package pl.szczodrzynski.edziennik;
import android.app.IntentService;
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.os.Build;
import android.util.Log;
import androidx.core.app.NotificationCompat;
import androidx.core.content.ContextCompat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import pl.szczodrzynski.edziennik.receivers.BootReceiver;
import pl.szczodrzynski.edziennik.utils.models.Date;
import pl.szczodrzynski.edziennik.utils.models.Time;
import static androidx.core.app.NotificationCompat.PRIORITY_DEFAULT;
import static androidx.core.app.NotificationCompat.PRIORITY_MAX;
public class Notifier {
private static final String TAG = "Notifier";
public static final int ID_GET_DATA = 1337000;
public static final int ID_GET_DATA_ERROR = 1337001;
private static String CHANNEL_GET_DATA_NAME;
private static String CHANNEL_GET_DATA_DESC;
private static final String GROUP_KEY_GET_DATA = "pl.szczodrzynski.edziennik.GET_DATA";
public static final int ID_NOTIFICATIONS = 1337002;
public static String CHANNEL_NOTIFICATIONS_NAME;
public static String CHANNEL_NOTIFICATIONS_DESC;
public static final String GROUP_KEY_NOTIFICATIONS = "pl.szczodrzynski.edziennik.NOTIFICATIONS";
public static final int ID_NOTIFICATIONS_QUIET = 1337002;
public static String CHANNEL_NOTIFICATIONS_QUIET_NAME;
public static String CHANNEL_NOTIFICATIONS_QUIET_DESC;
public static final String GROUP_KEY_NOTIFICATIONS_QUIET = "pl.szczodrzynski.edziennik.NOTIFICATIONS_QUIET";
private static final int ID_UPDATES = 1337003;
private static String CHANNEL_UPDATES_NAME;
private static String CHANNEL_UPDATES_DESC;
private static final String GROUP_KEY_UPDATES = "pl.szczodrzynski.edziennik.UPDATES";
private App app;
public NotificationManager notificationManager;
private NotificationCompat.Builder getDataNotificationBuilder;
public int notificationColor;
Notifier(App _app) {
this.app = _app;
CHANNEL_GET_DATA_NAME = app.getString(R.string.notification_channel_get_data_name);
CHANNEL_GET_DATA_DESC = app.getString(R.string.notification_channel_get_data_desc);
CHANNEL_NOTIFICATIONS_NAME = app.getString(R.string.notification_channel_notifications_name);
CHANNEL_NOTIFICATIONS_DESC = app.getString(R.string.notification_channel_notifications_desc);
CHANNEL_NOTIFICATIONS_QUIET_NAME = app.getString(R.string.notification_channel_notifications_quiet_name);
CHANNEL_NOTIFICATIONS_QUIET_DESC = app.getString(R.string.notification_channel_notifications_quiet_desc);
CHANNEL_UPDATES_NAME = app.getString(R.string.notification_channel_updates_name);
CHANNEL_UPDATES_DESC = app.getString(R.string.notification_channel_updates_desc);
notificationColor = ContextCompat.getColor(app.getContext(), R.color.colorPrimary);
notificationManager = (NotificationManager) app.getSystemService(Context.NOTIFICATION_SERVICE);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
NotificationChannel channelGetData = new NotificationChannel(GROUP_KEY_GET_DATA, CHANNEL_GET_DATA_NAME, NotificationManager.IMPORTANCE_MIN);
channelGetData.setDescription(CHANNEL_GET_DATA_DESC);
notificationManager.createNotificationChannel(channelGetData);
NotificationChannel channelNotifications = new NotificationChannel(GROUP_KEY_NOTIFICATIONS, CHANNEL_NOTIFICATIONS_NAME, NotificationManager.IMPORTANCE_HIGH);
channelNotifications.setDescription(CHANNEL_NOTIFICATIONS_DESC);
channelNotifications.enableLights(true);
channelNotifications.setLightColor(notificationColor);
notificationManager.createNotificationChannel(channelNotifications);
NotificationChannel channelNotificationsQuiet = new NotificationChannel(GROUP_KEY_NOTIFICATIONS_QUIET, CHANNEL_NOTIFICATIONS_QUIET_NAME, NotificationManager.IMPORTANCE_DEFAULT);
channelNotificationsQuiet.setDescription(CHANNEL_NOTIFICATIONS_QUIET_DESC);
channelNotificationsQuiet.setSound(null, null);
channelNotificationsQuiet.enableVibration(false);
notificationManager.createNotificationChannel(channelNotificationsQuiet);
NotificationChannel channelUpdates = new NotificationChannel(GROUP_KEY_UPDATES, CHANNEL_UPDATES_NAME, NotificationManager.IMPORTANCE_HIGH);
channelUpdates.setDescription(CHANNEL_UPDATES_DESC);
notificationManager.createNotificationChannel(channelUpdates);
}
}
public boolean shouldBeQuiet() {
long now = Time.getNow().getInMillis();
long start = app.config.getSync().getQuietHoursStart();
long end = app.config.getSync().getQuietHoursEnd();
if (start > end) {
end += 1000 * 60 * 60 * 24;
//Log.d(TAG, "Night passing");
}
if (start > now) {
now += 1000 * 60 * 60 * 24;
//Log.d(TAG, "Now is smaller");
}
//Log.d(TAG, "Start is "+start+", now is "+now+", end is "+end);
return start > 0 && now >= start && now <= end;
}
public int getNotificationDefaults() {
return (shouldBeQuiet() ? 0 : Notification.DEFAULT_ALL);
}
public String getNotificationGroup() {
return shouldBeQuiet() ? GROUP_KEY_NOTIFICATIONS_QUIET : GROUP_KEY_NOTIFICATIONS;
}
public int getNotificationPriority() {
return shouldBeQuiet() ? PRIORITY_DEFAULT : PRIORITY_MAX;
}
/* _____ _ _____ _
| __ \ | | / ____| | |
| | | | __ _| |_ __ _ | | __ ___| |_
| | | |/ _` | __/ _` | | | |_ |/ _ \ __|
| |__| | (_| | || (_| | | |__| | __/ |_
|_____/ \__,_|\__\__,_| \_____|\___|\_*/
public Notification notificationGetDataShow(int maxProgress) {
/*Intent notificationIntent = new Intent(app.getContext(), SyncService.class);
notificationIntent.setAction(ACTION_CANCEL);
PendingIntent pendingIntent = PendingIntent.getService(app.getContext(), 0,
notificationIntent, PendingIntent.FLAG_CANCEL_CURRENT);*/
getDataNotificationBuilder = new NotificationCompat.Builder(app, GROUP_KEY_GET_DATA)
.setSmallIcon(android.R.drawable.stat_sys_download)
.setColor(notificationColor)
.setContentTitle(app.getString(R.string.notification_get_data_title))
.setContentText(app.getString(R.string.notification_get_data_text))
//.addAction(R.drawable.ic_notification, app.getString(R.string.notification_get_data_cancel), pendingIntent)
//.setGroup(GROUP_KEY_GET_DATA)
.setOngoing(true)
.setProgress(maxProgress, 0, false)
.setTicker(app.getString(R.string.notification_get_data_summary))
.setPriority(NotificationCompat.PRIORITY_LOW);
return getDataNotificationBuilder.build();
}
public Notification notificationGetDataProgress(int progress, int maxProgress) {
getDataNotificationBuilder.setProgress(maxProgress, progress, false);
return getDataNotificationBuilder.build();
}
public Notification notificationGetDataAction(int stringResId) {
getDataNotificationBuilder.setContentTitle(app.getString(R.string.sync_action_format, app.getString(stringResId)));
return getDataNotificationBuilder.build();
}
public Notification notificationGetDataProfile(String profileName) {
getDataNotificationBuilder.setContentText(profileName);
return getDataNotificationBuilder.build();
}
public Notification notificationGetDataError(String profileName, String error, int failedProfileId) {
Intent notificationIntent = new Intent(app.getContext(), Notifier.GetDataRetryService.class);
notificationIntent.putExtra("failedProfileId", failedProfileId);
PendingIntent pendingIntent = PendingIntent.getService(app.getContext(), 0,
notificationIntent, PendingIntent.FLAG_CANCEL_CURRENT);
getDataNotificationBuilder.mActions.clear();
/*try {
//Use reflection clean up old actions
Field f = getDataNotificationBuilder.getClass().getDeclaredField("mActions");
f.setAccessible(true);
f.set(getDataNotificationBuilder, new ArrayList<NotificationCompat.Action>());
} catch (Exception e) {
e.printStackTrace();
}*/
getDataNotificationBuilder.setProgress(0, 0, false)
.setTicker(app.getString(R.string.notification_get_data_error_summary))
.setSmallIcon(android.R.drawable.stat_sys_warning)
.addAction(R.drawable.ic_notification, app.getString(R.string.notification_get_data_once_again), pendingIntent)
.setContentTitle(app.getString(R.string.notification_get_data_error_title, profileName))
.setContentText(error)
.setStyle(new NotificationCompat.BigTextStyle().bigText(error))
.setOngoing(false);
return getDataNotificationBuilder.build();
}
public void notificationPost(int id, Notification notification) {
notificationManager.notify(id, notification);
}
public void notificationCancel(int id) {
notificationManager.cancel(id);
}
//public void notificationGetDataHide() {
// notificationManager.cancel(ID_GET_DATA);
// }
public static class GetDataRetryService extends IntentService {
private static final String TAG = "Notifier/GetDataRetry";
public GetDataRetryService() {
super(Notifier.GetDataRetryService.class.getSimpleName());
}
@Override
protected void onHandleIntent(Intent intent) {
}
}
/* _ _ _ _ __ _ _ _
| \ | | | | (_)/ _(_) | | (_)
| \| | ___ | |_ _| |_ _ ___ __ _| |_ _ ___ _ __
| . ` |/ _ \| __| | _| |/ __/ _` | __| |/ _ \| '_ \
| |\ | (_) | |_| | | | | (_| (_| | |_| | (_) | | | |
|_| \_|\___/ \__|_|_| |_|\___\__,_|\__|_|\___/|_| |*/
public void add(pl.szczodrzynski.edziennik.utils.models.Notification notification) {
app.appConfig.notifications.add(notification);
}
public void postAll() {
Collections.sort(app.appConfig.notifications, (o1, o2) -> (o2.addedDate - o1.addedDate > 0) ? 1 : (o2.addedDate - o1.addedDate < 0) ? -1 : 0);
if (app.appConfig.notifications.size() > 40) {
app.appConfig.notifications.subList(40, app.appConfig.notifications.size() - 1).clear();
}
int unreadCount = 0;
List<pl.szczodrzynski.edziennik.utils.models.Notification> notificationList = new ArrayList<>();
for (pl.szczodrzynski.edziennik.utils.models.Notification notification: app.appConfig.notifications) {
if (!notification.notified) {
notification.seen = false;
notification.notified = true;
unreadCount++;
if (notificationList.size() < 10) {
notificationList.add(notification);
}
}
else {
notification.seen = true;
}
}
for (pl.szczodrzynski.edziennik.utils.models.Notification notification: notificationList) {
Intent intent = new Intent(app, MainActivity.class);
notification.fillIntent(intent);
PendingIntent pendingIntent = PendingIntent.getActivity(app, notification.id, intent, 0);
NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(app, getNotificationGroup())
// title, text, type, date
.setContentTitle(notification.title)
.setContentText(notification.text)
.setSubText(pl.szczodrzynski.edziennik.utils.models.Notification.stringType(app, notification.type))
.setWhen(notification.addedDate)
.setTicker(app.getString(R.string.notification_ticker_format, pl.szczodrzynski.edziennik.utils.models.Notification.stringType(app, notification.type)))
// icon, color, lights, priority
.setSmallIcon(R.drawable.ic_notification)
.setColor(notificationColor)
.setLights(0xFF00FFFF, 2000, 2000)
.setPriority(getNotificationPriority())
// channel, group, style
.setChannelId(getNotificationGroup())
.setGroup(getNotificationGroup())
.setGroupAlertBehavior(NotificationCompat.GROUP_ALERT_SUMMARY)
.setStyle(new NotificationCompat.BigTextStyle().bigText(notification.text))
// intent, auto cancel
.setContentIntent(pendingIntent)
.setAutoCancel(true);
if (!shouldBeQuiet()) {
notificationBuilder.setDefaults(getNotificationDefaults());
}
notificationManager.notify(notification.id, notificationBuilder.build());
}
if (notificationList.size() > 0 && Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
Intent intent = new Intent(app, MainActivity.class);
intent.setAction("android.intent.action.MAIN");
intent.putExtra("fragmentId", MainActivity.DRAWER_ITEM_NOTIFICATIONS);
PendingIntent pendingIntent = PendingIntent.getActivity(app, ID_NOTIFICATIONS,
intent, 0);
NotificationCompat.Builder groupBuilder =
new NotificationCompat.Builder(app, getNotificationGroup())
.setSmallIcon(R.drawable.ic_notification)
.setColor(notificationColor)
.setContentTitle(app.getString(R.string.notification_new_notification_title_format, unreadCount))
.setGroupSummary(true)
.setAutoCancel(true)
.setChannelId(getNotificationGroup())
.setGroup(getNotificationGroup())
.setLights(0xFF00FFFF, 2000, 2000)
.setPriority(getNotificationPriority())
.setContentIntent(pendingIntent)
.setStyle(new NotificationCompat.BigTextStyle());
if (!shouldBeQuiet()) {
groupBuilder.setDefaults(getNotificationDefaults());
}
notificationManager.notify(ID_NOTIFICATIONS, groupBuilder.build());
}
}
/* _ _ _ _
| | | | | | | |
| | | |_ __ __| | __ _| |_ ___ ___
| | | | '_ \ / _` |/ _` | __/ _ \/ __|
| |__| | |_) | (_| | (_| | || __/\__ \
\____/| .__/ \__,_|\__,_|\__\___||___/
| |
|*/
public void notificationUpdatesShow(String updateVersion, String updateUrl, String updateFilename, boolean updateDirect) {
if (!app.config.getSync().getNotifyAboutUpdates())
return;
Intent notificationIntent = new Intent(app.getContext(), BootReceiver.NotificationActionService.class)
.putExtra("update_version", updateVersion)
.putExtra("update_url", updateUrl)
.putExtra("update_filename", updateFilename)
.putExtra("update_direct", updateDirect);
PendingIntent pendingIntent = PendingIntent.getService(app.getContext(), 0,
notificationIntent, PendingIntent.FLAG_CANCEL_CURRENT);
NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(app, GROUP_KEY_UPDATES)
.setSmallIcon(android.R.drawable.stat_sys_download_done)
.setColor(notificationColor)
.setContentTitle(app.getString(R.string.notification_updates_title))
.setContentText(app.getString(R.string.notification_updates_text, updateVersion))
.setLights(0xFF00FFFF, 2000, 2000)
.setContentIntent(pendingIntent)
.setTicker(app.getString(R.string.notification_updates_summary))
.setPriority(PRIORITY_MAX)
.setAutoCancel(true);
if (!shouldBeQuiet()) {
notificationBuilder.setDefaults(getNotificationDefaults());
}
notificationManager.notify(ID_UPDATES, notificationBuilder.build());
}
public void notificationUpdatesHide() {
if (!app.config.getSync().getNotifyAboutUpdates())
return;
notificationManager.cancel(ID_UPDATES);
}
public void dump() {
for (pl.szczodrzynski.edziennik.utils.models.Notification notification: app.appConfig.notifications) {
Log.d(TAG, "Profile"+notification.profileId+" Notification from "+ Date.fromMillis(notification.addedDate).getFormattedString()+" "+ Time.fromMillis(notification.addedDate).getStringHMS()+" - "+notification.text);
}
}
}

View File

@ -16,12 +16,13 @@ import pl.szczodrzynski.edziennik.config.utils.ConfigMigration
import pl.szczodrzynski.edziennik.config.utils.get
import pl.szczodrzynski.edziennik.config.utils.set
import pl.szczodrzynski.edziennik.config.utils.toHashMap
import pl.szczodrzynski.edziennik.data.api.szkolny.response.Update
import pl.szczodrzynski.edziennik.data.db.AppDb
import kotlin.coroutines.CoroutineContext
class Config(val db: AppDb) : CoroutineScope, AbstractConfig {
companion object {
const val DATA_VERSION = 2
const val DATA_VERSION = 10
}
private val job = Job()
@ -45,6 +46,20 @@ class Config(val db: AppDb) : CoroutineScope, AbstractConfig {
get() { mHash = mHash ?: values.get("hash", ""); return mHash ?: "" }
set(value) { set("hash", value); mHash = value }
private var mLastProfileId: Int? = null
var lastProfileId: Int
get() { mLastProfileId = mLastProfileId ?: values.get("lastProfileId", 0); return mLastProfileId ?: 0 }
set(value) { set("lastProfileId", value); mLastProfileId = value }
private var mUpdatesChannel: String? = null
var updatesChannel: String
get() { mUpdatesChannel = mUpdatesChannel ?: values.get("updatesChannel", "release"); return mUpdatesChannel ?: "release" }
set(value) { set("updatesChannel", value); mUpdatesChannel = value }
private var mUpdate: Update? = null
var update: Update?
get() { mUpdate = mUpdate ?: values.get("update", null as Update?); return mUpdate ?: null as Update? }
set(value) { set("update", value); mUpdate = value }
private var mAppVersion: Int? = null
var appVersion: Int
get() { mAppVersion = mAppVersion ?: values.get("appVersion", BuildConfig.VERSION_CODE); return mAppVersion ?: BuildConfig.VERSION_CODE }

View File

@ -17,6 +17,6 @@ class ConfigGrades(private val config: Config) {
private var mOrderBy: Int? = null
var orderBy: Int
get() { mOrderBy = mOrderBy ?: config.values.get("gradesOrderBy", 0); return mOrderBy ?: 0 }
get() { mOrderBy = mOrderBy ?: config.values.get("gradesOrderBy", 0); return mOrderBy ?: ORDER_BY_DATE_DESC }
set(value) { config.set("gradesOrderBy", value); mOrderBy = value }
}

View File

@ -9,6 +9,11 @@ import pl.szczodrzynski.edziennik.config.utils.getIntList
import pl.szczodrzynski.edziennik.config.utils.set
class ConfigSync(private val config: Config) {
private var mDontShowAppManagerDialog: Boolean? = null
var dontShowAppManagerDialog: Boolean
get() { mDontShowAppManagerDialog = mDontShowAppManagerDialog ?: config.values.get("dontShowAppManagerDialog", false); return mDontShowAppManagerDialog ?: false }
set(value) { config.set("dontShowAppManagerDialog", value); mDontShowAppManagerDialog = value }
private var mSyncEnabled: Boolean? = null
var enabled: Boolean
get() { mSyncEnabled = mSyncEnabled ?: config.values.get("syncEnabled", true); return mSyncEnabled ?: true }

View File

@ -45,11 +45,6 @@ class ConfigUI(private val config: Config) {
get() { mOpenDrawerOnBackPressed = mOpenDrawerOnBackPressed ?: config.values.get("openDrawerOnBackPressed", false); return mOpenDrawerOnBackPressed ?: false }
set(value) { config.set("openDrawerOnBackPressed", value); mOpenDrawerOnBackPressed = value }
private var mAgendaViewType: Int? = null
var agendaViewType: Int
get() { mAgendaViewType = mAgendaViewType ?: config.values.get("agendaViewType", 0); return mAgendaViewType ?: 0 }
set(value) { config.set("agendaViewType", value); mAgendaViewType = value }
private var mHomeCards: List<HomeCardModel>? = null
var homeCards: List<HomeCardModel>
get() { mHomeCards = mHomeCards ?: config.values.get("homeCards", listOf(), HomeCardModel::class.java); return mHomeCards ?: listOf() }

View File

@ -9,6 +9,7 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
import pl.szczodrzynski.edziennik.config.db.ConfigEntry
import pl.szczodrzynski.edziennik.config.utils.ProfileConfigMigration
import pl.szczodrzynski.edziennik.config.utils.get
import pl.szczodrzynski.edziennik.config.utils.set
import pl.szczodrzynski.edziennik.config.utils.toHashMap
@ -27,6 +28,7 @@ class ProfileConfig(val db: AppDb, val profileId: Int, rawEntries: List<ConfigEn
val values: HashMap<String, String?> = hashMapOf()
val grades by lazy { ProfileConfigGrades(this) }
val ui by lazy { ProfileConfigUI(this) }
/*
val sync by lazy { ConfigSync(this) }
val timetable by lazy { ConfigTimetable(this) }
@ -44,8 +46,8 @@ class ProfileConfig(val db: AppDb, val profileId: Int, rawEntries: List<ConfigEn
init {
rawEntries.toHashMap(profileId, values)
/*if (dataVersion < DATA_VERSION)
ProfileConfigMigration(this)*/
if (dataVersion < DATA_VERSION)
ProfileConfigMigration(this)
}
override fun set(key: String, value: String?) {

View File

@ -0,0 +1,16 @@
/*
* Copyright (c) Kuba Szczodrzyński 2020-1-19.
*/
package pl.szczodrzynski.edziennik.config
import pl.szczodrzynski.edziennik.config.utils.get
import pl.szczodrzynski.edziennik.config.utils.set
import pl.szczodrzynski.edziennik.data.db.entity.Profile.Companion.AGENDA_DEFAULT
class ProfileConfigUI(private val config: ProfileConfig) {
private var mAgendaViewType: Int? = null
var agendaViewType: Int
get() { mAgendaViewType = mAgendaViewType ?: config.values.get("agendaViewType", 0); return mAgendaViewType ?: AGENDA_DEFAULT }
set(value) { config.set("agendaViewType", value); mAgendaViewType = value }
}

View File

@ -22,4 +22,7 @@ interface ConfigDao {
@Query("SELECT * FROM config WHERE profileId = :profileId")
fun getAllNow(profileId: Int): List<ConfigEntry>
@Query("DELETE FROM config WHERE profileId = :profileId")
fun clear(profileId: Int)
}

View File

@ -0,0 +1,81 @@
/*
* Copyright (c) Kuba Szczodrzyński 2020-1-19.
*/
package pl.szczodrzynski.edziennik.config.utils
import android.content.SharedPreferences
import com.google.gson.Gson
import com.google.gson.reflect.TypeToken
import pl.szczodrzynski.edziennik.BuildConfig
import pl.szczodrzynski.edziennik.MainActivity
import pl.szczodrzynski.edziennik.config.Config
import pl.szczodrzynski.edziennik.data.api.LOGIN_TYPE_LIBRUS
import pl.szczodrzynski.edziennik.data.api.LOGIN_TYPE_MOBIDZIENNIK
import pl.szczodrzynski.edziennik.data.api.LOGIN_TYPE_VULCAN
import pl.szczodrzynski.edziennik.utils.models.Time
class AppConfigMigrationV3(p: SharedPreferences, config: Config) {
init { config.apply {
val s = "app.appConfig"
if (dataVersion < 1) {
ui.theme = p.getString("$s.appTheme", null)?.toIntOrNull() ?: 1
sync.enabled = p.getString("$s.registerSyncEnabled", null)?.toBoolean() ?: true
sync.interval = p.getString("$s.registerSyncInterval", null)?.toIntOrNull() ?: 3600
val oldButtons = p.getString("$s.miniDrawerButtonIds", null)?.let { str ->
str.replace("[\\[\\]]*".toRegex(), "")
.split(",\\s?".toRegex())
.mapNotNull { it.toIntOrNull() }
}
ui.miniMenuButtons = oldButtons ?: listOf(
MainActivity.DRAWER_ITEM_HOME,
MainActivity.DRAWER_ITEM_TIMETABLE,
MainActivity.DRAWER_ITEM_AGENDA,
MainActivity.DRAWER_ITEM_GRADES,
MainActivity.DRAWER_ITEM_MESSAGES,
MainActivity.DRAWER_ITEM_HOMEWORK,
MainActivity.DRAWER_ITEM_SETTINGS
)
dataVersion = 1
}
if (dataVersion < 2) {
devModePassword = p.getString("$s.devModePassword", null).fix()
sync.tokenApp = p.getString("$s.fcmToken", null).fix()
timetable.bellSyncMultiplier = p.getString("$s.bellSyncMultiplier", null)?.toIntOrNull() ?: 0
sync.quietHoursStart = p.getString("$s.quietHoursStart", null)?.toLongOrNull() ?: 0
appRateSnackbarTime = p.getString("$s.appRateSnackbarTime", null)?.toLongOrNull() ?: 0
sync.quietHoursEnd = p.getString("$s.quietHoursEnd", null)?.toLongOrNull() ?: 0
timetable.countInSeconds = p.getString("$s.countInSeconds", null)?.toBoolean() ?: false
ui.headerBackground = p.getString("$s.headerBackground", null).fix()
ui.appBackground = p.getString("$s.appBackground", null).fix()
ui.language = p.getString("$s.language", null).fix()
appVersion = p.getString("$s.lastAppVersion", null)?.toIntOrNull() ?: BuildConfig.VERSION_CODE
appInstalledTime = p.getString("$s.appInstalledTime", null)?.toLongOrNull() ?: 0
grades.orderBy = p.getString("$s.gradesOrderBy", null)?.toIntOrNull() ?: 0
sync.quietDuringLessons = p.getString("$s.quietDuringLessons", null)?.toBoolean() ?: false
ui.miniMenuVisible = p.getString("$s.miniDrawerVisible", null)?.toBoolean() ?: false
loginFinished = p.getString("$s.loginFinished", null)?.toBoolean() ?: false
sync.onlyWifi = p.getString("$s.registerSyncOnlyWifi", null)?.toBoolean() ?: false
sync.notifyAboutUpdates = p.getString("$s.notifyAboutUpdates", null)?.toBoolean() ?: true
timetable.bellSyncDiff = p.getString("$s.bellSyncDiff", null)?.let { Gson().fromJson(it, Time::class.java) }
sync.tokenMobidziennikList = listOf()
sync.tokenVulcanList = listOf()
sync.tokenLibrusList = listOf()
val tokens = p.getString("$s.fcmTokens", null)?.let { Gson().fromJson<Map<Int, Pair<String, List<Int>>>>(it, object: TypeToken<Map<Int, Pair<String, List<Int>>>>(){}.type) }
tokens?.forEach {
val token = it.value.first
when (it.key) {
LOGIN_TYPE_MOBIDZIENNIK -> sync.tokenMobidziennik = token
LOGIN_TYPE_VULCAN -> sync.tokenVulcan = token
LOGIN_TYPE_LIBRUS -> sync.tokenLibrus = token
}
}
dataVersion = 2
}
}}
private fun String?.fix(): String? {
return this?.replace("\"", "")?.let { if (it == "null") null else it }
}
}

View File

@ -37,6 +37,9 @@ fun AbstractConfig.set(key: String, value: JsonElement?) {
fun AbstractConfig.set(key: String, value: List<Any>?) {
set(key, value?.let { gson.toJson(it) })
}
fun AbstractConfig.set(key: String, value: Any?) {
set(key, value?.let { gson.toJson(it) })
}
fun AbstractConfig.setStringList(key: String, value: List<String>?) {
set(key, value?.let { gson.toJson(it) })
}
@ -74,6 +77,9 @@ fun HashMap<String, String?>.get(key: String, default: JsonObject?): JsonObject?
fun HashMap<String, String?>.get(key: String, default: JsonArray?): JsonArray? {
return this[key]?.let { JsonParser().parse(it)?.asJsonArray } ?: default
}
inline fun <reified T> HashMap<String, String?>.get(key: String, default: T?): T? {
return this[key]?.let { Gson().fromJson(it, T::class.java) } ?: default
}
/* !!! cannot use mutable list here - modifying it will not update the DB */
fun <T> HashMap<String, String?>.get(key: String, default: List<T>?, classOfT: Class<T>): List<T>? {
return this[key]?.let { ConfigGsonUtils().deserializeList<T>(gson, it, classOfT) } ?: default

View File

@ -5,32 +5,32 @@
package pl.szczodrzynski.edziennik.config.utils
import android.content.Context
import com.google.gson.Gson
import com.google.gson.reflect.TypeToken
import pl.szczodrzynski.edziennik.App
import pl.szczodrzynski.edziennik.BuildConfig
import pl.szczodrzynski.edziennik.HOUR
import pl.szczodrzynski.edziennik.MainActivity
import pl.szczodrzynski.edziennik.data.api.LOGIN_TYPE_LIBRUS
import pl.szczodrzynski.edziennik.data.api.LOGIN_TYPE_MOBIDZIENNIK
import pl.szczodrzynski.edziennik.data.api.LOGIN_TYPE_VULCAN
import pl.szczodrzynski.edziennik.config.Config
import pl.szczodrzynski.edziennik.utils.models.Time
import pl.szczodrzynski.edziennik.config.ConfigGrades.Companion.ORDER_BY_DATE_DESC
class ConfigMigration(app: App, config: Config) {
init { config.apply {
val p = app.getSharedPreferences("pl.szczodrzynski.edziennik_profiles", Context.MODE_PRIVATE)
val s = "app.appConfig"
if (dataVersion < 1) {
ui.theme = p.getString("$s.appTheme", null)?.toIntOrNull() ?: 1
sync.enabled = p.getString("$s.registerSyncEnabled", null)?.toBoolean() ?: true
sync.interval = p.getString("$s.registerSyncEnabled", null)?.toIntOrNull() ?: 3600
val oldButtons = p.getString("$s.miniDrawerButtonIds", null)?.let { str ->
str.replace("[\\[\\]]*".toRegex(), "")
.split(",\\s?".toRegex())
.mapNotNull { it.toIntOrNull() }
}
ui.miniMenuButtons = oldButtons ?: listOf(
val p = app.getSharedPreferences("pl.szczodrzynski.edziennik_profiles", Context.MODE_PRIVATE)
if (p.contains("app.appConfig.appTheme")) {
// migrate appConfig from app version 3.x and lower.
// Updates dataVersion to level 2.
AppConfigMigrationV3(p, config)
}
if (dataVersion < 2) {
appVersion = BuildConfig.VERSION_CODE
loginFinished = false
ui.language = null
ui.theme = 1
ui.appBackground = null
ui.headerBackground = null
ui.miniMenuVisible = false
ui.miniMenuButtons = listOf(
MainActivity.DRAWER_ITEM_HOME,
MainActivity.DRAWER_ITEM_TIMETABLE,
MainActivity.DRAWER_ITEM_AGENDA,
@ -39,46 +39,36 @@ class ConfigMigration(app: App, config: Config) {
MainActivity.DRAWER_ITEM_HOMEWORK,
MainActivity.DRAWER_ITEM_SETTINGS
)
dataVersion = 1
}
if (dataVersion < 2) {
devModePassword = p.getString("$s.devModePassword", null).fix()
sync.tokenApp = p.getString("$s.fcmToken", null).fix()
timetable.bellSyncMultiplier = p.getString("$s.bellSyncMultiplier", null)?.toIntOrNull() ?: 0
sync.quietHoursStart = p.getString("$s.quietHoursStart", null)?.toLongOrNull() ?: 0
appRateSnackbarTime = p.getString("$s.appRateSnackbarTime", null)?.toLongOrNull() ?: 0
sync.quietHoursEnd = p.getString("$s.quietHoursEnd", null)?.toLongOrNull() ?: 0
timetable.countInSeconds = p.getString("$s.countInSeconds", null)?.toBoolean() ?: false
ui.headerBackground = p.getString("$s.headerBackground", null).fix()
ui.appBackground = p.getString("$s.appBackground", null).fix()
ui.language = p.getString("$s.language", null).fix()
appVersion = p.getString("$s.lastAppVersion", null)?.toIntOrNull() ?: BuildConfig.VERSION_CODE
appInstalledTime = p.getString("$s.appInstalledTime", null)?.toLongOrNull() ?: 0
grades.orderBy = p.getString("$s.gradesOrderBy", null)?.toIntOrNull() ?: 0
sync.quietDuringLessons = p.getString("$s.quietDuringLessons", null)?.toBoolean() ?: false
ui.miniMenuVisible = p.getString("$s.miniDrawerVisible", null)?.toBoolean() ?: false
loginFinished = p.getString("$s.loginFinished", null)?.toBoolean() ?: false
sync.onlyWifi = p.getString("$s.registerSyncOnlyWifi", null)?.toBoolean() ?: false
sync.notifyAboutUpdates = p.getString("$s.notifyAboutUpdates", null)?.toBoolean() ?: true
timetable.bellSyncDiff = p.getString("$s.bellSyncDiff", null)?.let { Gson().fromJson(it, Time::class.java) }
sync.enabled = true
sync.interval = 1*HOUR.toInt()
sync.notifyAboutUpdates = true
sync.onlyWifi = false
sync.quietHoursStart = 0
sync.quietHoursEnd = 0
sync.quietDuringLessons = false
sync.tokenApp = null
sync.tokenMobidziennik = null
sync.tokenMobidziennikList = listOf()
sync.tokenVulcanList = listOf()
sync.tokenLibrus = null
sync.tokenLibrusList = listOf()
val tokens = p.getString("$s.fcmTokens", null)?.let { Gson().fromJson<Map<Int, Pair<String, List<Int>>>>(it, object: TypeToken<Map<Int, Pair<String, List<Int>>>>(){}.type) }
tokens?.forEach {
val token = it.value.first
when (it.key) {
LOGIN_TYPE_MOBIDZIENNIK -> sync.tokenMobidziennik = token
LOGIN_TYPE_VULCAN -> sync.tokenVulcan = token
LOGIN_TYPE_LIBRUS -> sync.tokenLibrus = token
}
}
sync.tokenVulcan = null
sync.tokenVulcanList = listOf()
timetable.bellSyncMultiplier = 0
timetable.bellSyncDiff = null
timetable.countInSeconds = false
grades.orderBy = ORDER_BY_DATE_DESC
dataVersion = 2
}
}}
private fun String?.fix(): String? {
return this?.replace("\"", "")?.let { if (it == "null") null else it }
}
if (dataVersion < 10) {
ui.openDrawerOnBackPressed = false
ui.homeCards = listOf()
ui.snowfall = false
ui.bottomSheetOpened = false
sync.dontShowAppManagerDialog = false
dataVersion = 10
}
}}
}

View File

@ -4,23 +4,21 @@
package pl.szczodrzynski.edziennik.config.utils
import android.content.Context
import pl.szczodrzynski.edziennik.App
import pl.szczodrzynski.edziennik.config.Config
import pl.szczodrzynski.edziennik.config.ProfileConfig
import pl.szczodrzynski.edziennik.data.db.entity.Profile.Companion.AGENDA_DEFAULT
import pl.szczodrzynski.edziennik.data.db.entity.Profile.Companion.COLOR_MODE_WEIGHTED
import pl.szczodrzynski.edziennik.data.db.entity.Profile.Companion.YEAR_ALL_GRADES
class ProfileConfigMigration(app: App, config: Config) {
class ProfileConfigMigration(config: ProfileConfig) {
init { config.apply {
val p = app.getSharedPreferences("pl.szczodrzynski.edziennik_profiles", Context.MODE_PRIVATE)
val s = "app.appConfig"
if (dataVersion < 1) {
grades.colorMode = COLOR_MODE_WEIGHTED
grades.countZeroToAvg = true
grades.yearAverageMode = YEAR_ALL_GRADES
ui.agendaViewType = AGENDA_DEFAULT
//dataVersion = 1
}
if (dataVersion < 2) {
//gradesColorMode do profilu !
//agendaViewType do profilu !
// app.appConfig.dontCountZeroToAverage do profilu !
dataVersion = 1
}
}}
}

View File

@ -12,12 +12,15 @@ import org.greenrobot.eventbus.EventBus
import org.greenrobot.eventbus.Subscribe
import org.greenrobot.eventbus.ThreadMode
import pl.szczodrzynski.edziennik.App
import pl.szczodrzynski.edziennik.data.api.edziennik.EdziennikTask
import pl.szczodrzynski.edziennik.data.api.events.*
import pl.szczodrzynski.edziennik.data.api.events.requests.ServiceCloseRequest
import pl.szczodrzynski.edziennik.data.api.events.requests.TaskCancelRequest
import pl.szczodrzynski.edziennik.data.api.interfaces.EdziennikCallback
import pl.szczodrzynski.edziennik.data.api.models.ApiError
import pl.szczodrzynski.edziennik.data.api.task.*
import pl.szczodrzynski.edziennik.data.api.task.ErrorReportTask
import pl.szczodrzynski.edziennik.data.api.task.IApiTask
import pl.szczodrzynski.edziennik.data.api.task.SzkolnyTask
import pl.szczodrzynski.edziennik.data.db.entity.Profile
import pl.szczodrzynski.edziennik.utils.Utils.d
import kotlin.math.min
@ -40,11 +43,8 @@ class ApiService : Service() {
private val syncingProfiles = mutableListOf<Profile>()
private val finishingTaskQueue = mutableListOf(
SzkolnyTask.sync(syncingProfiles),
NotifyTask()
)
private val allTaskList = mutableListOf<IApiTask>()
private var szkolnyTaskFinished = false
private val allTaskRequestList = mutableListOf<Any>()
private val taskQueue = mutableListOf<IApiTask>()
private val errorList = mutableListOf<ApiError>()
@ -132,14 +132,20 @@ class ApiService : Service() {
checkIfTaskFrozen()
if (taskIsRunning)
return
if (taskCancelled || serviceClosed || (taskQueue.isEmpty() && finishingTaskQueue.isEmpty())) {
if (taskCancelled || serviceClosed || (taskQueue.isEmpty() && szkolnyTaskFinished)) {
allCompleted()
return
}
lastEventTime = System.currentTimeMillis()
val task = if (taskQueue.isEmpty()) finishingTaskQueue.removeAt(0) else taskQueue.removeAt(0)
val task = if (taskQueue.isNotEmpty()) {
taskQueue.removeAt(0)
} else {
szkolnyTaskFinished = true
SzkolnyTask(app, syncingProfiles)
}
task.taskId = ++taskMaximumId
task.prepare(app)
taskIsRunning = true
@ -162,9 +168,8 @@ class ApiService : Service() {
try {
when (task) {
is EdziennikTask -> task.run(app, taskCallback)
is NotifyTask -> task.run(app, taskCallback)
is ErrorReportTask -> task.run(app, taskCallback, notification, errorList)
is SzkolnyTask -> task.run(app, taskCallback)
is SzkolnyTask -> task.run(taskCallback)
}
} catch (e: Exception) {
taskCallback.onError(ApiError(TAG, EXCEPTION_API_TASK).withThrowable(e))
@ -230,10 +235,12 @@ class ApiService : Service() {
EventBus.getDefault().removeStickyEvent(task)
d(TAG, task.toString())
// fix for duplicated tasks, thank you EventBus
if (task in allTaskList)
return
allTaskList += task
if (task is EdziennikTask) {
// fix for duplicated tasks, thank you EventBus
if (task.request in allTaskRequestList)
return
allTaskRequestList += task.request
}
if (task is EdziennikTask) {
when (task.request) {

View File

@ -1,206 +0,0 @@
package pl.szczodrzynski.edziennik.data.api
import pl.szczodrzynski.edziennik.MainActivity.Companion.DRAWER_ITEM_AGENDA
import pl.szczodrzynski.edziennik.MainActivity.Companion.DRAWER_ITEM_ANNOUNCEMENTS
import pl.szczodrzynski.edziennik.MainActivity.Companion.DRAWER_ITEM_ATTENDANCE
import pl.szczodrzynski.edziennik.MainActivity.Companion.DRAWER_ITEM_BEHAVIOUR
import pl.szczodrzynski.edziennik.MainActivity.Companion.DRAWER_ITEM_GRADES
import pl.szczodrzynski.edziennik.MainActivity.Companion.DRAWER_ITEM_HOME
import pl.szczodrzynski.edziennik.MainActivity.Companion.DRAWER_ITEM_HOMEWORK
import pl.szczodrzynski.edziennik.MainActivity.Companion.DRAWER_ITEM_MESSAGES
import pl.szczodrzynski.edziennik.MainActivity.Companion.DRAWER_ITEM_TIMETABLE
import pl.szczodrzynski.edziennik.R
import pl.szczodrzynski.edziennik.data.api.models.Data
import pl.szczodrzynski.edziennik.data.db.entity.*
import pl.szczodrzynski.edziennik.data.db.entity.Grade.*
import pl.szczodrzynski.edziennik.data.db.entity.Notification.Companion.TYPE_LUCKY_NUMBER
import pl.szczodrzynski.edziennik.data.db.entity.Notification.Companion.TYPE_NEW_ANNOUNCEMENT
import pl.szczodrzynski.edziennik.data.db.entity.Notification.Companion.TYPE_NEW_ATTENDANCE
import pl.szczodrzynski.edziennik.data.db.entity.Notification.Companion.TYPE_NEW_EVENT
import pl.szczodrzynski.edziennik.data.db.entity.Notification.Companion.TYPE_NEW_GRADE
import pl.szczodrzynski.edziennik.data.db.entity.Notification.Companion.TYPE_NEW_HOMEWORK
import pl.szczodrzynski.edziennik.data.db.entity.Notification.Companion.TYPE_NEW_MESSAGE
import pl.szczodrzynski.edziennik.data.db.entity.Notification.Companion.TYPE_NEW_NOTICE
import pl.szczodrzynski.edziennik.data.db.entity.Notification.Companion.TYPE_TIMETABLE_LESSON_CHANGE
import pl.szczodrzynski.edziennik.getNotificationTitle
import pl.szczodrzynski.edziennik.utils.models.Date
class DataNotifications(val data: Data) {
companion object {
private const val TAG = "DataNotifications"
}
val app = data.app
val profileId = data.profile?.id ?: -1
val profileName = data.profile?.name ?: ""
val profile = data.profile
val loginStore = data.loginStore
init { run {
if (profile == null) {
return@run
}
val today = Date.getToday()
val todayValue = today.value
for (lesson in app.db.timetableDao().getNotNotifiedNow(profileId)) {
val text = app.getString(R.string.notification_lesson_change_format, lesson.getDisplayChangeType(app), if (lesson.displayDate == null) "" else lesson.displayDate!!.formattedString, lesson.changeSubjectName)
data.notifications += Notification(
title = app.getNotificationTitle(TYPE_TIMETABLE_LESSON_CHANGE),
text = text,
type = TYPE_TIMETABLE_LESSON_CHANGE,
profileId = profileId,
profileName = profileName,
viewId = DRAWER_ITEM_TIMETABLE,
addedDate = lesson.addedDate
).addExtra("timetableDate", lesson.displayDate?.stringY_m_d ?: "")
}
for (event in app.db.eventDao().getNotNotifiedNow(profileId)) {
val text = if (event.type == Event.TYPE_HOMEWORK)
app.getString(
if (event.subjectLongName.isNullOrEmpty())
R.string.notification_homework_no_subject_format
else
R.string.notification_homework_format,
event.subjectLongName,
event.eventDate.formattedString
)
else
app.getString(
if (event.subjectLongName.isNullOrEmpty())
R.string.notification_event_no_subject_format
else
R.string.notification_event_format,
event.typeName,
event.eventDate.formattedString,
event.subjectLongName
)
val type = if (event.type == Event.TYPE_HOMEWORK) TYPE_NEW_HOMEWORK else TYPE_NEW_EVENT
data.notifications += Notification(
title = app.getNotificationTitle(type),
text = text,
type = type,
profileId = profileId,
profileName = profileName,
viewId = if (event.type == Event.TYPE_HOMEWORK) DRAWER_ITEM_HOMEWORK else DRAWER_ITEM_AGENDA,
addedDate = event.addedDate
).addExtra("eventId", event.id).addExtra("eventDate", event.eventDate.value.toLong())
}
for (grade in app.db.gradeDao().getNotNotifiedNow(profileId)) {
val gradeName = when (grade.type) {
TYPE_SEMESTER1_PROPOSED, TYPE_SEMESTER2_PROPOSED -> app.getString(R.string.grade_semester_proposed_format_2, grade.name)
TYPE_SEMESTER1_FINAL, TYPE_SEMESTER2_FINAL -> app.getString(R.string.grade_semester_final_format_2, grade.name)
TYPE_YEAR_PROPOSED -> app.getString(R.string.grade_year_proposed_format_2, grade.name)
TYPE_YEAR_FINAL -> app.getString(R.string.grade_year_final_format_2, grade.name)
else -> grade.name
}
val text = app.getString(R.string.notification_grade_format, gradeName, grade.subjectLongName)
data.notifications += Notification(
title = app.getNotificationTitle(TYPE_NEW_GRADE),
text = text,
type = TYPE_NEW_GRADE,
profileId = profileId,
profileName = profileName,
viewId = DRAWER_ITEM_GRADES,
addedDate = grade.addedDate
).addExtra("gradeId", grade.id).addExtra("gradesSubjectId", grade.subjectId)
}
for (notice in app.db.noticeDao().getNotNotifiedNow(profileId)) {
val noticeTypeStr = if (notice.type == Notice.TYPE_POSITIVE) app.getString(R.string.notification_notice_praise) else if (notice.type == Notice.TYPE_NEGATIVE) app.getString(R.string.notification_notice_warning) else app.getString(R.string.notification_notice_new)
val text = app.getString(R.string.notification_notice_format, noticeTypeStr, notice.teacherFullName, Date.fromMillis(notice.addedDate).formattedString)
data.notifications += Notification(
title = app.getNotificationTitle(TYPE_NEW_NOTICE),
text = text,
type = TYPE_NEW_NOTICE,
profileId = profileId,
profileName = profileName,
viewId = DRAWER_ITEM_BEHAVIOUR,
addedDate = notice.addedDate
).addExtra("noticeId", notice.id)
}
for (attendance in app.db.attendanceDao().getNotNotifiedNow(profileId)) {
val attendanceTypeStr = when (attendance.type) {
Attendance.TYPE_ABSENT -> app.getString(R.string.notification_absence)
Attendance.TYPE_ABSENT_EXCUSED -> app.getString(R.string.notification_absence_excused)
Attendance.TYPE_BELATED -> app.getString(R.string.notification_belated)
Attendance.TYPE_BELATED_EXCUSED -> app.getString(R.string.notification_belated_excused)
Attendance.TYPE_RELEASED -> app.getString(R.string.notification_release)
Attendance.TYPE_DAY_FREE -> app.getString(R.string.notification_day_free)
else -> app.getString(R.string.notification_type_attendance)
}
val text = app.getString(
if (attendance.subjectLongName.isNullOrEmpty())
R.string.notification_attendance_no_lesson_format
else
R.string.notification_attendance_format,
attendanceTypeStr,
attendance.subjectLongName,
attendance.lessonDate.formattedString
)
data.notifications += Notification(
title = app.getNotificationTitle(TYPE_NEW_ATTENDANCE),
text = text,
type = TYPE_NEW_ATTENDANCE,
profileId = profileId,
profileName = profileName,
viewId = DRAWER_ITEM_ATTENDANCE,
addedDate = attendance.addedDate
).addExtra("attendanceId", attendance.id).addExtra("attendanceSubjectId", attendance.subjectId)
}
for (announcement in app.db.announcementDao().getNotNotifiedNow(profileId)) {
val text = app.context.getString(R.string.notification_announcement_format, announcement.subject)
data.notifications += Notification(
title = app.getNotificationTitle(TYPE_NEW_ANNOUNCEMENT),
text = text,
type = TYPE_NEW_ANNOUNCEMENT,
profileId = profileId,
profileName = profileName,
viewId = DRAWER_ITEM_ANNOUNCEMENTS,
addedDate = announcement.addedDate
).addExtra("announcementId", announcement.id)
}
for (message in app.db.messageDao().getReceivedNotNotifiedNow(profileId)) {
val text = app.context.getString(R.string.notification_message_format, message.senderFullName, message.subject)
data.notifications += Notification(
title = app.getNotificationTitle(TYPE_NEW_MESSAGE),
text = text,
type = TYPE_NEW_MESSAGE,
profileId = profileId,
profileName = profileName,
viewId = DRAWER_ITEM_MESSAGES,
addedDate = message.addedDate
).addExtra("messageType", Message.TYPE_RECEIVED.toLong()).addExtra("messageId", message.id)
}
val luckyNumbers = app.db.luckyNumberDao().getNotNotifiedNow(profileId)
luckyNumbers?.removeAll { it.date < today }
luckyNumbers?.forEach { luckyNumber ->
val text = when (luckyNumber.date.value) {
todayValue -> // LN for today
app.getString(if (profile.studentNumber != -1 && profile.studentNumber == luckyNumber.number) R.string.notification_lucky_number_yours_format else R.string.notification_lucky_number_format, luckyNumber.number)
todayValue + 1 -> // LN for tomorrow
app.getString(if (profile.studentNumber != -1 && profile.studentNumber == luckyNumber.number) R.string.notification_lucky_number_yours_tomorrow_format else R.string.notification_lucky_number_tomorrow_format, luckyNumber.number)
else -> // LN for later
app.getString(if (profile.studentNumber != -1 && profile.studentNumber == luckyNumber.number) R.string.notification_lucky_number_yours_later_format else R.string.notification_lucky_number_later_format, luckyNumber.date.formattedString, luckyNumber.number)
}
data.notifications += Notification(
title = app.getNotificationTitle(TYPE_LUCKY_NUMBER),
text = text,
type = TYPE_LUCKY_NUMBER,
profileId = profileId,
profileName = profileName,
viewId = DRAWER_ITEM_HOME,
addedDate = luckyNumber.addedDate
)
}
data.db.metadataDao().setAllNotified(profileId, true)
}}
}

View File

@ -1,4 +1,8 @@
package pl.szczodrzynski.edziennik.data.api.task
/*
* Copyright (c) Kuba Szczodrzyński 2020-1-16.
*/
package pl.szczodrzynski.edziennik.data.api.edziennik
import com.google.gson.JsonObject
import pl.szczodrzynski.edziennik.App
@ -12,11 +16,12 @@ import pl.szczodrzynski.edziennik.data.api.edziennik.template.Template
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.Vulcan
import pl.szczodrzynski.edziennik.data.api.interfaces.EdziennikCallback
import pl.szczodrzynski.edziennik.data.api.interfaces.EdziennikInterface
import pl.szczodrzynski.edziennik.data.db.full.AnnouncementFull
import pl.szczodrzynski.edziennik.data.api.task.IApiTask
import pl.szczodrzynski.edziennik.data.db.entity.LoginStore
import pl.szczodrzynski.edziennik.data.db.entity.Message
import pl.szczodrzynski.edziennik.data.db.full.MessageFull
import pl.szczodrzynski.edziennik.data.db.entity.Teacher
import pl.szczodrzynski.edziennik.data.db.full.AnnouncementFull
import pl.szczodrzynski.edziennik.data.db.full.MessageFull
open class EdziennikTask(override val profileId: Int, val request: Any) : IApiTask(profileId) {
companion object {

View File

@ -15,12 +15,12 @@ import pl.szczodrzynski.edziennik.data.api.edziennik.edudziennik.login.Edudzienn
import pl.szczodrzynski.edziennik.data.api.interfaces.EdziennikCallback
import pl.szczodrzynski.edziennik.data.api.interfaces.EdziennikInterface
import pl.szczodrzynski.edziennik.data.api.models.ApiError
import pl.szczodrzynski.edziennik.data.db.full.AnnouncementFull
import pl.szczodrzynski.edziennik.data.db.entity.LoginStore
import pl.szczodrzynski.edziennik.data.db.entity.Message
import pl.szczodrzynski.edziennik.data.db.full.MessageFull
import pl.szczodrzynski.edziennik.data.db.entity.Profile
import pl.szczodrzynski.edziennik.data.db.entity.Teacher
import pl.szczodrzynski.edziennik.data.db.full.AnnouncementFull
import pl.szczodrzynski.edziennik.data.db.full.MessageFull
import pl.szczodrzynski.edziennik.utils.Utils.d
class Edudziennik(val app: App, val profile: Profile?, val loginStore: LoginStore, val callback: EdziennikCallback) : EdziennikInterface {
@ -41,9 +41,7 @@ class Edudziennik(val app: App, val profile: Profile?, val loginStore: LoginStor
private fun completed() {
data.saveData()
data.notify {
callback.onCompleted()
}
callback.onCompleted()
}
/* _______ _ _ _ _ _

View File

@ -13,9 +13,9 @@ import pl.szczodrzynski.edziennik.data.api.edziennik.edudziennik.DataEdudziennik
import pl.szczodrzynski.edziennik.data.api.edziennik.edudziennik.ENDPOINT_EDUDZIENNIK_WEB_EXAMS
import pl.szczodrzynski.edziennik.data.api.edziennik.edudziennik.data.EdudziennikWeb
import pl.szczodrzynski.edziennik.data.api.models.DataRemoveModel
import pl.szczodrzynski.edziennik.data.db.entity.SYNC_ALWAYS
import pl.szczodrzynski.edziennik.data.db.entity.Event
import pl.szczodrzynski.edziennik.data.db.entity.Metadata
import pl.szczodrzynski.edziennik.data.db.entity.SYNC_ALWAYS
import pl.szczodrzynski.edziennik.get
import pl.szczodrzynski.edziennik.utils.models.Date
@ -60,7 +60,7 @@ class EdudziennikWebExams(override val data: DataEdudziennik,
startTime,
topic,
-1,
eventType.id.toInt(),
eventType.id,
false,
-1,
subject.id,

View File

@ -17,12 +17,12 @@ import pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.login.IdziennikLo
import pl.szczodrzynski.edziennik.data.api.interfaces.EdziennikCallback
import pl.szczodrzynski.edziennik.data.api.interfaces.EdziennikInterface
import pl.szczodrzynski.edziennik.data.api.models.ApiError
import pl.szczodrzynski.edziennik.data.db.full.AnnouncementFull
import pl.szczodrzynski.edziennik.data.db.entity.LoginStore
import pl.szczodrzynski.edziennik.data.db.entity.Message
import pl.szczodrzynski.edziennik.data.db.full.MessageFull
import pl.szczodrzynski.edziennik.data.db.entity.Profile
import pl.szczodrzynski.edziennik.data.db.entity.Teacher
import pl.szczodrzynski.edziennik.data.db.full.AnnouncementFull
import pl.szczodrzynski.edziennik.data.db.full.MessageFull
import pl.szczodrzynski.edziennik.utils.Utils.d
class Idziennik(val app: App, val profile: Profile?, val loginStore: LoginStore, val callback: EdziennikCallback) : EdziennikInterface {
@ -43,9 +43,7 @@ class Idziennik(val app: App, val profile: Profile?, val loginStore: LoginStore,
private fun completed() {
data.saveData()
data.notify {
callback.onCompleted()
}
callback.onCompleted()
}
/* _______ _ _ _ _ _

View File

@ -19,12 +19,12 @@ import pl.szczodrzynski.edziennik.data.api.edziennik.librus.login.LibrusLogin
import pl.szczodrzynski.edziennik.data.api.interfaces.EdziennikCallback
import pl.szczodrzynski.edziennik.data.api.interfaces.EdziennikInterface
import pl.szczodrzynski.edziennik.data.api.models.ApiError
import pl.szczodrzynski.edziennik.data.db.full.AnnouncementFull
import pl.szczodrzynski.edziennik.data.db.entity.LoginStore
import pl.szczodrzynski.edziennik.data.db.entity.Message
import pl.szczodrzynski.edziennik.data.db.full.MessageFull
import pl.szczodrzynski.edziennik.data.db.entity.Profile
import pl.szczodrzynski.edziennik.data.db.entity.Teacher
import pl.szczodrzynski.edziennik.data.db.full.AnnouncementFull
import pl.szczodrzynski.edziennik.data.db.full.MessageFull
import pl.szczodrzynski.edziennik.utils.Utils.d
class Librus(val app: App, val profile: Profile?, val loginStore: LoginStore, val callback: EdziennikCallback) : EdziennikInterface {
@ -45,9 +45,7 @@ class Librus(val app: App, val profile: Profile?, val loginStore: LoginStore, va
private fun completed() {
data.saveData()
data.notify {
callback.onCompleted()
}
callback.onCompleted()
}
/* _______ _ _ _ _ _

View File

@ -10,9 +10,9 @@ import pl.szczodrzynski.edziennik.data.api.edziennik.librus.DataLibrus
import pl.szczodrzynski.edziennik.data.api.edziennik.librus.ENDPOINT_LIBRUS_API_EVENTS
import pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.LibrusApi
import pl.szczodrzynski.edziennik.data.api.models.DataRemoveModel
import pl.szczodrzynski.edziennik.data.db.entity.SYNC_ALWAYS
import pl.szczodrzynski.edziennik.data.db.entity.Event
import pl.szczodrzynski.edziennik.data.db.entity.Metadata
import pl.szczodrzynski.edziennik.data.db.entity.SYNC_ALWAYS
import pl.szczodrzynski.edziennik.utils.models.Date
import pl.szczodrzynski.edziennik.utils.models.Time
@ -34,7 +34,7 @@ class LibrusApiEvents(override val data: DataLibrus,
val id = event.getLong("Id") ?: return@forEach
val eventDate = Date.fromY_m_d(event.getString("Date"))
val topic = event.getString("Content") ?: ""
val type = event.getJsonObject("Category")?.getInt("Id") ?: -1
val type = event.getJsonObject("Category")?.getLong("Id") ?: -1
val teacherId = event.getJsonObject("CreatedBy")?.getLong("Id") ?: -1
val subjectId = event.getJsonObject("Subject")?.getLong("Id") ?: -1
val teamId = event.getJsonObject("Class")?.getLong("Id") ?: -1

View File

@ -17,12 +17,12 @@ import pl.szczodrzynski.edziennik.data.api.edziennik.mobidziennik.login.Mobidzie
import pl.szczodrzynski.edziennik.data.api.interfaces.EdziennikCallback
import pl.szczodrzynski.edziennik.data.api.interfaces.EdziennikInterface
import pl.szczodrzynski.edziennik.data.api.models.ApiError
import pl.szczodrzynski.edziennik.data.db.full.AnnouncementFull
import pl.szczodrzynski.edziennik.data.db.entity.LoginStore
import pl.szczodrzynski.edziennik.data.db.entity.Message
import pl.szczodrzynski.edziennik.data.db.full.MessageFull
import pl.szczodrzynski.edziennik.data.db.entity.Profile
import pl.szczodrzynski.edziennik.data.db.entity.Teacher
import pl.szczodrzynski.edziennik.data.db.full.AnnouncementFull
import pl.szczodrzynski.edziennik.data.db.full.MessageFull
import pl.szczodrzynski.edziennik.utils.Utils.d
class Mobidziennik(val app: App, val profile: Profile?, val loginStore: LoginStore, val callback: EdziennikCallback) : EdziennikInterface {
@ -45,9 +45,7 @@ class Mobidziennik(val app: App, val profile: Profile?, val loginStore: LoginSto
private fun completed() {
data.saveData()
data.notify {
callback.onCompleted()
}
callback.onCompleted()
}
/* _______ _ _ _ _ _

View File

@ -15,12 +15,12 @@ import pl.szczodrzynski.edziennik.data.api.interfaces.EdziennikInterface
import pl.szczodrzynski.edziennik.data.api.models.ApiError
import pl.szczodrzynski.edziennik.data.api.prepare
import pl.szczodrzynski.edziennik.data.api.templateLoginMethods
import pl.szczodrzynski.edziennik.data.db.full.AnnouncementFull
import pl.szczodrzynski.edziennik.data.db.entity.LoginStore
import pl.szczodrzynski.edziennik.data.db.entity.Message
import pl.szczodrzynski.edziennik.data.db.full.MessageFull
import pl.szczodrzynski.edziennik.data.db.entity.Profile
import pl.szczodrzynski.edziennik.data.db.entity.Teacher
import pl.szczodrzynski.edziennik.data.db.full.AnnouncementFull
import pl.szczodrzynski.edziennik.data.db.full.MessageFull
import pl.szczodrzynski.edziennik.utils.Utils.d
class Template(val app: App, val profile: Profile?, val loginStore: LoginStore, val callback: EdziennikCallback) : EdziennikInterface {
@ -40,9 +40,7 @@ class Template(val app: App, val profile: Profile?, val loginStore: LoginStore,
private fun completed() {
data.saveData()
data.notify {
callback.onCompleted()
}
callback.onCompleted()
}
/* _______ _ _ _ _ _

View File

@ -18,12 +18,12 @@ import pl.szczodrzynski.edziennik.data.api.models.ApiError
import pl.szczodrzynski.edziennik.data.api.prepare
import pl.szczodrzynski.edziennik.data.api.prepareFor
import pl.szczodrzynski.edziennik.data.api.vulcanLoginMethods
import pl.szczodrzynski.edziennik.data.db.full.AnnouncementFull
import pl.szczodrzynski.edziennik.data.db.entity.LoginStore
import pl.szczodrzynski.edziennik.data.db.entity.Message
import pl.szczodrzynski.edziennik.data.db.full.MessageFull
import pl.szczodrzynski.edziennik.data.db.entity.Profile
import pl.szczodrzynski.edziennik.data.db.entity.Teacher
import pl.szczodrzynski.edziennik.data.db.full.AnnouncementFull
import pl.szczodrzynski.edziennik.data.db.full.MessageFull
import pl.szczodrzynski.edziennik.utils.Utils.d
class Vulcan(val app: App, val profile: Profile?, val loginStore: LoginStore, val callback: EdziennikCallback) : EdziennikInterface {
@ -44,9 +44,7 @@ class Vulcan(val app: App, val profile: Profile?, val loginStore: LoginStore, va
private fun completed() {
data.saveData()
data.notify {
callback.onCompleted()
}
callback.onCompleted()
}
/* _______ _ _ _ _ _

View File

@ -0,0 +1,7 @@
/*
* Copyright (c) Kuba Szczodrzyński 2020-1-18.
*/
package pl.szczodrzynski.edziennik.data.api.events
class ProfileListEmptyEvent

View File

@ -194,7 +194,6 @@ abstract class Data(val app: App, val profile: Profile?, val loginStore: LoginSt
if (profile == null)
return // return on first login
profile.empty = false
profile.userCode = generateUserCode()
db.profileDao().add(profile)
@ -294,21 +293,6 @@ abstract class Data(val app: App, val profile: Profile?, val loginStore: LoginSt
db.messageRecipientDao().addAllIgnore(messageRecipientIgnoreList)
}
fun notify(onSuccess: () -> Unit) {
if (profile == null) {
onSuccess()
return
}
try {
DataNotifications(this)
db.notificationDao().addAll(notifications)
onSuccess()
} catch (e: Exception) {
error(ApiError(TAG, EXCEPTION_NOTIFY)
.withThrowable(e))
}
}
fun setSyncNext(endpointId: Int, syncIn: Long? = null, viewId: Int? = null, syncAt: Long? = null) {
EndpointTimer(profile?.id
?: -1, endpointId).apply {

View File

@ -47,11 +47,11 @@ open class DataRemoveModel {
}
}
class Events(private val type: Int?, private val exceptType: Int?, private val exceptTypes: List<Int>?) : DataRemoveModel() {
class Events(private val type: Long?, private val exceptType: Long?, private val exceptTypes: List<Long>?) : DataRemoveModel() {
companion object {
fun futureExceptType(exceptType: Int) = Events(null, exceptType, null)
fun futureExceptTypes(exceptTypes: List<Int>) = Events(null, null, exceptTypes)
fun futureWithType(type: Int) = Events(type, null, null)
fun futureExceptType(exceptType: Long) = Events(null, exceptType, null)
fun futureExceptTypes(exceptTypes: List<Long>) = Events(null, null, exceptTypes)
fun futureWithType(type: Long) = Events(type, null, null)
}
fun commit(profileId: Int, dao: EventDao) {

View File

@ -1,52 +0,0 @@
/*
* Copyright (c) Kacper Ziubryniewicz 2019-12-13
*/
package pl.szczodrzynski.edziennik.data.api.szkolny
import pl.szczodrzynski.edziennik.App
import pl.szczodrzynski.edziennik.data.api.interfaces.EdziennikCallback
import pl.szczodrzynski.edziennik.data.db.entity.Metadata
import pl.szczodrzynski.edziennik.data.db.entity.Profile
class Szkolny(val app: App, val callback: EdziennikCallback) {
private val api = SzkolnyApi(app)
fun sync(profileList: List<Profile>) {
val profiles = profileList.filter { it.registration == Profile.REGISTRATION_ENABLED }
if (profiles.isNotEmpty()) {
val events = api.getEvents(profiles)
if (events.isNotEmpty()) {
app.db.eventDao().addAll(events)
app.db.metadataDao().addAllIgnore(events.map { event ->
Metadata(
event.profileId,
Metadata.TYPE_EVENT,
event.id,
event.seen,
event.notified,
event.addedDate
)
})
}
}
completed()
}
/*fun shareEvent(event: EventFull) {
api.shareEvent(event)
completed()
}
fun unshareEvent(event: EventFull) {
api.unshareEvent(event)
completed()
}*/
private fun completed() {
callback.onCompleted()
}
}

View File

@ -12,15 +12,14 @@ import pl.szczodrzynski.edziennik.BuildConfig
import pl.szczodrzynski.edziennik.data.api.szkolny.adapter.DateAdapter
import pl.szczodrzynski.edziennik.data.api.szkolny.adapter.TimeAdapter
import pl.szczodrzynski.edziennik.data.api.szkolny.interceptor.SignatureInterceptor
import pl.szczodrzynski.edziennik.data.api.szkolny.request.ErrorReportRequest
import pl.szczodrzynski.edziennik.data.api.szkolny.request.EventShareRequest
import pl.szczodrzynski.edziennik.data.api.szkolny.request.ServerSyncRequest
import pl.szczodrzynski.edziennik.data.api.szkolny.request.WebPushRequest
import pl.szczodrzynski.edziennik.data.api.szkolny.request.*
import pl.szczodrzynski.edziennik.data.api.szkolny.response.ApiResponse
import pl.szczodrzynski.edziennik.data.api.szkolny.response.Update
import pl.szczodrzynski.edziennik.data.api.szkolny.response.WebPushResponse
import pl.szczodrzynski.edziennik.data.db.entity.Event
import pl.szczodrzynski.edziennik.data.db.full.EventFull
import pl.szczodrzynski.edziennik.data.db.entity.Notification
import pl.szczodrzynski.edziennik.data.db.entity.Profile
import pl.szczodrzynski.edziennik.data.db.full.EventFull
import pl.szczodrzynski.edziennik.md5
import pl.szczodrzynski.edziennik.utils.models.Date
import pl.szczodrzynski.edziennik.utils.models.Time
@ -56,9 +55,8 @@ class SzkolnyApi(val app: App) {
api = retrofit.create()
}
fun getEvents(profiles: List<Profile>): List<EventFull> {
fun getEvents(profiles: List<Profile>, notifications: List<Notification>, blacklistedIds: List<Long>): List<EventFull> {
val teams = app.db.teamDao().allNow
val notifications = app.db.notificationDao().getNotPostedNow()
val response = api.serverSync(ServerSyncRequest(
deviceId = app.deviceId,
@ -88,8 +86,8 @@ class SzkolnyApi(val app: App) {
val config = app.config.getFor(profile.id)
val user = ServerSyncRequest.User(
profile.userCode,
profile.studentNameLong ?: "",
profile.studentNameShort ?: "",
profile.studentNameLong,
profile.studentNameShort,
profile.loginStoreType,
teams.filter { it.profileId == profile.id }.map { it.code }
)
@ -108,17 +106,19 @@ class SzkolnyApi(val app: App) {
val events = mutableListOf<EventFull>()
response?.data?.events?.forEach { event ->
teams.filter { it.code == event.teamCode }.forEach { team ->
val profile = profiles.firstOrNull { it.id == team.profileId }
if (event.id in blacklistedIds)
return@forEach
teams.filter { it.code == event.teamCode }.onEach { team ->
val profile = profiles.firstOrNull { it.id == team.profileId } ?: return@onEach
events.add(event.apply {
events.add(EventFull(event).apply {
profileId = team.profileId
teamId = team.id
addedManually = true
seen = profile?.empty ?: false
notified = profile?.empty ?: false
seen = profile.empty
notified = profile.empty
if (profile?.userCode == event.sharedBy) sharedBy = "self"
if (profile.userCode == event.sharedBy) sharedBy = "self"
})
}
}
@ -193,4 +193,15 @@ class SzkolnyApi(val app: App) {
errors = errors
)).execute().body()
}
fun unregisterAppUser(userCode: String): ApiResponse<Nothing>? {
return api.appUser(AppUserRequest(
deviceId = app.deviceId,
userCode = userCode
)).execute().body()
}
fun getUpdate(): ApiResponse<List<Update>>? {
return api.updates().execute().body()
}
}

View File

@ -4,16 +4,16 @@
package pl.szczodrzynski.edziennik.data.api.szkolny
import pl.szczodrzynski.edziennik.data.api.szkolny.request.ErrorReportRequest
import pl.szczodrzynski.edziennik.data.api.szkolny.request.EventShareRequest
import pl.szczodrzynski.edziennik.data.api.szkolny.request.ServerSyncRequest
import pl.szczodrzynski.edziennik.data.api.szkolny.request.WebPushRequest
import pl.szczodrzynski.edziennik.data.api.szkolny.request.*
import pl.szczodrzynski.edziennik.data.api.szkolny.response.ApiResponse
import pl.szczodrzynski.edziennik.data.api.szkolny.response.ServerSyncResponse
import pl.szczodrzynski.edziennik.data.api.szkolny.response.Update
import pl.szczodrzynski.edziennik.data.api.szkolny.response.WebPushResponse
import retrofit2.Call
import retrofit2.http.Body
import retrofit2.http.GET
import retrofit2.http.POST
import retrofit2.http.Query
interface SzkolnyService {
@ -28,4 +28,10 @@ interface SzkolnyService {
@POST("errorReport")
fun errorReport(@Body request: ErrorReportRequest): Call<ApiResponse<Nothing>>
@POST("appUser")
fun appUser(@Body request: AppUserRequest): Call<ApiResponse<Nothing>>
@GET("updates/app")
fun updates(@Query("channel") channel: String = "release"): Call<ApiResponse<List<Update>>>
}

View File

@ -0,0 +1,12 @@
/*
* Copyright (c) Kuba Szczodrzyński 2020-1-18.
*/
package pl.szczodrzynski.edziennik.data.api.szkolny.request
data class AppUserRequest(
val action: String = "unregister",
val deviceId: String,
val userCode: String
)

View File

@ -0,0 +1,15 @@
/*
* Copyright (c) Kuba Szczodrzyński 2020-1-18.
*/
package pl.szczodrzynski.edziennik.data.api.szkolny.response
data class Update(
val versionCode: Int,
val versionName: String,
val releaseDate: String,
val releaseNotes: String?,
val releaseType: String,
val isOnGooglePlay: Boolean,
val downloadUrl: String?
)

View File

@ -0,0 +1,49 @@
/*
* Copyright (c) Kuba Szczodrzyński 2020-1-17.
*/
package pl.szczodrzynski.edziennik.data.api.task
import pl.szczodrzynski.edziennik.App
import pl.szczodrzynski.edziennik.data.api.szkolny.SzkolnyApi
import pl.szczodrzynski.edziennik.data.db.entity.Metadata
import pl.szczodrzynski.edziennik.data.db.entity.Notification
import pl.szczodrzynski.edziennik.data.db.entity.Profile
class AppSync(val app: App, val notifications: MutableList<Notification>, val profiles: List<Profile>, val api: SzkolnyApi) {
companion object {
private const val TAG = "AppSync"
}
/**
* Run the app sync, sending all pending notifications
* and retrieving a list of shared events.
*
* Events are automatically saved to app database,
* along with corresponding metadata objects.
*
* @return a number of events inserted to DB, possibly needing a notification
*/
fun run(): Int {
val profiles = profiles.filter { it.registration == Profile.REGISTRATION_ENABLED && !it.archived }
if (profiles.isNotEmpty()) {
val blacklistedIds = app.db.eventDao().blacklistedIds;
val events = api.getEvents(profiles, notifications, blacklistedIds)
if (events.isNotEmpty()) {
app.db.metadataDao().addAllIgnore(events.map { event ->
Metadata(
event.profileId,
Metadata.TYPE_EVENT,
event.id,
event.seen,
event.notified,
event.addedDate
)
})
return app.db.eventDao().addAll(events).size
}
}
return 0;
}
}

View File

@ -0,0 +1,277 @@
/*
* Copyright (c) Kuba Szczodrzyński 2020-1-16.
*/
package pl.szczodrzynski.edziennik.data.api.task
import pl.szczodrzynski.edziennik.App
import pl.szczodrzynski.edziennik.MainActivity
import pl.szczodrzynski.edziennik.R
import pl.szczodrzynski.edziennik.data.db.entity.*
import pl.szczodrzynski.edziennik.getNotificationTitle
import pl.szczodrzynski.edziennik.utils.models.Date
class Notifications(val app: App, val notifications: MutableList<Notification>, val profiles: List<Profile>) {
companion object {
private const val TAG = "Notifications"
}
private val today by lazy { Date.getToday() }
private val todayValue by lazy { today.value }
/**
* Create a [Notification] from every possible
* data type. Notifications are posted whenever
* the object's metadata `notified` property is
* set to false.
*/
fun run() {
timetableNotifications()
eventNotifications()
gradeNotifications()
behaviourNotifications()
attendanceNotifications()
announcementNotifications()
messageNotifications()
luckyNumberNotifications()
}
private fun timetableNotifications() {
for (lesson in app.db.timetableDao().getNotNotifiedNow()) {
val text = app.getString(
R.string.notification_lesson_change_format,
lesson.getDisplayChangeType(app),
if (lesson.displayDate == null) "" else lesson.displayDate!!.formattedString,
lesson.changeSubjectName
)
notifications += Notification(
id = Notification.buildId(lesson.profileId, Notification.TYPE_TIMETABLE_LESSON_CHANGE, lesson.id),
title = app.getNotificationTitle(Notification.TYPE_TIMETABLE_LESSON_CHANGE),
text = text,
type = Notification.TYPE_TIMETABLE_LESSON_CHANGE,
profileId = lesson.profileId,
profileName = profiles.singleOrNull { it.id == lesson.profileId }?.name,
viewId = MainActivity.DRAWER_ITEM_TIMETABLE,
addedDate = lesson.addedDate
).addExtra("timetableDate", lesson.displayDate?.stringY_m_d ?: "")
}
}
private fun eventNotifications() {
for (event in app.db.eventDao().notNotifiedNow) {
val text = if (event.type == Event.TYPE_HOMEWORK)
app.getString(
if (event.subjectLongName.isNullOrEmpty())
R.string.notification_homework_no_subject_format
else
R.string.notification_homework_format,
event.subjectLongName,
event.eventDate.formattedString
)
else
app.getString(
if (event.subjectLongName.isNullOrEmpty())
R.string.notification_event_no_subject_format
else
R.string.notification_event_format,
event.typeName,
event.eventDate.formattedString,
event.subjectLongName
)
val type = if (event.type == Event.TYPE_HOMEWORK) Notification.TYPE_NEW_HOMEWORK else Notification.TYPE_NEW_EVENT
notifications += Notification(
id = Notification.buildId(event.profileId, type, event.id),
title = app.getNotificationTitle(type),
text = text,
type = type,
profileId = event.profileId,
profileName = profiles.singleOrNull { it.id == event.profileId }?.name,
viewId = if (event.type == Event.TYPE_HOMEWORK) MainActivity.DRAWER_ITEM_HOMEWORK else MainActivity.DRAWER_ITEM_AGENDA,
addedDate = event.addedDate
).addExtra("eventId", event.id).addExtra("eventDate", event.eventDate.value.toLong())
}
}
fun sharedEventNotifications() {
for (event in app.db.eventDao().notNotifiedNow.filter { it.sharedBy != null }) {
val text = app.getString(
R.string.notification_shared_event_format,
event.sharedByName,
event.typeName ?: "wydarzenie",
event.eventDate.formattedString,
event.topic
)
val type = if (event.type == Event.TYPE_HOMEWORK) Notification.TYPE_NEW_HOMEWORK else Notification.TYPE_NEW_EVENT
notifications += Notification(
id = Notification.buildId(event.profileId, type, event.id),
title = app.getNotificationTitle(type),
text = text,
type = type,
profileId = event.profileId,
profileName = profiles.singleOrNull { it.id == event.profileId }?.name,
viewId = if (event.type == Event.TYPE_HOMEWORK) MainActivity.DRAWER_ITEM_HOMEWORK else MainActivity.DRAWER_ITEM_AGENDA,
addedDate = event.addedDate
).addExtra("eventId", event.id).addExtra("eventDate", event.eventDate.value.toLong())
}
}
private fun gradeNotifications() {
for (grade in app.db.gradeDao().notNotifiedNow) {
val gradeName = when (grade.type) {
Grade.TYPE_SEMESTER1_PROPOSED, Grade.TYPE_SEMESTER2_PROPOSED -> app.getString(R.string.grade_semester_proposed_format_2, grade.name)
Grade.TYPE_SEMESTER1_FINAL, Grade.TYPE_SEMESTER2_FINAL -> app.getString(R.string.grade_semester_final_format_2, grade.name)
Grade.TYPE_YEAR_PROPOSED -> app.getString(R.string.grade_year_proposed_format_2, grade.name)
Grade.TYPE_YEAR_FINAL -> app.getString(R.string.grade_year_final_format_2, grade.name)
else -> grade.name
}
val text = app.getString(
R.string.notification_grade_format,
gradeName,
grade.subjectLongName
)
notifications += Notification(
id = Notification.buildId(grade.profileId, Notification.TYPE_NEW_GRADE, grade.id),
title = app.getNotificationTitle(Notification.TYPE_NEW_GRADE),
text = text,
type = Notification.TYPE_NEW_GRADE,
profileId = grade.profileId,
profileName = profiles.singleOrNull { it.id == grade.profileId }?.name,
viewId = MainActivity.DRAWER_ITEM_GRADES,
addedDate = grade.addedDate
).addExtra("gradeId", grade.id).addExtra("gradesSubjectId", grade.subjectId)
}
}
private fun behaviourNotifications() {
for (notice in app.db.noticeDao().notNotifiedNow) {
val noticeTypeStr = when (notice.type) {
Notice.TYPE_POSITIVE -> app.getString(R.string.notification_notice_praise)
Notice.TYPE_NEGATIVE -> app.getString(R.string.notification_notice_warning)
else -> app.getString(R.string.notification_notice_new)
}
val text = app.getString(
R.string.notification_notice_format,
noticeTypeStr,
notice.teacherFullName,
Date.fromMillis(notice.addedDate).formattedString
)
notifications += Notification(
id = Notification.buildId(notice.profileId, Notification.TYPE_NEW_NOTICE, notice.id),
title = app.getNotificationTitle(Notification.TYPE_NEW_NOTICE),
text = text,
type = Notification.TYPE_NEW_NOTICE,
profileId = notice.profileId,
profileName = profiles.singleOrNull { it.id == notice.profileId }?.name,
viewId = MainActivity.DRAWER_ITEM_BEHAVIOUR,
addedDate = notice.addedDate
).addExtra("noticeId", notice.id)
}
}
private fun attendanceNotifications() {
for (attendance in app.db.attendanceDao().notNotifiedNow) {
val attendanceTypeStr = when (attendance.type) {
Attendance.TYPE_ABSENT -> app.getString(R.string.notification_absence)
Attendance.TYPE_ABSENT_EXCUSED -> app.getString(R.string.notification_absence_excused)
Attendance.TYPE_BELATED -> app.getString(R.string.notification_belated)
Attendance.TYPE_BELATED_EXCUSED -> app.getString(R.string.notification_belated_excused)
Attendance.TYPE_RELEASED -> app.getString(R.string.notification_release)
Attendance.TYPE_DAY_FREE -> app.getString(R.string.notification_day_free)
else -> app.getString(R.string.notification_type_attendance)
}
val text = app.getString(
if (attendance.subjectLongName.isNullOrEmpty())
R.string.notification_attendance_no_lesson_format
else
R.string.notification_attendance_format,
attendanceTypeStr,
attendance.subjectLongName,
attendance.lessonDate.formattedString
)
notifications += Notification(
id = Notification.buildId(attendance.profileId, Notification.TYPE_NEW_ATTENDANCE, attendance.id),
title = app.getNotificationTitle(Notification.TYPE_NEW_ATTENDANCE),
text = text,
type = Notification.TYPE_NEW_ATTENDANCE,
profileId = attendance.profileId,
profileName = profiles.singleOrNull { it.id == attendance.profileId }?.name,
viewId = MainActivity.DRAWER_ITEM_ATTENDANCE,
addedDate = attendance.addedDate
).addExtra("attendanceId", attendance.id).addExtra("attendanceSubjectId", attendance.subjectId)
}
}
private fun announcementNotifications() {
for (announcement in app.db.announcementDao().notNotifiedNow) {
val text = app.getString(
R.string.notification_announcement_format,
announcement.teacherFullName,
announcement.subject
)
notifications += Notification(
id = Notification.buildId(announcement.profileId, Notification.TYPE_NEW_ANNOUNCEMENT, announcement.id),
title = app.getNotificationTitle(Notification.TYPE_NEW_ANNOUNCEMENT),
text = text,
type = Notification.TYPE_NEW_ANNOUNCEMENT,
profileId = announcement.profileId,
profileName = profiles.singleOrNull { it.id == announcement.profileId }?.name,
viewId = MainActivity.DRAWER_ITEM_ANNOUNCEMENTS,
addedDate = announcement.addedDate
).addExtra("announcementId", announcement.id)
}
}
private fun messageNotifications() {
for (message in app.db.messageDao().receivedNotNotifiedNow) {
val text = app.getString(
R.string.notification_message_format,
message.senderFullName,
message.subject
)
notifications += Notification(
id = Notification.buildId(message.profileId, Notification.TYPE_NEW_MESSAGE, message.id),
title = app.getNotificationTitle(Notification.TYPE_NEW_MESSAGE),
text = text,
type = Notification.TYPE_NEW_MESSAGE,
profileId = message.profileId,
profileName = profiles.singleOrNull { it.id == message.profileId }?.name,
viewId = MainActivity.DRAWER_ITEM_MESSAGES,
addedDate = message.addedDate
).addExtra("messageType", Message.TYPE_RECEIVED.toLong()).addExtra("messageId", message.id)
}
}
private fun luckyNumberNotifications() {
val luckyNumbers = app.db.luckyNumberDao().notNotifiedNow
luckyNumbers?.removeAll { it.date < today }
luckyNumbers?.forEach { luckyNumber ->
val profile = profiles.singleOrNull { it.id == luckyNumber.profileId } ?: return@forEach
val text = when (profile.studentNumber != -1 && profile.studentNumber == luckyNumber.number) {
true -> when (luckyNumber.date.value) {
todayValue -> R.string.notification_lucky_number_yours_format
todayValue + 1 -> R.string.notification_lucky_number_yours_tomorrow_format
else -> R.string.notification_lucky_number_yours_later_format
}
else -> when (luckyNumber.date.value) {
todayValue -> R.string.notification_lucky_number_format
todayValue + 1 -> R.string.notification_lucky_number_tomorrow_format
else -> R.string.notification_lucky_number_later_format
}
}
notifications += Notification(
id = Notification.buildId(luckyNumber.profileId, Notification.TYPE_LUCKY_NUMBER, luckyNumber.date.value.toLong()),
title = app.getNotificationTitle(Notification.TYPE_LUCKY_NUMBER),
text = app.getString(text, luckyNumber.date.formattedString, luckyNumber.number),
type = Notification.TYPE_LUCKY_NUMBER,
profileId = luckyNumber.profileId,
profileName = profile.name,
viewId = MainActivity.DRAWER_ITEM_HOME,
addedDate = luckyNumber.addedDate
)
}
}
}

View File

@ -1,90 +0,0 @@
package pl.szczodrzynski.edziennik.data.api.task
import android.app.PendingIntent
import android.content.Intent
import android.os.Build
import androidx.core.app.NotificationCompat
import pl.szczodrzynski.edziennik.App
import pl.szczodrzynski.edziennik.MainActivity
import pl.szczodrzynski.edziennik.Notifier.ID_NOTIFICATIONS
import pl.szczodrzynski.edziennik.R
import pl.szczodrzynski.edziennik.data.api.interfaces.EdziennikCallback
import pl.szczodrzynski.edziennik.getNotificationTitle
import pl.szczodrzynski.edziennik.utils.models.Notification
import kotlin.math.min
class NotifyTask : IApiTask(-1) {
override fun prepare(app: App) {
taskName = app.getString(R.string.edziennik_notification_api_notify_title)
}
override fun cancel() {
}
fun run(app: App, taskCallback: EdziennikCallback) {
val list = app.db.notificationDao().getNotPostedNow()
val notificationList = list.subList(0, min(10, list.size))
val unreadCount = list.size
for (notification in notificationList) {
val intent = Intent(app, MainActivity::class.java)
notification.fillIntent(intent)
val pendingIntent = PendingIntent.getActivity(app, notification.id, intent, 0)
val notificationBuilder = NotificationCompat.Builder(app, app.notifier.notificationGroup)
// title, text, type, date
.setContentTitle(notification.profileName)
.setContentText(notification.text)
.setSubText(app.getNotificationTitle(notification.type))
.setWhen(notification.addedDate)
.setTicker(app.getString(R.string.notification_ticker_format, Notification.stringType(app, notification.type)))
// icon, color, lights, priority
.setSmallIcon(R.drawable.ic_notification)
.setColor(app.notifier.notificationColor)
.setLights(-0xff0001, 2000, 2000)
.setPriority(app.notifier.notificationPriority)
// channel, group, style
.setChannelId(app.notifier.notificationGroup)
.setGroup(app.notifier.notificationGroup)
.setGroupAlertBehavior(NotificationCompat.GROUP_ALERT_SUMMARY)
.setStyle(NotificationCompat.BigTextStyle().bigText(notification.text))
// intent, auto cancel
.setContentIntent(pendingIntent)
.setAutoCancel(true)
if (!app.notifier.shouldBeQuiet()) {
notificationBuilder.setDefaults(app.notifier.notificationDefaults)
}
app.notifier.notificationManager.notify(notification.id, notificationBuilder.build())
}
if (notificationList.isNotEmpty() && Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
val intent = Intent(app, MainActivity::class.java)
intent.action = "android.intent.action.MAIN"
intent.putExtra("fragmentId", MainActivity.DRAWER_ITEM_NOTIFICATIONS)
val pendingIntent = PendingIntent.getActivity(app, ID_NOTIFICATIONS,
intent, 0)
val groupBuilder = NotificationCompat.Builder(app, app.notifier.notificationGroup)
.setSmallIcon(R.drawable.ic_notification)
.setColor(app.notifier.notificationColor)
.setContentTitle(app.getString(R.string.notification_new_notification_title_format, unreadCount))
.setGroupSummary(true)
.setAutoCancel(true)
.setChannelId(app.notifier.notificationGroup)
.setGroup(app.notifier.notificationGroup)
.setLights(-0xff0001, 2000, 2000)
.setPriority(app.notifier.notificationPriority)
.setContentIntent(pendingIntent)
.setStyle(NotificationCompat.BigTextStyle())
if (!app.notifier.shouldBeQuiet()) {
groupBuilder.setDefaults(app.notifier.notificationDefaults)
}
app.notifier.notificationManager.notify(ID_NOTIFICATIONS, groupBuilder.build())
}
app.db.notificationDao().setAllPosted()
taskCallback.onCompleted()
}
}

View File

@ -0,0 +1,169 @@
package pl.szczodrzynski.edziennik.data.api.task
import android.app.NotificationManager
import android.app.PendingIntent
import android.content.Context
import android.os.Build
import android.util.SparseIntArray
import androidx.core.app.NotificationCompat
import androidx.core.util.forEach
import androidx.core.util.set
import pl.szczodrzynski.edziennik.*
import pl.szczodrzynski.edziennik.data.db.entity.Notification as AppNotification
class PostNotifications(val app: App, nList: MutableList<AppNotification>) {
companion object {
private const val TAG = "PostNotifications"
}
/*public boolean shouldBeQuiet() {
long now = Time.getNow().getInMillis();
long start = app.config.getSync().getQuietHoursStart();
long end = app.config.getSync().getQuietHoursEnd();
if (start > end) {
end += 1000 * 60 * 60 * 24;
//Log.d(TAG, "Night passing");
}
if (start > now) {
now += 1000 * 60 * 60 * 24;
//Log.d(TAG, "Now is smaller");
}
//Log.d(TAG, "Start is "+start+", now is "+now+", end is "+end);
return start > 0 && now >= start && now <= end;
}*/
fun shouldBeQuiet() = false
private fun buildSummaryText(summaryCounts: SparseIntArray): CharSequence {
val summaryTexts = mutableListOf<String>()
summaryCounts.forEach { key, value ->
if (value <= 0)
return@forEach
val pluralRes = when (key) {
AppNotification.TYPE_TIMETABLE_LESSON_CHANGE -> R.plurals.notification_new_timetable_change_format
AppNotification.TYPE_NEW_GRADE -> R.plurals.notification_new_grades_format
AppNotification.TYPE_NEW_EVENT -> R.plurals.notification_new_events_format
AppNotification.TYPE_NEW_HOMEWORK -> R.plurals.notification_new_homework_format
AppNotification.TYPE_NEW_SHARED_EVENT -> R.plurals.notification_new_shared_events_format
AppNotification.TYPE_NEW_SHARED_HOMEWORK -> R.plurals.notification_new_shared_homework_format
AppNotification.TYPE_NEW_MESSAGE -> R.plurals.notification_new_messages_format
AppNotification.TYPE_NEW_NOTICE -> R.plurals.notification_new_notices_format
AppNotification.TYPE_NEW_ATTENDANCE -> R.plurals.notification_new_attendance_format
AppNotification.TYPE_LUCKY_NUMBER -> R.plurals.notification_new_lucky_number_format
AppNotification.TYPE_NEW_ANNOUNCEMENT -> R.plurals.notification_new_announcements_format
else -> R.plurals.notification_other_format
}
summaryTexts += app.resources.getQuantityString(pluralRes, value, value)
}
return summaryTexts.concat(", ")
}
init {
val notificationManager = app.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
val count = nList.size
val summaryCounts = SparseIntArray()
val newNotificationsText = app.resources.getQuantityString(R.plurals.notification_count_format, count, count)
val newNotificationsShortText = app.resources.getQuantityString(R.plurals.notification_count_short_format, count, count)
val intent = Intent(
app,
MainActivity::class.java,
"fragmentId" to MainActivity.DRAWER_ITEM_NOTIFICATIONS
)
val summaryIntent = PendingIntent.getActivity(app, app.notifications.dataId, intent, PendingIntent.FLAG_ONE_SHOT)
// On Nougat or newer - show maximum 8 notifications
// On Marshmallow or older - show maximum 4 notifications
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N && count > 4 || Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && count > 8) {
val summaryList = mutableListOf<CharSequence>()
nList.forEach {
summaryCounts[it.type]++
summaryList += listOf(
it.profileName.asBoldSpannable(),
it.text
).concat(": ")
}
// Create a summary to show *instead* of notifications
val combined = NotificationCompat.Builder(app, app.notifications.dataKey)
.setContentTitle(app.getString(R.string.app_name))
.setContentText(buildSummaryText(summaryCounts))
.setTicker(newNotificationsText)
.setSmallIcon(R.drawable.ic_notification)
.setStyle(NotificationCompat.InboxStyle()
.also {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
it.setBigContentTitle(app.getString(R.string.app_name))
it.setSummaryText(newNotificationsShortText)
}
else {
it.setBigContentTitle(newNotificationsText)
it.setSummaryText(app.getString(R.string.notification_click_to_see_all))
}
summaryList.forEach { line ->
it.addLine(line)
}
})
.setColor(0xff2196f3.toInt())
.setLights(0xff2196f3.toInt(), 2000, 2000)
.setPriority(NotificationCompat.PRIORITY_MAX)
.setDefaults(NotificationCompat.DEFAULT_ALL)
.setGroup(app.notifications.dataKey)
.setContentIntent(summaryIntent)
.setAutoCancel(true)
.build()
notificationManager.notify(System.currentTimeMillis().toInt(), combined)
}
else {
// Less than 8 notifications
val notifications = nList.map {
summaryCounts[it.type]++
NotificationCompat.Builder(app, app.notifications.dataKey)
.setContentTitle(it.profileName ?: app.getString(R.string.app_name))
.setContentText(it.text)
.setSubText(it.title)
.setTicker("${it.profileName}: ${it.title}")
.setSmallIcon(R.drawable.ic_notification)
.setStyle(NotificationCompat.BigTextStyle()
.bigText(it.text))
.setWhen(it.addedDate)
.setColor(0xff2196f3.toInt())
.setLights(0xff2196f3.toInt(), 2000, 2000)
.setPriority(NotificationCompat.PRIORITY_MAX)
.setDefaults(NotificationCompat.DEFAULT_ALL)
.setGroup(app.notifications.dataKey)
.setGroupAlertBehavior(NotificationCompat.GROUP_ALERT_SUMMARY)
.setContentIntent(it.getPendingIntent(app))
.setAutoCancel(true)
.build()
}
val time = System.currentTimeMillis()
notificationManager.apply {
notifications.forEachIndexed { index, it ->
notificationManager.notify((time + index).toInt(), it)
}
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
val summary = NotificationCompat.Builder(app, app.notifications.dataKey)
.setContentTitle(newNotificationsText)
.setContentText(buildSummaryText(summaryCounts))
.setTicker(newNotificationsText)
.setSmallIcon(R.drawable.ic_notification)
.setColor(0xff2196f3.toInt())
.setLights(0xff2196f3.toInt(), 2000, 2000)
.setPriority(NotificationCompat.PRIORITY_MAX)
.setDefaults(NotificationCompat.DEFAULT_ALL)
.setGroup(app.notifications.dataKey)
.setGroupSummary(true)
.setContentIntent(summaryIntent)
.setAutoCancel(true)
.build()
notificationManager.notify(app.notifications.dataId, summary)
}
}
}
}

View File

@ -7,39 +7,39 @@ package pl.szczodrzynski.edziennik.data.api.task
import pl.szczodrzynski.edziennik.App
import pl.szczodrzynski.edziennik.R
import pl.szczodrzynski.edziennik.data.api.interfaces.EdziennikCallback
import pl.szczodrzynski.edziennik.data.api.szkolny.Szkolny
import pl.szczodrzynski.edziennik.data.api.szkolny.SzkolnyApi
import pl.szczodrzynski.edziennik.data.db.entity.Notification
import pl.szczodrzynski.edziennik.data.db.entity.Profile
import pl.szczodrzynski.edziennik.utils.Utils.d
class SzkolnyTask(val request: Any) : IApiTask(-1) {
class SzkolnyTask(val app: App, val syncingProfiles: List<Profile>) : IApiTask(-1) {
companion object {
private const val TAG = "SzkolnyTask"
fun sync(profiles: List<Profile>) = SzkolnyTask(SyncRequest(profiles))
/*fun shareEvent(event: EventFull) = SzkolnyTask(ShareEventRequest(event))
fun unshareEvent(event: EventFull) = SzkolnyTask(UnshareEventRequest(event))*/
}
private val api by lazy { SzkolnyApi(app) }
private val profiles by lazy { app.db.profileDao().allNow }
override fun prepare(app: App) { taskName = app.getString(R.string.edziennik_szkolny_creating_notifications) }
override fun cancel() {}
private lateinit var szkolny: Szkolny
private val notificationList = mutableListOf<Notification>()
override fun prepare(app: App) {
taskName = app.getString(R.string.edziennik_szkolny_api_sync_title)
internal fun run(taskCallback: EdziennikCallback) {
val startTime = System.currentTimeMillis()
val notifications = Notifications(app, notificationList, profiles)
// create all e-register data notifications
notifications.run()
// send notifications to web push, get shared events
AppSync(app, notificationList, profiles, api).run()
// create notifications for shared events (not present before app sync)
notifications.sharedEventNotifications()
d(TAG, "Created ${notificationList.count()} notifications.")
// update the database
app.db.metadataDao().setAllNotified(true)
app.db.notificationDao().addAll(notificationList)
app.db.profileDao().setAllNotEmpty()
// post all notifications
PostNotifications(app, notificationList)
d(TAG, "SzkolnyTask: finished in ${System.currentTimeMillis()-startTime} ms.")
taskCallback.onCompleted()
}
override fun cancel() {
// TODO
}
internal fun run(app: App, taskCallback: EdziennikCallback) {
szkolny = Szkolny(app, taskCallback)
when (request) {
is SyncRequest -> szkolny.sync(request.profiles)
/*is ShareEventRequest -> szkolny.shareEvent(request.event)
is UnshareEventRequest -> szkolny.unshareEvent(request.event)*/
}
}
data class SyncRequest(val profiles: List<Profile>)
/*data class ShareEventRequest(val event: EventFull)
data class UnshareEventRequest(val event: EventFull)*/
}

View File

@ -16,8 +16,8 @@ import androidx.sqlite.db.SupportSQLiteQuery;
import java.util.List;
import pl.szczodrzynski.edziennik.data.db.entity.Announcement;
import pl.szczodrzynski.edziennik.data.db.full.AnnouncementFull;
import pl.szczodrzynski.edziennik.data.db.entity.Metadata;
import pl.szczodrzynski.edziennik.data.db.full.AnnouncementFull;
import static pl.szczodrzynski.edziennik.data.db.entity.Metadata.TYPE_ANNOUNCEMENT;
@ -69,4 +69,14 @@ public abstract class AnnouncementDao {
public List<AnnouncementFull> getNotNotifiedNow(int profileId) {
return getAllNow(profileId, "notified = 0");
}
@Query("SELECT " +
"*, " +
"teachers.teacherName || ' ' || teachers.teacherSurname AS teacherFullName " +
"FROM announcements " +
"LEFT JOIN teachers USING(profileId, teacherId) " +
"LEFT JOIN metadata ON announcementId = thingId AND thingType = "+TYPE_ANNOUNCEMENT+" AND metadata.profileId = announcements.profileId " +
"WHERE notified = 0 " +
"ORDER BY addedDate DESC")
public abstract List<AnnouncementFull> getNotNotifiedNow();
}

View File

@ -5,20 +5,20 @@
package pl.szczodrzynski.edziennik.data.db.dao;
import androidx.lifecycle.LiveData;
import androidx.sqlite.db.SimpleSQLiteQuery;
import androidx.sqlite.db.SupportSQLiteQuery;
import androidx.room.Dao;
import androidx.room.Insert;
import androidx.room.OnConflictStrategy;
import androidx.room.Query;
import androidx.room.RawQuery;
import androidx.sqlite.db.SimpleSQLiteQuery;
import androidx.sqlite.db.SupportSQLiteQuery;
import java.util.List;
import pl.szczodrzynski.edziennik.data.db.entity.Attendance;
import pl.szczodrzynski.edziennik.data.db.full.AttendanceFull;
import pl.szczodrzynski.edziennik.utils.models.Date;
import java.util.List;
import static pl.szczodrzynski.edziennik.data.db.entity.Metadata.TYPE_ATTENDANCE;
@Dao
@ -72,6 +72,13 @@ public abstract class AttendanceDao {
return getAllNow(profileId, "notified = 0");
}
@Query("SELECT * FROM attendances " +
"LEFT JOIN subjects USING(profileId, subjectId) " +
"LEFT JOIN metadata ON attendanceId = thingId AND thingType = " + TYPE_ATTENDANCE + " AND metadata.profileId = attendances.profileId " +
"WHERE notified = 0 " +
"ORDER BY attendanceLessonDate DESC, attendanceStartTime DESC")
public abstract List<AttendanceFull> getNotNotifiedNow();
// only absent and absent_excused count as absences
// all the other types are counted as being present
@Query("SELECT \n" +

View File

@ -34,7 +34,7 @@ public abstract class EventDao {
public abstract long add(Event event);
@Insert(onConflict = OnConflictStrategy.REPLACE)
public abstract void addAll(List<Event> eventList);
public abstract long[] addAll(List<Event> eventList);
@Query("DELETE FROM events WHERE profileId = :profileId")
public abstract void clear(int profileId);
@ -44,7 +44,7 @@ public abstract class EventDao {
@Query("DELETE FROM metadata WHERE profileId = :profileId AND thingType = :thingType AND thingId = :thingId")
public abstract void removeMetadata(int profileId, int thingType, long thingId);
@Transaction
public void remove(int profileId, int type, long id) {
public void remove(int profileId, long type, long id) {
remove(profileId, id);
removeMetadata(profileId, type == Event.TYPE_HOMEWORK ? TYPE_HOMEWORK : TYPE_EVENT, id);
}
@ -88,7 +88,7 @@ public abstract class EventDao {
public LiveData<List<EventFull>> getAllWhere(int profileId, String filter) {
return getAll(profileId, filter);
}
public LiveData<List<EventFull>> getAllByType(int profileId, int type, String filter) {
public LiveData<List<EventFull>> getAllByType(int profileId, long type, String filter) {
return getAll(profileId, "eventType = "+type+" AND "+filter);
}
public LiveData<List<EventFull>> getAllByDate(int profileId, @NonNull Date date) {
@ -125,6 +125,24 @@ public abstract class EventDao {
return getAllNow(profileId, "notified = 0");
}
@Query("SELECT eventId FROM events WHERE profileId = :profileId AND eventBlacklisted = 1")
public abstract List<Long> getBlacklistedIds(int profileId);
@Query("SELECT eventId FROM events WHERE eventBlacklisted = 1")
public abstract List<Long> getBlacklistedIds();
@Query("SELECT " +
"*, " +
"eventTypes.eventTypeName AS typeName, " +
"eventTypes.eventTypeColor AS typeColor " +
"FROM events " +
"LEFT JOIN subjects USING(profileId, subjectId) " +
"LEFT JOIN eventTypes USING(profileId, eventType) " +
"LEFT JOIN metadata ON eventId = thingId AND (thingType = " + TYPE_EVENT + " OR thingType = " + TYPE_HOMEWORK + ") AND metadata.profileId = events.profileId " +
"WHERE events.eventBlacklisted = 0 AND notified = 0 " +
"GROUP BY eventId " +
"ORDER BY addedDate ASC")
public abstract List<EventFull> getNotNotifiedNow();
public EventFull getByIdNow(int profileId, long eventId) {
List<EventFull> eventList = getAllNow(profileId, "eventId = "+eventId);
return eventList.size() == 0 ? null : eventList.get(0);
@ -146,13 +164,13 @@ public abstract class EventDao {
}
@Query("DELETE FROM events WHERE profileId = :profileId AND eventAddedManually = 0 AND eventDate >= :todayDate AND eventType = :type")
public abstract void removeFutureWithType(int profileId, Date todayDate, int type);
public abstract void removeFutureWithType(int profileId, Date todayDate, long type);
@Query("DELETE FROM events WHERE profileId = :profileId AND eventAddedManually = 0 AND eventDate >= :todayDate AND eventType != :exceptType")
public abstract void removeFutureExceptType(int profileId, Date todayDate, int exceptType);
public abstract void removeFutureExceptType(int profileId, Date todayDate, long exceptType);
@Transaction
public void removeFutureExceptTypes(int profileId, Date todayDate, List<Integer> exceptTypes) {
public void removeFutureExceptTypes(int profileId, Date todayDate, List<Long> exceptTypes) {
removeFuture(profileId, todayDate, "eventType NOT IN " + exceptTypes.toString().replace('[', '(').replace(']', ')'));
}

View File

@ -4,14 +4,14 @@
package pl.szczodrzynski.edziennik.data.db.dao;
import java.util.List;
import androidx.lifecycle.LiveData;
import androidx.room.Dao;
import androidx.room.Insert;
import androidx.room.OnConflictStrategy;
import androidx.room.Query;
import java.util.List;
import pl.szczodrzynski.edziennik.data.db.entity.EventType;
@Dao
@ -26,11 +26,14 @@ public interface EventTypeDao {
void clear(int profileId);
@Query("SELECT * FROM eventTypes WHERE profileId = :profileId AND eventType = :typeId")
EventType getByIdNow(int profileId, int typeId);
EventType getByIdNow(int profileId, long typeId);
@Query("SELECT * FROM eventTypes WHERE profileId = :profileId")
LiveData<List<EventType>> getAll(int profileId);
@Query("SELECT * FROM eventTypes WHERE profileId = :profileId")
List<EventType> getAllNow(int profileId);
@Query("SELECT * FROM eventTypes")
List<EventType> getAllNow();
}

View File

@ -83,6 +83,13 @@ public abstract class GradeDao {
return getAllNow(profileId, "gradeParentId = "+parentId);
}
@Query("SELECT * FROM grades " +
"LEFT JOIN subjects USING(profileId, subjectId) " +
"LEFT JOIN metadata ON gradeId = thingId AND thingType = " + TYPE_GRADE + " AND metadata.profileId = grades.profileId " +
"WHERE notified = 0 " +
"ORDER BY addedDate DESC")
public abstract List<GradeFull> getNotNotifiedNow();
@RawQuery
abstract GradeFull getNow(SupportSQLiteQuery query);
public GradeFull getNow(int profileId, String filter) {

View File

@ -20,4 +20,7 @@ interface LibrusLessonDao {
@Query("SELECT * FROM librusLessons WHERE profileId = :profileId")
fun getAllNow(profileId: Int): List<LibrusLesson>
@Query("DELETE FROM librusLessons WHERE profileId = :profileId")
fun clear(profileId: Int)
}

View File

@ -17,9 +17,9 @@ import androidx.sqlite.db.SupportSQLiteQuery;
import java.util.List;
import pl.szczodrzynski.edziennik.data.db.entity.LuckyNumber;
import pl.szczodrzynski.edziennik.data.db.full.LuckyNumberFull;
import pl.szczodrzynski.edziennik.data.db.entity.Metadata;
import pl.szczodrzynski.edziennik.data.db.entity.Notice;
import pl.szczodrzynski.edziennik.data.db.full.LuckyNumberFull;
import pl.szczodrzynski.edziennik.utils.models.Date;
import static pl.szczodrzynski.edziennik.data.db.entity.Metadata.TYPE_LUCKY_NUMBER;
@ -78,4 +78,10 @@ public abstract class LuckyNumberDao {
public List<LuckyNumberFull> getNotNotifiedNow(int profileId) {
return getAllNow(profileId, "notified = 0");
}
@Query("SELECT * FROM luckyNumbers\n" +
"LEFT JOIN metadata ON luckyNumberDate = thingId AND thingType = "+TYPE_LUCKY_NUMBER+" AND metadata.profileId = luckyNumbers.profileId " +
"WHERE notified = 0 " +
"ORDER BY addedDate DESC")
public abstract List<LuckyNumberFull> getNotNotifiedNow();
}

View File

@ -17,8 +17,8 @@ import androidx.sqlite.db.SupportSQLiteQuery;
import java.util.List;
import pl.szczodrzynski.edziennik.data.db.entity.Message;
import pl.szczodrzynski.edziennik.data.db.full.MessageFull;
import pl.szczodrzynski.edziennik.data.db.entity.Metadata;
import pl.szczodrzynski.edziennik.data.db.full.MessageFull;
import static pl.szczodrzynski.edziennik.data.db.entity.Message.TYPE_DELETED;
import static pl.szczodrzynski.edziennik.data.db.entity.Message.TYPE_RECEIVED;
@ -101,4 +101,14 @@ public abstract class MessageDao {
public List<MessageFull> getReceivedNotNotifiedNow(int profileId) {
return getReceivedNow(profileId, "notified = 0");
}
@Query("SELECT " +
"*, " +
"teachers.teacherName || ' ' || teachers.teacherSurname AS senderFullName " +
"FROM messages " +
"LEFT JOIN teachers ON teachers.profileId = messages.profileId AND teacherId = senderId " +
"LEFT JOIN metadata ON messageId = thingId AND thingType = "+TYPE_MESSAGE+" AND metadata.profileId = messages.profileId " +
"WHERE messageType = 0 AND notified = 0 " +
"ORDER BY addedDate DESC")
public abstract List<MessageFull> getReceivedNotNotifiedNow();
}

View File

@ -40,6 +40,9 @@ public abstract class MetadataDao {
@Insert(onConflict = OnConflictStrategy.IGNORE)
public abstract void addAllIgnore(List<Metadata> metadataList);
@Insert(onConflict = OnConflictStrategy.REPLACE)
public abstract void addAllReplace(List<Metadata> metadataList);
@Query("UPDATE metadata SET seen = :seen WHERE thingId = :thingId AND thingType = :thingType AND profileId = :profileId")
abstract void updateSeen(int profileId, int thingType, long thingId, boolean seen);
@ -165,6 +168,9 @@ public abstract class MetadataDao {
@Query("UPDATE metadata SET notified = :notified WHERE profileId = :profileId")
public abstract void setAllNotified(int profileId, boolean notified);
@Query("UPDATE metadata SET notified = :notified")
public abstract void setAllNotified(boolean notified);
@Query("SELECT count() FROM metadata WHERE profileId = :profileId AND thingType = :thingType AND seen = 0")

View File

@ -5,13 +5,13 @@
package pl.szczodrzynski.edziennik.data.db.dao;
import androidx.lifecycle.LiveData;
import androidx.sqlite.db.SimpleSQLiteQuery;
import androidx.sqlite.db.SupportSQLiteQuery;
import androidx.room.Dao;
import androidx.room.Insert;
import androidx.room.OnConflictStrategy;
import androidx.room.Query;
import androidx.room.RawQuery;
import androidx.sqlite.db.SimpleSQLiteQuery;
import androidx.sqlite.db.SupportSQLiteQuery;
import java.util.List;
@ -69,4 +69,14 @@ public abstract class NoticeDao {
public List<NoticeFull> getNotNotifiedNow(int profileId) {
return getAllNow(profileId, "notified = 0");
}
@Query("SELECT " +
"*, " +
"teachers.teacherName || ' ' || teachers.teacherSurname AS teacherFullName " +
"FROM notices " +
"LEFT JOIN teachers USING(profileId, teacherId) " +
"LEFT JOIN metadata ON noticeId = thingId AND thingType = "+TYPE_NOTICE+" AND metadata.profileId = notices.profileId " +
"WHERE notified = 0 " +
"ORDER BY addedDate DESC")
public abstract List<NoticeFull> getNotNotifiedNow();
}

View File

@ -52,9 +52,12 @@ interface ProfileDao {
@Query("SELECT profiles.* FROM teams JOIN profiles USING(profileId) WHERE teamCode = :teamCode AND registration = " + Profile.REGISTRATION_ENABLED + " AND enableSharedEvents = 1")
fun getByTeamCodeNowWithRegistration(teamCode: String?): List<Profile>
@get:Query("SELECT profileId FROM profiles WHERE profileId >= 0 ORDER BY profileId ASC LIMIT 1")
@get:Query("SELECT profileId FROM profiles WHERE profileId > 0 ORDER BY profileId ASC LIMIT 1")
val firstId: Int?
@get:Query("SELECT profileId FROM profiles WHERE profileId >= 0 ORDER BY profileId DESC LIMIT 1")
@get:Query("SELECT profileId FROM profiles WHERE profileId > 0 ORDER BY profileId DESC LIMIT 1")
val lastId: Int?
@Query("UPDATE profiles SET empty = 0")
fun setAllNotEmpty()
}

View File

@ -107,4 +107,18 @@ interface TimetableDao {
WHERE timetable.profileId = :profileId AND timetable.type NOT IN (${Lesson.TYPE_NORMAL}, ${Lesson.TYPE_NO_LESSONS}, ${Lesson.TYPE_SHIFTED_SOURCE}) AND metadata.notified = 0
""")
fun getNotNotifiedNow(profileId: Int): List<LessonFull>
@Query("""
SELECT
timetable.*,
teachers.teacherName ||" "|| teachers.teacherSurname AS teacherName,
oldT.teacherName ||" "|| oldT.teacherSurname AS oldTeacherName,
metadata.seen, metadata.notified, metadata.addedDate
FROM timetable
LEFT JOIN teachers USING(profileId, teacherId)
LEFT JOIN teachers AS oldT ON timetable.profileId = oldT.profileId AND timetable.oldTeacherId = oldT.teacherId
LEFT JOIN metadata ON id = thingId AND thingType = ${Metadata.TYPE_LESSON_CHANGE} AND metadata.profileId = timetable.profileId
WHERE timetable.type NOT IN (${Lesson.TYPE_NORMAL}, ${Lesson.TYPE_NO_LESSONS}, ${Lesson.TYPE_SHIFTED_SOURCE}) AND metadata.notified = 0
""")
fun getNotNotifiedNow(): List<LessonFull>
}

View File

@ -32,19 +32,19 @@ public class Event {
public String topic;
@ColumnInfo(name = "eventColor")
public int color = -1;
public static final int TYPE_UNDEFINED = -2;
public static final int TYPE_HOMEWORK = -1;
public static final int TYPE_DEFAULT = 0;
public static final int TYPE_EXAM = 1;
public static final int TYPE_SHORT_QUIZ = 2;
public static final int TYPE_ESSAY = 3;
public static final int TYPE_PROJECT = 4;
public static final int TYPE_PT_MEETING = 5;
public static final int TYPE_EXCURSION = 6;
public static final int TYPE_READING = 7;
public static final int TYPE_CLASS_EVENT = 8;
public static final int TYPE_INFORMATION = 9;
public static final int TYPE_TEACHER_ABSENCE = 10;
public static final long TYPE_UNDEFINED = -2;
public static final long TYPE_HOMEWORK = -1;
public static final long TYPE_DEFAULT = 0;
public static final long TYPE_EXAM = 1;
public static final long TYPE_SHORT_QUIZ = 2;
public static final long TYPE_ESSAY = 3;
public static final long TYPE_PROJECT = 4;
public static final long TYPE_PT_MEETING = 5;
public static final long TYPE_EXCURSION = 6;
public static final long TYPE_READING = 7;
public static final long TYPE_CLASS_EVENT = 8;
public static final long TYPE_INFORMATION = 9;
public static final long TYPE_TEACHER_ABSENCE = 10;
public static final int COLOR_HOMEWORK = 0xff795548;
public static final int COLOR_DEFAULT = 0xffffc107;
public static final int COLOR_EXAM = 0xfff44336;
@ -58,7 +58,7 @@ public class Event {
public static final int COLOR_INFORMATION = 0xff039be5;
public static final int COLOR_TEACHER_ABSENCE = 0xff039be5;
@ColumnInfo(name = "eventType")
public int type = TYPE_DEFAULT;
public long type = TYPE_DEFAULT;
@ColumnInfo(name = "eventAddedManually")
public boolean addedManually;
@ColumnInfo(name = "eventSharedBy")
@ -76,7 +76,7 @@ public class Event {
@Ignore
public Event() {}
public Event(int profileId, long id, Date eventDate, @Nullable Time startTime, String topic, int color, int type, boolean addedManually, long teacherId, long subjectId, long teamId)
public Event(int profileId, long id, Date eventDate, @Nullable Time startTime, String topic, int color, long type, boolean addedManually, long teacherId, long subjectId, long teamId)
{
this.profileId = profileId;
this.id = id;
@ -97,8 +97,8 @@ public class Event {
}
@Override
public Event clone() throws CloneNotSupportedException {
return new Event(
public Event clone() {
Event event = new Event(
profileId,
id,
eventDate.clone(),
@ -111,6 +111,10 @@ public class Event {
teacherId,
teamId
);
event.sharedBy = sharedBy;
event.sharedByName = sharedByName;
event.blacklisted = blacklisted;
return event;
}
@Override

View File

@ -4,34 +4,18 @@
package pl.szczodrzynski.edziennik.data.db.entity
import android.app.PendingIntent
import android.content.Context
import android.content.Intent
import androidx.room.Entity
import androidx.room.PrimaryKey
import com.google.gson.JsonObject
import pl.szczodrzynski.edziennik.R
import pl.szczodrzynski.edziennik.data.db.entity.Notification.Companion.TYPE_AUTO_ARCHIVING
import pl.szczodrzynski.edziennik.data.db.entity.Notification.Companion.TYPE_ERROR
import pl.szczodrzynski.edziennik.data.db.entity.Notification.Companion.TYPE_FEEDBACK_MESSAGE
import pl.szczodrzynski.edziennik.data.db.entity.Notification.Companion.TYPE_GENERAL
import pl.szczodrzynski.edziennik.data.db.entity.Notification.Companion.TYPE_LUCKY_NUMBER
import pl.szczodrzynski.edziennik.data.db.entity.Notification.Companion.TYPE_NEW_ANNOUNCEMENT
import pl.szczodrzynski.edziennik.data.db.entity.Notification.Companion.TYPE_NEW_ATTENDANCE
import pl.szczodrzynski.edziennik.data.db.entity.Notification.Companion.TYPE_NEW_EVENT
import pl.szczodrzynski.edziennik.data.db.entity.Notification.Companion.TYPE_NEW_GRADE
import pl.szczodrzynski.edziennik.data.db.entity.Notification.Companion.TYPE_NEW_HOMEWORK
import pl.szczodrzynski.edziennik.data.db.entity.Notification.Companion.TYPE_NEW_MESSAGE
import pl.szczodrzynski.edziennik.data.db.entity.Notification.Companion.TYPE_NEW_NOTICE
import pl.szczodrzynski.edziennik.data.db.entity.Notification.Companion.TYPE_NEW_SHARED_EVENT
import pl.szczodrzynski.edziennik.data.db.entity.Notification.Companion.TYPE_SERVER_MESSAGE
import pl.szczodrzynski.edziennik.data.db.entity.Notification.Companion.TYPE_TIMETABLE_CHANGED
import pl.szczodrzynski.edziennik.data.db.entity.Notification.Companion.TYPE_TIMETABLE_LESSON_CHANGE
import pl.szczodrzynski.edziennik.data.db.entity.Notification.Companion.TYPE_UPDATE
import pl.szczodrzynski.edziennik.MainActivity
@Entity(tableName = "notifications")
data class Notification(
@PrimaryKey(autoGenerate = true)
val id: Int = 0,
val id: Long = 0,
val title: String,
val text: String,
@ -41,7 +25,7 @@ data class Notification(
val profileId: Int?,
val profileName: String?,
var posted: Boolean = false,
var posted: Boolean = true,
var viewId: Int? = null,
var extras: JsonObject? = null,
@ -59,6 +43,7 @@ data class Notification(
const val TYPE_NEW_HOMEWORK = 10
const val TYPE_NEW_SHARED_EVENT = 7
const val TYPE_NEW_SHARED_HOMEWORK = 12
const val TYPE_REMOVED_SHARED_EVENT = 18
const val TYPE_NEW_MESSAGE = 8
const val TYPE_NEW_NOTICE = 9
const val TYPE_NEW_ATTENDANCE = 13
@ -67,6 +52,10 @@ data class Notification(
const val TYPE_NEW_ANNOUNCEMENT = 15
const val TYPE_FEEDBACK_MESSAGE = 16
const val TYPE_AUTO_ARCHIVING = 17
fun buildId(profileId: Int, type: Int, itemId: Long): Long {
return 1000000000000 + profileId*10000000000 + type*100000000 + itemId;
}
}
fun addExtra(key: String, value: Long?): Notification {
@ -100,4 +89,10 @@ data class Notification(
e.printStackTrace()
}
}
fun getPendingIntent(context: Context): PendingIntent {
val intent = Intent(context, MainActivity::class.java)
fillIntent(intent)
return PendingIntent.getActivity(context, id.toInt(), intent, PendingIntent.FLAG_ONE_SHOT)
}
}

View File

@ -30,21 +30,21 @@ open class Profile(
val loginStoreId: Int,
val loginStoreType: Int,
override var name: String,
override var subname: String?,
override var name: String = "",
override var subname: String? = null,
/**
* The name of the student.
* This doesn't change, no matter if it's a parent or student account.
*/
var studentNameLong: String,
var studentNameShort: String,
var studentNameLong: String = "",
var studentNameShort: String = "",
/**
* A full name of the account owner.
* If null, then it's a student account.
* If not null, then it's a parent account with this name.
*/
var accountName: String?,
var accountName: String? = null,
val studentData: JsonObject = JsonObject()

View File

@ -45,6 +45,35 @@ public class EventFull extends Event {
this.sharedByName = event.sharedByName;
this.blacklisted = event.blacklisted;
}
public EventFull(EventFull event) {
super(
event.profileId,
event.id,
event.eventDate.clone(),
event.startTime == null ? null : event.startTime.clone(),
event.topic,
event.color,
event.type,
event.addedManually,
event.teacherId,
event.subjectId,
event.teamId
);
this.sharedBy = event.sharedBy;
this.sharedByName = event.sharedByName;
this.blacklisted = event.blacklisted;
this.typeName = event.typeName;
this.typeColor = event.typeColor;
this.teacherFullName = event.teacherFullName;
this.subjectLongName = event.subjectLongName;
this.subjectShortName = event.subjectShortName;
this.teamName = event.teamName;
this.teamCode = event.teamCode;
this.seen = event.seen;
this.notified = event.notified;
this.addedDate = event.addedDate;
}
public EventFull(Event event, Metadata metadata) {
this(event);

View File

@ -11,11 +11,14 @@ import android.content.Intent
import android.util.Log
import com.google.firebase.iid.zzaq
import com.google.firebase.iid.zzv
import com.google.firebase.messaging.MessagingAnalytics
import com.google.firebase.messaging.zzc
import com.google.gson.JsonObject
import pl.szczodrzynski.edziennik.*
import java.util.*
import com.google.firebase.messaging.zzo.zza as logNotificationOpen
import com.google.firebase.messaging.zzo.zza as logNotificationReceived
import com.google.firebase.messaging.zzo.zzb as logNotificationDismiss
import com.google.firebase.messaging.zzo.zzd as shouldUploadMetrics
@SuppressLint("Registered")
open class FirebaseService : zzc() {
@ -45,8 +48,8 @@ open class FirebaseService : zzc() {
}
}
if (MessagingAnalytics.shouldUploadMetrics(intent)) {
MessagingAnalytics.logNotificationOpen(intent)
if (shouldUploadMetrics(intent)) {
logNotificationOpen(intent)
}
return true
@ -62,8 +65,8 @@ open class FirebaseService : zzc() {
when (action) {
"com.google.firebase.messaging.NOTIFICATION_DISMISS" -> {
if (MessagingAnalytics.shouldUploadMetrics(intent)) {
MessagingAnalytics.logNotificationDismiss(intent)
if (shouldUploadMetrics(intent)) {
logNotificationDismiss(intent)
}
}
"com.google.firebase.messaging.NEW_TOKEN" -> {
@ -106,8 +109,8 @@ open class FirebaseService : zzc() {
// get the message type
when (val it = json.getString("message_type") ?: "gcm") {
"gcm" -> { // 0
if (MessagingAnalytics.shouldUploadMetrics(intent)) {
MessagingAnalytics.logNotificationReceived(intent)
if (shouldUploadMetrics(intent)) {
logNotificationReceived(intent, null)
}
onMessageReceived(Message(messageId, json))

View File

@ -5,36 +5,19 @@
package pl.szczodrzynski.edziennik.data.firebase;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.AsyncTask;
import android.util.Log;
import com.google.firebase.messaging.FirebaseMessagingService;
import com.google.firebase.messaging.RemoteMessage;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import java.util.List;
import pl.szczodrzynski.edziennik.App;
import pl.szczodrzynski.edziennik.BuildConfig;
import pl.szczodrzynski.edziennik.MainActivity;
import pl.szczodrzynski.edziennik.R;
import pl.szczodrzynski.edziennik.data.api.task.EdziennikTask;
import pl.szczodrzynski.edziennik.data.db.entity.Event;
import pl.szczodrzynski.edziennik.data.db.entity.EventType;
import pl.szczodrzynski.edziennik.data.db.entity.FeedbackMessage;
import pl.szczodrzynski.edziennik.data.api.edziennik.EdziennikTask;
import pl.szczodrzynski.edziennik.data.db.entity.LoginStore;
import pl.szczodrzynski.edziennik.data.db.entity.Profile;
import pl.szczodrzynski.edziennik.data.db.entity.Team;
import pl.szczodrzynski.edziennik.data.db.full.EventFull;
import pl.szczodrzynski.edziennik.network.ServerRequest;
import pl.szczodrzynski.edziennik.ui.modules.base.DebugFragment;
import pl.szczodrzynski.edziennik.utils.models.Notification;
import static pl.szczodrzynski.edziennik.App.APP_URL;
import static pl.szczodrzynski.edziennik.data.db.entity.Event.TYPE_HOMEWORK;
import static pl.szczodrzynski.edziennik.utils.Utils.d;
import static pl.szczodrzynski.edziennik.utils.Utils.strToInt;
@ -45,16 +28,16 @@ public class MyFirebaseMessagingService extends FirebaseMessagingService {
public void onNewToken(String s) {
super.onNewToken(s);
Log.d(TAG, "New token: "+s);
/* Log.d(TAG, "New token: "+s);
App app = (App)getApplicationContext();
if (app.config.getSync().getTokenApp() == null || !app.config.getSync().getTokenApp().equals(s)) {
app.config.getSync().setTokenApp(s);
}
}*/
}
@Override
public void onMessageReceived(RemoteMessage remoteMessage) {
App app = ((App) getApplicationContext());
/*App app = ((App) getApplicationContext());
// Not getting messages here? See why this may be: https://goo.gl/39bRNJ
String from = remoteMessage.getFrom();
@ -78,7 +61,7 @@ public class MyFirebaseMessagingService extends FirebaseMessagingService {
processVulcanPush(app, remoteMessage);
break;
}
}
}*/
}
private void processMobidziennikPush(App app, RemoteMessage remoteMessage) {
@ -102,14 +85,7 @@ public class MyFirebaseMessagingService extends FirebaseMessagingService {
if (profile != null) {
if (remoteMessage.getData().get("id_wiadomosci") != null) {
/*app.notifier.add(new Notification(app.getContext(), remoteMessage.getData().get("message"))
.withProfileData(profile.id, profile.name)
.withTitle(remoteMessage.getData().get("title"))
.withType(Notification.TYPE_NEW_MESSAGE)
.withFragmentRedirect(MainActivity.DRAWER_ITEM_MESSAGES)
);
app.notifier.postAll(profile);
app.saveConfig("notifications");*/
d(TAG, "Syncing profile " + profile.getId());
EdziennikTask.Companion.syncProfile(profile.getId(), null, null).enqueue(app);
} else {
@ -141,7 +117,7 @@ public class MyFirebaseMessagingService extends FirebaseMessagingService {
private void processAppPush(App app, RemoteMessage remoteMessage) {
// Check if message contains a data payload.
String type = remoteMessage.getData().get("type");
/*String type = remoteMessage.getData().get("type");
if (remoteMessage.getData().size() > 0
&& type != null) {
//Log.d(TAG, "Message data payload: " + remoteMessage.sync());
@ -239,90 +215,8 @@ public class MyFirebaseMessagingService extends FirebaseMessagingService {
break;
case "ping":
// just a ping
break;
/* ______ _ _
| ____| | | | |
| |____ _____ _ __ | |_ ______ ___| |__ __ _ _ __ ___
| __\ \ / / _ \ '_ \| __| |______| / __| '_ \ / _` | '__/ _ \
| |___\ V / __/ | | | |_ \__ \ | | | (_| | | | __/
|______\_/ \___|_| |_|\__| |___/_| |_|\__,_|_| \__*/
case "event":
case "event_removed":
AsyncTask.execute(() -> {
String teamCode = remoteMessage.getData().get("team");
String teamUnshareCode = remoteMessage.getData().get("team_unshare");
while (teamCode != null || teamUnshareCode != null) {
d(TAG, "Got an event for teamCode " + teamCode + " and teamUnshareCode " + teamUnshareCode);
// get the target Profile by the corresponding teamCode
List<Profile> profiles = app.db.profileDao().getByTeamCodeNowWithRegistration(teamCode == null ? teamUnshareCode : teamCode);
for (Profile profile : profiles) {
d(TAG, "Matched profile " + profile.getName());
if (teamCode != null) {
// SHARING
JsonObject jEvent = new JsonParser().parse(remoteMessage.getData().get("data")).getAsJsonObject();
d(TAG, "An event is there! " + jEvent.toString());
// get the target Team from teamCode
Team team = app.db.teamDao().getByCodeNow(profile.getId(), teamCode);
if (team != null) {
d(TAG, "The target team is " + team.name + ", ID " + team.id);
// create the event from Json. Add the missing teamId and !!profileId!!
Event event = app.gson.fromJson(jEvent.toString(), Event.class);
if (jEvent.get("colorDefault") != null) {
event.color = -1;
}
event.profileId = profile.getId();
event.teamId = team.id;
d(TAG, "Created the event! " + event);
// TODO? i guess
Event oldEvent = app.db.eventDao().getByIdNow(profile.getId(), event.id);
if (event.sharedBy != null && event.sharedBy.equals(profile.getUserCode())) {
d(TAG, "Shared by self! Changing name");
event.sharedBy = "self";
event.sharedByName = profile.getStudentNameLong();
}
d(TAG, "Old event found? " + oldEvent);
EventType eventType = app.db.eventTypeDao().getByIdNow(profile.getId(), event.type);
app.notifier.add(new Notification(app.getContext(), app.getString((oldEvent == null ? R.string.notification_shared_event_format : R.string.notification_shared_event_modified_format), event.sharedByName, eventType == null ? "wydarzenie" : eventType.name, event.eventDate.getFormattedString(), event.topic))
.withProfileData(profile.getId(), profile.getName())
.withType(event.type == TYPE_HOMEWORK ? pl.szczodrzynski.edziennik.data.db.entity.Notification.TYPE_NEW_SHARED_HOMEWORK : pl.szczodrzynski.edziennik.data.db.entity.Notification.TYPE_NEW_SHARED_EVENT)
.withFragmentRedirect(event.type == TYPE_HOMEWORK ? MainActivity.DRAWER_ITEM_HOMEWORK : MainActivity.DRAWER_ITEM_AGENDA)
.withLongExtra("eventDate", event.eventDate.getValue())
);
d(TAG, "Finishing adding event " + event);
app.db.eventDao().add(event);
try {
app.db.metadataDao().setBoth(profile.getId(), event, false, true, jEvent.get("addedDate").getAsLong());
} catch (Exception e) {
e.printStackTrace();
}
}
} else {
// UNSHARING
long eventId = Long.parseLong(remoteMessage.getData().get("remove_id"));
EventFull oldEvent = app.db.eventDao().getByIdNow(profile.getId(), eventId);
if (oldEvent != null) {
app.notifier.add(new Notification(app.getContext(), app.getString(R.string.notification_shared_event_removed_format, oldEvent.sharedByName, oldEvent.typeName, oldEvent.eventDate.getFormattedString(), oldEvent.topic))
.withProfileData(profile.getId(), profile.getName())
.withType(oldEvent.type == TYPE_HOMEWORK ? pl.szczodrzynski.edziennik.data.db.entity.Notification.TYPE_NEW_SHARED_HOMEWORK : pl.szczodrzynski.edziennik.data.db.entity.Notification.TYPE_NEW_SHARED_EVENT)
.withFragmentRedirect(oldEvent.type == TYPE_HOMEWORK ? MainActivity.DRAWER_ITEM_HOMEWORK : MainActivity.DRAWER_ITEM_AGENDA)
.withLongExtra("eventDate", oldEvent.eventDate.getValue())
);
app.db.eventDao().remove(oldEvent);
}
}
}
if (teamCode != null) {
teamCode = null;
} else {
teamUnshareCode = null;
}
}
app.notifier.postAll();
app.saveConfig();
});
break;
break
}
}
}*/
}
}

View File

@ -4,11 +4,129 @@
package pl.szczodrzynski.edziennik.data.firebase
import pl.szczodrzynski.edziennik.App
import com.google.gson.JsonParser
import pl.szczodrzynski.edziennik.*
import pl.szczodrzynski.edziennik.data.api.task.PostNotifications
import pl.szczodrzynski.edziennik.data.db.entity.Event
import pl.szczodrzynski.edziennik.data.db.entity.Metadata
import pl.szczodrzynski.edziennik.data.db.entity.Notification
import pl.szczodrzynski.edziennik.data.db.entity.Profile
import pl.szczodrzynski.edziennik.utils.models.Date
import pl.szczodrzynski.edziennik.utils.models.Time
class SzkolnyAppFirebase(val app: App, val profiles: List<Profile>, val message: FirebaseService.Message) {
init {
run {
val type = message.data.getString("type") ?: return@run
when (type) {
"sharedEvent" -> sharedEvent(
message.data.getString("shareTeamCode") ?: return@run,
message.data.getString("event") ?: return@run,
message.data.getString("message") ?: return@run
)
"unsharedEvent" -> unsharedEvent(
message.data.getString("unshareTeamCode") ?: return@run,
message.data.getLong("eventId") ?: return@run,
message.data.getString("message") ?: return@run
)
}
}
}
}
private fun sharedEvent(teamCode: String, jsonStr: String, message: String) {
val json = JsonParser().parse(jsonStr).asJsonObject
val teams = app.db.teamDao().allNow
val eventTypes = app.db.eventTypeDao().allNow
val events = mutableListOf<Event>()
val metadataList = mutableListOf<Metadata>()
val notificationList = mutableListOf<Notification>()
teams.filter { it.code == teamCode }.distinctBy { it.profileId }.forEach { team ->
val profile = profiles.firstOrNull { it.id == team.profileId }
val event = Event(
team.profileId,
json.getLong("id") ?: return,
json.getInt("eventDate")?.let { Date.fromValue(it) } ?: return,
json.getInt("startTime")?.let { Time.fromValue(it) },
json.getString("topic") ?: "",
json.getInt("color") ?: -1,
json.getLong("type") ?: 0,
true,
json.getLong("teacherId") ?: -1,
json.getLong("subjectId") ?: -1,
team.id
)
// TODO? i guess - this comment is here for like a year
//val oldEvent: Event? = app.db.eventDao().getByIdNow(profile?.id ?: -1, event.id)
event.sharedBy = json.getString("sharedBy")
event.sharedByName = json.getString("sharedByName")
if (profile?.userCode == event.sharedBy) event.sharedBy = "self"
val metadata = Metadata(
event.profileId,
if (event.type == Event.TYPE_HOMEWORK) Metadata.TYPE_HOMEWORK else Metadata.TYPE_EVENT,
event.id,
false,
true,
json.getLong("addedDate") ?: System.currentTimeMillis()
)
//val eventType = eventTypes.firstOrNull { it.profileId == profile?.id && it.id == event.type }
/*val text = app.getString(
if (oldEvent == null)
R.string.notification_shared_event_format
else
R.string.notification_shared_event_modified_format,
event.sharedByName,
eventType?.name ?: "wydarzenie",
event.eventDate.formattedString,
event.topic
)*/
val type = if (event.type == Event.TYPE_HOMEWORK) Notification.TYPE_NEW_SHARED_HOMEWORK else Notification.TYPE_NEW_SHARED_EVENT
val notification = Notification(
id = Notification.buildId(event.profileId, type, event.id),
title = app.getNotificationTitle(type),
text = message,
type = type,
profileId = profile?.id,
profileName = profile?.name,
viewId = if (event.type == Event.TYPE_HOMEWORK) MainActivity.DRAWER_ITEM_HOMEWORK else MainActivity.DRAWER_ITEM_AGENDA,
addedDate = metadata.addedDate
).addExtra("eventId", event.id).addExtra("eventDate", event.eventDate.value.toLong())
events += event
metadataList += metadata
notificationList += notification
}
app.db.eventDao().addAll(events)
app.db.metadataDao().addAllReplace(metadataList)
app.db.notificationDao().addAll(notificationList)
PostNotifications(app, notificationList)
}
private fun unsharedEvent(teamCode: String, eventId: Long, message: String) {
val teams = app.db.teamDao().allNow
val notificationList = mutableListOf<Notification>()
teams.filter { it.code == teamCode }.distinctBy { it.profileId }.forEach { team ->
val profile = profiles.firstOrNull { it.id == team.profileId }
val notification = Notification(
id = Notification.buildId(profile?.id ?: 0, Notification.TYPE_REMOVED_SHARED_EVENT, eventId),
title = app.getNotificationTitle(Notification.TYPE_REMOVED_SHARED_EVENT),
text = message,
type = Notification.TYPE_REMOVED_SHARED_EVENT,
profileId = profile?.id,
profileName = profile?.name,
viewId = MainActivity.DRAWER_ITEM_AGENDA
)
notificationList += notification
app.db.eventDao().remove(team.profileId, eventId)
}
app.db.notificationDao().addAll(notificationList)
PostNotifications(app, notificationList)
}
}

View File

@ -5,7 +5,7 @@
package pl.szczodrzynski.edziennik.data.firebase
import pl.szczodrzynski.edziennik.App
import pl.szczodrzynski.edziennik.data.api.task.EdziennikTask
import pl.szczodrzynski.edziennik.data.api.edziennik.EdziennikTask
import pl.szczodrzynski.edziennik.data.api.task.IApiTask
import pl.szczodrzynski.edziennik.data.db.entity.Profile
import pl.szczodrzynski.edziennik.getString

View File

@ -9,7 +9,7 @@ import pl.szczodrzynski.edziennik.MainActivity.Companion.DRAWER_ITEM_ATTENDANCE
import pl.szczodrzynski.edziennik.MainActivity.Companion.DRAWER_ITEM_BEHAVIOUR
import pl.szczodrzynski.edziennik.MainActivity.Companion.DRAWER_ITEM_GRADES
import pl.szczodrzynski.edziennik.MainActivity.Companion.DRAWER_ITEM_MESSAGES
import pl.szczodrzynski.edziennik.data.api.task.EdziennikTask
import pl.szczodrzynski.edziennik.data.api.edziennik.EdziennikTask
import pl.szczodrzynski.edziennik.data.api.task.IApiTask
import pl.szczodrzynski.edziennik.data.db.entity.Message.TYPE_RECEIVED
import pl.szczodrzynski.edziennik.data.db.entity.Profile

View File

@ -22,7 +22,7 @@ public class NetworkUtils {
public boolean isOnline() {
assert app != null;
ConnectivityManager cm =
(ConnectivityManager) app.getContext().getSystemService(Context.CONNECTIVITY_SERVICE);
(ConnectivityManager) app.getSystemService(Context.CONNECTIVITY_SERVICE);
assert cm != null;
NetworkInfo netInfo = cm.getActiveNetworkInfo();
return netInfo != null && netInfo.isConnectedOrConnecting();
@ -30,7 +30,7 @@ public class NetworkUtils {
public int checkBackgroundDataRestricted() {
ConnectivityManager connMgr = (ConnectivityManager) app.getContext().getSystemService(Context.CONNECTIVITY_SERVICE);
ConnectivityManager connMgr = (ConnectivityManager) app.getSystemService(Context.CONNECTIVITY_SERVICE);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
assert connMgr != null;

View File

@ -29,7 +29,7 @@ public class ServerRequest {
private String source = "";
public ServerRequest(App app, String url, String source) {
this(app, url, source, app.profile);
this(app, url, source, App.Companion.getProfile());
}
public ServerRequest(App app, String url, String source, Profile profileFull) {
@ -40,7 +40,7 @@ public class ServerRequest {
this.app = app;
this.url = url;
this.params = new ArrayList<>();
this.username = (profile != null && profile.getRegistration() == REGISTRATION_ENABLED ? usernameId : app.deviceId);
this.username = (profile != null && profile.getRegistration() == REGISTRATION_ENABLED ? usernameId : app.getDeviceId());
this.source = source;
if (profile != null && profile.getRegistration() == REGISTRATION_ENABLED) {
this.setBodyParameter("login_type", Integer.toString(loginStoreType));
@ -51,7 +51,7 @@ public class ServerRequest {
this.setBodyParameter("team_ids", "UI_THREAD");
}
else {
this.setBodyParameter("team_ids", app.gson.toJson(app.db.teamDao().getAllCodesNow(profile.getId())));
this.setBodyParameter("team_ids", app.getGson().toJson(App.db.teamDao().getAllCodesNow(profile.getId())));
}
}
}
@ -86,15 +86,15 @@ public class ServerRequest {
.addParameter("app_version_build_type", BuildConfig.BUILD_TYPE)
.addParameter("app_version_code", Integer.toString(BuildConfig.VERSION_CODE))
.addParameter("app_version", BuildConfig.VERSION_NAME + " " + BuildConfig.BUILD_TYPE + " (" + BuildConfig.VERSION_CODE + ")")
.addParameter("device_id", Settings.Secure.getString(app.getContext().getContentResolver(), Settings.Secure.ANDROID_ID))
.addParameter("device_id", Settings.Secure.getString(app.getContentResolver(), Settings.Secure.ANDROID_ID))
.addParameter("device_model", Build.MANUFACTURER+" "+Build.MODEL)
.addParameter("device_os_version", Build.VERSION.RELEASE)
.addParameter("fcm_token", app.config.getSync().getTokenApp())
.addParameter("signature", sign(app.signature, timestamp))
.addParameter("fcm_token", app.getConfig().getSync().getTokenApp())
.addParameter("signature", "TODO")
.addParameter("signature_timestamp", timestamp)
.addParameter("package_name", "pl.szczodrzynski.edziennik")
.addParameter("source", source)
.addParameter("update_frequency", app.config.getSync().getEnabled() ? app.config.getSync().getInterval() : -1)
.addParameter("update_frequency", app.getConfig().getSync().getEnabled() ? app.getConfig().getSync().getInterval() : -1)
.post()
.callback(new JsonCallbackHandler() {
@Override
@ -123,15 +123,15 @@ public class ServerRequest {
.addParameter("app_version_build_type", BuildConfig.BUILD_TYPE)
.addParameter("app_version_code", Integer.toString(BuildConfig.VERSION_CODE))
.addParameter("app_version", BuildConfig.VERSION_NAME + " " + BuildConfig.BUILD_TYPE + " (" + BuildConfig.VERSION_CODE + ")")
.addParameter("device_id", Settings.Secure.getString(app.getContext().getContentResolver(), Settings.Secure.ANDROID_ID))
.addParameter("device_id", Settings.Secure.getString(app.getContentResolver(), Settings.Secure.ANDROID_ID))
.addParameter("device_model", Build.MANUFACTURER+" "+Build.MODEL)
.addParameter("device_os_version", Build.VERSION.RELEASE)
.addParameter("fcm_token", app.config.getSync().getTokenApp())
.addParameter("signature", sign(app.signature, timestamp))
.addParameter("fcm_token", app.getConfig().getSync().getTokenApp())
.addParameter("signature", "TODO")
.addParameter("signature_timestamp", timestamp)
.addParameter("package_name", "pl.szczodrzynski.edziennik")
.addParameter("source", source)
.addParameter("update_frequency", app.config.getSync().getEnabled() ? app.config.getSync().getInterval() : -1)
.addParameter("update_frequency", app.getConfig().getSync().getEnabled() ? app.getConfig().getSync().getInterval() : -1)
.post()
.build()
.execute();

View File

@ -1,287 +0,0 @@
package pl.szczodrzynski.edziennik.receivers;
import android.app.AlarmManager;
import android.app.DownloadManager;
import android.app.IntentService;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.net.Uri;
import android.os.Build;
import android.widget.Toast;
import androidx.core.content.FileProvider;
import java.io.File;
import java.util.List;
import pl.szczodrzynski.edziennik.App;
import pl.szczodrzynski.edziennik.R;
import pl.szczodrzynski.edziennik.network.ServerRequest;
import pl.szczodrzynski.edziennik.sync.SyncWorker;
import pl.szczodrzynski.edziennik.utils.Utils;
import static android.content.Context.DOWNLOAD_SERVICE;
import static pl.szczodrzynski.edziennik.App.APP_URL;
import static pl.szczodrzynski.edziennik.App.UPDATES_ON_PLAY_STORE;
public class BootReceiver extends BroadcastReceiver {
private static final int NO_INTERNET_RETRY_TIMEOUT = 60;
private static boolean alarmSet = false;
public static long update_download_id;
public static String update_url;
public static String update_filename;
private static final String TAG = "receivers.BootReceiver";
private App app;
@Override
public void onReceive(final Context context, final Intent intent) {
app = (App)context.getApplicationContext();
if (intent.getBooleanExtra("ExecutedByAlarm", false))
{
alarmSet = false;
}
if (intent.getAction() != null && intent.getAction().equals(DownloadManager.ACTION_DOWNLOAD_COMPLETE))
{
long referenceId = intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, -1);
if(referenceId == update_download_id) {
Intent downloadIntent;
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
if (!app.permissionChecker.canRequestApkInstall()) {
app.permissionChecker.requestApkInstall();
return;
}
}
File fileLocation = new File(app.getContext().getExternalFilesDir(null), update_filename);
Uri apkUri = FileProvider.getUriForFile(app.getContext(), app.getContext().getApplicationContext().getPackageName() + ".provider", fileLocation);
downloadIntent = new Intent(Intent.ACTION_VIEW);
downloadIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
downloadIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
downloadIntent.setDataAndType(apkUri, "application/vnd.android.package-archive");
List<ResolveInfo> resInfoList = app.getContext().getPackageManager().queryIntentActivities(downloadIntent, PackageManager.MATCH_DEFAULT_ONLY);
for (ResolveInfo resolveInfo : resInfoList) {
String packageName = resolveInfo.activityInfo.packageName;
app.getContext().grantUriPermission(packageName, apkUri, Intent.FLAG_GRANT_WRITE_URI_PERMISSION | Intent.FLAG_GRANT_READ_URI_PERMISSION);
}
} else {
File fileLocation = new File(app.getContext().getExternalFilesDir(null), update_filename);
downloadIntent = new Intent(Intent.ACTION_VIEW);
downloadIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
downloadIntent.setDataAndType(Uri.fromFile(fileLocation), "application/vnd.android.package-archive");
}
app.getContext().startActivity(downloadIntent);
}
}
else
{
SyncWorker.Companion.scheduleNext(app, false);
if (app.networkUtils.isOnline())
{
checkUpdate(context, intent);
}
else
{
//Toast.makeText(context, "No internet, retrying in "+NO_INTERNET_RETRY_TIMEOUT+" seconds", Toast.LENGTH_SHORT).show();
scheduleUpdateCheck(context, NO_INTERNET_RETRY_TIMEOUT);
}
}
}
private boolean scheduleUpdateCheck(Context context, int secondsLater)
{
Intent alarmIntent = new Intent(context, BootReceiver.class);
alarmIntent.putExtra("ExecutedByAlarm", true);
PendingIntent alarmPendingIntent = PendingIntent.getBroadcast(context, 234324243, alarmIntent, 0);
AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
if (alarmManager != null) {
alarmManager.set(AlarmManager.RTC_WAKEUP, System.currentTimeMillis() + (secondsLater * 1000), alarmPendingIntent);
return true;
}
return false;
}
private void checkUpdate(final Context context, final Intent intent)
{
if (!alarmSet) {
//Toast.makeText(context, "Update scheduled for 48 hours since now", Toast.LENGTH_SHORT).show();
alarmSet = scheduleUpdateCheck(context, 48 * 60 * 60);
}
//app.networkUtils.setSelfSignedSSL(app.getContext(), null);
new ServerRequest(app, app.requestScheme + APP_URL + "main.php?get_update", "BootReceiver/UPD")
.run(((e, result) -> {
if (result != null) {
if (result.get("update_available").getAsBoolean()) {
String updateVersion = result.get("update_version").getAsString();
String updateUrl = result.get("update_url").getAsString();
String updateFilename = result.get("update_filename").getAsString();
boolean updateMandatory = result.get("update_mandatory").getAsBoolean();
boolean updateDirect = result.get("update_direct").getAsBoolean();
if (app.appConfig.updateVersion == null || !app.appConfig.updateVersion.equals(updateVersion)) {
app.appConfig.updateVersion = updateVersion;
app.appConfig.updateUrl = updateUrl;
app.appConfig.updateFilename = updateFilename;
app.appConfig.updateMandatory = updateMandatory;
app.appConfig.updateDirect = updateDirect;
app.saveConfig();
}
if (!UPDATES_ON_PLAY_STORE || intent.getBooleanExtra("UserChecked", false)) {
app.notifier.notificationUpdatesShow(
updateVersion,
updateUrl,
updateFilename,
updateDirect);
}
} else {
if (app.appConfig.updateVersion == null || !app.appConfig.updateVersion.equals("")) {
app.appConfig.updateVersion = "";
app.appConfig.updateMandatory = false;
app.saveConfig();
}
app.notifier.notificationUpdatesHide();
if (intent.getBooleanExtra("UserChecked", false)) {
Toast.makeText(context, context.getString(R.string.notification_no_update), Toast.LENGTH_LONG).show();
}
}
}
else
{
//Toast.makeText(context, "Server returned nothing, retrying in "+NO_INTERNET_RETRY_TIMEOUT+" seconds", Toast.LENGTH_SHORT).show();
scheduleUpdateCheck(context, NO_INTERNET_RETRY_TIMEOUT);
}
}));
/*Ion.with(app.getContext())
.load(app.requestScheme + APP_URL + "main.php?get_update")
.setBodyParameter("username", (app.profile.autoRegistrationAllowed ? app.profile.registrationUsername : app.appConfig.deviceId))
.setBodyParameter("app_version_build_type", BuildConfig.BUILD_TYPE)
.setBodyParameter("app_version_code", Integer.toString(BuildConfig.VERSION_CODE))
.setBodyParameter("app_version", BuildConfig.VERSION_NAME + " " + BuildConfig.BUILD_TYPE + " (" + BuildConfig.VERSION_CODE + ")")
.setBodyParameter("device_id", Settings.Secure.getString(app.getContext().getContentResolver(), Settings.Secure.ANDROID_ID))
.setBodyParameter("device_model", Build.MANUFACTURER+" "+Build.MODEL)
.setBodyParameter("device_os_version", Build.VERSION.RELEASE)
.setBodyParameter("fcm_token", app.appConfig.fcmToken)
.asJsonObject()
.setCallback((e, result) -> {
// do stuff with the result or error
if (result != null) {
if (result.get("update_available").getAsBoolean()) {
String updateVersion = result.get("update_version").getAsString();
String updateUrl = result.get("update_url").getAsString();
String updateFilename = result.get("update_filename").getAsString();
if (app.appConfig.updateVersion == null || !app.appConfig.updateVersion.equals(updateVersion)) {
app.appConfig.updateVersion = updateVersion;
app.appConfig.updateUrl = updateUrl;
app.appConfig.updateFilename = updateFilename;
app.saveConfig();
}
app.notifier.notificationUpdatesShow(
updateVersion,
updateUrl,
updateFilename);
} else {
if (app.appConfig.updateVersion == null || !app.appConfig.updateVersion.equals("")) {
app.appConfig.updateVersion = "";
app.saveConfig();
}
app.notifier.notificationUpdatesHide();
if (intent.getBooleanExtra("UserChecked", false)) {
Toast.makeText(context, context.getString(R.string.notification_no_update), Toast.LENGTH_LONG).show();
}
}
}
else
{
//Toast.makeText(context, "Server returned nothing, retrying in "+NO_INTERNET_RETRY_TIMEOUT+" seconds", Toast.LENGTH_SHORT).show();
scheduleUpdateCheck(context, NO_INTERNET_RETRY_TIMEOUT);
}
});*/
}
private static DownloadManager downloadManager;
public static long downloadFile(Context context) {
Toast.makeText(context, R.string.downloading, Toast.LENGTH_SHORT).show();
File dir = new File(context.getExternalFilesDir(null)+""/*, update_filename*/);
/*if (existingFile.exists())
{
existingFile.delete();
}*/
if (dir.isDirectory())
{
String[] children = dir.list();
for (int i = 0; i < children.length; i++)
{
new File(dir, children[i]).delete();
}
}
Uri uri = Uri.parse(update_url);
long downloadReference;
// Create request for android download manager
downloadManager = (DownloadManager)context.getSystemService(DOWNLOAD_SERVICE);
DownloadManager.Request request = new DownloadManager.Request(uri);
//Setting title of request
request.setTitle(context.getString(R.string.app_name));
//Setting description of request
request.setDescription(context.getString(R.string.notification_downloading_update));
//Set the local destination for the downloaded file to a path within the application's external files directory
try {
request.setDestinationInExternalFilesDir(context, null, update_filename);
}
catch (java.lang.IllegalStateException e)
{
e.printStackTrace();
Toast.makeText(context, "Failed to get external storage files directory", Toast.LENGTH_SHORT).show();
}
//Enqueue download and save into referenceId
downloadReference = downloadManager.enqueue(request);
return downloadReference;
}
public static class NotificationActionService extends IntentService {
private static final String TAG = "BootReceiver/NAS";
public NotificationActionService() {
super(NotificationActionService.class.getSimpleName());
//IntentFilter filter = new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE);
//registerReceiver(downloadReceiver, filter);
}
@Override
protected void onHandleIntent(Intent intent) {
if (UPDATES_ON_PLAY_STORE && !intent.getBooleanExtra("update_direct", false)) {
Utils.openGooglePlay(this, "pl.szczodrzynski.edziennik");
return;
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
App app = (App)getApplication();
if (!app.permissionChecker.canRequestApkInstall()) {
app.permissionChecker.requestApkInstall();
app.notifier.notificationUpdatesShow(
intent.getStringExtra("update_version"),
intent.getStringExtra("update_url"),
intent.getStringExtra("update_filename"),
intent.getBooleanExtra("update_direct", false));
return;
}
}
update_url = intent.getStringExtra("update_url");
update_filename = intent.getStringExtra("update_filename");
//update_filename = "Edziennik_update.apk";
update_download_id = downloadFile(this);
}
}
}

View File

@ -8,9 +8,9 @@ import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import pl.szczodrzynski.edziennik.data.api.ApiService
import pl.szczodrzynski.edziennik.data.api.edziennik.EdziennikTask
import pl.szczodrzynski.edziennik.data.api.events.requests.ServiceCloseRequest
import pl.szczodrzynski.edziennik.data.api.events.requests.TaskCancelRequest
import pl.szczodrzynski.edziennik.data.api.task.EdziennikTask
class SzkolnyReceiver : BroadcastReceiver() {
companion object {

View File

@ -2,13 +2,9 @@ package pl.szczodrzynski.edziennik.sync
import android.annotation.SuppressLint
import android.content.Context
import android.os.AsyncTask
import androidx.work.*
import androidx.work.impl.WorkManagerImpl
import org.greenrobot.eventbus.EventBus
import pl.szczodrzynski.edziennik.App
import pl.szczodrzynski.edziennik.MINUTE
import pl.szczodrzynski.edziennik.data.api.task.EdziennikTask
import pl.szczodrzynski.edziennik.data.api.edziennik.EdziennikTask
import pl.szczodrzynski.edziennik.formatDate
import pl.szczodrzynski.edziennik.utils.Utils.d
import java.util.concurrent.TimeUnit
@ -22,35 +18,8 @@ class SyncWorker(val context: Context, val params: WorkerParameters) : Worker(co
*/
@SuppressLint("RestrictedApi")
fun scheduleNext(app: App, rescheduleIfFailedFound: Boolean = true) {
AsyncTask.execute {
val workManager = WorkManager.getInstance(app) as WorkManagerImpl
val scheduledWork = workManager.workDatabase.workSpecDao().scheduledWork
scheduledWork.forEach {
d(TAG, "Work: ${it.id} at ${(it.periodStartTime+it.initialDelay).formatDate()}. State = ${it.state} (finished = ${it.state.isFinished})")
}
// remove finished work and other than SyncWorker
scheduledWork.removeAll { it.workerClassName != SyncWorker::class.java.canonicalName || it.isPeriodic || it.state.isFinished }
d(TAG, "Found ${scheduledWork.size} unfinished work")
// remove all enqueued work that had to (but didn't) run at some point in the past (at least 1min ago)
val failedWork = scheduledWork.filter { it.state == WorkInfo.State.ENQUEUED && it.periodStartTime+it.initialDelay < System.currentTimeMillis() - 1*MINUTE*1000 }
d(TAG, "${failedWork.size} work requests failed to start (out of ${scheduledWork.size} requests)")
if (rescheduleIfFailedFound) {
if (failedWork.isNotEmpty()) {
d(TAG, "App Manager detected!")
EventBus.getDefault().postSticky(AppManagerDetectedEvent(failedWork.map { it.periodStartTime + it.initialDelay }))
}
if (scheduledWork.size - failedWork.size < 1) {
d(TAG, "No pending work found, scheduling next:")
rescheduleNext(app)
}
}
else {
d(TAG, "NOT rescheduling: waiting to open the activity")
if (scheduledWork.size < 1) {
d(TAG, "No work found *at all*, scheduling next:")
rescheduleNext(app)
}
}
WorkerUtils.scheduleNext(app, rescheduleIfFailedFound) {
rescheduleNext(app)
}
}

View File

@ -0,0 +1,120 @@
/*
* Copyright (c) Kuba Szczodrzyński 2020-1-18.
*/
package pl.szczodrzynski.edziennik.sync
import android.app.DownloadManager
import android.app.IntentService
import android.app.NotificationManager
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.net.Uri
import android.os.Build
import android.widget.Toast
import androidx.core.content.FileProvider
import pl.szczodrzynski.edziennik.App
import pl.szczodrzynski.edziennik.R
import pl.szczodrzynski.edziennik.data.api.szkolny.response.Update
import pl.szczodrzynski.edziennik.utils.Utils
import java.io.File
class UpdateDownloaderService : IntentService(UpdateDownloaderService::class.java.simpleName) {
companion object {
private const val TAG = "UpdateDownloaderService"
private var downloadId = 0L
private var downloadFilename = ""
}
private fun tryUpdateWithGooglePlay(update: Update): Boolean {
if (!update.isOnGooglePlay)
return false
return try {
Utils.openGooglePlay(this, application.packageName)
true
}
catch (e: Exception) {
e.printStackTrace()
false
}
}
class DownloadProgressReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent?) {
if (intent?.action != DownloadManager.ACTION_DOWNLOAD_COMPLETE)
return
if (intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, -1) != downloadId)
return
val app = context.applicationContext as App
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && !app.permissionChecker.canRequestApkInstall()) {
app.permissionChecker.requestApkInstall()
return
}
val file = File(app.getExternalFilesDir(null), downloadFilename)
val installIntent = Intent(Intent.ACTION_VIEW)
installIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
installIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
installIntent.setDataAndType(Uri.fromFile(file), "application/vnd.android.package-archive")
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
val apkUri = FileProvider.getUriForFile(app, "${app.packageName}.provider", file)
installIntent.setDataAndType(apkUri, "application/vnd.android.package-archive")
val resInfoList = app.packageManager.queryIntentActivities(installIntent, PackageManager.MATCH_DEFAULT_ONLY)
for (resolveInfo in resInfoList) {
val packageName = resolveInfo.activityInfo.packageName
app.grantUriPermission(packageName, apkUri, Intent.FLAG_GRANT_WRITE_URI_PERMISSION or Intent.FLAG_GRANT_READ_URI_PERMISSION)
}
}
app.startActivity(installIntent)
}
}
override fun onHandleIntent(intent: Intent?) {
val app = application as App
val update = App.config.update ?: return
if (tryUpdateWithGooglePlay(update))
return
if (update.downloadUrl == null) {
Toast.makeText(app, "Nie można pobrać tej aktualizacji. Pobierz ręcznie z Google Play.", Toast.LENGTH_LONG).show()
return
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && !app.permissionChecker.canRequestApkInstall()) {
app.permissionChecker.requestApkInstall()
return
}
(app.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager).cancel(app.notifications.updatesId)
val dir: File? = app.getExternalFilesDir(null)
if (dir?.isDirectory == true) {
dir.listFiles()?.forEach {
it.delete()
}
}
val uri = Uri.parse(update.downloadUrl)
downloadFilename = "${update.versionName}.apk"
val downloadManager = app.getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager
val request = DownloadManager.Request(uri)
request.setTitle(app.getString(R.string.app_name)+" "+update.versionName)
request.setDescription(app.getString(R.string.notification_downloading_update))
try {
request.setDestinationInExternalFilesDir(app, null, downloadFilename)
} catch (e: IllegalStateException) {
e.printStackTrace()
Toast.makeText(app, "Nie można znaleźć katalogu docelowego. Pobierz aktualizację ręcznie z Google Play.", Toast.LENGTH_LONG).show()
return
}
downloadId = downloadManager.enqueue(request)
}
}

View File

@ -0,0 +1,133 @@
/*
* Copyright (c) Kuba Szczodrzyński 2020-1-18.
*/
package pl.szczodrzynski.edziennik.sync
import android.annotation.SuppressLint
import android.app.NotificationManager
import android.app.PendingIntent
import android.content.Context
import android.content.Intent
import android.text.Html
import androidx.core.app.NotificationCompat
import androidx.work.*
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
import pl.szczodrzynski.edziennik.*
import pl.szczodrzynski.edziennik.R
import pl.szczodrzynski.edziennik.data.api.szkolny.SzkolnyApi
import pl.szczodrzynski.edziennik.utils.Utils
import java.util.concurrent.TimeUnit
import kotlin.coroutines.CoroutineContext
class UpdateWorker(val context: Context, val params: WorkerParameters) : Worker(context, params), CoroutineScope {
companion object {
const val TAG = "UpdateWorker"
/**
* Schedule the sync job only if it's not already scheduled.
*/
@SuppressLint("RestrictedApi")
fun scheduleNext(app: App, rescheduleIfFailedFound: Boolean = true) {
WorkerUtils.scheduleNext(app, rescheduleIfFailedFound) {
rescheduleNext(app)
}
}
/**
* Cancel any existing sync jobs and schedule a new one.
*
* If [ConfigSync.enabled] is not true, just cancel every job.
*/
fun rescheduleNext(app: App) {
cancelNext(app)
if (!app.config.sync.notifyAboutUpdates) {
return
}
val syncInterval = 4 * DAY;
val syncAt = System.currentTimeMillis() + syncInterval*1000
Utils.d(TAG, "Scheduling work at ${syncAt.formatDate()}")
val constraints = Constraints.Builder()
.setRequiredNetworkType(NetworkType.CONNECTED)
.build()
val syncWorkRequest = OneTimeWorkRequestBuilder<UpdateWorker>()
.setInitialDelay(syncInterval, TimeUnit.SECONDS)
.setConstraints(constraints)
.addTag(TAG)
.build()
WorkManager.getInstance(app).enqueue(syncWorkRequest)
}
/**
* Cancel any scheduled sync job.
*/
fun cancelNext(app: App) {
Utils.d(TAG, "Cancelling work by tag $TAG")
WorkManager.getInstance(app).cancelAllWorkByTag(TAG)
}
fun runNow(app: App) {
try {
val api = SzkolnyApi(app)
val response = api.getUpdate()
if (response?.success != true)
return
val updates = response.data
if (updates?.isNotEmpty() != true)
return
val update = updates[0]
app.config.update = update
val notificationIntent = Intent(app, UpdateDownloaderService::class.java)
val pendingIntent = PendingIntent.getService(app, 0, notificationIntent, PendingIntent.FLAG_UPDATE_CURRENT)
val notification = NotificationCompat.Builder(app, app.notifications.updatesKey)
.setContentTitle(app.getString(R.string.notification_updates_title))
.setContentText(app.getString(R.string.notification_updates_text, update.versionName))
.setTicker(app.getString(R.string.notification_updates_summary))
.setSmallIcon(R.drawable.ic_notification)
.setStyle(NotificationCompat.BigTextStyle()
.bigText(listOf(
app.getString(R.string.notification_updates_text, update.versionName),
update.releaseNotes?.let { Html.fromHtml(it) }
).concat("\n")))
.setColor(0xff2196f3.toInt())
.setLights(0xFF00FFFF.toInt(), 2000, 2000)
.setPriority(NotificationCompat.PRIORITY_HIGH)
.setDefaults(NotificationCompat.DEFAULT_ALL)
.setGroup(app.notifications.updatesKey)
.setContentIntent(pendingIntent)
.setAutoCancel(false)
.build()
(app.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager).notify(app.notifications.updatesId, notification)
} catch (ignore: Exception) { }
}
}
private val job = Job()
override val coroutineContext: CoroutineContext
get() = job + Dispatchers.Default
override fun doWork(): Result {
Utils.d(TAG, "Running worker ID ${params.id}")
val app = context as App
if (!app.config.sync.notifyAboutUpdates) {
return Result.success()
}
launch {
runNow(app)
}
rescheduleNext(this.context)
return Result.success()
}
}

View File

@ -0,0 +1,54 @@
/*
* Copyright (c) Kuba Szczodrzyński 2020-1-18.
*/
package pl.szczodrzynski.edziennik.sync
import android.annotation.SuppressLint
import android.os.AsyncTask
import androidx.work.WorkInfo
import androidx.work.WorkManager
import androidx.work.impl.WorkManagerImpl
import org.greenrobot.eventbus.EventBus
import pl.szczodrzynski.edziennik.App
import pl.szczodrzynski.edziennik.MINUTE
import pl.szczodrzynski.edziennik.formatDate
import pl.szczodrzynski.edziennik.utils.Utils
object WorkerUtils {
/**
* Schedule the sync job only if it's not already scheduled.
*/
@SuppressLint("RestrictedApi")
inline fun scheduleNext(app: App, rescheduleIfFailedFound: Boolean = true, crossinline onReschedule: () -> Unit) {
AsyncTask.execute {
val workManager = WorkManager.getInstance(app) as WorkManagerImpl
val scheduledWork = workManager.workDatabase.workSpecDao().scheduledWork
scheduledWork.forEach {
Utils.d("WorkerUtils", "Work: ${it.id} at ${(it.periodStartTime + it.initialDelay).formatDate()}. State = ${it.state} (finished = ${it.state.isFinished})")
}
// remove finished work and other than SyncWorker
scheduledWork.removeAll { it.workerClassName != SyncWorker::class.java.canonicalName || it.isPeriodic || it.state.isFinished }
Utils.d("WorkerUtils", "Found ${scheduledWork.size} unfinished work")
// remove all enqueued work that had to (but didn't) run at some point in the past (at least 1min ago)
val failedWork = scheduledWork.filter { it.state == WorkInfo.State.ENQUEUED && it.periodStartTime + it.initialDelay < System.currentTimeMillis() - 1 * MINUTE * 1000 }
Utils.d("WorkerUtils", "${failedWork.size} work requests failed to start (out of ${scheduledWork.size} requests)")
if (rescheduleIfFailedFound) {
if (failedWork.isNotEmpty()) {
Utils.d("WorkerUtils", "App Manager detected!")
EventBus.getDefault().postSticky(AppManagerDetectedEvent(failedWork.map { it.periodStartTime + it.initialDelay }))
}
if (scheduledWork.size - failedWork.size < 1) {
Utils.d("WorkerUtils", "No pending work found, scheduling next:")
onReschedule()
}
} else {
Utils.d("WorkerUtils", "NOT rescheduling: waiting to open the activity")
if (scheduledWork.size < 1) {
Utils.d("WorkerUtils", "No work found *at all*, scheduling next:")
onReschedule()
}
}
}
}
}

View File

@ -44,7 +44,7 @@ class ChangelogDialog(
val text = app.assets.open("pl-changelog.html").bufferedReader().use {
it.readText()
}
}.replace("<li>", "<br><li> - ")
textView.text = Html.fromHtml(text)
val scrollView = ScrollView(activity)

View File

@ -21,13 +21,8 @@ import kotlinx.coroutines.*
import pl.szczodrzynski.edziennik.*
import pl.szczodrzynski.edziennik.MainActivity.Companion.DRAWER_ITEM_AGENDA
import pl.szczodrzynski.edziennik.data.api.szkolny.SzkolnyApi
import pl.szczodrzynski.edziennik.data.db.entity.Event
import pl.szczodrzynski.edziennik.data.db.entity.*
import pl.szczodrzynski.edziennik.data.db.full.EventFull
import pl.szczodrzynski.edziennik.data.db.entity.EventType
import pl.szczodrzynski.edziennik.data.db.entity.Metadata
import pl.szczodrzynski.edziennik.data.db.entity.Subject
import pl.szczodrzynski.edziennik.data.db.entity.Team
import pl.szczodrzynski.edziennik.data.db.entity.Lesson
import pl.szczodrzynski.edziennik.data.db.full.LessonFull
import pl.szczodrzynski.edziennik.databinding.DialogEventManualV2Binding
import pl.szczodrzynski.edziennik.utils.Anim
@ -43,7 +38,7 @@ class EventManualDialog(
val defaultLesson: LessonFull? = null,
val defaultDate: Date? = null,
val defaultTime: Time? = null,
val defaultType: Int? = null,
val defaultType: Long? = null,
val editingEvent: EventFull? = null,
val onShowListener: ((tag: String) -> Unit)? = null,
val onDismissListener: ((tag: String) -> Unit)? = null
@ -149,7 +144,7 @@ class EventManualDialog(
else -> R.string.dialog_event_manual_share_first_notice
}
b.shareDetails.setText(text)
b.shareDetails.setText(text, event.sharedByName ?: "")
}
private fun loadLists() { launch {
@ -588,8 +583,7 @@ class EventManualDialog(
startTime,
topic,
customColor ?: -1,
type?.toInt()
?: Event.TYPE_DEFAULT,
type ?: Event.TYPE_DEFAULT,
true,
teacherId ?: -1,
subjectId ?: -1,
@ -598,7 +592,7 @@ class EventManualDialog(
val metadataObject = Metadata(
profileId,
when (type?.toInt()) {
when (type) {
Event.TYPE_HOMEWORK -> Metadata.TYPE_HOMEWORK
else -> Metadata.TYPE_EVENT
},

View File

@ -32,7 +32,7 @@ public class GradeDetailsDialog {
public GradeDetailsDialog(Context context) {
this.context = context;
this.profileId = App.profileId;
this.profileId = App.Companion.getProfileId();
}
public GradeDetailsDialog(Context context, int profileId) {
this.context = context;
@ -74,7 +74,7 @@ public class GradeDetailsDialog {
b.setGrade(grade);
int gradeColor;
if (App.getConfig().getFor(profileId).getGrades().getColorMode() == COLOR_MODE_DEFAULT) {
if (App.Companion.getConfig().getFor(profileId).getGrades().getColorMode() == COLOR_MODE_DEFAULT) {
gradeColor = grade.color;
}
else {
@ -103,7 +103,7 @@ public class GradeDetailsDialog {
b.setCommentVisible(false);
b.setDevMode(App.devMode);
b.setDevMode(App.Companion.getDevMode());
b.gradeName.setTextColor(ColorUtils.calculateLuminance(gradeColor) > 0.25 ? 0xff000000 : 0xffffffff);
b.gradeName.getBackground().setColorFilter(new PorterDuffColorFilter(gradeColor, PorterDuff.Mode.MULTIPLY));

View File

@ -19,7 +19,7 @@ public class LessonChangeDialog {
public LessonChangeDialog(Context context) {
this.context = context;
this.profileId = App.profileId;
this.profileId = App.Companion.getProfileId();
}
public LessonChangeDialog(Context context, int profileId) {
this.context = context;

View File

@ -12,8 +12,6 @@ import pl.szczodrzynski.edziennik.App
import pl.szczodrzynski.edziennik.MainActivity
import pl.szczodrzynski.edziennik.R
import pl.szczodrzynski.edziennik.databinding.DialogLessonDetailsBinding
import pl.szczodrzynski.edziennik.utils.models.Notification
import java.util.*
import kotlin.coroutines.CoroutineContext
class ProfileRemoveDialog(
@ -52,26 +50,35 @@ class ProfileRemoveDialog(
val profileObject = app.db.profileDao().getByIdNow(profileId) ?: return@async
app.db.announcementDao().clear(profileId)
app.db.attendanceDao().clear(profileId)
app.db.eventDao().clear(profileId)
app.db.eventTypeDao().clear(profileId)
app.db.gradeDao().clear(profileId)
app.db.gradeCategoryDao().clear(profileId)
app.db.luckyNumberDao().clear(profileId)
app.db.noticeDao().clear(profileId)
app.db.subjectDao().clear(profileId)
app.db.teacherDao().clear(profileId)
app.db.teamDao().clear(profileId)
app.db.messageRecipientDao().clear(profileId)
app.db.messageDao().clear(profileId)
app.db.endpointTimerDao().clear(profileId)
app.db.attendanceTypeDao().clear(profileId)
app.db.classroomDao().clear(profileId)
app.db.configDao().clear(profileId)
app.db.endpointTimerDao().clear(profileId)
app.db.eventDao().clear(profileId)
app.db.eventTypeDao().clear(profileId)
app.db.gradeCategoryDao().clear(profileId)
app.db.gradeDao().clear(profileId)
app.db.lessonRangeDao().clear(profileId)
app.db.librusLessonDao().clear(profileId)
app.db.luckyNumberDao().clear(profileId)
app.db.messageDao().clear(profileId)
app.db.messageRecipientDao().clear(profileId)
app.db.noticeDao().clear(profileId)
app.db.noticeTypeDao().clear(profileId)
app.db.noticeTypeDao().clear(profileId)
app.db.notificationDao().clear(profileId)
app.db.subjectDao().clear(profileId)
app.db.teacherAbsenceDao().clear(profileId)
app.db.teacherAbsenceDao().clear(profileId)
app.db.teacherAbsenceTypeDao().clear(profileId)
app.db.teacherDao().clear(profileId)
app.db.teamDao().clear(profileId)
app.db.timetableDao().clear(profileId)
val homeCards = app.config.ui.homeCards.toMutableList()
homeCards.removeAll { it.profileId == profileId }
app.config.ui.homeCards = homeCards
val loginStoreId = profileObject.loginStoreId
val profilesUsingLoginStore = app.db.profileDao().getIdsByLoginStoreIdNow(loginStoreId)
if (profilesUsingLoginStore.size == 1) {
@ -80,18 +87,9 @@ class ProfileRemoveDialog(
app.db.profileDao().remove(profileId)
app.db.metadataDao().deleteAll(profileId)
val toRemove = ArrayList<Notification>()
for (notification in app.appConfig.notifications) {
if (notification.profileId == profileId) {
toRemove.add(notification)
}
if (App.profileId == profileId) {
app.profileLoadLast { }
}
app.appConfig.notifications.removeAll(toRemove)
app.profile = null
App.profileId = -1
app.profileLoadById(app.profileLastId())
}
deferred.await()
dialog.dismiss()

View File

@ -12,7 +12,7 @@ import kotlinx.coroutines.Job
import pl.szczodrzynski.edziennik.App
import pl.szczodrzynski.edziennik.MainActivity
import pl.szczodrzynski.edziennik.R
import pl.szczodrzynski.edziennik.data.api.task.EdziennikTask
import pl.szczodrzynski.edziennik.data.api.edziennik.EdziennikTask
import pl.szczodrzynski.edziennik.databinding.DialogLessonDetailsBinding
import pl.szczodrzynski.edziennik.ui.modules.messages.MessagesFragment
import kotlin.coroutines.CoroutineContext

View File

@ -77,6 +77,9 @@ class LessonDetailsDialog(
)
}
if (App.devMode)
b.lessonId.visibility = View.VISIBLE
update()
}}

View File

@ -78,10 +78,10 @@ public class AgendaFragment extends Fragment {
return null;
app = (App) activity.getApplication();
getContext().getTheme().applyStyle(Themes.INSTANCE.getAppTheme(), true);
if (app.profile == null)
if (app.getProfile() == null)
return inflater.inflate(R.layout.fragment_loading, container, false);
// activity, context and profile is valid
viewType = app.config.getUi().getAgendaViewType();
viewType = app.getConfig().forProfile().getUi().getAgendaViewType();
if (viewType == AGENDA_DEFAULT) {
b_default = DataBindingUtil.inflate(inflater, R.layout.fragment_agenda_default, container, false);
return b_default.getRoot();
@ -94,7 +94,7 @@ public class AgendaFragment extends Fragment {
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
if (app == null || app.profile == null || activity == null || (b_default == null && b_calendar == null) || !isAdded())
if (app == null || activity == null || b_default == null && b_calendar == null || !isAdded())
return;
activity.getBottomSheet().prependItems(
@ -106,7 +106,7 @@ public class AgendaFragment extends Fragment {
activity.getBottomSheet().close();
new EventManualDialog(
activity,
App.profileId,
App.Companion.getProfileId(),
null,
actualDate,
null,
@ -122,7 +122,7 @@ public class AgendaFragment extends Fragment {
.withOnClickListener(v3 -> {
activity.getBottomSheet().close();
viewType = viewType == AGENDA_DEFAULT ? AGENDA_CALENDAR : AGENDA_DEFAULT;
app.config.getUi().setAgendaViewType(viewType);
app.getConfig().forProfile().getUi().setAgendaViewType(viewType);
activity.reloadTarget();
}),
new BottomSheetSeparatorItem(true),
@ -131,7 +131,7 @@ public class AgendaFragment extends Fragment {
.withIcon(CommunityMaterial.Icon.cmd_eye_check_outline)
.withOnClickListener(v3 -> {
activity.getBottomSheet().close();
AsyncTask.execute(() -> app.db.metadataDao().setAllSeen(App.profileId, TYPE_EVENT, true));
AsyncTask.execute(() -> App.db.metadataDao().setAllSeen(App.Companion.getProfileId(), TYPE_EVENT, true));
Toast.makeText(activity, R.string.main_menu_mark_as_read_success, Toast.LENGTH_SHORT).show();
})
);
@ -141,7 +141,7 @@ public class AgendaFragment extends Fragment {
activity.getNavView().bottomBar.setFabIcon(CommunityMaterial.Icon2.cmd_plus);
activity.getNavView().setFabOnClickListener(v -> new EventManualDialog(
activity,
App.profileId,
App.Companion.getProfileId(),
null,
actualDate,
null,
@ -167,7 +167,7 @@ public class AgendaFragment extends Fragment {
final Handler handler = new Handler();
handler.postDelayed(() -> AsyncTask.execute(() -> {
if (app == null || app.profile == null || activity == null || b_default == null || !isAdded())
if (app == null || activity == null || b_default == null || !isAdded())
return;
List<CalendarEvent> eventList = new ArrayList<>();
@ -193,8 +193,8 @@ public class AgendaFragment extends Fragment {
));
} TODO: Implement new timetable lesson changes */
if (app.profile.getStudentData("showTeacherAbsences", true)) {
List<TeacherAbsenceFull> teacherAbsenceList = app.db.teacherAbsenceDao().getAllFullNow(App.profileId);
if (app.getProfile().getStudentData("showTeacherAbsences", true)) {
List<TeacherAbsenceFull> teacherAbsenceList = App.db.teacherAbsenceDao().getAllFullNow(App.Companion.getProfileId());
List<TeacherAbsenceCounter> teacherAbsenceCounters = new ArrayList<>();
for (TeacherAbsenceFull absence : teacherAbsenceList) {
@ -226,7 +226,7 @@ public class AgendaFragment extends Fragment {
Colors.legibleTextColor(0xffff1744),
startTime,
endTime,
App.profileId,
App.Companion.getProfileId(),
date,
counter.getTeacherAbsenceCount()
));
@ -234,7 +234,7 @@ public class AgendaFragment extends Fragment {
}
List<EventFull> events = app.db.eventDao().getAllNow(App.profileId);
List<EventFull> events = App.db.eventDao().getAllNow(App.Companion.getProfileId());
for (EventFull event : events) {
Calendar startTime = Calendar.getInstance();
Calendar endTime = Calendar.getInstance();
@ -321,7 +321,7 @@ public class AgendaFragment extends Fragment {
actualDate = Date.fromCalendar(calendar);
int scrolledDate = actualDate.getValue();
if (unreadEventDates.contains(scrolledDate)) {
AsyncTask.execute(() -> app.db.eventDao().setSeenByDate(App.profileId, Date.fromYmd(intToStr(scrolledDate)), true));
AsyncTask.execute(() -> App.db.eventDao().setSeenByDate(App.Companion.getProfileId(), Date.fromYmd(intToStr(scrolledDate)), true));
unreadEventDates.remove((Integer) scrolledDate);
}
}
@ -342,7 +342,7 @@ public class AgendaFragment extends Fragment {
// new EventListDialogOld(activity).show(app, Date.fromCalendar(calendarEvent.getInstanceDay()));
new DayDialog(
activity,
App.profileId,
App.Companion.getProfileId(),
Date.fromCalendar(calendarEvent.getInstanceDay()),
null,
null
@ -373,7 +373,7 @@ public class AgendaFragment extends Fragment {
final Handler handler = new Handler();
handler.postDelayed(() -> AsyncTask.execute(() -> {
if (app == null || app.profile == null || activity == null || b_calendar == null || !isAdded())
if (app == null || activity == null || b_calendar == null || !isAdded())
return;
Context c = getContext();
Activity a = getActivity();
@ -385,7 +385,7 @@ public class AgendaFragment extends Fragment {
List<EventDay> eventList = new ArrayList<>();
List<EventFull> events = app.db.eventDao().getAllNow(App.profileId);
List<EventFull> events = App.db.eventDao().getAllNow(App.Companion.getProfileId());
for (EventFull event : events) {
if (event.eventDate == null)
continue;
@ -434,13 +434,13 @@ public class AgendaFragment extends Fragment {
Date dayDate = Date.fromCalendar(eventDay.getCalendar());
int scrolledDate = dayDate.getValue();
if (unreadEventDates.contains(scrolledDate)) {
AsyncTask.execute(() -> app.db.eventDao().setSeenByDate(App.profileId, Date.fromYmd(intToStr(scrolledDate)), true));
AsyncTask.execute(() -> App.db.eventDao().setSeenByDate(App.Companion.getProfileId(), Date.fromYmd(intToStr(scrolledDate)), true));
unreadEventDates.remove((Integer) scrolledDate);
}
new DayDialog(
activity,
App.profileId,
App.Companion.getProfileId(),
dayDate,
null,
null

View File

@ -24,8 +24,8 @@ import org.greenrobot.eventbus.ThreadMode;
import pl.szczodrzynski.edziennik.App;
import pl.szczodrzynski.edziennik.MainActivity;
import pl.szczodrzynski.edziennik.R;
import pl.szczodrzynski.edziennik.data.api.edziennik.EdziennikTask;
import pl.szczodrzynski.edziennik.data.api.events.AnnouncementGetEvent;
import pl.szczodrzynski.edziennik.data.api.task.EdziennikTask;
import pl.szczodrzynski.edziennik.data.db.full.AnnouncementFull;
import pl.szczodrzynski.edziennik.databinding.DialogAnnouncementBinding;
import pl.szczodrzynski.edziennik.databinding.FragmentAnnouncementsBinding;
@ -49,8 +49,6 @@ public class AnnouncementsFragment extends Fragment {
return null;
app = (App) activity.getApplication();
getContext().getTheme().applyStyle(Themes.INSTANCE.getAppTheme(), true);
if (app.profile == null)
return inflater.inflate(R.layout.fragment_loading, container, false);
// activity, context and profile is valid
b = DataBindingUtil.inflate(inflater, R.layout.fragment_announcements, container, false);
b.refreshLayout.setParent(activity.getSwipeRefreshLayout());
@ -61,7 +59,7 @@ public class AnnouncementsFragment extends Fragment {
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
if (app == null || app.profile == null || activity == null || b == null || !isAdded())
if (app == null || activity == null || b == null || !isAdded())
return;
activity.getBottomSheet().prependItems(
@ -70,10 +68,10 @@ public class AnnouncementsFragment extends Fragment {
.withIcon(CommunityMaterial.Icon.cmd_eye_check_outline)
.withOnClickListener(v3 -> {
activity.getBottomSheet().close();
if (app.profile.getLoginStoreType() == LOGIN_TYPE_LIBRUS) {
EdziennikTask.Companion.announcementsRead(App.profileId).enqueue(requireContext());
if (app.getProfile().getLoginStoreType() == LOGIN_TYPE_LIBRUS) {
EdziennikTask.Companion.announcementsRead(App.Companion.getProfileId()).enqueue(requireContext());
} else {
AsyncTask.execute(() -> app.db.metadataDao().setAllSeen(App.profileId, TYPE_ANNOUNCEMENT, true));
AsyncTask.execute(() -> App.db.metadataDao().setAllSeen(App.Companion.getProfileId(), TYPE_ANNOUNCEMENT, true));
Toast.makeText(activity, R.string.main_menu_mark_as_read_success, Toast.LENGTH_SHORT).show();
}
})
@ -92,8 +90,8 @@ public class AnnouncementsFragment extends Fragment {
recyclerView.setLayoutManager(linearLayoutManager);
recyclerView.addItemDecoration(new SimpleDividerItemDecoration(view.getContext()));
app.db.announcementDao().getAll(App.profileId).observe(this, announcements -> {
if (app == null || app.profile == null || activity == null || b == null || !isAdded())
app.db.announcementDao().getAll(App.Companion.getProfileId()).observe(this, announcements -> {
if (app == null || activity == null || b == null || !isAdded())
return;
if (announcements == null) {
@ -109,8 +107,8 @@ public class AnnouncementsFragment extends Fragment {
return;
}*/
AnnouncementsAdapter announcementsAdapter = new AnnouncementsAdapter(activity, announcements, (v, announcement) -> {
if (announcement.text == null || (app.profile.getLoginStoreType() == LOGIN_TYPE_LIBRUS && !announcement.seen && app.networkUtils.isOnline())) {
EdziennikTask.Companion.announcementGet(App.profileId, announcement).enqueue(requireContext());
if (announcement.text == null || (app.getProfile().getLoginStoreType() == LOGIN_TYPE_LIBRUS && !announcement.seen && app.getNetworkUtils().isOnline())) {
EdziennikTask.Companion.announcementGet(App.Companion.getProfileId(), announcement).enqueue(requireContext());
} else {
showAnnouncementDetailsDialog(announcement);
}
@ -157,9 +155,9 @@ public class AnnouncementsFragment extends Fragment {
.show();
DialogAnnouncementBinding b = DialogAnnouncementBinding.bind(dialog.getCustomView());
b.text.setText(announcement.teacherFullName+"\n\n"+ (announcement.startDate != null ? announcement.startDate.getFormattedString() : "-") + (announcement.endDate != null ? " do " + announcement.endDate.getFormattedString() : "")+"\n\n" +announcement.text);
if (!announcement.seen && app.profile.getLoginStoreType() != LOGIN_TYPE_LIBRUS) {
if (!announcement.seen && app.getProfile().getLoginStoreType() != LOGIN_TYPE_LIBRUS) {
announcement.seen = true;
AsyncTask.execute(() -> app.db.metadataDao().setSeen(App.profileId, announcement, true));
AsyncTask.execute(() -> App.db.metadataDao().setSeen(App.Companion.getProfileId(), announcement, true));
if (recyclerView.getAdapter() != null)
recyclerView.getAdapter().notifyDataSetChanged();
}

View File

@ -97,7 +97,7 @@ public class AttendanceAdapter extends RecyclerView.Adapter<AttendanceAdapter.Vi
holder.attendanceLessonTopic.getBackground().setColorFilter(new PorterDuffColorFilter(0x692196f3, PorterDuff.Mode.MULTIPLY));
attendance.seen = true;
AsyncTask.execute(() -> {
app.db.metadataDao().setSeen(App.profileId, attendance, true);
App.db.metadataDao().setSeen(App.Companion.getProfileId(), attendance, true);
//Intent i = new Intent("android.intent.action.MAIN").putExtra(MainActivity.ACTION_UPDATE_BADGES, "yes, sure");
//context.sendBroadcast(i);
});

View File

@ -33,8 +33,8 @@ import antonkozyriatskyi.circularprogressindicator.CircularProgressIndicator;
import pl.szczodrzynski.edziennik.App;
import pl.szczodrzynski.edziennik.MainActivity;
import pl.szczodrzynski.edziennik.R;
import pl.szczodrzynski.edziennik.data.db.full.AttendanceFull;
import pl.szczodrzynski.edziennik.data.db.entity.Subject;
import pl.szczodrzynski.edziennik.data.db.full.AttendanceFull;
import pl.szczodrzynski.edziennik.databinding.FragmentAttendanceBinding;
import pl.szczodrzynski.edziennik.utils.Themes;
import pl.szczodrzynski.navlib.bottomsheet.items.BottomSheetPrimaryItem;
@ -73,8 +73,6 @@ public class AttendanceFragment extends Fragment {
return null;
app = (App) activity.getApplication();
getContext().getTheme().applyStyle(Themes.INSTANCE.getAppTheme(), true);
if (app.profile == null)
return inflater.inflate(R.layout.fragment_loading, container, false);
// activity, context and profile is valid
b = DataBindingUtil.inflate(inflater, R.layout.fragment_attendance, container, false);
b.refreshLayout.setParent(activity.getSwipeRefreshLayout());
@ -83,7 +81,7 @@ public class AttendanceFragment extends Fragment {
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
if (app == null || app.profile == null || activity == null || b == null || !isAdded())
if (app == null || activity == null || b == null || !isAdded())
return;
activity.getBottomSheet().prependItems(
@ -92,7 +90,7 @@ public class AttendanceFragment extends Fragment {
.withIcon(CommunityMaterial.Icon.cmd_eye_check_outline)
.withOnClickListener(v3 -> {
activity.getBottomSheet().close();
AsyncTask.execute(() -> app.db.metadataDao().setAllSeen(App.profileId, TYPE_ATTENDANCE, true));
AsyncTask.execute(() -> App.db.metadataDao().setAllSeen(App.Companion.getProfileId(), TYPE_ATTENDANCE, true));
Toast.makeText(activity, R.string.main_menu_mark_as_read_success, Toast.LENGTH_SHORT).show();
})
);
@ -149,13 +147,13 @@ public class AttendanceFragment extends Fragment {
}
}*/
if (app.profile.getLoginStoreType() == LOGIN_TYPE_MOBIDZIENNIK) {
if (app.getProfile().getLoginStoreType() == LOGIN_TYPE_MOBIDZIENNIK) {
b.attendanceSummarySubject.setVisibility(View.GONE);
}
else {
b.attendanceSummarySubject.setOnClickListener((v -> {
AsyncTask.execute(() -> {
List<Subject> subjectList = app.db.subjectDao().getAllNow(App.profileId);
List<Subject> subjectList = App.db.subjectDao().getAllNow(App.Companion.getProfileId());
PopupMenu popupMenu = new PopupMenu(activity, b.attendanceSummarySubject, Gravity.END);
popupMenu.getMenu().add(0, -1, 0, R.string.subject_filter_disabled);
int index = 0;
@ -187,8 +185,8 @@ public class AttendanceFragment extends Fragment {
b.attendanceView.setHasFixedSize(true);
b.attendanceView.setLayoutManager(linearLayoutManager);
app.db.attendanceDao().getAll(App.profileId).observe(this, attendance -> {
if (app == null || app.profile == null || activity == null || b == null || !isAdded())
App.db.attendanceDao().getAll(App.Companion.getProfileId()).observe(this, attendance -> {
if (app == null || activity == null || b == null || !isAdded())
return;
if (attendance == null) {
@ -209,7 +207,7 @@ public class AttendanceFragment extends Fragment {
subjectTotalCount = new LongSparseArray<>();
subjectAbsentCount = new LongSparseArray<>();
for (AttendanceFull attendance: attendanceList) {
if (app.profile.getLoginStoreType() == LOGIN_TYPE_VULCAN && attendance.type == TYPE_RELEASED)
if (app.getProfile().getLoginStoreType() == LOGIN_TYPE_VULCAN && attendance.type == TYPE_RELEASED)
continue;
int[] subjectTotal = subjectTotalCount.get(attendance.subjectId, new int[3]);
int[] subjectAbsent = subjectAbsentCount.get(attendance.subjectId, new int[3]);
@ -228,7 +226,7 @@ public class AttendanceFragment extends Fragment {
}
private void updateList() {
if (app == null || app.profile == null || activity == null || b == null || !isAdded())
if (app == null || activity == null || b == null || !isAdded())
return;
int presentCount = 0;
@ -306,7 +304,7 @@ public class AttendanceFragment extends Fragment {
float attendancePercentage;
// in Mobidziennik there are no TYPE_PRESENT records so we cannot calculate the percentage
if (app.profile.getLoginStoreType() == LOGIN_TYPE_VULCAN) {
if (app.getProfile().getLoginStoreType() == LOGIN_TYPE_VULCAN) {
float allCount = presentCount + absentCount + belatedCount; // do not count releases
float present = allCount - absentCount;
attendancePercentage = present / allCount * 100.0f;

View File

@ -2,9 +2,9 @@ package pl.szczodrzynski.edziennik.ui.modules.base;
import android.content.Context;
import android.os.Bundle;
import androidx.appcompat.app.AppCompatActivity;
import pl.szczodrzynski.edziennik.App;
import pl.szczodrzynski.edziennik.R;
public class CrashGtfoActivity extends AppCompatActivity {
@ -12,7 +12,7 @@ public class CrashGtfoActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setTheme((((App)getApplication()).getContext()
setTheme((getApplication()
.getSharedPreferences(getString(R.string.preference_file_global), Context.MODE_PRIVATE)
.getBoolean("dark_theme", false) ? R.style.AppTheme_Dark : R.style.AppTheme));
setContentView(R.layout.activity_gtfo);

View File

@ -1,15 +1,15 @@
package pl.szczodrzynski.edziennik.ui.modules.base;
import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import com.afollestad.materialdialogs.MaterialDialog;
import com.google.android.material.textfield.TextInputEditText;
import com.google.gson.Gson;
@ -100,7 +100,7 @@ public class DebugFragment extends Fragment {
forceType = "i";
parameter = parameter.substring(0, parameter.length()-1);
}
SimpleObj obj = app.gson.fromJson("{\"value\":"+parameter+"}", SimpleObj.class);
SimpleObj obj = app.getGson().fromJson("{\"value\":"+parameter+"}", SimpleObj.class);
Class type = obj.value.getClass();
Object value = obj.value;
if ("d".equals(forceType)) {
@ -180,7 +180,7 @@ public class DebugFragment extends Fragment {
getByPath(app, target, path, true);
// set the value if specified
if (valueToSet != null) {
targetField.set(target, app.gson.fromJson(valueToSet, targetField.getGenericType()));
targetField.set(target, app.getGson().fromJson(valueToSet, targetField.getGenericType()));
targetField = null;
}
if (targetField != null) {
@ -194,7 +194,7 @@ public class DebugFragment extends Fragment {
return Log.getStackTraceString(e);
}
}
return app.gson.toJson(target);
return app.getGson().toJson(target);
}
@Override
@ -218,13 +218,13 @@ public class DebugFragment extends Fragment {
getView().findViewById(R.id.debugAppconfig).setOnClickListener(v -> {
JsonRecyclerView mRecyclerView = getView().findViewById(R.id.rv_json);
// bind json
mRecyclerView.bindJson(new Gson().toJson(app.appConfig));
mRecyclerView.bindJson(new Gson().toJson(app.getConfig()));
mRecyclerView.setTextSize(20);
});
getView().findViewById(R.id.debugAppprofile).setOnClickListener(v -> {
JsonRecyclerView mRecyclerView = getView().findViewById(R.id.rv_json);
// bind json
mRecyclerView.bindJson(new Gson().toJson(app.profile));
mRecyclerView.bindJson(new Gson().toJson(app.getProfile()));
mRecyclerView.setTextSize(20);
});
}

View File

@ -53,8 +53,6 @@ public class BehaviourFragment extends Fragment {
return null;
app = (App) activity.getApplication();
getContext().getTheme().applyStyle(Themes.INSTANCE.getAppTheme(), true);
if (app.profile == null)
return inflater.inflate(R.layout.fragment_loading, container, false);
// activity, context and profile is valid
b = DataBindingUtil.inflate(inflater, R.layout.fragment_behaviour, container, false);
b.refreshLayout.setParent(activity.getSwipeRefreshLayout());
@ -63,7 +61,7 @@ public class BehaviourFragment extends Fragment {
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
if (app == null || app.profile == null || activity == null || b == null || !isAdded())
if (app == null || activity == null || b == null || !isAdded())
return;
activity.getBottomSheet().prependItems(
@ -72,7 +70,7 @@ public class BehaviourFragment extends Fragment {
.withIcon(CommunityMaterial.Icon.cmd_eye_check_outline)
.withOnClickListener(v3 -> {
activity.getBottomSheet().close();
AsyncTask.execute(() -> app.db.metadataDao().setAllSeen(App.profileId, TYPE_NOTICE, true));
AsyncTask.execute(() -> App.db.metadataDao().setAllSeen(App.Companion.getProfileId(), TYPE_NOTICE, true));
Toast.makeText(activity, R.string.main_menu_mark_as_read_success, Toast.LENGTH_SHORT).show();
})
);
@ -99,8 +97,8 @@ public class BehaviourFragment extends Fragment {
b.noticesView.setHasFixedSize(true);
b.noticesView.setLayoutManager(linearLayoutManager);
app.db.noticeDao().getAll(App.profileId).observe(this, notices -> {
if (app == null || app.profile == null || activity == null || b == null || !isAdded())
app.db.noticeDao().getAll(App.Companion.getProfileId()).observe(this, notices -> {
if (app == null || activity == null || b == null || !isAdded())
return;
if (notices == null) {

View File

@ -1,18 +1,5 @@
package pl.szczodrzynski.edziennik.ui.modules.feedback;
import androidx.appcompat.app.AppCompatActivity;
import androidx.databinding.DataBindingUtil;
import pl.szczodrzynski.edziennik.App;
import pl.szczodrzynski.edziennik.R;
import pl.szczodrzynski.edziennik.databinding.ActivityFeedbackBinding;
import pl.szczodrzynski.edziennik.data.db.entity.FeedbackMessage;
import pl.szczodrzynski.edziennik.data.db.full.FeedbackMessageWithCount;
import pl.szczodrzynski.edziennik.network.ServerRequest;
import pl.szczodrzynski.edziennik.utils.Anim;
import pl.szczodrzynski.edziennik.utils.Themes;
import pl.szczodrzynski.edziennik.utils.Utils;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
@ -28,6 +15,9 @@ import android.view.animation.Animation;
import android.widget.PopupMenu;
import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity;
import androidx.databinding.DataBindingUtil;
import com.afollestad.materialdialogs.MaterialDialog;
import com.github.bassaer.chatmessageview.model.IChatUser;
import com.github.bassaer.chatmessageview.model.Message;
@ -36,7 +26,16 @@ import com.github.bassaer.chatmessageview.view.ChatView;
import java.util.Calendar;
import java.util.List;
import static pl.szczodrzynski.edziennik.App.APP_URL;
import pl.szczodrzynski.edziennik.App;
import pl.szczodrzynski.edziennik.R;
import pl.szczodrzynski.edziennik.data.db.entity.FeedbackMessage;
import pl.szczodrzynski.edziennik.data.db.full.FeedbackMessageWithCount;
import pl.szczodrzynski.edziennik.databinding.ActivityFeedbackBinding;
import pl.szczodrzynski.edziennik.network.ServerRequest;
import pl.szczodrzynski.edziennik.utils.Anim;
import pl.szczodrzynski.edziennik.utils.Themes;
import pl.szczodrzynski.edziennik.utils.Utils;
import static pl.szczodrzynski.edziennik.utils.Utils.crc16;
import static pl.szczodrzynski.edziennik.utils.Utils.openUrl;
@ -101,7 +100,7 @@ public class FeedbackActivity extends AppCompatActivity {
.content(R.string.sending_message)
.negativeText(R.string.cancel)
.show();
new ServerRequest(app, app.requestScheme + APP_URL + "main.php?feedback_message", "FeedbackSend")
new ServerRequest(app, "https://edziennik.szczodrzynski.pl/app/main.php?feedback_message", "FeedbackSend")
.setBodyParameter("message_text", text)
.setBodyParameter("target_device", deviceToSend == null ? "null" : deviceToSend)
.run(((e, result) -> {
@ -199,7 +198,7 @@ public class FeedbackActivity extends AppCompatActivity {
receiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
FeedbackMessage message = app.gson.fromJson(intent.getStringExtra("message"), FeedbackMessage.class);
FeedbackMessage message = app.getGson().fromJson(intent.getStringExtra("message"), FeedbackMessage.class);
Calendar c = Calendar.getInstance();
c.setTimeInMillis(message.sentTime);
Message chatMessage = new Message.Builder()
@ -238,7 +237,7 @@ public class FeedbackActivity extends AppCompatActivity {
mChatView.setMessageMarginTop(5);
mChatView.setMessageMarginBottom(5);
if (App.devMode && app.deviceId.equals("f054761fbdb6a238")) {
if (App.Companion.getDevMode() && app.getDeviceId().equals("f054761fbdb6a238")) {
b.targetDeviceLayout.setVisibility(View.VISIBLE);
b.targetDeviceDropDown.setOnClickListener((v -> {
AsyncTask.execute(() -> {

View File

@ -21,11 +21,10 @@ import com.github.bassaer.chatmessageview.model.IChatUser
import com.github.bassaer.chatmessageview.model.Message
import com.github.bassaer.chatmessageview.view.ChatView
import pl.szczodrzynski.edziennik.App
import pl.szczodrzynski.edziennik.App.APP_URL
import pl.szczodrzynski.edziennik.R
import pl.szczodrzynski.edziennik.MainActivity
import pl.szczodrzynski.edziennik.databinding.FragmentFeedbackBinding
import pl.szczodrzynski.edziennik.R
import pl.szczodrzynski.edziennik.data.db.entity.FeedbackMessage
import pl.szczodrzynski.edziennik.databinding.FragmentFeedbackBinding
import pl.szczodrzynski.edziennik.network.ServerRequest
import pl.szczodrzynski.edziennik.utils.Anim
import pl.szczodrzynski.edziennik.utils.Themes
@ -248,7 +247,7 @@ class FeedbackFragment : Fragment() {
.content(R.string.sending_message)
.negativeText(R.string.cancel)
.show()
ServerRequest(app, app.requestScheme + APP_URL + "main.php?feedback_message", "FeedbackSend")
ServerRequest(app, "https://edziennik.szczodrzynski.pl/app/main.php?feedback_message", "FeedbackSend")
.setBodyParameter("message_text", text)
.setBodyParameter("target_device", if (deviceToSend == null) "null" else deviceToSend)
.run { e, result ->

View File

@ -16,7 +16,6 @@ import androidx.fragment.app.Fragment;
import com.afollestad.materialdialogs.MaterialDialog;
import com.mikepenz.iconics.typeface.library.community.material.CommunityMaterial;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.List;
@ -25,8 +24,8 @@ import pl.szczodrzynski.edziennik.MainActivity;
import pl.szczodrzynski.edziennik.R;
import pl.szczodrzynski.edziennik.config.ProfileConfigGrades;
import pl.szczodrzynski.edziennik.data.db.entity.Grade;
import pl.szczodrzynski.edziennik.data.db.full.GradeFull;
import pl.szczodrzynski.edziennik.data.db.entity.Subject;
import pl.szczodrzynski.edziennik.data.db.full.GradeFull;
import pl.szczodrzynski.edziennik.databinding.FragmentGradesBinding;
import pl.szczodrzynski.edziennik.utils.Themes;
import pl.szczodrzynski.edziennik.utils.models.ItemGradesSubjectModel;
@ -56,8 +55,6 @@ public class GradesFragment extends Fragment {
return null;
app = (App) activity.getApplication();
getContext().getTheme().applyStyle(Themes.INSTANCE.getAppTheme(), true);
if (app.profile == null)
return inflater.inflate(R.layout.fragment_loading, container, false);
// activity, context and profile is valid
b = DataBindingUtil.inflate(inflater, R.layout.fragment_grades, container, false);
b.refreshLayout.setParent(activity.getSwipeRefreshLayout());
@ -68,10 +65,8 @@ public class GradesFragment extends Fragment {
ListView listView;
List<ItemGradesSubjectModel> subjectList;
private boolean sortModeChanged = false;
private String getRegisterCardAverageModeSubText() {
switch (App.getConfig().forProfile().getGrades().getYearAverageMode()) {
switch (App.Companion.getConfig().forProfile().getGrades().getYearAverageMode()) {
default:
case YEAR_1_AVG_2_AVG:
return getString(R.string.settings_register_avg_mode_0_short);
@ -88,7 +83,7 @@ public class GradesFragment extends Fragment {
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
if (app == null || app.profile == null || activity == null || b == null || !isAdded())
if (app == null || activity == null || b == null || !isAdded())
return;
/*activity.getBottomSheet().setToggleGroupEnabled(true);
@ -136,7 +131,7 @@ public class GradesFragment extends Fragment {
.withIcon(CommunityMaterial.Icon2.cmd_palette_outline)
.withOnClickListener(v3 -> {
activity.getBottomSheet().close();
ProfileConfigGrades config = app.config.getFor(App.profileId).getGrades();
ProfileConfigGrades config = app.getConfig().getFor(App.Companion.getProfileId()).getGrades();
new MaterialDialog.Builder(activity)
.title(R.string.dialog_grades_color_mode_title)
.items(R.array.dialog_grades_color_modes)
@ -155,8 +150,8 @@ public class GradesFragment extends Fragment {
new MaterialDialog.Builder(activity)
.title(R.string.dialog_grades_sort_title)
.items(R.array.dialog_grades_sort_modes)
.itemsCallbackSingleChoice(app.config.getGrades().getOrderBy(), (dialog, view1, which, text) -> {
app.config.getGrades().setOrderBy(which);
.itemsCallbackSingleChoice(app.getConfig().getGrades().getOrderBy(), (dialog, view1, which, text) -> {
app.getConfig().getGrades().setOrderBy(which);
activity.reloadTarget();
return true;
})
@ -184,8 +179,8 @@ public class GradesFragment extends Fragment {
.title(getString(R.string.settings_register_avg_mode_dialog_title))
.content(getString(R.string.settings_register_avg_mode_dialog_text))
.items(modeNames)
.itemsCallbackSingleChoice(modeIds.indexOf(App.getConfig().forProfile().getGrades().getYearAverageMode()), (dialog, itemView, which, text) -> {
App.getConfig().forProfile().getGrades().setYearAverageMode(modeIds.get(which));
.itemsCallbackSingleChoice(modeIds.indexOf(App.Companion.getConfig().forProfile().getGrades().getYearAverageMode()), (dialog, itemView, which, text) -> {
App.Companion.getConfig().forProfile().getGrades().setYearAverageMode(modeIds.get(which));
activity.reloadTarget();
return true;
})
@ -197,7 +192,7 @@ public class GradesFragment extends Fragment {
.withIcon(CommunityMaterial.Icon.cmd_eye_check_outline)
.withOnClickListener(v3 -> {
activity.getBottomSheet().close();
AsyncTask.execute(() -> app.db.metadataDao().setAllSeen(App.profileId, TYPE_GRADE, true));
AsyncTask.execute(() -> App.db.metadataDao().setAllSeen(App.Companion.getProfileId(), TYPE_GRADE, true));
Toast.makeText(activity, R.string.main_menu_mark_as_read_success, Toast.LENGTH_SHORT).show();
})
);
@ -227,41 +222,41 @@ public class GradesFragment extends Fragment {
long finalExpandSubjectId = expandSubjectId;
String orderBy;
if (app.config.getGrades().getOrderBy() == ORDER_BY_SUBJECT_ASC) {
if (app.getConfig().getGrades().getOrderBy() == ORDER_BY_SUBJECT_ASC) {
orderBy = "subjectLongName ASC, addedDate DESC";
}
else if (app.config.getGrades().getOrderBy() == ORDER_BY_DATE_DESC) {
else if (app.getConfig().getGrades().getOrderBy() == ORDER_BY_DATE_DESC) {
orderBy = "addedDate DESC";
}
else if (app.config.getGrades().getOrderBy() == ORDER_BY_DATE_ASC) {
else if (app.getConfig().getGrades().getOrderBy() == ORDER_BY_DATE_ASC) {
orderBy = "addedDate ASC";
}
else {
orderBy = "subjectLongName DESC, addedDate DESC";
}
app.db.gradeDao().getAllOrderBy(App.profileId, orderBy).observe(this, grades -> {
if (app == null || app.profile == null || activity == null || b == null || !isAdded())
App.db.gradeDao().getAllOrderBy(App.Companion.getProfileId(), orderBy).observe(this, grades -> {
if (app == null || activity == null || b == null || !isAdded())
return;
subjectList = new ArrayList<>();
ProfileConfigGrades config = app.config.getFor(App.profileId).getGrades();
ProfileConfigGrades config = app.getConfig().getFor(App.Companion.getProfileId()).getGrades();
// now we have all grades from the newest to the oldest
for (GradeFull grade: grades) {
ItemGradesSubjectModel model = ItemGradesSubjectModel.searchModelBySubjectId(subjectList, grade.subjectId);
if (model == null) {
model = new ItemGradesSubjectModel(app.profile,
new Subject(App.profileId, grade.subjectId, grade.subjectLongName, grade.subjectShortName),
model = new ItemGradesSubjectModel(app.getProfile(),
new Subject(App.Companion.getProfileId(), grade.subjectId, grade.subjectLongName, grade.subjectShortName),
new ArrayList<>(),
new ArrayList<>());
subjectList.add(model);
if (model.subject != null && model.subject.id == finalExpandSubjectId) {
model.expandView = true;
}
model.colorMode = App.getConfig().forProfile().getGrades().getColorMode();
model.yearAverageMode = App.getConfig().forProfile().getGrades().getYearAverageMode();
model.colorMode = App.Companion.getConfig().forProfile().getGrades().getColorMode();
model.yearAverageMode = App.Companion.getConfig().forProfile().getGrades().getYearAverageMode();
}
if (!grade.seen && grade.semester == 1) {
model.semester1Unread++;
@ -380,7 +375,7 @@ public class GradesFragment extends Fragment {
else if (!model.isBehaviourSubject && model.isNormalSubject) {
// applies for normal grades & normal+descriptive grades
// calculate the normal grade average based on the user's setting
switch (App.getConfig().forProfile().getGrades().getYearAverageMode()) {
switch (App.Companion.getConfig().forProfile().getGrades().getYearAverageMode()) {
case YEAR_1_AVG_2_AVG:
model.yearAverage = (model.semester1Average + model.semester2Average) / 2;
break;
@ -433,7 +428,7 @@ public class GradesFragment extends Fragment {
}
public void showAverages() {
if (app == null || app.profile == null || activity == null || b == null || !isAdded() || subjectList == null)
if (app == null || activity == null || b == null || !isAdded() || subjectList == null)
return;
float semester1Sum = 0;
@ -529,8 +524,6 @@ public class GradesFragment extends Fragment {
}
}
DecimalFormat df = new DecimalFormat("0.00");
String semester1ExpectedAverageStr = semester1Count > semester1ProposedCount || semester1Count > semester1FinalCount ? getString(R.string.dialog_averages_expected_format, 1, semester1Sum / semester1Count) : "";
String semester1ProposedAverageStr = semester1ProposedCount > 0 ? getString(R.string.dialog_averages_proposed_format, 1, semester1ProposedSum / semester1ProposedCount) : "";
String semester1FinalAverageStr = semester1FinalCount > 0 ? getString(R.string.dialog_averages_final_format, 1, semester1FinalSum / semester1FinalCount) : "";

View File

@ -52,11 +52,11 @@ public class GradesListAdapter extends RecyclerView.Adapter<GradesListAdapter.Vi
GradeFull grade = gradeList.get(position);
holder.root.setOnClickListener((v -> {
new GradeDetailsDialog(v.getContext(), App.profileId).show(app, grade);
new GradeDetailsDialog(v.getContext(), App.Companion.getProfileId()).show(app, grade);
}));
int gradeColor;
if (App.getConfig().forProfile().getGrades().getColorMode() == COLOR_MODE_DEFAULT) {
if (App.Companion.getConfig().forProfile().getGrades().getColorMode() == COLOR_MODE_DEFAULT) {
gradeColor = grade.color;
}
else {

View File

@ -31,8 +31,8 @@ import pl.szczodrzynski.edziennik.App;
import pl.szczodrzynski.edziennik.MainActivity;
import pl.szczodrzynski.edziennik.R;
import pl.szczodrzynski.edziennik.data.db.AppDb;
import pl.szczodrzynski.edziennik.data.db.full.GradeFull;
import pl.szczodrzynski.edziennik.data.db.entity.Subject;
import pl.szczodrzynski.edziennik.data.db.full.GradeFull;
import pl.szczodrzynski.edziennik.utils.Anim;
import pl.szczodrzynski.edziennik.utils.Colors;
import pl.szczodrzynski.edziennik.utils.Utils;
@ -86,34 +86,34 @@ public class GradesSubjectAdapter extends ArrayAdapter<ItemGradesSubjectModel> i
private boolean gradesSetAsRead(ViewHolder holder, ItemGradesSubjectModel model, int semester) {
boolean somethingChanged = false;
AppDb db = AppDb.getDatabase(null);
AppDb db = App.Companion.getDb();
if (semester == 1) {
model.semester1Unread = 0;
for (GradeFull grade : model.grades1) {
if (!grade.seen) {
db.metadataDao().setSeen(App.profileId, grade, somethingChanged = true);
db.metadataDao().setSeen(App.Companion.getProfileId(), grade, somethingChanged = true);
}
}
if (model.semester1Proposed != null && !model.semester1Proposed.seen)
db.metadataDao().setSeen(App.profileId, model.semester1Proposed, somethingChanged = true);
db.metadataDao().setSeen(App.Companion.getProfileId(), model.semester1Proposed, somethingChanged = true);
if (model.semester1Final != null && !model.semester1Final.seen)
db.metadataDao().setSeen(App.profileId, model.semester1Final, somethingChanged = true);
db.metadataDao().setSeen(App.Companion.getProfileId(), model.semester1Final, somethingChanged = true);
}
else if (semester == 2) {
model.semester2Unread = 0;
for (GradeFull grade : model.grades2) {
if (!grade.seen) {
db.metadataDao().setSeen(App.profileId, grade, somethingChanged = true);
db.metadataDao().setSeen(App.Companion.getProfileId(), grade, somethingChanged = true);
}
}
if (model.semester2Proposed != null && !model.semester2Proposed.seen)
db.metadataDao().setSeen(App.profileId, model.semester2Proposed, somethingChanged = true);
db.metadataDao().setSeen(App.Companion.getProfileId(), model.semester2Proposed, somethingChanged = true);
if (model.semester2Final != null && !model.semester2Final.seen)
db.metadataDao().setSeen(App.profileId, model.semester2Final, somethingChanged = true);
db.metadataDao().setSeen(App.Companion.getProfileId(), model.semester2Final, somethingChanged = true);
if (model.yearProposed != null && !model.yearProposed.seen)
db.metadataDao().setSeen(App.profileId, model.yearProposed, somethingChanged = true);
db.metadataDao().setSeen(App.Companion.getProfileId(), model.yearProposed, somethingChanged = true);
if (model.yearFinal != null && !model.yearFinal.seen)
db.metadataDao().setSeen(App.profileId, model.yearFinal, somethingChanged = true);
db.metadataDao().setSeen(App.Companion.getProfileId(), model.yearFinal, somethingChanged = true);
}
if (somethingChanged) updateSubjectSemesterBadges(holder, model);
return somethingChanged;
@ -219,7 +219,7 @@ public class GradesSubjectAdapter extends ArrayAdapter<ItemGradesSubjectModel> i
layoutParams.setMargins(0, 0, _5dp, 0);
DisplayMetrics displayMetrics = getContext().getResources().getDisplayMetrics();
int maxWidthPx = displayMetrics.widthPixels - Utils.dpToPx((app.config.getUi().getMiniMenuVisible() ? 72 : 0)/*miniDrawer size*/ + 8 + 8/*left and right offsets*/ + 24/*ellipsize width*/);
int maxWidthPx = displayMetrics.widthPixels - Utils.dpToPx((app.getConfig().getUi().getMiniMenuVisible() ? 72 : 0)/*miniDrawer size*/ + 8 + 8/*left and right offsets*/ + 24/*ellipsize width*/);
int totalWidthPx = 0;
boolean ellipsized = false;

View File

@ -40,11 +40,11 @@ class HomeFragment : Fragment(), CoroutineScope {
private const val TAG = "HomeFragment"
fun swapCards(fromPosition: Int, toPosition: Int, cardAdapter: HomeCardAdapter) {
val homeCards = App.getConfig().ui.homeCards.toMutableList()
val homeCards = App.config.ui.homeCards.toMutableList()
val fromPair = homeCards[fromPosition]
homeCards[fromPosition] = homeCards[toPosition]
homeCards[toPosition] = fromPair
App.getConfig().ui.homeCards = homeCards
App.config.ui.homeCards = homeCards
val fromCard = cardAdapter.items[fromPosition]
cardAdapter.items[fromPosition] = cardAdapter.items[toPosition]

View File

@ -20,7 +20,7 @@ import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
import pl.szczodrzynski.edziennik.App
import pl.szczodrzynski.edziennik.MainActivity
import pl.szczodrzynski.edziennik.data.api.task.EdziennikTask
import pl.szczodrzynski.edziennik.data.api.edziennik.EdziennikTask
import pl.szczodrzynski.edziennik.data.db.entity.Profile
import pl.szczodrzynski.edziennik.databinding.CardHomeDebugBinding
import pl.szczodrzynski.edziennik.dp

View File

@ -29,9 +29,9 @@ import pl.szczodrzynski.edziennik.App
import pl.szczodrzynski.edziennik.MainActivity
import pl.szczodrzynski.edziennik.R
import pl.szczodrzynski.edziennik.data.db.entity.Grade.*
import pl.szczodrzynski.edziennik.data.db.full.GradeFull
import pl.szczodrzynski.edziennik.data.db.entity.Profile
import pl.szczodrzynski.edziennik.data.db.entity.Subject
import pl.szczodrzynski.edziennik.data.db.full.GradeFull
import pl.szczodrzynski.edziennik.databinding.CardHomeGradesBinding
import pl.szczodrzynski.edziennik.dp
import pl.szczodrzynski.edziennik.ui.modules.home.HomeCard
@ -121,7 +121,7 @@ class HomeGradesCard(
16 /*ellipsize width*/)) / 1.5f
subject.grades1.onEach { grade ->
val gradeColor = when (App.getConfig().forProfile().grades.colorMode) {
val gradeColor = when (App.config.forProfile().grades.colorMode) {
Profile.COLOR_MODE_DEFAULT -> grade.color
else -> Colors.gradeToColor(grade)
}

View File

@ -94,7 +94,7 @@ class HomeLuckyNumberCard(
holder.root.onClick {
StudentNumberDialog(activity, profile, onDismissListener = {
app.profileSaveAsync(profile)
app.profileSave(profile)
val newSubTextRes = if (profile.studentNumber == -1)
R.string.home_lucky_number_details_click_to_set
else

View File

@ -22,11 +22,11 @@ import org.greenrobot.eventbus.EventBus
import org.greenrobot.eventbus.Subscribe
import org.greenrobot.eventbus.ThreadMode
import pl.szczodrzynski.edziennik.*
import pl.szczodrzynski.edziennik.data.api.edziennik.EdziennikTask
import pl.szczodrzynski.edziennik.data.api.events.ApiTaskAllFinishedEvent
import pl.szczodrzynski.edziennik.data.api.task.EdziennikTask
import pl.szczodrzynski.edziennik.data.db.entity.Event
import pl.szczodrzynski.edziennik.data.db.entity.Profile
import pl.szczodrzynski.edziennik.data.db.entity.Lesson
import pl.szczodrzynski.edziennik.data.db.entity.Profile
import pl.szczodrzynski.edziennik.data.db.full.LessonFull
import pl.szczodrzynski.edziennik.databinding.CardHomeTimetableBinding
import pl.szczodrzynski.edziennik.ui.dialogs.bell.BellSyncTimeChooseDialog

View File

@ -85,7 +85,7 @@ public class HomeworkAdapter extends RecyclerView.Adapter<HomeworkAdapter.ViewHo
holder.homeworkItemTopic.getBackground().setColorFilter(new PorterDuffColorFilter(0x692196f3, PorterDuff.Mode.MULTIPLY));
homework.seen = true;
AsyncTask.execute(() -> {
app.db.metadataDao().setSeen(App.profileId, homework, true);
App.db.metadataDao().setSeen(App.Companion.getProfileId(), homework, true);
});
}
else {

View File

@ -78,6 +78,6 @@ public class ChangelogIntroActivity extends IntroActivity {
.build());
}*/
app.config.setAppVersion(BuildConfig.VERSION_CODE);
app.getConfig().setAppVersion(BuildConfig.VERSION_CODE);
}
}

Some files were not shown because too many files have changed in this diff Show More