Compare commits

...

37 Commits

Author SHA1 Message Date
13b58a1d56 [3.9.6-dev] Widgets. Timetables. RIP APIv1. 2019-11-15 00:02:00 +01:00
0a3261b8b3 [APIv2/Mobidziennik] Fix incorrect profile ID in teams & timetable. 2019-11-14 23:46:53 +01:00
dbdfc7fdd8 [Widget] Add new Timetable widget with APIv2. 2019-11-14 23:33:13 +01:00
56062f5bfa [Timetable] Fix day fragment crashing on restoring saved instance 2019-11-14 22:13:32 +01:00
0cbba2eb45 [Models] Make Time comparable. Implement faster Date.stepForward 2019-11-14 19:31:50 +01:00
aa84356dd6 [APIv2/Vulcan] Add getting timetable with lesson changes 2019-11-14 00:41:34 +01:00
efaad0a4dd [APIv1] Remove remaining APIv1 components. [*] 2019-11-13 22:37:30 +01:00
71015c0137 [APIv1] Remove most APIv1 components. 2019-11-13 22:26:12 +01:00
85b5667a7e [Sync] New manual sync dialog. Remove most APIv1 dependencies. 2019-11-13 21:57:47 +01:00
dfdc6817a1 [APIv2/Vulcan] Fix getting sent messages with unknown recipient 2019-11-13 21:01:09 +01:00
058345b9c9 [Error] Add user friendly error strings. Add error snackbar to activity & login. 2019-11-13 19:44:08 +01:00
831b7876b4 [APIv2] Fix duplicated error code 2019-11-13 18:23:21 +01:00
729cf6f08e [Settings] New Profile removal dialog. 2019-11-13 17:19:25 +01:00
860a16b32d [3.9.5-dev] Messages & manual events 2019-11-13 17:19:25 +01:00
9f871c077b [APIv2/Librus] Fix getting messages with unknown recipient 2019-11-13 00:00:28 +01:00
a8f89abf7d [APIv2/Vulcan] Add changing message status 2019-11-12 23:59:48 +01:00
16102de619 [Event] Add new manual event dialog 2019-11-12 23:35:47 +01:00
472e768369 [Gradle] Update MaterialDrawer and NavLib 2019-11-12 23:35:13 +01:00
131a769c26 [Gradle] Update dependencies 2019-11-12 22:05:40 +01:00
4a0a6c54e4 [Timetable] Make timetable sync disable the button on click. 2019-11-12 14:36:20 +01:00
c83abe57d5 [Messages] Implement APIv2 in MessageFragment. 2019-11-12 14:11:35 +01:00
c6e2519dcc [APIv2/Librus] Add getting message info 2019-11-12 00:30:26 +01:00
74db524db6 [Timetable] Add lesson details dialog. 2019-11-11 23:59:45 +01:00
810976d976 [3.9.4-dev] The Timetable Release 2019-11-11 19:22:15 +01:00
eb0540b5cb [Timetable] Fix adding NO_LESSONS in Mobidziennik. Change ID generation. 2019-11-11 19:14:02 +01:00
124437fd73 [Login] Allow fake login with DevMode. 2019-11-11 18:41:39 +01:00
69b512e3d1 [Timetable] Extract string resources. Increase offscreen page limit. 2019-11-11 18:32:49 +01:00
29d74e14bd [Timetable] Fix scrolling to first lesson. Update lesson ID generation. 2019-11-11 18:13:37 +01:00
f42ec8435a [Timetable] Show user friendly day name in view pager. 2019-11-11 15:46:04 +01:00
1052b824db [Timetable] Make it sync only timetable when getting a single week. 2019-11-11 14:52:09 +01:00
0742a6a74c [Timetable] Fix removing all lessons when not needed. 2019-11-11 14:16:31 +01:00
d4e9e1730f [APIv2] Implement getting timetable for one week. Support arguments in EdziennikTask. Create new DataRemoveModel. 2019-11-11 14:11:05 +01:00
4eeaa54a47 [Timetable] Implement Librus timetable with lesson changes and shifts. Update UI. 2019-11-10 22:57:19 +01:00
5fa7409317 [APIv2/Librus] Add getting normal lessons 2019-11-10 21:49:40 +01:00
0bcd190714 [APIv2] Add Librus Fake login. 2019-11-10 20:27:26 +01:00
563f08b0ab [UI] Update Timetable lesson layout. Add lesson number text. 2019-11-10 18:53:45 +01:00
1b75424604 [APIv2/UI] Add new Timetable module. Implement in Mobidziennik. 2019-11-10 17:53:10 +01:00
139 changed files with 5830 additions and 12642 deletions

5
.idea/misc.xml generated
View File

@ -6,8 +6,9 @@
</configurations> </configurations>
</component> </component>
<component name="EntryPointsManager"> <component name="EntryPointsManager">
<list size="1"> <list size="2">
<item index="0" class="java.lang.String" itemvalue="org.greenrobot.eventbus.Subscribe" /> <item index="0" class="java.lang.String" itemvalue="androidx.databinding.BindingAdapter" />
<item index="1" class="java.lang.String" itemvalue="org.greenrobot.eventbus.Subscribe" />
</list> </list>
</component> </component>
<component name="NullableNotNullManager"> <component name="NullableNotNullManager">

View File

