mirror of
https://github.com/szkolny-eu/szkolny-android.git
synced 2025-06-14 06:30:18 +02:00
Compare commits
63 Commits
v3.9.5-dev
...
v3.9.11-de
Author | SHA1 | Date | |
---|---|---|---|
f79b7eaf83 | |||
ae13bf946f | |||
f116c4f1f4 | |||
867c8920a8 | |||
6e6dd34872 | |||
0759468fa7 | |||
1b1fb09211 | |||
de414c912c | |||
d274a2fed1 | |||
285b7e9b9e | |||
875efcff7e | |||
07ae37167d | |||
f689f4d427 | |||
19bc2b8b37 | |||
673116e27e | |||
59fcb0a050 | |||
cd76f99bbf | |||
6a4994b9c2 | |||
63960c5e05 | |||
540afb6a28 | |||
ae10b8abbd | |||
db2ebab879 | |||
6ec3d062df | |||
86b6060a09 | |||
83d123e341 | |||
34061695f9 | |||
de68476442 | |||
678a81a44b | |||
cfb3096d53 | |||
9b7aca745a | |||
82852389fa | |||
ce06084e6f | |||
3ca051983f | |||
cd379e4175 | |||
62fdfa2b6f | |||
9866017f7e | |||
67f98b08c6 | |||
fdb5f7ec02 | |||
04c3c7ca6e | |||
f424315d97 | |||
c907a8df37 | |||
37ea65e3fc | |||
a3e5f824c8 | |||
e0c850a455 | |||
1c6815f708 | |||
9a20511935 | |||
965f5e73d9 | |||
13b58a1d56 | |||
0a3261b8b3 | |||
dbdfc7fdd8 | |||
56062f5bfa | |||
0cbba2eb45 | |||
aa84356dd6 | |||
efaad0a4dd | |||
71015c0137 | |||
85b5667a7e | |||
dfdc6817a1 | |||
058345b9c9 | |||
831b7876b4 | |||
729cf6f08e | |||
860a16b32d | |||
9f871c077b | |||
a8f89abf7d |
2
.gitignore
vendored
2
.gitignore
vendored
@ -81,3 +81,5 @@ lint/generated/
|
||||
lint/outputs/
|
||||
lint/tmp/
|
||||
# lint/reports/
|
||||
|
||||
app/schemas/
|
||||
|
@ -169,7 +169,7 @@ dependencies {
|
||||
|
||||
implementation 'com.github.kuba2k2:RecyclerTabLayout:700f980584'
|
||||
|
||||
implementation 'com.linkedin.android.tachyon:tachyon:1.0.2'
|
||||
implementation 'com.github.kuba2k2:Tachyon:551943a6b5'
|
||||
}
|
||||
repositories {
|
||||
mavenCentral()
|
||||
|
@ -105,6 +105,11 @@
|
||||
android:excludeFromRecents="true"
|
||||
android:noHistory="true"
|
||||
android:theme="@style/AppTheme.NoDisplay" />
|
||||
<activity android:name=".widgets.timetable.LessonDialogActivity"
|
||||
android:configChanges="orientation|keyboardHidden"
|
||||
android:excludeFromRecents="true"
|
||||
android:noHistory="true"
|
||||
android:theme="@style/AppTheme.NoDisplay" />
|
||||
<activity
|
||||
android:name=".ui.modules.settings.SettingsLicenseActivity"
|
||||
android:configChanges="orientation|keyboardHidden"
|
||||
|
@ -68,11 +68,6 @@ import me.leolin.shortcutbadger.ShortcutBadger;
|
||||
import okhttp3.ConnectionSpec;
|
||||
import okhttp3.OkHttpClient;
|
||||
import okhttp3.TlsVersion;
|
||||
import pl.szczodrzynski.edziennik.data.api.Edziennik;
|
||||
import pl.szczodrzynski.edziennik.data.api.Iuczniowie;
|
||||
import pl.szczodrzynski.edziennik.data.api.Librus;
|
||||
import pl.szczodrzynski.edziennik.data.api.Mobidziennik;
|
||||
import pl.szczodrzynski.edziennik.data.api.Vulcan;
|
||||
import pl.szczodrzynski.edziennik.data.db.AppDb;
|
||||
import pl.szczodrzynski.edziennik.data.db.modules.debuglog.DebugLog;
|
||||
import pl.szczodrzynski.edziennik.data.db.modules.login.LoginStore;
|
||||
@ -141,12 +136,6 @@ public class App extends androidx.multidex.MultiDexApplication implements Config
|
||||
public PersistentCookieJar cookieJar;
|
||||
public OkHttpClient http;
|
||||
public OkHttpClient httpLazy;
|
||||
//public Jakdojade apiJakdojade;
|
||||
public Edziennik apiEdziennik;
|
||||
public Mobidziennik apiMobidziennik;
|
||||
public Iuczniowie apiIuczniowie;
|
||||
public Librus apiLibrus;
|
||||
public Vulcan apiVulcan;
|
||||
|
||||
public SharedPreferences appSharedPrefs; // sharedPreferences for APPCONFIG + JOBS STORE
|
||||
public AppConfig appConfig; // APPCONFIG: common for all profiles
|
||||
@ -204,12 +193,6 @@ public class App extends androidx.multidex.MultiDexApplication implements Config
|
||||
db = AppDb.getDatabase(this);
|
||||
gson = new Gson();
|
||||
networkUtils = new NetworkUtils(this);
|
||||
apiEdziennik = new Edziennik(this);
|
||||
//apiJakdojade = new Jakdojade(this);
|
||||
apiMobidziennik = new Mobidziennik(this);
|
||||
apiIuczniowie = new Iuczniowie(this);
|
||||
apiLibrus = new Librus(this);
|
||||
apiVulcan = new Vulcan(this);
|
||||
|
||||
Iconics.init(getApplicationContext());
|
||||
Iconics.registerFont(SzkolnyFont.INSTANCE);
|
||||
|
@ -4,7 +4,9 @@ import android.Manifest
|
||||
import android.app.Activity
|
||||
import android.content.Context
|
||||
import android.content.pm.PackageManager
|
||||
import android.content.res.Resources
|
||||
import android.graphics.Typeface
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.text.*
|
||||
@ -13,9 +15,11 @@ import android.text.style.StrikethroughSpan
|
||||
import android.text.style.StyleSpan
|
||||
import android.util.LongSparseArray
|
||||
import android.util.SparseArray
|
||||
import android.util.TypedValue
|
||||
import android.view.View
|
||||
import android.widget.CompoundButton
|
||||
import android.widget.TextView
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.annotation.*
|
||||
import androidx.core.app.ActivityCompat
|
||||
import androidx.core.util.forEach
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
@ -25,9 +29,13 @@ import com.google.gson.JsonArray
|
||||
import com.google.gson.JsonElement
|
||||
import com.google.gson.JsonObject
|
||||
import im.wangchao.mhttp.Response
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import pl.szczodrzynski.edziennik.data.db.modules.profiles.Profile
|
||||
import pl.szczodrzynski.edziennik.data.db.modules.teachers.Teacher
|
||||
import pl.szczodrzynski.edziennik.data.db.modules.teams.Team
|
||||
import pl.szczodrzynski.edziennik.utils.models.Time
|
||||
import pl.szczodrzynski.navlib.R
|
||||
import pl.szczodrzynski.navlib.getColorFromRes
|
||||
import java.text.SimpleDateFormat
|
||||
@ -95,6 +103,15 @@ fun String.swapFirstLastName(): String {
|
||||
}
|
||||
}
|
||||
|
||||
fun String.getFirstLastName(): Pair<String, String>? {
|
||||
return this.split(" ").let {
|
||||
if (it.size >= 2) Pair(it[0], it[1])
|
||||
else null
|
||||
}
|
||||
}
|
||||
|
||||
fun String.getLastFirstName() = this.getFirstLastName()
|
||||
|
||||
fun changeStringCase(s: String): String {
|
||||
val delimiters = " '-/"
|
||||
val sb = StringBuilder()
|
||||
@ -153,8 +170,9 @@ fun colorFromName(context: Context, name: String?): Int {
|
||||
return context.getColorFromRes(color)
|
||||
}
|
||||
|
||||
fun MutableList<out Profile>.filterOutArchived() {
|
||||
fun MutableList<Profile>.filterOutArchived(): MutableList<Profile> {
|
||||
this.removeAll { it.archived }
|
||||
return this
|
||||
}
|
||||
|
||||
fun Activity.isStoragePermissionGranted(): Boolean {
|
||||
@ -404,15 +422,15 @@ fun TextView.setText(@StringRes resid: Int, vararg formatArgs: Any) {
|
||||
text = context.getString(resid, *formatArgs)
|
||||
}
|
||||
|
||||
fun JsonObject(vararg properties: Pair<String, Any>): JsonObject {
|
||||
fun JsonObject(vararg properties: Pair<String, Any?>): JsonObject {
|
||||
return JsonObject().apply {
|
||||
for (property in properties) {
|
||||
when (property.second) {
|
||||
is JsonElement -> add(property.first, property.second as JsonElement)
|
||||
is String -> addProperty(property.first, property.second as String)
|
||||
is Char -> addProperty(property.first, property.second as Char)
|
||||
is Number -> addProperty(property.first, property.second as Number)
|
||||
is Boolean -> addProperty(property.first, property.second as Boolean)
|
||||
is JsonElement -> add(property.first, property.second as JsonElement?)
|
||||
is String -> addProperty(property.first, property.second as String?)
|
||||
is Char -> addProperty(property.first, property.second as Char?)
|
||||
is Number -> addProperty(property.first, property.second as Number?)
|
||||
is Boolean -> addProperty(property.first, property.second as Boolean?)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -428,6 +446,13 @@ inline fun <T : View> T.onClick(crossinline onClickListener: (v: T) -> Unit) {
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
inline fun <T : CompoundButton> T.onChange(crossinline onChangeListener: (v: T, isChecked: Boolean) -> Unit) {
|
||||
setOnCheckedChangeListener { buttonView, isChecked ->
|
||||
onChangeListener(buttonView as T, isChecked)
|
||||
}
|
||||
}
|
||||
|
||||
fun <T> LiveData<T>.observeOnce(lifecycleOwner: LifecycleOwner, observer: Observer<T>) {
|
||||
observe(lifecycleOwner, object : Observer<T> {
|
||||
override fun onChanged(t: T?) {
|
||||
@ -435,4 +460,76 @@ fun <T> LiveData<T>.observeOnce(lifecycleOwner: LifecycleOwner, observer: Observ
|
||||
removeObserver(this)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a value in dp to pixels.
|
||||
*/
|
||||
val Int.dp: Int
|
||||
get() = (this * Resources.getSystem().displayMetrics.density).toInt()
|
||||
/**
|
||||
* Convert a value in pixels to dp.
|
||||
*/
|
||||
val Int.px: Int
|
||||
get() = (this / Resources.getSystem().displayMetrics.density).toInt()
|
||||
|
||||
@ColorInt
|
||||
fun @receiver:AttrRes Int.resolveAttr(context: Context?): Int {
|
||||
val typedValue = TypedValue()
|
||||
context?.theme?.resolveAttribute(this, typedValue, true)
|
||||
return typedValue.data
|
||||
}
|
||||
@ColorInt
|
||||
fun @receiver:ColorRes Int.resolveColor(context: Context): Int {
|
||||
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
context.resources.getColor(this, context.theme)
|
||||
}
|
||||
else {
|
||||
context.resources.getColor(this)
|
||||
}
|
||||
}
|
||||
fun @receiver:DrawableRes Int.resolveDrawable(context: Context): Drawable {
|
||||
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
context.resources.getDrawable(this, context.theme)
|
||||
}
|
||||
else {
|
||||
context.resources.getDrawable(this)
|
||||
}
|
||||
}
|
||||
|
||||
fun View.findParentById(targetId: Int): View? {
|
||||
if (id == targetId) {
|
||||
return this
|
||||
}
|
||||
val viewParent = this.parent ?: return null
|
||||
if (viewParent is View) {
|
||||
return viewParent.findParentById(targetId)
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
fun CoroutineScope.startCoroutineTimer(delayMillis: Long = 0, repeatMillis: Long = 0, action: () -> Unit) = launch {
|
||||
delay(delayMillis)
|
||||
if (repeatMillis > 0) {
|
||||
while (true) {
|
||||
action()
|
||||
delay(repeatMillis)
|
||||
}
|
||||
} else {
|
||||
action()
|
||||
}
|
||||
}
|
||||
|
||||
operator fun Time?.compareTo(other: Time?): Int {
|
||||
if (this == null && other == null)
|
||||
return 0
|
||||
if (this == null)
|
||||
return -1
|
||||
if (other == null)
|
||||
return 1
|
||||
return this.compareTo(other)
|
||||
}
|
||||
|
||||
operator fun StringBuilder.plusAssign(str: String?) {
|
||||
this.append(str)
|
||||
}
|
||||
|
@ -38,23 +38,25 @@ import pl.droidsonroids.gif.GifDrawable
|
||||
import pl.szczodrzynski.edziennik.App.APP_URL
|
||||
import pl.szczodrzynski.edziennik.api.v2.events.*
|
||||
import pl.szczodrzynski.edziennik.api.v2.events.task.EdziennikTask
|
||||
import pl.szczodrzynski.edziennik.data.api.interfaces.EdziennikInterface.*
|
||||
import pl.szczodrzynski.edziennik.data.db.modules.metadata.Metadata.*
|
||||
import pl.szczodrzynski.edziennik.databinding.ActivitySzkolnyBinding
|
||||
import pl.szczodrzynski.edziennik.network.ServerRequest
|
||||
import pl.szczodrzynski.edziennik.sync.AppManagerDetectedEvent
|
||||
import pl.szczodrzynski.edziennik.sync.SyncWorker
|
||||
import pl.szczodrzynski.edziennik.ui.dialogs.changelog.ChangelogDialog
|
||||
import pl.szczodrzynski.edziennik.ui.dialogs.settings.ProfileRemoveDialog
|
||||
import pl.szczodrzynski.edziennik.ui.dialogs.sync.SyncViewListDialog
|
||||
import pl.szczodrzynski.edziennik.ui.modules.agenda.AgendaFragment
|
||||
import pl.szczodrzynski.edziennik.ui.modules.announcements.AnnouncementsFragment
|
||||
import pl.szczodrzynski.edziennik.ui.modules.attendance.AttendanceFragment
|
||||
import pl.szczodrzynski.edziennik.ui.modules.base.DebugFragment
|
||||
import pl.szczodrzynski.edziennik.ui.modules.behaviour.BehaviourFragment
|
||||
import pl.szczodrzynski.edziennik.ui.modules.error.ErrorSnackbar
|
||||
import pl.szczodrzynski.edziennik.ui.modules.feedback.FeedbackFragment
|
||||
import pl.szczodrzynski.edziennik.ui.modules.feedback.HelpFragment
|
||||
import pl.szczodrzynski.edziennik.ui.modules.grades.GradesFragment
|
||||
import pl.szczodrzynski.edziennik.ui.modules.grades.editor.GradesEditorFragment
|
||||
import pl.szczodrzynski.edziennik.ui.modules.home.HomeFragment
|
||||
import pl.szczodrzynski.edziennik.ui.modules.home.HomeFragmentV2
|
||||
import pl.szczodrzynski.edziennik.ui.modules.homework.HomeworkFragment
|
||||
import pl.szczodrzynski.edziennik.ui.modules.login.LoginActivity
|
||||
import pl.szczodrzynski.edziennik.ui.modules.messages.MessageFragment
|
||||
@ -120,9 +122,9 @@ class MainActivity : AppCompatActivity() {
|
||||
val list: MutableList<NavTarget> = mutableListOf()
|
||||
|
||||
// home item
|
||||
list += NavTarget(DRAWER_ITEM_HOME, R.string.menu_home_page, HomeFragment::class)
|
||||
list += NavTarget(DRAWER_ITEM_HOME, R.string.menu_home_page, HomeFragmentV2::class)
|
||||
.withTitle(R.string.app_name)
|
||||
.withIcon(CommunityMaterial.Icon2.cmd_home)
|
||||
.withIcon(CommunityMaterial.Icon2.cmd_home_outline)
|
||||
.isInDrawer(true)
|
||||
.isStatic(true)
|
||||
.withPopToHome(false)
|
||||
@ -133,50 +135,50 @@ class MainActivity : AppCompatActivity() {
|
||||
.isInDrawer(true)
|
||||
|
||||
list += NavTarget(DRAWER_ITEM_AGENDA, R.string.menu_agenda, AgendaFragment::class)
|
||||
.withIcon(CommunityMaterial.Icon.cmd_calendar)
|
||||
.withIcon(CommunityMaterial.Icon.cmd_calendar_outline)
|
||||
.withBadgeTypeId(TYPE_EVENT)
|
||||
.isInDrawer(true)
|
||||
|
||||
list += NavTarget(DRAWER_ITEM_GRADES, R.string.menu_grades, GradesFragment::class)
|
||||
.withIcon(CommunityMaterial.Icon2.cmd_numeric_5_box)
|
||||
.withIcon(CommunityMaterial.Icon2.cmd_numeric_5_box_outline)
|
||||
.withBadgeTypeId(TYPE_GRADE)
|
||||
.isInDrawer(true)
|
||||
|
||||
list += NavTarget(DRAWER_ITEM_MESSAGES, R.string.menu_messages, MessagesFragment::class)
|
||||
.withIcon(CommunityMaterial.Icon.cmd_email)
|
||||
.withIcon(CommunityMaterial.Icon.cmd_email_outline)
|
||||
.withBadgeTypeId(TYPE_MESSAGE)
|
||||
.isInDrawer(true)
|
||||
|
||||
list += NavTarget(DRAWER_ITEM_HOMEWORK, R.string.menu_homework, HomeworkFragment::class)
|
||||
.withIcon(SzkolnyFont.Icon.szf_file_document_edit)
|
||||
.withIcon(SzkolnyFont.Icon.szf_notebook_outline)
|
||||
.withBadgeTypeId(TYPE_HOMEWORK)
|
||||
.isInDrawer(true)
|
||||
|
||||
list += NavTarget(DRAWER_ITEM_BEHAVIOUR, R.string.menu_notices, BehaviourFragment::class)
|
||||
.withIcon(CommunityMaterial.Icon2.cmd_message_alert)
|
||||
.withIcon(CommunityMaterial.Icon.cmd_emoticon_outline)
|
||||
.withBadgeTypeId(TYPE_NOTICE)
|
||||
.isInDrawer(true)
|
||||
|
||||
list += NavTarget(DRAWER_ITEM_ATTENDANCE, R.string.menu_attendance, AttendanceFragment::class)
|
||||
.withIcon(CommunityMaterial.Icon.cmd_calendar_remove)
|
||||
.withIcon(CommunityMaterial.Icon.cmd_calendar_remove_outline)
|
||||
.withBadgeTypeId(TYPE_ATTENDANCE)
|
||||
.isInDrawer(true)
|
||||
|
||||
list += NavTarget(DRAWER_ITEM_ANNOUNCEMENTS, R.string.menu_announcements, AnnouncementsFragment::class)
|
||||
.withIcon(CommunityMaterial.Icon.cmd_bulletin_board)
|
||||
.withIcon(CommunityMaterial.Icon.cmd_bullhorn_outline)
|
||||
.withBadgeTypeId(TYPE_ANNOUNCEMENT)
|
||||
.isInDrawer(true)
|
||||
|
||||
|
||||
// static drawer items
|
||||
list += NavTarget(DRAWER_ITEM_NOTIFICATIONS, R.string.menu_notifications, NotificationsFragment::class)
|
||||
.withIcon(CommunityMaterial.Icon.cmd_bell_ring)
|
||||
.withIcon(CommunityMaterial.Icon.cmd_bell_ring_outline)
|
||||
.isInDrawer(true)
|
||||
.isStatic(true)
|
||||
.isBelowSeparator(true)
|
||||
|
||||
list += NavTarget(DRAWER_ITEM_SETTINGS, R.string.menu_settings, SettingsNewFragment::class)
|
||||
.withIcon(CommunityMaterial.Icon2.cmd_settings)
|
||||
.withIcon(CommunityMaterial.Icon2.cmd_settings_outline)
|
||||
.isInDrawer(true)
|
||||
.isStatic(true)
|
||||
.isBelowSeparator(true)
|
||||
@ -195,7 +197,7 @@ class MainActivity : AppCompatActivity() {
|
||||
.isInProfileList(false)
|
||||
|
||||
list += NavTarget(DRAWER_PROFILE_SYNC_ALL, R.string.menu_sync_all, null)
|
||||
.withIcon(CommunityMaterial.Icon2.cmd_sync)
|
||||
.withIcon(CommunityMaterial.Icon.cmd_download_outline)
|
||||
.isInProfileList(true)
|
||||
|
||||
|
||||
@ -214,6 +216,7 @@ class MainActivity : AppCompatActivity() {
|
||||
val navView: NavView by lazy { b.navView }
|
||||
val drawer: NavDrawer by lazy { navView.drawer }
|
||||
val bottomSheet: NavBottomSheet by lazy { navView.bottomSheet }
|
||||
val errorSnackbar: ErrorSnackbar by lazy { ErrorSnackbar(this) }
|
||||
|
||||
val swipeRefreshLayout: SwipeRefreshLayoutNoTouch by lazy { b.swipeRefreshLayout }
|
||||
|
||||
@ -246,6 +249,8 @@ class MainActivity : AppCompatActivity() {
|
||||
|
||||
setContentView(b.root)
|
||||
|
||||
errorSnackbar.setCoordinator(b.navView.coordinator, b.navView.bottomBar)
|
||||
|
||||
navLoading = true
|
||||
|
||||
b.navView.apply {
|
||||
@ -371,6 +376,11 @@ class MainActivity : AppCompatActivity() {
|
||||
|
||||
b.swipeRefreshLayout.isEnabled = true
|
||||
b.swipeRefreshLayout.setOnRefreshListener { this.syncCurrentFeature() }
|
||||
b.swipeRefreshLayout.setColorSchemeResources(
|
||||
R.color.md_blue_500,
|
||||
R.color.md_amber_500,
|
||||
R.color.md_green_500
|
||||
)
|
||||
|
||||
isStoragePermissionGranted()
|
||||
|
||||
@ -424,7 +434,7 @@ class MainActivity : AppCompatActivity() {
|
||||
navView.coordinator.postDelayed({
|
||||
CafeBar.builder(this)
|
||||
.content(R.string.rate_snackbar_text)
|
||||
.icon(IconicsDrawable(this).icon(CommunityMaterial.Icon2.cmd_star).size(IconicsSize.dp(20)).color(IconicsColor.colorInt(Themes.getPrimaryTextColor(this))))
|
||||
.icon(IconicsDrawable(this).icon(CommunityMaterial.Icon2.cmd_star_outline).size(IconicsSize.dp(20)).color(IconicsColor.colorInt(Themes.getPrimaryTextColor(this))))
|
||||
.positiveText(R.string.rate_snackbar_positive)
|
||||
.positiveColor(-0xb350b0)
|
||||
.negativeText(R.string.rate_snackbar_negative)
|
||||
@ -461,25 +471,25 @@ class MainActivity : AppCompatActivity() {
|
||||
bottomSheet.appendItems(
|
||||
BottomSheetPrimaryItem(false)
|
||||
.withTitle(R.string.menu_sync)
|
||||
.withIcon(CommunityMaterial.Icon2.cmd_sync)
|
||||
.withIcon(CommunityMaterial.Icon.cmd_download_outline)
|
||||
.withOnClickListener(View.OnClickListener {
|
||||
bottomSheet.close()
|
||||
app.apiEdziennik.guiSyncFeature(app, this, App.profileId, R.string.sync_dialog_title, R.string.sync_dialog_text, R.string.sync_done, fragmentToFeature(navTargetId))
|
||||
SyncViewListDialog(this, navTargetId)
|
||||
}),
|
||||
BottomSheetSeparatorItem(false),
|
||||
BottomSheetPrimaryItem(false)
|
||||
.withTitle(R.string.menu_settings)
|
||||
.withIcon(CommunityMaterial.Icon2.cmd_settings)
|
||||
.withIcon(CommunityMaterial.Icon2.cmd_settings_outline)
|
||||
.withOnClickListener(View.OnClickListener { loadTarget(DRAWER_ITEM_SETTINGS) }),
|
||||
BottomSheetPrimaryItem(false)
|
||||
.withTitle(R.string.menu_feedback)
|
||||
.withIcon(CommunityMaterial.Icon2.cmd_help_circle)
|
||||
.withIcon(CommunityMaterial.Icon2.cmd_help_circle_outline)
|
||||
.withOnClickListener(View.OnClickListener { loadTarget(TARGET_FEEDBACK) })
|
||||
)
|
||||
if (App.devMode) {
|
||||
bottomSheet += BottomSheetPrimaryItem(false)
|
||||
.withTitle(R.string.menu_debug)
|
||||
.withIcon(CommunityMaterial.Icon.cmd_android_debug_bridge)
|
||||
.withIcon(CommunityMaterial.Icon.cmd_android_studio)
|
||||
.withOnClickListener(View.OnClickListener { loadTarget(DRAWER_ITEM_DEBUG) })
|
||||
}
|
||||
|
||||
@ -527,9 +537,14 @@ class MainActivity : AppCompatActivity() {
|
||||
DRAWER_ITEM_MESSAGES -> MessagesFragment.pageSelection
|
||||
else -> 0
|
||||
}
|
||||
val arguments = when (navTargetId) {
|
||||
DRAWER_ITEM_TIMETABLE -> JsonObject("weekStart" to TimetableFragment.pageSelection?.weekStart?.stringY_m_d)
|
||||
else -> null
|
||||
}
|
||||
EdziennikTask.syncProfile(
|
||||
App.profileId,
|
||||
listOf(navTargetId to fragmentParam)
|
||||
listOf(navTargetId to fragmentParam),
|
||||
arguments
|
||||
).enqueue(this)
|
||||
}
|
||||
@Subscribe(threadMode = ThreadMode.MAIN)
|
||||
@ -573,7 +588,12 @@ class MainActivity : AppCompatActivity() {
|
||||
}
|
||||
@Subscribe(threadMode = ThreadMode.MAIN)
|
||||
fun onSyncErrorEvent(event: ApiTaskErrorEvent) {
|
||||
|
||||
navView.toolbar.apply {
|
||||
subtitleFormat = R.string.toolbar_subtitle
|
||||
subtitleFormatWithUnread = R.plurals.toolbar_subtitle_with_unread
|
||||
subtitle = "Gotowe"
|
||||
}
|
||||
errorSnackbar.addError(event.error).show()
|
||||
}
|
||||
@Subscribe(threadMode = ThreadMode.MAIN, sticky = true)
|
||||
fun onAppManagerDetectedEvent(event: AppManagerDetectedEvent) {
|
||||
@ -605,22 +625,7 @@ class MainActivity : AppCompatActivity() {
|
||||
.setCancelable(false)
|
||||
.show()
|
||||
}
|
||||
private fun fragmentToFeature(currentFragment: Int): Int {
|
||||
return when (currentFragment) {
|
||||
DRAWER_ITEM_TIMETABLE -> FEATURE_TIMETABLE
|
||||
DRAWER_ITEM_AGENDA -> FEATURE_AGENDA
|
||||
DRAWER_ITEM_GRADES -> FEATURE_GRADES
|
||||
DRAWER_ITEM_HOMEWORK -> FEATURE_HOMEWORK
|
||||
DRAWER_ITEM_BEHAVIOUR -> FEATURE_NOTICES
|
||||
DRAWER_ITEM_ATTENDANCE -> FEATURE_ATTENDANCE
|
||||
DRAWER_ITEM_MESSAGES -> when (MessagesFragment.pageSelection) {
|
||||
1 -> FEATURE_MESSAGES_OUTBOX
|
||||
else -> FEATURE_MESSAGES_INBOX
|
||||
}
|
||||
DRAWER_ITEM_ANNOUNCEMENTS -> FEATURE_ANNOUNCEMENTS
|
||||
else -> FEATURE_ALL
|
||||
}
|
||||
}
|
||||
|
||||
private fun fragmentToSyncName(currentFragment: Int): Int {
|
||||
return when (currentFragment) {
|
||||
DRAWER_ITEM_TIMETABLE -> R.string.sync_feature_timetable
|
||||
@ -694,10 +699,13 @@ class MainActivity : AppCompatActivity() {
|
||||
app.profile == null -> {
|
||||
if (intentProfileId == -1)
|
||||
intentProfileId = app.appSharedPrefs.getInt("current_profile_id", 1)
|
||||
loadProfile(intentProfileId, intentTargetId)
|
||||
loadProfile(intentProfileId, intentTargetId, extras)
|
||||
}
|
||||
intentProfileId != -1 -> {
|
||||
loadProfile(intentProfileId, intentTargetId)
|
||||
if (app.profile.id != intentProfileId)
|
||||
loadProfile(intentProfileId, intentTargetId, extras)
|
||||
else
|
||||
loadTarget(intentTargetId, extras)
|
||||
}
|
||||
intentTargetId != -1 -> {
|
||||
drawer.currentProfile = app.profile.id
|
||||
@ -1043,7 +1051,7 @@ class MainActivity : AppCompatActivity() {
|
||||
}
|
||||
loadTarget(DRAWER_ITEM_SETTINGS, null)
|
||||
} else if (item.itemId == 2) {
|
||||
app.apiEdziennik.guiRemoveProfile(this@MainActivity, profileId, profile.name?.getText(this).toString())
|
||||
ProfileRemoveDialog(this, profileId, profile.name?.getText(this)?.toString() ?: "?")
|
||||
}
|
||||
true
|
||||
}
|
||||
|
@ -311,13 +311,14 @@ public class Notifier {
|
||||
\____/| .__/ \__,_|\__,_|\__\___||___/
|
||||
| |
|
||||
|*/
|
||||
public void notificationUpdatesShow(String updateVersion, String updateUrl, String updateFilename) {
|
||||
public void notificationUpdatesShow(String updateVersion, String updateUrl, String updateFilename, boolean updateDirect) {
|
||||
if (!app.appConfig.notifyAboutUpdates)
|
||||
return;
|
||||
Intent notificationIntent = new Intent(app.getContext(), BootReceiver.NotificationActionService.class)
|
||||
.putExtra("update_version", updateVersion)
|
||||
.putExtra("update_url", updateUrl)
|
||||
.putExtra("update_filename", updateFilename);
|
||||
.putExtra("update_filename", updateFilename)
|
||||
.putExtra("update_direct", updateDirect);
|
||||
|
||||
PendingIntent pendingIntent = PendingIntent.getService(app.getContext(), 0,
|
||||
notificationIntent, PendingIntent.FLAG_CANCEL_CURRENT);
|
||||
|
@ -1,450 +0,0 @@
|
||||
package pl.szczodrzynski.edziennik;
|
||||
|
||||
import android.app.PendingIntent;
|
||||
import android.appwidget.AppWidgetManager;
|
||||
import android.appwidget.AppWidgetProvider;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.PorterDuff;
|
||||
import android.graphics.drawable.BitmapDrawable;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.util.SparseArray;
|
||||
import android.view.View;
|
||||
import android.widget.RemoteViews;
|
||||
|
||||
import com.mikepenz.iconics.IconicsColor;
|
||||
import com.mikepenz.iconics.IconicsDrawable;
|
||||
import com.mikepenz.iconics.IconicsSize;
|
||||
import com.mikepenz.iconics.typeface.library.community.material.CommunityMaterial;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import pl.szczodrzynski.edziennik.api.v2.events.task.EdziennikTask;
|
||||
import pl.szczodrzynski.edziennik.data.db.modules.events.EventFull;
|
||||
import pl.szczodrzynski.edziennik.data.db.modules.lessons.LessonChange;
|
||||
import pl.szczodrzynski.edziennik.data.db.modules.lessons.LessonFull;
|
||||
import pl.szczodrzynski.edziennik.data.db.modules.profiles.Profile;
|
||||
import pl.szczodrzynski.edziennik.ui.modules.home.HomeFragment;
|
||||
import pl.szczodrzynski.edziennik.utils.models.Date;
|
||||
import pl.szczodrzynski.edziennik.utils.models.ItemWidgetTimetableModel;
|
||||
import pl.szczodrzynski.edziennik.utils.models.Time;
|
||||
import pl.szczodrzynski.edziennik.utils.models.Week;
|
||||
import pl.szczodrzynski.edziennik.widgets.WidgetConfig;
|
||||
import pl.szczodrzynski.edziennik.widgets.timetable.LessonDetailsActivity;
|
||||
import pl.szczodrzynski.edziennik.widgets.timetable.WidgetTimetableService;
|
||||
|
||||
import static pl.szczodrzynski.edziennik.ExtensionsKt.filterOutArchived;
|
||||
import static pl.szczodrzynski.edziennik.data.db.modules.events.Event.TYPE_HOMEWORK;
|
||||
import static pl.szczodrzynski.edziennik.utils.Utils.bs;
|
||||
|
||||
|
||||
public class WidgetTimetable extends AppWidgetProvider {
|
||||
|
||||
|
||||
public static final String ACTION_SYNC_DATA = "ACTION_SYNC_DATA";
|
||||
private static final String TAG = "WidgetTimetable";
|
||||
private static int modeInt = 0;
|
||||
|
||||
public WidgetTimetable() {
|
||||
// Start the worker thread
|
||||
//HandlerThread sWorkerThread = new HandlerThread("WidgetTimetable-worker");
|
||||
//sWorkerThread.start();
|
||||
//Handler sWorkerQueue = new Handler(sWorkerThread.getLooper());
|
||||
}
|
||||
|
||||
public static SparseArray<List<ItemWidgetTimetableModel>> timetables = null;
|
||||
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
if (ACTION_SYNC_DATA.equals(intent.getAction())) {
|
||||
EdziennikTask.Companion.sync().enqueue(context);
|
||||
}
|
||||
super.onReceive(context, intent);
|
||||
}
|
||||
|
||||
public static PendingIntent getPendingSelfIntent(Context context, String action) {
|
||||
Intent intent = new Intent(context, WidgetTimetable.class);
|
||||
intent.setAction(action);
|
||||
return getPendingSelfIntent(context, intent);
|
||||
}
|
||||
public static PendingIntent getPendingSelfIntent(Context context, Intent intent) {
|
||||
return PendingIntent.getBroadcast(context, 0, intent, 0);
|
||||
}
|
||||
|
||||
public static Bitmap drawableToBitmap (Drawable drawable) {
|
||||
|
||||
if (drawable instanceof BitmapDrawable) {
|
||||
return ((BitmapDrawable)drawable).getBitmap();
|
||||
}
|
||||
|
||||
Bitmap bitmap = Bitmap.createBitmap(drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
|
||||
Canvas canvas = new Canvas(bitmap);
|
||||
drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
|
||||
drawable.draw(canvas);
|
||||
|
||||
return bitmap;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
|
||||
ComponentName thisWidget = new ComponentName(context, WidgetTimetable.class);
|
||||
|
||||
timetables = new SparseArray<>();
|
||||
//timetables.clear();
|
||||
|
||||
App app = (App)context.getApplicationContext();
|
||||
|
||||
int[] allWidgetIds = appWidgetManager.getAppWidgetIds(thisWidget);
|
||||
// There may be multiple widgets active, so update all of them
|
||||
for (int appWidgetId : allWidgetIds) {
|
||||
|
||||
//d(TAG, "thr "+Thread.currentThread().getName());
|
||||
|
||||
WidgetConfig widgetConfig = app.appConfig.widgetTimetableConfigs.get(appWidgetId);
|
||||
if (widgetConfig == null) {
|
||||
widgetConfig = new WidgetConfig(app.profileFirstId());
|
||||
app.appConfig.widgetTimetableConfigs.put(appWidgetId, widgetConfig);
|
||||
app.appConfig.savePending = true;
|
||||
}
|
||||
|
||||
RemoteViews views;
|
||||
if (widgetConfig.bigStyle) {
|
||||
views = new RemoteViews(context.getPackageName(), widgetConfig.darkTheme ? R.layout.widget_timetable_dark_big : R.layout.widget_timetable_big);
|
||||
}
|
||||
else {
|
||||
views = new RemoteViews(context.getPackageName(), widgetConfig.darkTheme ? R.layout.widget_timetable_dark : R.layout.widget_timetable);
|
||||
}
|
||||
|
||||
PorterDuff.Mode mode = PorterDuff.Mode.DST_IN;
|
||||
/*if (widgetConfig.darkTheme) {
|
||||
switch (modeInt) {
|
||||
case 0:
|
||||
mode = PorterDuff.Mode.ADD;
|
||||
d(TAG, "ADD");
|
||||
break;
|
||||
case 1:
|
||||
mode = PorterDuff.Mode.DST_ATOP;
|
||||
d(TAG, "DST_ATOP");
|
||||
break;
|
||||
case 2:
|
||||
mode = PorterDuff.Mode.DST_IN;
|
||||
d(TAG, "DST_IN");
|
||||
break;
|
||||
case 3:
|
||||
mode = PorterDuff.Mode.DST_OUT;
|
||||
d(TAG, "DST_OUT");
|
||||
break;
|
||||
case 4:
|
||||
mode = PorterDuff.Mode.DST_OVER;
|
||||
d(TAG, "DST_OVER");
|
||||
break;
|
||||
case 5:
|
||||
mode = PorterDuff.Mode.LIGHTEN;
|
||||
d(TAG, "LIGHTEN");
|
||||
break;
|
||||
case 6:
|
||||
mode = PorterDuff.Mode.MULTIPLY;
|
||||
d(TAG, "MULTIPLY");
|
||||
break;
|
||||
case 7:
|
||||
mode = PorterDuff.Mode.OVERLAY;
|
||||
d(TAG, "OVERLAY");
|
||||
break;
|
||||
case 8:
|
||||
mode = PorterDuff.Mode.SCREEN;
|
||||
d(TAG, "SCREEN");
|
||||
break;
|
||||
case 9:
|
||||
mode = PorterDuff.Mode.SRC_ATOP;
|
||||
d(TAG, "SRC_ATOP");
|
||||
break;
|
||||
case 10:
|
||||
mode = PorterDuff.Mode.SRC_IN;
|
||||
d(TAG, "SRC_IN");
|
||||
break;
|
||||
case 11:
|
||||
mode = PorterDuff.Mode.SRC_OUT;
|
||||
d(TAG, "SRC_OUT");
|
||||
break;
|
||||
case 12:
|
||||
mode = PorterDuff.Mode.SRC_OVER;
|
||||
d(TAG, "SRC_OVER");
|
||||
break;
|
||||
case 13:
|
||||
mode = PorterDuff.Mode.XOR;
|
||||
d(TAG, "XOR");
|
||||
break;
|
||||
default:
|
||||
modeInt = 0;
|
||||
mode = PorterDuff.Mode.ADD;
|
||||
d(TAG, "ADD");
|
||||
break;
|
||||
}
|
||||
}*/
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P) {
|
||||
// this code seems to crash the launcher on >= P
|
||||
float transparency = widgetConfig.opacity; //0...1
|
||||
long colorFilter = 0x01000000L * (long) (255f * transparency);
|
||||
try {
|
||||
final Method[] declaredMethods = Class.forName("android.widget.RemoteViews").getDeclaredMethods();
|
||||
final int len = declaredMethods.length;
|
||||
if (len > 0) {
|
||||
for (int m = 0; m < len; m++) {
|
||||
final Method method = declaredMethods[m];
|
||||
if (method.getName().equals("setDrawableParameters")) {
|
||||
method.setAccessible(true);
|
||||
method.invoke(views, R.id.widgetTimetableListView, true, -1, (int) colorFilter, mode, -1);
|
||||
method.invoke(views, R.id.widgetTimetableHeader, true, -1, (int) colorFilter, mode, -1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (ClassNotFoundException e) {
|
||||
e.printStackTrace();
|
||||
} catch (InvocationTargetException e) {
|
||||
e.printStackTrace();
|
||||
} catch (IllegalAccessException e) {
|
||||
e.printStackTrace();
|
||||
} catch (IllegalArgumentException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
Intent refreshIntent = new Intent(context, WidgetTimetable.class);
|
||||
refreshIntent.setAction(AppWidgetManager.ACTION_APPWIDGET_UPDATE);
|
||||
refreshIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, appWidgetIds);
|
||||
PendingIntent pendingRefreshIntent = PendingIntent.getBroadcast(context,
|
||||
0, refreshIntent, PendingIntent.FLAG_UPDATE_CURRENT);
|
||||
views.setOnClickPendingIntent(R.id.widgetTimetableRefresh, pendingRefreshIntent);
|
||||
|
||||
views.setOnClickPendingIntent(R.id.widgetTimetableSync, WidgetTimetable.getPendingSelfIntent(context, ACTION_SYNC_DATA));
|
||||
|
||||
views.setImageViewBitmap(R.id.widgetTimetableRefresh, new IconicsDrawable(context, CommunityMaterial.Icon2.cmd_refresh)
|
||||
.color(IconicsColor.colorInt(Color.WHITE))
|
||||
.size(IconicsSize.dp(widgetConfig.bigStyle ? 24 : 16)).toBitmap());
|
||||
|
||||
views.setImageViewBitmap(R.id.widgetTimetableSync, new IconicsDrawable(context, CommunityMaterial.Icon2.cmd_sync)
|
||||
.color(IconicsColor.colorInt(Color.WHITE))
|
||||
.size(IconicsSize.dp(widgetConfig.bigStyle ? 24 : 16)).toBitmap());
|
||||
|
||||
boolean unified = widgetConfig.profileId == -1;
|
||||
|
||||
List<Profile> profileList = new ArrayList<>();
|
||||
if (unified) {
|
||||
profileList = app.db.profileDao().getAllNow();
|
||||
filterOutArchived(profileList);
|
||||
}
|
||||
else {
|
||||
Profile profile = app.db.profileDao().getFullByIdNow(widgetConfig.profileId);
|
||||
if (profile != null) {
|
||||
profileList.add(profile);
|
||||
}
|
||||
}
|
||||
|
||||
//d(TAG, "Profiles: "+ Arrays.toString(profileList.toArray()));
|
||||
|
||||
if (profileList == null || profileList.size() == 0) {
|
||||
views.setViewVisibility(R.id.widgetTimetableLoading, View.VISIBLE);
|
||||
views.setTextViewText(R.id.widgetTimetableLoading, app.getString(R.string.widget_timetable_profile_doesnt_exist));
|
||||
}
|
||||
else {
|
||||
views.setViewVisibility(R.id.widgetTimetableLoading, View.GONE);
|
||||
//Register profile;
|
||||
|
||||
long bellSyncDiffMillis = 0;
|
||||
if (app.appConfig.bellSyncDiff != null) {
|
||||
bellSyncDiffMillis = app.appConfig.bellSyncDiff.hour * 60 * 60 * 1000 + app.appConfig.bellSyncDiff.minute * 60 * 1000 + app.appConfig.bellSyncDiff.second * 1000;
|
||||
bellSyncDiffMillis *= app.appConfig.bellSyncMultiplier;
|
||||
bellSyncDiffMillis *= -1;
|
||||
}
|
||||
|
||||
List<ItemWidgetTimetableModel> lessonList = new ArrayList<>();
|
||||
|
||||
Time syncedNow = Time.fromMillis(Time.getNow().getInMillis() + bellSyncDiffMillis);
|
||||
|
||||
Date today = Date.getToday();
|
||||
|
||||
int openProfileId = -1;
|
||||
Date displayingDate = null;
|
||||
int displayingWeekDay = 0;
|
||||
if (unified) {
|
||||
views.setTextViewText(R.id.widgetTimetableSubtitle, app.getString(R.string.widget_timetable_title_unified));
|
||||
}
|
||||
else {
|
||||
views.setTextViewText(R.id.widgetTimetableSubtitle, profileList.get(0).getName());
|
||||
openProfileId = profileList.get(0).getId();
|
||||
}
|
||||
|
||||
List<LessonFull> lessons = app.db.lessonDao().getAllWeekNow(unified ? -1 : openProfileId, today.clone().stepForward(0, 0, -today.getWeekDay()), today);
|
||||
|
||||
int scrollPos = 0;
|
||||
|
||||
for (Profile profile: profileList) {
|
||||
Date profileDisplayingDate = HomeFragment.findDateWithLessons(profile.getId(), lessons, syncedNow, 1);
|
||||
int profileDisplayingWeekDay = profileDisplayingDate.getWeekDay();
|
||||
int dayDiff = Date.diffDays(profileDisplayingDate, Date.getToday());
|
||||
|
||||
//d(TAG, "For profile "+profile.name+" displayingDate is "+profileDisplayingDate.getStringY_m_d());
|
||||
if (displayingDate == null || profileDisplayingDate.getValue() < displayingDate.getValue()) {
|
||||
displayingDate = profileDisplayingDate;
|
||||
displayingWeekDay = profileDisplayingWeekDay;
|
||||
//d(TAG, "Setting as global dd");
|
||||
if (dayDiff == 0) {
|
||||
views.setTextViewText(R.id.widgetTimetableTitle, app.getString(R.string.day_today_format, Week.getFullDayName(displayingWeekDay)));
|
||||
} else if (dayDiff == 1) {
|
||||
views.setTextViewText(R.id.widgetTimetableTitle, app.getString(R.string.day_tomorrow_format, Week.getFullDayName(displayingWeekDay)));
|
||||
} else {
|
||||
views.setTextViewText(R.id.widgetTimetableTitle, Week.getFullDayName(displayingWeekDay) + " " + profileDisplayingDate.getStringDm());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (Profile profile: profileList) {
|
||||
int pos = 0;
|
||||
|
||||
List<EventFull> events = app.db.eventDao().getAllByDateNow(profile.getId(), displayingDate);
|
||||
if (events == null)
|
||||
events = new ArrayList<>();
|
||||
|
||||
if (unified) {
|
||||
ItemWidgetTimetableModel separator = new ItemWidgetTimetableModel();
|
||||
separator.profileId = profile.getId();
|
||||
separator.bigStyle = widgetConfig.bigStyle;
|
||||
separator.darkTheme = widgetConfig.darkTheme;
|
||||
separator.separatorProfileName = profile.getName();
|
||||
lessonList.add(separator);
|
||||
}
|
||||
|
||||
for (LessonFull lesson : lessons) {
|
||||
//d(TAG, "Profile "+profile.id+" Lesson profileId "+lesson.profileId+" weekDay "+lesson.weekDay+", "+lesson);
|
||||
if (profile.getId() != lesson.profileId || displayingWeekDay != lesson.weekDay)
|
||||
continue;
|
||||
//d(TAG, "Not skipped");
|
||||
ItemWidgetTimetableModel model = new ItemWidgetTimetableModel();
|
||||
|
||||
model.bigStyle = widgetConfig.bigStyle;
|
||||
model.darkTheme = widgetConfig.darkTheme;
|
||||
|
||||
model.profileId = profile.getId();
|
||||
|
||||
model.lessonDate = displayingDate;
|
||||
model.startTime = lesson.startTime;
|
||||
model.endTime = lesson.endTime;
|
||||
|
||||
model.lessonPassed = (syncedNow.getValue() > lesson.endTime.getValue()) && displayingWeekDay == Week.getTodayWeekDay();
|
||||
model.lessonCurrent = (Time.inRange(lesson.startTime, lesson.endTime, syncedNow)) && displayingWeekDay == Week.getTodayWeekDay();
|
||||
|
||||
if (model.lessonCurrent) {
|
||||
scrollPos = pos;
|
||||
} else if (model.lessonPassed) {
|
||||
scrollPos = pos + 1;
|
||||
}
|
||||
pos++;
|
||||
|
||||
model.subjectName = bs(lesson.subjectLongName);
|
||||
model.classroomName = lesson.classroomName;
|
||||
|
||||
model.bellSyncDiffMillis = bellSyncDiffMillis;
|
||||
|
||||
if (lesson.changeId != 0) {
|
||||
if (lesson.changeType == LessonChange.TYPE_CHANGE) {
|
||||
model.lessonChange = true;
|
||||
if (lesson.changedClassroomName()) {
|
||||
model.newClassroomName = lesson.changeClassroomName;
|
||||
}
|
||||
|
||||
if (lesson.changedSubjectLongName()) {
|
||||
model.newSubjectName = lesson.changeSubjectLongName;
|
||||
}
|
||||
}
|
||||
if (lesson.changeType == LessonChange.TYPE_CANCELLED) {
|
||||
model.lessonCancelled = true;
|
||||
}
|
||||
}
|
||||
|
||||
for (EventFull event : events) {
|
||||
if (event.startTime == null)
|
||||
continue;
|
||||
if (event.eventDate.getValue() == displayingDate.getValue()
|
||||
&& event.startTime.getValue() == lesson.startTime.getValue()) {
|
||||
model.eventColors.add(event.type == TYPE_HOMEWORK ? ItemWidgetTimetableModel.EVENT_COLOR_HOMEWORK : event.getColor());
|
||||
}
|
||||
}
|
||||
|
||||
lessonList.add(model);
|
||||
}
|
||||
}
|
||||
|
||||
if (lessonList.size() == 0) {
|
||||
views.setViewVisibility(R.id.widgetTimetableLoading, View.VISIBLE);
|
||||
views.setRemoteAdapter(R.id.widgetTimetableListView, new Intent());
|
||||
views.setTextViewText(R.id.widgetTimetableLoading, app.getString(R.string.widget_timetable_no_lessons));
|
||||
appWidgetManager.updateAppWidget(appWidgetId, views);
|
||||
}
|
||||
else {
|
||||
views.setViewVisibility(R.id.widgetTimetableLoading, View.GONE);
|
||||
|
||||
timetables.put(appWidgetId, lessonList);
|
||||
//WidgetTimetableListProvider.widgetsLessons.put(appWidgetId, lessons);
|
||||
//views.setRemoteAdapter(R.id.widgetTimetableListView, new Intent());
|
||||
Intent listIntent = new Intent(context, WidgetTimetableService.class);
|
||||
listIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
|
||||
listIntent.setData(Uri.parse(listIntent.toUri(Intent.URI_INTENT_SCHEME)));
|
||||
views.setRemoteAdapter(R.id.widgetTimetableListView, listIntent);
|
||||
|
||||
// template to handle the click listener for each item
|
||||
Intent intentTemplate = new Intent(context, LessonDetailsActivity.class);
|
||||
// Old activities shouldn't be in the history stack
|
||||
intentTemplate.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
|
||||
PendingIntent pendingIntentTimetable = PendingIntent.getActivity(context,
|
||||
0,
|
||||
intentTemplate,
|
||||
0);
|
||||
views.setPendingIntentTemplate(R.id.widgetTimetableListView, pendingIntentTimetable);
|
||||
|
||||
Intent openIntent = new Intent(context, MainActivity.class);
|
||||
openIntent.setAction("android.intent.action.MAIN");
|
||||
if (!unified) {
|
||||
openIntent.putExtra("profileId", openProfileId);
|
||||
openIntent.putExtra("timetableDate", displayingDate.getValue());
|
||||
}
|
||||
openIntent.putExtra("fragmentId", MainActivity.DRAWER_ITEM_TIMETABLE);
|
||||
PendingIntent pendingOpenIntent = PendingIntent.getActivity(context,
|
||||
appWidgetId, openIntent, PendingIntent.FLAG_UPDATE_CURRENT);
|
||||
views.setOnClickPendingIntent(R.id.widgetTimetableHeader, pendingOpenIntent);
|
||||
|
||||
if (!unified)
|
||||
views.setScrollPosition(R.id.widgetTimetableListView, scrollPos);
|
||||
}
|
||||
}
|
||||
|
||||
appWidgetManager.updateAppWidget(appWidgetId, views);
|
||||
appWidgetManager.notifyAppWidgetViewDataChanged(appWidgetId, R.id.widgetTimetableListView);
|
||||
}
|
||||
//modeInt++;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEnabled(Context context) {
|
||||
// Enter relevant functionality for when the first widget is created
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDeleted(Context context, int[] appWidgetIds) {
|
||||
App app = (App) context.getApplicationContext();
|
||||
for (int appWidgetId: appWidgetIds) {
|
||||
app.appConfig.widgetTimetableConfigs.remove(appWidgetId);
|
||||
}
|
||||
app.saveConfig("widgetTimetableConfigs");
|
||||
}
|
||||
}
|
||||
|
371
app/src/main/java/pl/szczodrzynski/edziennik/WidgetTimetable.kt
Normal file
371
app/src/main/java/pl/szczodrzynski/edziennik/WidgetTimetable.kt
Normal file
@ -0,0 +1,371 @@
|
||||
package pl.szczodrzynski.edziennik
|
||||
|
||||
import android.app.PendingIntent
|
||||
import android.appwidget.AppWidgetManager
|
||||
import android.appwidget.AppWidgetProvider
|
||||
import android.content.ComponentName
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.Canvas
|
||||
import android.graphics.Color
|
||||
import android.graphics.PorterDuff
|
||||
import android.graphics.drawable.BitmapDrawable
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.util.SparseArray
|
||||
import android.view.View
|
||||
import android.widget.RemoteViews
|
||||
import com.mikepenz.iconics.IconicsDrawable
|
||||
import com.mikepenz.iconics.typeface.library.community.material.CommunityMaterial
|
||||
import com.mikepenz.iconics.utils.colorInt
|
||||
import com.mikepenz.iconics.utils.sizeDp
|
||||
import pl.szczodrzynski.edziennik.api.v2.events.task.EdziennikTask
|
||||
import pl.szczodrzynski.edziennik.data.db.modules.events.Event.TYPE_HOMEWORK
|
||||
import pl.szczodrzynski.edziennik.data.db.modules.timetable.Lesson
|
||||
import pl.szczodrzynski.edziennik.utils.models.Date
|
||||
import pl.szczodrzynski.edziennik.utils.models.ItemWidgetTimetableModel
|
||||
import pl.szczodrzynski.edziennik.utils.models.Time
|
||||
import pl.szczodrzynski.edziennik.utils.models.Week
|
||||
import pl.szczodrzynski.edziennik.widgets.WidgetConfig
|
||||
import pl.szczodrzynski.edziennik.widgets.timetable.LessonDialogActivity
|
||||
import pl.szczodrzynski.edziennik.widgets.timetable.WidgetTimetableService
|
||||
import java.lang.reflect.InvocationTargetException
|
||||
|
||||
|
||||
class WidgetTimetable : AppWidgetProvider() {
|
||||
|
||||
override fun onReceive(context: Context, intent: Intent) {
|
||||
if (ACTION_SYNC_DATA == intent.action) {
|
||||
EdziennikTask.sync().enqueue(context)
|
||||
}
|
||||
super.onReceive(context, intent)
|
||||
}
|
||||
|
||||
override fun onUpdate(context: Context, appWidgetManager: AppWidgetManager, appWidgetIds: IntArray) {
|
||||
val thisWidget = ComponentName(context, WidgetTimetable::class.java)
|
||||
|
||||
timetables = SparseArray()
|
||||
//timetables.clear();
|
||||
|
||||
val app = context.applicationContext as App
|
||||
|
||||
var bellSyncDiffMillis: Long = 0
|
||||
if (app.appConfig.bellSyncDiff != null) {
|
||||
bellSyncDiffMillis = (app.appConfig.bellSyncDiff.hour * 60 * 60 * 1000 + app.appConfig.bellSyncDiff.minute * 60 * 1000 + app.appConfig.bellSyncDiff.second * 1000).toLong()
|
||||
bellSyncDiffMillis *= app.appConfig.bellSyncMultiplier.toLong()
|
||||
bellSyncDiffMillis *= -1
|
||||
}
|
||||
|
||||
val allWidgetIds = appWidgetManager.getAppWidgetIds(thisWidget)
|
||||
|
||||
allWidgetIds?.forEach { appWidgetId ->
|
||||
var widgetConfig = app.appConfig.widgetTimetableConfigs[appWidgetId]
|
||||
if (widgetConfig == null) {
|
||||
widgetConfig = WidgetConfig(app.profileFirstId())
|
||||
app.appConfig.widgetTimetableConfigs[appWidgetId] = widgetConfig
|
||||
app.appConfig.savePending = true
|
||||
}
|
||||
|
||||
val views = if (widgetConfig.bigStyle) {
|
||||
RemoteViews(context.packageName, if (widgetConfig.darkTheme) R.layout.widget_timetable_dark_big else R.layout.widget_timetable_big)
|
||||
} else {
|
||||
RemoteViews(context.packageName, if (widgetConfig.darkTheme) R.layout.widget_timetable_dark else R.layout.widget_timetable)
|
||||
}
|
||||
|
||||
val refreshIntent = Intent(app, WidgetTimetable::class.java)
|
||||
refreshIntent.action = AppWidgetManager.ACTION_APPWIDGET_UPDATE
|
||||
refreshIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, appWidgetIds)
|
||||
val pendingRefreshIntent = PendingIntent.getBroadcast(context,
|
||||
0, refreshIntent, PendingIntent.FLAG_UPDATE_CURRENT)
|
||||
views.setOnClickPendingIntent(R.id.widgetTimetableRefresh, pendingRefreshIntent)
|
||||
|
||||
views.setOnClickPendingIntent(R.id.widgetTimetableSync, getPendingSelfIntent(context, ACTION_SYNC_DATA))
|
||||
|
||||
views.setImageViewBitmap(R.id.widgetTimetableRefresh, IconicsDrawable(context, CommunityMaterial.Icon2.cmd_refresh)
|
||||
.colorInt(Color.WHITE)
|
||||
.sizeDp(if (widgetConfig.bigStyle) 24 else 16).toBitmap())
|
||||
|
||||
views.setImageViewBitmap(R.id.widgetTimetableSync, IconicsDrawable(context, CommunityMaterial.Icon.cmd_download_outline)
|
||||
.colorInt(Color.WHITE)
|
||||
.sizeDp(if (widgetConfig.bigStyle) 24 else 16).toBitmap())
|
||||
|
||||
prepareAppWidget(app, appWidgetId, views, widgetConfig, bellSyncDiffMillis)
|
||||
|
||||
appWidgetManager.updateAppWidget(appWidgetId, views)
|
||||
appWidgetManager.notifyAppWidgetViewDataChanged(appWidgetId, R.id.widgetTimetableListView)
|
||||
}
|
||||
}
|
||||
|
||||
private fun prepareAppWidget(
|
||||
app: App,
|
||||
appWidgetId: Int,
|
||||
views: RemoteViews,
|
||||
widgetConfig: WidgetConfig,
|
||||
bellSyncDiffMillis: Long
|
||||
) {
|
||||
// get the current bell-synced time
|
||||
val now = Time.fromMillis(Time.getNow().inMillis + bellSyncDiffMillis)
|
||||
|
||||
// set the widget transparency
|
||||
val mode = PorterDuff.Mode.DST_IN
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P) {
|
||||
// this code seems to crash the launcher on >= P
|
||||
val transparency = widgetConfig.opacity //0...1
|
||||
val colorFilter = 0x01000000L * (255f * transparency).toLong()
|
||||
try {
|
||||
val declaredMethods = Class.forName("android.widget.RemoteViews").declaredMethods
|
||||
val len = declaredMethods.size
|
||||
if (len > 0) {
|
||||
for (m in 0 until len) {
|
||||
val method = declaredMethods[m]
|
||||
if (method.name == "setDrawableParameters") {
|
||||
method.isAccessible = true
|
||||
method.invoke(views, R.id.widgetTimetableListView, true, -1, colorFilter.toInt(), mode, -1)
|
||||
method.invoke(views, R.id.widgetTimetableHeader, true, -1, colorFilter.toInt(), mode, -1)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e: ClassNotFoundException) {
|
||||
e.printStackTrace()
|
||||
} catch (e: InvocationTargetException) {
|
||||
e.printStackTrace()
|
||||
} catch (e: IllegalAccessException) {
|
||||
e.printStackTrace()
|
||||
} catch (e: IllegalArgumentException) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
|
||||
val unified = widgetConfig.profileId == -1
|
||||
|
||||
// get all profiles or one profile with the specified id
|
||||
val profileList = if (unified)
|
||||
app.db.profileDao().allNow.filterOutArchived()
|
||||
else
|
||||
listOfNotNull(app.db.profileDao().getByIdNow(widgetConfig.profileId))
|
||||
|
||||
// no profile was found
|
||||
if (profileList.isEmpty()) {
|
||||
views.setViewVisibility(R.id.widgetTimetableLoading, View.VISIBLE)
|
||||
views.setTextViewText(R.id.widgetTimetableLoading, app.getString(R.string.widget_timetable_profile_doesnt_exist))
|
||||
return
|
||||
}
|
||||
|
||||
views.setViewVisibility(R.id.widgetTimetableLoading, View.GONE)
|
||||
|
||||
// set lesson search bounds
|
||||
val today = Date.getToday()
|
||||
val searchEnd = today.clone().stepForward(0, 0, 7)
|
||||
|
||||
var scrollPos = 0
|
||||
|
||||
var profileId: Int? = null
|
||||
var displayingDate: Date? = null
|
||||
|
||||
val models = mutableListOf<ItemWidgetTimetableModel>()
|
||||
|
||||
// get all lessons within the search bounds
|
||||
val lessonList = app.db.timetableDao().getBetweenDatesNow(today, searchEnd)
|
||||
|
||||
for (profile in profileList) {
|
||||
|
||||
// add a profile separator with its name
|
||||
if (unified) {
|
||||
val separator = ItemWidgetTimetableModel()
|
||||
separator.profileId = profile.id
|
||||
separator.bigStyle = widgetConfig.bigStyle
|
||||
separator.darkTheme = widgetConfig.darkTheme
|
||||
separator.separatorProfileName = profile.name
|
||||
models.add(separator)
|
||||
}
|
||||
|
||||
// search for lessons to display
|
||||
val timetableDate = Date.getToday()
|
||||
var checkedDays = 0
|
||||
var lessons = lessonList.filter { it.profileId == profile.id && it.displayDate == timetableDate && it.type != Lesson.TYPE_NO_LESSONS }
|
||||
while ((lessons.isEmpty() || lessons.none {
|
||||
it.displayDate != today || (it.displayDate == today && it.displayEndTime != null && it.displayEndTime!! >= now)
|
||||
}) && checkedDays < 7) {
|
||||
timetableDate.stepForward(0, 0, 1)
|
||||
lessons = lessonList.filter { it.profileId == profile.id && it.displayDate == timetableDate && it.type != Lesson.TYPE_NO_LESSONS }
|
||||
checkedDays++
|
||||
}
|
||||
|
||||
// set the displayingDate to show in the header
|
||||
if (!unified) {
|
||||
if (lessons.isNotEmpty())
|
||||
displayingDate = timetableDate
|
||||
profileId = profile.id
|
||||
}
|
||||
|
||||
// get all events for the current date
|
||||
val events = app.db.eventDao().getAllByDateNow(profile.id, timetableDate)?.filterNotNull() ?: emptyList()
|
||||
|
||||
lessons.forEachIndexed { pos, lesson ->
|
||||
val model = ItemWidgetTimetableModel()
|
||||
|
||||
model.bigStyle = widgetConfig.bigStyle
|
||||
model.darkTheme = widgetConfig.darkTheme
|
||||
|
||||
model.profileId = profile.id
|
||||
|
||||
model.lessonId = lesson.id
|
||||
model.lessonDate = timetableDate
|
||||
model.startTime = lesson.displayStartTime
|
||||
model.endTime = lesson.displayEndTime
|
||||
|
||||
// check if the lesson has already passed or it's currently in progress
|
||||
if (lesson.displayDate == today) {
|
||||
lesson.displayEndTime?.let { endTime ->
|
||||
model.lessonPassed = now > endTime
|
||||
lesson.displayStartTime?.let { startTime ->
|
||||
model.lessonCurrent = now in startTime..endTime
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// set where should the list view scroll to
|
||||
if (model.lessonCurrent) {
|
||||
scrollPos = pos
|
||||
} else if (model.lessonPassed) {
|
||||
scrollPos = pos + 1
|
||||
}
|
||||
|
||||
// set the subject and classroom name
|
||||
model.subjectName = lesson.displaySubjectName
|
||||
model.classroomName = lesson.displayClassroom
|
||||
|
||||
// set the bell sync to calculate progress in ListProvider
|
||||
model.bellSyncDiffMillis = bellSyncDiffMillis
|
||||
|
||||
// make the model aware of the lesson type
|
||||
when (lesson.type) {
|
||||
Lesson.TYPE_CANCELLED -> {
|
||||
model.lessonCancelled = true
|
||||
}
|
||||
Lesson.TYPE_CHANGE,
|
||||
Lesson.TYPE_SHIFTED_SOURCE,
|
||||
Lesson.TYPE_SHIFTED_TARGET -> {
|
||||
model.lessonChange = true
|
||||
}
|
||||
}
|
||||
|
||||
// add every event on this lesson
|
||||
for (event in events) {
|
||||
if (event.startTime != null && event.startTime != lesson.displayStartTime)
|
||||
continue
|
||||
model.eventColors.add(if (event.type == TYPE_HOMEWORK) ItemWidgetTimetableModel.EVENT_COLOR_HOMEWORK else event.getColor())
|
||||
}
|
||||
|
||||
models += model
|
||||
}
|
||||
}
|
||||
|
||||
if (unified) {
|
||||
// set the title for an unified widget
|
||||
views.setTextViewText(R.id.widgetTimetableTitle, app.getString(R.string.widget_timetable_title_unified))
|
||||
views.setViewVisibility(R.id.widgetTimetableSubtitle, View.GONE)
|
||||
} else {
|
||||
// set the title to present the widget's profile
|
||||
views.setTextViewText(R.id.widgetTimetableTitle, profileList[0].name)
|
||||
views.setViewVisibility(R.id.widgetTimetableTitle, View.VISIBLE)
|
||||
// make the subtitle show current date for these lessons
|
||||
displayingDate?.let {
|
||||
when (Date.diffDays(it, Date.getToday())) {
|
||||
0 -> views.setTextViewText(R.id.widgetTimetableSubtitle, app.getString(R.string.day_today_format, Week.getFullDayName(it.weekDay)))
|
||||
1 -> views.setTextViewText(R.id.widgetTimetableSubtitle, app.getString(R.string.day_tomorrow_format, Week.getFullDayName(it.weekDay)))
|
||||
else -> views.setTextViewText(R.id.widgetTimetableSubtitle, Week.getFullDayName(it.weekDay) + " " + it.formattedString)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// intent running when the header is clicked
|
||||
val openIntent = Intent(app, MainActivity::class.java)
|
||||
openIntent.action = "android.intent.action.MAIN"
|
||||
if (!unified) {
|
||||
// per-profile widget should redirect to it + correct day
|
||||
profileId?.let {
|
||||
openIntent.putExtra("profileId", it)
|
||||
}
|
||||
displayingDate?.let {
|
||||
openIntent.putExtra("timetableDate", it.value)
|
||||
}
|
||||
}
|
||||
openIntent.putExtra("fragmentId", MainActivity.DRAWER_ITEM_TIMETABLE)
|
||||
val pendingOpenIntent = PendingIntent.getActivity(app, appWidgetId, openIntent, 0)
|
||||
views.setOnClickPendingIntent(R.id.widgetTimetableHeader, pendingOpenIntent)
|
||||
|
||||
if (lessonList.isEmpty()) {
|
||||
views.setViewVisibility(R.id.widgetTimetableLoading, View.VISIBLE)
|
||||
views.setRemoteAdapter(R.id.widgetTimetableListView, Intent())
|
||||
views.setTextViewText(R.id.widgetTimetableLoading, app.getString(R.string.widget_timetable_no_lessons))
|
||||
return
|
||||
}
|
||||
|
||||
timetables!!.put(appWidgetId, models)
|
||||
|
||||
// apply the list service to the list view
|
||||
val listIntent = Intent(app, WidgetTimetableService::class.java)
|
||||
listIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId)
|
||||
listIntent.data = Uri.parse(listIntent.toUri(Intent.URI_INTENT_SCHEME))
|
||||
views.setRemoteAdapter(R.id.widgetTimetableListView, listIntent)
|
||||
|
||||
// create an intent used to display the lesson details dialog
|
||||
val intentTemplate = Intent(app, LessonDialogActivity::class.java)
|
||||
intentTemplate.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK)
|
||||
val pendingIntentTimetable = PendingIntent.getActivity(app, appWidgetId, intentTemplate, 0)
|
||||
views.setPendingIntentTemplate(R.id.widgetTimetableListView, pendingIntentTimetable)
|
||||
|
||||
if (!unified)
|
||||
views.setScrollPosition(R.id.widgetTimetableListView, scrollPos)
|
||||
}
|
||||
|
||||
override fun onEnabled(context: Context) {
|
||||
// Enter relevant functionality for when the first widget is created
|
||||
}
|
||||
|
||||
override fun onDeleted(context: Context, appWidgetIds: IntArray) {
|
||||
val app = context.applicationContext as App
|
||||
for (appWidgetId in appWidgetIds) {
|
||||
app.appConfig.widgetTimetableConfigs.remove(appWidgetId)
|
||||
}
|
||||
app.saveConfig("widgetTimetableConfigs")
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
|
||||
val ACTION_SYNC_DATA = "ACTION_SYNC_DATA"
|
||||
private val TAG = "WidgetTimetable"
|
||||
private val modeInt = 0
|
||||
|
||||
var timetables: SparseArray<List<ItemWidgetTimetableModel>>? = null
|
||||
|
||||
fun getPendingSelfIntent(context: Context, action: String): PendingIntent {
|
||||
val intent = Intent(context, WidgetTimetable::class.java)
|
||||
intent.action = action
|
||||
return getPendingSelfIntent(context, intent)
|
||||
}
|
||||
|
||||
fun getPendingSelfIntent(context: Context, intent: Intent): PendingIntent {
|
||||
return PendingIntent.getBroadcast(context, 0, intent, 0)
|
||||
}
|
||||
|
||||
fun drawableToBitmap(drawable: Drawable): Bitmap {
|
||||
|
||||
if (drawable is BitmapDrawable) {
|
||||
return drawable.bitmap
|
||||
}
|
||||
|
||||
val bitmap = Bitmap.createBitmap(drawable.intrinsicWidth, drawable.intrinsicHeight, Bitmap.Config.ARGB_8888)
|
||||
val canvas = Canvas(bitmap)
|
||||
drawable.setBounds(0, 0, canvas.width, canvas.height)
|
||||
drawable.draw(canvas)
|
||||
|
||||
return bitmap
|
||||
}
|
||||
}
|
||||
}
|
@ -60,6 +60,9 @@ class ApiService : Service() {
|
||||
|
||||
private val notification by lazy { EdziennikNotification(this) }
|
||||
|
||||
private var lastEventTime = System.currentTimeMillis()
|
||||
private var taskCancelTries = 0
|
||||
|
||||
/* ______ _ _ _ _ _____ _ _ _ _
|
||||
| ____| | | (_) (_) | / ____| | | | | | |
|
||||
| |__ __| |_____ ___ _ __ _ __ _| | __ | | __ _| | | |__ __ _ ___| | __
|
||||
@ -68,22 +71,17 @@ class ApiService : Service() {
|
||||
|______\__,_/___|_|\___|_| |_|_| |_|_|_|\_\ \_____\__,_|_|_|_.__/ \__,_|\___|_|\*/
|
||||
private val taskCallback = object : EdziennikCallback {
|
||||
override fun onCompleted() {
|
||||
lastEventTime = System.currentTimeMillis()
|
||||
d(TAG, "Task $taskRunningId (profile $taskProfileId) - $taskProgressText - finished")
|
||||
//if (!taskCancelled) {
|
||||
EventBus.getDefault().post(ApiTaskFinishedEvent(taskProfileId))
|
||||
//}
|
||||
taskIsRunning = false
|
||||
taskRunningId = -1
|
||||
taskRunning = null
|
||||
taskProfileId = -1
|
||||
taskProgress = -1f
|
||||
taskProgressText = null
|
||||
EventBus.getDefault().post(ApiTaskFinishedEvent(taskProfileId))
|
||||
clearTask()
|
||||
|
||||
notification.setIdle().post()
|
||||
runTask()
|
||||
}
|
||||
|
||||
override fun onError(apiError: ApiError) {
|
||||
lastEventTime = System.currentTimeMillis()
|
||||
d(TAG, "Task $taskRunningId threw an error - $apiError")
|
||||
apiError.profileId = taskProfileId
|
||||
EventBus.getDefault().post(ApiTaskErrorEvent(apiError))
|
||||
@ -92,9 +90,7 @@ class ApiService : Service() {
|
||||
if (apiError.isCritical) {
|
||||
taskRunning?.cancel()
|
||||
notification.setCriticalError().post()
|
||||
taskRunning = null
|
||||
taskIsRunning = false
|
||||
taskRunningId = -1
|
||||
clearTask()
|
||||
runTask()
|
||||
}
|
||||
else {
|
||||
@ -103,6 +99,7 @@ class ApiService : Service() {
|
||||
}
|
||||
|
||||
override fun onProgress(step: Float) {
|
||||
lastEventTime = System.currentTimeMillis()
|
||||
if (step <= 0)
|
||||
return
|
||||
if (taskProgress < 0)
|
||||
@ -115,6 +112,7 @@ class ApiService : Service() {
|
||||
}
|
||||
|
||||
override fun onStartProgress(stringRes: Int) {
|
||||
lastEventTime = System.currentTimeMillis()
|
||||
taskProgressText = getString(stringRes)
|
||||
d(TAG, "Task $taskRunningId progress: $taskProgressText")
|
||||
EventBus.getDefault().post(ApiTaskProgressEvent(taskProfileId, taskProgress, taskProgressText))
|
||||
@ -129,6 +127,7 @@ class ApiService : Service() {
|
||||
| | (_| \__ \ < | __/> < __/ (__| |_| | |_| | (_) | | | |
|
||||
|_|\__,_|___/_|\_\ \___/_/\_\___|\___|\__,_|\__|_|\___/|_| |*/
|
||||
private fun runTask() {
|
||||
checkIfTaskFrozen()
|
||||
if (taskIsRunning)
|
||||
return
|
||||
if (taskCancelled || serviceClosed || (taskQueue.isEmpty() && finishingTaskQueue.isEmpty())) {
|
||||
@ -137,6 +136,8 @@ class ApiService : Service() {
|
||||
return
|
||||
}
|
||||
|
||||
lastEventTime = System.currentTimeMillis()
|
||||
|
||||
val task = if (taskQueue.isEmpty()) finishingTaskQueue.removeAt(0) else taskQueue.removeAt(0)
|
||||
task.taskId = ++taskMaximumId
|
||||
task.prepare(app)
|
||||
@ -166,6 +167,48 @@ class ApiService : Service() {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a task is inactive for more than 30 seconds.
|
||||
* If the user tries to cancel a task with no success at least three times,
|
||||
* consider it frozen as well.
|
||||
*
|
||||
* This usually means it is broken and won't become active again.
|
||||
* This method cancels the task and removes any pointers to it.
|
||||
*/
|
||||
private fun checkIfTaskFrozen(): Boolean {
|
||||
if (System.currentTimeMillis() - lastEventTime > 30*1000
|
||||
|| taskCancelTries >= 3) {
|
||||
val time = System.currentTimeMillis() - lastEventTime
|
||||
d(TAG, "!!! Task $taskRunningId froze for $time ms. $taskRunning")
|
||||
clearTask()
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops the service if the current task is frozen/broken.
|
||||
*/
|
||||
private fun stopIfTaskFrozen() {
|
||||
if (checkIfTaskFrozen()) {
|
||||
stopSelf()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove any task descriptors or pointers from the service.
|
||||
*/
|
||||
private fun clearTask() {
|
||||
taskIsRunning = false
|
||||
taskRunningId = -1
|
||||
taskRunning = null
|
||||
taskProfileId = -1
|
||||
taskProgress = -1f
|
||||
taskProgressText = null
|
||||
taskCancelled = false
|
||||
taskCancelTries = 0
|
||||
}
|
||||
|
||||
private fun allCompleted() {
|
||||
EventBus.getDefault().post(ApiTaskAllFinishedEvent())
|
||||
stopSelf()
|
||||
@ -211,8 +254,10 @@ class ApiService : Service() {
|
||||
EventBus.getDefault().removeStickyEvent(request)
|
||||
d(TAG, request.toString())
|
||||
|
||||
taskCancelTries++
|
||||
taskCancelled = true
|
||||
taskRunning?.cancel()
|
||||
stopIfTaskFrozen()
|
||||
}
|
||||
@Subscribe(sticky = true, threadMode = ThreadMode.ASYNC)
|
||||
fun onServiceCloseRequest(request: ServiceCloseRequest) {
|
||||
|
@ -45,8 +45,8 @@ class DataNotifications(val data: Data) {
|
||||
return@run
|
||||
}
|
||||
|
||||
for (change in app.db.lessonChangeDao().getNotNotifiedNow(profileId)) {
|
||||
val text = app.getString(R.string.notification_lesson_change_format, change.changeTypeStr(app), if (change.lessonDate == null) "" else change.lessonDate!!.formattedString, change.subjectLongName)
|
||||
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,
|
||||
@ -54,8 +54,8 @@ class DataNotifications(val data: Data) {
|
||||
profileId = profileId,
|
||||
profileName = profileName,
|
||||
viewId = DRAWER_ITEM_TIMETABLE,
|
||||
addedDate = change.addedDate
|
||||
).addExtra("timetableDate", change.lessonDate?.value?.toLong())
|
||||
addedDate = lesson.addedDate
|
||||
).addExtra("timetableDate", lesson.displayDate?.stringY_m_d ?: "")
|
||||
}
|
||||
|
||||
for (event in app.db.eventDao().getNotNotifiedNow(profileId)) {
|
||||
@ -186,10 +186,10 @@ class DataNotifications(val data: Data) {
|
||||
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
|
||||
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)
|
||||
luckyNumber.date.value == todayValue + 1 -> // LN for tomorrow
|
||||
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)
|
||||
@ -207,4 +207,4 @@ class DataNotifications(val data: Data) {
|
||||
|
||||
data.db.metadataDao().setAllNotified(profileId, true)
|
||||
}}
|
||||
}
|
||||
}
|
||||
|
@ -39,13 +39,16 @@ const val ERROR_REQUEST_HTTP_404 = 54
|
||||
const val ERROR_REQUEST_HTTP_405 = 55
|
||||
const val ERROR_REQUEST_HTTP_410 = 56
|
||||
const val ERROR_REQUEST_HTTP_500 = 57
|
||||
const val ERROR_REQUEST_FAILURE_HOSTNAME_NOT_FOUND = 60
|
||||
const val ERROR_REQUEST_FAILURE_TIMEOUT = 61
|
||||
const val ERROR_REQUEST_FAILURE_NO_INTERNET = 62
|
||||
const val ERROR_RESPONSE_EMPTY = 100
|
||||
const val ERROR_LOGIN_DATA_MISSING = 101
|
||||
const val ERROR_LOGIN_DATA_INVALID = 102
|
||||
const val ERROR_PROFILE_MISSING = 105
|
||||
const val ERROR_INVALID_LOGIN_MODE = 110
|
||||
const val ERROR_LOGIN_METHOD_NOT_SATISFIED = 111
|
||||
const val ERROR_NOT_IMPLEMENTED = 112
|
||||
const val ERROR_FILE_DOWNLOAD = 113
|
||||
|
||||
const val ERROR_NO_STUDENTS_IN_ACCOUNT = 115
|
||||
|
||||
@ -99,6 +102,13 @@ const val ERROR_LOGIN_LIBRUS_PORTAL_REFRESH_INVALID = 172
|
||||
const val ERROR_LOGIN_LIBRUS_PORTAL_REFRESH_REVOKED = 173
|
||||
const val ERROR_LIBRUS_SYNERGIA_OTHER = 174
|
||||
const val ERROR_LIBRUS_SYNERGIA_MAINTENANCE = 175
|
||||
const val ERROR_LIBRUS_MESSAGES_MAINTENANCE = 176
|
||||
const val ERROR_LIBRUS_MESSAGES_ERROR = 177
|
||||
const val ERROR_LIBRUS_MESSAGES_OTHER = 178
|
||||
const val ERROR_LOGIN_LIBRUS_MESSAGES_INVALID_LOGIN = 179
|
||||
const val ERROR_LOGIN_LIBRUS_PORTAL_INVALID_LOGIN = 180
|
||||
const val ERROR_LIBRUS_API_MAINTENANCE = 181
|
||||
const val ERROR_LIBRUS_PORTAL_MAINTENANCE = 182
|
||||
|
||||
const val ERROR_LOGIN_MOBIDZIENNIK_WEB_INVALID_LOGIN = 201
|
||||
const val ERROR_LOGIN_MOBIDZIENNIK_WEB_OLD_PASSWORD = 202
|
||||
@ -109,7 +119,7 @@ const val ERROR_LOGIN_MOBIDZIENNIK_WEB_INVALID_ADDRESS = 206
|
||||
const val ERROR_LOGIN_MOBIDZIENNIK_WEB_OTHER = 210
|
||||
const val ERROR_MOBIDZIENNIK_WEB_ACCESS_DENIED = 211
|
||||
const val ERROR_MOBIDZIENNIK_WEB_NO_SESSION_KEY = 212
|
||||
const val ERROR_MOBIDZIENNIK_WEB_NO_SESSION_VALUE = 212
|
||||
const val ERROR_MOBIDZIENNIK_WEB_NO_SESSION_VALUE = 216
|
||||
const val ERROR_MOBIDZIENNIK_WEB_NO_SERVER_ID = 213
|
||||
const val ERROR_MOBIDZIENNIK_WEB_INVALID_RESPONSE = 214
|
||||
const val ERROR_LOGIN_MOBIDZIENNIK_WEB_NO_SESSION_ID = 215
|
||||
@ -163,3 +173,5 @@ const val EXCEPTION_LIBRUS_MESSAGES_REQUEST = 911
|
||||
const val EXCEPTION_IDZIENNIK_WEB_REQUEST = 912
|
||||
const val EXCEPTION_IDZIENNIK_WEB_API_REQUEST = 913
|
||||
const val EXCEPTION_IDZIENNIK_API_REQUEST = 914
|
||||
|
||||
const val LOGIN_NO_ARGUMENTS = 1201
|
||||
|
@ -7,39 +7,36 @@ package pl.szczodrzynski.edziennik.api.v2
|
||||
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_BEHAVIOUR
|
||||
import pl.szczodrzynski.edziennik.MainActivity.Companion.DRAWER_ITEM_TIMETABLE
|
||||
import pl.szczodrzynski.edziennik.data.db.modules.messages.Message.TYPE_RECEIVED
|
||||
import pl.szczodrzynski.edziennik.data.db.modules.messages.Message.TYPE_SENT
|
||||
|
||||
const val FEATURE_ALL = 0
|
||||
const val FEATURE_TIMETABLE = 1
|
||||
const val FEATURE_AGENDA = 2
|
||||
const val FEATURE_GRADES = 3
|
||||
const val FEATURE_HOMEWORK = 4
|
||||
const val FEATURE_BEHAVIOUR = 5
|
||||
const val FEATURE_ATTENDANCE = 6
|
||||
const val FEATURE_MESSAGES_INBOX = 7
|
||||
const val FEATURE_MESSAGES_SENT = 8
|
||||
const val FEATURE_ANNOUNCEMENTS = 9
|
||||
internal const val FEATURE_TIMETABLE = 1
|
||||
internal const val FEATURE_AGENDA = 2
|
||||
internal const val FEATURE_GRADES = 3
|
||||
internal const val FEATURE_HOMEWORK = 4
|
||||
internal const val FEATURE_BEHAVIOUR = 5
|
||||
internal const val FEATURE_ATTENDANCE = 6
|
||||
internal const val FEATURE_MESSAGES_INBOX = 7
|
||||
internal const val FEATURE_MESSAGES_SENT = 8
|
||||
internal const val FEATURE_ANNOUNCEMENTS = 9
|
||||
|
||||
const val FEATURE_ALWAYS_NEEDED = 100
|
||||
const val FEATURE_STUDENT_INFO = 101
|
||||
const val FEATURE_STUDENT_NUMBER = 109
|
||||
const val FEATURE_SCHOOL_INFO = 102
|
||||
const val FEATURE_CLASS_INFO = 103
|
||||
const val FEATURE_TEAM_INFO = 104
|
||||
const val FEATURE_LUCKY_NUMBER = 105
|
||||
const val FEATURE_TEACHERS = 106
|
||||
const val FEATURE_SUBJECTS = 107
|
||||
const val FEATURE_CLASSROOMS = 108
|
||||
const val FEATURE_PUSH_CONFIG = 120
|
||||
|
||||
const val FEATURE_MESSAGE_GET = 201
|
||||
internal const val FEATURE_ALWAYS_NEEDED = 100
|
||||
internal const val FEATURE_STUDENT_INFO = 101
|
||||
internal const val FEATURE_STUDENT_NUMBER = 109
|
||||
internal const val FEATURE_SCHOOL_INFO = 102
|
||||
internal const val FEATURE_CLASS_INFO = 103
|
||||
internal const val FEATURE_TEAM_INFO = 104
|
||||
internal const val FEATURE_LUCKY_NUMBER = 105
|
||||
internal const val FEATURE_TEACHERS = 106
|
||||
internal const val FEATURE_SUBJECTS = 107
|
||||
internal const val FEATURE_CLASSROOMS = 108
|
||||
internal const val FEATURE_PUSH_CONFIG = 120
|
||||
|
||||
object Features {
|
||||
private fun getAllNecessary(): List<Int> = listOf(
|
||||
|
@ -37,6 +37,16 @@ object Regexes {
|
||||
"""events: (.+),$""".toRegex(RegexOption.MULTILINE)
|
||||
}
|
||||
|
||||
val MOBIDZIENNIK_MESSAGE_READ_DATE by lazy {
|
||||
"""czas przeczytania:.+?,\s([0-9]+)\s(.+?)\s([0-9]{4}),\sgodzina\s([0-9:]+)""".toRegex(RegexOption.DOT_MATCHES_ALL)
|
||||
}
|
||||
val MOBIDZIENNIK_MESSAGE_SENT_READ_DATE by lazy {
|
||||
""".+?,\s([0-9]+)\s(.+?)\s([0-9]{4}),\sgodzina\s([0-9:]+)""".toRegex(RegexOption.DOT_MATCHES_ALL)
|
||||
}
|
||||
val MOBIDZIENNIK_MESSAGE_ATTACHMENT by lazy {
|
||||
"""href="https://.+?\.mobidziennik.pl/.+?&(?:amp;)?zalacznik=([0-9]+)"(?:.+?<small.+?\(([0-9.]+)\s(M|K|G|)B\))*""".toRegex(RegexOption.DOT_MATCHES_ALL)
|
||||
}
|
||||
|
||||
|
||||
|
||||
val IDZIENNIK_LOGIN_HIDDEN_FIELDS by lazy {
|
||||
@ -60,4 +70,16 @@ object Regexes {
|
||||
val IDZIENNIK_LOGIN_FIRST_STUDENT by lazy {
|
||||
"""<option.*?value="([0-9]+)"\sdata-id-ucznia="([A-z0-9]+?)".*?>(.+?)\s(.+?)\s*\((.+?),\s*(.+?)\)</option>""".toRegex(RegexOption.DOT_MATCHES_ALL)
|
||||
}
|
||||
|
||||
|
||||
|
||||
val VULCAN_SHITFT_ANNOTATION by lazy {
|
||||
"""\(przeniesiona (z|na) lekcj[ię] ([0-9]+), (.+)\)""".toRegex()
|
||||
}
|
||||
|
||||
|
||||
|
||||
val LIBRUS_ATTACHMENT_KEY by lazy {
|
||||
"""singleUseKey=([0-9A-f_]+)""".toRegex()
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,14 @@
|
||||
/*
|
||||
* Copyright (c) Kacper Ziubryniewicz 2019-11-24
|
||||
*/
|
||||
|
||||
package pl.szczodrzynski.edziennik.api.v2.events
|
||||
|
||||
data class AttachmentGetEvent(val profileId: Int, val messageId: Long, val attachmentId: Long,
|
||||
var eventType: Int = TYPE_PROGRESS, val fileName: String? = null,
|
||||
val bytesWritten: Long = 0) {
|
||||
companion object {
|
||||
const val TYPE_PROGRESS = 0
|
||||
const val TYPE_FINISHED = 1
|
||||
}
|
||||
}
|
@ -24,6 +24,7 @@ open class EdziennikTask(override val profileId: Int, val request: Any) : IApiTa
|
||||
fun syncProfileList(profileList: List<Int>) = EdziennikTask(-1, SyncProfileListRequest(profileList))
|
||||
fun messageGet(profileId: Int, message: MessageFull) = EdziennikTask(profileId, MessageGetRequest(message))
|
||||
fun announcementsRead(profileId: Int) = EdziennikTask(profileId, AnnouncementsReadRequest())
|
||||
fun attachmentGet(profileId: Int, messageId: Long, attachmentId: Long, attachmentName: String) = EdziennikTask(profileId, AttachmentGetRequest(messageId, attachmentId, attachmentName))
|
||||
}
|
||||
|
||||
private lateinit var loginStore: LoginStore
|
||||
@ -35,8 +36,7 @@ open class EdziennikTask(override val profileId: Int, val request: Any) : IApiTa
|
||||
loginStore = request.loginStore
|
||||
// save the profile ID and name as the current task's
|
||||
taskName = app.getString(R.string.edziennik_notification_api_first_login_title)
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
// get the requested profile and login store
|
||||
val profile = app.db.profileDao().getByIdNow(profileId)
|
||||
this.profile = profile
|
||||
@ -67,12 +67,14 @@ open class EdziennikTask(override val profileId: Int, val request: Any) : IApiTa
|
||||
|
||||
when (request) {
|
||||
is SyncProfileRequest -> edziennikInterface?.sync(
|
||||
featureIds = request.viewIds?.flatMap { Features.getIdsByView(it.first, it.second) } ?: Features.getAllIds(),
|
||||
featureIds = request.viewIds?.flatMap { Features.getIdsByView(it.first, it.second) }
|
||||
?: Features.getAllIds(),
|
||||
viewId = request.viewIds?.get(0)?.first,
|
||||
arguments = request.arguments)
|
||||
is MessageGetRequest -> edziennikInterface?.getMessage(request.message)
|
||||
is FirstLoginRequest -> edziennikInterface?.firstLogin()
|
||||
is AnnouncementsReadRequest -> edziennikInterface?.markAllAnnouncementsAsRead()
|
||||
is AttachmentGetRequest -> edziennikInterface?.getAttachment(request.messageId, request.attachmentId, request.attachmentName)
|
||||
}
|
||||
}
|
||||
|
||||
@ -90,4 +92,5 @@ open class EdziennikTask(override val profileId: Int, val request: Any) : IApiTa
|
||||
data class SyncProfileListRequest(val profileList: List<Int>)
|
||||
data class MessageGetRequest(val message: MessageFull)
|
||||
class AnnouncementsReadRequest
|
||||
data class AttachmentGetRequest(val messageId: Long, val attachmentId: Long, val attachmentName: String)
|
||||
}
|
||||
|
@ -9,6 +9,7 @@ import pl.szczodrzynski.edziennik.MainActivity
|
||||
import pl.szczodrzynski.edziennik.Notifier.ID_NOTIFICATIONS
|
||||
import pl.szczodrzynski.edziennik.R
|
||||
import pl.szczodrzynski.edziennik.api.v2.interfaces.EdziennikCallback
|
||||
import pl.szczodrzynski.edziennik.data.db.modules.notification.getNotificationTitle
|
||||
import pl.szczodrzynski.edziennik.utils.models.Notification
|
||||
import kotlin.math.min
|
||||
|
||||
@ -33,9 +34,9 @@ class NotifyTask : IApiTask(-1) {
|
||||
val pendingIntent = PendingIntent.getActivity(app, notification.id, intent, 0)
|
||||
val notificationBuilder = NotificationCompat.Builder(app, app.notifier.notificationGroup)
|
||||
// title, text, type, date
|
||||
.setContentTitle(notification.title)
|
||||
.setContentTitle(notification.profileName)
|
||||
.setContentText(notification.text)
|
||||
.setSubText(Notification.stringType(app, notification.type))
|
||||
.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
|
||||
|
@ -138,10 +138,12 @@ class DataIdziennik(app: App, profile: Profile?, loginStore: LoginStore) : Data(
|
||||
val teacher = teacherList.singleOrNull { it.fullName == "$firstName $lastName" }
|
||||
return validateTeacher(teacher, firstName, lastName)
|
||||
}
|
||||
|
||||
fun getTeacher(firstNameChar: Char, lastName: String): Teacher {
|
||||
val teacher = teacherList.singleOrNull { it.shortName == "$firstNameChar.$lastName" }
|
||||
return validateTeacher(teacher, firstNameChar.toString(), lastName)
|
||||
}
|
||||
|
||||
fun getTeacherByLastFirst(nameLastFirst: String): Teacher {
|
||||
val nameParts = nameLastFirst.split(" ")
|
||||
return if (nameParts.size == 1) getTeacher(nameParts[0], "") else getTeacher(nameParts[1], nameParts[0])
|
||||
|
@ -70,6 +70,10 @@ class Idziennik(val app: App, val profile: Profile?, val loginStore: LoginStore,
|
||||
|
||||
}
|
||||
|
||||
override fun getAttachment(messageId: Long, attachmentId: Long, attachmentName: String) {
|
||||
|
||||
}
|
||||
|
||||
override fun firstLogin() {
|
||||
IdziennikFirstLogin(data) {
|
||||
completed()
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) Kuba Szczodrzyński 2019-10-27.
|
||||
* Copyright (c) Kacper Ziubryniewicz 2019-11-22
|
||||
*/
|
||||
|
||||
package pl.szczodrzynski.edziennik.api.v2.idziennik.data.web
|
||||
@ -12,33 +12,38 @@ import pl.szczodrzynski.edziennik.api.v2.idziennik.DataIdziennik
|
||||
import pl.szczodrzynski.edziennik.api.v2.idziennik.ENDPOINT_IDZIENNIK_WEB_TIMETABLE
|
||||
import pl.szczodrzynski.edziennik.api.v2.idziennik.data.IdziennikWeb
|
||||
import pl.szczodrzynski.edziennik.api.v2.models.ApiError
|
||||
import pl.szczodrzynski.edziennik.api.v2.models.DataRemoveModel
|
||||
import pl.szczodrzynski.edziennik.data.db.modules.api.SYNC_ALWAYS
|
||||
import pl.szczodrzynski.edziennik.data.db.modules.lessons.Lesson
|
||||
import pl.szczodrzynski.edziennik.data.db.modules.lessons.LessonChange
|
||||
import pl.szczodrzynski.edziennik.data.db.modules.lessons.LessonChange.TYPE_CANCELLED
|
||||
import pl.szczodrzynski.edziennik.data.db.modules.lessons.LessonChange.TYPE_CHANGE
|
||||
import pl.szczodrzynski.edziennik.data.db.modules.lessons.LessonRange
|
||||
import pl.szczodrzynski.edziennik.data.db.modules.metadata.Metadata
|
||||
import pl.szczodrzynski.edziennik.data.db.modules.timetable.Lesson
|
||||
import pl.szczodrzynski.edziennik.utils.Utils.d
|
||||
import pl.szczodrzynski.edziennik.utils.models.Date
|
||||
import pl.szczodrzynski.edziennik.utils.models.Time
|
||||
import pl.szczodrzynski.edziennik.utils.models.Week
|
||||
|
||||
class IdziennikWebTimetable(override val data: DataIdziennik,
|
||||
val onSuccess: () -> Unit) : IdziennikWeb(data) {
|
||||
val onSuccess: () -> Unit) : IdziennikWeb(data) {
|
||||
companion object {
|
||||
private const val TAG = "IdziennikWebTimetable"
|
||||
}
|
||||
|
||||
init {
|
||||
val weekStart = Week.getWeekStart()
|
||||
init { data.profile?.also { profile ->
|
||||
val currentWeekStart = Week.getWeekStart()
|
||||
|
||||
if (Date.getToday().weekDay > 4) {
|
||||
weekStart.stepForward(0, 0, 7)
|
||||
currentWeekStart.stepForward(0, 0, 7)
|
||||
}
|
||||
|
||||
val getDate = data.arguments?.getString("weekStart") ?: currentWeekStart.stringY_m_d
|
||||
|
||||
val weekStart = Date.fromY_m_d(getDate)
|
||||
val weekEnd = weekStart.clone().stepForward(0, 0, 6)
|
||||
|
||||
webApiGet(TAG, IDZIENNIK_WEB_TIMETABLE, mapOf(
|
||||
"idPozDziennika" to data.registerId,
|
||||
"pidRokSzkolny" to data.schoolYearId,
|
||||
"data" to weekStart.stringY_m_d+"T10:00:00.000Z"
|
||||
"data" to "${weekStart.stringY_m_d}T10:00:00.000Z"
|
||||
)) { result ->
|
||||
val json = result.getJsonObject("d") ?: run {
|
||||
data.error(ApiError(TAG, ERROR_IDZIENNIK_WEB_REQUEST_NO_DATA)
|
||||
@ -56,73 +61,132 @@ class IdziennikWebTimetable(override val data: DataIdziennik,
|
||||
data.lessonRanges[lessonRange.lessonNumber] = lessonRange
|
||||
}
|
||||
|
||||
val dates = mutableSetOf<Int>()
|
||||
val lessons = mutableListOf<Lesson>()
|
||||
|
||||
json.getJsonArray("Przedmioty")?.asJsonObjectList()?.forEach { lesson ->
|
||||
val subject = data.getSubject(
|
||||
lesson.getString("Nazwa") ?: return@forEach,
|
||||
lesson.getLong("Id"),
|
||||
lesson.getString("Skrot") ?: ""
|
||||
)
|
||||
val teacher = data.getTeacherByFDotLast(lesson.getString("Nauczyciel") ?: return@forEach)
|
||||
val weekDay = lesson.getInt("DzienTygodnia")?.minus(1) ?: return@forEach
|
||||
val lessonRange = data.lessonRanges[lesson.getInt("Godzina")?.plus(1) ?: return@forEach]
|
||||
val teacher = data.getTeacherByFDotLast(lesson.getString("Nauczyciel")
|
||||
?: return@forEach)
|
||||
|
||||
val lessonObject = Lesson(
|
||||
profileId,
|
||||
weekDay,
|
||||
lessonRange.startTime,
|
||||
lessonRange.endTime
|
||||
).apply {
|
||||
subjectId = subject.id
|
||||
teacherId = teacher.id
|
||||
teamId = data.teamClass?.id ?: -1
|
||||
classroomName = lesson.getString("NazwaSali") ?: ""
|
||||
val newSubjectName = lesson.getString("PrzedmiotZastepujacy")
|
||||
val newSubject = when (newSubjectName.isNullOrBlank()) {
|
||||
true -> null
|
||||
else -> data.getSubject(newSubjectName, null, newSubjectName)
|
||||
}
|
||||
|
||||
data.lessonList.add(lessonObject)
|
||||
val newTeacherName = lesson.getString("NauZastepujacy")
|
||||
val newTeacher = when (newTeacherName.isNullOrBlank()) {
|
||||
true -> null
|
||||
else -> data.getTeacherByFDotLast(newTeacherName)
|
||||
}
|
||||
|
||||
val weekDay = lesson.getInt("DzienTygodnia")?.minus(1) ?: return@forEach
|
||||
val lessonRange = data.lessonRanges[lesson.getInt("Godzina")?.plus(1)
|
||||
?: return@forEach]
|
||||
val lessonDate = weekStart.clone().stepForward(0, 0, weekDay)
|
||||
val classroom = lesson.getString("NazwaSali")
|
||||
|
||||
val type = lesson.getInt("TypZastepstwa") ?: -1
|
||||
if (type != -1) {
|
||||
// we have a lesson change to process
|
||||
val lessonChangeObject = LessonChange(
|
||||
profileId,
|
||||
weekStart.clone().stepForward(0, 0, weekDay),
|
||||
lessonObject.startTime,
|
||||
lessonObject.endTime
|
||||
)
|
||||
|
||||
lessonChangeObject.teamId = lessonObject.teamId
|
||||
lessonChangeObject.teacherId = lessonObject.teacherId
|
||||
lessonChangeObject.subjectId = lessonObject.subjectId
|
||||
lessonChangeObject.classroomName = lessonObject.classroomName
|
||||
when (type) {
|
||||
0 -> lessonChangeObject.type = TYPE_CANCELLED
|
||||
1, 2, 3, 4, 5 -> {
|
||||
lessonChangeObject.type = TYPE_CHANGE
|
||||
val newTeacher = lesson.getString("NauZastepujacy")
|
||||
val newSubject = lesson.getString("PrzedmiotZastepujacy")
|
||||
if (newTeacher != null) {
|
||||
lessonChangeObject.teacherId = data.getTeacherByFDotLast(newTeacher).id
|
||||
}
|
||||
if (newSubject != null) {
|
||||
lessonChangeObject.subjectId = data.getSubject(newSubject, null, "").id
|
||||
}
|
||||
val lessonObject = Lesson(profileId, -1)
|
||||
|
||||
when (type) {
|
||||
1, 2, 3, 4, 5 -> {
|
||||
lessonObject.apply {
|
||||
this.type = Lesson.TYPE_CHANGE
|
||||
|
||||
this.date = lessonDate
|
||||
this.lessonNumber = lessonRange.lessonNumber
|
||||
this.startTime = lessonRange.startTime
|
||||
this.endTime = lessonRange.endTime
|
||||
this.subjectId = newSubject?.id
|
||||
this.teacherId = newTeacher?.id
|
||||
this.teamId = data.teamClass?.id
|
||||
this.classroom = classroom
|
||||
|
||||
this.oldDate = lessonDate
|
||||
this.oldLessonNumber = lessonRange.lessonNumber
|
||||
this.oldStartTime = lessonRange.startTime
|
||||
this.oldEndTime = lessonRange.endTime
|
||||
this.oldSubjectId = subject.id
|
||||
this.oldTeacherId = teacher.id
|
||||
this.oldTeamId = data.teamClass?.id
|
||||
this.oldClassroom = classroom
|
||||
}
|
||||
}
|
||||
0 -> {
|
||||
lessonObject.apply {
|
||||
this.type = Lesson.TYPE_CANCELLED
|
||||
|
||||
data.lessonChangeList.add(lessonChangeObject)
|
||||
this.oldDate = lessonDate
|
||||
this.oldLessonNumber = lessonRange.lessonNumber
|
||||
this.oldStartTime = lessonRange.startTime
|
||||
this.oldEndTime = lessonRange.endTime
|
||||
this.oldSubjectId = subject.id
|
||||
this.oldTeacherId = teacher.id
|
||||
this.oldTeamId = data.teamClass?.id
|
||||
this.oldClassroom = classroom
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
lessonObject.apply {
|
||||
this.type = Lesson.TYPE_NORMAL
|
||||
|
||||
this.date = lessonDate
|
||||
this.lessonNumber = lessonRange.lessonNumber
|
||||
this.startTime = lessonRange.startTime
|
||||
this.endTime = lessonRange.endTime
|
||||
this.subjectId = subject.id
|
||||
this.teacherId = teacher.id
|
||||
this.teamId = data.teamClass?.id
|
||||
this.classroom = classroom
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
lessonObject.id = lessonObject.buildId()
|
||||
|
||||
dates.add(lessonDate.value)
|
||||
lessons.add(lessonObject)
|
||||
|
||||
val seen = profile.empty || lessonDate < Date.getToday()
|
||||
|
||||
if (lessonObject.type != Lesson.TYPE_NORMAL && lessonDate >= Date.getToday()) {
|
||||
data.metadataList.add(Metadata(
|
||||
profileId,
|
||||
Metadata.TYPE_LESSON_CHANGE,
|
||||
lessonChangeObject.id,
|
||||
profile?.empty ?: false,
|
||||
profile?.empty ?: false,
|
||||
lessonObject.id,
|
||||
seen,
|
||||
seen,
|
||||
System.currentTimeMillis()
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
val date: Date = weekStart.clone()
|
||||
while (date <= weekEnd) {
|
||||
if (!dates.contains(date.value)) {
|
||||
lessons.add(Lesson(profileId, date.value.toLong()).apply {
|
||||
this.type = Lesson.TYPE_NO_LESSONS
|
||||
this.date = date.clone()
|
||||
})
|
||||
}
|
||||
|
||||
date.stepForward(0, 0, 1)
|
||||
}
|
||||
|
||||
d(TAG, "Clearing lessons between ${weekStart.stringY_m_d} and ${weekEnd.stringY_m_d} - timetable downloaded for $getDate")
|
||||
|
||||
data.lessonNewList.addAll(lessons)
|
||||
data.toRemove.add(DataRemoveModel.Timetable.between(weekStart, weekEnd))
|
||||
|
||||
data.setSyncNext(ENDPOINT_IDZIENNIK_WEB_TIMETABLE, SYNC_ALWAYS)
|
||||
onSuccess()
|
||||
}
|
||||
}
|
||||
}}
|
||||
}
|
||||
|
@ -11,6 +11,7 @@ interface EdziennikInterface {
|
||||
fun sync(featureIds: List<Int>, viewId: Int? = null, arguments: JsonObject? = null)
|
||||
fun getMessage(message: MessageFull)
|
||||
fun markAllAnnouncementsAsRead()
|
||||
fun getAttachment(messageId: Long, attachmentId: Long, attachmentName: String)
|
||||
fun firstLogin()
|
||||
fun cancel()
|
||||
}
|
||||
|
@ -10,13 +10,11 @@ import pl.szczodrzynski.edziennik.api.v2.*
|
||||
import pl.szczodrzynski.edziennik.api.v2.interfaces.EdziennikCallback
|
||||
import pl.szczodrzynski.edziennik.api.v2.interfaces.EdziennikInterface
|
||||
import pl.szczodrzynski.edziennik.api.v2.librus.data.LibrusData
|
||||
import pl.szczodrzynski.edziennik.api.v2.librus.data.messages.LibrusMessagesGetAttachment
|
||||
import pl.szczodrzynski.edziennik.api.v2.librus.data.messages.LibrusMessagesGetMessage
|
||||
import pl.szczodrzynski.edziennik.api.v2.librus.data.synergia.LibrusSynergiaMarkAllAnnouncementsAsRead
|
||||
import pl.szczodrzynski.edziennik.api.v2.librus.firstlogin.LibrusFirstLogin
|
||||
import pl.szczodrzynski.edziennik.api.v2.librus.login.LibrusLogin
|
||||
import pl.szczodrzynski.edziennik.api.v2.librus.login.LibrusLoginApi
|
||||
import pl.szczodrzynski.edziennik.api.v2.librus.login.LibrusLoginMessages
|
||||
import pl.szczodrzynski.edziennik.api.v2.librus.login.LibrusLoginSynergia
|
||||
import pl.szczodrzynski.edziennik.api.v2.librus.login.*
|
||||
import pl.szczodrzynski.edziennik.api.v2.models.ApiError
|
||||
import pl.szczodrzynski.edziennik.data.db.modules.login.LoginStore
|
||||
import pl.szczodrzynski.edziennik.data.db.modules.messages.MessageFull
|
||||
@ -82,11 +80,13 @@ class Librus(val app: App, val profile: Profile?, val loginStore: LoginStore, va
|
||||
}
|
||||
|
||||
override fun getMessage(message: MessageFull) {
|
||||
LibrusLoginApi(data) {
|
||||
LibrusLoginSynergia(data) {
|
||||
LibrusLoginMessages(data) {
|
||||
LibrusMessagesGetMessage(data, message) {
|
||||
completed()
|
||||
LibrusLoginPortal(data) {
|
||||
LibrusLoginApi(data) {
|
||||
LibrusLoginSynergia(data) {
|
||||
LibrusLoginMessages(data) {
|
||||
LibrusMessagesGetMessage(data, message) {
|
||||
completed()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -94,10 +94,26 @@ class Librus(val app: App, val profile: Profile?, val loginStore: LoginStore, va
|
||||
}
|
||||
|
||||
override fun markAllAnnouncementsAsRead() {
|
||||
LibrusLoginApi(data) {
|
||||
LibrusLoginSynergia(data) {
|
||||
LibrusSynergiaMarkAllAnnouncementsAsRead(data) {
|
||||
completed()
|
||||
LibrusLoginPortal(data) {
|
||||
LibrusLoginApi(data) {
|
||||
LibrusLoginSynergia(data) {
|
||||
LibrusSynergiaMarkAllAnnouncementsAsRead(data) {
|
||||
completed()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun getAttachment(messageId: Long, attachmentId: Long, attachmentName: String) {
|
||||
LibrusLoginPortal(data) {
|
||||
LibrusLoginApi(data) {
|
||||
LibrusLoginSynergia(data) {
|
||||
LibrusLoginMessages(data) {
|
||||
LibrusMessagesGetAttachment(data, messageId, attachmentId, attachmentName) {
|
||||
completed()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -24,6 +24,7 @@ const val ENDPOINT_LIBRUS_API_DESCRIPTIVE_GC = 1023
|
||||
const val ENDPOINT_LIBRUS_API_TEXT_GC = 1024
|
||||
const val ENDPOINT_LIBRUS_API_DESCRIPTIVE_TEXT_GC = 1025
|
||||
const val ENDPOINT_LIBRUS_API_BEHAVIOUR_GC = 1026
|
||||
const val ENDPOINT_LIBRUS_API_NORMAL_GRADE_COMMENTS = 1030
|
||||
const val ENDPOINT_LIBRUS_API_NORMAL_GRADES = 1031
|
||||
const val ENDPOINT_LIBRUS_API_POINT_GRADES = 1032
|
||||
const val ENDPOINT_LIBRUS_API_DESCRIPTIVE_GRADES = 1033
|
||||
@ -97,6 +98,7 @@ val LibrusFeatures = listOf(
|
||||
ENDPOINT_LIBRUS_API_TEXT_GC to LOGIN_METHOD_LIBRUS_API,
|
||||
ENDPOINT_LIBRUS_API_DESCRIPTIVE_TEXT_GC to LOGIN_METHOD_LIBRUS_API,
|
||||
ENDPOINT_LIBRUS_API_BEHAVIOUR_GC to LOGIN_METHOD_LIBRUS_API,
|
||||
ENDPOINT_LIBRUS_API_NORMAL_GRADE_COMMENTS to LOGIN_METHOD_LIBRUS_API,
|
||||
ENDPOINT_LIBRUS_API_NORMAL_GRADES to LOGIN_METHOD_LIBRUS_API,
|
||||
ENDPOINT_LIBRUS_API_POINT_GRADES to LOGIN_METHOD_LIBRUS_API,
|
||||
ENDPOINT_LIBRUS_API_DESCRIPTIVE_GRADES to LOGIN_METHOD_LIBRUS_API,
|
||||
|
@ -32,6 +32,13 @@ open class LibrusApi(open val data: DataLibrus) {
|
||||
|
||||
val callback = object : JsonCallbackHandler() {
|
||||
override fun onSuccess(json: JsonObject?, response: Response?) {
|
||||
if (response?.code() == HTTP_UNAVAILABLE) {
|
||||
data.error(ApiError(tag, ERROR_LIBRUS_API_MAINTENANCE)
|
||||
.withApiResponse(json)
|
||||
.withResponse(response))
|
||||
return
|
||||
}
|
||||
|
||||
if (json == null && response?.parserErrorBody == null) {
|
||||
data.error(ApiError(TAG, ERROR_RESPONSE_EMPTY)
|
||||
.withResponse(response))
|
||||
@ -104,6 +111,7 @@ open class LibrusApi(open val data: DataLibrus) {
|
||||
.allowErrorCode(HTTP_BAD_REQUEST)
|
||||
.allowErrorCode(HTTP_FORBIDDEN)
|
||||
.allowErrorCode(HTTP_UNAUTHORIZED)
|
||||
.allowErrorCode(HTTP_UNAVAILABLE)
|
||||
.callback(callback)
|
||||
.build()
|
||||
.enqueue()
|
||||
|
@ -85,6 +85,10 @@ class LibrusData(val data: DataLibrus, val onSuccess: () -> Unit) {
|
||||
data.startProgress(R.string.edziennik_progress_endpoint_grades)
|
||||
LibrusApiGrades(data, onSuccess)
|
||||
}
|
||||
ENDPOINT_LIBRUS_API_NORMAL_GRADE_COMMENTS -> {
|
||||
data.startProgress(R.string.edziennik_progress_endpoint_grade_comments)
|
||||
LibrusApiGradeComments(data, onSuccess)
|
||||
}
|
||||
ENDPOINT_LIBRUS_API_NORMAL_GC -> {
|
||||
data.startProgress(R.string.edziennik_progress_endpoint_grade_categories)
|
||||
LibrusApiGradeCategories(data, onSuccess)
|
||||
|
@ -4,9 +4,12 @@
|
||||
|
||||
package pl.szczodrzynski.edziennik.api.v2.librus.data
|
||||
|
||||
import com.google.gson.JsonObject
|
||||
import im.wangchao.mhttp.Request
|
||||
import im.wangchao.mhttp.Response
|
||||
import im.wangchao.mhttp.body.MediaTypeUtils
|
||||
import im.wangchao.mhttp.callback.FileCallbackHandler
|
||||
import im.wangchao.mhttp.callback.JsonCallbackHandler
|
||||
import im.wangchao.mhttp.callback.TextCallbackHandler
|
||||
import okhttp3.Cookie
|
||||
import org.jsoup.Jsoup
|
||||
@ -15,8 +18,8 @@ import org.jsoup.parser.Parser
|
||||
import pl.szczodrzynski.edziennik.api.v2.*
|
||||
import pl.szczodrzynski.edziennik.api.v2.librus.DataLibrus
|
||||
import pl.szczodrzynski.edziennik.api.v2.models.ApiError
|
||||
import pl.szczodrzynski.edziennik.get
|
||||
import pl.szczodrzynski.edziennik.utils.Utils.d
|
||||
import java.io.File
|
||||
import java.io.StringWriter
|
||||
import javax.xml.parsers.DocumentBuilderFactory
|
||||
import javax.xml.transform.OutputKeys
|
||||
@ -43,19 +46,19 @@ open class LibrusMessages(open val data: DataLibrus) {
|
||||
val callback = object : TextCallbackHandler() {
|
||||
override fun onSuccess(text: String?, response: Response?) {
|
||||
if (text.isNullOrEmpty()) {
|
||||
data.error(ApiError(LibrusSynergia.TAG, ERROR_RESPONSE_EMPTY)
|
||||
data.error(ApiError(TAG, ERROR_RESPONSE_EMPTY)
|
||||
.withResponse(response))
|
||||
return
|
||||
}
|
||||
|
||||
// TODO: Finish error handling
|
||||
|
||||
if ("error" in text) {
|
||||
when ("<type>(.*)</type>".toRegex().find(text)?.get(1)) {
|
||||
"eAccessDeny" -> data.error(ApiError(tag, ERROR_LIBRUS_MESSAGES_ACCESS_DENIED)
|
||||
.withResponse(response)
|
||||
.withApiResponse(text))
|
||||
}
|
||||
when {
|
||||
text.contains("<message>Niepoprawny login i/lub hasło.</message>") -> data.error(TAG, ERROR_LOGIN_LIBRUS_MESSAGES_INVALID_LOGIN, response, text)
|
||||
text.contains("stop.png") -> data.error(TAG, ERROR_LIBRUS_SYNERGIA_ACCESS_DENIED, response, text)
|
||||
text.contains("eAccessDeny") -> data.error(TAG, ERROR_LIBRUS_MESSAGES_ACCESS_DENIED, response, text)
|
||||
text.contains("OffLine") -> data.error(TAG, ERROR_LIBRUS_MESSAGES_MAINTENANCE, response, text)
|
||||
text.contains("<status>error</status>") -> data.error(TAG, ERROR_LIBRUS_MESSAGES_ERROR, response, text)
|
||||
text.contains("<type>eVarWhitThisNameNotExists</type>") -> data.error(TAG, ERROR_LIBRUS_MESSAGES_ACCESS_DENIED, response, text)
|
||||
text.contains("<error>") -> data.error(TAG, ERROR_LIBRUS_MESSAGES_OTHER, response, text)
|
||||
}
|
||||
|
||||
try {
|
||||
@ -132,4 +135,95 @@ open class LibrusMessages(open val data: DataLibrus) {
|
||||
.build()
|
||||
.enqueue()
|
||||
}
|
||||
|
||||
fun sandboxGet(tag: String, action: String, parameters: Map<String, Any>? = null,
|
||||
onSuccess: (json: JsonObject) -> Unit) {
|
||||
|
||||
d(tag, "Request: Librus/Messages - $LIBRUS_SANDBOX_URL$action")
|
||||
|
||||
val callback = object : JsonCallbackHandler() {
|
||||
override fun onSuccess(json: JsonObject?, response: Response?) {
|
||||
if (json == null) {
|
||||
data.error(ApiError(TAG, ERROR_RESPONSE_EMPTY)
|
||||
.withResponse(response))
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
onSuccess(json)
|
||||
} catch (e: Exception) {
|
||||
data.error(ApiError(tag, EXCEPTION_LIBRUS_MESSAGES_REQUEST)
|
||||
.withResponse(response)
|
||||
.withThrowable(e)
|
||||
.withApiResponse(json))
|
||||
}
|
||||
}
|
||||
|
||||
override fun onFailure(response: Response?, throwable: Throwable?) {
|
||||
data.error(ApiError(tag, ERROR_REQUEST_FAILURE)
|
||||
.withResponse(response)
|
||||
.withThrowable(throwable))
|
||||
}
|
||||
}
|
||||
|
||||
Request.builder()
|
||||
.url("$LIBRUS_SANDBOX_URL$action")
|
||||
.userAgent(SYNERGIA_USER_AGENT)
|
||||
.apply {
|
||||
parameters?.forEach { (k, v) ->
|
||||
addParameter(k, v)
|
||||
}
|
||||
}
|
||||
.post()
|
||||
.callback(callback)
|
||||
.build()
|
||||
.enqueue()
|
||||
}
|
||||
|
||||
fun sandboxGetFile(tag: String, action: String, targetFile: File, onSuccess: (file: File) -> Unit,
|
||||
onProgress: (written: Long, total: Long) -> Unit) {
|
||||
|
||||
d(tag, "Request: Librus/Messages - $LIBRUS_SANDBOX_URL$action")
|
||||
|
||||
val callback = object : FileCallbackHandler(targetFile) {
|
||||
override fun onSuccess(file: File?, response: Response?) {
|
||||
if (file == null) {
|
||||
data.error(ApiError(TAG, ERROR_FILE_DOWNLOAD)
|
||||
.withResponse(response))
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
onSuccess(file)
|
||||
} catch (e: Exception) {
|
||||
data.error(ApiError(tag, EXCEPTION_LIBRUS_MESSAGES_REQUEST)
|
||||
.withResponse(response)
|
||||
.withThrowable(e))
|
||||
}
|
||||
}
|
||||
|
||||
override fun onProgress(bytesWritten: Long, bytesTotal: Long) {
|
||||
try {
|
||||
onProgress(bytesWritten, bytesTotal)
|
||||
} catch (e: Exception) {
|
||||
data.error(ApiError(tag, EXCEPTION_LIBRUS_MESSAGES_REQUEST)
|
||||
.withThrowable(e))
|
||||
}
|
||||
}
|
||||
|
||||
override fun onFailure(response: Response?, throwable: Throwable?) {
|
||||
data.error(ApiError(tag, ERROR_REQUEST_FAILURE)
|
||||
.withResponse(response)
|
||||
.withThrowable(throwable))
|
||||
}
|
||||
}
|
||||
|
||||
Request.builder()
|
||||
.url("$LIBRUS_SANDBOX_URL$action")
|
||||
.userAgent(SYNERGIA_USER_AGENT)
|
||||
.post()
|
||||
.callback(callback)
|
||||
.build()
|
||||
.enqueue()
|
||||
}
|
||||
}
|
||||
|
@ -14,7 +14,7 @@ import pl.szczodrzynski.edziennik.utils.Utils.d
|
||||
|
||||
open class LibrusSynergia(open val data: DataLibrus) {
|
||||
companion object {
|
||||
const val TAG = "LibrusSynergia"
|
||||
private const val TAG = "LibrusSynergia"
|
||||
}
|
||||
|
||||
val profileId
|
||||
|
@ -0,0 +1,47 @@
|
||||
/*
|
||||
* Copyright (c) Kacper Ziubryniewicz 2019-11-20
|
||||
*/
|
||||
|
||||
package pl.szczodrzynski.edziennik.api.v2.librus.data.api
|
||||
|
||||
import pl.szczodrzynski.edziennik.api.v2.librus.DataLibrus
|
||||
import pl.szczodrzynski.edziennik.api.v2.librus.ENDPOINT_LIBRUS_API_NORMAL_GRADE_COMMENTS
|
||||
import pl.szczodrzynski.edziennik.api.v2.librus.data.LibrusApi
|
||||
import pl.szczodrzynski.edziennik.asJsonObjectList
|
||||
import pl.szczodrzynski.edziennik.data.db.modules.api.SYNC_ALWAYS
|
||||
import pl.szczodrzynski.edziennik.data.db.modules.grades.GradeCategory
|
||||
import pl.szczodrzynski.edziennik.getJsonArray
|
||||
import pl.szczodrzynski.edziennik.getLong
|
||||
import pl.szczodrzynski.edziennik.getString
|
||||
|
||||
class LibrusApiGradeComments(override val data: DataLibrus,
|
||||
val onSuccess: () -> Unit) : LibrusApi(data) {
|
||||
companion object {
|
||||
const val TAG = "LibrusApiGradeComments"
|
||||
}
|
||||
|
||||
init {
|
||||
apiGet(TAG, "Grades/Comments") { json ->
|
||||
|
||||
json.getJsonArray("Comments")?.asJsonObjectList()?.forEach { comment ->
|
||||
val id = comment.getLong("Id") ?: return@forEach
|
||||
val text = comment.getString("Text")
|
||||
|
||||
val gradeCategoryObject = GradeCategory(
|
||||
profileId,
|
||||
id,
|
||||
-1f,
|
||||
-1,
|
||||
text
|
||||
).apply {
|
||||
type = GradeCategory.TYPE_COMMENT
|
||||
}
|
||||
|
||||
data.gradeCategories.put(id, gradeCategoryObject)
|
||||
}
|
||||
|
||||
data.setSyncNext(ENDPOINT_LIBRUS_API_NORMAL_GRADE_COMMENTS, SYNC_ALWAYS)
|
||||
onSuccess()
|
||||
}
|
||||
}
|
||||
}
|
@ -6,6 +6,7 @@ import pl.szczodrzynski.edziennik.api.v2.librus.ENDPOINT_LIBRUS_API_NORMAL_GRADE
|
||||
import pl.szczodrzynski.edziennik.api.v2.librus.data.LibrusApi
|
||||
import pl.szczodrzynski.edziennik.data.db.modules.api.SYNC_ALWAYS
|
||||
import pl.szczodrzynski.edziennik.data.db.modules.grades.Grade
|
||||
import pl.szczodrzynski.edziennik.data.db.modules.grades.GradeCategory
|
||||
import pl.szczodrzynski.edziennik.data.db.modules.metadata.Metadata
|
||||
import pl.szczodrzynski.edziennik.utils.Utils
|
||||
import pl.szczodrzynski.edziennik.utils.models.Date
|
||||
@ -42,12 +43,21 @@ class LibrusApiGrades(override val data: DataLibrus,
|
||||
weight = 0f
|
||||
}
|
||||
|
||||
val description = grade.getJsonArray("Comments")?.asJsonObjectList()?.let { comments ->
|
||||
if (comments.isNotEmpty()) {
|
||||
data.gradeCategories.singleOrNull {
|
||||
it.type == GradeCategory.TYPE_COMMENT
|
||||
&& it.categoryId == comments[0].asJsonObject.getLong("Id")
|
||||
}?.text
|
||||
} else null
|
||||
} ?: ""
|
||||
|
||||
val gradeObject = Grade(
|
||||
profileId,
|
||||
id,
|
||||
categoryName,
|
||||
color,
|
||||
"",
|
||||
description,
|
||||
name,
|
||||
value,
|
||||
weight,
|
||||
|
@ -17,6 +17,7 @@ import pl.szczodrzynski.edziennik.data.db.modules.timetable.Lesson
|
||||
import pl.szczodrzynski.edziennik.utils.Utils.d
|
||||
import pl.szczodrzynski.edziennik.utils.models.Date
|
||||
import pl.szczodrzynski.edziennik.utils.models.Time
|
||||
import pl.szczodrzynski.edziennik.utils.models.Week
|
||||
|
||||
class LibrusApiTimetables(override val data: DataLibrus,
|
||||
val onSuccess: () -> Unit) : LibrusApi(data) {
|
||||
@ -29,9 +30,18 @@ class LibrusApiTimetables(override val data: DataLibrus,
|
||||
data.db.classroomDao().getAllNow(profileId).toSparseArray(data.classrooms) { it.id }
|
||||
}
|
||||
|
||||
val currentWeekStart = Date.getToday().let { it.stepForward(0, 0, -it.weekDay) }
|
||||
val currentWeekStart = Week.getWeekStart()
|
||||
|
||||
if (Date.getToday().weekDay > 4) {
|
||||
currentWeekStart.stepForward(0, 0, 7)
|
||||
}
|
||||
|
||||
val getDate = data.arguments?.getString("weekStart") ?: currentWeekStart.stringY_m_d
|
||||
apiGet(TAG, "Timetables?weekStart=$getDate") { json ->
|
||||
|
||||
val weekStart = Date.fromY_m_d(getDate)
|
||||
val weekEnd = weekStart.clone().stepForward(0, 0, 6)
|
||||
|
||||
apiGet(TAG, "Timetables?weekStart=${weekStart.stringY_m_d}") { json ->
|
||||
val days = json.getJsonObject("Timetable")
|
||||
|
||||
days?.entrySet()?.forEach { (dateString, dayEl) ->
|
||||
@ -44,21 +54,19 @@ class LibrusApiTimetables(override val data: DataLibrus,
|
||||
val lessonRange = lessonRangeEl?.asJsonArray?.asJsonObjectList()
|
||||
if (lessonRange?.isNullOrEmpty() == false)
|
||||
lessonsFound = true
|
||||
lessonRange?.forEachIndexed { index, lesson ->
|
||||
lessonRange?.forEach { lesson ->
|
||||
parseLesson(lessonDate, lesson)
|
||||
}
|
||||
}
|
||||
|
||||
if (day.isNullOrEmpty() || !lessonsFound) {
|
||||
data.lessonNewList += Lesson(profileId, lessonDate.value.toLong()).apply {
|
||||
data.lessonNewList.add(Lesson(profileId, lessonDate.value.toLong()).apply {
|
||||
type = Lesson.TYPE_NO_LESSONS
|
||||
date = lessonDate
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
val weekStart = Date.fromY_m_d(getDate)
|
||||
val weekEnd = weekStart.clone().stepForward(0, 0, 6)
|
||||
d(TAG, "Clearing lessons between ${weekStart.stringY_m_d} and ${weekEnd.stringY_m_d} - timetable downloaded for $getDate")
|
||||
|
||||
data.toRemove.add(DataRemoveModel.Timetable.between(weekStart, weekEnd))
|
||||
@ -67,7 +75,7 @@ class LibrusApiTimetables(override val data: DataLibrus,
|
||||
}
|
||||
}
|
||||
|
||||
private fun parseLesson(lessonDate: Date, lesson: JsonObject) {
|
||||
private fun parseLesson(lessonDate: Date, lesson: JsonObject) { data.profile?.also { profile ->
|
||||
val isSubstitution = lesson.getBoolean("IsSubstitutionClass") ?: false
|
||||
val isCancelled = lesson.getBoolean("IsCanceled") ?: false
|
||||
|
||||
@ -80,8 +88,7 @@ class LibrusApiTimetables(override val data: DataLibrus,
|
||||
val virtualClassId = lesson.getJsonObject("VirtualClass")?.getLong("Id")
|
||||
val teamId = lesson.getJsonObject("Class")?.getLong("Id") ?: virtualClassId
|
||||
|
||||
val id = lessonDate.combineWith(startTime) / 6L * 10L + (lesson.hashCode() and 0xFFFF)
|
||||
val lessonObject = Lesson(profileId, id)
|
||||
val lessonObject = Lesson(profileId, -1)
|
||||
|
||||
if (isSubstitution && isCancelled) {
|
||||
// shifted lesson - source
|
||||
@ -176,17 +183,21 @@ class LibrusApiTimetables(override val data: DataLibrus,
|
||||
}
|
||||
}
|
||||
|
||||
lessonObject.id = lessonObject.buildId()
|
||||
|
||||
val seen = profile.empty || lessonDate < Date.getToday()
|
||||
|
||||
if (lessonObject.type != Lesson.TYPE_NORMAL) {
|
||||
data.metadataList.add(
|
||||
Metadata(
|
||||
data.profileId,
|
||||
profileId,
|
||||
Metadata.TYPE_LESSON_CHANGE,
|
||||
lessonObject.id,
|
||||
data.profile?.empty ?: false,
|
||||
data.profile?.empty ?: false,
|
||||
seen,
|
||||
seen,
|
||||
System.currentTimeMillis()
|
||||
))
|
||||
}
|
||||
data.lessonNewList.add(lessonObject)
|
||||
}
|
||||
}}
|
||||
}
|
||||
|
@ -0,0 +1,123 @@
|
||||
/*
|
||||
* Copyright (c) Kacper Ziubryniewicz 2019-11-24
|
||||
*/
|
||||
|
||||
package pl.szczodrzynski.edziennik.api.v2.librus.data.messages
|
||||
|
||||
import kotlinx.coroutines.*
|
||||
import org.greenrobot.eventbus.EventBus
|
||||
import pl.szczodrzynski.edziennik.api.v2.ERROR_FILE_DOWNLOAD
|
||||
import pl.szczodrzynski.edziennik.api.v2.EXCEPTION_LIBRUS_MESSAGES_REQUEST
|
||||
import pl.szczodrzynski.edziennik.api.v2.Regexes
|
||||
import pl.szczodrzynski.edziennik.api.v2.events.AttachmentGetEvent
|
||||
import pl.szczodrzynski.edziennik.api.v2.events.AttachmentGetEvent.Companion.TYPE_FINISHED
|
||||
import pl.szczodrzynski.edziennik.api.v2.events.AttachmentGetEvent.Companion.TYPE_PROGRESS
|
||||
import pl.szczodrzynski.edziennik.api.v2.librus.DataLibrus
|
||||
import pl.szczodrzynski.edziennik.api.v2.librus.data.LibrusMessages
|
||||
import pl.szczodrzynski.edziennik.api.v2.models.ApiError
|
||||
import pl.szczodrzynski.edziennik.get
|
||||
import pl.szczodrzynski.edziennik.getString
|
||||
import pl.szczodrzynski.edziennik.utils.Utils
|
||||
import java.io.File
|
||||
import kotlin.coroutines.CoroutineContext
|
||||
|
||||
|
||||
class LibrusMessagesGetAttachment(override val data: DataLibrus, val messageId: Long, val attachmentId: Long,
|
||||
val attachmentName: String, val onSuccess: () -> Unit) : LibrusMessages(data), CoroutineScope {
|
||||
companion object {
|
||||
const val TAG = "LibrusMessagesGetAttachment"
|
||||
}
|
||||
|
||||
private var job = Job()
|
||||
|
||||
override val coroutineContext: CoroutineContext
|
||||
get() = job + Dispatchers.Default
|
||||
|
||||
private var getAttachmentCheckKeyTries = 0
|
||||
|
||||
init {
|
||||
messagesGet(TAG, "GetFileDownloadLink", parameters = mapOf(
|
||||
"fileId" to attachmentId,
|
||||
"msgId" to messageId,
|
||||
"archive" to 0
|
||||
)) { doc ->
|
||||
val downloadLink = doc.select("response GetFileDownloadLink downloadLink").text()
|
||||
val keyMatcher = Regexes.LIBRUS_ATTACHMENT_KEY.find(downloadLink)
|
||||
|
||||
if (keyMatcher != null) {
|
||||
getAttachmentCheckKeyTries = 0
|
||||
|
||||
val attachmentKey = keyMatcher[1]
|
||||
getAttachmentCheckKey(attachmentKey) {
|
||||
downloadAttachment(attachmentKey)
|
||||
}
|
||||
} else {
|
||||
data.error(ApiError(TAG, ERROR_FILE_DOWNLOAD)
|
||||
.withApiResponse(doc.toString()))
|
||||
}
|
||||
|
||||
onSuccess()
|
||||
}
|
||||
}
|
||||
|
||||
private fun getAttachmentCheckKey(attachmentKey: String, callback: () -> Unit) {
|
||||
sandboxGet(TAG, "CSCheckKey",
|
||||
parameters = mapOf("singleUseKey" to attachmentKey)) { json ->
|
||||
|
||||
when (json.getString("status")) {
|
||||
"not_downloaded_yet" -> {
|
||||
if (getAttachmentCheckKeyTries++ > 5) {
|
||||
data.error(ApiError(TAG, ERROR_FILE_DOWNLOAD)
|
||||
.withApiResponse(json))
|
||||
return@sandboxGet
|
||||
}
|
||||
launch {
|
||||
delay(2000)
|
||||
getAttachmentCheckKey(attachmentKey, callback)
|
||||
}
|
||||
}
|
||||
|
||||
"ready" -> {
|
||||
launch { callback() }
|
||||
}
|
||||
|
||||
else -> {
|
||||
data.error(ApiError(TAG, EXCEPTION_LIBRUS_MESSAGES_REQUEST)
|
||||
.withApiResponse(json))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun downloadAttachment(attachmentKey: String) {
|
||||
val targetFile = File(Utils.getStorageDir(), attachmentName)
|
||||
|
||||
sandboxGetFile(TAG, "CSDownload&singleUseKey=$attachmentKey",
|
||||
targetFile, { file ->
|
||||
|
||||
val event = AttachmentGetEvent(
|
||||
profileId,
|
||||
messageId,
|
||||
attachmentId,
|
||||
TYPE_FINISHED,
|
||||
file.absolutePath
|
||||
)
|
||||
|
||||
val attachmentDataFile = File(Utils.getStorageDir(), ".${profileId}_${event.messageId}_${event.attachmentId}")
|
||||
Utils.writeStringToFile(attachmentDataFile, event.fileName)
|
||||
|
||||
EventBus.getDefault().post(event)
|
||||
|
||||
}) { written, _ ->
|
||||
val event = AttachmentGetEvent(
|
||||
profileId,
|
||||
messageId,
|
||||
attachmentId,
|
||||
TYPE_PROGRESS,
|
||||
bytesWritten = written
|
||||
)
|
||||
|
||||
EventBus.getDefault().post(event)
|
||||
}
|
||||
}
|
||||
}
|
@ -13,6 +13,7 @@ import pl.szczodrzynski.edziennik.api.v2.librus.ENDPOINT_LIBRUS_MESSAGES_SENT
|
||||
import pl.szczodrzynski.edziennik.api.v2.librus.data.LibrusMessages
|
||||
import pl.szczodrzynski.edziennik.data.db.modules.api.SYNC_ALWAYS
|
||||
import pl.szczodrzynski.edziennik.data.db.modules.messages.Message
|
||||
import pl.szczodrzynski.edziennik.data.db.modules.messages.Message.TYPE_RECEIVED
|
||||
import pl.szczodrzynski.edziennik.data.db.modules.messages.MessageRecipient
|
||||
import pl.szczodrzynski.edziennik.data.db.modules.metadata.Metadata
|
||||
import pl.szczodrzynski.edziennik.data.db.modules.teachers.Teacher
|
||||
@ -20,7 +21,7 @@ import pl.szczodrzynski.edziennik.singleOrNull
|
||||
import pl.szczodrzynski.edziennik.utils.Utils
|
||||
import pl.szczodrzynski.edziennik.utils.models.Date
|
||||
|
||||
class LibrusMessagesGetList(override val data: DataLibrus, private val type: Int = Message.TYPE_RECEIVED,
|
||||
class LibrusMessagesGetList(override val data: DataLibrus, private val type: Int = TYPE_RECEIVED,
|
||||
archived: Boolean = false, val onSuccess: () -> Unit) : LibrusMessages(data) {
|
||||
companion object {
|
||||
const val TAG = "LibrusMessagesGetList"
|
||||
@ -28,7 +29,7 @@ class LibrusMessagesGetList(override val data: DataLibrus, private val type: Int
|
||||
|
||||
init {
|
||||
val endpoint = when (type) {
|
||||
Message.TYPE_RECEIVED -> "Inbox/action/GetList"
|
||||
TYPE_RECEIVED -> "Inbox/action/GetList"
|
||||
Message.TYPE_SENT -> "Outbox/action/GetList"
|
||||
else -> null
|
||||
}
|
||||
@ -46,34 +47,38 @@ class LibrusMessagesGetList(override val data: DataLibrus, private val type: Int
|
||||
else -> 0
|
||||
}
|
||||
val sentDate = Date.fromIso(element.select("sendDate").text().trim())
|
||||
var senderId: Long = -1
|
||||
var receiverId: Long = -1
|
||||
|
||||
when (type) {
|
||||
Message.TYPE_RECEIVED -> {
|
||||
val senderFirstName = element.select("senderFirstName").text().trim()
|
||||
val senderLastName = element.select("senderLastName").text().trim()
|
||||
senderId = data.teacherList.singleOrNull {
|
||||
it.name == senderFirstName && it.surname == senderLastName
|
||||
}?.id ?: -1
|
||||
}
|
||||
val recipientFirstName = element.select(when (type) {
|
||||
TYPE_RECEIVED -> "senderFirstName"
|
||||
else -> "receiverFirstName"
|
||||
}).text().trim()
|
||||
|
||||
Message.TYPE_SENT -> {
|
||||
val receiverFirstName = element.select("receiverFirstName").text().trim()
|
||||
val receiverLastName = element.select("receiverLastName").text().trim()
|
||||
receiverId = data.teacherList.singleOrNull {
|
||||
it.name == receiverFirstName && it.surname == receiverLastName
|
||||
}?.id ?: {
|
||||
val teacherObject = Teacher(
|
||||
profileId,
|
||||
-1 * Utils.crc16("$receiverFirstName $receiverLastName".toByteArray()).toLong(),
|
||||
receiverFirstName,
|
||||
receiverLastName
|
||||
)
|
||||
data.teacherList.put(teacherObject.id, teacherObject)
|
||||
teacherObject.id
|
||||
}.invoke()
|
||||
}
|
||||
val recipientLastName = element.select(when (type) {
|
||||
TYPE_RECEIVED -> "senderLastName"
|
||||
else -> "receiverLastName"
|
||||
}).text().trim()
|
||||
|
||||
val recipientId = data.teacherList.singleOrNull {
|
||||
it.name == recipientFirstName && it.surname == recipientLastName
|
||||
}?.id ?: {
|
||||
val teacherObject = Teacher(
|
||||
profileId,
|
||||
-1 * Utils.crc16("$recipientFirstName $recipientLastName".toByteArray()).toLong(),
|
||||
recipientFirstName,
|
||||
recipientLastName
|
||||
)
|
||||
data.teacherList.put(teacherObject.id, teacherObject)
|
||||
teacherObject.id
|
||||
}.invoke()
|
||||
|
||||
val senderId = when (type) {
|
||||
TYPE_RECEIVED -> recipientId
|
||||
else -> -1
|
||||
}
|
||||
|
||||
val receiverId = when (type) {
|
||||
TYPE_RECEIVED -> -1
|
||||
else -> recipientId
|
||||
}
|
||||
|
||||
val notified = when (type) {
|
||||
@ -112,7 +117,7 @@ class LibrusMessagesGetList(override val data: DataLibrus, private val type: Int
|
||||
}
|
||||
|
||||
when (type) {
|
||||
Message.TYPE_RECEIVED -> data.setSyncNext(ENDPOINT_LIBRUS_MESSAGES_RECEIVED, SYNC_ALWAYS)
|
||||
TYPE_RECEIVED -> data.setSyncNext(ENDPOINT_LIBRUS_MESSAGES_RECEIVED, SYNC_ALWAYS)
|
||||
Message.TYPE_SENT -> data.setSyncNext(ENDPOINT_LIBRUS_MESSAGES_SENT, DAY, DRAWER_ITEM_MESSAGES)
|
||||
}
|
||||
onSuccess()
|
||||
|
@ -16,8 +16,7 @@ import pl.szczodrzynski.edziennik.getInt
|
||||
import pl.szczodrzynski.edziennik.getString
|
||||
import pl.szczodrzynski.edziennik.getUnixDate
|
||||
import pl.szczodrzynski.edziennik.utils.Utils.d
|
||||
import java.net.HttpURLConnection.HTTP_BAD_REQUEST
|
||||
import java.net.HttpURLConnection.HTTP_UNAUTHORIZED
|
||||
import java.net.HttpURLConnection.*
|
||||
|
||||
class LibrusLoginApi {
|
||||
companion object {
|
||||
@ -117,6 +116,13 @@ class LibrusLoginApi {
|
||||
|
||||
private val tokenCallback = object : JsonCallbackHandler() {
|
||||
override fun onSuccess(json: JsonObject?, response: Response?) {
|
||||
if (response?.code() == HTTP_UNAVAILABLE) {
|
||||
data.error(ApiError(TAG, ERROR_LIBRUS_API_MAINTENANCE)
|
||||
.withApiResponse(json)
|
||||
.withResponse(response))
|
||||
return
|
||||
}
|
||||
|
||||
if (json == null) {
|
||||
data.error(ApiError(TAG, ERROR_RESPONSE_EMPTY)
|
||||
.withResponse(response))
|
||||
@ -176,6 +182,7 @@ class LibrusLoginApi {
|
||||
.post()
|
||||
.allowErrorCode(HTTP_BAD_REQUEST)
|
||||
.allowErrorCode(HTTP_UNAUTHORIZED)
|
||||
.allowErrorCode(HTTP_UNAVAILABLE)
|
||||
.callback(tokenCallback)
|
||||
.build()
|
||||
.enqueue()
|
||||
|
@ -6,20 +6,57 @@ package pl.szczodrzynski.edziennik.api.v2.librus.login
|
||||
|
||||
import im.wangchao.mhttp.Request
|
||||
import im.wangchao.mhttp.Response
|
||||
import im.wangchao.mhttp.body.MediaTypeUtils
|
||||
import im.wangchao.mhttp.callback.TextCallbackHandler
|
||||
import okhttp3.Cookie
|
||||
import pl.szczodrzynski.edziennik.api.v2.*
|
||||
import pl.szczodrzynski.edziennik.api.v2.librus.DataLibrus
|
||||
import pl.szczodrzynski.edziennik.api.v2.models.ApiError
|
||||
import pl.szczodrzynski.edziennik.getUnixDate
|
||||
import pl.szczodrzynski.edziennik.utils.Utils
|
||||
import pl.szczodrzynski.edziennik.utils.Utils.d
|
||||
import java.io.StringWriter
|
||||
import javax.xml.parsers.DocumentBuilderFactory
|
||||
import javax.xml.transform.OutputKeys
|
||||
import javax.xml.transform.TransformerFactory
|
||||
import javax.xml.transform.dom.DOMSource
|
||||
import javax.xml.transform.stream.StreamResult
|
||||
|
||||
class LibrusLoginMessages(val data: DataLibrus, val onSuccess: () -> Unit) {
|
||||
companion object {
|
||||
private const val TAG = "LoginLibrusMessages"
|
||||
}
|
||||
|
||||
private val callback by lazy { object : TextCallbackHandler() {
|
||||
override fun onSuccess(text: String?, response: Response?) {
|
||||
val location = response?.headers()?.get("Location")
|
||||
when {
|
||||
location?.contains("MultiDomainLogon") == true -> loginWithSynergia(location)
|
||||
location?.contains("AutoLogon") == true -> {
|
||||
saveSessionId(response, text)
|
||||
onSuccess()
|
||||
}
|
||||
|
||||
text?.contains("<status>ok</status>") == true -> {
|
||||
saveSessionId(response, text)
|
||||
onSuccess()
|
||||
}
|
||||
text?.contains("<message>Niepoprawny login i/lub hasło.</message>") == true -> data.error(TAG, ERROR_LOGIN_LIBRUS_MESSAGES_INVALID_LOGIN, response, text)
|
||||
text?.contains("stop.png") == true -> data.error(TAG, ERROR_LIBRUS_SYNERGIA_ACCESS_DENIED, response, text)
|
||||
text?.contains("eAccessDeny") == true -> data.error(TAG, ERROR_LIBRUS_MESSAGES_ACCESS_DENIED, response, text)
|
||||
text?.contains("OffLine") == true -> data.error(TAG, ERROR_LIBRUS_MESSAGES_MAINTENANCE, response, text)
|
||||
text?.contains("<status>error</status>") == true -> data.error(TAG, ERROR_LIBRUS_MESSAGES_ERROR, response, text)
|
||||
text?.contains("<type>eVarWhitThisNameNotExists</type>") == true -> data.error(TAG, ERROR_LIBRUS_MESSAGES_ACCESS_DENIED, response, text)
|
||||
text?.contains("<error>") == true -> data.error(TAG, ERROR_LIBRUS_MESSAGES_OTHER, response, text)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onFailure(response: Response?, throwable: Throwable?) {
|
||||
data.error(ApiError(TAG, ERROR_REQUEST_FAILURE)
|
||||
.withResponse(response)
|
||||
.withThrowable(throwable))
|
||||
}
|
||||
}}
|
||||
|
||||
init { run {
|
||||
if (data.profile == null) {
|
||||
data.error(ApiError(TAG, ERROR_PROFILE_MISSING))
|
||||
@ -41,7 +78,7 @@ class LibrusLoginMessages(val data: DataLibrus, val onSuccess: () -> Unit) {
|
||||
if (data.loginMethods.contains(LOGIN_METHOD_LIBRUS_SYNERGIA)) {
|
||||
loginWithSynergia()
|
||||
}
|
||||
else if (data.apiLogin != null && data.apiPassword != null && false) {
|
||||
else if (data.apiLogin != null && data.apiPassword != null) {
|
||||
loginWithCredentials()
|
||||
}
|
||||
else {
|
||||
@ -54,7 +91,44 @@ class LibrusLoginMessages(val data: DataLibrus, val onSuccess: () -> Unit) {
|
||||
* XML (Flash messages website) login method. Uses a Synergia login and password.
|
||||
*/
|
||||
private fun loginWithCredentials() {
|
||||
d(TAG, "Request: Librus/Login/Messages - $LIBRUS_MESSAGES_URL/Login")
|
||||
|
||||
val docBuilder = DocumentBuilderFactory.newInstance().newDocumentBuilder()
|
||||
val doc = docBuilder.newDocument()
|
||||
val serviceElement = doc.createElement("service")
|
||||
val headerElement = doc.createElement("header")
|
||||
val dataElement = doc.createElement("data")
|
||||
val loginElement = doc.createElement("login")
|
||||
loginElement.appendChild(doc.createTextNode(data.apiLogin))
|
||||
dataElement.appendChild(loginElement)
|
||||
val passwordElement = doc.createElement("login")
|
||||
passwordElement.appendChild(doc.createTextNode(data.apiPassword))
|
||||
dataElement.appendChild(passwordElement)
|
||||
val keyStrokeElement = doc.createElement("KeyStroke")
|
||||
val keysElement = doc.createElement("Keys")
|
||||
val upElement = doc.createElement("Up")
|
||||
keysElement.appendChild(upElement)
|
||||
val downElement = doc.createElement("Down")
|
||||
keysElement.appendChild(downElement)
|
||||
keyStrokeElement.appendChild(keysElement)
|
||||
dataElement.appendChild(keyStrokeElement)
|
||||
serviceElement.appendChild(headerElement)
|
||||
serviceElement.appendChild(dataElement)
|
||||
doc.appendChild(serviceElement)
|
||||
val transformer = TransformerFactory.newInstance().newTransformer()
|
||||
transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes")
|
||||
val stringWriter = StringWriter()
|
||||
transformer.transform(DOMSource(doc), StreamResult(stringWriter))
|
||||
val requestXml = stringWriter.toString()
|
||||
|
||||
Request.builder()
|
||||
.url("$LIBRUS_MESSAGES_URL/Login")
|
||||
.userAgent(SYNERGIA_USER_AGENT)
|
||||
.setTextBody(requestXml, MediaTypeUtils.APPLICATION_XML)
|
||||
.post()
|
||||
.callback(callback)
|
||||
.build()
|
||||
.enqueue()
|
||||
}
|
||||
|
||||
/**
|
||||
@ -63,37 +137,6 @@ class LibrusLoginMessages(val data: DataLibrus, val onSuccess: () -> Unit) {
|
||||
private fun loginWithSynergia(url: String = "https://synergia.librus.pl/wiadomosci2") {
|
||||
d(TAG, "Request: Librus/Login/Messages - $url")
|
||||
|
||||
val callback = object : TextCallbackHandler() {
|
||||
override fun onSuccess(text: String?, response: Response?) {
|
||||
val location = response?.headers()?.get("Location")
|
||||
when {
|
||||
location?.contains("MultiDomainLogon") == true -> loginWithSynergia(location)
|
||||
location?.contains("AutoLogon") == true -> {
|
||||
var sessionId = data.app.cookieJar.getCookie("wiadomosci.librus.pl", "DZIENNIKSID")
|
||||
sessionId = sessionId?.replace("-MAINT", "")
|
||||
if (sessionId == null) {
|
||||
data.error(ApiError(TAG, ERROR_LOGIN_LIBRUS_MESSAGES_NO_SESSION_ID)
|
||||
.withResponse(response)
|
||||
.withApiResponse(text))
|
||||
return
|
||||
}
|
||||
data.messagesSessionId = sessionId
|
||||
data.messagesSessionIdExpiryTime = response.getUnixDate() + 45 * 60 /* 45min */
|
||||
onSuccess()
|
||||
}
|
||||
|
||||
text?.contains("eAccessDeny") == true -> data.error(TAG, ERROR_LIBRUS_MESSAGES_ACCESS_DENIED, response, text)
|
||||
text?.contains("stop.png") == true -> data.error(TAG, ERROR_LIBRUS_SYNERGIA_ACCESS_DENIED, response, text)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onFailure(response: Response?, throwable: Throwable?) {
|
||||
data.error(ApiError(TAG, ERROR_REQUEST_FAILURE)
|
||||
.withResponse(response)
|
||||
.withThrowable(throwable))
|
||||
}
|
||||
}
|
||||
|
||||
Request.builder()
|
||||
.url(url)
|
||||
.userAgent(SYNERGIA_USER_AGENT)
|
||||
@ -103,4 +146,17 @@ class LibrusLoginMessages(val data: DataLibrus, val onSuccess: () -> Unit) {
|
||||
.build()
|
||||
.enqueue()
|
||||
}
|
||||
|
||||
private fun saveSessionId(response: Response?, text: String?) {
|
||||
var sessionId = data.app.cookieJar.getCookie("wiadomosci.librus.pl", "DZIENNIKSID")
|
||||
sessionId = sessionId?.replace("-MAINT", "") // dunno what's this
|
||||
if (sessionId == null) {
|
||||
data.error(ApiError(TAG, ERROR_LOGIN_LIBRUS_MESSAGES_NO_SESSION_ID)
|
||||
.withResponse(response)
|
||||
.withApiResponse(text))
|
||||
return
|
||||
}
|
||||
data.messagesSessionId = sessionId
|
||||
data.messagesSessionIdExpiryTime = response.getUnixDate() + 45 * 60 /* 45min */
|
||||
}
|
||||
}
|
@ -99,6 +99,14 @@ class LibrusLoginPortal(val data: DataLibrus, val onSuccess: () -> Unit) {
|
||||
.post()
|
||||
.callback(object : JsonCallbackHandler() {
|
||||
override fun onSuccess(json: JsonObject?, response: Response) {
|
||||
val location = response.headers()?.get("Location")
|
||||
if (location == "http://localhost/bar?command=close") {
|
||||
data.error(ApiError(TAG, ERROR_LIBRUS_PORTAL_MAINTENANCE)
|
||||
.withApiResponse(json)
|
||||
.withResponse(response))
|
||||
return
|
||||
}
|
||||
|
||||
if (json == null) {
|
||||
if (response.parserErrorBody?.contains("wciąż nieaktywne") == true) {
|
||||
data.error(ApiError(TAG, ERROR_LOGIN_LIBRUS_PORTAL_NOT_ACTIVATED)
|
||||
@ -120,7 +128,7 @@ class LibrusLoginPortal(val data: DataLibrus, val onSuccess: () -> Unit) {
|
||||
|
||||
override fun onFailure(response: Response, throwable: Throwable) {
|
||||
if (response.code() == 403 || response.code() == 401) {
|
||||
data.error(ApiError(TAG, ERROR_LOGIN_DATA_INVALID)
|
||||
data.error(ApiError(TAG, ERROR_LOGIN_LIBRUS_PORTAL_INVALID_LOGIN)
|
||||
.withResponse(response)
|
||||
.withThrowable(throwable))
|
||||
return
|
||||
|
@ -7,7 +7,6 @@ package pl.szczodrzynski.edziennik.api.v2.librus.login
|
||||
import com.google.gson.JsonObject
|
||||
import im.wangchao.mhttp.Request
|
||||
import im.wangchao.mhttp.Response
|
||||
import im.wangchao.mhttp.callback.JsonCallbackHandler
|
||||
import im.wangchao.mhttp.callback.TextCallbackHandler
|
||||
import okhttp3.Cookie
|
||||
import pl.szczodrzynski.edziennik.api.v2.*
|
||||
@ -16,7 +15,6 @@ import pl.szczodrzynski.edziennik.api.v2.librus.data.LibrusApi
|
||||
import pl.szczodrzynski.edziennik.api.v2.models.ApiError
|
||||
import pl.szczodrzynski.edziennik.getString
|
||||
import pl.szczodrzynski.edziennik.getUnixDate
|
||||
import pl.szczodrzynski.edziennik.utils.Utils
|
||||
import pl.szczodrzynski.edziennik.utils.Utils.d
|
||||
import java.net.HttpURLConnection
|
||||
|
||||
@ -86,6 +84,13 @@ class LibrusLoginSynergia(override val data: DataLibrus, val onSuccess: () -> Un
|
||||
val callback = object : TextCallbackHandler() {
|
||||
override fun onSuccess(json: String?, response: Response?) {
|
||||
val location = response?.headers()?.get("Location")
|
||||
if (location?.endsWith("przerwa_techniczna") == true) {
|
||||
data.error(ApiError(TAG, ERROR_LIBRUS_SYNERGIA_MAINTENANCE)
|
||||
.withApiResponse(json)
|
||||
.withResponse(response))
|
||||
return
|
||||
}
|
||||
|
||||
if (location?.endsWith("centrum_powiadomien") == true) {
|
||||
val sessionId = data.app.cookieJar.getCookie("synergia.librus.pl", "DZIENNIKSID")
|
||||
if (sessionId == null) {
|
||||
|
@ -10,8 +10,10 @@ import pl.szczodrzynski.edziennik.api.v2.CODE_INTERNAL_LIBRUS_ACCOUNT_410
|
||||
import pl.szczodrzynski.edziennik.api.v2.interfaces.EdziennikCallback
|
||||
import pl.szczodrzynski.edziennik.api.v2.interfaces.EdziennikInterface
|
||||
import pl.szczodrzynski.edziennik.api.v2.mobidziennik.data.MobidziennikData
|
||||
import pl.szczodrzynski.edziennik.api.v2.mobidziennik.data.web.MobidziennikWebGetMessage
|
||||
import pl.szczodrzynski.edziennik.api.v2.mobidziennik.firstlogin.MobidziennikFirstLogin
|
||||
import pl.szczodrzynski.edziennik.api.v2.mobidziennik.login.MobidziennikLogin
|
||||
import pl.szczodrzynski.edziennik.api.v2.mobidziennik.login.MobidziennikLoginWeb
|
||||
import pl.szczodrzynski.edziennik.api.v2.mobidziennikLoginMethods
|
||||
import pl.szczodrzynski.edziennik.api.v2.models.ApiError
|
||||
import pl.szczodrzynski.edziennik.api.v2.prepare
|
||||
@ -63,13 +65,21 @@ class Mobidziennik(val app: App, val profile: Profile?, val loginStore: LoginSto
|
||||
}
|
||||
|
||||
override fun getMessage(message: MessageFull) {
|
||||
|
||||
MobidziennikLoginWeb(data) {
|
||||
MobidziennikWebGetMessage(data, message) {
|
||||
completed()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun markAllAnnouncementsAsRead() {
|
||||
|
||||
}
|
||||
|
||||
override fun getAttachment(messageId: Long, attachmentId: Long, attachmentName: String) {
|
||||
|
||||
}
|
||||
|
||||
override fun firstLogin() {
|
||||
MobidziennikFirstLogin(data) {
|
||||
completed()
|
||||
|
@ -4,7 +4,6 @@
|
||||
|
||||
package pl.szczodrzynski.edziennik.api.v2.mobidziennik.data.api
|
||||
|
||||
import pl.szczodrzynski.edziennik.App.profileId
|
||||
import pl.szczodrzynski.edziennik.api.v2.mobidziennik.DataMobidziennik
|
||||
import pl.szczodrzynski.edziennik.data.db.modules.teams.Team
|
||||
import pl.szczodrzynski.edziennik.getById
|
||||
@ -25,7 +24,7 @@ class MobidziennikApiTeams(val data: DataMobidziennik, tableTeams: List<String>?
|
||||
val teacherId = cols[4].toLongOrNull() ?: -1
|
||||
|
||||
val teamObject = Team(
|
||||
profileId,
|
||||
data.profileId,
|
||||
id,
|
||||
name,
|
||||
type,
|
||||
|
@ -4,7 +4,6 @@
|
||||
|
||||
package pl.szczodrzynski.edziennik.api.v2.mobidziennik.data.api
|
||||
|
||||
import pl.szczodrzynski.edziennik.App.profileId
|
||||
import pl.szczodrzynski.edziennik.api.v2.mobidziennik.DataMobidziennik
|
||||
import pl.szczodrzynski.edziennik.api.v2.models.DataRemoveModel
|
||||
import pl.szczodrzynski.edziennik.data.db.modules.metadata.Metadata
|
||||
@ -15,7 +14,7 @@ import pl.szczodrzynski.edziennik.utils.models.Date
|
||||
import pl.szczodrzynski.edziennik.utils.models.Time
|
||||
|
||||
class MobidziennikApiTimetable(val data: DataMobidziennik, rows: List<String>) {
|
||||
init {
|
||||
init { data.profile?.also { profile ->
|
||||
val lessons = rows.filterNot { it.isEmpty() }.map { it.split("|") }
|
||||
|
||||
val dataStart = Date.getToday()
|
||||
@ -33,7 +32,6 @@ class MobidziennikApiTimetable(val data: DataMobidziennik, rows: List<String>) {
|
||||
val date = Date.fromYmd(lesson[2])
|
||||
val startTime = Time.fromYmdHm(lesson[3])
|
||||
val endTime = Time.fromYmdHm(lesson[4])
|
||||
val id = date.combineWith(startTime) / 6L * 10L + (lesson.joinToString("|").hashCode() and 0xFFFF)
|
||||
|
||||
dataDays.remove(date.value)
|
||||
|
||||
@ -42,7 +40,7 @@ class MobidziennikApiTimetable(val data: DataMobidziennik, rows: List<String>) {
|
||||
val teamId = data.teamList.singleOrNull { it.name == lesson[8]+lesson[9] }?.id ?: -1
|
||||
val classroom = lesson[11]
|
||||
|
||||
Lesson(data.profileId, id).also {
|
||||
Lesson(data.profileId, -1).also {
|
||||
when (lesson[1]) {
|
||||
"plan_lekcji", "lekcja" -> {
|
||||
it.type = Lesson.TYPE_NORMAL
|
||||
@ -76,14 +74,18 @@ class MobidziennikApiTimetable(val data: DataMobidziennik, rows: List<String>) {
|
||||
}
|
||||
}
|
||||
|
||||
it.id = it.buildId()
|
||||
|
||||
val seen = profile.empty || date < Date.getToday()
|
||||
|
||||
if (it.type != Lesson.TYPE_NORMAL) {
|
||||
data.metadataList.add(
|
||||
Metadata(
|
||||
data.profileId,
|
||||
Metadata.TYPE_LESSON_CHANGE,
|
||||
it.id,
|
||||
data.profile?.empty ?: false,
|
||||
data.profile?.empty ?: false,
|
||||
seen,
|
||||
seen,
|
||||
System.currentTimeMillis()
|
||||
))
|
||||
}
|
||||
@ -93,7 +95,7 @@ class MobidziennikApiTimetable(val data: DataMobidziennik, rows: List<String>) {
|
||||
|
||||
for (day in dataDays) {
|
||||
val lessonDate = Date.fromValue(day)
|
||||
data.lessonNewList += Lesson(profileId, lessonDate.value.toLong()).apply {
|
||||
data.lessonNewList += Lesson(data.profileId, lessonDate.value.toLong()).apply {
|
||||
type = Lesson.TYPE_NO_LESSONS
|
||||
date = lessonDate
|
||||
}
|
||||
@ -195,5 +197,5 @@ class MobidziennikApiTimetable(val data: DataMobidziennik, rows: List<String>) {
|
||||
}
|
||||
}
|
||||
}*/
|
||||
}
|
||||
}
|
||||
}}
|
||||
}
|
||||
|
@ -0,0 +1,157 @@
|
||||
/*
|
||||
* Copyright (c) Kuba Szczodrzyński 2019-11-18.
|
||||
*/
|
||||
|
||||
package pl.szczodrzynski.edziennik.api.v2.mobidziennik.data.web
|
||||
|
||||
import org.greenrobot.eventbus.EventBus
|
||||
import org.jsoup.Jsoup
|
||||
import pl.szczodrzynski.edziennik.api.v2.Regexes
|
||||
import pl.szczodrzynski.edziennik.api.v2.events.MessageGetEvent
|
||||
import pl.szczodrzynski.edziennik.api.v2.mobidziennik.DataMobidziennik
|
||||
import pl.szczodrzynski.edziennik.api.v2.mobidziennik.data.MobidziennikWeb
|
||||
import pl.szczodrzynski.edziennik.data.db.modules.messages.Message
|
||||
import pl.szczodrzynski.edziennik.data.db.modules.messages.Message.TYPE_RECEIVED
|
||||
import pl.szczodrzynski.edziennik.data.db.modules.messages.MessageFull
|
||||
import pl.szczodrzynski.edziennik.data.db.modules.messages.MessageRecipientFull
|
||||
import pl.szczodrzynski.edziennik.data.db.modules.metadata.Metadata
|
||||
import pl.szczodrzynski.edziennik.get
|
||||
import pl.szczodrzynski.edziennik.singleOrNull
|
||||
import pl.szczodrzynski.edziennik.utils.Utils.monthFromName
|
||||
import pl.szczodrzynski.edziennik.utils.models.Date
|
||||
import pl.szczodrzynski.edziennik.utils.models.Time
|
||||
|
||||
class MobidziennikWebGetMessage(
|
||||
override val data: DataMobidziennik,
|
||||
private val message: MessageFull,
|
||||
val onSuccess: () -> Unit) : MobidziennikWeb(data) {
|
||||
companion object {
|
||||
private const val TAG = "MobidziennikWebGetMessage"
|
||||
}
|
||||
|
||||
init {
|
||||
val typeUrl = if (message.type == Message.TYPE_SENT)
|
||||
"wiadwyslana"
|
||||
else
|
||||
"wiadodebrana"
|
||||
webGet(TAG, "/dziennik/$typeUrl/?id=${message.id}") { text ->
|
||||
MobidziennikLuckyNumberExtractor(data, text)
|
||||
|
||||
val messageRecipientList = mutableListOf<MessageRecipientFull>()
|
||||
|
||||
val doc = Jsoup.parse(text)
|
||||
|
||||
val content = doc.select("#content").first()
|
||||
|
||||
val body = content.select(".wiadomosc_tresc").first()
|
||||
|
||||
if (message.type == TYPE_RECEIVED) {
|
||||
var readDate = System.currentTimeMillis()
|
||||
Regexes.MOBIDZIENNIK_MESSAGE_READ_DATE.find(body.html())?.let {
|
||||
val date = Date(
|
||||
it[3].toIntOrNull() ?: 2019,
|
||||
monthFromName(it[2]),
|
||||
it[1].toIntOrNull() ?: 1
|
||||
)
|
||||
val time = Time.fromH_m_s(
|
||||
it[4] // TODO blank string safety
|
||||
)
|
||||
readDate = date.combineWith(time)
|
||||
}
|
||||
|
||||
val recipient = MessageRecipientFull(
|
||||
profileId,
|
||||
-1,
|
||||
-1,
|
||||
readDate,
|
||||
message.id
|
||||
)
|
||||
|
||||
recipient.fullName = profile?.accountNameLong ?: profile?.studentNameLong
|
||||
|
||||
messageRecipientList.add(recipient)
|
||||
} else {
|
||||
message.senderId = -1
|
||||
message.senderReplyId = -1
|
||||
|
||||
content.select("table.spis tr:has(td)")?.forEach { recipientEl ->
|
||||
val senderEl = recipientEl.select("td:eq(0)").first()
|
||||
val senderName = senderEl.text()
|
||||
|
||||
val teacher = data.teacherList.singleOrNull { it.fullNameLastFirst == senderName }
|
||||
val receiverId = teacher?.id ?: -1
|
||||
|
||||
var readDate = 0L
|
||||
val isReadEl = recipientEl.select("td:eq(2)").first()
|
||||
if (isReadEl.ownText() != "NIE") {
|
||||
val readDateEl = recipientEl.select("td:eq(3) small").first()
|
||||
Regexes.MOBIDZIENNIK_MESSAGE_SENT_READ_DATE.find(readDateEl.ownText())?.let {
|
||||
val date = Date(
|
||||
it[3].toIntOrNull() ?: 2019,
|
||||
monthFromName(it[2]),
|
||||
it[1].toIntOrNull() ?: 1
|
||||
)
|
||||
val time = Time.fromH_m_s(
|
||||
it[4] // TODO blank string safety
|
||||
)
|
||||
readDate = date.combineWith(time)
|
||||
}
|
||||
}
|
||||
|
||||
val recipient = MessageRecipientFull(
|
||||
profileId,
|
||||
receiverId,
|
||||
-1,
|
||||
readDate,
|
||||
message.id
|
||||
)
|
||||
|
||||
recipient.fullName = teacher?.fullName ?: "?"
|
||||
|
||||
messageRecipientList.add(recipient)
|
||||
}
|
||||
}
|
||||
|
||||
// this line removes the sender and read date details
|
||||
body.select("div").remove()
|
||||
|
||||
// this needs to be at the end
|
||||
message.apply {
|
||||
this.body = body.html()
|
||||
|
||||
clearAttachments()
|
||||
content.select("ul li").map { it.select("a").first() }.forEach {
|
||||
val attachmentName = it.ownText()
|
||||
Regexes.MOBIDZIENNIK_MESSAGE_ATTACHMENT.find(it.outerHtml())?.let { match ->
|
||||
val attachmentId = match[1].toLong()
|
||||
var size = match[2].toFloatOrNull() ?: 0f
|
||||
when (match[3]) {
|
||||
"K" -> size *= 1024f
|
||||
"M" -> size *= 1024f * 1024f
|
||||
"G" -> size *= 1024f * 1024f * 1024f
|
||||
}
|
||||
message.addAttachment(attachmentId, attachmentName, size.toLong())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!message.seen) { // TODO discover why this monstrosity instead of MetadataDao.setSeen
|
||||
data.messageMetadataList.add(Metadata(
|
||||
message.profileId,
|
||||
Metadata.TYPE_MESSAGE,
|
||||
message.id,
|
||||
true,
|
||||
true,
|
||||
message.addedDate
|
||||
))
|
||||
}
|
||||
|
||||
message.recipients = messageRecipientList
|
||||
data.messageRecipientList.addAll(messageRecipientList)
|
||||
data.messageList.add(message)
|
||||
|
||||
EventBus.getDefault().postSticky(MessageGetEvent(message))
|
||||
onSuccess()
|
||||
}
|
||||
}
|
||||
}
|
@ -4,6 +4,7 @@
|
||||
|
||||
package pl.szczodrzynski.edziennik.api.v2.models
|
||||
|
||||
import android.content.Context
|
||||
import com.google.gson.JsonObject
|
||||
import im.wangchao.mhttp.Request
|
||||
import im.wangchao.mhttp.Response
|
||||
@ -52,6 +53,15 @@ class ApiError(val tag: String, val errorCode: Int) {
|
||||
)
|
||||
}
|
||||
|
||||
fun getStringReason(context: Context): String {
|
||||
return context.resources.getIdentifier("error_${errorCode}_reason", "string", context.packageName).let {
|
||||
if (it != 0)
|
||||
context.getString(it)
|
||||
else
|
||||
"?"
|
||||
}
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
return "ApiError(tag='$tag', errorCode=$errorCode, profileId=$profileId, throwable=$throwable, apiResponse=$apiResponse, request=$request, response=$response, isCritical=$isCritical)"
|
||||
}
|
||||
|
@ -375,7 +375,7 @@ open class Data(val app: App, val profile: Profile?, val loginStore: LoginStore)
|
||||
}
|
||||
|
||||
fun shouldSyncLuckyNumber(): Boolean {
|
||||
return (db.luckyNumberDao().getNearestFutureNow(profileId, Date.getToday()) ?: -1) == -1
|
||||
return (db.luckyNumberDao().getNearestFutureNow(profileId, Date.getToday().value) ?: -1) == -1
|
||||
}
|
||||
|
||||
fun error(tag: String, errorCode: Int, response: Response? = null, throwable: Throwable? = null, apiResponse: JsonObject? = null) {
|
||||
|
@ -70,6 +70,10 @@ class Template(val app: App, val profile: Profile?, val loginStore: LoginStore,
|
||||
|
||||
}
|
||||
|
||||
override fun getAttachment(messageId: Long, attachmentId: Long, attachmentName: String) {
|
||||
|
||||
}
|
||||
|
||||
override fun firstLogin() {
|
||||
TemplateFirstLogin(data) {
|
||||
completed()
|
||||
|
@ -7,15 +7,14 @@ package pl.szczodrzynski.edziennik.api.v2.vulcan
|
||||
import pl.szczodrzynski.edziennik.App
|
||||
import pl.szczodrzynski.edziennik.api.v2.LOGIN_METHOD_VULCAN_API
|
||||
import pl.szczodrzynski.edziennik.api.v2.models.Data
|
||||
import pl.szczodrzynski.edziennik.currentTimeUnix
|
||||
import pl.szczodrzynski.edziennik.data.db.modules.login.LoginStore
|
||||
import pl.szczodrzynski.edziennik.data.db.modules.profiles.Profile
|
||||
import pl.szczodrzynski.edziennik.isNotNullNorEmpty
|
||||
|
||||
class DataVulcan(app: App, profile: Profile?, loginStore: LoginStore) : Data(app, profile, loginStore) {
|
||||
|
||||
fun isApiLoginValid() = apiCertificateExpiryTime-30 > currentTimeUnix()
|
||||
&& apiCertificateKey.isNotNullNorEmpty()
|
||||
fun isApiLoginValid() = /*apiCertificateExpiryTime-30 > currentTimeUnix()
|
||||
&&*/ apiCertificateKey.isNotNullNorEmpty()
|
||||
&& apiCertificatePrivate.isNotNullNorEmpty()
|
||||
&& symbol.isNotNullNorEmpty()
|
||||
|
||||
@ -165,6 +164,8 @@ class DataVulcan(app: App, profile: Profile?, loginStore: LoginStore) : Data(app
|
||||
"GD1" -> "https://uonetplus-komunikacja.edu.gdansk.pl"
|
||||
"KA1" -> "https://uonetplus-komunikacja.mcuw.katowice.eu"
|
||||
"KA2" -> "https://uonetplus-komunikacja-test.mcuw.katowice.eu"
|
||||
"LU1" -> "https://uonetplus-komunikacja.edu.lublin.eu"
|
||||
"LU2" -> "https://test-uonetplus-komunikacja.edu.lublin.eu"
|
||||
"P03" -> "https://efeb-komunikacja-pro-efebmobile.pro.vulcan.pl"
|
||||
"P01" -> "http://efeb-komunikacja.pro-hudson.win.vulcan.pl"
|
||||
"P02" -> "http://efeb-komunikacja.pro-hudsonrc.win.vulcan.pl"
|
||||
@ -173,7 +174,7 @@ class DataVulcan(app: App, profile: Profile?, loginStore: LoginStore) : Data(app
|
||||
"SZ9" -> "http://vulcan.szkolny.eu"
|
||||
else -> null
|
||||
}
|
||||
return if (url != null) "$url/$symbol" else null
|
||||
return if (url != null) "$url/$symbol" else loginStore.getLoginData("apiUrl", null)
|
||||
}
|
||||
|
||||
val fullApiUrl: String?
|
||||
|
@ -12,8 +12,10 @@ import pl.szczodrzynski.edziennik.api.v2.interfaces.EdziennikInterface
|
||||
import pl.szczodrzynski.edziennik.api.v2.models.ApiError
|
||||
import pl.szczodrzynski.edziennik.api.v2.prepare
|
||||
import pl.szczodrzynski.edziennik.api.v2.vulcan.data.VulcanData
|
||||
import pl.szczodrzynski.edziennik.api.v2.vulcan.data.api.VulcanApiMessagesChangeStatus
|
||||
import pl.szczodrzynski.edziennik.api.v2.vulcan.firstlogin.VulcanFirstLogin
|
||||
import pl.szczodrzynski.edziennik.api.v2.vulcan.login.VulcanLogin
|
||||
import pl.szczodrzynski.edziennik.api.v2.vulcan.login.VulcanLoginApi
|
||||
import pl.szczodrzynski.edziennik.api.v2.vulcanLoginMethods
|
||||
import pl.szczodrzynski.edziennik.data.db.modules.login.LoginStore
|
||||
import pl.szczodrzynski.edziennik.data.db.modules.messages.MessageFull
|
||||
@ -63,13 +65,21 @@ class Vulcan(val app: App, val profile: Profile?, val loginStore: LoginStore, va
|
||||
}
|
||||
|
||||
override fun getMessage(message: MessageFull) {
|
||||
|
||||
VulcanLoginApi(data) {
|
||||
VulcanApiMessagesChangeStatus(data, message) {
|
||||
completed()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun markAllAnnouncementsAsRead() {
|
||||
|
||||
}
|
||||
|
||||
override fun getAttachment(messageId: Long, attachmentId: Long, attachmentName: String) {
|
||||
|
||||
}
|
||||
|
||||
override fun firstLogin() {
|
||||
VulcanFirstLogin(data) {
|
||||
completed()
|
||||
|
@ -64,6 +64,10 @@ class VulcanData(val data: DataVulcan, val onSuccess: () -> Unit) {
|
||||
data.startProgress(R.string.edziennik_progress_endpoint_attendance)
|
||||
VulcanApiAttendance(data, onSuccess)
|
||||
}
|
||||
ENDPOINT_VULCAN_API_TIMETABLE -> {
|
||||
data.startProgress(R.string.edziennik_progress_endpoint_timetable)
|
||||
VulcanApiTimetable(data, onSuccess)
|
||||
}
|
||||
ENDPOINT_VULCAN_API_MESSAGES_INBOX -> {
|
||||
data.startProgress(R.string.edziennik_progress_endpoint_messages_inbox)
|
||||
VulcanApiMessagesInbox(data, onSuccess)
|
||||
|
@ -55,7 +55,7 @@ class VulcanApiAttendance(override val data: DataVulcan, val onSuccess: () -> Un
|
||||
lessonSemester,
|
||||
attendance.getString("PrzedmiotNazwa") + attendanceCategory.name.let { " - $it" },
|
||||
lessonDate,
|
||||
data.lessonRanges.get(attendance.getInt("IdPoraLekcji") ?: 0)?.startTime,
|
||||
data.lessonRanges.get(attendance.getInt("Numer") ?: 0)?.startTime,
|
||||
type)
|
||||
|
||||
data.attendanceList.add(attendanceObject)
|
||||
|
@ -35,7 +35,7 @@ class VulcanApiDictionaries(override val data: DataVulcan, val onSuccess: () ->
|
||||
elements?.getJsonArray("KategorieUwag")?.forEach { saveNoticeType(it.asJsonObject) }
|
||||
elements?.getJsonArray("KategorieFrekwencji")?.forEach { saveAttendanceType(it.asJsonObject) }
|
||||
|
||||
data.setSyncNext(ENDPOINT_VULCAN_API_DICTIONARIES, 4*DAY)
|
||||
data.setSyncNext(ENDPOINT_VULCAN_API_DICTIONARIES, 4 * DAY)
|
||||
onSuccess()
|
||||
}
|
||||
}
|
||||
@ -73,7 +73,7 @@ class VulcanApiDictionaries(override val data: DataVulcan, val onSuccess: () ->
|
||||
}
|
||||
|
||||
private fun saveLessonRange(lessonRange: JsonObject) {
|
||||
val lessonNumber = lessonRange.getInt("Id") ?: return
|
||||
val lessonNumber = lessonRange.getInt("Numer") ?: return
|
||||
val startTime = lessonRange.getString("PoczatekTekst")?.let { Time.fromH_m(it) } ?: return
|
||||
val endTime = lessonRange.getString("KoniecTekst")?.let { Time.fromH_m(it) } ?: return
|
||||
|
||||
@ -126,8 +126,7 @@ class VulcanApiDictionaries(override val data: DataVulcan, val onSuccess: () ->
|
||||
Attendance.TYPE_ABSENT_EXCUSED
|
||||
else
|
||||
Attendance.TYPE_ABSENT
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
val belated = attendanceType.getBoolean("Spoznienie") ?: false
|
||||
val released = attendanceType.getBoolean("Zwolnienie") ?: false
|
||||
val present = attendanceType.getBoolean("Obecnosc") ?: true
|
||||
|
@ -0,0 +1,61 @@
|
||||
/*
|
||||
* Copyright (c) Kacper Ziubryniewicz 2019-11-12
|
||||
*/
|
||||
|
||||
package pl.szczodrzynski.edziennik.api.v2.vulcan.data.api
|
||||
|
||||
import pl.szczodrzynski.edziennik.api.v2.VULCAN_API_ENDPOINT_MESSAGES_CHANGE_STATUS
|
||||
import pl.szczodrzynski.edziennik.api.v2.vulcan.DataVulcan
|
||||
import pl.szczodrzynski.edziennik.api.v2.vulcan.data.VulcanApi
|
||||
import pl.szczodrzynski.edziennik.data.db.modules.messages.Message.TYPE_SENT
|
||||
import pl.szczodrzynski.edziennik.data.db.modules.messages.MessageFull
|
||||
import pl.szczodrzynski.edziennik.data.db.modules.messages.MessageRecipient
|
||||
import pl.szczodrzynski.edziennik.data.db.modules.metadata.Metadata
|
||||
|
||||
class VulcanApiMessagesChangeStatus(
|
||||
override val data: DataVulcan,
|
||||
private val messageObject: MessageFull,
|
||||
val onSuccess: () -> Unit
|
||||
) : VulcanApi(data) {
|
||||
companion object {
|
||||
const val TAG = "VulcanApiMessagesChangeStatus"
|
||||
}
|
||||
|
||||
init {
|
||||
data.profile?.also { profile ->
|
||||
apiGet(TAG, VULCAN_API_ENDPOINT_MESSAGES_CHANGE_STATUS, parameters = mapOf(
|
||||
"WiadomoscId" to messageObject.id,
|
||||
"FolderWiadomosci" to "Odebrane",
|
||||
"Status" to "Widoczna",
|
||||
"LoginId" to data.studentLoginId,
|
||||
"IdUczen" to data.studentId
|
||||
)) { _, _ ->
|
||||
|
||||
if (!messageObject.seen) {
|
||||
data.messageMetadataList.add(Metadata(
|
||||
profileId,
|
||||
Metadata.TYPE_MESSAGE,
|
||||
messageObject.id,
|
||||
true,
|
||||
true,
|
||||
messageObject.addedDate
|
||||
))
|
||||
}
|
||||
|
||||
if (messageObject.type != TYPE_SENT) {
|
||||
val messageRecipientObject = MessageRecipient(
|
||||
profileId,
|
||||
-1,
|
||||
-1,
|
||||
System.currentTimeMillis(),
|
||||
messageObject.id
|
||||
)
|
||||
|
||||
data.messageRecipientList.add(messageRecipientObject)
|
||||
}
|
||||
|
||||
onSuccess()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -14,6 +14,8 @@ import pl.szczodrzynski.edziennik.data.db.modules.messages.Message
|
||||
import pl.szczodrzynski.edziennik.data.db.modules.messages.Message.TYPE_RECEIVED
|
||||
import pl.szczodrzynski.edziennik.data.db.modules.messages.MessageRecipient
|
||||
import pl.szczodrzynski.edziennik.data.db.modules.metadata.Metadata
|
||||
import pl.szczodrzynski.edziennik.data.db.modules.teachers.Teacher
|
||||
import pl.szczodrzynski.edziennik.utils.Utils
|
||||
import pl.szczodrzynski.edziennik.utils.models.Date
|
||||
|
||||
class VulcanApiMessagesInbox(override val data: DataVulcan, val onSuccess: () -> Unit) : VulcanApi(data) {
|
||||
@ -43,7 +45,22 @@ class VulcanApiMessagesInbox(override val data: DataVulcan, val onSuccess: () ->
|
||||
|
||||
val senderLoginId = message.getString("NadawcaId") ?: return@forEach
|
||||
val senderId = data.teacherList
|
||||
.singleOrNull { it.loginId == senderLoginId }?.id ?: return@forEach
|
||||
.singleOrNull { it.loginId == senderLoginId }?.id ?: {
|
||||
|
||||
val senderName = message.getString("Nadawca") ?: ""
|
||||
|
||||
senderName.getLastFirstName()?.let { (senderLastName, senderFirstName) ->
|
||||
val teacherObject = Teacher(
|
||||
profileId,
|
||||
-1 * Utils.crc16(senderName.toByteArray()).toLong(),
|
||||
senderFirstName,
|
||||
senderLastName,
|
||||
senderLoginId
|
||||
)
|
||||
data.teacherList.put(teacherObject.id, teacherObject)
|
||||
teacherObject.id
|
||||
}
|
||||
}.invoke() ?: -1
|
||||
|
||||
val sentDate = message.getLong("DataWyslaniaUnixEpoch")?.let { it * 1000 }
|
||||
?: -1
|
||||
|
@ -14,6 +14,8 @@ import pl.szczodrzynski.edziennik.data.db.modules.messages.Message
|
||||
import pl.szczodrzynski.edziennik.data.db.modules.messages.Message.TYPE_SENT
|
||||
import pl.szczodrzynski.edziennik.data.db.modules.messages.MessageRecipient
|
||||
import pl.szczodrzynski.edziennik.data.db.modules.metadata.Metadata
|
||||
import pl.szczodrzynski.edziennik.data.db.modules.teachers.Teacher
|
||||
import pl.szczodrzynski.edziennik.utils.Utils
|
||||
import pl.szczodrzynski.edziennik.utils.models.Date
|
||||
|
||||
class VulcanApiMessagesSent(override val data: DataVulcan, val onSuccess: () -> Unit) : VulcanApi(data) {
|
||||
@ -23,11 +25,11 @@ class VulcanApiMessagesSent(override val data: DataVulcan, val onSuccess: () ->
|
||||
|
||||
init {
|
||||
data.profile?.also { profile ->
|
||||
val startDate: String = when (profile.empty) {
|
||||
true -> profile.getSemesterStart(profile.currentSemester).stringY_m_d
|
||||
else -> Date.getToday().stepForward(0, -1, 0).stringY_m_d
|
||||
val startDate: Long = when (profile.empty) {
|
||||
true -> profile.getSemesterStart(profile.currentSemester).inUnix
|
||||
else -> Date.getToday().stepForward(0, -1, 0).inUnix
|
||||
}
|
||||
val endDate: String = profile.getSemesterEnd(profile.currentSemester).stringY_m_d
|
||||
val endDate: Long = profile.getSemesterEnd(profile.currentSemester).inUnix
|
||||
|
||||
apiGet(TAG, VULCAN_API_ENDPOINT_MESSAGES_SENT, parameters = mapOf(
|
||||
"DataPoczatkowa" to startDate,
|
||||
@ -43,23 +45,27 @@ class VulcanApiMessagesSent(override val data: DataVulcan, val onSuccess: () ->
|
||||
val unreadBy = message.getInt("Nieprzeczytane") ?: 0
|
||||
val sentDate = message.getLong("DataWyslaniaUnixEpoch")?.let { it * 1000 } ?: -1
|
||||
|
||||
val messageObject = Message(
|
||||
profileId,
|
||||
id,
|
||||
subject,
|
||||
body,
|
||||
TYPE_SENT,
|
||||
-1,
|
||||
-1
|
||||
)
|
||||
|
||||
message.getJsonArray("Adresaci")?.asJsonObjectList()
|
||||
?.forEachIndexed { _, recipient ->
|
||||
?.onEach { receiver ->
|
||||
|
||||
val recipientLoginId = recipient.getString("LoginId")
|
||||
?: return@forEachIndexed
|
||||
val recipientId = data.teacherList.singleOrNull { it.loginId == recipientLoginId }?.id
|
||||
?: return@forEachIndexed
|
||||
val receiverLoginId = receiver.getString("LoginId")
|
||||
?: return@onEach
|
||||
val receiverId = data.teacherList.singleOrNull { it.loginId == receiverLoginId }?.id
|
||||
?: {
|
||||
val receiverName = receiver.getString("Nazwa") ?: ""
|
||||
|
||||
receiverName.getLastFirstName()?.let { (receiverLastName, receiverFirstName) ->
|
||||
val teacherObject = Teacher(
|
||||
profileId,
|
||||
-1 * Utils.crc16(receiverName.toByteArray()).toLong(),
|
||||
receiverFirstName,
|
||||
receiverLastName,
|
||||
receiverLoginId
|
||||
)
|
||||
data.teacherList.put(teacherObject.id, teacherObject)
|
||||
teacherObject.id
|
||||
}
|
||||
}.invoke() ?: -1
|
||||
|
||||
val readDate: Long = when (readBy) {
|
||||
0 -> 0
|
||||
@ -71,7 +77,7 @@ class VulcanApiMessagesSent(override val data: DataVulcan, val onSuccess: () ->
|
||||
|
||||
val messageRecipientObject = MessageRecipient(
|
||||
profileId,
|
||||
recipientId,
|
||||
receiverId,
|
||||
-1,
|
||||
readDate,
|
||||
id
|
||||
@ -80,6 +86,16 @@ class VulcanApiMessagesSent(override val data: DataVulcan, val onSuccess: () ->
|
||||
data.messageRecipientList.add(messageRecipientObject)
|
||||
}
|
||||
|
||||
val messageObject = Message(
|
||||
profileId,
|
||||
id,
|
||||
subject,
|
||||
body,
|
||||
TYPE_SENT,
|
||||
-1,
|
||||
-1
|
||||
)
|
||||
|
||||
data.messageIgnoreList.add(messageObject)
|
||||
data.metadataList.add(Metadata(
|
||||
profileId,
|
||||
|
@ -0,0 +1,213 @@
|
||||
/*
|
||||
* Copyright (c) Kacper Ziubryniewicz 2019-11-13
|
||||
*/
|
||||
|
||||
package pl.szczodrzynski.edziennik.api.v2.vulcan.data.api
|
||||
|
||||
import androidx.core.util.set
|
||||
import pl.szczodrzynski.edziennik.*
|
||||
import pl.szczodrzynski.edziennik.api.v2.Regexes
|
||||
import pl.szczodrzynski.edziennik.api.v2.VULCAN_API_ENDPOINT_TIMETABLE
|
||||
import pl.szczodrzynski.edziennik.api.v2.models.DataRemoveModel
|
||||
import pl.szczodrzynski.edziennik.api.v2.vulcan.DataVulcan
|
||||
import pl.szczodrzynski.edziennik.api.v2.vulcan.ENDPOINT_VULCAN_API_TIMETABLE
|
||||
import pl.szczodrzynski.edziennik.api.v2.vulcan.data.VulcanApi
|
||||
import pl.szczodrzynski.edziennik.data.db.modules.api.SYNC_ALWAYS
|
||||
import pl.szczodrzynski.edziennik.data.db.modules.metadata.Metadata
|
||||
import pl.szczodrzynski.edziennik.data.db.modules.subjects.Subject
|
||||
import pl.szczodrzynski.edziennik.data.db.modules.teams.Team
|
||||
import pl.szczodrzynski.edziennik.data.db.modules.timetable.Lesson
|
||||
import pl.szczodrzynski.edziennik.utils.Utils.crc16
|
||||
import pl.szczodrzynski.edziennik.utils.Utils.d
|
||||
import pl.szczodrzynski.edziennik.utils.models.Date
|
||||
import pl.szczodrzynski.edziennik.utils.models.Week
|
||||
|
||||
class VulcanApiTimetable(override val data: DataVulcan, val onSuccess: () -> Unit) : VulcanApi(data) {
|
||||
companion object {
|
||||
const val TAG = "VulcanApiTimetable"
|
||||
}
|
||||
|
||||
init { data.profile?.also { profile ->
|
||||
val currentWeekStart = Week.getWeekStart()
|
||||
|
||||
if (Date.getToday().weekDay > 4) {
|
||||
currentWeekStart.stepForward(0, 0, 7)
|
||||
}
|
||||
|
||||
val getDate = data.arguments?.getString("weekStart") ?: currentWeekStart.stringY_m_d
|
||||
|
||||
val weekStart = Date.fromY_m_d(getDate)
|
||||
val weekEnd = weekStart.clone().stepForward(0, 0, 6)
|
||||
|
||||
apiGet(TAG, VULCAN_API_ENDPOINT_TIMETABLE, parameters = mapOf(
|
||||
"DataPoczatkowa" to weekStart.stringY_m_d,
|
||||
"DataKoncowa" to weekEnd.stringY_m_d,
|
||||
"IdUczen" to data.studentId,
|
||||
"IdOddzial" to data.studentClassId,
|
||||
"IdOkresKlasyfikacyjny" to data.studentSemesterId
|
||||
)) { json, _ ->
|
||||
val dates = mutableSetOf<Int>()
|
||||
val lessons = mutableListOf<Lesson>()
|
||||
|
||||
json.getJsonArray("Data")?.asJsonObjectList()?.forEach { lesson ->
|
||||
if (lesson.getBoolean("PlanUcznia") != true)
|
||||
return@forEach
|
||||
val lessonDate = Date.fromY_m_d(lesson.getString("DzienTekst"))
|
||||
val lessonNumber = lesson.getInt("NumerLekcji")
|
||||
val lessonRange = data.lessonRanges.singleOrNull { it.lessonNumber == lessonNumber }
|
||||
val startTime = lessonRange?.startTime
|
||||
val endTime = lessonRange?.endTime
|
||||
val teacherId = lesson.getLong("IdPracownik")
|
||||
val classroom = lesson.getString("Sala")
|
||||
|
||||
val oldTeacherId = lesson.getLong("IdPracownikOld")
|
||||
|
||||
val changeAnnotation = lesson.getString("AdnotacjaOZmianie") ?: ""
|
||||
val type = when {
|
||||
changeAnnotation.startsWith("(przeniesiona z") -> Lesson.TYPE_SHIFTED_TARGET
|
||||
changeAnnotation.startsWith("(przeniesiona na") -> Lesson.TYPE_SHIFTED_SOURCE
|
||||
changeAnnotation.startsWith("(zastępstwo") -> Lesson.TYPE_CHANGE
|
||||
lesson.getBoolean("PrzekreslonaNazwa") == true -> Lesson.TYPE_CANCELLED
|
||||
else -> Lesson.TYPE_NORMAL
|
||||
}
|
||||
|
||||
val teamId = lesson.getString("PodzialSkrot")?.let { teamName ->
|
||||
val name = "${data.teamClass?.name} $teamName"
|
||||
val id = name.crc16().toLong()
|
||||
var team = data.teamList.singleOrNull { it.name == name }
|
||||
if (team == null) {
|
||||
team = Team(
|
||||
profileId,
|
||||
id,
|
||||
name,
|
||||
Team.TYPE_VIRTUAL,
|
||||
"${data.schoolName}:$name",
|
||||
teacherId ?: oldTeacherId ?: -1
|
||||
)
|
||||
data.teamList[id] = team
|
||||
}
|
||||
team.id
|
||||
} ?: data.studentClassId.toLong()
|
||||
|
||||
val subjectId = lesson.getLong("IdPrzedmiot")?.let {
|
||||
when (it) {
|
||||
0L -> {
|
||||
val subjectName = lesson.getString("PrzedmiotNazwa") ?: ""
|
||||
|
||||
data.subjectList.singleOrNull { subject -> subject.longName == subjectName }?.id
|
||||
?: {
|
||||
/**
|
||||
* CREATE A NEW SUBJECT IF IT DOESN'T EXIST
|
||||
*/
|
||||
|
||||
val subjectObject = Subject(
|
||||
profileId,
|
||||
-1 * crc16(subjectName.toByteArray()).toLong(),
|
||||
subjectName,
|
||||
subjectName
|
||||
)
|
||||
data.subjectList.put(subjectObject.id, subjectObject)
|
||||
subjectObject.id
|
||||
}.invoke()
|
||||
}
|
||||
else -> it
|
||||
}
|
||||
}
|
||||
|
||||
val lessonObject = Lesson(profileId, -1).apply {
|
||||
this.type = type
|
||||
|
||||
when (type) {
|
||||
Lesson.TYPE_NORMAL, Lesson.TYPE_CHANGE, Lesson.TYPE_SHIFTED_TARGET -> {
|
||||
this.date = lessonDate
|
||||
this.lessonNumber = lessonNumber
|
||||
this.startTime = startTime
|
||||
this.endTime = endTime
|
||||
this.subjectId = subjectId
|
||||
this.teacherId = teacherId
|
||||
this.teamId = teamId
|
||||
this.classroom = classroom
|
||||
|
||||
this.oldTeacherId = oldTeacherId
|
||||
}
|
||||
|
||||
Lesson.TYPE_CANCELLED, Lesson.TYPE_SHIFTED_SOURCE -> {
|
||||
this.oldDate = lessonDate
|
||||
this.oldLessonNumber = lessonNumber
|
||||
this.oldStartTime = startTime
|
||||
this.oldEndTime = endTime
|
||||
this.oldSubjectId = subjectId
|
||||
this.oldTeacherId = teacherId
|
||||
this.oldTeamId = teamId
|
||||
this.oldClassroom = classroom
|
||||
}
|
||||
}
|
||||
|
||||
if (type == Lesson.TYPE_SHIFTED_SOURCE || type == Lesson.TYPE_SHIFTED_TARGET) {
|
||||
val shift = Regexes.VULCAN_SHITFT_ANNOTATION.find(changeAnnotation)
|
||||
val oldLessonNumber = shift?.get(2)?.toInt()
|
||||
val oldLessonDate = shift?.get(3)?.let { Date.fromd_m_Y(it) }
|
||||
|
||||
val oldLessonRange = data.lessonRanges.singleOrNull { it.lessonNumber == oldLessonNumber }
|
||||
val oldStartTime = oldLessonRange?.startTime
|
||||
val oldEndTime = oldLessonRange?.endTime
|
||||
|
||||
when (type) {
|
||||
Lesson.TYPE_SHIFTED_SOURCE -> {
|
||||
this.lessonNumber = oldLessonNumber
|
||||
this.date = oldLessonDate
|
||||
this.startTime = oldStartTime
|
||||
this.endTime = oldEndTime
|
||||
}
|
||||
|
||||
Lesson.TYPE_SHIFTED_TARGET -> {
|
||||
this.oldLessonNumber = oldLessonNumber
|
||||
this.oldDate = oldLessonDate
|
||||
this.oldStartTime = oldStartTime
|
||||
this.oldEndTime = oldEndTime
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.id = buildId()
|
||||
}
|
||||
|
||||
val seen = profile.empty || lessonDate < Date.getToday()
|
||||
|
||||
if (type != Lesson.TYPE_NORMAL) {
|
||||
data.metadataList.add(Metadata(
|
||||
profileId,
|
||||
Metadata.TYPE_LESSON_CHANGE,
|
||||
lessonObject.id,
|
||||
seen,
|
||||
seen,
|
||||
System.currentTimeMillis()
|
||||
))
|
||||
}
|
||||
|
||||
dates.add(lessonDate.value)
|
||||
lessons.add(lessonObject)
|
||||
}
|
||||
|
||||
val date: Date = weekStart.clone()
|
||||
while (date <= weekEnd) {
|
||||
if (!dates.contains(date.value)) {
|
||||
lessons.add(Lesson(profileId, date.value.toLong()).apply {
|
||||
this.type = Lesson.TYPE_NO_LESSONS
|
||||
this.date = date.clone()
|
||||
})
|
||||
}
|
||||
|
||||
date.stepForward(0, 0, 1)
|
||||
}
|
||||
|
||||
d(TAG, "Clearing lessons between ${weekStart.stringY_m_d} and ${weekEnd.stringY_m_d} - timetable downloaded for $getDate")
|
||||
|
||||
data.lessonNewList.addAll(lessons)
|
||||
data.toRemove.add(DataRemoveModel.Timetable.between(weekStart, weekEnd))
|
||||
|
||||
data.setSyncNext(ENDPOINT_VULCAN_API_TIMETABLE, SYNC_ALWAYS)
|
||||
onSuccess()
|
||||
}
|
||||
}}
|
||||
}
|
@ -38,10 +38,12 @@ class VulcanLoginApi(val data: DataVulcan, val onSuccess: () -> Unit) {
|
||||
if (data.apiToken?.get(0) == 'F') VULCAN_API_PASSWORD_FAKELOG else VULCAN_API_PASSWORD,
|
||||
data.apiCertificatePfx ?: ""
|
||||
)
|
||||
onSuccess()
|
||||
return@run
|
||||
data.loginStore.removeLoginData("certificatePfx")
|
||||
} catch (e: Throwable) {
|
||||
e.printStackTrace()
|
||||
} finally {
|
||||
onSuccess()
|
||||
return@run
|
||||
}
|
||||
}
|
||||
if (data.symbol.isNotNullNorEmpty() && data.apiToken.isNotNullNorEmpty() && data.apiPin.isNotNullNorEmpty()) {
|
||||
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -1,11 +0,0 @@
|
||||
package pl.szczodrzynski.edziennik.data.api.interfaces;
|
||||
|
||||
import im.wangchao.mhttp.Request;
|
||||
|
||||
/**
|
||||
* Callback containing a {@link Request.Builder} which has correct headers and body to download a corresponding message attachment when ran.
|
||||
* {@code onSuccess} has to be ran on the UI thread.
|
||||
*/
|
||||
public interface AttachmentGetCallback {
|
||||
void onSuccess(Request.Builder builder);
|
||||
}
|
@ -1,92 +0,0 @@
|
||||
package pl.szczodrzynski.edziennik.data.api.interfaces;
|
||||
|
||||
import android.content.Context;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import pl.szczodrzynski.edziennik.data.db.modules.login.LoginStore;
|
||||
import pl.szczodrzynski.edziennik.data.db.modules.messages.MessageFull;
|
||||
import pl.szczodrzynski.edziennik.data.db.modules.profiles.Profile;
|
||||
import pl.szczodrzynski.edziennik.data.db.modules.profiles.ProfileFull;
|
||||
import pl.szczodrzynski.edziennik.data.db.modules.teachers.Teacher;
|
||||
import pl.szczodrzynski.edziennik.ui.modules.messages.MessagesComposeInfo;
|
||||
import pl.szczodrzynski.edziennik.utils.models.Endpoint;
|
||||
|
||||
public interface EdziennikInterface {
|
||||
|
||||
/**
|
||||
* Sync all Edziennik data.
|
||||
* Ran always on worker thread.
|
||||
*
|
||||
* @param activityContext a {@link Context}, used for resource extractions, passed back to {@link SyncCallback}
|
||||
* @param callback ran on worker thread.
|
||||
* @param profileId
|
||||
* @param profile
|
||||
* @param loginStore
|
||||
*/
|
||||
void sync(@NonNull Context activityContext, @NonNull SyncCallback callback, int profileId, @Nullable Profile profile, @NonNull LoginStore loginStore);
|
||||
void syncMessages(@NonNull Context activityContext, @NonNull SyncCallback errorCallback, @NonNull ProfileFull profile);
|
||||
void syncFeature(@NonNull Context activityContext, @NonNull SyncCallback callback, @NonNull ProfileFull profile, int ... featureList);
|
||||
|
||||
int FEATURE_ALL = 0;
|
||||
int FEATURE_TIMETABLE = 1;
|
||||
int FEATURE_AGENDA = 2;
|
||||
int FEATURE_GRADES = 3;
|
||||
int FEATURE_HOMEWORK = 4;
|
||||
int FEATURE_NOTICES = 5;
|
||||
int FEATURE_ATTENDANCE = 6;
|
||||
int FEATURE_MESSAGES_INBOX = 7;
|
||||
int FEATURE_MESSAGES_OUTBOX = 8;
|
||||
int FEATURE_ANNOUNCEMENTS = 9;
|
||||
|
||||
/**
|
||||
* Download a single message or get its recipient list if it's already downloaded.
|
||||
*
|
||||
* May be executed on any thread.
|
||||
*
|
||||
* @param activityContext
|
||||
* @param errorCallback used for error reporting. Ran on a background thread.
|
||||
* @param profile
|
||||
* @param message a message of which body and recipient list should be downloaded.
|
||||
* @param messageCallback always executed on UI thread.
|
||||
*/
|
||||
void getMessage(@NonNull Context activityContext, @NonNull SyncCallback errorCallback, @NonNull ProfileFull profile, @NonNull MessageFull message, @NonNull MessageGetCallback messageCallback);
|
||||
void getAttachment(@NonNull Context activityContext, @NonNull SyncCallback errorCallback, @NonNull ProfileFull profile, @NonNull MessageFull message, long attachmentId, @NonNull AttachmentGetCallback attachmentCallback);
|
||||
//void getMessageList(@NonNull Context activityContext, @NonNull SyncCallback errorCallback, @NonNull ProfileFull profile, int type, @NonNull MessageListCallback messageCallback);
|
||||
/**
|
||||
* Download a list of available message recipients.
|
||||
*
|
||||
* Updates a database-saved {@code teacherList} with {@code loginId}s.
|
||||
*
|
||||
* A {@link Teacher} is considered as a recipient when its {@code loginId} is not null.
|
||||
*
|
||||
* May be executed on any thread.
|
||||
*
|
||||
* @param activityContext
|
||||
* @param errorCallback used for error reporting. Ran on a background thread.
|
||||
* @param profile
|
||||
* @param recipientListGetCallback always executed on UI thread.
|
||||
*/
|
||||
void getRecipientList(@NonNull Context activityContext, @NonNull SyncCallback errorCallback, @NonNull ProfileFull profile, @NonNull RecipientListGetCallback recipientListGetCallback);
|
||||
MessagesComposeInfo getComposeInfo(@NonNull ProfileFull profile);
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
* @param profile a {@link Profile} containing already changed endpoints
|
||||
* @return a map of configurable {@link Endpoint}s along with their names, {@code null} when unsupported
|
||||
*/
|
||||
Map<String, Endpoint> getConfigurableEndpoints(Profile profile);
|
||||
|
||||
/**
|
||||
* Check if the specified endpoint is enabled for the current profile.
|
||||
*
|
||||
* @param profile a {@link Profile} containing already changed endpoints
|
||||
* @param defaultActive if the endpoint is enabled by default.
|
||||
* @param name the endpoint's name
|
||||
* @return {@code true} if the endpoint is enabled, {@code false} when it's not. Return {@code defaultActive} if unsupported.
|
||||
*/
|
||||
boolean isEndpointEnabled(Profile profile, boolean defaultActive, String name);
|
||||
}
|
@ -1,11 +0,0 @@
|
||||
package pl.szczodrzynski.edziennik.data.api.interfaces;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import pl.szczodrzynski.edziennik.data.api.AppError;
|
||||
|
||||
public interface ErrorCallback {
|
||||
void onError(Context activityContext, @NonNull AppError error);
|
||||
}
|
@ -1,5 +0,0 @@
|
||||
package pl.szczodrzynski.edziennik.data.api.interfaces;
|
||||
|
||||
public interface LoginCallback {
|
||||
void onSuccess();
|
||||
}
|
@ -1,11 +0,0 @@
|
||||
package pl.szczodrzynski.edziennik.data.api.interfaces;
|
||||
|
||||
import pl.szczodrzynski.edziennik.data.db.modules.messages.MessageFull;
|
||||
|
||||
/**
|
||||
* Callback containing a {@link MessageFull} which already has its {@code body} and {@code recipients}.
|
||||
* {@code onSuccess} is always ran on the UI thread.
|
||||
*/
|
||||
public interface MessageGetCallback {
|
||||
void onSuccess(MessageFull message);
|
||||
}
|
@ -1,9 +0,0 @@
|
||||
package pl.szczodrzynski.edziennik.data.api.interfaces;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import pl.szczodrzynski.edziennik.data.db.modules.messages.MessageFull;
|
||||
|
||||
public interface MessageListCallback {
|
||||
void onSuccess(List<MessageFull> messageList);
|
||||
}
|
@ -1,8 +0,0 @@
|
||||
package pl.szczodrzynski.edziennik.data.api.interfaces;
|
||||
|
||||
import androidx.annotation.StringRes;
|
||||
|
||||
public interface ProgressCallback extends ErrorCallback {
|
||||
void onProgress(int progressStep);
|
||||
void onActionStarted(@StringRes int stringResId);
|
||||
}
|
@ -1,9 +0,0 @@
|
||||
package pl.szczodrzynski.edziennik.data.api.interfaces;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import pl.szczodrzynski.edziennik.data.db.modules.teachers.Teacher;
|
||||
|
||||
public interface RecipientListGetCallback {
|
||||
void onSuccess(List<Teacher> teacherList);
|
||||
}
|
@ -1,19 +0,0 @@
|
||||
package pl.szczodrzynski.edziennik.data.api.interfaces;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import pl.szczodrzynski.edziennik.data.db.modules.login.LoginStore;
|
||||
import pl.szczodrzynski.edziennik.data.db.modules.profiles.Profile;
|
||||
import pl.szczodrzynski.edziennik.data.db.modules.profiles.ProfileFull;
|
||||
|
||||
/**
|
||||
* A callback used for error reporting, progress information.
|
||||
* All the methods are always ran on a worker thread.
|
||||
*/
|
||||
public interface SyncCallback extends ProgressCallback {
|
||||
void onLoginFirst(List<Profile> profileList, LoginStore loginStore);
|
||||
void onSuccess(Context activityContext, ProfileFull profileFull);
|
||||
|
||||
}
|
@ -1,6 +0,0 @@
|
||||
package pl.szczodrzynski.edziennik.data.api.v2
|
||||
|
||||
import pl.szczodrzynski.edziennik.data.api.AppError
|
||||
import pl.szczodrzynski.edziennik.data.db.modules.login.LoginStore
|
||||
|
||||
data class ApiLoginResult(val loginStore: LoginStore, val error: AppError?)
|
@ -1,6 +0,0 @@
|
||||
package pl.szczodrzynski.edziennik.data.api.v2
|
||||
|
||||
import pl.szczodrzynski.edziennik.data.api.AppError
|
||||
import pl.szczodrzynski.edziennik.data.db.modules.profiles.Profile
|
||||
|
||||
data class FirstLoginResult(val profileList: ArrayList<Profile>, val error: AppError?)
|
@ -1,12 +0,0 @@
|
||||
package pl.szczodrzynski.edziennik.data.api.v2.librus.firstlogin
|
||||
|
||||
import pl.szczodrzynski.edziennik.App
|
||||
import pl.szczodrzynski.edziennik.data.api.interfaces.ProgressCallback
|
||||
import pl.szczodrzynski.edziennik.data.db.modules.login.LoginStore
|
||||
import pl.szczodrzynski.edziennik.data.db.modules.profiles.Profile
|
||||
|
||||
class FirstLoginLibrus(val app: App, val loginStore: LoginStore, val progressCallback: ProgressCallback, val onSuccess: (profileList: List<Profile>) -> Unit) {
|
||||
init {
|
||||
|
||||
}
|
||||
}
|
@ -1,12 +0,0 @@
|
||||
package pl.szczodrzynski.edziennik.data.api.v2.librus.firstlogin
|
||||
|
||||
import pl.szczodrzynski.edziennik.App
|
||||
import pl.szczodrzynski.edziennik.data.api.interfaces.ProgressCallback
|
||||
import pl.szczodrzynski.edziennik.data.db.modules.login.LoginStore
|
||||
import pl.szczodrzynski.edziennik.data.db.modules.profiles.Profile
|
||||
|
||||
class FirstLoginSynergia(val app: App, val loginStore: LoginStore, val progressCallback: ProgressCallback, val onSuccess: (profileList: List<Profile>) -> Unit) {
|
||||
init {
|
||||
|
||||
}
|
||||
}
|
@ -1,10 +1,10 @@
|
||||
package pl.szczodrzynski.edziennik.data.db.modules.grades;
|
||||
|
||||
import androidx.room.Entity;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import androidx.room.Entity;
|
||||
|
||||
@Entity(tableName = "gradeCategories",
|
||||
primaryKeys = {"profileId", "categoryId"})
|
||||
public class GradeCategory {
|
||||
@ -25,6 +25,9 @@ public class GradeCategory {
|
||||
*/
|
||||
public int type = 0;
|
||||
|
||||
public static final int TYPE_NORMAL = 0;
|
||||
public static final int TYPE_COMMENT = 1;
|
||||
|
||||
public GradeCategory(int profileId, long categoryId, float weight, int color, String text) {
|
||||
this.profileId = profileId;
|
||||
this.categoryId = categoryId;
|
||||
|
@ -37,7 +37,10 @@ public abstract class LuckyNumberDao {
|
||||
|
||||
@Nullable
|
||||
@Query("SELECT * FROM luckyNumbers WHERE profileId = :profileId AND luckyNumberDate >= :date ORDER BY luckyNumberDate DESC LIMIT 1")
|
||||
public abstract LuckyNumber getNearestFutureNow(int profileId, Date date);
|
||||
public abstract LuckyNumber getNearestFutureNow(int profileId, int date);
|
||||
|
||||
@Query("SELECT * FROM luckyNumbers WHERE profileId = :profileId AND luckyNumberDate >= :date ORDER BY luckyNumberDate DESC LIMIT 1")
|
||||
public abstract LiveData<LuckyNumber> getNearestFuture(int profileId, int date);
|
||||
|
||||
@RawQuery(observedEntities = {LuckyNumber.class})
|
||||
abstract LiveData<List<LuckyNumberFull>> getAll(SupportSQLiteQuery query);
|
||||
|
@ -21,7 +21,10 @@ interface NotificationDao {
|
||||
@Query("DELETE FROM notifications WHERE profileId = :profileId")
|
||||
fun clear(profileId: Int)
|
||||
|
||||
@Query("SELECT * FROM notifications")
|
||||
@Query("DELETE FROM notifications")
|
||||
fun clearAll()
|
||||
|
||||
@Query("SELECT * FROM notifications ORDER BY addedDate DESC")
|
||||
fun getAll(): LiveData<List<Notification>>
|
||||
|
||||
@Query("SELECT * FROM notifications")
|
||||
|
@ -15,7 +15,7 @@ import pl.szczodrzynski.edziennik.utils.models.Time
|
||||
Index(value = ["profileId", "type", "date"]),
|
||||
Index(value = ["profileId", "type", "oldDate"])
|
||||
])
|
||||
open class Lesson(val profileId: Int, @PrimaryKey val id: Long) {
|
||||
open class Lesson(val profileId: Int, @PrimaryKey var id: Long) {
|
||||
companion object {
|
||||
const val TYPE_NO_LESSONS = -1
|
||||
const val TYPE_NORMAL = 0
|
||||
@ -45,6 +45,22 @@ open class Lesson(val profileId: Int, @PrimaryKey val id: Long) {
|
||||
var oldTeamId: Long? = null
|
||||
var oldClassroom: String? = null
|
||||
|
||||
val displayDate: Date?
|
||||
get() {
|
||||
if (type == TYPE_SHIFTED_SOURCE)
|
||||
return oldDate
|
||||
return date ?: oldDate
|
||||
}
|
||||
|
||||
val displayStartTime: Time?
|
||||
get() {
|
||||
if (type == TYPE_SHIFTED_SOURCE)
|
||||
return oldStartTime
|
||||
return startTime ?: oldStartTime
|
||||
}
|
||||
|
||||
fun buildId(): Long = (displayDate?.combineWith(displayStartTime) ?: 0L) / 6L * 10L + (hashCode() and 0xFFFF)
|
||||
|
||||
override fun toString(): String {
|
||||
return "Lesson(profileId=$profileId, " +
|
||||
"id=$id, " +
|
||||
@ -66,6 +82,55 @@ open class Lesson(val profileId: Int, @PrimaryKey val id: Long) {
|
||||
"oldTeamId=$oldTeamId, " +
|
||||
"oldClassroom=$oldClassroom)"
|
||||
}
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (other !is Lesson) return false
|
||||
|
||||
if (profileId != other.profileId) return false
|
||||
if (id != other.id) return false
|
||||
if (type != other.type) return false
|
||||
if (date != other.date) return false
|
||||
if (lessonNumber != other.lessonNumber) return false
|
||||
if (startTime != other.startTime) return false
|
||||
if (endTime != other.endTime) return false
|
||||
if (subjectId != other.subjectId) return false
|
||||
if (teacherId != other.teacherId) return false
|
||||
if (teamId != other.teamId) return false
|
||||
if (classroom != other.classroom) return false
|
||||
if (oldDate != other.oldDate) return false
|
||||
if (oldLessonNumber != other.oldLessonNumber) return false
|
||||
if (oldStartTime != other.oldStartTime) return false
|
||||
if (oldEndTime != other.oldEndTime) return false
|
||||
if (oldSubjectId != other.oldSubjectId) return false
|
||||
if (oldTeacherId != other.oldTeacherId) return false
|
||||
if (oldTeamId != other.oldTeamId) return false
|
||||
if (oldClassroom != other.oldClassroom) return false
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
var result = profileId
|
||||
result = 31 * result + type
|
||||
result = 31 * result + (date?.hashCode() ?: 0)
|
||||
result = 31 * result + (lessonNumber ?: 0)
|
||||
result = 31 * result + (startTime?.hashCode() ?: 0)
|
||||
result = 31 * result + (endTime?.hashCode() ?: 0)
|
||||
result = 31 * result + (subjectId?.hashCode() ?: 0)
|
||||
result = 31 * result + (teacherId?.hashCode() ?: 0)
|
||||
result = 31 * result + (teamId?.hashCode() ?: 0)
|
||||
result = 31 * result + (classroom?.hashCode() ?: 0)
|
||||
result = 31 * result + (oldDate?.hashCode() ?: 0)
|
||||
result = 31 * result + (oldLessonNumber ?: 0)
|
||||
result = 31 * result + (oldStartTime?.hashCode() ?: 0)
|
||||
result = 31 * result + (oldEndTime?.hashCode() ?: 0)
|
||||
result = 31 * result + (oldSubjectId?.hashCode() ?: 0)
|
||||
result = 31 * result + (oldTeacherId?.hashCode() ?: 0)
|
||||
result = 31 * result + (oldTeamId?.hashCode() ?: 0)
|
||||
result = 31 * result + (oldClassroom?.hashCode() ?: 0)
|
||||
return result
|
||||
}
|
||||
}
|
||||
/*
|
||||
DROP TABLE lessons;
|
||||
@ -94,4 +159,4 @@ CREATE TABLE lessons (
|
||||
|
||||
PRIMARY KEY(profileId)
|
||||
);
|
||||
*/
|
||||
*/
|
||||
|
@ -1,6 +1,7 @@
|
||||
package pl.szczodrzynski.edziennik.data.db.modules.timetable
|
||||
|
||||
import pl.szczodrzynski.edziennik.utils.models.Date
|
||||
import android.content.Context
|
||||
import pl.szczodrzynski.edziennik.R
|
||||
import pl.szczodrzynski.edziennik.utils.models.Time
|
||||
|
||||
class LessonFull(profileId: Int, id: Long) : Lesson(profileId, id) {
|
||||
@ -11,24 +12,13 @@ class LessonFull(profileId: Int, id: Long) : Lesson(profileId, id) {
|
||||
var oldTeacherName: String? = null
|
||||
var oldTeamName: String? = null
|
||||
|
||||
val displayDate: Date?
|
||||
get() {
|
||||
if (type == TYPE_SHIFTED_SOURCE)
|
||||
return oldDate
|
||||
return date ?: oldDate
|
||||
}
|
||||
val displayLessonNumber: Int?
|
||||
get() {
|
||||
if (type == TYPE_SHIFTED_SOURCE)
|
||||
return oldLessonNumber
|
||||
return lessonNumber ?: oldLessonNumber
|
||||
}
|
||||
val displayStartTime: Time?
|
||||
get() {
|
||||
if (type == TYPE_SHIFTED_SOURCE)
|
||||
return oldStartTime
|
||||
return startTime ?: oldStartTime
|
||||
}
|
||||
|
||||
val displayEndTime: Time?
|
||||
get() {
|
||||
if (type == TYPE_SHIFTED_SOURCE)
|
||||
@ -42,12 +32,14 @@ class LessonFull(profileId: Int, id: Long) : Lesson(profileId, id) {
|
||||
return oldSubjectName
|
||||
return subjectName ?: oldSubjectName
|
||||
}
|
||||
|
||||
val displayTeacherName: String?
|
||||
get() {
|
||||
if (type == TYPE_SHIFTED_SOURCE)
|
||||
return oldTeacherName
|
||||
return teacherName ?: oldTeacherName
|
||||
}
|
||||
|
||||
val displayTeamName: String?
|
||||
get() {
|
||||
if (type == TYPE_SHIFTED_SOURCE)
|
||||
@ -68,12 +60,14 @@ class LessonFull(profileId: Int, id: Long) : Lesson(profileId, id) {
|
||||
return oldTeamId
|
||||
return teamId ?: oldTeamId
|
||||
}
|
||||
|
||||
val displaySubjectId: Long?
|
||||
get() {
|
||||
if (type == TYPE_SHIFTED_SOURCE)
|
||||
return oldSubjectId
|
||||
return subjectId ?: oldSubjectId
|
||||
}
|
||||
|
||||
val displayTeacherId: Long?
|
||||
get() {
|
||||
if (type == TYPE_SHIFTED_SOURCE)
|
||||
@ -81,8 +75,35 @@ class LessonFull(profileId: Int, id: Long) : Lesson(profileId, id) {
|
||||
return teacherId ?: oldTeacherId
|
||||
}
|
||||
|
||||
fun getDisplayChangeType(context: Context): String {
|
||||
return context.getString(when (type) {
|
||||
TYPE_CHANGE -> R.string.lesson_change
|
||||
TYPE_CANCELLED -> R.string.lesson_cancelled
|
||||
TYPE_SHIFTED_TARGET, TYPE_SHIFTED_SOURCE -> R.string.lesson_shifted
|
||||
else -> R.string.lesson_timetable_change
|
||||
})
|
||||
}
|
||||
|
||||
val changeSubjectName: String
|
||||
get() {
|
||||
val first = when (type) {
|
||||
TYPE_CHANGE, TYPE_CANCELLED, TYPE_SHIFTED_SOURCE -> oldSubjectName
|
||||
else -> subjectName
|
||||
}
|
||||
|
||||
val second = when (type) {
|
||||
TYPE_CHANGE -> subjectName
|
||||
else -> null
|
||||
}
|
||||
|
||||
return when (second) {
|
||||
null -> first ?: ""
|
||||
else -> "$first -> $second"
|
||||
}
|
||||
}
|
||||
|
||||
// metadata
|
||||
var seen: Boolean = false
|
||||
var notified: Boolean = false
|
||||
var addedDate: Long = 0
|
||||
}
|
||||
}
|
||||
|
@ -10,6 +10,27 @@ import pl.szczodrzynski.edziennik.utils.models.Date
|
||||
|
||||
@Dao
|
||||
interface TimetableDao {
|
||||
companion object {
|
||||
private const val QUERY = """
|
||||
SELECT
|
||||
timetable.*,
|
||||
subjects.subjectLongName AS subjectName,
|
||||
teachers.teacherName ||" "|| teachers.teacherSurname AS teacherName,
|
||||
teams.teamName AS teamName,
|
||||
oldS.subjectLongName AS oldSubjectName,
|
||||
oldT.teacherName ||" "|| oldT.teacherSurname AS oldTeacherName,
|
||||
oldG.teamName AS oldTeamName,
|
||||
metadata.seen, metadata.notified, metadata.addedDate
|
||||
FROM timetable
|
||||
LEFT JOIN subjects USING(profileId, subjectId)
|
||||
LEFT JOIN teachers USING(profileId, teacherId)
|
||||
LEFT JOIN teams USING(profileId, teamId)
|
||||
LEFT JOIN subjects AS oldS ON timetable.profileId = oldS.profileId AND timetable.oldSubjectId = oldS.subjectId
|
||||
LEFT JOIN teachers AS oldT ON timetable.profileId = oldT.profileId AND timetable.oldTeacherId = oldT.teacherId
|
||||
LEFT JOIN teams AS oldG ON timetable.profileId = oldG.profileId AND timetable.oldTeamId = oldG.teamId
|
||||
LEFT JOIN metadata ON id = thingId AND thingType = ${Metadata.TYPE_LESSON_CHANGE} AND metadata.profileId = timetable.profileId
|
||||
"""
|
||||
}
|
||||
|
||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||
operator fun plusAssign(lessonList: List<Lesson>)
|
||||
@ -17,57 +38,60 @@ interface TimetableDao {
|
||||
@Query("DELETE FROM timetable WHERE profileId = :profileId")
|
||||
fun clear(profileId: Int)
|
||||
|
||||
@Query("DELETE FROM timetable WHERE profileId = :profileId AND (type != 3 AND date >= :dateFrom) OR ((type = 3 OR type = 1) AND oldDate >= :dateFrom)")
|
||||
@Query("DELETE FROM timetable WHERE profileId = :profileId AND ((type != 3 AND date >= :dateFrom) OR ((type = 3 OR type = 1) AND oldDate >= :dateFrom))")
|
||||
fun clearFromDate(profileId: Int, dateFrom: Date)
|
||||
@Query("DELETE FROM timetable WHERE profileId = :profileId AND (type != 3 AND date <= :dateTo) OR ((type = 3 OR type = 1) AND oldDate <= :dateTo)")
|
||||
@Query("DELETE FROM timetable WHERE profileId = :profileId AND ((type != 3 AND date <= :dateTo) OR ((type = 3 OR type = 1) AND oldDate <= :dateTo))")
|
||||
fun clearToDate(profileId: Int, dateTo: Date)
|
||||
@Query("DELETE FROM timetable WHERE profileId = :profileId AND (type != 3 AND date >= :dateFrom AND date <= :dateTo) OR ((type = 3 OR type = 1) AND oldDate >= :dateFrom AND oldDate <= :dateTo)")
|
||||
@Query("DELETE FROM timetable WHERE profileId = :profileId AND ((type != 3 AND date >= :dateFrom AND date <= :dateTo) OR ((type = 3 OR type = 1) AND oldDate >= :dateFrom AND oldDate <= :dateTo))")
|
||||
fun clearBetweenDates(profileId: Int, dateFrom: Date, dateTo: Date)
|
||||
|
||||
@Query("""
|
||||
SELECT
|
||||
timetable.*,
|
||||
subjects.subjectLongName AS subjectName,
|
||||
teachers.teacherName ||" "|| teachers.teacherSurname AS teacherName,
|
||||
teams.teamName AS teamName,
|
||||
oldS.subjectLongName AS oldSubjectName,
|
||||
oldT.teacherName ||" "|| oldT.teacherSurname AS oldTeacherName,
|
||||
oldG.teamName AS oldTeamName,
|
||||
metadata.seen, metadata.notified, metadata.addedDate
|
||||
FROM timetable
|
||||
LEFT JOIN subjects USING(profileId, subjectId)
|
||||
LEFT JOIN teachers USING(profileId, teacherId)
|
||||
LEFT JOIN teams USING(profileId, teamId)
|
||||
LEFT JOIN subjects AS oldS ON timetable.profileId = oldS.profileId AND timetable.oldSubjectId = oldS.subjectId
|
||||
LEFT JOIN teachers AS oldT ON timetable.profileId = oldT.profileId AND timetable.oldTeacherId = oldT.teacherId
|
||||
LEFT JOIN teams AS oldG ON timetable.profileId = oldG.profileId AND timetable.oldTeamId = oldG.teamId
|
||||
LEFT JOIN metadata ON id = thingId AND thingType = ${Metadata.TYPE_LESSON_CHANGE} AND metadata.profileId = timetable.profileId
|
||||
WHERE timetable.profileId = :profileId AND (type != 3 AND date = :date) OR ((type = 3 OR type = 1) AND oldDate = :date)
|
||||
$QUERY
|
||||
WHERE timetable.profileId = :profileId AND ((type != 3 AND date = :date) OR ((type = 3 OR type = 1) AND oldDate = :date))
|
||||
ORDER BY id, type
|
||||
""")
|
||||
fun getForDate(profileId: Int, date: Date) : LiveData<List<LessonFull>>
|
||||
|
||||
@Query("""
|
||||
SELECT
|
||||
timetable.*,
|
||||
subjects.subjectLongName AS subjectName,
|
||||
teachers.teacherName ||" "|| teachers.teacherSurname AS teacherName,
|
||||
teams.teamName AS teamName,
|
||||
oldS.subjectLongName AS oldSubjectName,
|
||||
oldT.teacherName ||" "|| oldT.teacherSurname AS oldTeacherName,
|
||||
oldG.teamName AS oldTeamName,
|
||||
metadata.seen, metadata.notified, metadata.addedDate
|
||||
FROM timetable
|
||||
LEFT JOIN subjects USING(profileId, subjectId)
|
||||
LEFT JOIN teachers USING(profileId, teacherId)
|
||||
LEFT JOIN teams USING(profileId, teamId)
|
||||
LEFT JOIN subjects AS oldS ON timetable.profileId = oldS.profileId AND timetable.oldSubjectId = oldS.subjectId
|
||||
LEFT JOIN teachers AS oldT ON timetable.profileId = oldT.profileId AND timetable.oldTeacherId = oldT.teacherId
|
||||
LEFT JOIN teams AS oldG ON timetable.profileId = oldG.profileId AND timetable.oldTeamId = oldG.teamId
|
||||
LEFT JOIN metadata ON id = thingId AND thingType = ${Metadata.TYPE_LESSON_CHANGE} AND metadata.profileId = timetable.profileId
|
||||
$QUERY
|
||||
WHERE timetable.profileId = :profileId AND ((type != 3 AND date > :today) OR ((type = 3 OR type = 1) AND oldDate > :today)) AND timetable.subjectId = :subjectId
|
||||
ORDER BY id, type
|
||||
LIMIT 1
|
||||
""")
|
||||
fun getNextWithSubject(profileId: Int, today: Date, subjectId: Long) : LiveData<LessonFull>
|
||||
fun getNextWithSubject(profileId: Int, today: Date, subjectId: Long) : LiveData<LessonFull?>
|
||||
|
||||
@Query("""
|
||||
$QUERY
|
||||
WHERE timetable.profileId = :profileId AND ((type != 3 AND date > :today) OR ((type = 3 OR type = 1) AND oldDate > :today)) AND timetable.subjectId = :subjectId AND timetable.teamId = :teamId
|
||||
ORDER BY id, type
|
||||
LIMIT 1
|
||||
""")
|
||||
fun getNextWithSubjectAndTeam(profileId: Int, today: Date, subjectId: Long, teamId: Long): LiveData<LessonFull?>
|
||||
|
||||
@Query("""
|
||||
$QUERY
|
||||
WHERE (type != 3 AND date >= :dateFrom AND date <= :dateTo) OR ((type = 3 OR type = 1) AND oldDate >= :dateFrom AND oldDate <= :dateTo)
|
||||
ORDER BY profileId, id, type
|
||||
""")
|
||||
fun getBetweenDatesNow(dateFrom: Date, dateTo: Date) : List<LessonFull>
|
||||
|
||||
@Query("""
|
||||
$QUERY
|
||||
WHERE (type != 3 AND date >= :dateFrom AND date <= :dateTo) OR ((type = 3 OR type = 1) AND oldDate >= :dateFrom AND oldDate <= :dateTo)
|
||||
ORDER BY profileId, id, type
|
||||
""")
|
||||
fun getBetweenDates(dateFrom: Date, dateTo: Date) : LiveData<List<LessonFull>>
|
||||
|
||||
@Query("""
|
||||
$QUERY
|
||||
WHERE timetable.profileId = :profileId AND timetable.id = :lessonId
|
||||
ORDER BY id, type
|
||||
""")
|
||||
fun getByIdNow(profileId: Int, lessonId: Long) : LessonFull?
|
||||
|
||||
@Query("""
|
||||
$QUERY
|
||||
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>
|
||||
}
|
||||
|
@ -125,19 +125,22 @@ public class BootReceiver extends BroadcastReceiver {
|
||||
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);
|
||||
updateFilename,
|
||||
updateDirect);
|
||||
}
|
||||
} else {
|
||||
if (app.appConfig.updateVersion == null || !app.appConfig.updateVersion.equals("")) {
|
||||
@ -259,7 +262,7 @@ public class BootReceiver extends BroadcastReceiver {
|
||||
|
||||
@Override
|
||||
protected void onHandleIntent(Intent intent) {
|
||||
if (UPDATES_ON_PLAY_STORE) {
|
||||
if (UPDATES_ON_PLAY_STORE && !intent.getBooleanExtra("update_direct", false)) {
|
||||
Utils.openGooglePlay(this, "pl.szczodrzynski.edziennik");
|
||||
return;
|
||||
}
|
||||
@ -270,7 +273,8 @@ public class BootReceiver extends BroadcastReceiver {
|
||||
app.notifier.notificationUpdatesShow(
|
||||
intent.getStringExtra("update_version"),
|
||||
intent.getStringExtra("update_url"),
|
||||
intent.getStringExtra("update_filename"));
|
||||
intent.getStringExtra("update_filename"),
|
||||
intent.getBooleanExtra("update_direct", false));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
@ -154,19 +154,22 @@ public class MyFirebaseMessagingService extends FirebaseMessagingService {
|
||||
String updateUrl = remoteMessage.getData().get("update_url");
|
||||
String updateFilename = remoteMessage.getData().get("update_filename");
|
||||
boolean updateMandatory = Boolean.parseBoolean(remoteMessage.getData().get("update_mandatory"));
|
||||
boolean updateDirect = Boolean.parseBoolean(remoteMessage.getData().get("update_direct"));
|
||||
|
||||
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("updateVersion", "updateUrl", "updateFilename", "updateMandatory");
|
||||
}
|
||||
if (!remoteMessage.getData().containsKey("update_silent")) {
|
||||
app.notifier.notificationUpdatesShow(
|
||||
updateVersion,
|
||||
updateUrl,
|
||||
updateFilename);
|
||||
updateFilename,
|
||||
updateDirect);
|
||||
}
|
||||
} else {
|
||||
if (app.appConfig.updateVersion == null || !app.appConfig.updateVersion.equals("")) {
|
||||
|
@ -36,7 +36,6 @@ import java.util.List;
|
||||
import pl.szczodrzynski.edziennik.App;
|
||||
import pl.szczodrzynski.edziennik.MainActivity;
|
||||
import pl.szczodrzynski.edziennik.R;
|
||||
import pl.szczodrzynski.edziennik.data.api.AppError;
|
||||
import pl.szczodrzynski.edziennik.data.db.modules.events.Event;
|
||||
import pl.szczodrzynski.edziennik.data.db.modules.events.EventFull;
|
||||
import pl.szczodrzynski.edziennik.data.db.modules.events.EventType;
|
||||
@ -54,7 +53,6 @@ import pl.szczodrzynski.edziennik.utils.models.Time;
|
||||
import pl.szczodrzynski.edziennik.utils.models.Week;
|
||||
|
||||
import static pl.szczodrzynski.edziennik.App.APP_URL;
|
||||
import static pl.szczodrzynski.edziennik.data.api.AppError.CODE_OTHER;
|
||||
import static pl.szczodrzynski.edziennik.data.db.modules.events.Event.COLOR_DEFAULT;
|
||||
import static pl.szczodrzynski.edziennik.data.db.modules.events.Event.TYPE_HOMEWORK;
|
||||
import static pl.szczodrzynski.edziennik.data.db.modules.events.Event.TYPE_UNDEFINED;
|
||||
@ -374,7 +372,8 @@ public class EventManualDialog {
|
||||
}
|
||||
} catch (Exception e) {
|
||||
activity.runOnUiThread(() -> {
|
||||
app.apiEdziennik.guiShowErrorDialog(activity, new AppError(TAG, 379, CODE_OTHER, null, e), R.string.error_occured);
|
||||
// TODO show error in EventManualDialog
|
||||
//app.apiEdziennik.guiShowErrorDialog(activity, new AppError(TAG, 379, CODE_OTHER, null, e), R.string.error_occured);
|
||||
});
|
||||
}
|
||||
});
|
||||
@ -518,7 +517,7 @@ public class EventManualDialog {
|
||||
|
||||
registerEventManualDateLayout = dialogView.findViewById(R.id.registerEventManualDateLayout);
|
||||
registerEventManualDate = dialogView.findViewById(R.id.registerEventManualDate);
|
||||
registerEventManualDate.setCompoundDrawablesWithIntrinsicBounds(null, null, new IconicsDrawable(context, CommunityMaterial.Icon.cmd_calendar).size(IconicsSize.dp(16)).color(IconicsColor.colorInt(primaryTextColor)), null);
|
||||
registerEventManualDate.setCompoundDrawablesWithIntrinsicBounds(null, null, new IconicsDrawable(context, CommunityMaterial.Icon.cmd_calendar_outline).size(IconicsSize.dp(16)).color(IconicsColor.colorInt(primaryTextColor)), null);
|
||||
//registerEventManualDate.setCompoundDrawablePadding(Utils.dpToPx(6));
|
||||
registerEventManualLessonLayout = dialogView.findViewById(R.id.registerEventManualLessonLayout);
|
||||
registerEventManualLesson = dialogView.findViewById(R.id.registerEventManualLesson);
|
||||
|
@ -4,19 +4,26 @@
|
||||
|
||||
package pl.szczodrzynski.edziennik.ui.dialogs.event
|
||||
|
||||
import android.graphics.PorterDuff
|
||||
import android.graphics.PorterDuffColorFilter
|
||||
import android.view.View
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.lifecycle.Observer
|
||||
import com.google.android.material.datepicker.MaterialDatePicker
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import com.jaredrummler.android.colorpicker.ColorPickerDialog
|
||||
import com.jaredrummler.android.colorpicker.ColorPickerDialogListener
|
||||
import kotlinx.coroutines.*
|
||||
import pl.szczodrzynski.edziennik.*
|
||||
import pl.szczodrzynski.edziennik.data.db.modules.events.Event
|
||||
import pl.szczodrzynski.edziennik.data.db.modules.events.EventType
|
||||
import pl.szczodrzynski.edziennik.data.db.modules.subjects.Subject
|
||||
import pl.szczodrzynski.edziennik.data.db.modules.teams.Team
|
||||
import pl.szczodrzynski.edziennik.data.db.modules.timetable.Lesson
|
||||
import pl.szczodrzynski.edziennik.data.db.modules.timetable.LessonFull
|
||||
import pl.szczodrzynski.edziennik.databinding.DialogEventManualV2Binding
|
||||
import pl.szczodrzynski.edziennik.utils.Anim
|
||||
import pl.szczodrzynski.edziennik.utils.TextInputDropDown
|
||||
import pl.szczodrzynski.edziennik.utils.models.Date
|
||||
import pl.szczodrzynski.edziennik.utils.models.Time
|
||||
@ -30,7 +37,9 @@ class EventManualV2Dialog(
|
||||
val defaultDate: Date? = null,
|
||||
val defaultTime: Time? = null,
|
||||
val defaultType: Int? = null,
|
||||
val editingEvent: Event? = null
|
||||
val editingEvent: Event? = null,
|
||||
val onShowListener: ((tag: String) -> Unit)? = null,
|
||||
val onDismissListener: ((tag: String) -> Unit)? = null
|
||||
) : CoroutineScope {
|
||||
|
||||
companion object {
|
||||
@ -44,18 +53,25 @@ class EventManualV2Dialog(
|
||||
private val app by lazy { activity.application as App }
|
||||
private lateinit var b: DialogEventManualV2Binding
|
||||
private lateinit var dialog: AlertDialog
|
||||
private lateinit var event: Event
|
||||
private var defaultLoaded = false
|
||||
|
||||
init { run {
|
||||
job = Job()
|
||||
private lateinit var event: Event
|
||||
private var customColor: Int? = null
|
||||
|
||||
init { run {
|
||||
if (activity.isFinishing)
|
||||
return@run
|
||||
job = Job()
|
||||
onShowListener?.invoke(TAG)
|
||||
b = DialogEventManualV2Binding.inflate(activity.layoutInflater)
|
||||
dialog = MaterialAlertDialogBuilder(activity)
|
||||
.setTitle(R.string.dialog_event_manual_title)
|
||||
.setView(b.root)
|
||||
.setNegativeButton(R.string.cancel) { dialog, _ -> dialog.dismiss() }
|
||||
.setPositiveButton(R.string.save) { _, _ -> saveEvent() }
|
||||
.setOnDismissListener {
|
||||
onDismissListener?.invoke(TAG)
|
||||
}
|
||||
.show()
|
||||
|
||||
event = editingEvent?.clone() ?: Event().also { event ->
|
||||
@ -71,11 +87,46 @@ class EventManualV2Dialog(
|
||||
defaultType?.let {
|
||||
event.type = it
|
||||
}*/
|
||||
b.shareSwitch.isChecked = event.sharedBy != null
|
||||
}
|
||||
|
||||
b.showMore.onClick { // TODO iconics is broken
|
||||
it.apply {
|
||||
refreshDrawableState()
|
||||
|
||||
if (isChecked)
|
||||
Anim.expand(b.moreLayout, 200, null)
|
||||
else
|
||||
Anim.collapse(b.moreLayout, 200, null)
|
||||
}
|
||||
}
|
||||
|
||||
updateShareText()
|
||||
b.shareSwitch.onChange { _, isChecked ->
|
||||
updateShareText(isChecked)
|
||||
}
|
||||
|
||||
loadLists()
|
||||
}}
|
||||
|
||||
private fun updateShareText(checked: Boolean = b.shareSwitch.isChecked) {
|
||||
val editingShared = editingEvent?.sharedBy != null
|
||||
val editingOwn = editingEvent?.sharedBy == "self"
|
||||
|
||||
b.shareDetails.visibility = if (checked || editingShared)
|
||||
View.VISIBLE
|
||||
else View.GONE
|
||||
|
||||
val text = when {
|
||||
checked && editingShared && editingOwn -> R.string.dialog_event_manual_share_will_change
|
||||
checked && editingShared -> R.string.dialog_event_manual_share_will_request
|
||||
!checked && editingShared -> R.string.dialog_event_manual_share_will_remove
|
||||
else -> R.string.dialog_event_manual_share_first_notice
|
||||
}
|
||||
|
||||
b.shareDetails.setText(text)
|
||||
}
|
||||
|
||||
private fun loadLists() { launch {
|
||||
val deferred = async(Dispatchers.Default) {
|
||||
// get the team list
|
||||
@ -107,18 +158,33 @@ class EventManualV2Dialog(
|
||||
""
|
||||
)
|
||||
b.teacherDropdown += teachers.map { TextInputDropDown.Item(it.id, it.fullName, tag = it) }
|
||||
|
||||
// get the event type list
|
||||
val eventTypes = app.db.eventTypeDao().getAllNow(profileId)
|
||||
b.typeDropdown.clear()
|
||||
b.typeDropdown += eventTypes.map { TextInputDropDown.Item(it.id, it.name, tag = it) }
|
||||
}
|
||||
deferred.await()
|
||||
|
||||
b.teamDropdown.isEnabled = true
|
||||
b.subjectDropdown.isEnabled = true
|
||||
b.teacherDropdown.isEnabled = true
|
||||
b.typeDropdown.isEnabled = true
|
||||
|
||||
b.typeDropdown.selected?.let { item ->
|
||||
customColor = (item.tag as EventType).color
|
||||
}
|
||||
|
||||
// copy IDs from event being edited
|
||||
editingEvent?.let {
|
||||
b.teamDropdown.select(it.teamId)
|
||||
b.subjectDropdown.select(it.subjectId)
|
||||
b.teacherDropdown.select(it.teacherId)
|
||||
b.typeDropdown.select(it.type)?.let { item ->
|
||||
customColor = (item.tag as EventType).color
|
||||
}
|
||||
if (it.color != -1)
|
||||
customColor = it.color
|
||||
}
|
||||
|
||||
// copy IDs from the LessonFull
|
||||
@ -128,6 +194,30 @@ class EventManualV2Dialog(
|
||||
b.teacherDropdown.select(it.displayTeacherId)
|
||||
}
|
||||
|
||||
b.typeDropdown.setOnChangeListener {
|
||||
b.typeColor.background.colorFilter = PorterDuffColorFilter((it.tag as EventType).color, PorterDuff.Mode.SRC_ATOP)
|
||||
customColor = null
|
||||
return@setOnChangeListener true
|
||||
}
|
||||
(customColor ?: Event.COLOR_DEFAULT).let {
|
||||
b.typeColor.background.colorFilter = PorterDuffColorFilter(it, PorterDuff.Mode.SRC_ATOP)
|
||||
}
|
||||
b.typeColor.onClick {
|
||||
val currentColor = (b.typeDropdown?.selected?.tag as EventType?)?.color ?: Event.COLOR_DEFAULT
|
||||
val colorPickerDialog = ColorPickerDialog.newBuilder()
|
||||
.setColor(currentColor)
|
||||
.create()
|
||||
colorPickerDialog.setColorPickerDialogListener(
|
||||
object : ColorPickerDialogListener {
|
||||
override fun onDialogDismissed(dialogId: Int) {}
|
||||
override fun onColorSelected(dialogId: Int, color: Int) {
|
||||
b.typeColor.background.colorFilter = PorterDuffColorFilter(color, PorterDuff.Mode.SRC_ATOP)
|
||||
customColor = color
|
||||
}
|
||||
})
|
||||
colorPickerDialog.show(activity.fragmentManager, "color-picker-dialog")
|
||||
}
|
||||
|
||||
loadDates()
|
||||
}}
|
||||
|
||||
@ -198,12 +288,20 @@ class EventManualV2Dialog(
|
||||
val dates = deferred.await()
|
||||
b.dateDropdown.clear().append(dates)
|
||||
|
||||
editingEvent?.let {
|
||||
b.dateDropdown.select(it.eventDate.value.toLong())
|
||||
editingEvent?.eventDate?.let {
|
||||
b.dateDropdown.select(TextInputDropDown.Item(
|
||||
it.value.toLong(),
|
||||
it.formattedString,
|
||||
tag = it
|
||||
))
|
||||
}
|
||||
|
||||
defaultLesson?.let {
|
||||
b.dateDropdown.select(it.displayDate?.value?.toLong())
|
||||
defaultLesson?.displayDate?.let {
|
||||
b.dateDropdown.select(TextInputDropDown.Item(
|
||||
it.value.toLong(),
|
||||
it.formattedString,
|
||||
tag = it
|
||||
))
|
||||
}
|
||||
|
||||
if (b.dateDropdown.selected == null) {
|
||||
@ -216,22 +314,24 @@ class EventManualV2Dialog(
|
||||
when {
|
||||
// next lesson with specified subject
|
||||
item.id < -1 -> {
|
||||
app.db.timetableDao().getNextWithSubject(profileId, Date.getToday(), -item.id).observeOnce(activity, Observer {
|
||||
val teamId = defaultLesson?.teamId ?: -1
|
||||
val selectedLessonDate = defaultLesson?.date ?: Date.getToday()
|
||||
|
||||
when (teamId) {
|
||||
-1L -> app.db.timetableDao().getNextWithSubject(profileId, selectedLessonDate, -item.id)
|
||||
else -> app.db.timetableDao().getNextWithSubjectAndTeam(profileId, selectedLessonDate, -item.id, teamId)
|
||||
}.observeOnce(activity, Observer {
|
||||
val lessonDate = it?.displayDate ?: return@Observer
|
||||
b.dateDropdown.selected = TextInputDropDown.Item(
|
||||
b.dateDropdown.select(TextInputDropDown.Item(
|
||||
lessonDate.value.toLong(),
|
||||
lessonDate.formattedString,
|
||||
tag = lessonDate
|
||||
)
|
||||
// TODO load correct hour when selecting next lesson
|
||||
b.dateDropdown.updateText()
|
||||
it.let {
|
||||
b.teamDropdown.select(it.displayTeamId)
|
||||
b.subjectDropdown.select(it.displaySubjectId)
|
||||
b.teacherDropdown.select(it.displayTeacherId)
|
||||
}
|
||||
))
|
||||
b.teamDropdown.select(it.displayTeamId)
|
||||
b.subjectDropdown.select(it.displaySubjectId)
|
||||
b.teacherDropdown.select(it.displayTeacherId)
|
||||
defaultLoaded = false
|
||||
loadHours()
|
||||
loadHours(it.displayStartTime)
|
||||
})
|
||||
return@setOnChangeListener false
|
||||
}
|
||||
@ -239,17 +339,17 @@ class EventManualV2Dialog(
|
||||
item.id == -1L -> {
|
||||
MaterialDatePicker.Builder
|
||||
.datePicker()
|
||||
.setSelection((b.dateDropdown.selectedId?.let { Date.fromValue(it.toInt()) } ?: Date.getToday()).inMillis)
|
||||
.setSelection((b.dateDropdown.selectedId?.let { Date.fromValue(it.toInt()) }
|
||||
?: Date.getToday()).inMillis)
|
||||
.build()
|
||||
.apply {
|
||||
addOnPositiveButtonClickListener {
|
||||
val dateSelected = Date.fromMillis(it)
|
||||
b.dateDropdown.selected = TextInputDropDown.Item(
|
||||
b.dateDropdown.select(TextInputDropDown.Item(
|
||||
dateSelected.value.toLong(),
|
||||
dateSelected.formattedString,
|
||||
tag = dateSelected
|
||||
)
|
||||
b.dateDropdown.updateText()
|
||||
))
|
||||
loadHours()
|
||||
}
|
||||
show(this@EventManualV2Dialog.activity.supportFragmentManager, "MaterialDatePicker")
|
||||
@ -269,7 +369,7 @@ class EventManualV2Dialog(
|
||||
loadHours()
|
||||
}}
|
||||
|
||||
private fun loadHours() {
|
||||
private fun loadHours(defaultHour: Time? = null) {
|
||||
b.timeDropdown.isEnabled = false
|
||||
// get the selected date
|
||||
val date = b.dateDropdown.selectedId?.let { Date.fromValue(it.toInt()) } ?: return
|
||||
@ -295,7 +395,8 @@ class EventManualV2Dialog(
|
||||
lesson.displayStartTime?.stringHM ?: "",
|
||||
lesson.displaySubjectName?.let {
|
||||
when {
|
||||
lesson.type == Lesson.TYPE_CANCELLED -> it.asStrikethroughSpannable()
|
||||
lesson.type == Lesson.TYPE_CANCELLED
|
||||
|| lesson.type == Lesson.TYPE_SHIFTED_SOURCE -> it.asStrikethroughSpannable()
|
||||
lesson.type != Lesson.TYPE_NORMAL -> it.asItalicSpannable()
|
||||
else -> it
|
||||
}
|
||||
@ -331,6 +432,10 @@ class EventManualV2Dialog(
|
||||
defaultLesson?.let {
|
||||
b.timeDropdown.select(it.displayStartTime?.value?.toLong())
|
||||
}
|
||||
|
||||
defaultHour?.let {
|
||||
b.timeDropdown.select(it.value.toLong())
|
||||
}
|
||||
}
|
||||
defaultLoaded = true
|
||||
b.timeDropdown.isEnabled = true
|
||||
@ -338,16 +443,16 @@ class EventManualV2Dialog(
|
||||
// attach a listener to time dropdown
|
||||
b.timeDropdown.setOnChangeListener { item ->
|
||||
when {
|
||||
// custom start hour
|
||||
item.id == -1L -> {
|
||||
|
||||
return@setOnChangeListener false
|
||||
}
|
||||
// no lessons this day
|
||||
item.id == -2L -> {
|
||||
b.timeDropdown.deselect()
|
||||
return@setOnChangeListener false
|
||||
}
|
||||
// custom start hour
|
||||
item.id == -1L -> {
|
||||
|
||||
return@setOnChangeListener false
|
||||
}
|
||||
// selected a specific lesson
|
||||
else -> {
|
||||
if (item.tag is LessonFull) {
|
||||
@ -376,4 +481,4 @@ class EventManualV2Dialog(
|
||||
private fun saveEvent() {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,46 @@
|
||||
/*
|
||||
* Copyright (c) Kuba Szczodrzyński 2019-11-24.
|
||||
*/
|
||||
|
||||
package pl.szczodrzynski.edziennik.ui.dialogs.home
|
||||
|
||||
import android.text.InputType
|
||||
import android.widget.Toast
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import com.afollestad.materialdialogs.MaterialDialog
|
||||
import pl.szczodrzynski.edziennik.R
|
||||
import pl.szczodrzynski.edziennik.data.db.modules.profiles.Profile
|
||||
|
||||
class StudentNumberDialog(
|
||||
val activity: AppCompatActivity,
|
||||
val profile: Profile,
|
||||
val onShowListener: ((tag: String) -> Unit)? = null,
|
||||
val onDismissListener: ((tag: String) -> Unit)? = null
|
||||
) {
|
||||
companion object {
|
||||
private const val TAG = "StudentNumberDialog"
|
||||
}
|
||||
|
||||
private lateinit var dialog: AlertDialog
|
||||
|
||||
init { run {
|
||||
if (activity.isFinishing)
|
||||
return@run
|
||||
onShowListener?.invoke(TAG)
|
||||
MaterialDialog.Builder(activity)
|
||||
.title(R.string.card_lucky_number_set_title)
|
||||
.content(R.string.card_lucky_number_set_text)
|
||||
.inputType(InputType.TYPE_CLASS_NUMBER)
|
||||
.input(null, if (profile.studentNumber == -1) "" else profile.studentNumber.toString()) { _: MaterialDialog?, input: CharSequence ->
|
||||
try {
|
||||
profile.studentNumber = input.toString().toInt()
|
||||
} catch (e: Exception) {
|
||||
Toast.makeText(activity, R.string.incorrect_format, Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
||||
.dismissListener {
|
||||
onDismissListener?.invoke(TAG)
|
||||
}.show()
|
||||
}}
|
||||
}
|
@ -0,0 +1,103 @@
|
||||
/*
|
||||
* Copyright (c) Kuba Szczodrzyński 2019-11-13.
|
||||
*/
|
||||
|
||||
package pl.szczodrzynski.edziennik.ui.dialogs.settings
|
||||
|
||||
import android.widget.Toast
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import kotlinx.coroutines.*
|
||||
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(
|
||||
val activity: MainActivity,
|
||||
val profileId: Int,
|
||||
val profileName: String
|
||||
) : CoroutineScope {
|
||||
companion object {
|
||||
private const val TAG = "ProfileRemoveDialog"
|
||||
}
|
||||
|
||||
private lateinit var job: Job
|
||||
override val coroutineContext: CoroutineContext
|
||||
get() = job + Dispatchers.Main
|
||||
|
||||
private val app by lazy { activity.application as App }
|
||||
private lateinit var b: DialogLessonDetailsBinding
|
||||
private lateinit var dialog: AlertDialog
|
||||
|
||||
init { run {
|
||||
job = Job()
|
||||
|
||||
dialog = MaterialAlertDialogBuilder(activity)
|
||||
.setTitle(R.string.profile_menu_remove_confirm)
|
||||
.setMessage(activity.getString(R.string.profile_menu_remove_confirm_text_format, profileName, profileName))
|
||||
.setPositiveButton(R.string.remove) { _, _ ->
|
||||
removeProfile()
|
||||
}
|
||||
.setNeutralButton(R.string.cancel) { dialog, _ -> dialog.dismiss() }
|
||||
.setCancelable(false)
|
||||
.show()
|
||||
}}
|
||||
|
||||
private fun removeProfile() { launch {
|
||||
val deferred = async(Dispatchers.Default) {
|
||||
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.lessonDao().clear(profileId)
|
||||
app.db.lessonChangeDao().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.lessonRangeDao().clear(profileId)
|
||||
app.db.noticeTypeDao().clear(profileId)
|
||||
app.db.teacherAbsenceDao().clear(profileId)
|
||||
app.db.teacherAbsenceTypeDao().clear(profileId)
|
||||
app.db.timetableDao().clear(profileId)
|
||||
|
||||
val loginStoreId = profileObject.loginStoreId
|
||||
val profilesUsingLoginStore = app.db.profileDao().getIdsByLoginStoreIdNow(loginStoreId)
|
||||
if (profilesUsingLoginStore.size == 1) {
|
||||
app.db.loginStoreDao().remove(loginStoreId)
|
||||
}
|
||||
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)
|
||||
}
|
||||
}
|
||||
app.appConfig.notifications.removeAll(toRemove)
|
||||
|
||||
app.profile = null
|
||||
App.profileId = -1
|
||||
|
||||
app.profileLoadById(app.profileLastId())
|
||||
}
|
||||
deferred.await()
|
||||
dialog.dismiss()
|
||||
activity.reloadTarget()
|
||||
Toast.makeText(activity, R.string.dialog_profile_remove_success, Toast.LENGTH_LONG).show()
|
||||
}}
|
||||
}
|
@ -0,0 +1,116 @@
|
||||
/*
|
||||
* Copyright (c) Kuba Szczodrzyński 2019-11-13.
|
||||
*/
|
||||
|
||||
package pl.szczodrzynski.edziennik.ui.dialogs.sync
|
||||
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import pl.szczodrzynski.edziennik.App
|
||||
import pl.szczodrzynski.edziennik.MainActivity
|
||||
import pl.szczodrzynski.edziennik.R
|
||||
import pl.szczodrzynski.edziennik.api.v2.events.task.EdziennikTask
|
||||
import pl.szczodrzynski.edziennik.databinding.DialogLessonDetailsBinding
|
||||
import pl.szczodrzynski.edziennik.ui.modules.messages.MessagesFragment
|
||||
import kotlin.coroutines.CoroutineContext
|
||||
|
||||
class SyncViewListDialog(
|
||||
val activity: MainActivity,
|
||||
val currentViewId: Int? = null
|
||||
) : CoroutineScope {
|
||||
companion object {
|
||||
private const val TAG = "SyncViewListDialog"
|
||||
}
|
||||
|
||||
private lateinit var job: Job
|
||||
override val coroutineContext: CoroutineContext
|
||||
get() = job + Dispatchers.Main
|
||||
|
||||
private val app by lazy { activity.application as App }
|
||||
private lateinit var b: DialogLessonDetailsBinding
|
||||
private lateinit var dialog: AlertDialog
|
||||
|
||||
init { run {
|
||||
job = Job()
|
||||
|
||||
val viewIds = arrayOf(
|
||||
MainActivity.DRAWER_ITEM_TIMETABLE,
|
||||
MainActivity.DRAWER_ITEM_AGENDA,
|
||||
MainActivity.DRAWER_ITEM_GRADES,
|
||||
MainActivity.DRAWER_ITEM_HOMEWORK,
|
||||
MainActivity.DRAWER_ITEM_BEHAVIOUR,
|
||||
MainActivity.DRAWER_ITEM_ATTENDANCE,
|
||||
MainActivity.DRAWER_ITEM_MESSAGES,
|
||||
MainActivity.DRAWER_ITEM_MESSAGES,
|
||||
MainActivity.DRAWER_ITEM_ANNOUNCEMENTS
|
||||
)
|
||||
|
||||
val items = arrayOf<String>(
|
||||
app.getString(R.string.menu_timetable),
|
||||
app.getString(R.string.menu_agenda),
|
||||
app.getString(R.string.menu_grades),
|
||||
app.getString(R.string.menu_homework),
|
||||
app.getString(R.string.menu_notices),
|
||||
app.getString(R.string.menu_attendance),
|
||||
app.getString(R.string.title_messages_inbox_single),
|
||||
app.getString(R.string.title_messages_sent_single),
|
||||
app.getString(R.string.menu_announcements)
|
||||
)
|
||||
|
||||
val everything = currentViewId == MainActivity.DRAWER_ITEM_HOME
|
||||
val checkedItems = booleanArrayOf(
|
||||
everything || currentViewId == MainActivity.DRAWER_ITEM_TIMETABLE,
|
||||
everything || currentViewId == MainActivity.DRAWER_ITEM_AGENDA,
|
||||
everything || currentViewId == MainActivity.DRAWER_ITEM_GRADES,
|
||||
everything || currentViewId == MainActivity.DRAWER_ITEM_HOMEWORK,
|
||||
everything || currentViewId == MainActivity.DRAWER_ITEM_BEHAVIOUR,
|
||||
everything || currentViewId == MainActivity.DRAWER_ITEM_ATTENDANCE,
|
||||
everything || currentViewId == MainActivity.DRAWER_ITEM_MESSAGES && MessagesFragment.pageSelection != 1,
|
||||
everything || currentViewId == MainActivity.DRAWER_ITEM_MESSAGES && MessagesFragment.pageSelection == 1,
|
||||
everything || currentViewId == MainActivity.DRAWER_ITEM_ANNOUNCEMENTS
|
||||
)
|
||||
val userChooses = checkedItems.toMutableList()
|
||||
|
||||
dialog = MaterialAlertDialogBuilder(activity)
|
||||
.setTitle(R.string.dialog_sync_view_list_title)
|
||||
.setMultiChoiceItems(items, checkedItems) { _, which, isChecked ->
|
||||
userChooses[which] = isChecked
|
||||
}
|
||||
.setPositiveButton(R.string.ok) { _, _ ->
|
||||
dialog.dismiss()
|
||||
|
||||
val selectedViewIds = userChooses.mapIndexed { index, it ->
|
||||
if (it)
|
||||
viewIds[index] to when (index) {
|
||||
7 -> 1
|
||||
else -> 0
|
||||
}
|
||||
else
|
||||
null
|
||||
}.let {
|
||||
listOfNotNull(*it.toTypedArray())
|
||||
}
|
||||
|
||||
if (selectedViewIds.isNotEmpty()) {
|
||||
activity.swipeRefreshLayout.isRefreshing = true
|
||||
EdziennikTask.syncProfile(
|
||||
App.profileId,
|
||||
selectedViewIds
|
||||
).enqueue(activity)
|
||||
}
|
||||
}
|
||||
.setNeutralButton(R.string.sync_feature_all) { _, _ ->
|
||||
dialog.dismiss()
|
||||
|
||||
activity.swipeRefreshLayout.isRefreshing = true
|
||||
EdziennikTask.syncProfile(App.profileId).enqueue(activity)
|
||||
}
|
||||
.setNegativeButton(R.string.cancel) { _, _ ->
|
||||
dialog.dismiss()
|
||||
}
|
||||
.show()
|
||||
}}
|
||||
}
|
@ -21,7 +21,9 @@ import pl.szczodrzynski.edziennik.utils.models.Week
|
||||
|
||||
class LessonDetailsDialog(
|
||||
val activity: AppCompatActivity,
|
||||
val lesson: LessonFull
|
||||
val lesson: LessonFull,
|
||||
val onShowListener: ((tag: String) -> Unit)? = null,
|
||||
val onDismissListener: ((tag: String) -> Unit)? = null
|
||||
) {
|
||||
companion object {
|
||||
private const val TAG = "LessonDetailsDialog"
|
||||
@ -31,6 +33,9 @@ class LessonDetailsDialog(
|
||||
private lateinit var dialog: AlertDialog
|
||||
|
||||
init { run {
|
||||
if (activity.isFinishing)
|
||||
return@run
|
||||
onShowListener?.invoke(TAG)
|
||||
b = DialogLessonDetailsBinding.inflate(activity.layoutInflater)
|
||||
dialog = MaterialAlertDialogBuilder(activity)
|
||||
.setView(b.root)
|
||||
@ -38,8 +43,13 @@ class LessonDetailsDialog(
|
||||
dialog.dismiss()
|
||||
}
|
||||
.setNeutralButton(R.string.add) { dialog, _ ->
|
||||
dialog.dismiss()
|
||||
EventManualV2Dialog(activity, lesson.profileId, lesson)
|
||||
EventManualV2Dialog(
|
||||
activity,
|
||||
lesson.profileId,
|
||||
lesson,
|
||||
onShowListener = onShowListener,
|
||||
onDismissListener = onDismissListener
|
||||
)
|
||||
/*MaterialAlertDialogBuilder(activity)
|
||||
.setItems(R.array.main_menu_add_options) { dialog2, which ->
|
||||
dialog2.dismiss()
|
||||
@ -59,6 +69,9 @@ class LessonDetailsDialog(
|
||||
.setNegativeButton(R.string.cancel) { dialog2, _ -> dialog2.dismiss() }
|
||||
.show()*/
|
||||
}
|
||||
.setOnDismissListener {
|
||||
onDismissListener?.invoke(TAG)
|
||||
}
|
||||
.show()
|
||||
update()
|
||||
}}
|
||||
@ -107,7 +120,7 @@ class LessonDetailsDialog(
|
||||
dialog.dismiss()
|
||||
val dateStr = otherLessonDate?.stringY_m_d ?: return@setOnClickListener
|
||||
val intent = Intent(TimetableFragment.ACTION_SCROLL_TO_DATE).apply {
|
||||
putExtra("date", dateStr)
|
||||
putExtra("timetableDate", dateStr)
|
||||
}
|
||||
activity.sendBroadcast(intent)
|
||||
}
|
||||
@ -119,29 +132,29 @@ class LessonDetailsDialog(
|
||||
if (lesson.type < Lesson.TYPE_SHIFTED_SOURCE && lesson.oldSubjectId != null && lesson.subjectId != lesson.oldSubjectId) {
|
||||
b.oldSubjectName = lesson.oldSubjectName
|
||||
}
|
||||
if (lesson.type != Lesson.TYPE_CANCELLED && lesson.subjectId != null) {
|
||||
if (lesson.type != Lesson.TYPE_CANCELLED && lesson.displaySubjectId != null) {
|
||||
b.subjectName = lesson.subjectName
|
||||
}
|
||||
|
||||
if (lesson.type < Lesson.TYPE_SHIFTED_SOURCE && lesson.oldTeacherId != null && lesson.teacherId != lesson.oldTeacherId) {
|
||||
b.oldTeacherName = lesson.oldTeacherName
|
||||
}
|
||||
if (lesson.type != Lesson.TYPE_CANCELLED && lesson.teacherId != null) {
|
||||
if (lesson.type != Lesson.TYPE_CANCELLED && lesson.displayTeacherId != null) {
|
||||
b.teacherName = lesson.teacherName
|
||||
}
|
||||
|
||||
if (lesson.oldClassroom != null && lesson.classroom != lesson.oldClassroom) {
|
||||
b.oldClassroom = lesson.oldClassroom
|
||||
}
|
||||
if (lesson.type != Lesson.TYPE_CANCELLED && lesson.classroom != null) {
|
||||
if (lesson.type != Lesson.TYPE_CANCELLED && lesson.displayClassroom != null) {
|
||||
b.classroom = lesson.classroom
|
||||
}
|
||||
|
||||
if (lesson.type < Lesson.TYPE_SHIFTED_SOURCE && lesson.oldTeamId != null && lesson.teamId != lesson.oldTeamId) {
|
||||
b.oldTeamName = lesson.oldTeamName
|
||||
}
|
||||
if (lesson.type != Lesson.TYPE_CANCELLED && lesson.teamId != null) {
|
||||
if (lesson.type != Lesson.TYPE_CANCELLED && lesson.displayTeamId != null) {
|
||||
b.teamName = lesson.teamName
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -6,15 +6,16 @@ import android.graphics.drawable.Drawable;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.databinding.DataBindingUtil;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.databinding.DataBindingUtil;
|
||||
import androidx.fragment.app.Fragment;
|
||||
|
||||
import com.afollestad.materialdialogs.MaterialDialog;
|
||||
import com.applandeo.materialcalendarview.CalendarView;
|
||||
import com.applandeo.materialcalendarview.EventDay;
|
||||
@ -27,6 +28,7 @@ import com.mikepenz.iconics.IconicsColor;
|
||||
import com.mikepenz.iconics.IconicsDrawable;
|
||||
import com.mikepenz.iconics.IconicsSize;
|
||||
import com.mikepenz.iconics.typeface.library.community.material.CommunityMaterial;
|
||||
import com.mikepenz.iconics.typeface.library.szkolny.font.SzkolnyFont;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Calendar;
|
||||
@ -34,13 +36,13 @@ import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
import pl.szczodrzynski.edziennik.App;
|
||||
import pl.szczodrzynski.edziennik.R;
|
||||
import pl.szczodrzynski.edziennik.MainActivity;
|
||||
import pl.szczodrzynski.edziennik.R;
|
||||
import pl.szczodrzynski.edziennik.data.db.modules.events.EventFull;
|
||||
import pl.szczodrzynski.edziennik.data.db.modules.lessons.LessonFull;
|
||||
import pl.szczodrzynski.edziennik.data.db.modules.teachers.TeacherAbsenceFull;
|
||||
import pl.szczodrzynski.edziennik.databinding.FragmentAgendaCalendarBinding;
|
||||
import pl.szczodrzynski.edziennik.databinding.FragmentAgendaDefaultBinding;
|
||||
import pl.szczodrzynski.edziennik.data.db.modules.events.EventFull;
|
||||
import pl.szczodrzynski.edziennik.data.db.modules.lessons.LessonFull;
|
||||
import pl.szczodrzynski.edziennik.ui.dialogs.event.EventListDialog;
|
||||
import pl.szczodrzynski.edziennik.ui.dialogs.event.EventManualDialog;
|
||||
import pl.szczodrzynski.edziennik.ui.dialogs.lessonchange.LessonChangeDialog;
|
||||
@ -51,11 +53,11 @@ import pl.szczodrzynski.edziennik.ui.modules.agenda.lessonchange.LessonChangeEve
|
||||
import pl.szczodrzynski.edziennik.ui.modules.agenda.teacherabsence.TeacherAbsenceCounter;
|
||||
import pl.szczodrzynski.edziennik.ui.modules.agenda.teacherabsence.TeacherAbsenceEvent;
|
||||
import pl.szczodrzynski.edziennik.ui.modules.agenda.teacherabsence.TeacherAbsenceEventRenderer;
|
||||
import pl.szczodrzynski.edziennik.utils.models.Date;
|
||||
import pl.szczodrzynski.edziennik.utils.models.Time;
|
||||
import pl.szczodrzynski.edziennik.utils.Colors;
|
||||
import pl.szczodrzynski.edziennik.utils.Themes;
|
||||
import pl.szczodrzynski.edziennik.utils.Utils;
|
||||
import pl.szczodrzynski.edziennik.utils.models.Date;
|
||||
import pl.szczodrzynski.edziennik.utils.models.Time;
|
||||
import pl.szczodrzynski.navlib.bottomsheet.items.BottomSheetPrimaryItem;
|
||||
import pl.szczodrzynski.navlib.bottomsheet.items.BottomSheetSeparatorItem;
|
||||
|
||||
@ -102,7 +104,7 @@ public class AgendaFragment extends Fragment {
|
||||
new BottomSheetPrimaryItem(true)
|
||||
.withTitle(R.string.menu_add_event)
|
||||
.withDescription(R.string.menu_add_event_desc)
|
||||
.withIcon(CommunityMaterial.Icon.cmd_calendar_plus)
|
||||
.withIcon(SzkolnyFont.Icon.szf_calendar_plus_outline)
|
||||
.withOnClickListener(v3 -> {
|
||||
activity.getBottomSheet().close();
|
||||
new MaterialDialog.Builder(activity)
|
||||
@ -122,7 +124,7 @@ public class AgendaFragment extends Fragment {
|
||||
}),
|
||||
new BottomSheetPrimaryItem(true)
|
||||
.withTitle(R.string.menu_agenda_change_view)
|
||||
.withIcon(viewType == AGENDA_DEFAULT ? CommunityMaterial.Icon.cmd_calendar : CommunityMaterial.Icon2.cmd_view_list)
|
||||
.withIcon(viewType == AGENDA_DEFAULT ? CommunityMaterial.Icon.cmd_calendar_outline : CommunityMaterial.Icon.cmd_format_list_bulleted_square)
|
||||
.withOnClickListener(v3 -> {
|
||||
activity.getBottomSheet().close();
|
||||
viewType = viewType == AGENDA_DEFAULT ? AGENDA_CALENDAR : AGENDA_DEFAULT;
|
||||
@ -133,7 +135,7 @@ public class AgendaFragment extends Fragment {
|
||||
new BottomSheetSeparatorItem(true),
|
||||
new BottomSheetPrimaryItem(true)
|
||||
.withTitle(R.string.menu_mark_as_read)
|
||||
.withIcon(CommunityMaterial.Icon.cmd_eye_check)
|
||||
.withIcon(CommunityMaterial.Icon.cmd_eye_check_outline)
|
||||
.withOnClickListener(v3 -> {
|
||||
activity.getBottomSheet().close();
|
||||
AsyncTask.execute(() -> app.db.metadataDao().setAllSeen(App.profileId, TYPE_EVENT, true));
|
||||
|
@ -58,7 +58,7 @@ public class AnnouncementsFragment extends Fragment {
|
||||
activity.getBottomSheet().prependItems(
|
||||
new BottomSheetPrimaryItem(true)
|
||||
.withTitle(R.string.menu_mark_as_read)
|
||||
.withIcon(CommunityMaterial.Icon.cmd_eye_check)
|
||||
.withIcon(CommunityMaterial.Icon.cmd_eye_check_outline)
|
||||
.withOnClickListener(v3 -> {
|
||||
activity.getBottomSheet().close();
|
||||
AsyncTask.execute(() -> app.db.metadataDao().setAllSeen(App.profileId, TYPE_ANNOUNCEMENT, true));
|
||||
|
@ -95,7 +95,7 @@ public class AttendanceFragment extends Fragment {
|
||||
activity.getBottomSheet().prependItems(
|
||||
new BottomSheetPrimaryItem(true)
|
||||
.withTitle(R.string.menu_mark_as_read)
|
||||
.withIcon(CommunityMaterial.Icon.cmd_eye_check)
|
||||
.withIcon(CommunityMaterial.Icon.cmd_eye_check_outline)
|
||||
.withOnClickListener(v3 -> {
|
||||
activity.getBottomSheet().close();
|
||||
AsyncTask.execute(() -> app.db.metadataDao().setAllSeen(App.profileId, TYPE_ATTENDANCE, true));
|
||||
@ -133,7 +133,7 @@ public class AttendanceFragment extends Fragment {
|
||||
CafeBar.builder(activity)
|
||||
.to(activity.getNavView().getCoordinator())
|
||||
.content(R.string.sync_old_data_info)
|
||||
.icon(new IconicsDrawable(activity).icon(CommunityMaterial.Icon2.cmd_sync).size(IconicsSize.dp(20)).color(IconicsColor.colorInt(Themes.INSTANCE.getPrimaryTextColor(activity))))
|
||||
.icon(new IconicsDrawable(activity).icon(CommunityMaterial.Icon.cmd_download_outline).size(IconicsSize.dp(20)).color(IconicsColor.colorInt(Themes.INSTANCE.getPrimaryTextColor(activity))))
|
||||
.positiveText(R.string.refresh)
|
||||
.positiveColor(0xff4caf50)
|
||||
.negativeText(R.string.ok)
|
||||
|
@ -69,7 +69,7 @@ public class BehaviourFragment extends Fragment {
|
||||
activity.getBottomSheet().prependItems(
|
||||
new BottomSheetPrimaryItem(true)
|
||||
.withTitle(R.string.menu_mark_as_read)
|
||||
.withIcon(CommunityMaterial.Icon.cmd_eye_check)
|
||||
.withIcon(CommunityMaterial.Icon.cmd_eye_check_outline)
|
||||
.withOnClickListener(v3 -> {
|
||||
activity.getBottomSheet().close();
|
||||
AsyncTask.execute(() -> app.db.metadataDao().setAllSeen(App.profileId, TYPE_NOTICE, true));
|
||||
|
@ -9,21 +9,20 @@ import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.ImageView
|
||||
import android.widget.TextView
|
||||
|
||||
import com.mikepenz.iconics.IconicsDrawable
|
||||
import androidx.cardview.widget.CardView
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.mikepenz.iconics.IconicsDrawable
|
||||
import com.mikepenz.iconics.typeface.library.community.material.CommunityMaterial
|
||||
import com.mikepenz.iconics.typeface.library.szkolny.font.SzkolnyFont
|
||||
import com.mikepenz.iconics.utils.colorRes
|
||||
import com.mikepenz.iconics.utils.sizeDp
|
||||
import pl.szczodrzynski.edziennik.App
|
||||
import pl.szczodrzynski.edziennik.R
|
||||
import pl.szczodrzynski.edziennik.data.db.modules.login.LoginStore.LOGIN_TYPE_MOBIDZIENNIK
|
||||
import pl.szczodrzynski.edziennik.data.db.modules.notices.Notice
|
||||
import pl.szczodrzynski.edziennik.data.db.modules.notices.NoticeFull
|
||||
import pl.szczodrzynski.edziennik.utils.models.Date
|
||||
|
||||
import pl.szczodrzynski.edziennik.data.db.modules.login.LoginStore.LOGIN_TYPE_MOBIDZIENNIK
|
||||
import pl.szczodrzynski.edziennik.utils.Utils.bs
|
||||
import pl.szczodrzynski.edziennik.utils.models.Date
|
||||
|
||||
class NoticesAdapter//getting the context and product list with constructor
|
||||
(private val context: Context, var noticeList: List<NoticeFull>) : RecyclerView.Adapter<NoticesAdapter.ViewHolder>() {
|
||||
@ -50,15 +49,15 @@ class NoticesAdapter//getting the context and product list with constructor
|
||||
holder.noticesItemAddedDate.text = Date.fromMillis(notice.addedDate).formattedString
|
||||
|
||||
if (notice.type == Notice.TYPE_POSITIVE) {
|
||||
holder.noticesItemType.setImageDrawable(IconicsDrawable(context, CommunityMaterial.Icon2.cmd_plus_circle)
|
||||
holder.noticesItemType.setImageDrawable(IconicsDrawable(context, CommunityMaterial.Icon2.cmd_plus_circle_outline)
|
||||
.colorRes(R.color.md_green_600)
|
||||
.sizeDp(36))
|
||||
} else if (notice.type == Notice.TYPE_NEGATIVE) {
|
||||
holder.noticesItemType.setImageDrawable(IconicsDrawable(context, CommunityMaterial.Icon.cmd_alert_decagram)
|
||||
holder.noticesItemType.setImageDrawable(IconicsDrawable(context, CommunityMaterial.Icon.cmd_alert_decagram_outline)
|
||||
.colorRes(R.color.md_red_600)
|
||||
.sizeDp(36))
|
||||
} else {
|
||||
holder.noticesItemType.setImageDrawable(IconicsDrawable(context, CommunityMaterial.Icon2.cmd_message_outline)
|
||||
holder.noticesItemType.setImageDrawable(IconicsDrawable(context, SzkolnyFont.Icon.szf_message_processing_outline)
|
||||
.colorRes(R.color.md_blue_500)
|
||||
.sizeDp(36))
|
||||
}
|
||||
|
@ -0,0 +1,46 @@
|
||||
/*
|
||||
* Copyright (c) Kuba Szczodrzyński 2019-11-13.
|
||||
*/
|
||||
|
||||
package pl.szczodrzynski.edziennik.ui.modules.error
|
||||
|
||||
import android.util.Log
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import pl.szczodrzynski.edziennik.App
|
||||
import pl.szczodrzynski.edziennik.R
|
||||
import pl.szczodrzynski.edziennik.databinding.DialogLessonDetailsBinding
|
||||
import kotlin.coroutines.CoroutineContext
|
||||
|
||||
class ErrorDialog(
|
||||
val activity: AppCompatActivity,
|
||||
val exception: Exception
|
||||
) : CoroutineScope {
|
||||
companion object {
|
||||
private const val TAG = "ErrorDialog"
|
||||
}
|
||||
|
||||
private lateinit var job: Job
|
||||
override val coroutineContext: CoroutineContext
|
||||
get() = job + Dispatchers.Main
|
||||
|
||||
private val app by lazy { activity.application as App }
|
||||
private lateinit var b: DialogLessonDetailsBinding
|
||||
private lateinit var dialog: AlertDialog
|
||||
|
||||
init { run {
|
||||
job = Job()
|
||||
|
||||
dialog = MaterialAlertDialogBuilder(activity)
|
||||
.setTitle(R.string.error_occured)
|
||||
.setMessage(exception.message + "\n" + Log.getStackTraceString(exception))
|
||||
.setPositiveButton(R.string.ok) { _, _ ->
|
||||
dialog.dismiss()
|
||||
}
|
||||
.show()
|
||||
}}
|
||||
}
|
@ -0,0 +1,47 @@
|
||||
/*
|
||||
* Copyright (c) Kuba Szczodrzyński 2019-11-13.
|
||||
*/
|
||||
|
||||
package pl.szczodrzynski.edziennik.ui.modules.error
|
||||
|
||||
import android.view.View
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||
import androidx.core.graphics.ColorUtils
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import pl.szczodrzynski.edziennik.R
|
||||
import pl.szczodrzynski.edziennik.api.v2.models.ApiError
|
||||
import pl.szczodrzynski.navlib.getColorFromAttr
|
||||
|
||||
class ErrorSnackbar(val activity: AppCompatActivity) {
|
||||
companion object {
|
||||
private const val TAG = "ErrorSnackbar"
|
||||
}
|
||||
|
||||
private var snackbar: Snackbar? = null
|
||||
private lateinit var coordinator: CoordinatorLayout
|
||||
private val errors = mutableListOf<ApiError>()
|
||||
|
||||
fun setCoordinator(coordinatorLayout: CoordinatorLayout, showAbove: View? = null) {
|
||||
this.coordinator = coordinatorLayout
|
||||
snackbar = Snackbar.make(coordinator, R.string.snackbar_error_text, Snackbar.LENGTH_INDEFINITE)
|
||||
snackbar?.setAction(R.string.more) {
|
||||
|
||||
}
|
||||
val bgColor = ColorUtils.compositeColors(
|
||||
getColorFromAttr(activity, R.attr.colorOnSurface) and 0xcfffffff.toInt(),
|
||||
getColorFromAttr(activity, R.attr.colorSurface)
|
||||
)
|
||||
snackbar?.setBackgroundTint(bgColor)
|
||||
showAbove?.let { snackbar?.anchorView = it }
|
||||
}
|
||||
|
||||
fun addError(apiError: ApiError): ErrorSnackbar {
|
||||
errors += apiError
|
||||
snackbar?.setText(apiError.getStringReason(activity))
|
||||
return this
|
||||
}
|
||||
|
||||
fun show() = snackbar?.show()
|
||||
fun dismiss() = snackbar?.dismiss()
|
||||
}
|
@ -132,7 +132,7 @@ public class GradesFragment extends Fragment {
|
||||
}),
|
||||
new BottomSheetPrimaryItem(true)
|
||||
.withTitle(R.string.menu_grades_color_mode)
|
||||
.withIcon(CommunityMaterial.Icon2.cmd_palette)
|
||||
.withIcon(CommunityMaterial.Icon2.cmd_palette_outline)
|
||||
.withOnClickListener(v3 -> {
|
||||
activity.getBottomSheet().close();
|
||||
new MaterialDialog.Builder(activity)
|
||||
@ -195,7 +195,7 @@ public class GradesFragment extends Fragment {
|
||||
new BottomSheetSeparatorItem(true),
|
||||
new BottomSheetPrimaryItem(true)
|
||||
.withTitle(R.string.menu_mark_as_read)
|
||||
.withIcon(CommunityMaterial.Icon.cmd_eye_check)
|
||||
.withIcon(CommunityMaterial.Icon.cmd_eye_check_outline)
|
||||
.withOnClickListener(v3 -> {
|
||||
activity.getBottomSheet().close();
|
||||
AsyncTask.execute(() -> app.db.metadataDao().setAllSeen(App.profileId, TYPE_GRADE, true));
|
||||
|
@ -0,0 +1,49 @@
|
||||
/*
|
||||
* Copyright (c) Kuba Szczodrzyński 2019-11-23.
|
||||
*/
|
||||
|
||||
package pl.szczodrzynski.edziennik.ui.modules.home
|
||||
|
||||
import androidx.recyclerview.widget.ItemTouchHelper
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.google.android.material.card.MaterialCardView
|
||||
import pl.szczodrzynski.edziennik.ui.modules.home.HomeFragmentV2.Companion.swapCards
|
||||
import pl.szczodrzynski.edziennik.utils.SwipeRefreshLayoutNoIndicator
|
||||
|
||||
class CardItemTouchHelperCallback(private val cardAdapter: HomeCardAdapter, private val refreshLayout: SwipeRefreshLayoutNoIndicator?) : ItemTouchHelper.Callback() {
|
||||
companion object {
|
||||
private const val TAG = "CardItemTouchHelperCallback"
|
||||
private const val DRAG_FLAGS = ItemTouchHelper.UP or ItemTouchHelper.DOWN
|
||||
private const val SWIPE_FLAGS = 0
|
||||
}
|
||||
|
||||
private var dragCardView: MaterialCardView? = null
|
||||
|
||||
override fun getMovementFlags(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder): Int {
|
||||
return makeMovementFlags(DRAG_FLAGS, SWIPE_FLAGS)
|
||||
}
|
||||
|
||||
override fun onMove(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder, target: RecyclerView.ViewHolder): Boolean {
|
||||
val fromPosition = viewHolder.adapterPosition
|
||||
val toPosition = target.adapterPosition
|
||||
|
||||
swapCards(fromPosition, toPosition, cardAdapter)
|
||||
return true
|
||||
}
|
||||
|
||||
override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) = Unit
|
||||
|
||||
override fun onSelectedChanged(viewHolder: RecyclerView.ViewHolder?, actionState: Int) {
|
||||
super.onSelectedChanged(viewHolder, actionState)
|
||||
|
||||
if (actionState == ItemTouchHelper.ACTION_STATE_DRAG && viewHolder != null) {
|
||||
dragCardView = viewHolder.itemView as MaterialCardView
|
||||
dragCardView?.isDragged = true
|
||||
refreshLayout?.isEnabled = false
|
||||
} else if (actionState == ItemTouchHelper.ACTION_STATE_IDLE && dragCardView != null) {
|
||||
refreshLayout?.isEnabled = true
|
||||
dragCardView?.isDragged = false
|
||||
dragCardView = null
|
||||
}
|
||||
}
|
||||
}
|
@ -1,11 +1,12 @@
|
||||
package pl.szczodrzynski.edziennik.ui.modules.home;
|
||||
|
||||
import androidx.databinding.DataBindingUtil;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import android.util.Log;
|
||||
import androidx.databinding.DataBindingUtil;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
@ -14,8 +15,8 @@ import java.util.TimerTask;
|
||||
|
||||
import pl.szczodrzynski.edziennik.App;
|
||||
import pl.szczodrzynski.edziennik.R;
|
||||
import pl.szczodrzynski.edziennik.databinding.ActivityCounterBinding;
|
||||
import pl.szczodrzynski.edziennik.data.db.modules.lessons.LessonFull;
|
||||
import pl.szczodrzynski.edziennik.databinding.ActivityCounterBinding;
|
||||
import pl.szczodrzynski.edziennik.utils.models.Date;
|
||||
import pl.szczodrzynski.edziennik.utils.models.Time;
|
||||
|
||||
@ -88,7 +89,7 @@ public class CounterActivity extends AppCompatActivity {
|
||||
private void findLessons(Time syncedNow) {
|
||||
AsyncTask.execute(() -> {
|
||||
Date today = Date.getToday();
|
||||
lessons = app.db.lessonDao().getAllNearestNow(App.profileId, today.clone().stepForward(0, 0, -today.getWeekDay()), today, syncedNow);
|
||||
lessons = app.db.lessonDao().getAllNearestNow(App.profileId, today.getWeekStart(), today, syncedNow);
|
||||
|
||||
if (lessons != null && lessons.size() != 0) {
|
||||
Date displayingDate = lessons.get(0).lessonDate;
|
||||
|
@ -0,0 +1,10 @@
|
||||
/*
|
||||
* Copyright (c) Kuba Szczodrzyński 2019-11-23.
|
||||
*/
|
||||
|
||||
package pl.szczodrzynski.edziennik.ui.modules.home
|
||||
|
||||
interface HomeCard {
|
||||
fun bind(position: Int, holder: HomeCardAdapter.ViewHolder)
|
||||
fun unbind(position: Int, holder: HomeCardAdapter.ViewHolder)
|
||||
}
|
@ -0,0 +1,51 @@
|
||||
/*
|
||||
* Copyright (c) Kuba Szczodrzyński 2019-11-23.
|
||||
*/
|
||||
|
||||
package pl.szczodrzynski.edziennik.ui.modules.home
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.view.LayoutInflater
|
||||
import android.view.ViewGroup
|
||||
import androidx.recyclerview.widget.ItemTouchHelper
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.google.android.material.card.MaterialCardView
|
||||
import pl.szczodrzynski.edziennik.R
|
||||
|
||||
class HomeCardAdapter(var items: MutableList<HomeCard>) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
|
||||
companion object {
|
||||
private const val TAG = "HomeCardAdapter"
|
||||
}
|
||||
|
||||
var itemTouchHelper: ItemTouchHelper? = null
|
||||
|
||||
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
|
||||
(holder as ViewHolder).bind(itemTouchHelper)
|
||||
items[position].bind(position, holder)
|
||||
}
|
||||
|
||||
override fun onViewRecycled(holder: RecyclerView.ViewHolder) {
|
||||
items.getOrNull(holder.adapterPosition)?.unbind(holder.adapterPosition, holder as ViewHolder)
|
||||
}
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
|
||||
return ViewHolder(
|
||||
LayoutInflater.from(parent.context).inflate(R.layout.card_home, parent, false) as MaterialCardView
|
||||
)
|
||||
}
|
||||
|
||||
override fun getItemCount(): Int = items.size
|
||||
|
||||
class ViewHolder(val root: MaterialCardView) : RecyclerView.ViewHolder(root) {
|
||||
@SuppressLint("ClickableViewAccessibility")
|
||||
fun bind(itemTouchHelper: ItemTouchHelper?) {
|
||||
/*root.setOnTouchListener { _: View?, event: MotionEvent ->
|
||||
if (event.action == MotionEvent.ACTION_DOWN) {
|
||||
itemTouchHelper?.startDrag(this)
|
||||
return@setOnTouchListener true
|
||||
}
|
||||
false
|
||||
}*/
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,60 @@
|
||||
/*
|
||||
* Copyright (c) Kuba Szczodrzyński 2019-11-23.
|
||||
*/
|
||||
|
||||
package pl.szczodrzynski.edziennik.ui.modules.home
|
||||
|
||||
import android.widget.TextView
|
||||
import androidx.core.view.plusAssign
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.launch
|
||||
import pl.szczodrzynski.edziennik.App
|
||||
import pl.szczodrzynski.edziennik.MainActivity
|
||||
import pl.szczodrzynski.edziennik.startCoroutineTimer
|
||||
import kotlin.coroutines.CoroutineContext
|
||||
|
||||
class HomeDummyCard(val id: Int) : HomeCard, CoroutineScope {
|
||||
companion object {
|
||||
private const val TAG = "HomeDummyCard"
|
||||
}
|
||||
|
||||
private lateinit var app: App
|
||||
private lateinit var activity: MainActivity
|
||||
|
||||
private var job: Job = Job()
|
||||
override val coroutineContext: CoroutineContext
|
||||
get() = job + Dispatchers.Main
|
||||
|
||||
var timer: Job? = null
|
||||
var time = 0
|
||||
|
||||
override fun bind(position: Int, holder: HomeCardAdapter.ViewHolder) { launch {
|
||||
holder.root.removeAllViews()
|
||||
//holder.setIsRecyclable(false)
|
||||
|
||||
val text = TextView(holder.root.context).apply {
|
||||
text = "This is a card #$id"
|
||||
}
|
||||
holder.root += text
|
||||
|
||||
timer = startCoroutineTimer(repeatMillis = 1000) {
|
||||
time++
|
||||
text.text = "Coroutine timer at #$id! $time seconds"
|
||||
}
|
||||
|
||||
/*val button = MaterialButton(holder.root.context).apply {
|
||||
setText("Cancel")
|
||||
onClick {
|
||||
timer.cancel()
|
||||
}
|
||||
}
|
||||
holder.root += button*/
|
||||
}}
|
||||
|
||||
override fun unbind(position: Int, holder: HomeCardAdapter.ViewHolder) {
|
||||
timer?.cancel()
|
||||
timer = null
|
||||
}
|
||||
}
|
@ -37,6 +37,7 @@ import com.mikepenz.iconics.IconicsColor;
|
||||
import com.mikepenz.iconics.IconicsDrawable;
|
||||
import com.mikepenz.iconics.IconicsSize;
|
||||
import com.mikepenz.iconics.typeface.library.community.material.CommunityMaterial;
|
||||
import com.mikepenz.iconics.typeface.library.szkolny.font.SzkolnyFont;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
@ -268,7 +269,7 @@ public class HomeFragment extends Fragment {
|
||||
activity.getBottomSheet().prependItems(
|
||||
new BottomSheetPrimaryItem(true)
|
||||
.withTitle(R.string.menu_set_student_number)
|
||||
.withIcon(CommunityMaterial.Icon.cmd_counter)
|
||||
.withIcon(SzkolnyFont.Icon.szf_clipboard_list_outline)
|
||||
.withOnClickListener(v3 -> {
|
||||
activity.getBottomSheet().close();
|
||||
setNumberDialog();
|
||||
@ -276,7 +277,7 @@ public class HomeFragment extends Fragment {
|
||||
new BottomSheetSeparatorItem(true),
|
||||
new BottomSheetPrimaryItem(true)
|
||||
.withTitle(R.string.menu_mark_everything_as_read)
|
||||
.withIcon(CommunityMaterial.Icon.cmd_eye_check)
|
||||
.withIcon(CommunityMaterial.Icon.cmd_eye_check_outline)
|
||||
.withOnClickListener(v3 -> {
|
||||
activity.getBottomSheet().close();
|
||||
AsyncTask.execute(() -> app.db.metadataDao().setAllSeen(App.profileId, true));
|
||||
|
@ -0,0 +1,141 @@
|
||||
/*
|
||||
* Copyright (c) Kuba Szczodrzyński 2019-11-23.
|
||||
*/
|
||||
|
||||
package pl.szczodrzynski.edziennik.ui.modules.home
|
||||
|
||||
import android.os.AsyncTask
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.View.OnClickListener
|
||||
import android.view.ViewGroup
|
||||
import android.widget.Toast
|
||||
import androidx.core.view.AccessibilityDelegateCompat
|
||||
import androidx.core.view.accessibility.AccessibilityNodeInfoCompat
|
||||
import androidx.core.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.recyclerview.widget.ItemTouchHelper
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerViewAccessibilityDelegate
|
||||
import com.mikepenz.iconics.typeface.library.community.material.CommunityMaterial.Icon
|
||||
import com.mikepenz.iconics.typeface.library.szkolny.font.SzkolnyFont
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import pl.szczodrzynski.edziennik.App
|
||||
import pl.szczodrzynski.edziennik.MainActivity
|
||||
import pl.szczodrzynski.edziennik.R
|
||||
import pl.szczodrzynski.edziennik.databinding.FragmentHomeV2Binding
|
||||
import pl.szczodrzynski.edziennik.ui.dialogs.home.StudentNumberDialog
|
||||
import pl.szczodrzynski.edziennik.ui.modules.home.cards.HomeLuckyNumberCard
|
||||
import pl.szczodrzynski.edziennik.ui.modules.home.cards.HomeTimetableCard
|
||||
import pl.szczodrzynski.edziennik.utils.Themes
|
||||
import pl.szczodrzynski.navlib.bottomsheet.items.BottomSheetPrimaryItem
|
||||
import pl.szczodrzynski.navlib.bottomsheet.items.BottomSheetSeparatorItem
|
||||
import kotlin.coroutines.CoroutineContext
|
||||
|
||||
class HomeFragmentV2 : Fragment(), CoroutineScope {
|
||||
companion object {
|
||||
private const val TAG = "HomeFragment"
|
||||
|
||||
fun swapCards(fromPosition: Int, toPosition: Int, cardAdapter: HomeCardAdapter) {
|
||||
val fromCard = cardAdapter.items[fromPosition]
|
||||
cardAdapter.items[fromPosition] = cardAdapter.items[toPosition]
|
||||
cardAdapter.items[toPosition] = fromCard
|
||||
cardAdapter.notifyItemMoved(fromPosition, toPosition)
|
||||
}
|
||||
}
|
||||
|
||||
private lateinit var app: App
|
||||
private lateinit var activity: MainActivity
|
||||
private lateinit var b: FragmentHomeV2Binding
|
||||
|
||||
private lateinit var job: Job
|
||||
override val coroutineContext: CoroutineContext
|
||||
get() = job + Dispatchers.Main
|
||||
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
||||
activity = (getActivity() as MainActivity?) ?: return null
|
||||
context ?: return null
|
||||
app = activity.application as App
|
||||
context!!.theme.applyStyle(Themes.appTheme, true)
|
||||
b = FragmentHomeV2Binding.inflate(inflater)
|
||||
b.refreshLayout.setParent(activity.swipeRefreshLayout)
|
||||
job = Job()
|
||||
return b.root
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
// TODO check if app, activity, b can be null
|
||||
if (app.profile == null || !isAdded)
|
||||
return
|
||||
|
||||
activity.bottomSheet.prependItems(
|
||||
BottomSheetPrimaryItem(true)
|
||||
.withTitle(R.string.menu_set_student_number)
|
||||
.withIcon(SzkolnyFont.Icon.szf_clipboard_list_outline)
|
||||
.withOnClickListener(OnClickListener {
|
||||
activity.bottomSheet.close()
|
||||
StudentNumberDialog(activity, app.profile) {
|
||||
app.profileSaveAsync()
|
||||
}
|
||||
}),
|
||||
BottomSheetSeparatorItem(true),
|
||||
BottomSheetPrimaryItem(true)
|
||||
.withTitle(R.string.menu_mark_everything_as_read)
|
||||
.withIcon(Icon.cmd_eye_check_outline)
|
||||
.withOnClickListener(OnClickListener {
|
||||
activity.bottomSheet.close()
|
||||
AsyncTask.execute { app.db.metadataDao().setAllSeen(App.profileId, true) }
|
||||
Toast.makeText(activity, R.string.main_menu_mark_as_read_success, Toast.LENGTH_SHORT).show()
|
||||
})
|
||||
)
|
||||
|
||||
val items = mutableListOf<HomeCard>(
|
||||
HomeLuckyNumberCard(0, app, activity, this, app.profile),
|
||||
HomeTimetableCard(1, app, activity, this, app.profile)
|
||||
)
|
||||
|
||||
val adapter = HomeCardAdapter(items)
|
||||
val itemTouchHelper = ItemTouchHelper(CardItemTouchHelperCallback(adapter, b.refreshLayout))
|
||||
adapter.itemTouchHelper = itemTouchHelper
|
||||
b.list.layoutManager = LinearLayoutManager(activity)
|
||||
b.list.adapter = adapter
|
||||
b.list.setAccessibilityDelegateCompat(object : RecyclerViewAccessibilityDelegate(b.list) {
|
||||
override fun getItemDelegate(): AccessibilityDelegateCompat {
|
||||
return object : ItemDelegate(this) {
|
||||
override fun onInitializeAccessibilityNodeInfo(host: View, info: AccessibilityNodeInfoCompat) {
|
||||
super.onInitializeAccessibilityNodeInfo(host, info)
|
||||
val position: Int = b.list.getChildLayoutPosition(host)
|
||||
if (position != 0) {
|
||||
info.addAction(AccessibilityActionCompat(
|
||||
R.id.move_card_up_action,
|
||||
host.resources.getString(R.string.card_action_move_up)
|
||||
))
|
||||
}
|
||||
if (position != adapter.itemCount - 1) {
|
||||
info.addAction(AccessibilityActionCompat(
|
||||
R.id.move_card_down_action,
|
||||
host.resources.getString(R.string.card_action_move_down)
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
override fun performAccessibilityAction(host: View, action: Int, args: Bundle): Boolean {
|
||||
val fromPosition: Int = b.list.getChildLayoutPosition(host)
|
||||
if (action == R.id.move_card_down_action) {
|
||||
swapCards(fromPosition, fromPosition + 1, adapter)
|
||||
return true
|
||||
} else if (action == R.id.move_card_up_action) {
|
||||
swapCards(fromPosition, fromPosition - 1, adapter)
|
||||
return true
|
||||
}
|
||||
return super.performAccessibilityAction(host, action, args)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
itemTouchHelper.attachToRecyclerView(b.list)
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user