@ -131,7 +131,7 @@ dependencies {
implementation("com.github.ozodrukh:CircularReveal:2.0.1@aar") {transitive = true} implementation("com.github.ozodrukh:CircularReveal:2.0.1@aar") {transitive = true}
implementation "com.heinrichreimersoftware:material-intro:1.5.8" // do not update implementation "com.heinrichreimersoftware:material-intro:1.5.8" // do not update
implementation "com.jaredrummler:colorpicker:1.0.2" implementation "com.jaredrummler:colorpicker:1.0.2"
implementation "com.squareup.okhttp3:okhttp:3.12.0" implementation "com.squareup.okhttp3:okhttp:3.12.2"
implementation "com.theartofdev.edmodo:android-image-cropper:2.8.0" // do not update implementation "com.theartofdev.edmodo:android-image-cropper:2.8.0" // do not update
implementation "com.wdullaer:materialdatetimepicker:4.1.2" implementation "com.wdullaer:materialdatetimepicker:4.1.2"
implementation "com.yuyh.json:jsonviewer:1.0.6" implementation "com.yuyh.json:jsonviewer:1.0.6"
@ -166,6 +166,10 @@ dependencies {
implementation "androidx.work:work-runtime-ktx:${versions.work}" implementation "androidx.work:work-runtime-ktx:${versions.work}"
implementation 'com.hypertrack:hyperlog:0.0.10' implementation 'com.hypertrack:hyperlog:0.0.10'
implementation 'com.github.kuba2k2:RecyclerTabLayout:700f980584'
implementation 'com.linkedin.android.tachyon:tachyon:1.0.2'
} }
repositories { repositories {
mavenCentral() mavenCentral()

View File

@ -39,4 +39,13 @@
-keep class okhttp3.** { *; } -keep class okhttp3.** { *; }
-keep class com.google.android.material.tabs.** {*;} -keep class com.google.android.material.tabs.** {*;}
# ServiceLoader support
-keepnames class kotlinx.coroutines.internal.MainDispatcherFactory {}
-keepnames class kotlinx.coroutines.CoroutineExceptionHandler {}
# Most of volatile fields are updated with AFU and should not be mangled
-keepclassmembernames class kotlinx.** {
volatile <fields>;
}

View File

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#FF4caf50"
android:pathData="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"/>
</vector>

View File

@ -105,6 +105,11 @@
android:excludeFromRecents="true" android:excludeFromRecents="true"
android:noHistory="true" android:noHistory="true"
android:theme="@style/AppTheme.NoDisplay" /> 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 <activity
android:name=".ui.modules.settings.SettingsLicenseActivity" android:name=".ui.modules.settings.SettingsLicenseActivity"
android:configChanges="orientation|keyboardHidden" android:configChanges="orientation|keyboardHidden"

View File

@ -68,11 +68,6 @@ import me.leolin.shortcutbadger.ShortcutBadger;
import okhttp3.ConnectionSpec; import okhttp3.ConnectionSpec;
import okhttp3.OkHttpClient; import okhttp3.OkHttpClient;
import okhttp3.TlsVersion; 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.AppDb;
import pl.szczodrzynski.edziennik.data.db.modules.debuglog.DebugLog; import pl.szczodrzynski.edziennik.data.db.modules.debuglog.DebugLog;
import pl.szczodrzynski.edziennik.data.db.modules.login.LoginStore; 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 PersistentCookieJar cookieJar;
public OkHttpClient http; public OkHttpClient http;
public OkHttpClient httpLazy; 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 SharedPreferences appSharedPrefs; // sharedPreferences for APPCONFIG + JOBS STORE
public AppConfig appConfig; // APPCONFIG: common for all profiles 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); db = AppDb.getDatabase(this);
gson = new Gson(); gson = new Gson();
networkUtils = new NetworkUtils(this); 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.init(getApplicationContext());
Iconics.registerFont(SzkolnyFont.INSTANCE); Iconics.registerFont(SzkolnyFont.INSTANCE);

View File

@ -0,0 +1,21 @@
/*
* Copyright (c) Kuba Szczodrzyński 2019-11-11.
*/
package pl.szczodrzynski.edziennik;
import android.graphics.Paint;
import android.widget.TextView;
import androidx.databinding.BindingAdapter;
public class Binding {
@BindingAdapter("strikeThrough")
public static void strikeThrough(TextView textView, Boolean strikeThrough) {
if (strikeThrough) {
textView.setPaintFlags(textView.getPaintFlags() | Paint.STRIKE_THRU_TEXT_FLAG);
} else {
textView.setPaintFlags(textView.getPaintFlags() & ~Paint.STRIKE_THRU_TEXT_FLAG);
}
}
}

View File

@ -4,12 +4,23 @@ import android.Manifest
import android.app.Activity import android.app.Activity
import android.content.Context import android.content.Context
import android.content.pm.PackageManager import android.content.pm.PackageManager
import android.graphics.Typeface
import android.os.Build import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.text.*
import android.text.style.ForegroundColorSpan
import android.text.style.StrikethroughSpan
import android.text.style.StyleSpan
import android.util.LongSparseArray import android.util.LongSparseArray
import android.util.SparseArray import android.util.SparseArray
import android.view.View
import android.widget.TextView
import androidx.annotation.StringRes
import androidx.core.app.ActivityCompat import androidx.core.app.ActivityCompat
import androidx.core.util.forEach import androidx.core.util.forEach
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.LiveData
import androidx.lifecycle.Observer
import com.google.gson.JsonArray import com.google.gson.JsonArray
import com.google.gson.JsonElement import com.google.gson.JsonElement
import com.google.gson.JsonObject import com.google.gson.JsonObject
@ -84,6 +95,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 { fun changeStringCase(s: String): String {
val delimiters = " '-/" val delimiters = " '-/"
val sb = StringBuilder() val sb = StringBuilder()
@ -142,8 +162,9 @@ fun colorFromName(context: Context, name: String?): Int {
return context.getColorFromRes(color) return context.getColorFromRes(color)
} }
fun MutableList<out Profile>.filterOutArchived() { fun MutableList<Profile>.filterOutArchived(): MutableList<Profile> {
this.removeAll { it.archived } this.removeAll { it.archived }
return this
} }
fun Activity.isStoragePermissionGranted(): Boolean { fun Activity.isStoragePermissionGranted(): Boolean {
@ -326,4 +347,102 @@ fun String.crc32(): Long {
return crc.value return crc.value
} }
fun Long.formatDate(format: String = "yyyy-MM-dd HH:mm:ss"): String = SimpleDateFormat(format).format(this) fun Long.formatDate(format: String = "yyyy-MM-dd HH:mm:ss"): String = SimpleDateFormat(format).format(this)
fun CharSequence?.asColoredSpannable(colorInt: Int): Spannable {
val spannable = SpannableString(this)
spannable.setSpan(ForegroundColorSpan(colorInt), 0, spannable.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
return spannable
}
fun CharSequence?.asStrikethroughSpannable(): Spannable {
val spannable = SpannableString(this)
spannable.setSpan(StrikethroughSpan(), 0, spannable.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
return spannable
}
fun CharSequence?.asItalicSpannable(): Spannable {
val spannable = SpannableString(this)
spannable.setSpan(StyleSpan(Typeface.ITALIC), 0, spannable.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
return spannable
}
/**
* Returns a new read-only list only of those given elements, that are not empty.
* Applies for CharSequence and descendants.
*/
fun <T : CharSequence> listOfNotEmpty(vararg elements: T): List<T> = elements.filterNot { it.isEmpty() }
fun List<CharSequence>.concat(delimiter: String? = null): CharSequence {
if (this.isEmpty()) {
return ""
}
if (this.size == 1) {
return this[0]
}
var spanned = false
for (piece in this) {
if (piece is Spanned) {
spanned = true
break
}
}
var first = true
if (spanned) {
val ssb = SpannableStringBuilder()
for (piece in this) {
if (!first && delimiter != null)
ssb.append(delimiter)
first = false
ssb.append(piece)
}
return SpannedString(ssb)
} else {
val sb = StringBuilder()
for (piece in this) {
if (!first && delimiter != null)
sb.append(delimiter)
first = false
sb.append(piece)
}
return sb.toString()
}
}
fun TextView.setText(@StringRes resid: Int, vararg formatArgs: Any) {
text = context.getString(resid, *formatArgs)
}
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)
}
}
}
}
fun JsonArray?.isNullOrEmpty(): Boolean = (this?.size() ?: 0) == 0
fun JsonArray.isEmpty(): Boolean = this.size() == 0
@Suppress("UNCHECKED_CAST")
inline fun <T : View> T.onClick(crossinline onClickListener: (v: T) -> Unit) {
setOnClickListener { v: View ->
onClickListener(v as T)
}
}
fun <T> LiveData<T>.observeOnce(lifecycleOwner: LifecycleOwner, observer: Observer<T>) {
observe(lifecycleOwner, object : Observer<T> {
override fun onChanged(t: T?) {
observer.onChanged(t)
removeObserver(this)
}
})
}

View File

@ -38,18 +38,20 @@ import pl.droidsonroids.gif.GifDrawable
import pl.szczodrzynski.edziennik.App.APP_URL import pl.szczodrzynski.edziennik.App.APP_URL
import pl.szczodrzynski.edziennik.api.v2.events.* import pl.szczodrzynski.edziennik.api.v2.events.*
import pl.szczodrzynski.edziennik.api.v2.events.task.EdziennikTask 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.data.db.modules.metadata.Metadata.*
import pl.szczodrzynski.edziennik.databinding.ActivitySzkolnyBinding import pl.szczodrzynski.edziennik.databinding.ActivitySzkolnyBinding
import pl.szczodrzynski.edziennik.network.ServerRequest import pl.szczodrzynski.edziennik.network.ServerRequest
import pl.szczodrzynski.edziennik.sync.AppManagerDetectedEvent import pl.szczodrzynski.edziennik.sync.AppManagerDetectedEvent
import pl.szczodrzynski.edziennik.sync.SyncWorker import pl.szczodrzynski.edziennik.sync.SyncWorker
import pl.szczodrzynski.edziennik.ui.dialogs.changelog.ChangelogDialog 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.agenda.AgendaFragment
import pl.szczodrzynski.edziennik.ui.modules.announcements.AnnouncementsFragment import pl.szczodrzynski.edziennik.ui.modules.announcements.AnnouncementsFragment
import pl.szczodrzynski.edziennik.ui.modules.attendance.AttendanceFragment import pl.szczodrzynski.edziennik.ui.modules.attendance.AttendanceFragment
import pl.szczodrzynski.edziennik.ui.modules.base.DebugFragment import pl.szczodrzynski.edziennik.ui.modules.base.DebugFragment
import pl.szczodrzynski.edziennik.ui.modules.behaviour.BehaviourFragment 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.FeedbackFragment
import pl.szczodrzynski.edziennik.ui.modules.feedback.HelpFragment import pl.szczodrzynski.edziennik.ui.modules.feedback.HelpFragment
import pl.szczodrzynski.edziennik.ui.modules.grades.GradesFragment import pl.szczodrzynski.edziennik.ui.modules.grades.GradesFragment
@ -57,12 +59,12 @@ import pl.szczodrzynski.edziennik.ui.modules.grades.editor.GradesEditorFragment
import pl.szczodrzynski.edziennik.ui.modules.home.HomeFragment import pl.szczodrzynski.edziennik.ui.modules.home.HomeFragment
import pl.szczodrzynski.edziennik.ui.modules.homework.HomeworkFragment import pl.szczodrzynski.edziennik.ui.modules.homework.HomeworkFragment
import pl.szczodrzynski.edziennik.ui.modules.login.LoginActivity import pl.szczodrzynski.edziennik.ui.modules.login.LoginActivity
import pl.szczodrzynski.edziennik.ui.modules.messages.MessagesDetailsFragment import pl.szczodrzynski.edziennik.ui.modules.messages.MessageFragment
import pl.szczodrzynski.edziennik.ui.modules.messages.MessagesFragment import pl.szczodrzynski.edziennik.ui.modules.messages.MessagesFragment
import pl.szczodrzynski.edziennik.ui.modules.notifications.NotificationsFragment import pl.szczodrzynski.edziennik.ui.modules.notifications.NotificationsFragment
import pl.szczodrzynski.edziennik.ui.modules.settings.ProfileManagerFragment import pl.szczodrzynski.edziennik.ui.modules.settings.ProfileManagerFragment
import pl.szczodrzynski.edziennik.ui.modules.settings.SettingsNewFragment import pl.szczodrzynski.edziennik.ui.modules.settings.SettingsNewFragment
import pl.szczodrzynski.edziennik.ui.modules.timetable.TimetableFragment import pl.szczodrzynski.edziennik.ui.modules.timetable.v2.TimetableFragment
import pl.szczodrzynski.edziennik.utils.SwipeRefreshLayoutNoTouch import pl.szczodrzynski.edziennik.utils.SwipeRefreshLayoutNoTouch
import pl.szczodrzynski.edziennik.utils.Themes import pl.szczodrzynski.edziennik.utils.Themes
import pl.szczodrzynski.edziennik.utils.Utils import pl.szczodrzynski.edziennik.utils.Utils
@ -203,7 +205,7 @@ class MainActivity : AppCompatActivity() {
list += NavTarget(TARGET_GRADES_EDITOR, R.string.menu_grades_editor, GradesEditorFragment::class) list += NavTarget(TARGET_GRADES_EDITOR, R.string.menu_grades_editor, GradesEditorFragment::class)
list += NavTarget(TARGET_HELP, R.string.menu_help, HelpFragment::class) list += NavTarget(TARGET_HELP, R.string.menu_help, HelpFragment::class)
list += NavTarget(TARGET_FEEDBACK, R.string.menu_feedback, FeedbackFragment::class) list += NavTarget(TARGET_FEEDBACK, R.string.menu_feedback, FeedbackFragment::class)
list += NavTarget(TARGET_MESSAGES_DETAILS, R.string.menu_message, MessagesDetailsFragment::class) list += NavTarget(TARGET_MESSAGES_DETAILS, R.string.menu_message, MessageFragment::class)
list += NavTarget(DRAWER_ITEM_DEBUG, R.string.menu_debug, DebugFragment::class) list += NavTarget(DRAWER_ITEM_DEBUG, R.string.menu_debug, DebugFragment::class)
list list
@ -214,6 +216,7 @@ class MainActivity : AppCompatActivity() {
val navView: NavView by lazy { b.navView } val navView: NavView by lazy { b.navView }
val drawer: NavDrawer by lazy { navView.drawer } val drawer: NavDrawer by lazy { navView.drawer }
val bottomSheet: NavBottomSheet by lazy { navView.bottomSheet } val bottomSheet: NavBottomSheet by lazy { navView.bottomSheet }
val errorSnackbar: ErrorSnackbar by lazy { ErrorSnackbar(this) }
val swipeRefreshLayout: SwipeRefreshLayoutNoTouch by lazy { b.swipeRefreshLayout } val swipeRefreshLayout: SwipeRefreshLayoutNoTouch by lazy { b.swipeRefreshLayout }
@ -246,6 +249,8 @@ class MainActivity : AppCompatActivity() {
setContentView(b.root) setContentView(b.root)
errorSnackbar.setCoordinator(b.navView.coordinator, b.navView.bottomBar)
navLoading = true navLoading = true
b.navView.apply { b.navView.apply {
@ -345,7 +350,7 @@ class MainActivity : AppCompatActivity() {
if (!profileListEmpty) { if (!profileListEmpty) {
handleIntent(intent?.extras) handleIntent(intent?.extras)
} }
app.db.profileDao().getAllFull().observe(this, Observer { profiles -> app.db.profileDao().allFull.observe(this, Observer { profiles ->
// TODO fix weird -1 profiles ??? // TODO fix weird -1 profiles ???
profiles.removeAll { it.id < 0 } profiles.removeAll { it.id < 0 }
drawer.setProfileList(profiles) drawer.setProfileList(profiles)
@ -362,7 +367,7 @@ class MainActivity : AppCompatActivity() {
if (app.profile != null) if (app.profile != null)
setDrawerItems() setDrawerItems()
app.db.metadataDao().getUnreadCounts().observe(this, Observer { unreadCounters -> app.db.metadataDao().unreadCounts.observe(this, Observer { unreadCounters ->
unreadCounters.map { unreadCounters.map {
it.type = it.thingType it.type = it.thingType
} }
@ -464,7 +469,7 @@ class MainActivity : AppCompatActivity() {
.withIcon(CommunityMaterial.Icon2.cmd_sync) .withIcon(CommunityMaterial.Icon2.cmd_sync)
.withOnClickListener(View.OnClickListener { .withOnClickListener(View.OnClickListener {
bottomSheet.close() 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), BottomSheetSeparatorItem(false),
BottomSheetPrimaryItem(false) BottomSheetPrimaryItem(false)
@ -573,7 +578,12 @@ class MainActivity : AppCompatActivity() {
} }
@Subscribe(threadMode = ThreadMode.MAIN) @Subscribe(threadMode = ThreadMode.MAIN)
fun onSyncErrorEvent(event: ApiTaskErrorEvent) { 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) @Subscribe(threadMode = ThreadMode.MAIN, sticky = true)
fun onAppManagerDetectedEvent(event: AppManagerDetectedEvent) { fun onAppManagerDetectedEvent(event: AppManagerDetectedEvent) {
@ -605,22 +615,7 @@ class MainActivity : AppCompatActivity() {
.setCancelable(false) .setCancelable(false)
.show() .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 { private fun fragmentToSyncName(currentFragment: Int): Int {
return when (currentFragment) { return when (currentFragment) {
DRAWER_ITEM_TIMETABLE -> R.string.sync_feature_timetable DRAWER_ITEM_TIMETABLE -> R.string.sync_feature_timetable
@ -701,7 +696,8 @@ class MainActivity : AppCompatActivity() {
} }
intentTargetId != -1 -> { intentTargetId != -1 -> {
drawer.currentProfile = app.profile.id drawer.currentProfile = app.profile.id
loadTarget(intentTargetId, extras) if (navTargetId != intentTargetId)
loadTarget(intentTargetId, extras)
} }
else -> { else -> {
drawer.currentProfile = app.profile.id drawer.currentProfile = app.profile.id
@ -1042,7 +1038,7 @@ class MainActivity : AppCompatActivity() {
} }
loadTarget(DRAWER_ITEM_SETTINGS, null) loadTarget(DRAWER_ITEM_SETTINGS, null)
} else if (item.itemId == 2) { } 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 true
} }

View File

@ -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");
}
}

View 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.Icon2.cmd_sync)
.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 }
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 }
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.startTime
model.endTime = lesson.endTime
// 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
}
}
}

View File

@ -14,6 +14,14 @@ val SYSTEM_USER_AGENT = System.getProperty("http.agent") ?: "Dalvik/2.1.0 Androi
val SERVER_USER_AGENT = "Szkolny.eu/${BuildConfig.VERSION_NAME} $SYSTEM_USER_AGENT" val SERVER_USER_AGENT = "Szkolny.eu/${BuildConfig.VERSION_NAME} $SYSTEM_USER_AGENT"
const val FAKE_LIBRUS_API = "http://librus.szkolny.eu/api"
const val FAKE_LIBRUS_PORTAL = "http://librus.szkolny.eu"
const val FAKE_LIBRUS_AUTHORIZE = "http://librus.szkolny.eu/authorize.php"
const val FAKE_LIBRUS_LOGIN = "http://librus.szkolny.eu/login_action.php"
const val FAKE_LIBRUS_TOKEN = "http://librus.szkolny.eu/access_token.php"
const val FAKE_LIBRUS_ACCOUNT = "/synergia_accounts_fresh.php?login="
const val FAKE_LIBRUS_ACCOUNTS = "/synergia_accounts.php"
val LIBRUS_USER_AGENT = "$SYSTEM_USER_AGENT LibrusMobileApp" val LIBRUS_USER_AGENT = "$SYSTEM_USER_AGENT LibrusMobileApp"
const val SYNERGIA_USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) Gecko/20100101 Firefox/62.0" const val SYNERGIA_USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) Gecko/20100101 Firefox/62.0"
const val LIBRUS_CLIENT_ID = "wmSyUMo8llDAs4y9tJVYY92oyZ6h4lAt7KCuy0Gv" const val LIBRUS_CLIENT_ID = "wmSyUMo8llDAs4y9tJVYY92oyZ6h4lAt7KCuy0Gv"

View File

@ -109,7 +109,7 @@ const val ERROR_LOGIN_MOBIDZIENNIK_WEB_INVALID_ADDRESS = 206
const val ERROR_LOGIN_MOBIDZIENNIK_WEB_OTHER = 210 const val ERROR_LOGIN_MOBIDZIENNIK_WEB_OTHER = 210
const val ERROR_MOBIDZIENNIK_WEB_ACCESS_DENIED = 211 const val ERROR_MOBIDZIENNIK_WEB_ACCESS_DENIED = 211
const val ERROR_MOBIDZIENNIK_WEB_NO_SESSION_KEY = 212 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_NO_SERVER_ID = 213
const val ERROR_MOBIDZIENNIK_WEB_INVALID_RESPONSE = 214 const val ERROR_MOBIDZIENNIK_WEB_INVALID_RESPONSE = 214
const val ERROR_LOGIN_MOBIDZIENNIK_WEB_NO_SESSION_ID = 215 const val ERROR_LOGIN_MOBIDZIENNIK_WEB_NO_SESSION_ID = 215
@ -163,3 +163,5 @@ const val EXCEPTION_LIBRUS_MESSAGES_REQUEST = 911
const val EXCEPTION_IDZIENNIK_WEB_REQUEST = 912 const val EXCEPTION_IDZIENNIK_WEB_REQUEST = 912
const val EXCEPTION_IDZIENNIK_WEB_API_REQUEST = 913 const val EXCEPTION_IDZIENNIK_WEB_API_REQUEST = 913
const val EXCEPTION_IDZIENNIK_API_REQUEST = 914 const val EXCEPTION_IDZIENNIK_API_REQUEST = 914
const val LOGIN_NO_ARGUMENTS = 1201

View File

@ -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_AGENDA
import pl.szczodrzynski.edziennik.MainActivity.Companion.DRAWER_ITEM_ANNOUNCEMENTS 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_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_GRADES
import pl.szczodrzynski.edziennik.MainActivity.Companion.DRAWER_ITEM_HOME 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_HOMEWORK
import pl.szczodrzynski.edziennik.MainActivity.Companion.DRAWER_ITEM_MESSAGES 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.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_RECEIVED
import pl.szczodrzynski.edziennik.data.db.modules.messages.Message.TYPE_SENT import pl.szczodrzynski.edziennik.data.db.modules.messages.Message.TYPE_SENT
const val FEATURE_ALL = 0 internal const val FEATURE_TIMETABLE = 1
const val FEATURE_TIMETABLE = 1 internal const val FEATURE_AGENDA = 2
const val FEATURE_AGENDA = 2 internal const val FEATURE_GRADES = 3
const val FEATURE_GRADES = 3 internal const val FEATURE_HOMEWORK = 4
const val FEATURE_HOMEWORK = 4 internal const val FEATURE_BEHAVIOUR = 5
const val FEATURE_BEHAVIOUR = 5 internal const val FEATURE_ATTENDANCE = 6
const val FEATURE_ATTENDANCE = 6 internal const val FEATURE_MESSAGES_INBOX = 7
const val FEATURE_MESSAGES_INBOX = 7 internal const val FEATURE_MESSAGES_SENT = 8
const val FEATURE_MESSAGES_SENT = 8 internal const val FEATURE_ANNOUNCEMENTS = 9
const val FEATURE_ANNOUNCEMENTS = 9
const val FEATURE_ALWAYS_NEEDED = 100 internal const val FEATURE_ALWAYS_NEEDED = 100
const val FEATURE_STUDENT_INFO = 101 internal const val FEATURE_STUDENT_INFO = 101
const val FEATURE_STUDENT_NUMBER = 109 internal const val FEATURE_STUDENT_NUMBER = 109
const val FEATURE_SCHOOL_INFO = 102 internal const val FEATURE_SCHOOL_INFO = 102
const val FEATURE_CLASS_INFO = 103 internal const val FEATURE_CLASS_INFO = 103
const val FEATURE_TEAM_INFO = 104 internal const val FEATURE_TEAM_INFO = 104
const val FEATURE_LUCKY_NUMBER = 105 internal const val FEATURE_LUCKY_NUMBER = 105
const val FEATURE_TEACHERS = 106 internal const val FEATURE_TEACHERS = 106
const val FEATURE_SUBJECTS = 107 internal const val FEATURE_SUBJECTS = 107
const val FEATURE_CLASSROOMS = 108 internal const val FEATURE_CLASSROOMS = 108
const val FEATURE_PUSH_CONFIG = 120 internal const val FEATURE_PUSH_CONFIG = 120
const val FEATURE_MESSAGE_GET = 201
object Features { object Features {
private fun getAllNecessary(): List<Int> = listOf( private fun getAllNecessary(): List<Int> = listOf(

View File

@ -66,13 +66,13 @@ val librusLoginMethods = listOf(
}, },
LoginMethod(LOGIN_TYPE_LIBRUS, LOGIN_METHOD_LIBRUS_SYNERGIA, LibrusLoginSynergia::class.java) LoginMethod(LOGIN_TYPE_LIBRUS, LOGIN_METHOD_LIBRUS_SYNERGIA, LibrusLoginSynergia::class.java)
.withIsPossible { _, _ -> true } .withIsPossible { _, loginStore -> !loginStore.hasLoginData("fakeLogin") }
.withRequiredLoginMethod { profile, _ -> .withRequiredLoginMethod { profile, _ ->
if (profile?.hasStudentData("accountPassword") == false) LOGIN_METHOD_LIBRUS_API else LOGIN_METHOD_NOT_NEEDED if (profile?.hasStudentData("accountPassword") == false) LOGIN_METHOD_LIBRUS_API else LOGIN_METHOD_NOT_NEEDED
}, },
LoginMethod(LOGIN_TYPE_LIBRUS, LOGIN_METHOD_LIBRUS_MESSAGES, LibrusLoginMessages::class.java) LoginMethod(LOGIN_TYPE_LIBRUS, LOGIN_METHOD_LIBRUS_MESSAGES, LibrusLoginMessages::class.java)
.withIsPossible { _, _ -> true } .withIsPossible { _, loginStore -> !loginStore.hasLoginData("fakeLogin") }
.withRequiredLoginMethod { profile, _ -> .withRequiredLoginMethod { profile, _ ->
if (profile?.hasStudentData("accountPassword") == false) LOGIN_METHOD_LIBRUS_SYNERGIA else LOGIN_METHOD_NOT_NEEDED if (profile?.hasStudentData("accountPassword") == false) LOGIN_METHOD_LIBRUS_SYNERGIA else LOGIN_METHOD_NOT_NEEDED
} }

View File

@ -60,4 +60,8 @@ object Regexes {
val IDZIENNIK_LOGIN_FIRST_STUDENT by lazy { 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) """<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()
}
} }

View File

@ -0,0 +1,9 @@
/*
* Copyright (c) Kuba Szczodrzyński 2019-11-12.
*/
package pl.szczodrzynski.edziennik.api.v2.events
import pl.szczodrzynski.edziennik.data.db.modules.messages.MessageFull
data class MessageGetEvent(val message: MessageFull)

View File

@ -1,5 +1,6 @@
package pl.szczodrzynski.edziennik.api.v2.events.task package pl.szczodrzynski.edziennik.api.v2.events.task
import com.google.gson.JsonObject
import pl.szczodrzynski.edziennik.App import pl.szczodrzynski.edziennik.App
import pl.szczodrzynski.edziennik.R import pl.szczodrzynski.edziennik.R
import pl.szczodrzynski.edziennik.api.v2.* import pl.szczodrzynski.edziennik.api.v2.*
@ -11,6 +12,7 @@ import pl.szczodrzynski.edziennik.api.v2.mobidziennik.Mobidziennik
import pl.szczodrzynski.edziennik.api.v2.template.Template import pl.szczodrzynski.edziennik.api.v2.template.Template
import pl.szczodrzynski.edziennik.api.v2.vulcan.Vulcan import pl.szczodrzynski.edziennik.api.v2.vulcan.Vulcan
import pl.szczodrzynski.edziennik.data.db.modules.login.LoginStore import pl.szczodrzynski.edziennik.data.db.modules.login.LoginStore
import pl.szczodrzynski.edziennik.data.db.modules.messages.MessageFull
open class EdziennikTask(override val profileId: Int, val request: Any) : IApiTask(profileId) { open class EdziennikTask(override val profileId: Int, val request: Any) : IApiTask(profileId) {
companion object { companion object {
@ -18,9 +20,9 @@ open class EdziennikTask(override val profileId: Int, val request: Any) : IApiTa
fun firstLogin(loginStore: LoginStore) = EdziennikTask(-1, FirstLoginRequest(loginStore)) fun firstLogin(loginStore: LoginStore) = EdziennikTask(-1, FirstLoginRequest(loginStore))
fun sync() = EdziennikTask(-1, SyncRequest()) fun sync() = EdziennikTask(-1, SyncRequest())
fun syncProfile(profileId: Int, viewIds: List<Pair<Int, Int>>? = null) = EdziennikTask(profileId, SyncProfileRequest(viewIds)) fun syncProfile(profileId: Int, viewIds: List<Pair<Int, Int>>? = null, arguments: JsonObject? = null) = EdziennikTask(profileId, SyncProfileRequest(viewIds, arguments))
fun syncProfileList(profileList: List<Int>) = EdziennikTask(-1, SyncProfileListRequest(profileList)) fun syncProfileList(profileList: List<Int>) = EdziennikTask(-1, SyncProfileListRequest(profileList))
fun messageGet(profileId: Int, messageId: Int) = EdziennikTask(profileId, MessageGetRequest(messageId)) fun messageGet(profileId: Int, message: MessageFull) = EdziennikTask(profileId, MessageGetRequest(message))
fun announcementsRead(profileId: Int) = EdziennikTask(profileId, AnnouncementsReadRequest()) fun announcementsRead(profileId: Int) = EdziennikTask(profileId, AnnouncementsReadRequest())
} }
@ -38,7 +40,7 @@ open class EdziennikTask(override val profileId: Int, val request: Any) : IApiTa
// get the requested profile and login store // get the requested profile and login store
val profile = app.db.profileDao().getByIdNow(profileId) val profile = app.db.profileDao().getByIdNow(profileId)
this.profile = profile this.profile = profile
if (profile == null || !profile.syncEnabled) { if (profile == null) {
return return
} }
val loginStore = app.db.loginStoreDao().getByIdNow(profile.loginStoreId) ?: return val loginStore = app.db.loginStoreDao().getByIdNow(profile.loginStoreId) ?: return
@ -50,7 +52,7 @@ open class EdziennikTask(override val profileId: Int, val request: Any) : IApiTa
private var edziennikInterface: EdziennikInterface? = null private var edziennikInterface: EdziennikInterface? = null
fun run(app: App, taskCallback: EdziennikCallback) { internal fun run(app: App, taskCallback: EdziennikCallback) {
edziennikInterface = when (loginStore.type) { edziennikInterface = when (loginStore.type) {
LOGIN_TYPE_LIBRUS -> Librus(app, profile, loginStore, taskCallback) LOGIN_TYPE_LIBRUS -> Librus(app, profile, loginStore, taskCallback)
LOGIN_TYPE_MOBIDZIENNIK -> Mobidziennik(app, profile, loginStore, taskCallback) LOGIN_TYPE_MOBIDZIENNIK -> Mobidziennik(app, profile, loginStore, taskCallback)
@ -66,8 +68,9 @@ open class EdziennikTask(override val profileId: Int, val request: Any) : IApiTa
when (request) { when (request) {
is SyncProfileRequest -> edziennikInterface?.sync( 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) viewId = request.viewIds?.get(0)?.first,
is MessageGetRequest -> edziennikInterface?.getMessage(request.messageId) arguments = request.arguments)
is MessageGetRequest -> edziennikInterface?.getMessage(request.message)
is FirstLoginRequest -> edziennikInterface?.firstLogin() is FirstLoginRequest -> edziennikInterface?.firstLogin()
is AnnouncementsReadRequest -> edziennikInterface?.markAllAnnouncementsAsRead() is AnnouncementsReadRequest -> edziennikInterface?.markAllAnnouncementsAsRead()
} }
@ -83,8 +86,8 @@ open class EdziennikTask(override val profileId: Int, val request: Any) : IApiTa
data class FirstLoginRequest(val loginStore: LoginStore) data class FirstLoginRequest(val loginStore: LoginStore)
class SyncRequest class SyncRequest
data class SyncProfileRequest(val viewIds: List<Pair<Int, Int>>? = null) data class SyncProfileRequest(val viewIds: List<Pair<Int, Int>>? = null, val arguments: JsonObject? = null)
data class SyncProfileListRequest(val profileList: List<Int>) data class SyncProfileListRequest(val profileList: List<Int>)
data class MessageGetRequest(val messageId: Int) data class MessageGetRequest(val message: MessageFull)
class AnnouncementsReadRequest class AnnouncementsReadRequest
} }

View File

@ -4,6 +4,7 @@
package pl.szczodrzynski.edziennik.api.v2.idziennik package pl.szczodrzynski.edziennik.api.v2.idziennik
import com.google.gson.JsonObject
import pl.szczodrzynski.edziennik.App import pl.szczodrzynski.edziennik.App
import pl.szczodrzynski.edziennik.api.v2.CODE_INTERNAL_LIBRUS_ACCOUNT_410 import pl.szczodrzynski.edziennik.api.v2.CODE_INTERNAL_LIBRUS_ACCOUNT_410
import pl.szczodrzynski.edziennik.api.v2.idziennik.data.IdziennikData import pl.szczodrzynski.edziennik.api.v2.idziennik.data.IdziennikData
@ -15,6 +16,7 @@ import pl.szczodrzynski.edziennik.api.v2.interfaces.EdziennikInterface
import pl.szczodrzynski.edziennik.api.v2.models.ApiError import pl.szczodrzynski.edziennik.api.v2.models.ApiError
import pl.szczodrzynski.edziennik.api.v2.prepare import pl.szczodrzynski.edziennik.api.v2.prepare
import pl.szczodrzynski.edziennik.data.db.modules.login.LoginStore 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.Profile
import pl.szczodrzynski.edziennik.utils.Utils.d import pl.szczodrzynski.edziennik.utils.Utils.d
@ -48,7 +50,8 @@ class Idziennik(val app: App, val profile: Profile?, val loginStore: LoginStore,
|_| |_| |_|\___| /_/ \_\_|\__, |\___/|_| |_|\__|_| |_|_| |_| |_| |_| |_| |_|\___| /_/ \_\_|\__, |\___/|_| |_|\__|_| |_|_| |_| |_|
__/ | __/ |
|__*/ |__*/
override fun sync(featureIds: List<Int>, viewId: Int?) { override fun sync(featureIds: List<Int>, viewId: Int?, arguments: JsonObject?) {
data.arguments = arguments
data.prepare(idziennikLoginMethods, IdziennikFeatures, featureIds, viewId) data.prepare(idziennikLoginMethods, IdziennikFeatures, featureIds, viewId)
d(TAG, "LoginMethod IDs: ${data.targetLoginMethodIds}") d(TAG, "LoginMethod IDs: ${data.targetLoginMethodIds}")
d(TAG, "Endpoint IDs: ${data.targetEndpointIds}") d(TAG, "Endpoint IDs: ${data.targetEndpointIds}")
@ -59,7 +62,7 @@ class Idziennik(val app: App, val profile: Profile?, val loginStore: LoginStore,
} }
} }
override fun getMessage(messageId: Int) { override fun getMessage(message: MessageFull) {
} }

View File

@ -75,7 +75,7 @@ class IdziennikApiMessagesInbox(override val data: DataIdziennik,
/*messageId*/ messageId /*messageId*/ messageId
) )
data.messageList.add(message) data.messageIgnoreList.add(message)
data.messageRecipientList.add(messageRecipient) data.messageRecipientList.add(messageRecipient)
data.messageMetadataList.add(Metadata( data.messageMetadataList.add(Metadata(
profileId, profileId,

View File

@ -74,7 +74,7 @@ class IdziennikApiMessagesSent(override val data: DataIdziennik,
data.messageRecipientIgnoreList.add(messageRecipient) data.messageRecipientIgnoreList.add(messageRecipient)
} }
data.messageList.add(message) data.messageIgnoreList.add(message)
data.metadataList.add(Metadata(profileId, Metadata.TYPE_MESSAGE, message.id, true, true, sentDate)) data.metadataList.add(Metadata(profileId, Metadata.TYPE_MESSAGE, message.id, true, true, sentDate))
} }

View File

@ -4,9 +4,12 @@
package pl.szczodrzynski.edziennik.api.v2.interfaces package pl.szczodrzynski.edziennik.api.v2.interfaces
import com.google.gson.JsonObject
import pl.szczodrzynski.edziennik.data.db.modules.messages.MessageFull
interface EdziennikInterface { interface EdziennikInterface {
fun sync(featureIds: List<Int>, viewId: Int? = null) fun sync(featureIds: List<Int>, viewId: Int? = null, arguments: JsonObject? = null)
fun getMessage(messageId: Int) fun getMessage(message: MessageFull)
fun markAllAnnouncementsAsRead() fun markAllAnnouncementsAsRead()
fun firstLogin() fun firstLogin()
fun cancel() fun cancel()

View File

@ -4,18 +4,22 @@
package pl.szczodrzynski.edziennik.api.v2.librus package pl.szczodrzynski.edziennik.api.v2.librus
import com.google.gson.JsonObject
import pl.szczodrzynski.edziennik.App import pl.szczodrzynski.edziennik.App
import pl.szczodrzynski.edziennik.api.v2.* import pl.szczodrzynski.edziennik.api.v2.*
import pl.szczodrzynski.edziennik.api.v2.interfaces.EdziennikCallback import pl.szczodrzynski.edziennik.api.v2.interfaces.EdziennikCallback
import pl.szczodrzynski.edziennik.api.v2.interfaces.EdziennikInterface 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.LibrusData
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.data.synergia.LibrusSynergiaMarkAllAnnouncementsAsRead
import pl.szczodrzynski.edziennik.api.v2.librus.firstlogin.LibrusFirstLogin 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.LibrusLogin
import pl.szczodrzynski.edziennik.api.v2.librus.login.LibrusLoginApi 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.LibrusLoginSynergia
import pl.szczodrzynski.edziennik.api.v2.models.ApiError import pl.szczodrzynski.edziennik.api.v2.models.ApiError
import pl.szczodrzynski.edziennik.data.db.modules.login.LoginStore 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.Profile
import pl.szczodrzynski.edziennik.utils.Utils.d import pl.szczodrzynski.edziennik.utils.Utils.d
@ -49,7 +53,8 @@ class Librus(val app: App, val profile: Profile?, val loginStore: LoginStore, va
|_| |_| |_|\___| /_/ \_\_|\__, |\___/|_| |_|\__|_| |_|_| |_| |_| |_| |_| |_|\___| /_/ \_\_|\__, |\___/|_| |_|\__|_| |_|_| |_| |_|
__/ | __/ |
|__*/ |__*/
override fun sync(featureIds: List<Int>, viewId: Int?) { override fun sync(featureIds: List<Int>, viewId: Int?, arguments: JsonObject?) {
data.arguments = arguments
data.prepare(librusLoginMethods, LibrusFeatures, featureIds, viewId) data.prepare(librusLoginMethods, LibrusFeatures, featureIds, viewId)
login() login()
} }
@ -76,8 +81,16 @@ class Librus(val app: App, val profile: Profile?, val loginStore: LoginStore, va
} }
} }
override fun getMessage(messageId: Int) { override fun getMessage(message: MessageFull) {
LibrusLoginApi(data) {
LibrusLoginSynergia(data) {
LibrusLoginMessages(data) {
LibrusMessagesGetMessage(data, message) {
completed()
}
}
}
}
} }
override fun markAllAnnouncementsAsRead() { override fun markAllAnnouncementsAsRead() {
@ -173,7 +186,10 @@ class Librus(val app: App, val profile: Profile?, val loginStore: LoginStore, va
login() login()
} }
// TODO PORTAL CAPTCHA // TODO PORTAL CAPTCHA
ERROR_LIBRUS_API_TIMETABLE_NOT_PUBLIC, ERROR_LIBRUS_API_TIMETABLE_NOT_PUBLIC -> {
loginStore.putLoginData("timetableNotPublic", true)
data()
}
ERROR_LIBRUS_API_LUCKY_NUMBER_NOT_ACTIVE, ERROR_LIBRUS_API_LUCKY_NUMBER_NOT_ACTIVE,
ERROR_LIBRUS_API_NOTES_NOT_ACTIVE -> { ERROR_LIBRUS_API_NOTES_NOT_ACTIVE -> {
data() data()

View File

@ -28,7 +28,7 @@ open class LibrusApi(open val data: DataLibrus) {
fun apiGet(tag: String, endpoint: String, method: Int = GET, payload: JsonObject? = null, onSuccess: (json: JsonObject) -> Unit) { fun apiGet(tag: String, endpoint: String, method: Int = GET, payload: JsonObject? = null, onSuccess: (json: JsonObject) -> Unit) {
d(tag, "Request: Librus/Api - $LIBRUS_API_URL/$endpoint") d(tag, "Request: Librus/Api - ${if (data.fakeLogin) FAKE_LIBRUS_API else LIBRUS_API_URL}/$endpoint")
val callback = object : JsonCallbackHandler() { val callback = object : JsonCallbackHandler() {
override fun onSuccess(json: JsonObject?, response: Response?) { override fun onSuccess(json: JsonObject?, response: Response?) {
@ -90,7 +90,7 @@ open class LibrusApi(open val data: DataLibrus) {
} }
Request.builder() Request.builder()
.url("$LIBRUS_API_URL/$endpoint") .url("${if (data.fakeLogin) FAKE_LIBRUS_API else LIBRUS_API_URL}/$endpoint")
.userAgent(LIBRUS_USER_AGENT) .userAgent(LIBRUS_USER_AGENT)
.addHeader("Authorization", "Bearer ${data.apiAccessToken}") .addHeader("Authorization", "Bearer ${data.apiAccessToken}")
.apply { .apply {

View File

@ -76,7 +76,10 @@ class LibrusData(val data: DataLibrus, val onSuccess: () -> Unit) {
LibrusApiClassrooms(data, onSuccess) LibrusApiClassrooms(data, onSuccess)
} }
// TODO push config // TODO push config
// TODO timetable ENDPOINT_LIBRUS_API_TIMETABLES -> {
data.startProgress(R.string.edziennik_progress_endpoint_timetable)
LibrusApiTimetables(data, onSuccess)
}
ENDPOINT_LIBRUS_API_NORMAL_GRADES -> { ENDPOINT_LIBRUS_API_NORMAL_GRADES -> {
data.startProgress(R.string.edziennik_progress_endpoint_grades) data.startProgress(R.string.edziennik_progress_endpoint_grades)

View File

@ -24,7 +24,7 @@ open class LibrusPortal(open val data: DataLibrus) {
fun portalGet(tag: String, endpoint: String, method: Int = GET, payload: JsonObject? = null, onSuccess: (json: JsonObject, response: Response?) -> Unit) { fun portalGet(tag: String, endpoint: String, method: Int = GET, payload: JsonObject? = null, onSuccess: (json: JsonObject, response: Response?) -> Unit) {
d(tag, "Request: Librus/Portal - $LIBRUS_PORTAL_URL$endpoint") d(tag, "Request: Librus/Portal - ${if (data.fakeLogin) FAKE_LIBRUS_PORTAL else LIBRUS_PORTAL_URL}$endpoint")
val callback = object : JsonCallbackHandler() { val callback = object : JsonCallbackHandler() {
override fun onSuccess(json: JsonObject?, response: Response?) { override fun onSuccess(json: JsonObject?, response: Response?) {
@ -81,7 +81,7 @@ open class LibrusPortal(open val data: DataLibrus) {
} }
Request.builder() Request.builder()
.url(LIBRUS_PORTAL_URL + endpoint) .url((if (data.fakeLogin) FAKE_LIBRUS_PORTAL else LIBRUS_PORTAL_URL) + endpoint)
.userAgent(LIBRUS_USER_AGENT) .userAgent(LIBRUS_USER_AGENT)
.addHeader("Authorization", "Bearer ${data.portalAccessToken}") .addHeader("Authorization", "Bearer ${data.portalAccessToken}")
.apply { .apply {

View File

@ -0,0 +1,192 @@
/*
* Copyright (c) Kuba Szczodrzyński 2019-11-10.
*/
package pl.szczodrzynski.edziennik.api.v2.librus.data.api
import androidx.core.util.isEmpty
import com.google.gson.JsonObject
import pl.szczodrzynski.edziennik.*
import pl.szczodrzynski.edziennik.api.v2.librus.DataLibrus
import pl.szczodrzynski.edziennik.api.v2.librus.ENDPOINT_LIBRUS_API_TIMETABLES
import pl.szczodrzynski.edziennik.api.v2.librus.data.LibrusApi
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.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
class LibrusApiTimetables(override val data: DataLibrus,
val onSuccess: () -> Unit) : LibrusApi(data) {
companion object {
const val TAG = "LibrusApiTimetables"
}
init {
if (data.classrooms.isEmpty()) {
data.db.classroomDao().getAllNow(profileId).toSparseArray(data.classrooms) { it.id }
}
val currentWeekStart = Date.getToday().let { it.stepForward(0, 0, -it.weekDay) }
val getDate = data.arguments?.getString("weekStart") ?: currentWeekStart.stringY_m_d
apiGet(TAG, "Timetables?weekStart=$getDate") { json ->
val days = json.getJsonObject("Timetable")
days?.entrySet()?.forEach { (dateString, dayEl) ->
val day = dayEl?.asJsonArray
val lessonDate = dateString?.let { Date.fromY_m_d(it) } ?: return@forEach
var lessonsFound = false
day?.forEach { lessonRangeEl ->
val lessonRange = lessonRangeEl?.asJsonArray?.asJsonObjectList()
if (lessonRange?.isNullOrEmpty() == false)
lessonsFound = true
lessonRange?.forEach { lesson ->
parseLesson(lessonDate, lesson)
}
}
if (day.isNullOrEmpty() || !lessonsFound) {
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))
data.setSyncNext(ENDPOINT_LIBRUS_API_TIMETABLES, SYNC_ALWAYS)
onSuccess()
}
}
private fun parseLesson(lessonDate: Date, lesson: JsonObject) {
val isSubstitution = lesson.getBoolean("IsSubstitutionClass") ?: false
val isCancelled = lesson.getBoolean("IsCanceled") ?: false
val lessonNo = lesson.getInt("LessonNo") ?: return
val startTime = lesson.getString("HourFrom")?.let { Time.fromH_m(it) } ?: return
val endTime = lesson.getString("HourTo")?.let { Time.fromH_m(it) } ?: return
val subjectId = lesson.getJsonObject("Subject")?.getLong("Id")
val teacherId = lesson.getJsonObject("Teacher")?.getLong("Id")
val classroomId = lesson.getJsonObject("Classroom")?.getLong("Id") ?: -1
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)
if (isSubstitution && isCancelled) {
// shifted lesson - source
val newDate = lesson.getString("NewDate")?.let { Date.fromY_m_d(it) } ?: return
val newLessonNo = lesson.getInt("NewLessonNo") ?: return
val newStartTime = lesson.getString("NewHourFrom")?.let { Time.fromH_m(it) } ?: return
val newEndTime = lesson.getString("NewHourTo")?.let { Time.fromH_m(it) } ?: return
val newSubjectId = lesson.getJsonObject("NewSubject")?.getLong("Id")
val newTeacherId = lesson.getJsonObject("NewTeacher")?.getLong("Id")
val newClassroomId = lesson.getJsonObject("NewClassroom")?.getLong("Id") ?: -1
val newVirtualClassId = lesson.getJsonObject("NewVirtualClass")?.getLong("Id")
val newTeamId = lesson.getJsonObject("NewClass")?.getLong("Id") ?: newVirtualClassId
lessonObject.let {
it.type = Lesson.TYPE_SHIFTED_SOURCE
it.oldDate = lessonDate
it.oldLessonNumber = lessonNo
it.oldStartTime = startTime
it.oldEndTime = endTime
it.oldSubjectId = subjectId
it.oldTeacherId = teacherId
it.oldTeamId = teamId
it.oldClassroom = data.classrooms[classroomId]?.name
it.date = newDate
it.lessonNumber = newLessonNo
it.startTime = newStartTime
it.endTime = newEndTime
it.subjectId = newSubjectId
it.teacherId = newTeacherId
it.teamId = newTeamId
it.classroom = data.classrooms[newClassroomId]?.name
}
}
else if (isSubstitution) {
// lesson change OR shifted lesson - target
val oldDate = lesson.getString("OrgDate")?.let { Date.fromY_m_d(it) } ?: return
val oldLessonNo = lesson.getInt("OrgLessonNo") ?: return
val oldStartTime = lesson.getString("OrgHourFrom")?.let { Time.fromH_m(it) } ?: return
val oldEndTime = lesson.getString("OrgHourTo")?.let { Time.fromH_m(it) } ?: return
val oldSubjectId = lesson.getJsonObject("OrgSubject")?.getLong("Id")
val oldTeacherId = lesson.getJsonObject("OrgTeacher")?.getLong("Id")
val oldClassroomId = lesson.getJsonObject("OrgClassroom")?.getLong("Id") ?: -1
val oldVirtualClassId = lesson.getJsonObject("OrgVirtualClass")?.getLong("Id")
val oldTeamId = lesson.getJsonObject("OrgClass")?.getLong("Id") ?: oldVirtualClassId
lessonObject.let {
it.type = if (lessonDate == oldDate && lessonNo == oldLessonNo) Lesson.TYPE_CHANGE else Lesson.TYPE_SHIFTED_TARGET
it.oldDate = oldDate
it.oldLessonNumber = oldLessonNo
it.oldStartTime = oldStartTime
it.oldEndTime = oldEndTime
it.oldSubjectId = oldSubjectId
it.oldTeacherId = oldTeacherId
it.oldTeamId = oldTeamId
it.oldClassroom = data.classrooms[oldClassroomId]?.name
it.date = lessonDate
it.lessonNumber = lessonNo
it.startTime = startTime
it.endTime = endTime
it.subjectId = subjectId
it.teacherId = teacherId
it.teamId = teamId
it.classroom = data.classrooms[classroomId]?.name
}
}
else if (isCancelled) {
lessonObject.let {
it.type = Lesson.TYPE_CANCELLED
it.oldDate = lessonDate
it.oldLessonNumber = lessonNo
it.oldStartTime = startTime
it.oldEndTime = endTime
it.oldSubjectId = subjectId
it.oldTeacherId = teacherId
it.oldTeamId = teamId
it.oldClassroom = data.classrooms[classroomId]?.name
}
}
else {
lessonObject.let {
it.type = Lesson.TYPE_NORMAL
it.date = lessonDate
it.lessonNumber = lessonNo
it.startTime = startTime
it.endTime = endTime
it.subjectId = subjectId
it.teacherId = teacherId
it.teamId = teamId
it.classroom = data.classrooms[classroomId]?.name
}
}
if (lessonObject.type != Lesson.TYPE_NORMAL) {
data.metadataList.add(
Metadata(
data.profileId,
Metadata.TYPE_LESSON_CHANGE,
lessonObject.id,
data.profile?.empty ?: false,
data.profile?.empty ?: false,
System.currentTimeMillis()
))
}
data.lessonNewList.add(lessonObject)
}
}

View File

@ -22,8 +22,8 @@ class LibrusApiUsers(override val data: DataLibrus,
users?.forEach { user -> users?.forEach { user ->
val id = user.getLong("Id") ?: return@forEach val id = user.getLong("Id") ?: return@forEach
val firstName = user.getString("FirstName")?.fixWhiteSpaces() ?: "" val firstName = user.getString("FirstName")?.fixName() ?: ""
val lastName = user.getString("LastName")?.fixWhiteSpaces() ?: "" val lastName = user.getString("LastName")?.fixName() ?: ""
data.teacherList.put(id, Teacher(profileId, id, firstName, lastName)) data.teacherList.put(id, Teacher(profileId, id, firstName, lastName))
} }

View File

@ -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.api.v2.librus.data.LibrusMessages
import pl.szczodrzynski.edziennik.data.db.modules.api.SYNC_ALWAYS 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
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.messages.MessageRecipient
import pl.szczodrzynski.edziennik.data.db.modules.metadata.Metadata import pl.szczodrzynski.edziennik.data.db.modules.metadata.Metadata
import pl.szczodrzynski.edziennik.data.db.modules.teachers.Teacher 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.Utils
import pl.szczodrzynski.edziennik.utils.models.Date 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) { archived: Boolean = false, val onSuccess: () -> Unit) : LibrusMessages(data) {
companion object { companion object {
const val TAG = "LibrusMessagesGetList" const val TAG = "LibrusMessagesGetList"
@ -28,7 +29,7 @@ class LibrusMessagesGetList(override val data: DataLibrus, private val type: Int
init { init {
val endpoint = when (type) { val endpoint = when (type) {
Message.TYPE_RECEIVED -> "Inbox/action/GetList" TYPE_RECEIVED -> "Inbox/action/GetList"
Message.TYPE_SENT -> "Outbox/action/GetList" Message.TYPE_SENT -> "Outbox/action/GetList"
else -> null else -> null
} }
@ -46,34 +47,38 @@ class LibrusMessagesGetList(override val data: DataLibrus, private val type: Int
else -> 0 else -> 0
} }
val sentDate = Date.fromIso(element.select("sendDate").text().trim()) val sentDate = Date.fromIso(element.select("sendDate").text().trim())
var senderId: Long = -1
var receiverId: Long = -1
when (type) { val recipientFirstName = element.select(when (type) {
Message.TYPE_RECEIVED -> { TYPE_RECEIVED -> "senderFirstName"
val senderFirstName = element.select("senderFirstName").text().trim() else -> "receiverFirstName"
val senderLastName = element.select("senderLastName").text().trim() }).text().trim()
senderId = data.teacherList.singleOrNull {
it.name == senderFirstName && it.surname == senderLastName
}?.id ?: -1
}
Message.TYPE_SENT -> { val recipientLastName = element.select(when (type) {
val receiverFirstName = element.select("receiverFirstName").text().trim() TYPE_RECEIVED -> "senderLastName"
val receiverLastName = element.select("receiverLastName").text().trim() else -> "receiverLastName"
receiverId = data.teacherList.singleOrNull { }).text().trim()
it.name == receiverFirstName && it.surname == receiverLastName
}?.id ?: { val recipientId = data.teacherList.singleOrNull {
val teacherObject = Teacher( it.name == recipientFirstName && it.surname == recipientLastName
profileId, }?.id ?: {
-1 * Utils.crc16("$receiverFirstName $receiverLastName".toByteArray()).toLong(), val teacherObject = Teacher(
receiverFirstName, profileId,
receiverLastName -1 * Utils.crc16("$recipientFirstName $recipientLastName".toByteArray()).toLong(),
) recipientFirstName,
data.teacherList.put(teacherObject.id, teacherObject) recipientLastName
teacherObject.id )
}.invoke() 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) { val notified = when (type) {
@ -99,7 +104,7 @@ class LibrusMessagesGetList(override val data: DataLibrus, private val type: Int
id id
) )
data.messageList.add(messageObject) data.messageIgnoreList.add(messageObject)
data.messageRecipientList.add(messageRecipientObject) data.messageRecipientList.add(messageRecipientObject)
data.metadataList.add(Metadata( data.metadataList.add(Metadata(
profileId, profileId,
@ -112,7 +117,7 @@ class LibrusMessagesGetList(override val data: DataLibrus, private val type: Int
} }
when (type) { 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) Message.TYPE_SENT -> data.setSyncNext(ENDPOINT_LIBRUS_MESSAGES_SENT, DAY, DRAWER_ITEM_MESSAGES)
} }
onSuccess() onSuccess()

View File

@ -0,0 +1,132 @@
/*
* Copyright (c) Kacper Ziubryniewicz 2019-11-11
*/
package pl.szczodrzynski.edziennik.api.v2.librus.data.messages
import android.util.Base64
import org.greenrobot.eventbus.EventBus
import pl.szczodrzynski.edziennik.api.v2.events.MessageGetEvent
import pl.szczodrzynski.edziennik.api.v2.librus.DataLibrus
import pl.szczodrzynski.edziennik.api.v2.librus.data.LibrusMessages
import pl.szczodrzynski.edziennik.data.db.modules.messages.Message.TYPE_RECEIVED
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.MessageRecipientFull
import pl.szczodrzynski.edziennik.data.db.modules.metadata.Metadata
import pl.szczodrzynski.edziennik.fixName
import pl.szczodrzynski.edziennik.singleOrNull
import pl.szczodrzynski.edziennik.utils.models.Date
import java.nio.charset.Charset
class LibrusMessagesGetMessage(
override val data: DataLibrus,
private val messageObject: MessageFull,
val onSuccess: () -> Unit
) : LibrusMessages(data) {
companion object {
const val TAG = "LibrusMessagesGetMessage"
}
init { data.profile?.also { profile ->
messagesGet(TAG, "GetMessage", parameters = mapOf(
"messageId" to messageObject.id,
"archive" to 0
)) { doc ->
val message = doc.select("response GetMessage data").first()
val body = Base64.decode(message.select("Message").text(), Base64.DEFAULT)
.toString(Charset.defaultCharset())
.replace("\n", "<br>")
.replace("<!\\[CDATA\\[", "")
.replace("]]>", "")
messageObject.apply {
this.body = body
clearAttachments()
message.select("attachments ArrayItem").forEach {
val attachmentId = it.select("id").text().toLong()
val attachmentName = it.select("filename").text()
addAttachment(attachmentId, attachmentName, -1)
}
}
val messageRecipientList = mutableListOf<MessageRecipientFull>()
when (messageObject.type) {
TYPE_RECEIVED -> {
val senderLoginId = message.select("senderId").text()
data.teacherList.singleOrNull { it.id == messageObject.senderId }?.loginId = senderLoginId
val readDateText = message.select("readDate").text()
val readDate = when (readDateText.isNotEmpty()) {
true -> Date.fromIso(readDateText)
else -> 0
}
val messageRecipientObject = MessageRecipientFull(
profileId,
-1,
-1,
readDate,
messageObject.id
)
messageRecipientObject.fullName = profile.accountNameLong ?: profile.studentNameLong
messageRecipientList.add(messageRecipientObject)
}
TYPE_SENT -> {
message.select("receivers ArrayItem").forEach { receiver ->
val receiverFirstName = receiver.select("firstName").text().fixName()
val receiverLastName = receiver.select("lastName").text().fixName()
val receiverLoginId = receiver.select("receiverId").text()
val teacher = data.teacherList.singleOrNull { it.name == receiverFirstName && it.surname == receiverLastName }
val receiverId = teacher?.id ?: -1
teacher?.loginId = receiverLoginId
val readDateText = message.select("readed").text()
val readDate = when (readDateText.isNotEmpty()) {
true -> Date.fromIso(readDateText)
else -> 0
}
val messageRecipientObject = MessageRecipientFull(
profileId,
receiverId,
-1,
readDate,
messageObject.id
)
messageRecipientObject.fullName = "$receiverFirstName $receiverLastName"
messageRecipientList.add(messageRecipientObject)
}
}
}
if (!messageObject.seen) {
data.messageMetadataList.add(Metadata(
messageObject.profileId,
Metadata.TYPE_MESSAGE,
messageObject.id,
true,
true,
messageObject.addedDate
))
}
messageObject.recipients = messageRecipientList
data.messageRecipientList.addAll(messageRecipientList)
data.messageList.add(messageObject)
EventBus.getDefault().postSticky(MessageGetEvent(messageObject))
onSuccess()
}
} ?: onSuccess()}
}

View File

@ -3,6 +3,7 @@ package pl.szczodrzynski.edziennik.api.v2.librus.firstlogin
import org.greenrobot.eventbus.EventBus import org.greenrobot.eventbus.EventBus
import pl.szczodrzynski.edziennik.* import pl.szczodrzynski.edziennik.*
import pl.szczodrzynski.edziennik.api.v2.ERROR_NO_STUDENTS_IN_ACCOUNT import pl.szczodrzynski.edziennik.api.v2.ERROR_NO_STUDENTS_IN_ACCOUNT
import pl.szczodrzynski.edziennik.api.v2.FAKE_LIBRUS_ACCOUNTS
import pl.szczodrzynski.edziennik.api.v2.LIBRUS_ACCOUNTS_URL import pl.szczodrzynski.edziennik.api.v2.LIBRUS_ACCOUNTS_URL
import pl.szczodrzynski.edziennik.api.v2.LOGIN_MODE_LIBRUS_EMAIL import pl.szczodrzynski.edziennik.api.v2.LOGIN_MODE_LIBRUS_EMAIL
import pl.szczodrzynski.edziennik.api.v2.events.FirstLoginFinishedEvent import pl.szczodrzynski.edziennik.api.v2.events.FirstLoginFinishedEvent
@ -29,7 +30,7 @@ class LibrusFirstLogin(val data: DataLibrus, val onSuccess: () -> Unit) {
if (data.loginStore.mode == LOGIN_MODE_LIBRUS_EMAIL) { if (data.loginStore.mode == LOGIN_MODE_LIBRUS_EMAIL) {
// email login: use Portal for account list // email login: use Portal for account list
LibrusLoginPortal(data) { LibrusLoginPortal(data) {
portal.portalGet(TAG, LIBRUS_ACCOUNTS_URL) { json, response -> portal.portalGet(TAG, if (data.fakeLogin) FAKE_LIBRUS_ACCOUNTS else LIBRUS_ACCOUNTS_URL) { json, response ->
val accounts = json.getJsonArray("accounts") val accounts = json.getJsonArray("accounts")
if (accounts == null || accounts.size() < 1) { if (accounts == null || accounts.size() < 1) {

View File

@ -7,14 +7,15 @@ import im.wangchao.mhttp.Response
import im.wangchao.mhttp.body.MediaTypeUtils import im.wangchao.mhttp.body.MediaTypeUtils
import im.wangchao.mhttp.callback.JsonCallbackHandler import im.wangchao.mhttp.callback.JsonCallbackHandler
import im.wangchao.mhttp.callback.TextCallbackHandler import im.wangchao.mhttp.callback.TextCallbackHandler
import pl.szczodrzynski.edziennik.*
import pl.szczodrzynski.edziennik.api.v2.* import pl.szczodrzynski.edziennik.api.v2.*
import pl.szczodrzynski.edziennik.api.v2.librus.DataLibrus import pl.szczodrzynski.edziennik.api.v2.librus.DataLibrus
import pl.szczodrzynski.edziennik.api.v2.models.ApiError import pl.szczodrzynski.edziennik.api.v2.models.ApiError
import pl.szczodrzynski.edziennik.utils.Utils import pl.szczodrzynski.edziennik.getInt
import pl.szczodrzynski.edziennik.getString
import pl.szczodrzynski.edziennik.getUnixDate
import pl.szczodrzynski.edziennik.utils.Utils.d import pl.szczodrzynski.edziennik.utils.Utils.d
import java.net.HttpURLConnection.HTTP_UNAUTHORIZED import java.net.HttpURLConnection.HTTP_UNAUTHORIZED
import java.util.ArrayList import java.util.*
import java.util.regex.Pattern import java.util.regex.Pattern
class LibrusLoginPortal(val data: DataLibrus, val onSuccess: () -> Unit) { class LibrusLoginPortal(val data: DataLibrus, val onSuccess: () -> Unit) {
@ -42,7 +43,7 @@ class LibrusLoginPortal(val data: DataLibrus, val onSuccess: () -> Unit) {
} }
else { else {
data.app.cookieJar.clearForDomain("portal.librus.pl") data.app.cookieJar.clearForDomain("portal.librus.pl")
authorize(LIBRUS_AUTHORIZE_URL) authorize(if (data.fakeLogin) FAKE_LIBRUS_AUTHORIZE else LIBRUS_AUTHORIZE_URL)
} }
}} }}
@ -86,10 +87,10 @@ class LibrusLoginPortal(val data: DataLibrus, val onSuccess: () -> Unit) {
} }
private fun login(csrfToken: String) { private fun login(csrfToken: String) {
d(TAG, "Request: Librus/Login/Portal - $LIBRUS_LOGIN_URL") d(TAG, "Request: Librus/Login/Portal - ${if (data.fakeLogin) FAKE_LIBRUS_LOGIN else LIBRUS_LOGIN_URL}")
Request.builder() Request.builder()
.url(LIBRUS_LOGIN_URL) .url(if (data.fakeLogin) FAKE_LIBRUS_LOGIN else LIBRUS_LOGIN_URL)
.userAgent(LIBRUS_USER_AGENT) .userAgent(LIBRUS_USER_AGENT)
.addParameter("email", data.portalEmail) .addParameter("email", data.portalEmail)
.addParameter("password", data.portalPassword) .addParameter("password", data.portalPassword)
@ -135,7 +136,7 @@ class LibrusLoginPortal(val data: DataLibrus, val onSuccess: () -> Unit) {
private var refreshTokenFailed = false private var refreshTokenFailed = false
private fun accessToken(code: String?, refreshToken: String?) { private fun accessToken(code: String?, refreshToken: String?) {
d(TAG, "Request: Librus/Login/Portal - $LIBRUS_TOKEN_URL") d(TAG, "Request: Librus/Login/Portal - ${if (data.fakeLogin) FAKE_LIBRUS_TOKEN else LIBRUS_TOKEN_URL}")
val onSuccess = { json: JsonObject, response: Response? -> val onSuccess = { json: JsonObject, response: Response? ->
data.portalAccessToken = json.getString("access_token") data.portalAccessToken = json.getString("access_token")
@ -204,7 +205,7 @@ class LibrusLoginPortal(val data: DataLibrus, val onSuccess: () -> Unit) {
} }
Request.builder() Request.builder()
.url(LIBRUS_TOKEN_URL) .url(if (data.fakeLogin) FAKE_LIBRUS_TOKEN else LIBRUS_TOKEN_URL)
.userAgent(LIBRUS_USER_AGENT) .userAgent(LIBRUS_USER_AGENT)
.addParams(params) .addParams(params)
.post() .post()

View File

@ -43,7 +43,7 @@ class SynergiaTokenExtractor(override val data: DataLibrus, val onSuccess: () ->
val accountLogin = data.apiLogin ?: return false val accountLogin = data.apiLogin ?: return false
data.portalAccessToken ?: return false data.portalAccessToken ?: return false
d(TAG, "Request: Librus/SynergiaTokenExtractor - $LIBRUS_ACCOUNT_URL$accountLogin") d(TAG, "Request: Librus/SynergiaTokenExtractor - ${if (data.fakeLogin) FAKE_LIBRUS_ACCOUNT else LIBRUS_ACCOUNT_URL}$accountLogin")
val onSuccess = { json: JsonObject, response: Response? -> val onSuccess = { json: JsonObject, response: Response? ->
// synergiaAccount is executed when a synergia token needs a refresh // synergiaAccount is executed when a synergia token needs a refresh
@ -67,7 +67,7 @@ class SynergiaTokenExtractor(override val data: DataLibrus, val onSuccess: () ->
} }
} }
portalGet(TAG, LIBRUS_ACCOUNT_URL+accountLogin, onSuccess = onSuccess) portalGet(TAG, (if (data.fakeLogin) FAKE_LIBRUS_ACCOUNT else LIBRUS_ACCOUNT_URL)+accountLogin, onSuccess = onSuccess)
return true return true
} }
} }

View File

@ -4,6 +4,7 @@
package pl.szczodrzynski.edziennik.api.v2.mobidziennik package pl.szczodrzynski.edziennik.api.v2.mobidziennik
import com.google.gson.JsonObject
import pl.szczodrzynski.edziennik.App import pl.szczodrzynski.edziennik.App
import pl.szczodrzynski.edziennik.api.v2.CODE_INTERNAL_LIBRUS_ACCOUNT_410 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.EdziennikCallback
@ -15,6 +16,7 @@ import pl.szczodrzynski.edziennik.api.v2.mobidziennikLoginMethods
import pl.szczodrzynski.edziennik.api.v2.models.ApiError import pl.szczodrzynski.edziennik.api.v2.models.ApiError
import pl.szczodrzynski.edziennik.api.v2.prepare import pl.szczodrzynski.edziennik.api.v2.prepare
import pl.szczodrzynski.edziennik.data.db.modules.login.LoginStore 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.Profile
import pl.szczodrzynski.edziennik.utils.Utils.d import pl.szczodrzynski.edziennik.utils.Utils.d
@ -48,7 +50,8 @@ class Mobidziennik(val app: App, val profile: Profile?, val loginStore: LoginSto
|_| |_| |_|\___| /_/ \_\_|\__, |\___/|_| |_|\__|_| |_|_| |_| |_| |_| |_| |_|\___| /_/ \_\_|\__, |\___/|_| |_|\__|_| |_|_| |_| |_|
__/ | __/ |
|__*/ |__*/
override fun sync(featureIds: List<Int>, viewId: Int?) { override fun sync(featureIds: List<Int>, viewId: Int?, arguments: JsonObject?) {
data.arguments = arguments
data.prepare(mobidziennikLoginMethods, MobidziennikFeatures, featureIds, viewId) data.prepare(mobidziennikLoginMethods, MobidziennikFeatures, featureIds, viewId)
d(TAG, "LoginMethod IDs: ${data.targetLoginMethodIds}") d(TAG, "LoginMethod IDs: ${data.targetLoginMethodIds}")
d(TAG, "Endpoint IDs: ${data.targetEndpointIds}") d(TAG, "Endpoint IDs: ${data.targetEndpointIds}")
@ -59,7 +62,7 @@ class Mobidziennik(val app: App, val profile: Profile?, val loginStore: LoginSto
} }
} }
override fun getMessage(messageId: Int) { override fun getMessage(message: MessageFull) {
} }

View File

@ -21,10 +21,10 @@ const val ENDPOINT_MOBIDZIENNIK_API2_MAIN = 3000
val MobidziennikFeatures = listOf( val MobidziennikFeatures = listOf(
// always synced // always synced
/*Feature(LOGIN_TYPE_MOBIDZIENNIK, FEATURE_ALWAYS_NEEDED, listOf( Feature(LOGIN_TYPE_MOBIDZIENNIK, FEATURE_ALWAYS_NEEDED, listOf(
ENDPOINT_MOBIDZIENNIK_API_MAIN to LOGIN_METHOD_MOBIDZIENNIK_WEB, ENDPOINT_MOBIDZIENNIK_API_MAIN to LOGIN_METHOD_MOBIDZIENNIK_WEB,
ENDPOINT_MOBIDZIENNIK_WEB_ACCOUNT_EMAIL to LOGIN_METHOD_MOBIDZIENNIK_WEB ENDPOINT_MOBIDZIENNIK_WEB_ACCOUNT_EMAIL to LOGIN_METHOD_MOBIDZIENNIK_WEB
), listOf(LOGIN_METHOD_MOBIDZIENNIK_WEB)),*/ ), listOf(LOGIN_METHOD_MOBIDZIENNIK_WEB)), // TODO divide features into separate view IDs (all with API_MAIN)
// push config // push config
/*Feature(LOGIN_TYPE_MOBIDZIENNIK, FEATURE_PUSH_CONFIG, listOf( /*Feature(LOGIN_TYPE_MOBIDZIENNIK, FEATURE_PUSH_CONFIG, listOf(

View File

@ -4,7 +4,6 @@
package pl.szczodrzynski.edziennik.api.v2.mobidziennik.data.api 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.mobidziennik.DataMobidziennik
import pl.szczodrzynski.edziennik.data.db.modules.teams.Team import pl.szczodrzynski.edziennik.data.db.modules.teams.Team
import pl.szczodrzynski.edziennik.getById import pl.szczodrzynski.edziennik.getById
@ -25,7 +24,7 @@ class MobidziennikApiTeams(val data: DataMobidziennik, tableTeams: List<String>?
val teacherId = cols[4].toLongOrNull() ?: -1 val teacherId = cols[4].toLongOrNull() ?: -1
val teamObject = Team( val teamObject = Team(
profileId, data.profileId,
id, id,
name, name,
type, type,

View File

@ -5,15 +5,100 @@
package pl.szczodrzynski.edziennik.api.v2.mobidziennik.data.api package pl.szczodrzynski.edziennik.api.v2.mobidziennik.data.api
import pl.szczodrzynski.edziennik.api.v2.mobidziennik.DataMobidziennik import pl.szczodrzynski.edziennik.api.v2.mobidziennik.DataMobidziennik
import pl.szczodrzynski.edziennik.data.db.modules.lessons.Lesson import pl.szczodrzynski.edziennik.api.v2.models.DataRemoveModel
import pl.szczodrzynski.edziennik.data.db.modules.lessons.LessonChange
import pl.szczodrzynski.edziennik.data.db.modules.metadata.Metadata import pl.szczodrzynski.edziennik.data.db.modules.metadata.Metadata
import pl.szczodrzynski.edziennik.data.db.modules.timetable.Lesson
import pl.szczodrzynski.edziennik.fixName import pl.szczodrzynski.edziennik.fixName
import pl.szczodrzynski.edziennik.singleOrNull import pl.szczodrzynski.edziennik.singleOrNull
import pl.szczodrzynski.edziennik.utils.models.Date
import pl.szczodrzynski.edziennik.utils.models.Time
class MobidziennikApiTimetable(val data: DataMobidziennik, rows: List<String>) { class MobidziennikApiTimetable(val data: DataMobidziennik, rows: List<String>) {
init { init {
for (lessonStr in rows) { val lessons = rows.filterNot { it.isEmpty() }.map { it.split("|") }
val dataStart = Date.getToday()
val dataEnd = dataStart.clone().stepForward(0, 0, 7 + (6 - dataStart.weekDay))
data.toRemove.add(DataRemoveModel.Timetable.between(dataStart.clone(), dataEnd))
val dataDays = mutableListOf<Int>()
while (dataStart <= dataEnd) {
dataDays += dataStart.value
dataStart.stepForward(0, 0, 1)
}
for (lesson in lessons) {
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)
val subjectId = data.subjectList.singleOrNull { it.longName == lesson[5] }?.id ?: -1
val teacherId = data.teacherList.singleOrNull { it.fullNameLastFirst == (lesson[7]+" "+lesson[6]).fixName() }?.id ?: -1
val teamId = data.teamList.singleOrNull { it.name == lesson[8]+lesson[9] }?.id ?: -1
val classroom = lesson[11]
Lesson(data.profileId, id).also {
when (lesson[1]) {
"plan_lekcji", "lekcja" -> {
it.type = Lesson.TYPE_NORMAL
it.date = date
it.startTime = startTime
it.endTime = endTime
it.subjectId = subjectId
it.teacherId = teacherId
it.teamId = teamId
it.classroom = classroom
}
"lekcja_odwolana" -> {
it.type = Lesson.TYPE_CANCELLED
it.date = date
it.startTime = startTime
it.endTime = endTime
it.oldSubjectId = subjectId
//it.oldTeacherId = teacherId
it.oldTeamId = teamId
//it.oldClassroom = classroom
}
"zastepstwo" -> {
it.type = Lesson.TYPE_CHANGE
it.date = date
it.startTime = startTime
it.endTime = endTime
it.subjectId = subjectId
it.teacherId = teacherId
it.teamId = teamId
it.classroom = classroom
}
}
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,
System.currentTimeMillis()
))
}
data.lessonNewList += it
}
}
for (day in dataDays) {
val lessonDate = Date.fromValue(day)
data.lessonNewList += Lesson(data.profileId, lessonDate.value.toLong()).apply {
type = Lesson.TYPE_NO_LESSONS
date = lessonDate
}
}
/*for (lessonStr in rows) {
if (lessonStr.isNotEmpty()) { if (lessonStr.isNotEmpty()) {
val lesson = lessonStr.split("|") val lesson = lessonStr.split("|")
@ -76,9 +161,9 @@ class MobidziennikApiTimetable(val data: DataMobidziennik, rows: List<String>) {
if (originalLesson == null) { if (originalLesson == null) {
// original lesson doesn't exist, save a new addition // original lesson doesn't exist, save a new addition
// TODO // TODO
/*if (!RegisterLessonChange.existsAddition(app.profile, registerLessonChange)) { *//*if (!RegisterLessonChange.existsAddition(app.profile, registerLessonChange)) {
app.profile.timetable.addLessonAddition(registerLessonChange); app.profile.timetable.addLessonAddition(registerLessonChange);
}*/ }*//*
} else { } else {
// original lesson exists, so we need to compare them // original lesson exists, so we need to compare them
if (!lessonChange.matches(originalLesson)) { if (!lessonChange.matches(originalLesson)) {
@ -108,6 +193,6 @@ class MobidziennikApiTimetable(val data: DataMobidziennik, rows: List<String>) {
} }
} }
} }
} }*/
} }
} }

View File

@ -8,9 +8,7 @@ import org.jsoup.Jsoup
import pl.szczodrzynski.edziennik.DAY import pl.szczodrzynski.edziennik.DAY
import pl.szczodrzynski.edziennik.api.v2.mobidziennik.DataMobidziennik import pl.szczodrzynski.edziennik.api.v2.mobidziennik.DataMobidziennik
import pl.szczodrzynski.edziennik.api.v2.mobidziennik.ENDPOINT_MOBIDZIENNIK_WEB_MESSAGES_ALL import pl.szczodrzynski.edziennik.api.v2.mobidziennik.ENDPOINT_MOBIDZIENNIK_WEB_MESSAGES_ALL
import pl.szczodrzynski.edziennik.api.v2.mobidziennik.ENDPOINT_MOBIDZIENNIK_WEB_MESSAGES_INBOX
import pl.szczodrzynski.edziennik.api.v2.mobidziennik.data.MobidziennikWeb import pl.szczodrzynski.edziennik.api.v2.mobidziennik.data.MobidziennikWeb
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
import pl.szczodrzynski.edziennik.data.db.modules.messages.Message.TYPE_RECEIVED import pl.szczodrzynski.edziennik.data.db.modules.messages.Message.TYPE_RECEIVED
import pl.szczodrzynski.edziennik.data.db.modules.messages.Message.TYPE_SENT import pl.szczodrzynski.edziennik.data.db.modules.messages.Message.TYPE_SENT
@ -79,7 +77,7 @@ class MobidziennikWebMessagesAll(override val data: DataMobidziennik,
-1 -1
) )
data.messageList.add(message) data.messageIgnoreList.add(message)
data.metadataList.add(Metadata(profileId, Metadata.TYPE_MESSAGE, message.id, true, true, addedDate)) data.metadataList.add(Metadata(profileId, Metadata.TYPE_MESSAGE, message.id, true, true, addedDate))
} }

View File

@ -67,7 +67,7 @@ class MobidziennikWebMessagesInbox(override val data: DataMobidziennik,
if (hasAttachments) if (hasAttachments)
message.setHasAttachments() message.setHasAttachments()
data.messageList.add(message) data.messageIgnoreList.add(message)
data.messageMetadataList.add( data.messageMetadataList.add(
Metadata( Metadata(
profileId, profileId,

View File

@ -4,6 +4,7 @@
package pl.szczodrzynski.edziennik.api.v2.models package pl.szczodrzynski.edziennik.api.v2.models
import android.content.Context
import com.google.gson.JsonObject import com.google.gson.JsonObject
import im.wangchao.mhttp.Request import im.wangchao.mhttp.Request
import im.wangchao.mhttp.Response 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 { override fun toString(): String {
return "ApiError(tag='$tag', errorCode=$errorCode, profileId=$profileId, throwable=$throwable, apiResponse=$apiResponse, request=$request, response=$response, isCritical=$isCritical)" return "ApiError(tag='$tag', errorCode=$errorCode, profileId=$profileId, throwable=$throwable, apiResponse=$apiResponse, request=$request, response=$response, isCritical=$isCritical)"
} }

View File

@ -60,6 +60,8 @@ open class Data(val app: App, val profile: Profile?, val loginStore: LoginStore)
val profileId val profileId
get() = profile?.id ?: -1 get() = profile?.id ?: -1
var arguments: JsonObject? = null
/** /**
* A callback passed to all [Feature]s and [LoginMethod]s * A callback passed to all [Feature]s and [LoginMethod]s
*/ */
@ -133,23 +135,20 @@ open class Data(val app: App, val profile: Profile?, val loginStore: LoginStore)
mTeamClass = value mTeamClass = value
} }
var lessonsToRemove: DataRemoveModel? = null var toRemove = mutableListOf<DataRemoveModel>()
val lessonList = mutableListOf<Lesson>() val lessonList = mutableListOf<Lesson>()
val lessonChangeList = mutableListOf<LessonChange>() val lessonChangeList = mutableListOf<LessonChange>()
val lessonNewList = mutableListOf<pl.szczodrzynski.edziennik.data.db.modules.timetable.Lesson>()
var gradesToRemove: DataRemoveModel? = null
val gradeList = mutableListOf<Grade>() val gradeList = mutableListOf<Grade>()
var eventsToRemove: DataRemoveModel? = null
val eventList = mutableListOf<Event>() val eventList = mutableListOf<Event>()
var noticesToRemove: DataRemoveModel? = null
val noticeList = mutableListOf<Notice>() val noticeList = mutableListOf<Notice>()
var attendancesToRemove: DataRemoveModel? = null
val attendanceList = mutableListOf<Attendance>() val attendanceList = mutableListOf<Attendance>()
var announcementsToRemove: DataRemoveModel? = null
val announcementList = mutableListOf<Announcement>() val announcementList = mutableListOf<Announcement>()
val luckyNumberList = mutableListOf<LuckyNumber>() val luckyNumberList = mutableListOf<LuckyNumber>()
@ -157,6 +156,7 @@ open class Data(val app: App, val profile: Profile?, val loginStore: LoginStore)
val teacherAbsenceList = mutableListOf<TeacherAbsence>() val teacherAbsenceList = mutableListOf<TeacherAbsence>()
val messageList = mutableListOf<Message>() val messageList = mutableListOf<Message>()
val messageIgnoreList = mutableListOf<Message>()
val messageRecipientList = mutableListOf<MessageRecipient>() val messageRecipientList = mutableListOf<MessageRecipient>()
val messageRecipientIgnoreList = mutableListOf<MessageRecipient>() val messageRecipientIgnoreList = mutableListOf<MessageRecipient>()
@ -166,6 +166,9 @@ open class Data(val app: App, val profile: Profile?, val loginStore: LoginStore)
val db: AppDb by lazy { app.db } val db: AppDb by lazy { app.db }
init { init {
if (App.devMode) {
fakeLogin = loginStore.hasLoginData("fakeLogin")
}
clear() clear()
if (profile != null) { if (profile != null) {
endpointTimers = db.endpointTimerDao().getAllNow(profile.id).toMutableList() endpointTimers = db.endpointTimerDao().getAllNow(profile.id).toMutableList()
@ -180,6 +183,7 @@ open class Data(val app: App, val profile: Profile?, val loginStore: LoginStore)
fun clear() { fun clear() {
loginMethods.clear() loginMethods.clear()
toRemove.clear()
endpointTimers.clear() endpointTimers.clear()
teacherList.clear() teacherList.clear()
subjectList.clear() subjectList.clear()
@ -195,13 +199,14 @@ open class Data(val app: App, val profile: Profile?, val loginStore: LoginStore)
lessonList.clear() lessonList.clear()
lessonChangeList.clear() lessonChangeList.clear()
lessonNewList.clear()
gradeList.clear() gradeList.clear()
noticeList.clear() noticeList.clear()
attendanceList.clear() attendanceList.clear()
announcementList.clear() announcementList.clear()
luckyNumberList.clear() luckyNumberList.clear()
teacherAbsenceList.clear() teacherAbsenceList.clear()
messageList.clear() messageIgnoreList.clear()
messageRecipientList.clear() messageRecipientList.clear()
messageRecipientIgnoreList.clear() messageRecipientIgnoreList.clear()
metadataList.clear() metadataList.clear()
@ -248,6 +253,7 @@ open class Data(val app: App, val profile: Profile?, val loginStore: LoginStore)
app.profile.loginStoreData = loginStore.data app.profile.loginStoreData = loginStore.data
} }
// always present and not empty, during every sync
db.endpointTimerDao().addAll(endpointTimers) db.endpointTimerDao().addAll(endpointTimers)
db.teacherDao().clear(profileId) db.teacherDao().clear(profileId)
db.teacherDao().addAll(teacherList.values()) db.teacherDao().addAll(teacherList.values())
@ -260,6 +266,7 @@ open class Data(val app: App, val profile: Profile?, val loginStore: LoginStore)
db.gradeCategoryDao().clear(profileId) db.gradeCategoryDao().clear(profileId)
db.gradeCategoryDao().addAll(gradeCategories.values()) db.gradeCategoryDao().addAll(gradeCategories.values())
// may be empty - extracted from DB on demand, by an endpoint
if (classrooms.size > 0) if (classrooms.size > 0)
db.classroomDao().addAll(classrooms.values()) db.classroomDao().addAll(classrooms.values())
if (attendanceTypes.size > 0) if (attendanceTypes.size > 0)
@ -271,17 +278,29 @@ open class Data(val app: App, val profile: Profile?, val loginStore: LoginStore)
if (teacherAbsenceTypes.size > 0) if (teacherAbsenceTypes.size > 0)
db.teacherAbsenceTypeDao().addAll(teacherAbsenceTypes.values()) db.teacherAbsenceTypeDao().addAll(teacherAbsenceTypes.values())
gradesToRemove?.let { it -> // clear DB with DataRemoveModels added by endpoints
it.removeAll?.let { _ -> db.gradeDao().clear(profileId) } for (model in toRemove) {
it.removeSemester?.let { semester -> db.gradeDao().clearForSemester(profileId, semester) } when (model) {
is DataRemoveModel.Timetable -> model.commit(profileId, db.timetableDao())
is DataRemoveModel.Grades -> model.commit(profileId, db.gradeDao())
}
} }
if (metadataList.isNotEmpty())
db.metadataDao().addAllIgnore(metadataList)
if (messageMetadataList.isNotEmpty())
db.metadataDao().setSeen(messageMetadataList)
// not extracted from DB - always new data
if (lessonList.isNotEmpty()) { if (lessonList.isNotEmpty()) {
db.lessonDao().clear(profile.id) db.lessonDao().clear(profile.id)
db.lessonDao().addAll(lessonList) db.lessonDao().addAll(lessonList)
} }
if (lessonChangeList.isNotEmpty()) if (lessonChangeList.isNotEmpty())
db.lessonChangeDao().addAll(lessonChangeList) db.lessonChangeDao().addAll(lessonChangeList)
if (lessonNewList.isNotEmpty()) {
db.timetableDao() += lessonNewList
}
if (gradeList.isNotEmpty()) { if (gradeList.isNotEmpty()) {
db.gradeDao().addAll(gradeList) db.gradeDao().addAll(gradeList)
} }
@ -303,15 +322,13 @@ open class Data(val app: App, val profile: Profile?, val loginStore: LoginStore)
db.teacherAbsenceDao().addAll(teacherAbsenceList) db.teacherAbsenceDao().addAll(teacherAbsenceList)
if (messageList.isNotEmpty()) if (messageList.isNotEmpty())
db.messageDao().addAllIgnore(messageList) db.messageDao().addAll(messageList)
if (messageIgnoreList.isNotEmpty())
db.messageDao().addAllIgnore(messageIgnoreList)
if (messageRecipientList.isNotEmpty()) if (messageRecipientList.isNotEmpty())
db.messageRecipientDao().addAll(messageRecipientList) db.messageRecipientDao().addAll(messageRecipientList)
if (messageRecipientIgnoreList.isNotEmpty()) if (messageRecipientIgnoreList.isNotEmpty())
db.messageRecipientDao().addAllIgnore(messageRecipientIgnoreList) db.messageRecipientDao().addAllIgnore(messageRecipientIgnoreList)
if (metadataList.isNotEmpty())
db.metadataDao().addAllIgnore(metadataList)
if (messageMetadataList.isNotEmpty())
db.metadataDao().setSeen(messageMetadataList)
} }
fun notifyAndSyncEvents(onSuccess: () -> Unit) { fun notifyAndSyncEvents(onSuccess: () -> Unit) {

View File

@ -4,28 +4,37 @@
package pl.szczodrzynski.edziennik.api.v2.models package pl.szczodrzynski.edziennik.api.v2.models
import pl.szczodrzynski.edziennik.data.db.modules.grades.GradeDao
import pl.szczodrzynski.edziennik.data.db.modules.timetable.TimetableDao
import pl.szczodrzynski.edziennik.utils.models.Date import pl.szczodrzynski.edziennik.utils.models.Date
class DataRemoveModel { open class DataRemoveModel {
var removeAll: Boolean? = null class Timetable(private val dateFrom: Date?, private val dateTo: Date?) : DataRemoveModel() {
var removeSemester: Int? = null companion object {
var removeDateFrom: Date? = null fun from(dateFrom: Date) = Timetable(dateFrom, null)
var removeDateTo: Date? = null fun to(dateTo: Date) = Timetable(null, dateTo)
fun between(dateFrom: Date, dateTo: Date) = Timetable(dateFrom, dateTo)
constructor() { }
this.removeAll = true fun commit(profileId: Int, dao: TimetableDao) {
if (dateFrom != null && dateTo != null) {
dao.clearBetweenDates(profileId, dateFrom, dateTo)
}
else {
dateFrom?.let { dateFrom -> dao.clearFromDate(profileId, dateFrom) }
dateTo?.let { dateTo -> dao.clearToDate(profileId, dateTo) }
}
}
} }
class Grades(val all: Boolean, val semester: Int?) : DataRemoveModel() {
constructor(semester: Int) { companion object {
this.removeSemester = semester fun all() = Grades(true, null)
} fun semester(semester: Int) = Grades(false, semester)
}
constructor(dateFrom: Date?, dateTo: Date) { fun commit(profileId: Int, dao: GradeDao) {
this.removeDateFrom = dateFrom if (all) {
this.removeDateTo = dateTo dao.clear(profileId)
} }
semester?.let { dao.clearForSemester(profileId, it) }
constructor(dateFrom: Date) { }
this.removeDateFrom = dateFrom
} }
} }

View File

@ -4,6 +4,7 @@
package pl.szczodrzynski.edziennik.api.v2.template package pl.szczodrzynski.edziennik.api.v2.template
import com.google.gson.JsonObject
import pl.szczodrzynski.edziennik.App import pl.szczodrzynski.edziennik.App
import pl.szczodrzynski.edziennik.api.v2.CODE_INTERNAL_LIBRUS_ACCOUNT_410 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.EdziennikCallback
@ -15,6 +16,7 @@ import pl.szczodrzynski.edziennik.api.v2.template.firstlogin.TemplateFirstLogin
import pl.szczodrzynski.edziennik.api.v2.template.login.TemplateLogin import pl.szczodrzynski.edziennik.api.v2.template.login.TemplateLogin
import pl.szczodrzynski.edziennik.api.v2.templateLoginMethods import pl.szczodrzynski.edziennik.api.v2.templateLoginMethods
import pl.szczodrzynski.edziennik.data.db.modules.login.LoginStore 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.Profile
import pl.szczodrzynski.edziennik.utils.Utils.d import pl.szczodrzynski.edziennik.utils.Utils.d
@ -48,7 +50,8 @@ class Template(val app: App, val profile: Profile?, val loginStore: LoginStore,
|_| |_| |_|\___| /_/ \_\_|\__, |\___/|_| |_|\__|_| |_|_| |_| |_| |_| |_| |_|\___| /_/ \_\_|\__, |\___/|_| |_|\__|_| |_|_| |_| |_|
__/ | __/ |
|__*/ |__*/
override fun sync(featureIds: List<Int>, viewId: Int?) { override fun sync(featureIds: List<Int>, viewId: Int?, arguments: JsonObject?) {
data.arguments = arguments
data.prepare(templateLoginMethods, TemplateFeatures, featureIds, viewId) data.prepare(templateLoginMethods, TemplateFeatures, featureIds, viewId)
d(TAG, "LoginMethod IDs: ${data.targetLoginMethodIds}") d(TAG, "LoginMethod IDs: ${data.targetLoginMethodIds}")
d(TAG, "Endpoint IDs: ${data.targetEndpointIds}") d(TAG, "Endpoint IDs: ${data.targetEndpointIds}")
@ -59,7 +62,7 @@ class Template(val app: App, val profile: Profile?, val loginStore: LoginStore,
} }
} }
override fun getMessage(messageId: Int) { override fun getMessage(message: MessageFull) {
} }

View File

@ -4,6 +4,7 @@
package pl.szczodrzynski.edziennik.api.v2.vulcan package pl.szczodrzynski.edziennik.api.v2.vulcan
import com.google.gson.JsonObject
import pl.szczodrzynski.edziennik.App import pl.szczodrzynski.edziennik.App
import pl.szczodrzynski.edziennik.api.v2.CODE_INTERNAL_LIBRUS_ACCOUNT_410 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.EdziennikCallback
@ -11,10 +12,13 @@ import pl.szczodrzynski.edziennik.api.v2.interfaces.EdziennikInterface
import pl.szczodrzynski.edziennik.api.v2.models.ApiError import pl.szczodrzynski.edziennik.api.v2.models.ApiError
import pl.szczodrzynski.edziennik.api.v2.prepare import pl.szczodrzynski.edziennik.api.v2.prepare
import pl.szczodrzynski.edziennik.api.v2.vulcan.data.VulcanData 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.firstlogin.VulcanFirstLogin
import pl.szczodrzynski.edziennik.api.v2.vulcan.login.VulcanLogin 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.api.v2.vulcanLoginMethods
import pl.szczodrzynski.edziennik.data.db.modules.login.LoginStore 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.Profile
import pl.szczodrzynski.edziennik.utils.Utils.d import pl.szczodrzynski.edziennik.utils.Utils.d
@ -48,7 +52,8 @@ class Vulcan(val app: App, val profile: Profile?, val loginStore: LoginStore, va
|_| |_| |_|\___| /_/ \_\_|\__, |\___/|_| |_|\__|_| |_|_| |_| |_| |_| |_| |_|\___| /_/ \_\_|\__, |\___/|_| |_|\__|_| |_|_| |_| |_|
__/ | __/ |
|__*/ |__*/
override fun sync(featureIds: List<Int>, viewId: Int?) { override fun sync(featureIds: List<Int>, viewId: Int?, arguments: JsonObject?) {
data.arguments = arguments
data.prepare(vulcanLoginMethods, VulcanFeatures, featureIds, viewId) data.prepare(vulcanLoginMethods, VulcanFeatures, featureIds, viewId)
d(TAG, "LoginMethod IDs: ${data.targetLoginMethodIds}") d(TAG, "LoginMethod IDs: ${data.targetLoginMethodIds}")
d(TAG, "Endpoint IDs: ${data.targetEndpointIds}") d(TAG, "Endpoint IDs: ${data.targetEndpointIds}")
@ -59,8 +64,12 @@ class Vulcan(val app: App, val profile: Profile?, val loginStore: LoginStore, va
} }
} }
override fun getMessage(messageId: Int) { override fun getMessage(message: MessageFull) {
VulcanLoginApi(data) {
VulcanApiMessagesChangeStatus(data, message) {
completed()
}
}
} }
override fun markAllAnnouncementsAsRead() { override fun markAllAnnouncementsAsRead() {

View File

@ -64,6 +64,10 @@ class VulcanData(val data: DataVulcan, val onSuccess: () -> Unit) {
data.startProgress(R.string.edziennik_progress_endpoint_attendance) data.startProgress(R.string.edziennik_progress_endpoint_attendance)
VulcanApiAttendance(data, onSuccess) VulcanApiAttendance(data, onSuccess)
} }
ENDPOINT_VULCAN_API_TIMETABLE -> {
data.startProgress(R.string.edziennik_progress_endpoint_timetable)
VulcanApiTimetable(data, onSuccess)
}
ENDPOINT_VULCAN_API_MESSAGES_INBOX -> { ENDPOINT_VULCAN_API_MESSAGES_INBOX -> {
data.startProgress(R.string.edziennik_progress_endpoint_messages_inbox) data.startProgress(R.string.edziennik_progress_endpoint_messages_inbox)
VulcanApiMessagesInbox(data, onSuccess) VulcanApiMessagesInbox(data, onSuccess)

View File

@ -55,7 +55,7 @@ class VulcanApiAttendance(override val data: DataVulcan, val onSuccess: () -> Un
lessonSemester, lessonSemester,
attendance.getString("PrzedmiotNazwa") + attendanceCategory.name.let { " - $it" }, attendance.getString("PrzedmiotNazwa") + attendanceCategory.name.let { " - $it" },
lessonDate, lessonDate,
data.lessonRanges.get(attendance.getInt("IdPoraLekcji") ?: 0)?.startTime, data.lessonRanges.get(attendance.getInt("Numer") ?: 0)?.startTime,
type) type)
data.attendanceList.add(attendanceObject) data.attendanceList.add(attendanceObject)

View File

@ -35,7 +35,7 @@ class VulcanApiDictionaries(override val data: DataVulcan, val onSuccess: () ->
elements?.getJsonArray("KategorieUwag")?.forEach { saveNoticeType(it.asJsonObject) } elements?.getJsonArray("KategorieUwag")?.forEach { saveNoticeType(it.asJsonObject) }
elements?.getJsonArray("KategorieFrekwencji")?.forEach { saveAttendanceType(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() onSuccess()
} }
} }
@ -73,7 +73,7 @@ class VulcanApiDictionaries(override val data: DataVulcan, val onSuccess: () ->
} }
private fun saveLessonRange(lessonRange: JsonObject) { 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 startTime = lessonRange.getString("PoczatekTekst")?.let { Time.fromH_m(it) } ?: return
val endTime = lessonRange.getString("KoniecTekst")?.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 Attendance.TYPE_ABSENT_EXCUSED
else else
Attendance.TYPE_ABSENT Attendance.TYPE_ABSENT
} } else {
else {
val belated = attendanceType.getBoolean("Spoznienie") ?: false val belated = attendanceType.getBoolean("Spoznienie") ?: false
val released = attendanceType.getBoolean("Zwolnienie") ?: false val released = attendanceType.getBoolean("Zwolnienie") ?: false
val present = attendanceType.getBoolean("Obecnosc") ?: true val present = attendanceType.getBoolean("Obecnosc") ?: true

View File

@ -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()
}
}
}
}

View File

@ -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.Message.TYPE_RECEIVED
import pl.szczodrzynski.edziennik.data.db.modules.messages.MessageRecipient 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.metadata.Metadata
import pl.szczodrzynski.edziennik.data.db.modules.teachers.Teacher
import pl.szczodrzynski.edziennik.utils.Utils
import pl.szczodrzynski.edziennik.utils.models.Date import pl.szczodrzynski.edziennik.utils.models.Date
class VulcanApiMessagesInbox(override val data: DataVulcan, val onSuccess: () -> Unit) : VulcanApi(data) { 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 senderLoginId = message.getString("NadawcaId") ?: return@forEach
val senderId = data.teacherList 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 } val sentDate = message.getLong("DataWyslaniaUnixEpoch")?.let { it * 1000 }
?: -1 ?: -1
@ -68,7 +85,7 @@ class VulcanApiMessagesInbox(override val data: DataVulcan, val onSuccess: () ->
id id
) )
data.messageList.add(messageObject) data.messageIgnoreList.add(messageObject)
data.messageRecipientList.add(messageRecipientObject) data.messageRecipientList.add(messageRecipientObject)
data.metadataList.add(Metadata( data.metadataList.add(Metadata(
profileId, profileId,

View File

@ -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.Message.TYPE_SENT
import pl.szczodrzynski.edziennik.data.db.modules.messages.MessageRecipient 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.metadata.Metadata
import pl.szczodrzynski.edziennik.data.db.modules.teachers.Teacher
import pl.szczodrzynski.edziennik.utils.Utils
import pl.szczodrzynski.edziennik.utils.models.Date import pl.szczodrzynski.edziennik.utils.models.Date
class VulcanApiMessagesSent(override val data: DataVulcan, val onSuccess: () -> Unit) : VulcanApi(data) { class VulcanApiMessagesSent(override val data: DataVulcan, val onSuccess: () -> Unit) : VulcanApi(data) {
@ -23,11 +25,11 @@ class VulcanApiMessagesSent(override val data: DataVulcan, val onSuccess: () ->
init { init {
data.profile?.also { profile -> data.profile?.also { profile ->
val startDate: String = when (profile.empty) { val startDate: Long = when (profile.empty) {
true -> profile.getSemesterStart(profile.currentSemester).stringY_m_d true -> profile.getSemesterStart(profile.currentSemester).inUnix
else -> Date.getToday().stepForward(0, -1, 0).stringY_m_d 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( apiGet(TAG, VULCAN_API_ENDPOINT_MESSAGES_SENT, parameters = mapOf(
"DataPoczatkowa" to startDate, "DataPoczatkowa" to startDate,
@ -43,23 +45,27 @@ class VulcanApiMessagesSent(override val data: DataVulcan, val onSuccess: () ->
val unreadBy = message.getInt("Nieprzeczytane") ?: 0 val unreadBy = message.getInt("Nieprzeczytane") ?: 0
val sentDate = message.getLong("DataWyslaniaUnixEpoch")?.let { it * 1000 } ?: -1 val sentDate = message.getLong("DataWyslaniaUnixEpoch")?.let { it * 1000 } ?: -1
val messageObject = Message(
profileId,
id,
subject,
body,
TYPE_SENT,
-1,
-1
)
message.getJsonArray("Adresaci")?.asJsonObjectList() message.getJsonArray("Adresaci")?.asJsonObjectList()
?.forEachIndexed { _, recipient -> ?.onEach { receiver ->
val recipientLoginId = recipient.getString("LoginId") val receiverLoginId = receiver.getString("LoginId")
?: return@forEachIndexed ?: return@onEach
val recipientId = data.teacherList.singleOrNull { it.loginId == recipientLoginId }?.id val receiverId = data.teacherList.singleOrNull { it.loginId == receiverLoginId }?.id
?: return@forEachIndexed ?: {
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) { val readDate: Long = when (readBy) {
0 -> 0 0 -> 0
@ -71,7 +77,7 @@ class VulcanApiMessagesSent(override val data: DataVulcan, val onSuccess: () ->
val messageRecipientObject = MessageRecipient( val messageRecipientObject = MessageRecipient(
profileId, profileId,
recipientId, receiverId,
-1, -1,
readDate, readDate,
id id
@ -80,7 +86,17 @@ class VulcanApiMessagesSent(override val data: DataVulcan, val onSuccess: () ->
data.messageRecipientList.add(messageRecipientObject) data.messageRecipientList.add(messageRecipientObject)
} }
data.messageList.add(messageObject) val messageObject = Message(
profileId,
id,
subject,
body,
TYPE_SENT,
-1,
-1
)
data.messageIgnoreList.add(messageObject)
data.metadataList.add(Metadata( data.metadataList.add(Metadata(
profileId, profileId,
Metadata.TYPE_MESSAGE, Metadata.TYPE_MESSAGE,

View File

@ -0,0 +1,186 @@
/*
* Copyright (c) Kacper Ziubryniewicz 2019-11-13
*/
package pl.szczodrzynski.edziennik.api.v2.vulcan.data.api
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.timetable.Lesson
import pl.szczodrzynski.edziennik.utils.Utils.crc16
import pl.szczodrzynski.edziennik.utils.Utils.d
import pl.szczodrzynski.edziennik.utils.models.Date
class VulcanApiTimetable(override val data: DataVulcan, val onSuccess: () -> Unit) : VulcanApi(data) {
companion object {
const val TAG = "VulcanApiTimetable"
}
init {
data.profile?.also { profile ->
val currentWeekStart = Date.getToday().let { it.stepForward(0, 0, -it.weekDay) }
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: MutableSet<Int> = mutableSetOf()
val lessons: MutableList<Lesson> = mutableListOf()
json.getJsonArray("Data")?.asJsonObjectList()?.forEach { lesson ->
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 teamId = data.studentClassId.toLong()
val classroom = lesson.getString("Classroom")
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 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 id = lessonDate.combineWith(startTime) / 6L * 10L + (lesson.hashCode() and 0xFFFF)
val lessonObject = Lesson(profileId, id).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
}
}
}
}
if (type != Lesson.TYPE_NORMAL) {
data.metadataList.add(Metadata(
profileId,
Metadata.TYPE_LESSON_CHANGE,
id,
profile.empty,
profile.empty,
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()
}
}
}
}

View File

@ -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);
}

View File

@ -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);
}

View File

@ -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);
}

View File

@ -1,5 +0,0 @@
package pl.szczodrzynski.edziennik.data.api.interfaces;
public interface LoginCallback {
void onSuccess();
}

View File

@ -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);
}

View File

@ -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);
}

View File

@ -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);
}

View File

@ -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);
}

View File

@ -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);
}

View File

@ -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?)

View File

@ -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?)

View File

@ -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 {
}
}

View File

@ -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 {
}
}

View File

@ -72,6 +72,7 @@ import pl.szczodrzynski.edziennik.data.db.modules.teachers.TeacherAbsenceTypeDao
import pl.szczodrzynski.edziennik.data.db.modules.teachers.TeacherDao; import pl.szczodrzynski.edziennik.data.db.modules.teachers.TeacherDao;
import pl.szczodrzynski.edziennik.data.db.modules.teams.Team; import pl.szczodrzynski.edziennik.data.db.modules.teams.Team;
import pl.szczodrzynski.edziennik.data.db.modules.teams.TeamDao; import pl.szczodrzynski.edziennik.data.db.modules.teams.TeamDao;
import pl.szczodrzynski.edziennik.data.db.modules.timetable.TimetableDao;
import pl.szczodrzynski.edziennik.utils.models.Date; import pl.szczodrzynski.edziennik.utils.models.Date;
@Database(entities = { @Database(entities = {
@ -103,7 +104,8 @@ import pl.szczodrzynski.edziennik.utils.models.Date;
Classroom.class, Classroom.class,
NoticeType.class, NoticeType.class,
AttendanceType.class, AttendanceType.class,
Metadata.class}, version = 63) pl.szczodrzynski.edziennik.data.db.modules.timetable.Lesson.class,
Metadata.class}, version = 64)
@TypeConverters({ @TypeConverters({
ConverterTime.class, ConverterTime.class,
ConverterDate.class, ConverterDate.class,
@ -141,6 +143,7 @@ public abstract class AppDb extends RoomDatabase {
public abstract ClassroomDao classroomDao(); public abstract ClassroomDao classroomDao();
public abstract NoticeTypeDao noticeTypeDao(); public abstract NoticeTypeDao noticeTypeDao();
public abstract AttendanceTypeDao attendanceTypeDao(); public abstract AttendanceTypeDao attendanceTypeDao();
public abstract TimetableDao timetableDao();
public abstract MetadataDao metadataDao(); public abstract MetadataDao metadataDao();
private static volatile AppDb INSTANCE; private static volatile AppDb INSTANCE;
@ -729,6 +732,37 @@ public abstract class AppDb extends RoomDatabase {
database.execSQL("ALTER TABLE profiles ADD COLUMN studentSchoolYear TEXT DEFAULT NULL"); database.execSQL("ALTER TABLE profiles ADD COLUMN studentSchoolYear TEXT DEFAULT NULL");
} }
}; };
private static final Migration MIGRATION_63_64 = new Migration(63, 64) {
@Override
public void migrate(@NonNull SupportSQLiteDatabase database) {
//database.execSQL("ALTER TABLE lessons RENAME TO lessonsOld;");
database.execSQL("CREATE TABLE timetable (" +
"profileId INTEGER NOT NULL," +
"id INTEGER NOT NULL," +
"type INTEGER NOT NULL," +
"date TEXT DEFAULT NULL," +
"lessonNumber INTEGER DEFAULT NULL," +
"startTime TEXT DEFAULT NULL," +
"endTime TEXT DEFAULT NULL," +
"subjectId INTEGER DEFAULT NULL," +
"teacherId INTEGER DEFAULT NULL," +
"teamId INTEGER DEFAULT NULL," +
"classroom TEXT DEFAULT NULL," +
"oldDate TEXT DEFAULT NULL," +
"oldLessonNumber INTEGER DEFAULT NULL," +
"oldStartTime TEXT DEFAULT NULL," +
"oldEndTime TEXT DEFAULT NULL," +
"oldSubjectId INTEGER DEFAULT NULL," +
"oldTeacherId INTEGER DEFAULT NULL," +
"oldTeamId INTEGER DEFAULT NULL," +
"oldClassroom TEXT DEFAULT NULL," +
"PRIMARY KEY(id));");
database.execSQL("CREATE INDEX index_lessons_profileId_type_date ON timetable (profileId, type, date);");
database.execSQL("CREATE INDEX index_lessons_profileId_type_oldDate ON timetable (profileId, type, oldDate);");
}
};
public static AppDb getDatabase(final Context context) { public static AppDb getDatabase(final Context context) {
@ -789,7 +823,8 @@ public abstract class AppDb extends RoomDatabase {
MIGRATION_59_60, MIGRATION_59_60,
MIGRATION_60_61, MIGRATION_60_61,
MIGRATION_61_62, MIGRATION_61_62,
MIGRATION_62_63 MIGRATION_62_63,
MIGRATION_63_64
) )
.allowMainThreadQueries() .allowMainThreadQueries()
//.fallbackToDestructiveMigration() //.fallbackToDestructiveMigration()

View File

@ -5,6 +5,7 @@ import androidx.room.ColumnInfo;
import androidx.room.Entity; import androidx.room.Entity;
import androidx.room.Ignore; import androidx.room.Ignore;
import androidx.room.Index; import androidx.room.Index;
import pl.szczodrzynski.edziennik.utils.models.Date; import pl.szczodrzynski.edziennik.utils.models.Date;
import pl.szczodrzynski.edziennik.utils.models.Time; import pl.szczodrzynski.edziennik.utils.models.Time;
@ -85,6 +86,23 @@ public class Event {
this.teamId = teamId; this.teamId = teamId;
} }
@Override
public Event clone() throws CloneNotSupportedException {
return new Event(
profileId,
id,
eventDate.clone(),
startTime == null ? null : startTime.clone(),
topic,
color,
type,
addedManually,
subjectId,
teacherId,
teamId
);
}
@Override @Override
public String toString() { public String toString() {
return "Event{" + return "Event{" +

View File

@ -1,19 +1,19 @@
package pl.szczodrzynski.edziennik.data.db.modules.messages; package pl.szczodrzynski.edziennik.data.db.modules.messages;
import java.util.ArrayList;
import java.util.List;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.room.ColumnInfo; import androidx.room.ColumnInfo;
import androidx.room.Entity; import androidx.room.Entity;
import androidx.room.Ignore; import androidx.room.Ignore;
import androidx.room.Index; import androidx.room.Index;
import java.util.ArrayList;
import java.util.List;
@Entity(tableName = "messages", @Entity(tableName = "messages",
primaryKeys = {"profileId", "messageId"}, primaryKeys = {"profileId", "messageId"},
indices = {@Index(value = {"profileId"})}) indices = {@Index(value = {"profileId"})})
public class Message { public class Message {
int profileId; public int profileId;
@ColumnInfo(name = "messageId") @ColumnInfo(name = "messageId")
public long id; public long id;

View File

@ -1,7 +1,6 @@
package pl.szczodrzynski.edziennik.data.db.modules.messages; package pl.szczodrzynski.edziennik.data.db.modules.messages;
import java.util.List; import androidx.annotation.Nullable;
import androidx.lifecycle.LiveData; import androidx.lifecycle.LiveData;
import androidx.room.Dao; import androidx.room.Dao;
import androidx.room.Insert; import androidx.room.Insert;
@ -11,6 +10,8 @@ import androidx.room.RawQuery;
import androidx.sqlite.db.SimpleSQLiteQuery; import androidx.sqlite.db.SimpleSQLiteQuery;
import androidx.sqlite.db.SupportSQLiteQuery; import androidx.sqlite.db.SupportSQLiteQuery;
import java.util.List;
import pl.szczodrzynski.edziennik.data.db.modules.metadata.Metadata; import pl.szczodrzynski.edziennik.data.db.modules.metadata.Metadata;
import static pl.szczodrzynski.edziennik.data.db.modules.messages.Message.TYPE_DELETED; import static pl.szczodrzynski.edziennik.data.db.modules.messages.Message.TYPE_DELETED;
@ -23,6 +24,9 @@ public abstract class MessageDao {
@Insert(onConflict = OnConflictStrategy.REPLACE) @Insert(onConflict = OnConflictStrategy.REPLACE)
public abstract long add(Message message); public abstract long add(Message message);
@Insert(onConflict = OnConflictStrategy.REPLACE)
public abstract void addAll(List<Message> messageList);
@Insert(onConflict = OnConflictStrategy.IGNORE) @Insert(onConflict = OnConflictStrategy.IGNORE)
public abstract void addAllIgnore(List<Message> messageList); public abstract void addAllIgnore(List<Message> messageList);
@ -56,6 +60,7 @@ public abstract class MessageDao {
"ORDER BY addedDate DESC")); "ORDER BY addedDate DESC"));
} }
@Nullable
public MessageFull getById(int profileId, long messageId) { public MessageFull getById(int profileId, long messageId) {
return getOneNow(new SimpleSQLiteQuery("SELECT \n" + return getOneNow(new SimpleSQLiteQuery("SELECT \n" +
"*, \n" + "*, \n" +

View File

@ -1,13 +1,15 @@
package pl.szczodrzynski.edziennik.data.db.modules.messages; package pl.szczodrzynski.edziennik.data.db.modules.messages;
import androidx.annotation.Nullable;
import androidx.room.Ignore;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import androidx.room.Ignore;
public class MessageFull extends Message { public class MessageFull extends Message {
public String senderFullName = null; public String senderFullName = null;
@Ignore @Ignore
@Nullable
public List<MessageRecipientFull> recipients = null; public List<MessageRecipientFull> recipients = null;
public MessageFull addRecipient(MessageRecipientFull recipient) { public MessageFull addRecipient(MessageRecipientFull recipient) {

View File

@ -4,12 +4,20 @@
package pl.szczodrzynski.edziennik.data.db.modules.timetable package pl.szczodrzynski.edziennik.data.db.modules.timetable
import androidx.room.ColumnInfo import androidx.room.Entity
import androidx.room.Index
import androidx.room.PrimaryKey
import pl.szczodrzynski.edziennik.utils.models.Date import pl.szczodrzynski.edziennik.utils.models.Date
import pl.szczodrzynski.edziennik.utils.models.Time import pl.szczodrzynski.edziennik.utils.models.Time
open class Lesson(val profileId: Int) { @Entity(tableName = "timetable",
indices = [
Index(value = ["profileId", "type", "date"]),
Index(value = ["profileId", "type", "oldDate"])
])
open class Lesson(val profileId: Int, @PrimaryKey val id: Long) {
companion object { companion object {
const val TYPE_NO_LESSONS = -1
const val TYPE_NORMAL = 0 const val TYPE_NORMAL = 0
const val TYPE_CANCELLED = 1 const val TYPE_CANCELLED = 1
const val TYPE_CHANGE = 2 const val TYPE_CHANGE = 2
@ -17,15 +25,14 @@ open class Lesson(val profileId: Int) {
const val TYPE_SHIFTED_TARGET = 4 /* target lesson */ const val TYPE_SHIFTED_TARGET = 4 /* target lesson */
} }
@ColumnInfo(name = "lessonType")
var type: Int = TYPE_NORMAL var type: Int = TYPE_NORMAL
var date: Date? = null var date: Date? = null
var lessonNumber: Int? = null var lessonNumber: Int? = null
var startTime: Time? = null var startTime: Time? = null
var endTime: Time? = null var endTime: Time? = null
var teacherId: Long? = null
var subjectId: Long? = null var subjectId: Long? = null
var teacherId: Long? = null
var teamId: Long? = null var teamId: Long? = null
var classroom: String? = null var classroom: String? = null
@ -33,8 +40,58 @@ open class Lesson(val profileId: Int) {
var oldLessonNumber: Int? = null var oldLessonNumber: Int? = null
var oldStartTime: Time? = null var oldStartTime: Time? = null
var oldEndTime: Time? = null var oldEndTime: Time? = null
var oldTeacherId: Long? = null
var oldSubjectId: Long? = null var oldSubjectId: Long? = null
var oldTeacherId: Long? = null
var oldTeamId: Long? = null var oldTeamId: Long? = null
var oldClassroom: String? = null var oldClassroom: String? = null
}
override fun toString(): String {
return "Lesson(profileId=$profileId, " +
"id=$id, " +
"type=$type, " +
"date=$date, " +
"lessonNumber=$lessonNumber, " +
"startTime=$startTime, " +
"endTime=$endTime, " +
"subjectId=$subjectId, " +
"teacherId=$teacherId, " +
"teamId=$teamId, " +
"classroom=$classroom, " +
"oldDate=$oldDate, " +
"oldLessonNumber=$oldLessonNumber, " +
"oldStartTime=$oldStartTime, " +
"oldEndTime=$oldEndTime, " +
"oldSubjectId=$oldSubjectId, " +
"oldTeacherId=$oldTeacherId, " +
"oldTeamId=$oldTeamId, " +
"oldClassroom=$oldClassroom)"
}
}
/*
DROP TABLE lessons;
DROP TABLE lessonChanges;
CREATE TABLE lessons (
profileId INTEGER NOT NULL,
type INTEGER NOT NULL,
date TEXT DEFAULT NULL,
lessonNumber INTEGER DEFAULT NULL,
startTime TEXT DEFAULT NULL,
endTime TEXT DEFAULT NULL,
teacherId INTEGER DEFAULT NULL,
subjectId INTEGER DEFAULT NULL,
teamId INTEGER DEFAULT NULL,
classroom TEXT DEFAULT NULL,
oldDate TEXT DEFAULT NULL,
oldLessonNumber INTEGER DEFAULT NULL,
oldStartTime TEXT DEFAULT NULL,
oldEndTime TEXT DEFAULT NULL,
oldTeacherId INTEGER DEFAULT NULL,
oldSubjectId INTEGER DEFAULT NULL,
oldTeamId INTEGER DEFAULT NULL,
oldClassroom TEXT DEFAULT NULL,
PRIMARY KEY(profileId)
);
*/

View File

@ -0,0 +1,88 @@
package pl.szczodrzynski.edziennik.data.db.modules.timetable
import pl.szczodrzynski.edziennik.utils.models.Date
import pl.szczodrzynski.edziennik.utils.models.Time
class LessonFull(profileId: Int, id: Long) : Lesson(profileId, id) {
var subjectName: String? = null
var teacherName: String? = null
var teamName: String? = null
var oldSubjectName: String? = null
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)
return oldEndTime
return endTime ?: oldEndTime
}
val displaySubjectName: String?
get() {
if (type == TYPE_SHIFTED_SOURCE)
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)
return oldTeamName
return teamName ?: oldTeamName
}
val displayClassroom: String?
get() {
if (type == TYPE_SHIFTED_SOURCE)
return oldClassroom
return classroom ?: oldClassroom
}
val displayTeamId: Long?
get() {
if (type == TYPE_SHIFTED_SOURCE)
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)
return oldTeacherId
return teacherId ?: oldTeacherId
}
// metadata
var seen: Boolean = false
var notified: Boolean = false
var addedDate: Long = 0
}

View File

@ -0,0 +1,76 @@
package pl.szczodrzynski.edziennik.data.db.modules.timetable
import androidx.lifecycle.LiveData
import androidx.room.Dao
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
import pl.szczodrzynski.edziennik.data.db.modules.metadata.Metadata
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>)
@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)")
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)")
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)")
fun clearBetweenDates(profileId: Int, dateFrom: Date, dateTo: Date)
@Query("""
$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("""
$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>
@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 timetable.profileId = :profileId AND timetable.id = :lessonId
ORDER BY id, type
""")
fun getByIdNow(profileId: Int, lessonId: Long) : LessonFull?
}

View File

@ -112,7 +112,7 @@ public class MyFirebaseMessagingService extends FirebaseMessagingService {
app.notifier.postAll(profile); app.notifier.postAll(profile);
app.saveConfig("notifications");*/ app.saveConfig("notifications");*/
d(TAG, "Syncing profile " + profile.getId()); d(TAG, "Syncing profile " + profile.getId());
EdziennikTask.Companion.syncProfile(profile.getId(), null).enqueue(app); EdziennikTask.Companion.syncProfile(profile.getId(), null, null).enqueue(app);
} else { } else {
/*app.notifier.add(new Notification(app.getContext(), remoteMessage.getData().get("message")) /*app.notifier.add(new Notification(app.getContext(), remoteMessage.getData().get("message"))
.withProfileData(profile.id, profile.name) .withProfileData(profile.id, profile.name)
@ -123,7 +123,7 @@ public class MyFirebaseMessagingService extends FirebaseMessagingService {
app.notifier.postAll(profile); app.notifier.postAll(profile);
app.saveConfig("notifications");*/ app.saveConfig("notifications");*/
d(TAG, "Syncing profile " + profile.getId()); d(TAG, "Syncing profile " + profile.getId());
EdziennikTask.Companion.syncProfile(profile.getId(), null).enqueue(app); EdziennikTask.Companion.syncProfile(profile.getId(), null, null).enqueue(app);
} }
} }
}); });

View File

@ -0,0 +1,47 @@
/*
* Copyright (c) Kuba Szczodrzyński 2019-11-12.
*/
package pl.szczodrzynski.edziennik.ui.dialogs.event
import android.app.Activity
import androidx.appcompat.app.AlertDialog
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import pl.szczodrzynski.edziennik.App
import pl.szczodrzynski.edziennik.R
import pl.szczodrzynski.edziennik.utils.models.Date
import pl.szczodrzynski.edziennik.utils.models.Time
class EventAddTypeDialog(
val activity: Activity,
val profileId: Int,
val date: Date? = null,
val time: Time? = null
) {
companion object {
private const val TAG = "EventAddTypeDialog"
}
private lateinit var dialog: AlertDialog
init { run {
dialog = MaterialAlertDialogBuilder(activity)
.setItems(R.array.main_menu_add_options) { dialog, which ->
dialog.dismiss()
EventManualDialog(activity, profileId)
.show(
activity.application as App,
null,
date,
time,
when (which) {
1 -> EventManualDialog.DIALOG_HOMEWORK
else -> EventManualDialog.DIALOG_EVENT
}
)
}
.setNegativeButton(R.string.cancel) { dialog, _ -> dialog.dismiss() }
.show()
}}
}

View File

@ -36,7 +36,6 @@ import java.util.List;
import pl.szczodrzynski.edziennik.App; import pl.szczodrzynski.edziennik.App;
import pl.szczodrzynski.edziennik.MainActivity; import pl.szczodrzynski.edziennik.MainActivity;
import pl.szczodrzynski.edziennik.R; 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.Event;
import pl.szczodrzynski.edziennik.data.db.modules.events.EventFull; import pl.szczodrzynski.edziennik.data.db.modules.events.EventFull;
import pl.szczodrzynski.edziennik.data.db.modules.events.EventType; 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 pl.szczodrzynski.edziennik.utils.models.Week;
import static pl.szczodrzynski.edziennik.App.APP_URL; 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.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_HOMEWORK;
import static pl.szczodrzynski.edziennik.data.db.modules.events.Event.TYPE_UNDEFINED; import static pl.szczodrzynski.edziennik.data.db.modules.events.Event.TYPE_UNDEFINED;
@ -374,7 +372,8 @@ public class EventManualDialog {
} }
} catch (Exception e) { } catch (Exception e) {
activity.runOnUiThread(() -> { 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);
}); });
} }
}); });

View File

@ -0,0 +1,384 @@
/*
* Copyright (c) Kuba Szczodrzyński 2019-11-12.
*/
package pl.szczodrzynski.edziennik.ui.dialogs.event
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 kotlinx.coroutines.*
import pl.szczodrzynski.edziennik.*
import pl.szczodrzynski.edziennik.data.db.modules.events.Event
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.TextInputDropDown
import pl.szczodrzynski.edziennik.utils.models.Date
import pl.szczodrzynski.edziennik.utils.models.Time
import pl.szczodrzynski.edziennik.utils.models.Week
import kotlin.coroutines.CoroutineContext
class EventManualV2Dialog(
val activity: AppCompatActivity,
val profileId: Int,
val defaultLesson: LessonFull? = null,
val defaultDate: Date? = null,
val defaultTime: Time? = null,
val defaultType: Int? = null,
val editingEvent: Event? = null,
val onShowListener: ((tag: String) -> Unit)? = null,
val onDismissListener: ((tag: String) -> Unit)? = null
) : CoroutineScope {
companion object {
private const val TAG = "EventManualDialog"
}
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: DialogEventManualV2Binding
private lateinit var dialog: AlertDialog
private lateinit var event: Event
private var defaultLoaded = false
init { 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 ->
event.profileId = profileId
/*defaultDate?.let {
event.eventDate = it
b.date = it
}
defaultTime?.let {
event.startTime = it
b.time = it
}
defaultType?.let {
event.type = it
}*/
}
loadLists()
}}
private fun loadLists() { launch {
val deferred = async(Dispatchers.Default) {
// get the team list
val teams = app.db.teamDao().getAllNow(profileId)
b.teamDropdown.clear()
b.teamDropdown += TextInputDropDown.Item(
-1,
activity.getString(R.string.dialog_event_manual_no_team),
""
)
b.teamDropdown += teams.map { TextInputDropDown.Item(it.id, it.name, tag = it) }
// get the subject list
val subjects = app.db.subjectDao().getAllNow(profileId)
b.subjectDropdown.clear()
b.subjectDropdown += TextInputDropDown.Item(
-1,
activity.getString(R.string.dialog_event_manual_no_subject),
""
)
b.subjectDropdown += subjects.map { TextInputDropDown.Item(it.id, it.longName, tag = it) }
// get the teacher list
val teachers = app.db.teacherDao().getAllNow(profileId)
b.teacherDropdown.clear()
b.teacherDropdown += TextInputDropDown.Item(
-1,
activity.getString(R.string.dialog_event_manual_no_teacher),
""
)
b.teacherDropdown += teachers.map { TextInputDropDown.Item(it.id, it.fullName, tag = it) }
}
deferred.await()
b.teamDropdown.isEnabled = true
b.subjectDropdown.isEnabled = true
b.teacherDropdown.isEnabled = true
// copy IDs from event being edited
editingEvent?.let {
b.teamDropdown.select(it.teamId)
b.subjectDropdown.select(it.subjectId)
b.teacherDropdown.select(it.teacherId)
}
// copy IDs from the LessonFull
defaultLesson?.let {
b.teamDropdown.select(it.displayTeamId)
b.subjectDropdown.select(it.displaySubjectId)
b.teacherDropdown.select(it.displayTeacherId)
}
loadDates()
}}
private fun loadDates() { launch {
val date = Date.getToday()
val today = date.value
var weekDay = date.weekDay
val deferred = async(Dispatchers.Default) {
val dates = mutableListOf<TextInputDropDown.Item>()
// item choosing the next lesson of specific subject
b.subjectDropdown.selected?.let {
if (it.tag is Subject) {
dates += TextInputDropDown.Item(
-it.id,
activity.getString(R.string.dialog_event_manual_date_next_lesson, it.tag.longName)
)
}
}
// TODAY
dates += TextInputDropDown.Item(
date.value.toLong(),
activity.getString(R.string.dialog_event_manual_date_today, date.formattedString),
tag = date.clone()
)
// TOMORROW
if (weekDay < 4) {
date.stepForward(0, 0, 1)
weekDay++
dates += TextInputDropDown.Item(
date.value.toLong(),
activity.getString(R.string.dialog_event_manual_date_tomorrow, date.formattedString),
tag = date.clone()
)
}
// REMAINING SCHOOL DAYS OF THE CURRENT WEEK
while (weekDay < 4) {
date.stepForward(0, 0, 1) // step one day forward
weekDay++
dates += TextInputDropDown.Item(
date.value.toLong(),
activity.getString(R.string.dialog_event_manual_date_this_week, Week.getFullDayName(weekDay), date.formattedString),
tag = date.clone()
)
}
// go to next week Monday
date.stepForward(0, 0, -weekDay + 7)
weekDay = 0
// ALL SCHOOL DAYS OF THE NEXT WEEK
while (weekDay < 4) {
dates += TextInputDropDown.Item(
date.value.toLong(),
activity.getString(R.string.dialog_event_manual_date_next_week, Week.getFullDayName(weekDay), date.formattedString),
tag = date.clone()
)
date.stepForward(0, 0, 1) // step one day forward
weekDay++
}
dates += TextInputDropDown.Item(
-1L,
activity.getString(R.string.dialog_event_manual_date_other)
)
dates
}
val dates = deferred.await()
b.dateDropdown.clear().append(dates)
editingEvent?.let {
b.dateDropdown.select(it.eventDate.value.toLong())
}
defaultLesson?.let {
b.dateDropdown.select(it.displayDate?.value?.toLong())
}
if (b.dateDropdown.selected == null) {
b.dateDropdown.select(today.toLong())
}
b.dateDropdown.isEnabled = true
b.dateDropdown.setOnChangeListener { item ->
when {
// next lesson with specified subject
item.id < -1 -> {
app.db.timetableDao().getNextWithSubject(profileId, Date.getToday(), -item.id).observeOnce(activity, Observer {
val lessonDate = it?.displayDate ?: return@Observer
b.dateDropdown.selected = 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)
}
defaultLoaded = false
loadHours()
})
return@setOnChangeListener false
}
// custom date
item.id == -1L -> {
MaterialDatePicker.Builder
.datePicker()
.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(
dateSelected.value.toLong(),
dateSelected.formattedString,
tag = dateSelected
)
b.dateDropdown.updateText()
loadHours()
}
show(this@EventManualV2Dialog.activity.supportFragmentManager, "MaterialDatePicker")
}
return@setOnChangeListener false
}
// a specific date
else -> {
b.dateDropdown.select(item)
loadHours()
}
}
return@setOnChangeListener true
}
loadHours()
}}
private fun loadHours() {
b.timeDropdown.isEnabled = false
// get the selected date
val date = b.dateDropdown.selectedId?.let { Date.fromValue(it.toInt()) } ?: return
// get all lessons for selected date
app.db.timetableDao().getForDate(profileId, date).observeOnce(activity, Observer { lessons ->
val hours = mutableListOf<TextInputDropDown.Item>()
// add All day time choice
hours += TextInputDropDown.Item(
0L,
activity.getString(R.string.dialog_event_manual_all_day)
)
lessons.forEach { lesson ->
if (lesson.type == Lesson.TYPE_NO_LESSONS) {
// indicate there are no lessons this day
hours += TextInputDropDown.Item(
-2L,
activity.getString(R.string.dialog_event_manual_no_lessons)
)
return@forEach
}
// create the lesson caption
val text = listOfNotEmpty(
lesson.displayStartTime?.stringHM ?: "",
lesson.displaySubjectName?.let {
when {
lesson.type == Lesson.TYPE_CANCELLED -> it.asStrikethroughSpannable()
lesson.type != Lesson.TYPE_NORMAL -> it.asItalicSpannable()
else -> it
}
} ?: ""
)
// add an item with LessonFull as the tag
hours += TextInputDropDown.Item(
lesson.displayStartTime?.value?.toLong() ?: -1,
text.concat(" "),
tag = lesson
)
}
b.timeDropdown.clear().append(hours)
if (defaultLoaded) {
b.timeDropdown.deselect()
// select the TEAM_CLASS if possible
b.teamDropdown.items.singleOrNull {
it.tag is Team && it.tag.type == Team.TYPE_CLASS
}?.let {
b.teamDropdown.select(it)
} ?: b.teamDropdown.deselect()
// clear subject, teacher selection
b.subjectDropdown.deselect()
b.teacherDropdown.deselect()
}
else {
editingEvent?.let {
b.timeDropdown.select(it.startTime?.value?.toLong())
}
defaultLesson?.let {
b.timeDropdown.select(it.displayStartTime?.value?.toLong())
}
}
defaultLoaded = true
b.timeDropdown.isEnabled = true
// 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
}
// selected a specific lesson
else -> {
if (item.tag is LessonFull) {
// update team, subject, teacher dropdowns,
// using the LessonFull from item tag
b.teamDropdown.deselect()
b.subjectDropdown.deselect()
b.teacherDropdown.deselect()
item.tag.displayTeamId?.let {
b.teamDropdown.select(it)
}
item.tag.displaySubjectId?.let {
b.subjectDropdown.select(it)
}
item.tag.displayTeacherId?.let {
b.teacherDropdown.select(it)
}
}
}
}
return@setOnChangeListener true
}
})
}
private fun saveEvent() {
}
}

View File

@ -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()
}}
}

View File

@ -0,0 +1,108 @@
/*
* 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())
}
activity.swipeRefreshLayout.isRefreshing = true
EdziennikTask.syncProfile(
App.profileId,
selectedViewIds
).enqueue(activity)
}
.setNegativeButton(R.string.cancel) { _, _ ->
dialog.dismiss()
}
.show()
}}
}

View File

@ -0,0 +1,158 @@
/*
* Copyright (c) Kuba Szczodrzyński 2019-11-11.
*/
package pl.szczodrzynski.edziennik.ui.dialogs.timetable
import android.content.Intent
import android.view.View
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import pl.szczodrzynski.edziennik.R
import pl.szczodrzynski.edziennik.data.db.modules.timetable.Lesson
import pl.szczodrzynski.edziennik.data.db.modules.timetable.LessonFull
import pl.szczodrzynski.edziennik.databinding.DialogLessonDetailsBinding
import pl.szczodrzynski.edziennik.setText
import pl.szczodrzynski.edziennik.ui.dialogs.event.EventManualV2Dialog
import pl.szczodrzynski.edziennik.ui.modules.timetable.v2.TimetableFragment
import pl.szczodrzynski.edziennik.utils.models.Date
import pl.szczodrzynski.edziennik.utils.models.Week
class LessonDetailsDialog(
val activity: AppCompatActivity,
val lesson: LessonFull,
val onShowListener: ((tag: String) -> Unit)? = null,
val onDismissListener: ((tag: String) -> Unit)? = null
) {
companion object {
private const val TAG = "LessonDetailsDialog"
}
private lateinit var b: DialogLessonDetailsBinding
private lateinit var dialog: AlertDialog
init { run {
onShowListener?.invoke(TAG)
b = DialogLessonDetailsBinding.inflate(activity.layoutInflater)
dialog = MaterialAlertDialogBuilder(activity)
.setView(b.root)
.setPositiveButton(R.string.close) { dialog, _ ->
dialog.dismiss()
}
.setNeutralButton(R.string.add) { dialog, _ ->
EventManualV2Dialog(
activity,
lesson.profileId,
lesson,
onShowListener = onShowListener,
onDismissListener = onDismissListener
)
/*MaterialAlertDialogBuilder(activity)
.setItems(R.array.main_menu_add_options) { dialog2, which ->
dialog2.dismiss()
EventManualDialog(activity, lesson.profileId)
.show(
activity.application as App,
null,
lesson.displayDate,
lesson.displayStartTime,
when (which) {
1 -> EventManualDialog.DIALOG_HOMEWORK
else -> EventManualDialog.DIALOG_EVENT
}
)
}
.setNegativeButton(R.string.cancel) { dialog2, _ -> dialog2.dismiss() }
.show()*/
}
.setOnDismissListener {
onDismissListener?.invoke(TAG)
}
.show()
update()
}}
private fun update() {
b.lesson = lesson
val lessonDate = lesson.displayDate ?: return
b.lessonDate.text = Week.getFullDayName(lessonDate.weekDay) + ", " + lessonDate.formattedString
if (lesson.type >= Lesson.TYPE_SHIFTED_SOURCE) {
b.shiftedLayout.visibility = View.VISIBLE
var otherLessonDate: Date? = null
when (lesson.type) {
Lesson.TYPE_SHIFTED_SOURCE -> {
otherLessonDate = lesson.date
when {
lesson.date != lesson.oldDate -> b.shiftedText.setText(
R.string.timetable_lesson_shifted_other_day,
lesson.date?.stringY_m_d ?: "?",
lesson.startTime?.stringHM ?: "?"
)
lesson.startTime != lesson.oldStartTime -> b.shiftedText.setText(
R.string.timetable_lesson_shifted_same_day,
lesson.startTime?.stringHM ?: "?"
)
else -> b.shiftedText.setText(R.string.timetable_lesson_shifted)
}
}
Lesson.TYPE_SHIFTED_TARGET -> {
otherLessonDate = lesson.oldDate
when {
lesson.date != lesson.oldDate -> b.shiftedText.setText(
R.string.timetable_lesson_shifted_from_other_day,
lesson.oldDate?.stringY_m_d ?: "?",
lesson.oldStartTime?.stringHM ?: "?"
)
lesson.startTime != lesson.oldStartTime -> b.shiftedText.setText(
R.string.timetable_lesson_shifted_from_same_day,
lesson.oldStartTime?.stringHM ?: "?"
)
else -> b.shiftedText.setText(R.string.timetable_lesson_shifted_from)
}
}
}
b.shiftedGoTo.setOnClickListener {
dialog.dismiss()
val dateStr = otherLessonDate?.stringY_m_d ?: return@setOnClickListener
val intent = Intent(TimetableFragment.ACTION_SCROLL_TO_DATE).apply {
putExtra("date", dateStr)
}
activity.sendBroadcast(intent)
}
}
else {
b.shiftedLayout.visibility = View.GONE
}
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) {
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) {
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) {
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) {
b.teamName = lesson.teamName
}
}
}

View File

@ -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()
}}
}

View File

@ -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()
}

View File

@ -15,8 +15,9 @@ import java.util.List;
import pl.szczodrzynski.edziennik.App; import pl.szczodrzynski.edziennik.App;
import pl.szczodrzynski.edziennik.R; import pl.szczodrzynski.edziennik.R;
import pl.szczodrzynski.edziennik.data.api.AppError; import pl.szczodrzynski.edziennik.api.v2.models.ApiError;
import pl.szczodrzynski.edziennik.databinding.ActivityLoginBinding; import pl.szczodrzynski.edziennik.databinding.ActivityLoginBinding;
import pl.szczodrzynski.edziennik.ui.modules.error.ErrorSnackbar;
public class LoginActivity extends AppCompatActivity { public class LoginActivity extends AppCompatActivity {
@ -26,7 +27,8 @@ public class LoginActivity extends AppCompatActivity {
public static final int RESULT_OK = 1; public static final int RESULT_OK = 1;
public static NavOptions navOptions; public static NavOptions navOptions;
static AppError error = null; static ApiError error = null;
ErrorSnackbar errorSnackbar = new ErrorSnackbar(this);
static List<LoginProfileObject> profileObjects; static List<LoginProfileObject> profileObjects;
public static boolean firstCompleted = false; // if a profile is already added during *this* login. This means that LoginChooser has to navigateUp onBackPressed. Else, finish the activity. public static boolean firstCompleted = false; // if a profile is already added during *this* login. This means that LoginChooser has to navigateUp onBackPressed. Else, finish the activity.
@ -69,6 +71,8 @@ public class LoginActivity extends AppCompatActivity {
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
setTheme(R.style.AppTheme_Light);
firstCompleted = false; firstCompleted = false;
profileObjects = new ArrayList<>(); profileObjects = new ArrayList<>();
error = null; error = null;
@ -83,6 +87,8 @@ public class LoginActivity extends AppCompatActivity {
b = DataBindingUtil.inflate(getLayoutInflater(), R.layout.activity_login, null, false); b = DataBindingUtil.inflate(getLayoutInflater(), R.layout.activity_login, null, false);
setContentView(b.getRoot()); setContentView(b.getRoot());
errorSnackbar.setCoordinator(b.coordinator, null);
app = (App) getApplication(); app = (App) getApplication();
if (!app.appConfig.loginFinished) { if (!app.appConfig.loginFinished) {

View File

@ -26,6 +26,7 @@ public class LoginChooserFragment extends Fragment {
private NavController nav; private NavController nav;
private FragmentLoginChooserBinding b; private FragmentLoginChooserBinding b;
private static final String TAG = "LoginTemplate"; private static final String TAG = "LoginTemplate";
public static boolean fakeLogin = false;
public LoginChooserFragment() { } public LoginChooserFragment() { }
@ -71,6 +72,10 @@ public class LoginChooserFragment extends Fragment {
b.cancelButton.setVisibility(View.GONE); b.cancelButton.setVisibility(View.GONE);
} }
b.fakeLogin.setVisibility(App.devMode ? View.VISIBLE : View.GONE);
b.fakeLogin.setChecked(fakeLogin);
b.fakeLogin.setOnCheckedChangeListener((v, isChecked) -> fakeLogin = isChecked);
b.helpButton.setOnClickListener((v -> { b.helpButton.setOnClickListener((v -> {
startActivity(new Intent(getActivity(), FeedbackActivity.class)); startActivity(new Intent(getActivity(), FeedbackActivity.class));
})); }));

View File

@ -1,25 +1,23 @@
package pl.szczodrzynski.edziennik.ui.modules.login; package pl.szczodrzynski.edziennik.ui.modules.login;
import android.os.Bundle; import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.databinding.DataBindingUtil;
import androidx.fragment.app.Fragment;
import android.text.Editable; import android.text.Editable;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import com.danimahardhika.cafebar.CafeBar; import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.databinding.DataBindingUtil;
import androidx.fragment.app.Fragment;
import androidx.navigation.NavController; import androidx.navigation.NavController;
import androidx.navigation.Navigation; import androidx.navigation.Navigation;
import pl.szczodrzynski.edziennik.App; import pl.szczodrzynski.edziennik.App;
import pl.szczodrzynski.edziennik.R; import pl.szczodrzynski.edziennik.R;
import pl.szczodrzynski.edziennik.data.api.AppError; import pl.szczodrzynski.edziennik.api.v2.models.ApiError;
import pl.szczodrzynski.edziennik.databinding.FragmentLoginIuczniowieBinding; import pl.szczodrzynski.edziennik.databinding.FragmentLoginIuczniowieBinding;
import pl.szczodrzynski.edziennik.ui.modules.error.ErrorSnackbar;
import static pl.szczodrzynski.edziennik.data.api.AppError.CODE_INVALID_LOGIN; import static pl.szczodrzynski.edziennik.data.api.AppError.CODE_INVALID_LOGIN;
import static pl.szczodrzynski.edziennik.data.api.AppError.CODE_INVALID_SCHOOL_NAME; import static pl.szczodrzynski.edziennik.data.api.AppError.CODE_INVALID_SCHOOL_NAME;
@ -31,6 +29,7 @@ public class LoginIuczniowieFragment extends Fragment {
private NavController nav; private NavController nav;
private FragmentLoginIuczniowieBinding b; private FragmentLoginIuczniowieBinding b;
private static final String TAG = "LoginIuczniowie"; private static final String TAG = "LoginIuczniowie";
private ErrorSnackbar errorSnackbar;
public LoginIuczniowieFragment() { } public LoginIuczniowieFragment() { }
@ -40,6 +39,7 @@ public class LoginIuczniowieFragment extends Fragment {
if (getActivity() != null) { if (getActivity() != null) {
app = (App) getActivity().getApplicationContext(); app = (App) getActivity().getApplicationContext();
nav = Navigation.findNavController(getActivity(), R.id.nav_host_fragment); nav = Navigation.findNavController(getActivity(), R.id.nav_host_fragment);
errorSnackbar = ((LoginActivity) getActivity()).errorSnackbar;
} }
else { else {
return null; return null;
@ -54,31 +54,17 @@ public class LoginIuczniowieFragment extends Fragment {
assert getActivity() != null; assert getActivity() != null;
view.postDelayed(() -> { view.postDelayed(() -> {
AppError error = LoginActivity.error; ApiError error = LoginActivity.error;
if (error != null) { if (error != null) {
switch (error.errorCode) { switch (error.getErrorCode()) {
case CODE_INVALID_SCHOOL_NAME: case CODE_INVALID_SCHOOL_NAME:
b.loginSchoolNameLayout.setError(getString(R.string.login_error_incorrect_school_name)); b.loginSchoolNameLayout.setError(getString(R.string.login_error_incorrect_school_name));
break; break;
case CODE_INVALID_LOGIN: case CODE_INVALID_LOGIN:
b.loginPasswordLayout.setError(getString(R.string.login_error_incorrect_login_or_password)); b.loginPasswordLayout.setError(getString(R.string.login_error_incorrect_login_or_password));
break; break;
default:
CafeBar.builder(getActivity())
.to(b.root)
.content(getString(R.string.login_error, error.asReadableString(getActivity())))
.autoDismiss(false)
.positiveText(R.string.ok)
.onPositive(CafeBar::dismiss)
.floating(true)
.swipeToDismiss(true)
.neutralText(R.string.more)
.onNeutral(cafeBar -> app.apiEdziennik.guiShowErrorDialog(getActivity(), error, R.string.error_details))
.negativeText(R.string.report)
.onNegative((cafeBar -> app.apiEdziennik.guiReportError(getActivity(), error, null)))
.show();
break;
} }
errorSnackbar.addError(error).show();
LoginActivity.error = null; LoginActivity.error = null;
} }
}, 100); }, 100);

View File

@ -1,28 +1,26 @@
package pl.szczodrzynski.edziennik.ui.modules.login; package pl.szczodrzynski.edziennik.ui.modules.login;
import android.os.Bundle; import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.databinding.DataBindingUtil;
import androidx.fragment.app.Fragment;
import android.text.Editable; import android.text.Editable;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import com.danimahardhika.cafebar.CafeBar; import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.databinding.DataBindingUtil;
import androidx.fragment.app.Fragment;
import androidx.navigation.NavController; import androidx.navigation.NavController;
import androidx.navigation.Navigation; import androidx.navigation.Navigation;
import pl.szczodrzynski.edziennik.App; import pl.szczodrzynski.edziennik.App;
import pl.szczodrzynski.edziennik.R; import pl.szczodrzynski.edziennik.R;
import pl.szczodrzynski.edziennik.data.api.AppError; import pl.szczodrzynski.edziennik.api.v2.models.ApiError;
import pl.szczodrzynski.edziennik.databinding.FragmentLoginLibrusBinding; import pl.szczodrzynski.edziennik.databinding.FragmentLoginLibrusBinding;
import pl.szczodrzynski.edziennik.ui.modules.error.ErrorSnackbar;
import static pl.szczodrzynski.edziennik.data.api.AppError.CODE_INVALID_LOGIN; import static pl.szczodrzynski.edziennik.api.v2.ErrorsKt.ERROR_LOGIN_DATA_INVALID;
import static pl.szczodrzynski.edziennik.data.api.AppError.CODE_LIBRUS_NOT_ACTIVATED; import static pl.szczodrzynski.edziennik.api.v2.ErrorsKt.ERROR_LOGIN_LIBRUS_PORTAL_NOT_ACTIVATED;
import static pl.szczodrzynski.edziennik.data.db.modules.login.LoginStore.LOGIN_TYPE_LIBRUS; import static pl.szczodrzynski.edziennik.data.db.modules.login.LoginStore.LOGIN_TYPE_LIBRUS;
public class LoginLibrusFragment extends Fragment { public class LoginLibrusFragment extends Fragment {
@ -31,6 +29,7 @@ public class LoginLibrusFragment extends Fragment {
private NavController nav; private NavController nav;
private FragmentLoginLibrusBinding b; private FragmentLoginLibrusBinding b;
private static final String TAG = "LoginLibrus"; private static final String TAG = "LoginLibrus";
private ErrorSnackbar errorSnackbar;
public LoginLibrusFragment() { } public LoginLibrusFragment() { }
@ -40,6 +39,7 @@ public class LoginLibrusFragment extends Fragment {
if (getActivity() != null) { if (getActivity() != null) {
app = (App) getActivity().getApplicationContext(); app = (App) getActivity().getApplicationContext();
nav = Navigation.findNavController(getActivity(), R.id.nav_host_fragment); nav = Navigation.findNavController(getActivity(), R.id.nav_host_fragment);
errorSnackbar = ((LoginActivity) getActivity()).errorSnackbar;
} }
else { else {
return null; return null;
@ -54,32 +54,17 @@ public class LoginLibrusFragment extends Fragment {
assert getActivity() != null; assert getActivity() != null;
view.postDelayed(() -> { view.postDelayed(() -> {
AppError error = LoginActivity.error; ApiError error = LoginActivity.error;
if (error != null) { if (error != null) {
switch (error.errorCode) { switch (error.getErrorCode()) {
case CODE_INVALID_LOGIN: case ERROR_LOGIN_DATA_INVALID:
b.loginPasswordLayout.setError(getString(R.string.login_error_incorrect_login_or_password)); b.loginPasswordLayout.setError(getString(R.string.login_error_incorrect_login_or_password));
break; break;
case CODE_LIBRUS_NOT_ACTIVATED: case ERROR_LOGIN_LIBRUS_PORTAL_NOT_ACTIVATED:
b.loginEmailLayout.setError(getString(R.string.login_error_account_not_activated)); b.loginEmailLayout.setError(getString(R.string.login_error_account_not_activated));
break; break;
default:
CafeBar.builder(getActivity())
.to(b.root)
.content(getString(R.string.login_error, error.asReadableString(getActivity())))
.duration(10000)
.autoDismiss(false)
.positiveText(R.string.ok)
.onPositive(CafeBar::dismiss)
.floating(true)
.swipeToDismiss(true)
.neutralText(R.string.more)
.onNeutral(cafeBar -> app.apiEdziennik.guiShowErrorDialog(getActivity(), error, R.string.error_details))
.negativeText(R.string.report)
.onNegative((cafeBar -> app.apiEdziennik.guiReportError(getActivity(), error, null)))
.show();
break;
} }
errorSnackbar.addError(error).show();
LoginActivity.error = null; LoginActivity.error = null;
} }
}, 100); }, 100);

View File

@ -1,23 +1,23 @@
package pl.szczodrzynski.edziennik.ui.modules.login; package pl.szczodrzynski.edziennik.ui.modules.login;
import androidx.databinding.DataBindingUtil;
import android.os.Bundle; import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import android.text.Editable; import android.text.Editable;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import com.danimahardhika.cafebar.CafeBar; import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.databinding.DataBindingUtil;
import androidx.fragment.app.Fragment;
import androidx.navigation.NavController; import androidx.navigation.NavController;
import androidx.navigation.Navigation; import androidx.navigation.Navigation;
import pl.szczodrzynski.edziennik.App; import pl.szczodrzynski.edziennik.App;
import pl.szczodrzynski.edziennik.R; import pl.szczodrzynski.edziennik.R;
import pl.szczodrzynski.edziennik.data.api.AppError; import pl.szczodrzynski.edziennik.api.v2.models.ApiError;
import pl.szczodrzynski.edziennik.databinding.FragmentLoginMobidziennikBinding; import pl.szczodrzynski.edziennik.databinding.FragmentLoginMobidziennikBinding;
import pl.szczodrzynski.edziennik.ui.modules.error.ErrorSnackbar;
import static pl.szczodrzynski.edziennik.data.api.AppError.CODE_ARCHIVED; import static pl.szczodrzynski.edziennik.data.api.AppError.CODE_ARCHIVED;
import static pl.szczodrzynski.edziennik.data.api.AppError.CODE_INVALID_LOGIN; import static pl.szczodrzynski.edziennik.data.api.AppError.CODE_INVALID_LOGIN;
@ -31,6 +31,7 @@ public class LoginMobidziennikFragment extends Fragment {
private NavController nav; private NavController nav;
private FragmentLoginMobidziennikBinding b; private FragmentLoginMobidziennikBinding b;
private static final String TAG = "LoginMobidziennik"; private static final String TAG = "LoginMobidziennik";
private ErrorSnackbar errorSnackbar;
public LoginMobidziennikFragment() { } public LoginMobidziennikFragment() { }
@ -40,6 +41,7 @@ public class LoginMobidziennikFragment extends Fragment {
if (getActivity() != null) { if (getActivity() != null) {
app = (App) getActivity().getApplicationContext(); app = (App) getActivity().getApplicationContext();
nav = Navigation.findNavController(getActivity(), R.id.nav_host_fragment); nav = Navigation.findNavController(getActivity(), R.id.nav_host_fragment);
errorSnackbar = ((LoginActivity) getActivity()).errorSnackbar;
} }
else { else {
return null; return null;
@ -54,9 +56,9 @@ public class LoginMobidziennikFragment extends Fragment {
assert getActivity() != null; assert getActivity() != null;
view.postDelayed(() -> { view.postDelayed(() -> {
AppError error = LoginActivity.error; ApiError error = LoginActivity.error;
if (error != null) { if (error != null) {
switch (error.errorCode) { switch (error.getErrorCode()) {
case CODE_INVALID_LOGIN: case CODE_INVALID_LOGIN:
b.loginPasswordLayout.setError(getString(R.string.login_error_incorrect_login_or_password)); b.loginPasswordLayout.setError(getString(R.string.login_error_incorrect_login_or_password));
break; break;
@ -69,22 +71,8 @@ public class LoginMobidziennikFragment extends Fragment {
case CODE_INVALID_SERVER_ADDRESS: case CODE_INVALID_SERVER_ADDRESS:
b.loginServerAddressLayout.setError(getString(R.string.login_error_incorrect_address)); b.loginServerAddressLayout.setError(getString(R.string.login_error_incorrect_address));
break; break;
default:
CafeBar.builder(getActivity())
.to(b.root)
.content(getString(R.string.login_error, error.asReadableString(getActivity())))
.autoDismiss(false)
.positiveText(R.string.ok)
.onPositive(CafeBar::dismiss)
.floating(true)
.swipeToDismiss(true)
.neutralText(R.string.more)
.onNeutral(cafeBar -> app.apiEdziennik.guiShowErrorDialog(getActivity(), error, R.string.error_details))
.negativeText(R.string.report)
.onNegative((cafeBar -> app.apiEdziennik.guiReportError(getActivity(), error, null)))
.show();
break;
} }
errorSnackbar.addError(error).show();
LoginActivity.error = null; LoginActivity.error = null;
} }
}, 100); }, 100);

View File

@ -23,11 +23,11 @@ import pl.szczodrzynski.edziennik.R;
import pl.szczodrzynski.edziennik.api.v2.events.ApiTaskErrorEvent; import pl.szczodrzynski.edziennik.api.v2.events.ApiTaskErrorEvent;
import pl.szczodrzynski.edziennik.api.v2.events.FirstLoginFinishedEvent; import pl.szczodrzynski.edziennik.api.v2.events.FirstLoginFinishedEvent;
import pl.szczodrzynski.edziennik.api.v2.events.task.EdziennikTask; import pl.szczodrzynski.edziennik.api.v2.events.task.EdziennikTask;
import pl.szczodrzynski.edziennik.data.api.AppError; import pl.szczodrzynski.edziennik.api.v2.models.ApiError;
import pl.szczodrzynski.edziennik.data.db.modules.login.LoginStore; import pl.szczodrzynski.edziennik.data.db.modules.login.LoginStore;
import pl.szczodrzynski.edziennik.databinding.FragmentLoginProgressBinding; import pl.szczodrzynski.edziennik.databinding.FragmentLoginProgressBinding;
import static pl.szczodrzynski.edziennik.data.api.AppError.CODE_OTHER; import static pl.szczodrzynski.edziennik.api.v2.ErrorsKt.LOGIN_NO_ARGUMENTS;
public class LoginProgressFragment extends Fragment { public class LoginProgressFragment extends Fragment {
@ -62,7 +62,7 @@ public class LoginProgressFragment extends Fragment {
@Subscribe(threadMode = ThreadMode.MAIN) @Subscribe(threadMode = ThreadMode.MAIN)
public void onSyncErrorEvent(ApiTaskErrorEvent event) { public void onSyncErrorEvent(ApiTaskErrorEvent event) {
LoginActivity.error = event.getError().toAppError(); LoginActivity.error = event.getError();
if (getActivity() == null) if (getActivity() == null)
return; return;
nav.navigateUp(); nav.navigateUp();
@ -79,7 +79,7 @@ public class LoginProgressFragment extends Fragment {
LoginActivity.error = null; LoginActivity.error = null;
if (args == null) { if (args == null) {
LoginActivity.error = new AppError(TAG, 72, CODE_OTHER, getString(R.string.login_error_no_arguments)); LoginActivity.error = new ApiError(TAG, LOGIN_NO_ARGUMENTS);
nav.navigateUp(); nav.navigateUp();
return; return;
} }
@ -89,6 +89,10 @@ public class LoginProgressFragment extends Fragment {
LoginStore loginStore = new LoginStore(-1, loginType, new JsonObject()); LoginStore loginStore = new LoginStore(-1, loginType, new JsonObject());
loginStore.copyFrom(args); loginStore.copyFrom(args);
if (App.devMode && LoginChooserFragment.fakeLogin) {
loginStore.putLoginData("fakeLogin", true);
}
EdziennikTask.Companion.firstLogin(loginStore).enqueue(getContext()); EdziennikTask.Companion.firstLogin(loginStore).enqueue(getContext());
} }

View File

@ -1,6 +1,9 @@
package pl.szczodrzynski.edziennik.ui.modules.login; package pl.szczodrzynski.edziennik.ui.modules.login;
import android.os.Bundle; import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
@ -8,14 +11,11 @@ import androidx.databinding.DataBindingUtil;
import androidx.fragment.app.Fragment; import androidx.fragment.app.Fragment;
import androidx.navigation.NavController; import androidx.navigation.NavController;
import androidx.navigation.Navigation; import androidx.navigation.Navigation;
import pl.szczodrzynski.edziennik.App; import pl.szczodrzynski.edziennik.App;
import pl.szczodrzynski.edziennik.R; import pl.szczodrzynski.edziennik.R;
import pl.szczodrzynski.edziennik.databinding.FragmentLoginSyncErrorBinding; import pl.szczodrzynski.edziennik.databinding.FragmentLoginSyncErrorBinding;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
public class LoginSyncErrorFragment extends Fragment { public class LoginSyncErrorFragment extends Fragment {
private App app; private App app;
@ -44,10 +44,10 @@ public class LoginSyncErrorFragment extends Fragment {
assert getContext() != null; assert getContext() != null;
assert getActivity() != null; assert getActivity() != null;
b.errorDetails.setText(LoginActivity.error == null ? "" : LoginActivity.error.asReadableString(getActivity())); b.errorDetails.setText(LoginActivity.error == null ? "" : LoginActivity.error.getStringReason(getActivity()));
b.reportButton.setOnClickListener((v -> { b.reportButton.setOnClickListener((v -> {
app.apiEdziennik.guiReportError(getActivity(), LoginActivity.error, null); // TODO error report activity open here app.apiEdziennik.guiReportError(getActivity(), LoginActivity.error, null);
})); }));
b.nextButton.setOnClickListener((v -> { b.nextButton.setOnClickListener((v -> {

View File

@ -65,7 +65,7 @@ class LoginSyncFragment : Fragment() {
@Subscribe(threadMode = ThreadMode.MAIN) @Subscribe(threadMode = ThreadMode.MAIN)
fun onSyncErrorEvent(event: ApiTaskErrorEvent) { fun onSyncErrorEvent(event: ApiTaskErrorEvent) {
LoginActivity.error = event.error.toAppError() LoginActivity.error = event.error
nav.navigate(R.id.loginSyncErrorFragment, null, LoginActivity.navOptions) nav.navigate(R.id.loginSyncErrorFragment, null, LoginActivity.navOptions)
} }

View File

@ -3,27 +3,24 @@ package pl.szczodrzynski.edziennik.ui.modules.login;
import android.content.Intent; import android.content.Intent;
import android.graphics.Color; import android.graphics.Color;
import android.os.Bundle; import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.databinding.DataBindingUtil;
import androidx.fragment.app.Fragment;
import android.text.Editable; import android.text.Editable;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.view.WindowManager; import android.view.WindowManager;
import com.danimahardhika.cafebar.CafeBar; import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.databinding.DataBindingUtil;
import androidx.fragment.app.Fragment;
import androidx.navigation.NavController;
import androidx.navigation.Navigation;
import com.mikepenz.iconics.IconicsColor; import com.mikepenz.iconics.IconicsColor;
import com.mikepenz.iconics.IconicsDrawable; import com.mikepenz.iconics.IconicsDrawable;
import com.mikepenz.iconics.IconicsSize; import com.mikepenz.iconics.IconicsSize;
import com.mikepenz.iconics.typeface.library.community.material.CommunityMaterial; import com.mikepenz.iconics.typeface.library.community.material.CommunityMaterial;
import androidx.navigation.NavController;
import androidx.navigation.Navigation;
import java.security.InvalidKeyException; import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException; import java.security.NoSuchAlgorithmException;
import java.util.regex.Matcher; import java.util.regex.Matcher;
@ -36,9 +33,10 @@ import javax.crypto.ShortBufferException;
import pl.szczodrzynski.edziennik.App; import pl.szczodrzynski.edziennik.App;
import pl.szczodrzynski.edziennik.R; import pl.szczodrzynski.edziennik.R;
import pl.szczodrzynski.edziennik.ui.modules.webpush.QrScannerActivity; import pl.szczodrzynski.edziennik.api.v2.models.ApiError;
import pl.szczodrzynski.edziennik.data.api.AppError;
import pl.szczodrzynski.edziennik.databinding.FragmentLoginVulcanBinding; import pl.szczodrzynski.edziennik.databinding.FragmentLoginVulcanBinding;
import pl.szczodrzynski.edziennik.ui.modules.error.ErrorSnackbar;
import pl.szczodrzynski.edziennik.ui.modules.webpush.QrScannerActivity;
import pl.szczodrzynski.edziennik.utils.Utils; import pl.szczodrzynski.edziennik.utils.Utils;
import static pl.szczodrzynski.edziennik.data.api.AppError.CODE_EXPIRED_TOKEN; import static pl.szczodrzynski.edziennik.data.api.AppError.CODE_EXPIRED_TOKEN;
@ -53,6 +51,7 @@ public class LoginVulcanFragment extends Fragment {
private NavController nav; private NavController nav;
private FragmentLoginVulcanBinding b; private FragmentLoginVulcanBinding b;
private static final String TAG = "LoginVulcan"; private static final String TAG = "LoginVulcan";
private ErrorSnackbar errorSnackbar;
public LoginVulcanFragment() { } public LoginVulcanFragment() { }
@ -62,6 +61,7 @@ public class LoginVulcanFragment extends Fragment {
if (getActivity() != null) { if (getActivity() != null) {
app = (App) getActivity().getApplicationContext(); app = (App) getActivity().getApplicationContext();
nav = Navigation.findNavController(getActivity(), R.id.nav_host_fragment); nav = Navigation.findNavController(getActivity(), R.id.nav_host_fragment);
errorSnackbar = ((LoginActivity) getActivity()).errorSnackbar;
} }
else { else {
return null; return null;
@ -76,9 +76,9 @@ public class LoginVulcanFragment extends Fragment {
assert getActivity() != null; assert getActivity() != null;
view.postDelayed(() -> { view.postDelayed(() -> {
AppError error = LoginActivity.error; ApiError error = LoginActivity.error;
if (error != null) { if (error != null) {
switch (error.errorCode) { switch (error.getErrorCode()) {
case CODE_INVALID_TOKEN: case CODE_INVALID_TOKEN:
b.loginTokenLayout.setError(getString(R.string.login_error_incorrect_token)); b.loginTokenLayout.setError(getString(R.string.login_error_incorrect_token));
break; break;
@ -89,28 +89,14 @@ public class LoginVulcanFragment extends Fragment {
b.loginSymbolLayout.setError(getString(R.string.login_error_incorrect_symbol)); b.loginSymbolLayout.setError(getString(R.string.login_error_incorrect_symbol));
break; break;
case CODE_INVALID_PIN: case CODE_INVALID_PIN:
if (!"?".equals(error.errorText)) { /*if (!"?".equals(error.errorText)) {
b.loginPinLayout.setError(getString(R.string.login_error_incorrect_pin_format, error.errorText)); b.loginPinLayout.setError(getString(R.string.login_error_incorrect_pin_format, error.errorText));
break; break;
} }*/
b.loginPinLayout.setError(getString(R.string.login_error_incorrect_pin)); b.loginPinLayout.setError(getString(R.string.login_error_incorrect_pin));
break; break;
default:
CafeBar.builder(getActivity())
.to(b.root)
.content(getString(R.string.login_error, error.asReadableString(getActivity())))
.autoDismiss(false)
.positiveText(R.string.ok)
.onPositive(CafeBar::dismiss)
.floating(true)
.swipeToDismiss(true)
.neutralText(R.string.more)
.onNeutral(cafeBar -> app.apiEdziennik.guiShowErrorDialog(getActivity(), error, R.string.error_details))
.negativeText(R.string.report)
.onNegative((cafeBar -> app.apiEdziennik.guiReportError(getActivity(), error, null)))
.show();
break;
} }
errorSnackbar.addError(error).show();
LoginActivity.error = null; LoginActivity.error = null;
} }
}, 100); }, 100);

View File

@ -0,0 +1,319 @@
/*
* Copyright (c) Kuba Szczodrzyński 2019-11-12.
*/
package pl.szczodrzynski.edziennik.ui.modules.messages
import android.os.Bundle
import android.os.Environment
import android.text.Html
import android.text.TextUtils
import android.view.Gravity.CENTER_VERTICAL
import android.view.Gravity.END
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.FrameLayout
import android.widget.ProgressBar
import androidx.fragment.app.Fragment
import com.google.android.material.chip.Chip
import com.mikepenz.iconics.IconicsColor
import com.mikepenz.iconics.IconicsDrawable
import com.mikepenz.iconics.IconicsSize
import com.mikepenz.iconics.typeface.IIcon
import com.mikepenz.iconics.typeface.library.community.material.CommunityMaterial
import com.mikepenz.iconics.utils.sizeDp
import kotlinx.coroutines.*
import org.greenrobot.eventbus.EventBus
import org.greenrobot.eventbus.Subscribe
import org.greenrobot.eventbus.ThreadMode
import pl.szczodrzynski.edziennik.App
import pl.szczodrzynski.edziennik.MainActivity
import pl.szczodrzynski.edziennik.R
import pl.szczodrzynski.edziennik.api.v2.events.MessageGetEvent
import pl.szczodrzynski.edziennik.api.v2.events.task.EdziennikTask
import pl.szczodrzynski.edziennik.data.db.modules.messages.Message.TYPE_SENT
import pl.szczodrzynski.edziennik.data.db.modules.messages.MessageFull
import pl.szczodrzynski.edziennik.databinding.MessageFragmentBinding
import pl.szczodrzynski.edziennik.onClick
import pl.szczodrzynski.edziennik.utils.Anim
import pl.szczodrzynski.edziennik.utils.Themes
import pl.szczodrzynski.edziennik.utils.Utils
import pl.szczodrzynski.edziennik.utils.Utils.getStringFromFile
import pl.szczodrzynski.edziennik.utils.Utils.readableFileSize
import pl.szczodrzynski.edziennik.utils.models.Date
import pl.szczodrzynski.edziennik.utils.models.Time
import pl.szczodrzynski.navlib.colorAttr
import java.io.File
import kotlin.coroutines.CoroutineContext
import kotlin.math.min
class MessageFragment : Fragment(), CoroutineScope {
companion object {
private const val TAG = "MessageFragment"
}
private lateinit var app: App
private lateinit var activity: MainActivity
private lateinit var b: MessageFragmentBinding
private lateinit var job: Job
override val coroutineContext: CoroutineContext
get() = job + Dispatchers.Main
private lateinit var message: MessageFull
private var attachmentList = mutableListOf<Attachment>()
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 = MessageFragmentBinding.inflate(inflater)
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
b.closeButton.setImageDrawable(
IconicsDrawable(activity, CommunityMaterial.Icon2.cmd_window_close)
.colorAttr(activity, android.R.attr.textColorSecondary)
.sizeDp(12)
)
b.closeButton.setOnClickListener { activity.navigateUp() }
val messageId = arguments?.getLong("messageId")
if (messageId == null) {
activity.navigateUp()
return
}
launch {
val deferred = async(Dispatchers.Default) {
val msg = app.db.messageDao().getById(App.profileId, messageId)?.also {
it.recipients = app.db.messageRecipientDao().getAllByMessageId(it.profileId, it.id)
if (it.body != null && !it.seen) {
app.db.metadataDao().setSeen(it.profileId, it, true)
}
}
msg
}
val msg = deferred.await() ?: run {
return@launch
}
message = msg
b.subject.text = message.subject
checkMessage()
}
// click to expand subject and sender
b.subject.onClick {
it.maxLines = if (it.maxLines == 30) 2 else 30
}
b.sender.onClick {
it.maxLines = if (it.maxLines == 30) 2 else 30
}
}
@Subscribe(threadMode = ThreadMode.MAIN, sticky = true)
fun onMessageGetEvent(event: MessageGetEvent) {
// TODO remove this: message = event.message
showMessage()
}
private fun checkMessage() {
if (message.body == null) {
EdziennikTask.messageGet(App.profileId, message).enqueue(activity)
return
}
var readByAll = true
message.recipients?.forEach { recipient ->
if (recipient.id == -1L)
recipient.fullName = app.profile.accountNameLong ?: app.profile.studentNameLong
if (message.type == TYPE_SENT && recipient.readDate < 1)
readByAll = false
}
// if a sent msg is not read by everyone, download it again to check the read status
if (!readByAll) {
EdziennikTask.messageGet(App.profileId, message).enqueue(activity)
return
}
showMessage()
}
private fun showMessage() {
b.body.text = Html.fromHtml(message.body?.replace("\\[META:[A-z0-9]+;[0-9-]+]".toRegex(), ""))
b.date.text = getString(R.string.messages_date_time_format, Date.fromMillis(message.addedDate).formattedStringShort, Time.fromMillis(message.addedDate).stringHM)
val messageInfo = MessagesUtils.getMessageInfo(app, message, 40, 20, 14, 10)
b.profileBackground.setImageBitmap(messageInfo.profileImage)
b.sender.text = messageInfo.profileName
b.subject.text = message.subject
val messageRecipients = StringBuilder("<ul>")
message.recipients?.forEach { recipient ->
when (recipient.readDate) {
-1L -> messageRecipients.append(getString(
R.string.messages_recipients_list_unknown_state_format,
recipient.fullName
))
0L -> messageRecipients.append(getString(
R.string.messages_recipients_list_unread_format,
recipient.fullName
))
1L -> messageRecipients.append(getString(
R.string.messages_recipients_list_read_unknown_date_format,
recipient.fullName
))
else -> messageRecipients.append(getString(
R.string.messages_recipients_list_read_format,
recipient.fullName,
Date.fromMillis(recipient.readDate).formattedString,
Time.fromMillis(recipient.readDate).stringHM
))
}
}
messageRecipients.append("</ul>")
b.recipients.text = Html.fromHtml(messageRecipients.toString())
showAttachments()
b.progress.visibility = View.GONE
Anim.fadeIn(b.content, 200, null)
MessagesFragment.pageSelection = min(message.type, 1)
}
private fun showAttachments() {
if (message.attachmentIds != null) {
val insertPoint = b.attachments
val chipLayoutParams = FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)
chipLayoutParams.setMargins(0, Utils.dpToPx(8), 0, Utils.dpToPx(8))
val progressLayoutParams = FrameLayout.LayoutParams(Utils.dpToPx(18), Utils.dpToPx(18))
progressLayoutParams.setMargins(Utils.dpToPx(8), 0, Utils.dpToPx(8), 0)
progressLayoutParams.gravity = END or CENTER_VERTICAL
// CREATE VIEWS AND AN OBJECT FOR EVERY ATTACHMENT
message.attachmentNames.forEachIndexed { index, name ->
val messageId = message.id
val id = message.attachmentIds[index]
val size = message.attachmentSizes[index]
// create the parent
val attachmentLayout = FrameLayout(b.root.context)
attachmentLayout.setPadding(Utils.dpToPx(16), 0, Utils.dpToPx(16), 0)
val attachmentChip = Chip(attachmentLayout.context)
//attachmentChip.setChipBackgroundColorResource(ThemeUtils.getChipColorRes());
attachmentChip.layoutParams = chipLayoutParams
attachmentChip.height = Utils.dpToPx(40)
// show the file size or not
if (size == -1L)
attachmentChip.text = getString(R.string.messages_attachment_no_size_format, name)
else
attachmentChip.text = getString(R.string.messages_attachment_format, name, readableFileSize(size))
attachmentChip.ellipsize = TextUtils.TruncateAt.MIDDLE
// create an icon for the attachment
var icon: IIcon = CommunityMaterial.Icon.cmd_file
when (Utils.getExtensionFromFileName(name)) {
"txt" -> icon = CommunityMaterial.Icon.cmd_file_document
"doc", "docx", "odt", "rtf" -> icon = CommunityMaterial.Icon.cmd_file_word
"xls", "xlsx", "ods" -> icon = CommunityMaterial.Icon.cmd_file_excel
"ppt", "pptx", "odp" -> icon = CommunityMaterial.Icon.cmd_file_powerpoint
"pdf" -> icon = CommunityMaterial.Icon.cmd_file_pdf
"mp3", "wav", "aac" -> icon = CommunityMaterial.Icon.cmd_file_music
"mp4", "avi", "3gp", "mkv", "flv" -> icon = CommunityMaterial.Icon.cmd_file_video
"jpg", "jpeg", "png", "bmp", "gif" -> icon = CommunityMaterial.Icon.cmd_file_image
"zip", "rar", "tar", "7z" -> icon = CommunityMaterial.Icon.cmd_file_lock
}
attachmentChip.chipIcon = IconicsDrawable(activity).color(IconicsColor.colorRes(R.color.colorPrimary)).icon(icon).size(IconicsSize.dp(26))
attachmentChip.closeIcon = IconicsDrawable(activity).icon(CommunityMaterial.Icon.cmd_check).size(IconicsSize.dp(18)).color(IconicsColor.colorInt(Utils.getAttr(activity, android.R.attr.textColorPrimary)))
attachmentChip.isCloseIconVisible = false
// set the object's index in the attachmentList as the tag
attachmentChip.tag = index
attachmentChip.setOnClickListener { v ->
if (v.tag is Int) {
// TODO downloadAttachment(v.tag as Int)
}
}
attachmentLayout.addView(attachmentChip)
val attachmentProgress = ProgressBar(attachmentLayout.context)
attachmentProgress.layoutParams = progressLayoutParams
attachmentProgress.visibility = View.GONE
attachmentLayout.addView(attachmentProgress)
insertPoint.addView(attachmentLayout)
// create an object and add to the list
val a = Attachment(App.profileId, messageId, id, name, size, attachmentLayout, attachmentChip, attachmentProgress)
attachmentList.add(a)
// check if the file is already downloaded. Show the check icon if necessary and set `downloaded` to true.
checkAttachment(a)
}
} else {
// no attachments found
b.attachmentsTitle.visibility = View.GONE
}
}
private fun checkAttachment(attachment: Attachment) {
val storageDir = Environment.getExternalStoragePublicDirectory("Szkolny.eu")
storageDir.mkdirs()
val attachmentDataFile = File(storageDir, "." + attachment.profileId + "_" + attachment.messageId + "_" + attachment.attachmentId)
if (attachmentDataFile.exists()) {
try {
val attachmentFileName = getStringFromFile(attachmentDataFile)
val attachmentFile = File(attachmentFileName)
if (attachmentFile.exists()) {
attachment.downloaded = attachmentFileName
attachment.chip.isCloseIconVisible = true
}
} catch (e: Exception) {
e.printStackTrace()
//app.apiEdziennik.guiReportException(activity, 355, e)
}
}
}
override fun onStart() {
EventBus.getDefault().register(this)
super.onStart()
}
override fun onStop() {
super.onStop()
EventBus.getDefault().unregister(this)
}
override fun onDestroy() {
super.onDestroy()
job.cancel()
}
private class Attachment(
var profileId: Int,
var messageId: Long,
var attachmentId: Long,
var attachmentName: String,
var attachmentSize: Long,
var parent: FrameLayout,
var chip: Chip,
var progressBar: ProgressBar
) {
/**
* An absolute path of the downloaded file. `null` if not downloaded yet.
*/
internal var downloaded: String? = null
}
}

View File

@ -4,7 +4,6 @@ import android.content.Context;
import android.graphics.Color; import android.graphics.Color;
import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.BitmapDrawable;
import android.os.Bundle; import android.os.Bundle;
import android.os.Handler;
import android.util.Log; import android.util.Log;
import android.view.Menu; import android.view.Menu;
import android.view.MotionEvent; import android.view.MotionEvent;
@ -13,7 +12,6 @@ import android.widget.Toast;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.appcompat.app.ActionBar; import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AppCompatActivity; import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar;
import androidx.databinding.DataBindingUtil; import androidx.databinding.DataBindingUtil;
import com.afollestad.materialdialogs.MaterialDialog; import com.afollestad.materialdialogs.MaterialDialog;
@ -35,12 +33,6 @@ import java.util.List;
import pl.szczodrzynski.edziennik.App; import pl.szczodrzynski.edziennik.App;
import pl.szczodrzynski.edziennik.R; import pl.szczodrzynski.edziennik.R;
import pl.szczodrzynski.edziennik.data.api.AppError;
import pl.szczodrzynski.edziennik.data.api.Edziennik;
import pl.szczodrzynski.edziennik.data.api.interfaces.SyncCallback;
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;
import pl.szczodrzynski.edziennik.data.db.modules.teachers.Teacher; import pl.szczodrzynski.edziennik.data.db.modules.teachers.Teacher;
import pl.szczodrzynski.edziennik.databinding.ActivityComposeMessageBinding; import pl.szczodrzynski.edziennik.databinding.ActivityComposeMessageBinding;
import pl.szczodrzynski.edziennik.utils.Colors; import pl.szczodrzynski.edziennik.utils.Colors;
@ -63,7 +55,7 @@ public class MessagesComposeActivity extends AppCompatActivity {
b = DataBindingUtil.inflate(getLayoutInflater(), R.layout.activity_compose_message, null, false); b = DataBindingUtil.inflate(getLayoutInflater(), R.layout.activity_compose_message, null, false);
setContentView(b.getRoot()); setContentView(b.getRoot());
composeInfo = Edziennik.getApi(app, app.profile.getLoginStoreType()).getComposeInfo(app.profile); /*composeInfo = Edziennik.getApi(app, app.profile.getLoginStoreType()).getComposeInfo(app.profile);
Toolbar toolbar = b.toolbar; Toolbar toolbar = b.toolbar;
setSupportActionBar(toolbar); setSupportActionBar(toolbar);
@ -99,7 +91,7 @@ public class MessagesComposeActivity extends AppCompatActivity {
MessagesComposeSuggestionAdapter adapter = new MessagesComposeSuggestionAdapter(this, teachers); MessagesComposeSuggestionAdapter adapter = new MessagesComposeSuggestionAdapter(this, teachers);
//ArrayAdapter<Teacher> adapter = new ArrayAdapter<>(this, android.R.layout.simple_dropdown_item_1line, teachers); //ArrayAdapter<Teacher> adapter = new ArrayAdapter<>(this, android.R.layout.simple_dropdown_item_1line, teachers);
b.nachoTextView.setAdapter(adapter); b.nachoTextView.setAdapter(adapter);
}); });*/
/*app.db.teacherDao().getAllTeachers(App.profileId).observe(this, teachers -> { /*app.db.teacherDao().getAllTeachers(App.profileId).observe(this, teachers -> {
});*/ });*/

View File

@ -1,17 +1,12 @@
package pl.szczodrzynski.edziennik.ui.modules.messages; package pl.szczodrzynski.edziennik.ui.modules.messages;
import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.graphics.Bitmap; import android.graphics.Bitmap;
import android.graphics.BitmapFactory; import android.graphics.BitmapFactory;
import android.media.ThumbnailUtils; import android.media.ThumbnailUtils;
import android.net.Uri; import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle; import android.os.Bundle;
import android.os.Environment; import android.os.Environment;
import android.os.Handler;
import android.text.Html;
import android.text.TextUtils;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
@ -19,12 +14,18 @@ import android.widget.FrameLayout;
import android.widget.ProgressBar; import android.widget.ProgressBar;
import android.widget.Toast; import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.graphics.drawable.RoundedBitmapDrawable;
import androidx.core.graphics.drawable.RoundedBitmapDrawableFactory;
import androidx.databinding.DataBindingUtil;
import androidx.fragment.app.Fragment;
import com.afollestad.materialdialogs.MaterialDialog; import com.afollestad.materialdialogs.MaterialDialog;
import com.google.android.material.chip.Chip; import com.google.android.material.chip.Chip;
import com.mikepenz.iconics.IconicsColor; import com.mikepenz.iconics.IconicsColor;
import com.mikepenz.iconics.IconicsDrawable; import com.mikepenz.iconics.IconicsDrawable;
import com.mikepenz.iconics.IconicsSize; import com.mikepenz.iconics.IconicsSize;
import com.mikepenz.iconics.typeface.IIcon;
import com.mikepenz.iconics.typeface.library.community.material.CommunityMaterial; import com.mikepenz.iconics.typeface.library.community.material.CommunityMaterial;
import com.theartofdev.edmodo.cropper.CropImage; import com.theartofdev.edmodo.cropper.CropImage;
@ -39,36 +40,16 @@ import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.graphics.drawable.RoundedBitmapDrawable;
import androidx.core.graphics.drawable.RoundedBitmapDrawableFactory;
import androidx.databinding.DataBindingUtil;
import androidx.fragment.app.Fragment;
import im.wangchao.mhttp.Response;
import im.wangchao.mhttp.callback.FileCallbackHandler;
import pl.szczodrzynski.edziennik.App; import pl.szczodrzynski.edziennik.App;
import pl.szczodrzynski.edziennik.R;
import pl.szczodrzynski.edziennik.MainActivity; import pl.szczodrzynski.edziennik.MainActivity;
import pl.szczodrzynski.edziennik.data.api.Edziennik; import pl.szczodrzynski.edziennik.R;
import pl.szczodrzynski.edziennik.data.api.AppError;
import pl.szczodrzynski.edziennik.data.api.interfaces.SyncCallback;
import pl.szczodrzynski.edziennik.databinding.MessagesDetailsBinding;
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.messages.MessageFull;
import pl.szczodrzynski.edziennik.data.db.modules.messages.MessageRecipientFull; import pl.szczodrzynski.edziennik.databinding.MessagesDetailsBinding;
import pl.szczodrzynski.edziennik.data.db.modules.profiles.Profile; import pl.szczodrzynski.edziennik.ui.modules.error.ErrorDialog;
import pl.szczodrzynski.edziennik.data.db.modules.profiles.ProfileFull;
import pl.szczodrzynski.edziennik.utils.models.Date;
import pl.szczodrzynski.edziennik.utils.models.Time;
import pl.szczodrzynski.edziennik.utils.Anim;
import pl.szczodrzynski.edziennik.utils.Themes; import pl.szczodrzynski.edziennik.utils.Themes;
import pl.szczodrzynski.edziennik.utils.Utils; import pl.szczodrzynski.edziennik.utils.Utils;
import static android.app.Activity.RESULT_OK; import static android.app.Activity.RESULT_OK;
import static android.view.Gravity.CENTER_VERTICAL;
import static android.view.Gravity.END;
import static pl.szczodrzynski.edziennik.utils.Utils.getResizedBitmap; import static pl.szczodrzynski.edziennik.utils.Utils.getResizedBitmap;
import static pl.szczodrzynski.edziennik.utils.Utils.getStringFromFile; import static pl.szczodrzynski.edziennik.utils.Utils.getStringFromFile;
import static pl.szczodrzynski.edziennik.utils.Utils.readableFileSize; import static pl.szczodrzynski.edziennik.utils.Utils.readableFileSize;
@ -108,7 +89,7 @@ public class MessagesDetailsFragment extends Fragment {
b.messageContent.setVisibility(View.GONE); b.messageContent.setVisibility(View.GONE);
if (messageId != -1) { /*if (messageId != -1) {
AsyncTask.execute(() -> { AsyncTask.execute(() -> {
if (app == null || app.profile == null || activity == null || b == null || !isAdded()) if (app == null || app.profile == null || activity == null || b == null || !isAdded())
return; return;
@ -286,7 +267,7 @@ public class MessagesDetailsFragment extends Fragment {
}); });
}); });
} }*/
// click to expand subject and sender // click to expand subject and sender
b.messageSubject.setOnClickListener(v -> { b.messageSubject.setOnClickListener(v -> {
@ -340,7 +321,7 @@ public class MessagesDetailsFragment extends Fragment {
} }
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); e.printStackTrace();
app.apiEdziennik.guiReportException(activity, 355, e); new ErrorDialog(activity, e);
} }
} }
} }
@ -359,7 +340,7 @@ public class MessagesDetailsFragment extends Fragment {
File storageDir = Utils.getStorageDir(); File storageDir = Utils.getStorageDir();
Edziennik.getApi(app, app.profile.getLoginStoreType()).getAttachment(activity, new SyncCallback() { /*Edziennik.getApi(app, app.profile.getLoginStoreType()).getAttachment(activity, new SyncCallback() {
@Override @Override
public void onLoginFirst(List<Profile> profileList, LoginStore loginStore) { public void onLoginFirst(List<Profile> profileList, LoginStore loginStore) {
@ -437,7 +418,7 @@ public class MessagesDetailsFragment extends Fragment {
} }
}) })
.build() .build()
.enqueue()); .enqueue());*/
} }
@Subscribe(threadMode = ThreadMode.POSTING) @Subscribe(threadMode = ThreadMode.POSTING)
@ -478,7 +459,7 @@ public class MessagesDetailsFragment extends Fragment {
.positiveText(R.string.ok) .positiveText(R.string.ok)
.neutralColor(R.string.report) .neutralColor(R.string.report)
.onNeutral((dialog, which) -> { .onNeutral((dialog, which) -> {
app.apiEdziennik.guiReportException(activity, 433, event.exception); new ErrorDialog(activity, event.exception);
}) })
.show(); .show();
} }
@ -486,7 +467,7 @@ public class MessagesDetailsFragment extends Fragment {
} }
} }
catch (Exception e) { catch (Exception e) {
app.apiEdziennik.guiReportException(activity, 425, e); new ErrorDialog(activity, e);
} }
} }

View File

@ -1,8 +1,6 @@
package pl.szczodrzynski.edziennik.ui.modules.messages package pl.szczodrzynski.edziennik.ui.modules.messages
import android.os.Bundle import android.os.Bundle
import android.text.Html
import android.text.InputType
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
@ -10,18 +8,14 @@ import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentManager import androidx.fragment.app.FragmentManager
import androidx.fragment.app.FragmentPagerAdapter import androidx.fragment.app.FragmentPagerAdapter
import androidx.viewpager.widget.ViewPager import androidx.viewpager.widget.ViewPager
import com.afollestad.materialdialogs.MaterialDialog
import org.greenrobot.eventbus.EventBus import org.greenrobot.eventbus.EventBus
import org.greenrobot.eventbus.Subscribe import org.greenrobot.eventbus.Subscribe
import org.greenrobot.eventbus.ThreadMode import org.greenrobot.eventbus.ThreadMode
import pl.szczodrzynski.edziennik.App import pl.szczodrzynski.edziennik.App
import pl.szczodrzynski.edziennik.MainActivity import pl.szczodrzynski.edziennik.MainActivity
import pl.szczodrzynski.edziennik.MainActivity.Companion.DRAWER_ITEM_MESSAGES
import pl.szczodrzynski.edziennik.R import pl.szczodrzynski.edziennik.R
import pl.szczodrzynski.edziennik.api.v2.LOGIN_TYPE_LIBRUS
import pl.szczodrzynski.edziennik.api.v2.events.ApiTaskErrorEvent import pl.szczodrzynski.edziennik.api.v2.events.ApiTaskErrorEvent
import pl.szczodrzynski.edziennik.api.v2.events.ApiTaskFinishedEvent import pl.szczodrzynski.edziennik.api.v2.events.ApiTaskFinishedEvent
import pl.szczodrzynski.edziennik.api.v2.events.task.EdziennikTask
import pl.szczodrzynski.edziennik.data.db.modules.messages.Message import pl.szczodrzynski.edziennik.data.db.modules.messages.Message
import pl.szczodrzynski.edziennik.databinding.FragmentMessagesBinding import pl.szczodrzynski.edziennik.databinding.FragmentMessagesBinding
import pl.szczodrzynski.edziennik.utils.Themes import pl.szczodrzynski.edziennik.utils.Themes
@ -92,7 +86,7 @@ class MessagesFragment : Fragment() {
b.tabLayout.setupWithViewPager(b.viewPager) b.tabLayout.setupWithViewPager(b.viewPager)
if (app.profile.loginStoreType == LOGIN_TYPE_LIBRUS && app.profile.getStudentData("accountPassword", null) == null) { /*if (app.profile.loginStoreType == LOGIN_TYPE_LIBRUS && app.profile.getStudentData("accountPassword", null) == null) {
MaterialDialog.Builder(activity) MaterialDialog.Builder(activity)
.title("Wiadomości w systemie Synergia") .title("Wiadomości w systemie Synergia")
.content("Moduł Wiadomości w aplikacji Szkolny.eu jest przeglądarką zasobów szkolnego konta Synergia. Z tego powodu, musisz wpisać swoje hasło do tego konta, aby móc korzystać z tej funkcji.") .content("Moduł Wiadomości w aplikacji Szkolny.eu jest przeglądarką zasobów szkolnego konta Synergia. Z tego powodu, musisz wpisać swoje hasło do tego konta, aby móc korzystać z tej funkcji.")
@ -115,7 +109,7 @@ class MessagesFragment : Fragment() {
.show() .show()
} }
.show() .show()
} }*/
} }
@Subscribe(threadMode = ThreadMode.MAIN) @Subscribe(threadMode = ThreadMode.MAIN)

View File

@ -1,474 +0,0 @@
package pl.szczodrzynski.edziennik.ui.modules.messages;
import android.Manifest;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.app.DownloadManager;
import android.content.ActivityNotFoundException;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.graphics.Bitmap;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
import android.provider.MediaStore;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.webkit.CookieManager;
import android.webkit.MimeTypeMap;
import android.webkit.ValueCallback;
import android.webkit.WebChromeClient;
import android.webkit.WebResourceError;
import android.webkit.WebResourceRequest;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import android.widget.ProgressBar;
import android.widget.TextView;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.app.ActivityCompat;
import androidx.core.content.FileProvider;
import androidx.databinding.DataBindingUtil;
import androidx.fragment.app.Fragment;
import com.afollestad.materialdialogs.MaterialDialog;
import com.afollestad.materialdialogs.StackingBehavior;
import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.text.SimpleDateFormat;
import java.util.Date;
import pl.szczodrzynski.edziennik.App;
import pl.szczodrzynski.edziennik.R;
import pl.szczodrzynski.edziennik.databinding.FragmentMessagesWebBinding;
import pl.szczodrzynski.edziennik.utils.Anim;
import pl.szczodrzynski.edziennik.utils.Themes;
import static android.app.Activity.RESULT_OK;
import static android.content.Context.DOWNLOAD_SERVICE;
import static pl.szczodrzynski.edziennik.utils.Utils.readableFileSize;
public class MessagesWebFragment extends Fragment {
private static final String TAG = "RegisterMessagesWeb";
private App app = null;
private Activity activity = null;
private FragmentMessagesWebBinding b = null;
private WebView webView;
private ProgressBar progressBar;
private TextView error;
@Override
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
activity = getActivity();
if (getActivity() == null || getContext() == null)
return null;
app = (App) activity.getApplication();
getContext().getTheme().applyStyle(Themes.INSTANCE.getAppTheme(), true);
if (app.profile == null)
return inflater.inflate(R.layout.fragment_loading, container, false);
// activity, context and profile is valid
b = DataBindingUtil.inflate(inflater, R.layout.fragment_messages_web, container, false);
return b.getRoot();
}
private boolean isStoragePermissionGranted(Activity a) {
if (Build.VERSION.SDK_INT >= 23) {
if (a.checkSelfPermission(android.Manifest.permission.WRITE_EXTERNAL_STORAGE)
== PackageManager.PERMISSION_GRANTED) {
Log.v(TAG,"Permission is granted");
return true;
} else {
Log.v(TAG,"Permission is revoked");
ActivityCompat.requestPermissions(a, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, 1);
return false;
}
}
else { //permission is automatically granted on sdk<23 upon installation
Log.v(TAG,"Permission is granted");
return true;
}
}
private String getFileNameFromDisposition(String header)
{
return header.substring(header.indexOf("\"") + 1, header.lastIndexOf("\""));
}
private File downloadingFile;
private void enqueueFile(String url, String filename, File downloadingFile, String cookieString) {
this.downloadingFile = downloadingFile;
long downloadReference;
DownloadManager downloadManager;
downloadManager = (DownloadManager)app.getSystemService(DOWNLOAD_SERVICE);
DownloadManager.Request request = new DownloadManager.Request(Uri.parse(url));
request.setTitle(filename);
request.setDescription(getString(R.string.downloading));
request.addRequestHeader("Cookie", cookieString);
try {
request.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, filename);
}
catch (IllegalStateException e)
{
e.printStackTrace();
Toast.makeText(app, "Failed to get external storage files directory", Toast.LENGTH_SHORT).show();
}
downloadReference = downloadManager.enqueue(request);
}
private static String getMimeType(String url) {
String type = null;
String extension = MimeTypeMap.getFileExtensionFromUrl(url);
if (extension != null) {
type = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension);
}
return type;
}
private void openFile(File url) {
Toast.makeText(app, getString(R.string.opening_file, url.getName()), Toast.LENGTH_SHORT).show();
try {
Uri uri = FileProvider.getUriForFile(app, app.getApplicationContext().getPackageName() + ".provider", url);
//Uri uri = Uri.fromFile(url);
Intent intent = new Intent(Intent.ACTION_VIEW);
if (url.toString().contains(".doc") || url.toString().contains(".docx")) {
// Word document
intent.setDataAndType(uri, "application/msword");
} else if (url.toString().contains(".pdf")) {
// PDF file
intent.setDataAndType(uri, "application/pdf");
} else if (url.toString().contains(".ppt") || url.toString().contains(".pptx")) {
// Powerpoint file
intent.setDataAndType(uri, "application/vnd.ms-powerpoint");
} else if (url.toString().contains(".xls") || url.toString().contains(".xlsx")) {
// Excel file
intent.setDataAndType(uri, "application/vnd.ms-excel");
} else if (url.toString().contains(".zip") || url.toString().contains(".rar")) {
// WAV audio file
intent.setDataAndType(uri, "application/x-wav");
} else if (url.toString().contains(".rtf")) {
// RTF file
intent.setDataAndType(uri, "application/rtf");
} else if (url.toString().contains(".wav") || url.toString().contains(".mp3")) {
// WAV audio file
intent.setDataAndType(uri, "audio/x-wav");
} else if (url.toString().contains(".gif")) {
// GIF file
intent.setDataAndType(uri, "image/gif");
} else if (url.toString().contains(".jpg") || url.toString().contains(".jpeg") || url.toString().contains(".png")) {
// JPG file
intent.setDataAndType(uri, "image/jpeg");
} else if (url.toString().contains(".txt")) {
// Text file
intent.setDataAndType(uri, "text/plain");
} else if (url.toString().contains(".3gp") || url.toString().contains(".mpg") ||
url.toString().contains(".mpeg") || url.toString().contains(".mpe") || url.toString().contains(".mp4") || url.toString().contains(".avi")) {
// Video files
intent.setDataAndType(uri, "video/*");
} else {
intent.setDataAndType(uri, "*/*");
}
intent.setDataAndType(uri, getMimeType(uri.toString()));
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
app.startActivity(intent);
} catch (ActivityNotFoundException e) {
Toast.makeText(app, R.string.opening_file_no_app, Toast.LENGTH_SHORT).show();
}
}
private void downloadFile(Activity a, String url, String filename, long contentLength, String cookieString) {
if (!isStoragePermissionGranted(a))
return;
new MaterialDialog.Builder(a)
.title(R.string.downloading_file)
.content(getString((R.string.download_file_question), filename, readableFileSize(contentLength)))
.positiveText(R.string.yes)
.negativeText(R.string.no)
.onPositive((dialog, which) -> {
File downloadsDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS);
File existingFile = new File(downloadsDir, filename);
if (existingFile.exists()) {
new MaterialDialog.Builder(a)
.title(R.string.downloading_file)
.content(getString(R.string.downloading_file_exists_choose, filename))
.stackingBehavior(StackingBehavior.ADAPTIVE)
.positiveText(R.string.downloading_file_exists_overwrite)
.negativeText(R.string.downloading_file_exists_open)
.neutralText(R.string.downloading_file_exists_create_new)
.onPositive(((dialog1, which1) -> {
if (!existingFile.delete())
{
new MaterialDialog.Builder(a)
.title(R.string.downloading_file)
.content(R.string.downloading_file_cannot_remove)
.positiveText(R.string.ok)
.negativeText(R.string.cancel)
.onPositive(((dialog2, which2) -> enqueueFile(url, filename, existingFile, cookieString)))
.show();
return;
}
enqueueFile(url, filename, existingFile, cookieString);
}))
.onNegative(((dialog1, which1) -> openFile(existingFile)))
.onNeutral((dialog1, which1) -> enqueueFile(url, filename, existingFile, cookieString))
.show();
return;
}
enqueueFile(url, filename, existingFile, cookieString);
})
.show();
}
private String photoPath;
private ValueCallback<Uri> mUM;
private ValueCallback<Uri[]> fileCallback;
private final static int REQUEST_FILE_CHOOSER = 1;
private boolean justLoaded = false;
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if(Build.VERSION.SDK_INT >= 21){
Uri[] results = null;
//Check if response is positive
if(resultCode == RESULT_OK){
if(requestCode == REQUEST_FILE_CHOOSER){
if(null == fileCallback){
return;
}
if(data == null){
//Capture Photo if no image available
if (photoPath != null) {
results = new Uri[]{Uri.parse(photoPath)};
}
}else{
String dataString = data.getDataString();
if(dataString != null){
results = new Uri[]{Uri.parse(dataString)};
}
}
}
}
fileCallback.onReceiveValue(results);
fileCallback = null;
} else {
if (requestCode == REQUEST_FILE_CHOOSER) {
if(null == mUM) return;
Uri result = data == null || resultCode != RESULT_OK ? null : data.getData();
mUM.onReceiveValue(result);
mUM = null;
}
}
}
private File createImageFile() throws IOException{
@SuppressLint("SimpleDateFormat") String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date());
String imageFileName = "img_"+timeStamp+"_";
File storageDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES);
return File.createTempFile(imageFileName,".jpg",storageDir);
}
@SuppressLint("SetJavaScriptEnabled")
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
if (app == null || app.profile == null || activity == null || b == null || !isAdded())
return;
webView = b.messagesWebView;
progressBar = b.messagesWebProgressBar;
error = b.messagesWebError;
justLoaded = true;
new Handler().postDelayed(() -> activity.runOnUiThread(() -> {
if (app == null || app.profile == null || activity == null || b == null || !isAdded())
return;
BroadcastReceiver onComplete = new BroadcastReceiver() {
public void onReceive(Context context, Intent intent) {
if (downloadingFile != null) {
openFile(downloadingFile);
downloadingFile = null;
}
}
};
activity.registerReceiver(onComplete, new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE));
app.apiEdziennik.initMessagesWebView(webView, app, /*app.profile.messagesWebFullVersion*/false, false);
webView.getSettings().setJavaScriptEnabled(true);
webView.getSettings().setAllowFileAccess(true);
webView.setDownloadListener((url, userAgent, contentDisposition, mimetype, contentLength) -> {
String filename = getFileNameFromDisposition(contentDisposition);
try {
URL urlObj = new URL(url);
Log.d(TAG, "Host "+urlObj.getProtocol()+"://"+urlObj.getHost());
Log.d(TAG, "Cookies "+CookieManager.getInstance().getCookie(urlObj.getProtocol()+"://"+urlObj.getHost()));
downloadFile(getActivity(), url, filename, contentLength, CookieManager.getInstance().getCookie(urlObj.getProtocol()+"://"+urlObj.getHost()));
} catch (MalformedURLException e) {
e.printStackTrace();
}
Anim.fadeOut(progressBar, 400, null);
});
webView.setWebViewClient(new WebViewClient() {
boolean loadingFinished = true;
boolean redirect = false;
@Override
public boolean shouldOverrideUrlLoading(WebView view, String request) {
if (!loadingFinished) {
redirect = true;
}
loadingFinished = false;
webView.loadUrl(request);
return true;
}
@Override
public void onPageStarted(WebView view, String url, Bitmap favicon) {
super.onPageStarted(view, url, favicon);
MessagesWebFragment.this.error.setVisibility(View.GONE);
loadingFinished = false;
//SHOW LOADING IF IT ISNT ALREADY VISIBLE
if (progressBar.getVisibility() != View.VISIBLE)
Anim.fadeIn(progressBar, 400, null);
}
@Override
public void onPageFinished(WebView view, String url) {
if (!redirect) {
loadingFinished = true;
}
if (loadingFinished && !redirect) {
//HIDE LOADING IT HAS FINISHED
//String cookies = CookieManager.getInstance().getCookie(url);
//Log.d(TAG, "All the cookies in a string:" + cookies);
Anim.fadeOut(progressBar, 400, null);
/*if (app.profile.messagesWebFullVersion && justLoaded && app.profile.loginType == Register.LOGIN_TYPE_MOBIDZIENNIK) {
if (!webView.getUrl().contains("wiadomosci")) {
// redirect to messages view
webView.loadUrl("https://" + app.getLoginData("serverName", "") + ".mobidziennik.pl/mobile/wiadomosci");
}
justLoaded = false;
}*/
} else {
redirect = false;
}
}
@Override
public void onReceivedError(WebView view, WebResourceRequest request, WebResourceError error) {
if (app == null || app.profile == null || activity == null || b == null || !isAdded())
return;
MessagesWebFragment.this.error.setVisibility(View.VISIBLE);
MessagesWebFragment.this.error.setText(getString(R.string.error_occured_format, error.toString()));
super.onReceivedError(view, request, error);
}
});
if(Build.VERSION.SDK_INT >= 21) {
webView.getSettings().setMixedContentMode(0);
webView.setLayerType(View.LAYER_TYPE_HARDWARE, null);
} else if(Build.VERSION.SDK_INT >= 19) {
webView.setLayerType(View.LAYER_TYPE_HARDWARE, null);
} else {
webView.setLayerType(View.LAYER_TYPE_SOFTWARE, null);
}
webView.setWebChromeClient(new WebChromeClient(){
//For Android 5.0+
public boolean onShowFileChooser(
WebView webView, ValueCallback<Uri[]> filePathCallback,
WebChromeClient.FileChooserParams fileChooserParams){
if(fileCallback != null){
fileCallback.onReceiveValue(null);
}
fileCallback = filePathCallback;
Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
if(takePictureIntent.resolveActivity(app.getPackageManager()) != null){
File photoFile = null;
try{
photoFile = createImageFile();
takePictureIntent.putExtra("PhotoPath", photoPath);
}catch(IOException ex){
Log.e(TAG, "Image file creation failed", ex);
}
if(photoFile != null){
photoPath = "file:" + photoFile.getAbsolutePath();
takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(photoFile));
}else{
takePictureIntent = null;
}
}
Intent contentSelectionIntent = new Intent(Intent.ACTION_GET_CONTENT);
contentSelectionIntent.addCategory(Intent.CATEGORY_OPENABLE);
contentSelectionIntent.setType("*/*");
Intent[] intentArray;
if(takePictureIntent != null){
intentArray = new Intent[]{takePictureIntent};
}else{
intentArray = new Intent[0];
}
Intent chooserIntent = new Intent(Intent.ACTION_CHOOSER);
chooserIntent.putExtra(Intent.EXTRA_INTENT, contentSelectionIntent);
chooserIntent.putExtra(Intent.EXTRA_TITLE, R.string.choose_file);
chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, intentArray);
startActivityForResult(chooserIntent, REQUEST_FILE_CHOOSER);
return true;
}
});
}), 200);
}
/*public void loadVersion(boolean fullVersion) {
if (app.profile.messagesWebFullVersion != fullVersion) {
app.profile.messagesWebFullVersion = fullVersion;
app.profile.savePending = true;
justLoaded = true;
app.apiEdziennik.initMessagesWebView(webView, app, fullVersion, true);
}
}*/
public void performReload() {
webView.reload();
}
public boolean processBackKey()
{
if (webView.canGoBack())
{
webView.goBack();
return true;
}
return false;
}
}

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