mirror of
https://github.com/szkolny-eu/szkolny-android.git
synced 2025-06-15 07:00:20 +02:00
Compare commits
43 Commits
v4.0-beta.
...
v4.0-beta.
Author | SHA1 | Date | |
---|---|---|---|
40cdc7d713 | |||
49825aca48 | |||
1d57c4e705 | |||
87ae5787ee | |||
20f16c25a3 | |||
6f1ec79d9b | |||
18c7eea89c | |||
f73060aeb6 | |||
2f653b83b6 | |||
445dec907d | |||
927316d24b | |||
3957453ed6 | |||
0296c704cb | |||
1e7fe972de | |||
c95bc656ea | |||
a082d95b04 | |||
6866dd4801 | |||
2186da416e | |||
22d859fcde | |||
39514b69b3 | |||
c384736840 | |||
507657f273 | |||
60641742ed | |||
0fc6f07986 | |||
1b2bdc0580 | |||
9bac239f77 | |||
371acb2d2a | |||
454f82e139 | |||
e8da249353 | |||
c7950c53da | |||
b5502478e4 | |||
4480a7e486 | |||
7c7dff743b | |||
c568cd3f2e | |||
6ec2bc6f21 | |||
af3b6f3a97 | |||
d855118610 | |||
c9992d9fe8 | |||
85fe2636cc | |||
35f4a31a76 | |||
1e494ebb70 | |||
ed93627505 | |||
b9b4b0036f |
@ -62,7 +62,7 @@
|
|||||||
android:configChanges="orientation|keyboardHidden"
|
android:configChanges="orientation|keyboardHidden"
|
||||||
android:excludeFromRecents="true"
|
android:excludeFromRecents="true"
|
||||||
android:noHistory="true"
|
android:noHistory="true"
|
||||||
android:theme="@style/AppTheme.NoDisplay">
|
android:theme="@style/AppTheme.Dark.NoDisplay">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.appwidget.action.APPWIDGET_CONFIGURE" />
|
<action android:name="android.appwidget.action.APPWIDGET_CONFIGURE" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
@ -84,7 +84,7 @@
|
|||||||
android:configChanges="orientation|keyboardHidden"
|
android:configChanges="orientation|keyboardHidden"
|
||||||
android:excludeFromRecents="true"
|
android:excludeFromRecents="true"
|
||||||
android:noHistory="true"
|
android:noHistory="true"
|
||||||
android:theme="@style/AppTheme.NoDisplay" />
|
android:theme="@style/AppTheme.Dark.NoDisplay" />
|
||||||
<!-- NOTIFICATIONS -->
|
<!-- NOTIFICATIONS -->
|
||||||
<receiver android:name=".ui.widgets.notifications.WidgetNotificationsProvider"
|
<receiver android:name=".ui.widgets.notifications.WidgetNotificationsProvider"
|
||||||
android:label="@string/widget_notifications_title">
|
android:label="@string/widget_notifications_title">
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
<h3>Wersja 4.0-beta.12, 2020-03-10</h3>
|
<h3>Wersja 4.0-beta.14, 2020-03-24</h3>
|
||||||
<ul>
|
<ul>
|
||||||
<li><b>Przebudowaliśmy cały moduł synchronizacji</b>, co oznacza większą stabilność aplikacji, szybkość oraz poprawność pobieranych danych.</li>
|
<li><b>Przebudowaliśmy cały moduł synchronizacji</b>, co oznacza większą stabilność aplikacji, szybkość oraz poprawność pobieranych danych.</li>
|
||||||
<li><b><u>Wysyłanie wiadomości</u></b> - funkcja, na którą czekał każdy. Od teraz w Szkolnym można wysyłać oraz odpowiadać na wiadomości do nauczycieli 👏</li>
|
<li><b><u>Wysyłanie wiadomości</u></b> - funkcja, na którą czekał każdy. Od teraz w Szkolnym można wysyłać oraz odpowiadać na wiadomości do nauczycieli 👏</li>
|
||||||
|
@ -9,7 +9,7 @@
|
|||||||
|
|
||||||
/*secret password - removed for source code publication*/
|
/*secret password - removed for source code publication*/
|
||||||
static toys AES_IV[16] = {
|
static toys AES_IV[16] = {
|
||||||
0xb8, 0x59, 0x75, 0xc7, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff };
|
0xc4, 0x97, 0xfb, 0xbd, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff };
|
||||||
|
|
||||||
unsigned char *agony(unsigned int laugh, unsigned char *box, unsigned char *heat);
|
unsigned char *agony(unsigned int laugh, unsigned char *box, unsigned char *heat);
|
||||||
|
|
||||||
|
@ -1,21 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
20
app/src/main/java/pl/szczodrzynski/edziennik/Binding.kt
Normal file
20
app/src/main/java/pl/szczodrzynski/edziennik/Binding.kt
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) Kuba Szczodrzyński 2019-11-11.
|
||||||
|
*/
|
||||||
|
package pl.szczodrzynski.edziennik
|
||||||
|
|
||||||
|
import android.graphics.Paint
|
||||||
|
import android.widget.TextView
|
||||||
|
import androidx.databinding.BindingAdapter
|
||||||
|
|
||||||
|
object Binding {
|
||||||
|
@JvmStatic
|
||||||
|
@BindingAdapter("strikeThrough")
|
||||||
|
fun strikeThrough(textView: TextView, strikeThrough: Boolean) {
|
||||||
|
if (strikeThrough) {
|
||||||
|
textView.paintFlags = textView.paintFlags or Paint.STRIKE_THRU_TEXT_FLAG
|
||||||
|
} else {
|
||||||
|
textView.paintFlags = textView.paintFlags and Paint.STRIKE_THRU_TEXT_FLAG.inv()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -2,6 +2,8 @@ package pl.szczodrzynski.edziennik
|
|||||||
|
|
||||||
import android.Manifest
|
import android.Manifest
|
||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
|
import android.content.ClipData
|
||||||
|
import android.content.ClipboardManager
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.pm.PackageManager
|
import android.content.pm.PackageManager
|
||||||
@ -10,6 +12,7 @@ import android.content.res.Resources
|
|||||||
import android.database.Cursor
|
import android.database.Cursor
|
||||||
import android.graphics.PorterDuff
|
import android.graphics.PorterDuff
|
||||||
import android.graphics.PorterDuffColorFilter
|
import android.graphics.PorterDuffColorFilter
|
||||||
|
import android.graphics.Rect
|
||||||
import android.graphics.Typeface
|
import android.graphics.Typeface
|
||||||
import android.graphics.drawable.Drawable
|
import android.graphics.drawable.Drawable
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
@ -23,6 +26,7 @@ import android.util.Base64
|
|||||||
import android.util.Base64.NO_WRAP
|
import android.util.Base64.NO_WRAP
|
||||||
import android.util.Base64.encodeToString
|
import android.util.Base64.encodeToString
|
||||||
import android.view.View
|
import android.view.View
|
||||||
|
import android.view.WindowManager
|
||||||
import android.widget.CheckBox
|
import android.widget.CheckBox
|
||||||
import android.widget.CompoundButton
|
import android.widget.CompoundButton
|
||||||
import android.widget.RadioButton
|
import android.widget.RadioButton
|
||||||
@ -51,6 +55,7 @@ import okhttp3.RequestBody
|
|||||||
import okhttp3.TlsVersion
|
import okhttp3.TlsVersion
|
||||||
import okio.Buffer
|
import okio.Buffer
|
||||||
import pl.szczodrzynski.edziennik.data.api.*
|
import pl.szczodrzynski.edziennik.data.api.*
|
||||||
|
import pl.szczodrzynski.edziennik.data.api.models.ApiError
|
||||||
import pl.szczodrzynski.edziennik.data.api.szkolny.SzkolnyApiException
|
import pl.szczodrzynski.edziennik.data.api.szkolny.SzkolnyApiException
|
||||||
import pl.szczodrzynski.edziennik.data.api.szkolny.response.ApiResponse
|
import pl.szczodrzynski.edziennik.data.api.szkolny.response.ApiResponse
|
||||||
import pl.szczodrzynski.edziennik.data.db.entity.Notification
|
import pl.szczodrzynski.edziennik.data.db.entity.Notification
|
||||||
@ -1080,6 +1085,7 @@ fun Throwable.toErrorCode() = when (this) {
|
|||||||
private fun ApiResponse.Error.toErrorCode() = when (this.code) {
|
private fun ApiResponse.Error.toErrorCode() = when (this.code) {
|
||||||
else -> ERROR_API_EXCEPTION
|
else -> ERROR_API_EXCEPTION
|
||||||
}
|
}
|
||||||
|
fun Throwable.toApiError(tag: String) = ApiError.fromThrowable(tag, this)
|
||||||
|
|
||||||
inline fun <A, B, R> ifNotNull(a: A?, b: B?, code: (A, B) -> R): R? {
|
inline fun <A, B, R> ifNotNull(a: A?, b: B?, code: (A, B) -> R): R? {
|
||||||
if (a != null && b != null) {
|
if (a != null && b != null) {
|
||||||
@ -1092,3 +1098,71 @@ inline fun <A, B, R> ifNotNull(a: A?, b: B?, code: (A, B) -> R): R? {
|
|||||||
fun Iterable<Int>.averageOrNull() = this.average().let { if (it.isNaN()) null else it }
|
fun Iterable<Int>.averageOrNull() = this.average().let { if (it.isNaN()) null else it }
|
||||||
@kotlin.jvm.JvmName("averageOrNullOfFloat")
|
@kotlin.jvm.JvmName("averageOrNullOfFloat")
|
||||||
fun Iterable<Float>.averageOrNull() = this.average().let { if (it.isNaN()) null else it }
|
fun Iterable<Float>.averageOrNull() = this.average().let { if (it.isNaN()) null else it }
|
||||||
|
|
||||||
|
fun String.copyToClipboard(context: Context) {
|
||||||
|
val clipboard = context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
|
||||||
|
val clipData = ClipData.newPlainText("Tekst", this)
|
||||||
|
clipboard.primaryClip = clipData
|
||||||
|
}
|
||||||
|
|
||||||
|
fun TextView.getTextPosition(range: IntRange): Rect {
|
||||||
|
val windowManager = context.getSystemService(Context.WINDOW_SERVICE) as WindowManager
|
||||||
|
|
||||||
|
// Initialize global value
|
||||||
|
var parentTextViewRect = Rect()
|
||||||
|
|
||||||
|
// Initialize values for the computing of clickedText position
|
||||||
|
//val completeText = parentTextView.text as SpannableString
|
||||||
|
val textViewLayout = this.layout
|
||||||
|
|
||||||
|
val startOffsetOfClickedText = range.first//completeText.getSpanStart(clickedText)
|
||||||
|
val endOffsetOfClickedText = range.last//completeText.getSpanEnd(clickedText)
|
||||||
|
var startXCoordinatesOfClickedText = textViewLayout.getPrimaryHorizontal(startOffsetOfClickedText)
|
||||||
|
var endXCoordinatesOfClickedText = textViewLayout.getPrimaryHorizontal(endOffsetOfClickedText)
|
||||||
|
|
||||||
|
// Get the rectangle of the clicked text
|
||||||
|
val currentLineStartOffset = textViewLayout.getLineForOffset(startOffsetOfClickedText)
|
||||||
|
val currentLineEndOffset = textViewLayout.getLineForOffset(endOffsetOfClickedText)
|
||||||
|
val keywordIsInMultiLine = currentLineStartOffset != currentLineEndOffset
|
||||||
|
textViewLayout.getLineBounds(currentLineStartOffset, parentTextViewRect)
|
||||||
|
|
||||||
|
// Update the rectangle position to his real position on screen
|
||||||
|
val parentTextViewLocation = intArrayOf(0, 0)
|
||||||
|
this.getLocationOnScreen(parentTextViewLocation)
|
||||||
|
|
||||||
|
val parentTextViewTopAndBottomOffset = (parentTextViewLocation[1] - this.scrollY + this.compoundPaddingTop)
|
||||||
|
parentTextViewRect.top += parentTextViewTopAndBottomOffset
|
||||||
|
parentTextViewRect.bottom += parentTextViewTopAndBottomOffset
|
||||||
|
|
||||||
|
// In the case of multi line text, we have to choose what rectangle take
|
||||||
|
if (keywordIsInMultiLine) {
|
||||||
|
val screenHeight = windowManager.defaultDisplay.height
|
||||||
|
val dyTop = parentTextViewRect.top
|
||||||
|
val dyBottom = screenHeight - parentTextViewRect.bottom
|
||||||
|
val onTop = dyTop > dyBottom
|
||||||
|
|
||||||
|
if (onTop) {
|
||||||
|
endXCoordinatesOfClickedText = textViewLayout.getLineRight(currentLineStartOffset);
|
||||||
|
} else {
|
||||||
|
parentTextViewRect = Rect()
|
||||||
|
textViewLayout.getLineBounds(currentLineEndOffset, parentTextViewRect);
|
||||||
|
parentTextViewRect.top += parentTextViewTopAndBottomOffset;
|
||||||
|
parentTextViewRect.bottom += parentTextViewTopAndBottomOffset;
|
||||||
|
startXCoordinatesOfClickedText = textViewLayout.getLineLeft(currentLineEndOffset);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
parentTextViewRect.left += (
|
||||||
|
parentTextViewLocation[0] +
|
||||||
|
startXCoordinatesOfClickedText +
|
||||||
|
this.compoundPaddingLeft -
|
||||||
|
this.scrollX
|
||||||
|
).toInt()
|
||||||
|
parentTextViewRect.right = (
|
||||||
|
parentTextViewRect.left +
|
||||||
|
endXCoordinatesOfClickedText -
|
||||||
|
startXCoordinatesOfClickedText
|
||||||
|
).toInt()
|
||||||
|
|
||||||
|
return parentTextViewRect
|
||||||
|
}
|
||||||
|
@ -49,6 +49,7 @@ import pl.szczodrzynski.edziennik.sync.SyncWorker
|
|||||||
import pl.szczodrzynski.edziennik.sync.UpdateWorker
|
import pl.szczodrzynski.edziennik.sync.UpdateWorker
|
||||||
import pl.szczodrzynski.edziennik.ui.dialogs.ServerMessageDialog
|
import pl.szczodrzynski.edziennik.ui.dialogs.ServerMessageDialog
|
||||||
import pl.szczodrzynski.edziennik.ui.dialogs.changelog.ChangelogDialog
|
import pl.szczodrzynski.edziennik.ui.dialogs.changelog.ChangelogDialog
|
||||||
|
import pl.szczodrzynski.edziennik.ui.dialogs.event.EventManualDialog
|
||||||
import pl.szczodrzynski.edziennik.ui.dialogs.settings.ProfileRemoveDialog
|
import pl.szczodrzynski.edziennik.ui.dialogs.settings.ProfileRemoveDialog
|
||||||
import pl.szczodrzynski.edziennik.ui.dialogs.sync.SyncViewListDialog
|
import pl.szczodrzynski.edziennik.ui.dialogs.sync.SyncViewListDialog
|
||||||
import pl.szczodrzynski.edziennik.ui.modules.agenda.AgendaFragment
|
import pl.szczodrzynski.edziennik.ui.modules.agenda.AgendaFragment
|
||||||
@ -444,6 +445,8 @@ class MainActivity : AppCompatActivity(), CoroutineScope {
|
|||||||
|
|
||||||
// WHAT'S NEW DIALOG
|
// WHAT'S NEW DIALOG
|
||||||
if (app.config.appVersion < BuildConfig.VERSION_CODE) {
|
if (app.config.appVersion < BuildConfig.VERSION_CODE) {
|
||||||
|
// force an AppSync after update
|
||||||
|
app.config.sync.lastAppSync = 0L
|
||||||
ChangelogDialog(this)
|
ChangelogDialog(this)
|
||||||
if (app.config.appVersion < 170) {
|
if (app.config.appVersion < 170) {
|
||||||
//Intent intent = new Intent(this, ChangelogIntroActivity.class);
|
//Intent intent = new Intent(this, ChangelogIntroActivity.class);
|
||||||
@ -721,6 +724,15 @@ class MainActivity : AppCompatActivity(), CoroutineScope {
|
|||||||
)
|
)
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
"createManualEvent" -> {
|
||||||
|
val date = extras.getString("eventDate")?.let { Date.fromY_m_d(it) } ?: Date.getToday()
|
||||||
|
EventManualDialog(
|
||||||
|
this,
|
||||||
|
App.profileId,
|
||||||
|
defaultDate = date
|
||||||
|
)
|
||||||
|
true
|
||||||
|
}
|
||||||
else -> false
|
else -> false
|
||||||
}
|
}
|
||||||
if (handled && !navLoading) {
|
if (handled && !navLoading) {
|
||||||
@ -1139,7 +1151,8 @@ class MainActivity : AppCompatActivity(), CoroutineScope {
|
|||||||
private var targetHomeId: Int = -1
|
private var targetHomeId: Int = -1
|
||||||
override fun onBackPressed() {
|
override fun onBackPressed() {
|
||||||
if (!b.navView.onBackPressed()) {
|
if (!b.navView.onBackPressed()) {
|
||||||
if (App.config.ui.openDrawerOnBackPressed) {
|
if (App.config.ui.openDrawerOnBackPressed && ((navTarget.popTo == null && navTarget.popToHome)
|
||||||
|
|| navTarget.id == DRAWER_ITEM_HOME)) {
|
||||||
b.navView.drawer.toggle()
|
b.navView.drawer.toggle()
|
||||||
} else {
|
} else {
|
||||||
navigateUp()
|
navigateUp()
|
||||||
|
@ -105,11 +105,6 @@ class Config(val db: AppDb) : CoroutineScope, AbstractConfig {
|
|||||||
get() { mWidgetConfigs = mWidgetConfigs ?: values.get("widgetConfigs", JsonObject()); return mWidgetConfigs ?: JsonObject() }
|
get() { mWidgetConfigs = mWidgetConfigs ?: values.get("widgetConfigs", JsonObject()); return mWidgetConfigs ?: JsonObject() }
|
||||||
set(value) { set("widgetConfigs", value); mWidgetConfigs = value }
|
set(value) { set("widgetConfigs", value); mWidgetConfigs = value }
|
||||||
|
|
||||||
private var mLastAppSync: Long? = null
|
|
||||||
var lastAppSync: Long
|
|
||||||
get() { mLastAppSync = mLastAppSync ?: values.get("lastAppSync", 0L); return mLastAppSync ?: 0L }
|
|
||||||
set(value) { set("lastAppSync", value); mLastAppSync = value }
|
|
||||||
|
|
||||||
private var rawEntries: List<ConfigEntry> = db.configDao().getAllNow()
|
private var rawEntries: List<ConfigEntry> = db.configDao().getAllNow()
|
||||||
private val profileConfigs: HashMap<Int, ProfileConfig> = hashMapOf()
|
private val profileConfigs: HashMap<Int, ProfileConfig> = hashMapOf()
|
||||||
init {
|
init {
|
||||||
|
@ -20,6 +20,11 @@ class ConfigSync(private val config: Config) {
|
|||||||
get() { mSyncEnabled = mSyncEnabled ?: config.values.get("syncEnabled", true); return mSyncEnabled ?: true }
|
get() { mSyncEnabled = mSyncEnabled ?: config.values.get("syncEnabled", true); return mSyncEnabled ?: true }
|
||||||
set(value) { config.set("syncEnabled", value); mSyncEnabled = value }
|
set(value) { config.set("syncEnabled", value); mSyncEnabled = value }
|
||||||
|
|
||||||
|
private var mWebPushEnabled: Boolean? = null
|
||||||
|
var webPushEnabled: Boolean
|
||||||
|
get() { mWebPushEnabled = mWebPushEnabled ?: config.values.get("webPushEnabled", true); return mWebPushEnabled ?: true }
|
||||||
|
set(value) { config.set("webPushEnabled", value); mWebPushEnabled = value }
|
||||||
|
|
||||||
private var mSyncOnlyWifi: Boolean? = null
|
private var mSyncOnlyWifi: Boolean? = null
|
||||||
var onlyWifi: Boolean
|
var onlyWifi: Boolean
|
||||||
get() { mSyncOnlyWifi = mSyncOnlyWifi ?: config.values.get("syncOnlyWifi", false); return mSyncOnlyWifi ?: notifyAboutUpdates }
|
get() { mSyncOnlyWifi = mSyncOnlyWifi ?: config.values.get("syncOnlyWifi", false); return mSyncOnlyWifi ?: notifyAboutUpdates }
|
||||||
@ -35,6 +40,11 @@ class ConfigSync(private val config: Config) {
|
|||||||
get() { mNotifyAboutUpdates = mNotifyAboutUpdates ?: config.values.get("notifyAboutUpdates", true); return mNotifyAboutUpdates ?: true }
|
get() { mNotifyAboutUpdates = mNotifyAboutUpdates ?: config.values.get("notifyAboutUpdates", true); return mNotifyAboutUpdates ?: true }
|
||||||
set(value) { config.set("notifyAboutUpdates", value); mNotifyAboutUpdates = value }
|
set(value) { config.set("notifyAboutUpdates", value); mNotifyAboutUpdates = value }
|
||||||
|
|
||||||
|
private var mLastAppSync: Long? = null
|
||||||
|
var lastAppSync: Long
|
||||||
|
get() { mLastAppSync = mLastAppSync ?: config.values.get("lastAppSync", 0L); return mLastAppSync ?: 0L }
|
||||||
|
set(value) { config.set("lastAppSync", value); mLastAppSync = value }
|
||||||
|
|
||||||
/* ____ _ _ _
|
/* ____ _ _ _
|
||||||
/ __ \ (_) | | | |
|
/ __ \ (_) | | | |
|
||||||
| | | |_ _ _ ___| |_ | |__ ___ _ _ _ __ ___
|
| | | |_ _ _ ___| |_ | |__ ___ _ _ _ __ ___
|
||||||
|
@ -21,11 +21,6 @@ class ProfileConfigGrades(private val config: ProfileConfig) {
|
|||||||
get() { mYearAverageMode = mYearAverageMode ?: config.values.get("yearAverageMode", YEAR_ALL_GRADES); return mYearAverageMode ?: YEAR_ALL_GRADES }
|
get() { mYearAverageMode = mYearAverageMode ?: config.values.get("yearAverageMode", YEAR_ALL_GRADES); return mYearAverageMode ?: YEAR_ALL_GRADES }
|
||||||
set(value) { config.set("yearAverageMode", value); mYearAverageMode = value }
|
set(value) { config.set("yearAverageMode", value); mYearAverageMode = value }
|
||||||
|
|
||||||
private var mCountZeroToAvg: Boolean? = null
|
|
||||||
var countZeroToAvg: Boolean
|
|
||||||
get() { mCountZeroToAvg = mCountZeroToAvg ?: config.values.get("countZeroToAvg", true); return mCountZeroToAvg ?: true }
|
|
||||||
set(value) { config.set("countZeroToAvg", value); mCountZeroToAvg = value }
|
|
||||||
|
|
||||||
private var mHideImproved: Boolean? = null
|
private var mHideImproved: Boolean? = null
|
||||||
var hideImproved: Boolean
|
var hideImproved: Boolean
|
||||||
get() { mHideImproved = mHideImproved ?: config.values.get("hideImproved", false); return mHideImproved ?: false }
|
get() { mHideImproved = mHideImproved ?: config.values.get("hideImproved", false); return mHideImproved ?: false }
|
||||||
@ -45,6 +40,11 @@ class ProfileConfigGrades(private val config: ProfileConfig) {
|
|||||||
get() { mMinusValue = mMinusValue ?: config.values.getFloat("minusValue"); return mMinusValue }
|
get() { mMinusValue = mMinusValue ?: config.values.getFloat("minusValue"); return mMinusValue }
|
||||||
set(value) { config.set("minusValue", value); mMinusValue = value }
|
set(value) { config.set("minusValue", value); mMinusValue = value }
|
||||||
|
|
||||||
|
private var mDontCountEnabled: Boolean? = null
|
||||||
|
var dontCountEnabled: Boolean
|
||||||
|
get() { mDontCountEnabled = mDontCountEnabled ?: config.values.get("dontCountEnabled", false); return mDontCountEnabled ?: false }
|
||||||
|
set(value) { config.set("dontCountEnabled", value); mDontCountEnabled = value }
|
||||||
|
|
||||||
private var mDontCountGrades: List<String>? = null
|
private var mDontCountGrades: List<String>? = null
|
||||||
var dontCountGrades: List<String>
|
var dontCountGrades: List<String>
|
||||||
get() { mDontCountGrades = mDontCountGrades ?: config.values.get("dontCountGrades", listOf()); return mDontCountGrades ?: listOf() }
|
get() { mDontCountGrades = mDontCountGrades ?: config.values.get("dontCountGrades", listOf()); return mDontCountGrades ?: listOf() }
|
||||||
|
@ -14,7 +14,7 @@ class ProfileConfigMigration(config: ProfileConfig) {
|
|||||||
|
|
||||||
if (dataVersion < 1) {
|
if (dataVersion < 1) {
|
||||||
grades.colorMode = COLOR_MODE_WEIGHTED
|
grades.colorMode = COLOR_MODE_WEIGHTED
|
||||||
grades.countZeroToAvg = true
|
grades.dontCountEnabled = false
|
||||||
grades.yearAverageMode = YEAR_ALL_GRADES
|
grades.yearAverageMode = YEAR_ALL_GRADES
|
||||||
ui.agendaViewType = AGENDA_DEFAULT
|
ui.agendaViewType = AGENDA_DEFAULT
|
||||||
|
|
||||||
|
@ -22,6 +22,7 @@ import pl.szczodrzynski.edziennik.data.api.task.ErrorReportTask
|
|||||||
import pl.szczodrzynski.edziennik.data.api.task.IApiTask
|
import pl.szczodrzynski.edziennik.data.api.task.IApiTask
|
||||||
import pl.szczodrzynski.edziennik.data.api.task.SzkolnyTask
|
import pl.szczodrzynski.edziennik.data.api.task.SzkolnyTask
|
||||||
import pl.szczodrzynski.edziennik.data.db.entity.Profile
|
import pl.szczodrzynski.edziennik.data.db.entity.Profile
|
||||||
|
import pl.szczodrzynski.edziennik.toApiError
|
||||||
import pl.szczodrzynski.edziennik.utils.Utils.d
|
import pl.szczodrzynski.edziennik.utils.Utils.d
|
||||||
import kotlin.math.min
|
import kotlin.math.min
|
||||||
import kotlin.math.roundToInt
|
import kotlin.math.roundToInt
|
||||||
@ -181,7 +182,7 @@ class ApiService : Service() {
|
|||||||
is SzkolnyTask -> task.run(taskCallback)
|
is SzkolnyTask -> task.run(taskCallback)
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
taskCallback.onError(ApiError(TAG, EXCEPTION_API_TASK).withThrowable(e))
|
taskCallback.onError(e.toApiError(TAG))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -124,6 +124,8 @@ const val ERROR_LIBRUS_PORTAL_MAINTENANCE = 182
|
|||||||
const val ERROR_LIBRUS_API_NOTICEBOARD_PROBLEM = 183
|
const val ERROR_LIBRUS_API_NOTICEBOARD_PROBLEM = 183
|
||||||
const val ERROR_LOGIN_LIBRUS_PORTAL_CSRF_EXPIRED = 184
|
const val ERROR_LOGIN_LIBRUS_PORTAL_CSRF_EXPIRED = 184
|
||||||
const val ERROR_LIBRUS_API_DEVICE_REGISTERED = 185
|
const val ERROR_LIBRUS_API_DEVICE_REGISTERED = 185
|
||||||
|
const val ERROR_LIBRUS_MESSAGES_NOT_FOUND = 186
|
||||||
|
const val ERROR_LOGIN_LIBRUS_API_INVALID_REQUEST = 187
|
||||||
|
|
||||||
const val ERROR_LOGIN_MOBIDZIENNIK_WEB_INVALID_LOGIN = 201
|
const val ERROR_LOGIN_MOBIDZIENNIK_WEB_INVALID_LOGIN = 201
|
||||||
const val ERROR_LOGIN_MOBIDZIENNIK_WEB_OLD_PASSWORD = 202
|
const val ERROR_LOGIN_MOBIDZIENNIK_WEB_OLD_PASSWORD = 202
|
||||||
@ -140,6 +142,7 @@ 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
|
||||||
const val ERROR_LOGIN_MOBIDZIENNIK_API2_INVALID_LOGIN = 216
|
const val ERROR_LOGIN_MOBIDZIENNIK_API2_INVALID_LOGIN = 216
|
||||||
const val ERROR_LOGIN_MOBIDZIENNIK_API2_OTHER = 217
|
const val ERROR_LOGIN_MOBIDZIENNIK_API2_OTHER = 217
|
||||||
|
const val ERROR_MOBIDZIENNIK_WEB_SERVER_PROBLEM = 218
|
||||||
|
|
||||||
const val ERROR_LOGIN_VULCAN_INVALID_SYMBOL = 301
|
const val ERROR_LOGIN_VULCAN_INVALID_SYMBOL = 301
|
||||||
const val ERROR_LOGIN_VULCAN_INVALID_TOKEN = 302
|
const val ERROR_LOGIN_VULCAN_INVALID_TOKEN = 302
|
||||||
|
@ -5,6 +5,7 @@
|
|||||||
package pl.szczodrzynski.edziennik.data.api
|
package pl.szczodrzynski.edziennik.data.api
|
||||||
|
|
||||||
import kotlin.text.RegexOption.DOT_MATCHES_ALL
|
import kotlin.text.RegexOption.DOT_MATCHES_ALL
|
||||||
|
import kotlin.text.RegexOption.IGNORE_CASE
|
||||||
|
|
||||||
object Regexes {
|
object Regexes {
|
||||||
val STYLE_CSS_COLOR by lazy {
|
val STYLE_CSS_COLOR by lazy {
|
||||||
@ -126,7 +127,7 @@ object Regexes {
|
|||||||
|
|
||||||
|
|
||||||
val LIBRUS_ATTACHMENT_KEY by lazy {
|
val LIBRUS_ATTACHMENT_KEY by lazy {
|
||||||
"""singleUseKey=([0-9A-f_]+)""".toRegex()
|
"""singleUseKey=([0-9A-z_]+)""".toRegex()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -199,4 +200,20 @@ object Regexes {
|
|||||||
val EDUDZIENNIK_TEACHERS by lazy {
|
val EDUDZIENNIK_TEACHERS by lazy {
|
||||||
"""<div class="teacher">.*?<p>(.+?) (.+?)</p>""".toRegex(DOT_MATCHES_ALL)
|
"""<div class="teacher">.*?<p>(.+?) (.+?)</p>""".toRegex(DOT_MATCHES_ALL)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
val LINKIFY_DATE_YMD by lazy {
|
||||||
|
"""(1\d{3}|20\d{2})[\-./](1[0-2]|0?\d)[\-./]([1-2]\d|3[0-1]|0?\d)""".toRegex()
|
||||||
|
}
|
||||||
|
val LINKIFY_DATE_DMY by lazy {
|
||||||
|
"""(?<![\d\-./])([1-2]\d|3[0-1]|0?\d)[\-./](1[0-2]|0?\d)(?:[\-./](1\d{3}|2?0?\d{2}))?(?![\d\-/])""".toRegex()
|
||||||
|
}
|
||||||
|
val LINKIFY_DATE_ABSOLUTE by lazy {
|
||||||
|
"""([1-3][0-9]|[1-9])\s(sty|lut|mar|kwi|maj|cze|lip|sie|wrz|paź|lis|gru).*?\s(1[0-9]{3}|20[0-9]{2})?""".toRegex(IGNORE_CASE)
|
||||||
|
}
|
||||||
|
val LINKIFY_DATE_RELATIVE by lazy {
|
||||||
|
"""za\s([0-9]+)?\s?(dni|dzień|tydzień|tygodnie)""".toRegex(IGNORE_CASE)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -16,6 +16,8 @@ import pl.szczodrzynski.edziennik.data.db.entity.Announcement
|
|||||||
import pl.szczodrzynski.edziennik.data.db.entity.Metadata
|
import pl.szczodrzynski.edziennik.data.db.entity.Metadata
|
||||||
import pl.szczodrzynski.edziennik.data.db.entity.SYNC_ALWAYS
|
import pl.szczodrzynski.edziennik.data.db.entity.SYNC_ALWAYS
|
||||||
import pl.szczodrzynski.edziennik.getJsonObject
|
import pl.szczodrzynski.edziennik.getJsonObject
|
||||||
|
import pl.szczodrzynski.edziennik.getLong
|
||||||
|
import pl.szczodrzynski.edziennik.getString
|
||||||
import pl.szczodrzynski.edziennik.utils.models.Date
|
import pl.szczodrzynski.edziennik.utils.models.Date
|
||||||
|
|
||||||
class IdziennikWebAnnouncements(override val data: DataIdziennik,
|
class IdziennikWebAnnouncements(override val data: DataIdziennik,
|
||||||
@ -43,11 +45,11 @@ class IdziennikWebAnnouncements(override val data: DataIdziennik,
|
|||||||
for (jAnnouncementEl in json.getAsJsonArray("ListK")) {
|
for (jAnnouncementEl in json.getAsJsonArray("ListK")) {
|
||||||
val jAnnouncement = jAnnouncementEl.asJsonObject
|
val jAnnouncement = jAnnouncementEl.asJsonObject
|
||||||
// jAnnouncement
|
// jAnnouncement
|
||||||
val announcementId = jAnnouncement.get("Id").asLong
|
val announcementId = jAnnouncement.getLong("Id") ?: -1
|
||||||
|
|
||||||
val rTeacher = data.getTeacherByFirstLast(jAnnouncement.get("Autor").asString)
|
val rTeacher = data.getTeacherByFirstLast(jAnnouncement.getString("Autor") ?: "")
|
||||||
val addedDate = java.lang.Long.parseLong(jAnnouncement.get("DataDodania").asString.replace("[^\\d]".toRegex(), ""))
|
val addedDate = jAnnouncement.getString("DataDodania")?.replace("[^\\d]".toRegex(), "")?.toLongOrNull() ?: System.currentTimeMillis()
|
||||||
val startDate = Date.fromMillis(java.lang.Long.parseLong(jAnnouncement.get("DataWydarzenia").asString.replace("[^\\d]".toRegex(), "")))
|
val startDate = jAnnouncement.getString("DataWydarzenia")?.replace("[^\\d]".toRegex(), "")?.toLongOrNull()?.let { Date.fromMillis(it) }
|
||||||
|
|
||||||
val announcementObject = Announcement(
|
val announcementObject = Announcement(
|
||||||
profileId,
|
profileId,
|
||||||
|
@ -55,13 +55,20 @@ open class LibrusMessages(open val data: DataLibrus, open val lastSync: Long?) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
when {
|
when {
|
||||||
text.contains("<message>Niepoprawny login i/lub hasło.</message>") -> data.error(TAG, ERROR_LOGIN_LIBRUS_MESSAGES_INVALID_LOGIN, response, text)
|
text.contains("<message>Niepoprawny login i/lub hasło.</message>") -> ERROR_LOGIN_LIBRUS_MESSAGES_INVALID_LOGIN
|
||||||
text.contains("stop.png") -> data.error(TAG, ERROR_LIBRUS_SYNERGIA_ACCESS_DENIED, response, text)
|
text.contains("<message>Nie odnaleziono wiadomości.</message>") -> ERROR_LIBRUS_MESSAGES_NOT_FOUND
|
||||||
text.contains("eAccessDeny") -> data.error(TAG, ERROR_LIBRUS_MESSAGES_ACCESS_DENIED, response, text)
|
text.contains("stop.png") -> ERROR_LIBRUS_SYNERGIA_ACCESS_DENIED
|
||||||
text.contains("OffLine") -> data.error(TAG, ERROR_LIBRUS_MESSAGES_MAINTENANCE, response, text)
|
text.contains("eAccessDeny") -> ERROR_LIBRUS_MESSAGES_ACCESS_DENIED
|
||||||
text.contains("<status>error</status>") -> data.error(TAG, ERROR_LIBRUS_MESSAGES_ERROR, response, text)
|
text.contains("OffLine") -> ERROR_LIBRUS_MESSAGES_MAINTENANCE
|
||||||
text.contains("<type>eVarWhitThisNameNotExists</type>") -> data.error(TAG, ERROR_LIBRUS_MESSAGES_ACCESS_DENIED, response, text)
|
text.contains("<status>error</status>") -> ERROR_LIBRUS_MESSAGES_ERROR
|
||||||
text.contains("<error>") -> data.error(TAG, ERROR_LIBRUS_MESSAGES_OTHER, response, text)
|
text.contains("<type>eVarWhitThisNameNotExists</type>") -> ERROR_LIBRUS_MESSAGES_ACCESS_DENIED
|
||||||
|
text.contains("<error>") -> ERROR_LIBRUS_MESSAGES_OTHER
|
||||||
|
else -> null
|
||||||
|
}?.let { errorCode ->
|
||||||
|
data.error(ApiError(tag, errorCode)
|
||||||
|
.withApiResponse(text)
|
||||||
|
.withResponse(response))
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@ -139,13 +146,20 @@ open class LibrusMessages(open val data: DataLibrus, open val lastSync: Long?) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
when {
|
when {
|
||||||
text.contains("<message>Niepoprawny login i/lub hasło.</message>") -> data.error(TAG, ERROR_LOGIN_LIBRUS_MESSAGES_INVALID_LOGIN, response, text)
|
text.contains("<message>Niepoprawny login i/lub hasło.</message>") -> ERROR_LOGIN_LIBRUS_MESSAGES_INVALID_LOGIN
|
||||||
text.contains("stop.png") -> data.error(TAG, ERROR_LIBRUS_SYNERGIA_ACCESS_DENIED, response, text)
|
text.contains("<message>Nie odnaleziono wiadomości.</message>") -> ERROR_LIBRUS_MESSAGES_NOT_FOUND
|
||||||
text.contains("eAccessDeny") -> data.error(TAG, ERROR_LIBRUS_MESSAGES_ACCESS_DENIED, response, text)
|
text.contains("stop.png") -> ERROR_LIBRUS_SYNERGIA_ACCESS_DENIED
|
||||||
text.contains("OffLine") -> data.error(TAG, ERROR_LIBRUS_MESSAGES_MAINTENANCE, response, text)
|
text.contains("eAccessDeny") -> ERROR_LIBRUS_MESSAGES_ACCESS_DENIED
|
||||||
text.contains("<status>error</status>") -> data.error(TAG, ERROR_LIBRUS_MESSAGES_ERROR, response, text)
|
text.contains("OffLine") -> ERROR_LIBRUS_MESSAGES_MAINTENANCE
|
||||||
text.contains("<type>eVarWhitThisNameNotExists</type>") -> data.error(TAG, ERROR_LIBRUS_MESSAGES_ACCESS_DENIED, response, text)
|
text.contains("<status>error</status>") -> ERROR_LIBRUS_MESSAGES_ERROR
|
||||||
text.contains("<error>") -> data.error(TAG, ERROR_LIBRUS_MESSAGES_OTHER, response, text)
|
text.contains("<type>eVarWhitThisNameNotExists</type>") -> ERROR_LIBRUS_MESSAGES_ACCESS_DENIED
|
||||||
|
text.contains("<error>") -> ERROR_LIBRUS_MESSAGES_OTHER
|
||||||
|
else -> null
|
||||||
|
}?.let { errorCode ->
|
||||||
|
data.error(ApiError(tag, errorCode)
|
||||||
|
.withApiResponse(text)
|
||||||
|
.withResponse(response))
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
@ -42,7 +42,7 @@ class LibrusApiAttendances(override val data: DataLibrus,
|
|||||||
val teacherId = attendance.getJsonObject("AddedBy")?.getLong("Id")
|
val teacherId = attendance.getJsonObject("AddedBy")?.getLong("Id")
|
||||||
val semester = attendance.getInt("Semester") ?: return@forEach
|
val semester = attendance.getInt("Semester") ?: return@forEach
|
||||||
val type = attendance.getJsonObject("Type")?.getLong("Id") ?: return@forEach
|
val type = attendance.getJsonObject("Type")?.getLong("Id") ?: return@forEach
|
||||||
val typeObject = data.attendanceTypes.get(type)
|
val typeObject = data.attendanceTypes[type] ?: null
|
||||||
val topic = typeObject?.name ?: ""
|
val topic = typeObject?.name ?: ""
|
||||||
|
|
||||||
val startTime = data.lessonRanges.get(lessonNo).startTime
|
val startTime = data.lessonRanges.get(lessonNo).startTime
|
||||||
@ -60,13 +60,13 @@ class LibrusApiAttendances(override val data: DataLibrus,
|
|||||||
topic,
|
topic,
|
||||||
lessonDate,
|
lessonDate,
|
||||||
startTime,
|
startTime,
|
||||||
typeObject.type
|
typeObject?.type ?: Attendance.TYPE_CUSTOM
|
||||||
)
|
)
|
||||||
|
|
||||||
val addedDate = Date.fromIso(attendance.getString("AddDate") ?: return@forEach)
|
val addedDate = Date.fromIso(attendance.getString("AddDate") ?: return@forEach)
|
||||||
|
|
||||||
data.attendanceList.add(attendanceObject)
|
data.attendanceList.add(attendanceObject)
|
||||||
if(typeObject.type != Attendance.TYPE_PRESENT) {
|
if(typeObject?.type != Attendance.TYPE_PRESENT) {
|
||||||
data.metadataList.add(Metadata(
|
data.metadataList.add(Metadata(
|
||||||
profileId,
|
profileId,
|
||||||
Metadata.TYPE_ATTENDANCE,
|
Metadata.TYPE_ATTENDANCE,
|
||||||
|
@ -111,7 +111,7 @@ class LibrusMessagesGetList(override val data: DataLibrus,
|
|||||||
|
|
||||||
data.messageIgnoreList.add(messageObject)
|
data.messageIgnoreList.add(messageObject)
|
||||||
data.messageRecipientList.add(messageRecipientObject)
|
data.messageRecipientList.add(messageRecipientObject)
|
||||||
data.metadataList.add(Metadata(
|
data.setSeenMetadataList.add(Metadata(
|
||||||
profileId,
|
profileId,
|
||||||
Metadata.TYPE_MESSAGE,
|
Metadata.TYPE_MESSAGE,
|
||||||
id,
|
id,
|
||||||
|
@ -29,7 +29,7 @@ class LibrusSynergiaHomework(override val data: DataLibrus,
|
|||||||
init { data.profile?.also { profile ->
|
init { data.profile?.also { profile ->
|
||||||
synergiaGet(TAG, "moje_zadania", method = POST, parameters = mapOf(
|
synergiaGet(TAG, "moje_zadania", method = POST, parameters = mapOf(
|
||||||
"dataOd" to
|
"dataOd" to
|
||||||
if (!data.profile.empty)
|
if (profile.empty)
|
||||||
profile.getSemesterStart(1).stringY_m_d
|
profile.getSemesterStart(1).stringY_m_d
|
||||||
else
|
else
|
||||||
Date.getToday().stringY_m_d,
|
Date.getToday().stringY_m_d,
|
||||||
@ -54,7 +54,7 @@ class LibrusSynergiaHomework(override val data: DataLibrus,
|
|||||||
val teacherId = data.teacherList.singleOrNull { teacherName == it.fullName }?.id
|
val teacherId = data.teacherList.singleOrNull { teacherName == it.fullName }?.id
|
||||||
?: -1
|
?: -1
|
||||||
val topic = elements[2].text().trim()
|
val topic = elements[2].text().trim()
|
||||||
val addedDate = Date.fromY_m_d(elements[4].text().trim()).inMillis
|
val addedDate = Date.fromY_m_d(elements[4].text().trim())
|
||||||
val eventDate = Date.fromY_m_d(elements[6].text().trim())
|
val eventDate = Date.fromY_m_d(elements[6].text().trim())
|
||||||
val id = "/podglad/([0-9]+)'".toRegex().find(
|
val id = "/podglad/([0-9]+)'".toRegex().find(
|
||||||
elements[9].select("input").attr("onclick")
|
elements[9].select("input").attr("onclick")
|
||||||
@ -63,10 +63,25 @@ class LibrusSynergiaHomework(override val data: DataLibrus,
|
|||||||
val lessons = data.db.timetableDao().getForDateNow(profileId, eventDate)
|
val lessons = data.db.timetableDao().getForDateNow(profileId, eventDate)
|
||||||
val startTime = lessons.firstOrNull { it.subjectId == subjectId }?.startTime
|
val startTime = lessons.firstOrNull { it.subjectId == subjectId }?.startTime
|
||||||
|
|
||||||
val moreInfo = graphElements[2 * i + 1].select("td[title]")
|
/*val moreInfo = graphElements[2 * i + 1].select("td[title]")
|
||||||
.attr("title").trim()
|
.attr("title").trim()*/
|
||||||
val description = "Treść: (.*)".toRegex(RegexOption.DOT_MATCHES_ALL).find(moreInfo)
|
|
||||||
?.get(1)?.replace("<br.*/>".toRegex(), "\n")?.trim()
|
var description = ""
|
||||||
|
|
||||||
|
graphElements.forEach { graphEl ->
|
||||||
|
graphEl.select("td[title]")?.also {
|
||||||
|
val title = it.attr("title")
|
||||||
|
val r = "Temat: (.*?)<br.?/>Data udostępnienia: (.*?)<br.?/>Termin wykonania: (.*?)<br.?/>Treść: (.*)"
|
||||||
|
.toRegex(RegexOption.DOT_MATCHES_ALL).find(title) ?: return@forEach
|
||||||
|
val gTopic = r[1].trim()
|
||||||
|
val gAddedDate = Date.fromY_m_d(r[2].trim())
|
||||||
|
val gEventDate = Date.fromY_m_d(r[3].trim())
|
||||||
|
if (gTopic == topic && gAddedDate == addedDate && gEventDate == eventDate) {
|
||||||
|
description = r[4].replace("<br.?/>".toRegex(), "\n").trim()
|
||||||
|
return@forEach
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
val seen = when (profile.empty) {
|
val seen = when (profile.empty) {
|
||||||
true -> true
|
true -> true
|
||||||
@ -94,7 +109,7 @@ class LibrusSynergiaHomework(override val data: DataLibrus,
|
|||||||
id,
|
id,
|
||||||
seen,
|
seen,
|
||||||
seen,
|
seen,
|
||||||
addedDate
|
addedDate.inMillis
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -54,6 +54,8 @@ class LibrusFirstLogin(val data: DataLibrus, val onSuccess: () -> Unit) {
|
|||||||
return@portalGet
|
return@portalGet
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val isParent = account.getString("group") == "parent"
|
||||||
|
|
||||||
val id = account.getInt("id") ?: continue
|
val id = account.getInt("id") ?: continue
|
||||||
val login = account.getString("login") ?: continue
|
val login = account.getString("login") ?: continue
|
||||||
val token = account.getString("accessToken") ?: continue
|
val token = account.getString("accessToken") ?: continue
|
||||||
@ -69,7 +71,7 @@ class LibrusFirstLogin(val data: DataLibrus, val onSuccess: () -> Unit) {
|
|||||||
data.portalEmail,
|
data.portalEmail,
|
||||||
studentNameLong,
|
studentNameLong,
|
||||||
studentNameShort,
|
studentNameShort,
|
||||||
null
|
if (isParent) studentNameLong else null /* temporarily - there is no parent name provided, only the type */
|
||||||
).apply {
|
).apply {
|
||||||
studentData["accountId"] = id
|
studentData["accountId"] = id
|
||||||
studentData["accountLogin"] = login
|
studentData["accountLogin"] = login
|
||||||
|
@ -137,6 +137,7 @@ class LibrusLoginApi {
|
|||||||
"librus_change_password_error" -> ERROR_LOGIN_LIBRUS_API_CHANGE_PASSWORD_ERROR
|
"librus_change_password_error" -> ERROR_LOGIN_LIBRUS_API_CHANGE_PASSWORD_ERROR
|
||||||
"librus_password_change_required" -> ERROR_LOGIN_LIBRUS_API_PASSWORD_CHANGE_REQUIRED
|
"librus_password_change_required" -> ERROR_LOGIN_LIBRUS_API_PASSWORD_CHANGE_REQUIRED
|
||||||
"invalid_grant" -> ERROR_LOGIN_LIBRUS_API_INVALID_LOGIN
|
"invalid_grant" -> ERROR_LOGIN_LIBRUS_API_INVALID_LOGIN
|
||||||
|
"invalid_request" -> ERROR_LOGIN_LIBRUS_API_INVALID_REQUEST
|
||||||
else -> ERROR_LOGIN_LIBRUS_API_OTHER
|
else -> ERROR_LOGIN_LIBRUS_API_OTHER
|
||||||
}.let { errorCode ->
|
}.let { errorCode ->
|
||||||
data.error(ApiError(TAG, errorCode)
|
data.error(ApiError(TAG, errorCode)
|
||||||
|
@ -26,6 +26,23 @@ open class MobidziennikWeb(open val data: DataMobidziennik, open val lastSync: L
|
|||||||
val profile
|
val profile
|
||||||
get() = data.profile
|
get() = data.profile
|
||||||
|
|
||||||
|
/* TODO
|
||||||
|
add error handling:
|
||||||
|
|
||||||
|
<!-- CONTENT -->
|
||||||
|
<div id="content">
|
||||||
|
<a name="tresc"></a>
|
||||||
|
<h1>Ważna informacja!</h1>
|
||||||
|
|
||||||
|
<div class="okienko_informacyjne">
|
||||||
|
Korzystasz z hasła, które zostało wygenerowane automatycznie.<hr/>
|
||||||
|
Pamiętaj, aby je zmienić oraz uzupełnić dane w swoim profilu.<br/><br/>
|
||||||
|
Obie te operacje można wykonać uruchamiając opcję
|
||||||
|
<a href="https://poznan.mobidziennik.pl/dziennik/edytujprofil" title="Edycja profilu, możliwość zmiany hasła">Moje konto->Edycja profilu</a>.
|
||||||
|
</div> </div>
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
fun webGet(
|
fun webGet(
|
||||||
tag: String,
|
tag: String,
|
||||||
endpoint: String,
|
endpoint: String,
|
||||||
@ -65,6 +82,12 @@ open class MobidziennikWeb(open val data: DataMobidziennik, open val lastSync: L
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (text.contains("<h2>Problemy z wydajnością</h2>")) {
|
||||||
|
data.error(ApiError(TAG, ERROR_MOBIDZIENNIK_WEB_SERVER_PROBLEM)
|
||||||
|
.withResponse(response))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
onSuccess(text)
|
onSuccess(text)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
|
@ -118,7 +118,7 @@ class MobidziennikWebGetMessage(override val data: DataMobidziennik,
|
|||||||
|
|
||||||
// this needs to be at the end
|
// this needs to be at the end
|
||||||
message.apply {
|
message.apply {
|
||||||
this.body = body.html().replace("\n", "<br>")
|
this.body = body.html()
|
||||||
|
|
||||||
clearAttachments()
|
clearAttachments()
|
||||||
content.select("ul li").map { it.select("a").first() }.forEach {
|
content.select("ul li").map { it.select("a").first() }.forEach {
|
||||||
|
@ -32,15 +32,15 @@ class MobidziennikWebMessagesAll(override val data: DataMobidziennik,
|
|||||||
|
|
||||||
val doc = Jsoup.parse(text)
|
val doc = Jsoup.parse(text)
|
||||||
|
|
||||||
val listElement = doc.getElementsByClass("spis").first()
|
val listElement = doc.getElementsByClass("spis")?.first()
|
||||||
if (listElement == null) {
|
if (listElement == null) {
|
||||||
data.setSyncNext(ENDPOINT_MOBIDZIENNIK_WEB_MESSAGES_ALL, 7*DAY)
|
data.setSyncNext(ENDPOINT_MOBIDZIENNIK_WEB_MESSAGES_ALL, 7*DAY)
|
||||||
onSuccess(ENDPOINT_MOBIDZIENNIK_WEB_MESSAGES_ALL)
|
onSuccess(ENDPOINT_MOBIDZIENNIK_WEB_MESSAGES_ALL)
|
||||||
return@webGet
|
return@webGet
|
||||||
}
|
}
|
||||||
val list = listElement.getElementsByClass("podswietl")
|
val list = listElement.getElementsByClass("podswietl")
|
||||||
for (item in list) {
|
list?.forEach { item ->
|
||||||
val id = item.attr("rel").replace("[^\\d]".toRegex(), "").toLongOrNull() ?: continue
|
val id = item.attr("rel").replace("[^\\d]".toRegex(), "").toLongOrNull() ?: return@forEach
|
||||||
|
|
||||||
val subjectEl = item.select("td:eq(0) div").first()
|
val subjectEl = item.select("td:eq(0) div").first()
|
||||||
val subject = subjectEl.text()
|
val subject = subjectEl.text()
|
||||||
|
@ -36,9 +36,9 @@ class MobidziennikWebMessagesInbox(override val data: DataMobidziennik,
|
|||||||
|
|
||||||
val doc = Jsoup.parse(text)
|
val doc = Jsoup.parse(text)
|
||||||
|
|
||||||
val list = doc.getElementsByClass("spis").first().getElementsByClass("podswietl")
|
val list = doc.getElementsByClass("spis")?.first()?.getElementsByClass("podswietl")
|
||||||
for (item in list) {
|
list?.forEach { item ->
|
||||||
val id = item.attr("rel").toLongOrNull() ?: continue
|
val id = item.attr("rel").toLongOrNull() ?: return@forEach
|
||||||
|
|
||||||
val subjectEl = item.select("td:eq(0)").first()
|
val subjectEl = item.select("td:eq(0)").first()
|
||||||
var hasAttachments = false
|
var hasAttachments = false
|
||||||
|
@ -40,9 +40,9 @@ class MobidziennikWebMessagesSent(override val data: DataMobidziennik,
|
|||||||
|
|
||||||
val doc = Jsoup.parse(text)
|
val doc = Jsoup.parse(text)
|
||||||
|
|
||||||
val list = doc.getElementsByClass("spis").first().getElementsByClass("podswietl")
|
val list = doc.getElementsByClass("spis")?.first()?.getElementsByClass("podswietl")
|
||||||
for (item in list) {
|
list?.forEach { item ->
|
||||||
val id = item.attr("rel").toLongOrNull() ?: continue
|
val id = item.attr("rel").toLongOrNull() ?: return@forEach
|
||||||
|
|
||||||
val subjectEl = item.select("td:eq(0)").first()
|
val subjectEl = item.select("td:eq(0)").first()
|
||||||
var hasAttachments = false
|
var hasAttachments = false
|
||||||
|
@ -10,7 +10,10 @@ import pl.szczodrzynski.edziennik.data.api.LOGIN_METHOD_VULCAN_API
|
|||||||
import pl.szczodrzynski.edziennik.data.api.models.Data
|
import pl.szczodrzynski.edziennik.data.api.models.Data
|
||||||
import pl.szczodrzynski.edziennik.data.db.entity.LoginStore
|
import pl.szczodrzynski.edziennik.data.db.entity.LoginStore
|
||||||
import pl.szczodrzynski.edziennik.data.db.entity.Profile
|
import pl.szczodrzynski.edziennik.data.db.entity.Profile
|
||||||
|
import pl.szczodrzynski.edziennik.data.db.entity.Team
|
||||||
import pl.szczodrzynski.edziennik.isNotNullNorEmpty
|
import pl.szczodrzynski.edziennik.isNotNullNorEmpty
|
||||||
|
import pl.szczodrzynski.edziennik.utils.Utils
|
||||||
|
import pl.szczodrzynski.edziennik.values
|
||||||
|
|
||||||
class DataVulcan(app: App, profile: Profile?, loginStore: LoginStore) : Data(app, profile, loginStore) {
|
class DataVulcan(app: App, profile: Profile?, loginStore: LoginStore) : Data(app, profile, loginStore) {
|
||||||
|
|
||||||
@ -26,6 +29,25 @@ class DataVulcan(app: App, profile: Profile?, loginStore: LoginStore) : Data(app
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
init {
|
||||||
|
// during the first sync `profile.studentClassName` is already set
|
||||||
|
if (teamList.values().none { it.type == Team.TYPE_CLASS }) {
|
||||||
|
profile?.studentClassName?.also { name ->
|
||||||
|
val id = Utils.crc16(name.toByteArray()).toLong()
|
||||||
|
|
||||||
|
val teamObject = Team(
|
||||||
|
profileId,
|
||||||
|
id,
|
||||||
|
name,
|
||||||
|
Team.TYPE_CLASS,
|
||||||
|
"$schoolName:$name",
|
||||||
|
-1
|
||||||
|
)
|
||||||
|
teamList.put(id, teamObject)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun generateUserCode() = "$schoolName:$studentId"
|
override fun generateUserCode() = "$schoolName:$studentId"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -188,11 +210,11 @@ class DataVulcan(app: App, profile: Profile?, loginStore: LoginStore) : Data(app
|
|||||||
"SZ9" -> "http://hack.szkolny.eu"
|
"SZ9" -> "http://hack.szkolny.eu"
|
||||||
else -> null
|
else -> null
|
||||||
}
|
}
|
||||||
return if (url != null) "$url/$symbol" else loginStore.getLoginData("apiUrl", null)
|
return if (url != null) "$url/$symbol/" else loginStore.getLoginData("apiUrl", null)
|
||||||
}
|
}
|
||||||
|
|
||||||
val fullApiUrl: String?
|
val fullApiUrl: String?
|
||||||
get() {
|
get() {
|
||||||
return "$apiUrl/$schoolSymbol"
|
return "$apiUrl$schoolSymbol/"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -13,8 +13,6 @@ import io.github.wulkanowy.signer.android.signContent
|
|||||||
import pl.szczodrzynski.edziennik.data.api.*
|
import pl.szczodrzynski.edziennik.data.api.*
|
||||||
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.DataVulcan
|
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.DataVulcan
|
||||||
import pl.szczodrzynski.edziennik.data.api.models.ApiError
|
import pl.szczodrzynski.edziennik.data.api.models.ApiError
|
||||||
import pl.szczodrzynski.edziennik.data.db.entity.Team
|
|
||||||
import pl.szczodrzynski.edziennik.utils.Utils
|
|
||||||
import pl.szczodrzynski.edziennik.utils.Utils.d
|
import pl.szczodrzynski.edziennik.utils.Utils.d
|
||||||
import java.net.HttpURLConnection
|
import java.net.HttpURLConnection
|
||||||
import java.util.*
|
import java.util.*
|
||||||
@ -38,26 +36,10 @@ open class VulcanApi(open val data: DataVulcan, open val lastSync: Long?) {
|
|||||||
baseUrl: Boolean = false,
|
baseUrl: Boolean = false,
|
||||||
onSuccess: (json: JsonObject, response: Response?) -> Unit
|
onSuccess: (json: JsonObject, response: Response?) -> Unit
|
||||||
) {
|
) {
|
||||||
val url = "${if (baseUrl) data.apiUrl else data.fullApiUrl}/$endpoint"
|
val url = "${if (baseUrl) data.apiUrl else data.fullApiUrl}$endpoint"
|
||||||
|
|
||||||
d(tag, "Request: Vulcan/Api - $url")
|
d(tag, "Request: Vulcan/Api - $url")
|
||||||
|
|
||||||
if (data.teamList.size() == 0) {
|
|
||||||
data.profile?.studentClassName?.also { name ->
|
|
||||||
val id = Utils.crc16(name.toByteArray()).toLong()
|
|
||||||
|
|
||||||
val teamObject = Team(
|
|
||||||
profileId,
|
|
||||||
id,
|
|
||||||
name,
|
|
||||||
Team.TYPE_CLASS,
|
|
||||||
"${data.schoolName}:$name",
|
|
||||||
-1
|
|
||||||
)
|
|
||||||
data.teamList.put(id, teamObject)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val finalPayload = JsonObject()
|
val finalPayload = JsonObject()
|
||||||
parameters.map { (name, value) ->
|
parameters.map { (name, value) ->
|
||||||
when (value) {
|
when (value) {
|
||||||
|
@ -86,31 +86,36 @@ class VulcanApiTimetable(override val data: DataVulcan,
|
|||||||
data.teamList[id] = team
|
data.teamList[id] = team
|
||||||
}
|
}
|
||||||
team.id
|
team.id
|
||||||
} ?: data.studentClassId.toLong()
|
} ?: data.teamClass?.id ?: -1
|
||||||
|
|
||||||
val subjectId = lesson.getLong("IdPrzedmiot")?.let {
|
val subjectId = lesson.getLong("IdPrzedmiot").let { id ->
|
||||||
when (it) {
|
// get the specified subject name
|
||||||
0L -> {
|
val subjectName = lesson.getString("PrzedmiotNazwa") ?: ""
|
||||||
val subjectName = lesson.getString("PrzedmiotNazwa") ?: ""
|
|
||||||
|
|
||||||
data.subjectList.singleOrNull { subject -> subject.longName == subjectName }?.id
|
val condition = when (id) {
|
||||||
?: {
|
// "special" subject - e.g. one time classes, a trip, etc.
|
||||||
/**
|
0L -> { subject: Subject -> subject.longName == subjectName }
|
||||||
* CREATE A NEW SUBJECT IF IT DOESN'T EXIST
|
// normal subject, check if it exists
|
||||||
*/
|
else -> { subject: Subject -> subject.id == id }
|
||||||
|
|
||||||
val subjectObject = Subject(
|
|
||||||
profileId,
|
|
||||||
-1 * crc16(subjectName.toByteArray()).toLong(),
|
|
||||||
subjectName,
|
|
||||||
subjectName
|
|
||||||
)
|
|
||||||
data.subjectList.put(subjectObject.id, subjectObject)
|
|
||||||
subjectObject.id
|
|
||||||
}.invoke()
|
|
||||||
}
|
|
||||||
else -> it
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
data.subjectList.singleOrNull(condition)?.id ?: {
|
||||||
|
/**
|
||||||
|
* CREATE A NEW SUBJECT IF IT DOESN'T EXIST
|
||||||
|
*/
|
||||||
|
|
||||||
|
val subjectObject = Subject(
|
||||||
|
profileId,
|
||||||
|
if (id == null || id == 0L)
|
||||||
|
-1 * crc16(subjectName.toByteArray()).toLong()
|
||||||
|
else
|
||||||
|
id,
|
||||||
|
subjectName,
|
||||||
|
subjectName
|
||||||
|
)
|
||||||
|
data.subjectList.put(subjectObject.id, subjectObject)
|
||||||
|
subjectObject.id
|
||||||
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
val lessonObject = Lesson(profileId, -1).apply {
|
val lessonObject = Lesson(profileId, -1).apply {
|
||||||
|
@ -137,7 +137,7 @@ class VulcanLoginApi(val data: DataVulcan, val onSuccess: () -> Unit) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Request.builder()
|
Request.builder()
|
||||||
.url("${data.apiUrl}/$VULCAN_API_ENDPOINT_CERTIFICATE")
|
.url("${data.apiUrl}$VULCAN_API_ENDPOINT_CERTIFICATE")
|
||||||
.userAgent(VULCAN_API_USER_AGENT)
|
.userAgent(VULCAN_API_USER_AGENT)
|
||||||
.addHeader("RequestMobileType", "RegisterDevice")
|
.addHeader("RequestMobileType", "RegisterDevice")
|
||||||
.addParameter("PIN", data.apiPin)
|
.addParameter("PIN", data.apiPin)
|
||||||
|
@ -12,10 +12,7 @@ import kotlinx.coroutines.Dispatchers
|
|||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import okhttp3.OkHttpClient
|
import okhttp3.OkHttpClient
|
||||||
import pl.szczodrzynski.edziennik.App
|
import pl.szczodrzynski.edziennik.*
|
||||||
import pl.szczodrzynski.edziennik.BuildConfig
|
|
||||||
import pl.szczodrzynski.edziennik.R
|
|
||||||
import pl.szczodrzynski.edziennik.data.api.models.ApiError
|
|
||||||
import pl.szczodrzynski.edziennik.data.api.szkolny.adapter.DateAdapter
|
import pl.szczodrzynski.edziennik.data.api.szkolny.adapter.DateAdapter
|
||||||
import pl.szczodrzynski.edziennik.data.api.szkolny.adapter.TimeAdapter
|
import pl.szczodrzynski.edziennik.data.api.szkolny.adapter.TimeAdapter
|
||||||
import pl.szczodrzynski.edziennik.data.api.szkolny.interceptor.SignatureInterceptor
|
import pl.szczodrzynski.edziennik.data.api.szkolny.interceptor.SignatureInterceptor
|
||||||
@ -28,7 +25,6 @@ import pl.szczodrzynski.edziennik.data.db.entity.FeedbackMessage
|
|||||||
import pl.szczodrzynski.edziennik.data.db.entity.Notification
|
import pl.szczodrzynski.edziennik.data.db.entity.Notification
|
||||||
import pl.szczodrzynski.edziennik.data.db.entity.Profile
|
import pl.szczodrzynski.edziennik.data.db.entity.Profile
|
||||||
import pl.szczodrzynski.edziennik.data.db.full.EventFull
|
import pl.szczodrzynski.edziennik.data.db.full.EventFull
|
||||||
import pl.szczodrzynski.edziennik.md5
|
|
||||||
import pl.szczodrzynski.edziennik.ui.modules.error.ErrorDetailsDialog
|
import pl.szczodrzynski.edziennik.ui.modules.error.ErrorDetailsDialog
|
||||||
import pl.szczodrzynski.edziennik.ui.modules.error.ErrorSnackbar
|
import pl.szczodrzynski.edziennik.ui.modules.error.ErrorSnackbar
|
||||||
import pl.szczodrzynski.edziennik.utils.models.Date
|
import pl.szczodrzynski.edziennik.utils.models.Date
|
||||||
@ -80,7 +76,7 @@ class SzkolnyApi(val app: App) : CoroutineScope {
|
|||||||
withContext(Dispatchers.Default) { block() }
|
withContext(Dispatchers.Default) { block() }
|
||||||
}
|
}
|
||||||
catch (e: Exception) {
|
catch (e: Exception) {
|
||||||
errorSnackbar.addError(ApiError.fromThrowable(TAG, e)).show()
|
errorSnackbar.addError(e.toApiError(TAG)).show()
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -91,7 +87,7 @@ class SzkolnyApi(val app: App) : CoroutineScope {
|
|||||||
catch (e: Exception) {
|
catch (e: Exception) {
|
||||||
ErrorDetailsDialog(
|
ErrorDetailsDialog(
|
||||||
activity,
|
activity,
|
||||||
listOf(ApiError.fromThrowable(TAG, e)),
|
listOf(e.toApiError(TAG)),
|
||||||
R.string.error_occured
|
R.string.error_occured
|
||||||
)
|
)
|
||||||
null
|
null
|
||||||
@ -160,7 +156,7 @@ class SzkolnyApi(val app: App) : CoroutineScope {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Throws(Exception::class)
|
@Throws(Exception::class)
|
||||||
fun getEvents(profiles: List<Profile>, notifications: List<Notification>, blacklistedIds: List<Long>): List<EventFull> {
|
fun getEvents(profiles: List<Profile>, notifications: List<Notification>, blacklistedIds: List<Long>, lastSyncTime: Long): List<EventFull> {
|
||||||
val teams = app.db.teamDao().allNow
|
val teams = app.db.teamDao().allNow
|
||||||
|
|
||||||
val response = api.serverSync(ServerSyncRequest(
|
val response = api.serverSync(ServerSyncRequest(
|
||||||
@ -185,19 +181,26 @@ class SzkolnyApi(val app: App) : CoroutineScope {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
lastSync = lastSyncTime,
|
||||||
notifications = notifications.map { ServerSyncRequest.Notification(it.profileName ?: "", it.type, it.text) }
|
notifications = notifications.map { ServerSyncRequest.Notification(it.profileName ?: "", it.type, it.text) }
|
||||||
)).execute()
|
)).execute()
|
||||||
parseResponse(response)
|
val (events, hasBrowsers) = parseResponse(response)
|
||||||
|
|
||||||
val events = mutableListOf<EventFull>()
|
hasBrowsers?.let {
|
||||||
|
app.config.sync.webPushEnabled = it
|
||||||
|
}
|
||||||
|
|
||||||
response.body()?.data?.events?.forEach { event ->
|
val eventList = mutableListOf<EventFull>()
|
||||||
|
|
||||||
|
events.forEach { event ->
|
||||||
|
// skip blacklisted events
|
||||||
if (event.id in blacklistedIds)
|
if (event.id in blacklistedIds)
|
||||||
return@forEach
|
return@forEach
|
||||||
|
// create the event for every matching team and profile
|
||||||
teams.filter { it.code == event.teamCode }.onEach { team ->
|
teams.filter { it.code == event.teamCode }.onEach { team ->
|
||||||
val profile = profiles.firstOrNull { it.id == team.profileId } ?: return@onEach
|
val profile = profiles.firstOrNull { it.id == team.profileId } ?: return@onEach
|
||||||
|
|
||||||
events.add(EventFull(event).apply {
|
eventList += EventFull(event).apply {
|
||||||
profileId = team.profileId
|
profileId = team.profileId
|
||||||
teamId = team.id
|
teamId = team.id
|
||||||
addedManually = true
|
addedManually = true
|
||||||
@ -205,11 +208,11 @@ class SzkolnyApi(val app: App) : CoroutineScope {
|
|||||||
notified = profile.empty
|
notified = profile.empty
|
||||||
|
|
||||||
if (profile.userCode == event.sharedBy) sharedBy = "self"
|
if (profile.userCode == event.sharedBy) sharedBy = "self"
|
||||||
})
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return events
|
return eventList
|
||||||
}
|
}
|
||||||
|
|
||||||
@Throws(Exception::class)
|
@Throws(Exception::class)
|
||||||
@ -253,9 +256,8 @@ class SzkolnyApi(val app: App) : CoroutineScope {
|
|||||||
browserId = browserId,
|
browserId = browserId,
|
||||||
pairToken = pairToken
|
pairToken = pairToken
|
||||||
)).execute()
|
)).execute()
|
||||||
parseResponse(response)
|
|
||||||
|
|
||||||
return response.body()?.data?.browsers ?: emptyList()
|
return parseResponse(response).browsers
|
||||||
}
|
}
|
||||||
|
|
||||||
@Throws(Exception::class)
|
@Throws(Exception::class)
|
||||||
@ -265,9 +267,8 @@ class SzkolnyApi(val app: App) : CoroutineScope {
|
|||||||
device = getDevice(),
|
device = getDevice(),
|
||||||
action = "listBrowsers"
|
action = "listBrowsers"
|
||||||
)).execute()
|
)).execute()
|
||||||
parseResponse(response)
|
|
||||||
|
|
||||||
return response.body()?.data?.browsers ?: emptyList()
|
return parseResponse(response).browsers
|
||||||
}
|
}
|
||||||
|
|
||||||
@Throws(Exception::class)
|
@Throws(Exception::class)
|
||||||
@ -278,9 +279,8 @@ class SzkolnyApi(val app: App) : CoroutineScope {
|
|||||||
action = "unpairBrowser",
|
action = "unpairBrowser",
|
||||||
browserId = browserId
|
browserId = browserId
|
||||||
)).execute()
|
)).execute()
|
||||||
parseResponse(response)
|
|
||||||
|
|
||||||
return response.body()?.data?.browsers ?: emptyList()
|
return parseResponse(response).browsers
|
||||||
}
|
}
|
||||||
|
|
||||||
@Throws(Exception::class)
|
@Throws(Exception::class)
|
||||||
@ -307,9 +307,7 @@ class SzkolnyApi(val app: App) : CoroutineScope {
|
|||||||
@Throws(Exception::class)
|
@Throws(Exception::class)
|
||||||
fun getUpdate(channel: String): List<Update> {
|
fun getUpdate(channel: String): List<Update> {
|
||||||
val response = api.updates(channel).execute()
|
val response = api.updates(channel).execute()
|
||||||
parseResponse(response)
|
return parseResponse(response)
|
||||||
|
|
||||||
return response.body()?.data ?: emptyList()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Throws(Exception::class)
|
@Throws(Exception::class)
|
||||||
@ -321,8 +319,7 @@ class SzkolnyApi(val app: App) : CoroutineScope {
|
|||||||
targetDeviceId = targetDeviceId,
|
targetDeviceId = targetDeviceId,
|
||||||
text = text
|
text = text
|
||||||
)).execute()
|
)).execute()
|
||||||
val data = parseResponse(response)
|
|
||||||
|
|
||||||
return data.message
|
return parseResponse(response).message
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -46,6 +46,6 @@ object Signing {
|
|||||||
|
|
||||||
/*fun provideKey(param1: String, param2: Long): ByteArray {*/
|
/*fun provideKey(param1: String, param2: Long): ByteArray {*/
|
||||||
fun pleaseStopRightNow(param1: String, param2: Long): ByteArray {
|
fun pleaseStopRightNow(param1: String, param2: Long): ByteArray {
|
||||||
return "$param1.MTIzNDU2Nzg5MDwj/ezwig===.$param2".sha256()
|
return "$param1.MTIzNDU2Nzg5MDurcz1Rjg===.$param2".sha256()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -11,6 +11,8 @@ data class ServerSyncRequest(
|
|||||||
val userCodes: List<String>,
|
val userCodes: List<String>,
|
||||||
val users: List<User>? = null,
|
val users: List<User>? = null,
|
||||||
|
|
||||||
|
val lastSync: Long,
|
||||||
|
|
||||||
val notifications: List<Notification>? = null
|
val notifications: List<Notification>? = null
|
||||||
) {
|
) {
|
||||||
data class User(
|
data class User(
|
||||||
|
@ -6,4 +6,7 @@ package pl.szczodrzynski.edziennik.data.api.szkolny.response
|
|||||||
|
|
||||||
import pl.szczodrzynski.edziennik.data.db.full.EventFull
|
import pl.szczodrzynski.edziennik.data.db.full.EventFull
|
||||||
|
|
||||||
data class ServerSyncResponse(val events: List<EventFull>)
|
data class ServerSyncResponse(
|
||||||
|
val events: List<EventFull>,
|
||||||
|
val hasBrowsers: Boolean? = null
|
||||||
|
)
|
||||||
|
@ -9,6 +9,7 @@ import pl.szczodrzynski.edziennik.data.api.szkolny.SzkolnyApi
|
|||||||
import pl.szczodrzynski.edziennik.data.db.entity.Metadata
|
import pl.szczodrzynski.edziennik.data.db.entity.Metadata
|
||||||
import pl.szczodrzynski.edziennik.data.db.entity.Notification
|
import pl.szczodrzynski.edziennik.data.db.entity.Notification
|
||||||
import pl.szczodrzynski.edziennik.data.db.entity.Profile
|
import pl.szczodrzynski.edziennik.data.db.entity.Profile
|
||||||
|
import pl.szczodrzynski.edziennik.utils.models.Date
|
||||||
|
|
||||||
class AppSync(val app: App, val notifications: MutableList<Notification>, val profiles: List<Profile>, val api: SzkolnyApi) {
|
class AppSync(val app: App, val notifications: MutableList<Notification>, val profiles: List<Profile>, val api: SzkolnyApi) {
|
||||||
companion object {
|
companion object {
|
||||||
@ -24,26 +25,27 @@ class AppSync(val app: App, val notifications: MutableList<Notification>, val pr
|
|||||||
*
|
*
|
||||||
* @return a number of events inserted to DB, possibly needing a notification
|
* @return a number of events inserted to DB, possibly needing a notification
|
||||||
*/
|
*/
|
||||||
fun run(): Int {
|
fun run(lastSyncTime: Long, markAsSeen: Boolean = false): Int {
|
||||||
val profiles = profiles.filter { it.registration == Profile.REGISTRATION_ENABLED && !it.archived }
|
val blacklistedIds = app.db.eventDao().blacklistedIds
|
||||||
if (profiles.isNotEmpty()) {
|
val events = api.getEvents(profiles, notifications, blacklistedIds, lastSyncTime)
|
||||||
val blacklistedIds = app.db.eventDao().blacklistedIds;
|
|
||||||
val events = api.getEvents(profiles, notifications, blacklistedIds)
|
|
||||||
|
|
||||||
if (events.isNotEmpty()) {
|
app.config.sync.lastAppSync = System.currentTimeMillis()
|
||||||
app.db.metadataDao().addAllIgnore(events.map { event ->
|
|
||||||
Metadata(
|
if (events.isNotEmpty()) {
|
||||||
event.profileId,
|
val today = Date.getToday()
|
||||||
Metadata.TYPE_EVENT,
|
app.db.metadataDao().addAllIgnore(events.map { event ->
|
||||||
event.id,
|
val isPast = event.eventDate < today
|
||||||
event.seen,
|
Metadata(
|
||||||
event.notified,
|
event.profileId,
|
||||||
event.addedDate
|
Metadata.TYPE_EVENT,
|
||||||
)
|
event.id,
|
||||||
})
|
isPast || markAsSeen || event.seen,
|
||||||
return app.db.eventDao().addAll(events).size
|
isPast || markAsSeen || event.notified,
|
||||||
}
|
event.addedDate
|
||||||
|
)
|
||||||
|
})
|
||||||
|
return app.db.eventDao().addAll(events).size
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -31,16 +31,23 @@ class SzkolnyTask(val app: App, val syncingProfiles: List<Profile>) : IApiTask(-
|
|||||||
val notifications = Notifications(app, notificationList, profiles)
|
val notifications = Notifications(app, notificationList, profiles)
|
||||||
notifications.run()
|
notifications.run()
|
||||||
|
|
||||||
val shouldAppSync = notificationList.isNotEmpty() || (System.currentTimeMillis() - app.config.lastAppSync > 24*HOUR*1000)
|
val appSyncProfiles = profiles.filter { it.registration == Profile.REGISTRATION_ENABLED && !it.archived }
|
||||||
// do an AppSync every 24 hours, or if WebPush has a notification
|
// App Sync conditions:
|
||||||
|
// - every 24 hours && any profile is registered
|
||||||
|
// - if there are new notifications && any browser is paired
|
||||||
|
val shouldAppSync =
|
||||||
|
System.currentTimeMillis() - app.config.sync.lastAppSync > 24*HOUR*1000
|
||||||
|
&& appSyncProfiles.isNotEmpty()
|
||||||
|
|| notificationList.isNotEmpty()
|
||||||
|
&& app.config.sync.webPushEnabled
|
||||||
|
|
||||||
if (shouldAppSync) {
|
if (shouldAppSync) {
|
||||||
// send notifications to web push, get shared events
|
// send notifications to web push, get shared events
|
||||||
val addedEvents = AppSync(app, notificationList, profiles, api).run()
|
val addedEvents = AppSync(app, notificationList, appSyncProfiles, api).run(app.config.sync.lastAppSync)
|
||||||
if (addedEvents > 0) {
|
if (addedEvents > 0) {
|
||||||
// create notifications for shared events (not present before app sync)
|
// create notifications for shared events (not present before app sync)
|
||||||
notifications.sharedEventNotifications()
|
notifications.sharedEventNotifications()
|
||||||
}
|
}
|
||||||
app.config.lastAppSync = System.currentTimeMillis()
|
|
||||||
}
|
}
|
||||||
d(TAG, "Created ${notificationList.count()} notifications.")
|
d(TAG, "Created ${notificationList.count()} notifications.")
|
||||||
|
|
||||||
|
@ -41,7 +41,7 @@ interface ProfileDao {
|
|||||||
fun getIdsByLoginStoreIdNow(loginStoreId: Int): List<Int>
|
fun getIdsByLoginStoreIdNow(loginStoreId: Int): List<Int>
|
||||||
|
|
||||||
@get:Query("SELECT * FROM profiles WHERE syncEnabled = 1 AND archived = 0 AND profileId >= 0 ORDER BY profileId")
|
@get:Query("SELECT * FROM profiles WHERE syncEnabled = 1 AND archived = 0 AND profileId >= 0 ORDER BY profileId")
|
||||||
val profilesForSyncNow: List<Profile>
|
val profilesForFirebaseNow: List<Profile>
|
||||||
|
|
||||||
@get:Query("SELECT profileId FROM profiles WHERE syncEnabled = 1 AND archived = 0 AND profileId >= 0 ORDER BY profileId")
|
@get:Query("SELECT profileId FROM profiles WHERE syncEnabled = 1 AND archived = 0 AND profileId >= 0 ORDER BY profileId")
|
||||||
val idsForSyncNow: List<Int>
|
val idsForSyncNow: List<Int>
|
||||||
|
@ -36,7 +36,7 @@ class MyFirebaseService : FirebaseService(), CoroutineScope {
|
|||||||
putString(System.currentTimeMillis().toString(), message.toString())
|
putString(System.currentTimeMillis().toString(), message.toString())
|
||||||
apply()
|
apply()
|
||||||
}
|
}
|
||||||
val profiles = app.db.profileDao().profilesForSyncNow
|
val profiles = app.db.profileDao().profilesForFirebaseNow
|
||||||
when (message.from) {
|
when (message.from) {
|
||||||
"640759989760" -> SzkolnyAppFirebase(app, profiles, message)
|
"640759989760" -> SzkolnyAppFirebase(app, profiles, message)
|
||||||
"747285019373" -> SzkolnyMobidziennikFirebase(app, profiles, message)
|
"747285019373" -> SzkolnyMobidziennikFirebase(app, profiles, message)
|
||||||
|
@ -101,7 +101,9 @@ class SzkolnyAppFirebase(val app: App, val profiles: List<Profile>, val message:
|
|||||||
val notificationList = mutableListOf<Notification>()
|
val notificationList = mutableListOf<Notification>()
|
||||||
|
|
||||||
teams.filter { it.code == teamCode }.distinctBy { it.profileId }.forEach { team ->
|
teams.filter { it.code == teamCode }.distinctBy { it.profileId }.forEach { team ->
|
||||||
val profile = profiles.firstOrNull { it.id == team.profileId }
|
val profile = profiles.firstOrNull { it.id == team.profileId } ?: return@forEach
|
||||||
|
if (profile.registration != Profile.REGISTRATION_ENABLED)
|
||||||
|
return@forEach
|
||||||
val event = Event(
|
val event = Event(
|
||||||
team.profileId,
|
team.profileId,
|
||||||
json.getLong("id") ?: return,
|
json.getLong("id") ?: return,
|
||||||
@ -116,12 +118,9 @@ class SzkolnyAppFirebase(val app: App, val profiles: List<Profile>, val message:
|
|||||||
team.id
|
team.id
|
||||||
)
|
)
|
||||||
|
|
||||||
// TODO? i guess - this comment is here for like a year
|
|
||||||
//val oldEvent: Event? = app.db.eventDao().getByIdNow(profile?.id ?: -1, event.id)
|
|
||||||
|
|
||||||
event.sharedBy = json.getString("sharedBy")
|
event.sharedBy = json.getString("sharedBy")
|
||||||
event.sharedByName = json.getString("sharedByName")
|
event.sharedByName = json.getString("sharedByName")
|
||||||
if (profile?.userCode == event.sharedBy) event.sharedBy = "self"
|
if (profile.userCode == event.sharedBy) event.sharedBy = "self"
|
||||||
|
|
||||||
val metadata = Metadata(
|
val metadata = Metadata(
|
||||||
event.profileId,
|
event.profileId,
|
||||||
@ -132,18 +131,6 @@ class SzkolnyAppFirebase(val app: App, val profiles: List<Profile>, val message:
|
|||||||
json.getLong("addedDate") ?: System.currentTimeMillis()
|
json.getLong("addedDate") ?: System.currentTimeMillis()
|
||||||
)
|
)
|
||||||
|
|
||||||
//val eventType = eventTypes.firstOrNull { it.profileId == profile?.id && it.id == event.type }
|
|
||||||
|
|
||||||
/*val text = app.getString(
|
|
||||||
if (oldEvent == null)
|
|
||||||
R.string.notification_shared_event_format
|
|
||||||
else
|
|
||||||
R.string.notification_shared_event_modified_format,
|
|
||||||
event.sharedByName,
|
|
||||||
eventType?.name ?: "wydarzenie",
|
|
||||||
event.eventDate.formattedString,
|
|
||||||
event.topic
|
|
||||||
)*/
|
|
||||||
val type = if (event.type == Event.TYPE_HOMEWORK) Notification.TYPE_NEW_SHARED_HOMEWORK else Notification.TYPE_NEW_SHARED_EVENT
|
val type = if (event.type == Event.TYPE_HOMEWORK) Notification.TYPE_NEW_SHARED_HOMEWORK else Notification.TYPE_NEW_SHARED_EVENT
|
||||||
val notificationFilter = app.config.getFor(event.profileId).sync.notificationFilter
|
val notificationFilter = app.config.getFor(event.profileId).sync.notificationFilter
|
||||||
|
|
||||||
@ -153,8 +140,8 @@ class SzkolnyAppFirebase(val app: App, val profiles: List<Profile>, val message:
|
|||||||
title = app.getNotificationTitle(type),
|
title = app.getNotificationTitle(type),
|
||||||
text = message,
|
text = message,
|
||||||
type = type,
|
type = type,
|
||||||
profileId = profile?.id,
|
profileId = profile.id,
|
||||||
profileName = profile?.name,
|
profileName = profile.name,
|
||||||
viewId = if (event.type == Event.TYPE_HOMEWORK) MainActivity.DRAWER_ITEM_HOMEWORK else MainActivity.DRAWER_ITEM_AGENDA,
|
viewId = if (event.type == Event.TYPE_HOMEWORK) MainActivity.DRAWER_ITEM_HOMEWORK else MainActivity.DRAWER_ITEM_AGENDA,
|
||||||
addedDate = metadata.addedDate
|
addedDate = metadata.addedDate
|
||||||
).addExtra("eventId", event.id).addExtra("eventDate", event.eventDate.value.toLong())
|
).addExtra("eventId", event.id).addExtra("eventDate", event.eventDate.value.toLong())
|
||||||
@ -177,18 +164,20 @@ class SzkolnyAppFirebase(val app: App, val profiles: List<Profile>, val message:
|
|||||||
val notificationList = mutableListOf<Notification>()
|
val notificationList = mutableListOf<Notification>()
|
||||||
|
|
||||||
teams.filter { it.code == teamCode }.distinctBy { it.profileId }.forEach { team ->
|
teams.filter { it.code == teamCode }.distinctBy { it.profileId }.forEach { team ->
|
||||||
val profile = profiles.firstOrNull { it.id == team.profileId }
|
val profile = profiles.firstOrNull { it.id == team.profileId } ?: return@forEach
|
||||||
|
if (profile.registration != Profile.REGISTRATION_ENABLED)
|
||||||
|
return@forEach
|
||||||
val notificationFilter = app.config.getFor(team.profileId).sync.notificationFilter
|
val notificationFilter = app.config.getFor(team.profileId).sync.notificationFilter
|
||||||
|
|
||||||
if (!notificationFilter.contains(Notification.TYPE_REMOVED_SHARED_EVENT)) {
|
if (!notificationFilter.contains(Notification.TYPE_REMOVED_SHARED_EVENT)) {
|
||||||
val notification = Notification(
|
val notification = Notification(
|
||||||
id = Notification.buildId(profile?.id
|
id = Notification.buildId(profile.id
|
||||||
?: 0, Notification.TYPE_REMOVED_SHARED_EVENT, eventId),
|
?: 0, Notification.TYPE_REMOVED_SHARED_EVENT, eventId),
|
||||||
title = app.getNotificationTitle(Notification.TYPE_REMOVED_SHARED_EVENT),
|
title = app.getNotificationTitle(Notification.TYPE_REMOVED_SHARED_EVENT),
|
||||||
text = message,
|
text = message,
|
||||||
type = Notification.TYPE_REMOVED_SHARED_EVENT,
|
type = Notification.TYPE_REMOVED_SHARED_EVENT,
|
||||||
profileId = profile?.id,
|
profileId = profile.id,
|
||||||
profileName = profile?.name,
|
profileName = profile.name,
|
||||||
viewId = MainActivity.DRAWER_ITEM_AGENDA
|
viewId = MainActivity.DRAWER_ITEM_AGENDA
|
||||||
)
|
)
|
||||||
notificationList += notification
|
notificationList += notification
|
||||||
|
@ -40,7 +40,7 @@ class QrScannerDialog(
|
|||||||
onShowListener?.invoke(TAG)
|
onShowListener?.invoke(TAG)
|
||||||
app = activity.applicationContext as App
|
app = activity.applicationContext as App
|
||||||
scannerView = ZXingScannerView(activity)
|
scannerView = ZXingScannerView(activity)
|
||||||
scannerView.setPadding(0, 16.dp, 0, 0)
|
scannerView.setPadding(0, 16.dp, 2.dp, 0)
|
||||||
dialog = MaterialAlertDialogBuilder(activity)
|
dialog = MaterialAlertDialogBuilder(activity)
|
||||||
.setTitle(R.string.qr_scanner_dialog_title)
|
.setTitle(R.string.qr_scanner_dialog_title)
|
||||||
.setView(scannerView)
|
.setView(scannerView)
|
||||||
@ -59,4 +59,4 @@ class QrScannerDialog(
|
|||||||
}
|
}
|
||||||
scannerView.startCamera()
|
scannerView.startCamera()
|
||||||
}}
|
}}
|
||||||
}
|
}
|
||||||
|
@ -19,6 +19,7 @@ import pl.szczodrzynski.edziennik.data.api.szkolny.SzkolnyApi
|
|||||||
import pl.szczodrzynski.edziennik.data.db.full.EventFull
|
import pl.szczodrzynski.edziennik.data.db.full.EventFull
|
||||||
import pl.szczodrzynski.edziennik.databinding.DialogEventDetailsBinding
|
import pl.szczodrzynski.edziennik.databinding.DialogEventDetailsBinding
|
||||||
import pl.szczodrzynski.edziennik.ui.modules.timetable.TimetableFragment
|
import pl.szczodrzynski.edziennik.ui.modules.timetable.TimetableFragment
|
||||||
|
import pl.szczodrzynski.edziennik.utils.BetterLink
|
||||||
import pl.szczodrzynski.edziennik.utils.models.Date
|
import pl.szczodrzynski.edziennik.utils.models.Date
|
||||||
import kotlin.coroutines.CoroutineContext
|
import kotlin.coroutines.CoroutineContext
|
||||||
|
|
||||||
@ -47,6 +48,8 @@ class EventDetailsDialog(
|
|||||||
SzkolnyApi(app)
|
SzkolnyApi(app)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private var progressDialog: AlertDialog? = null
|
||||||
|
|
||||||
init { run {
|
init { run {
|
||||||
if (activity.isFinishing)
|
if (activity.isFinishing)
|
||||||
return@run
|
return@run
|
||||||
@ -64,6 +67,7 @@ class EventDetailsDialog(
|
|||||||
}
|
}
|
||||||
.setOnDismissListener {
|
.setOnDismissListener {
|
||||||
onDismissListener?.invoke(TAG)
|
onDismissListener?.invoke(TAG)
|
||||||
|
progressDialog?.dismiss()
|
||||||
}
|
}
|
||||||
.show()
|
.show()
|
||||||
|
|
||||||
@ -156,6 +160,23 @@ class EventDetailsDialog(
|
|||||||
Toast.makeText(activity, R.string.hint_edit_event, Toast.LENGTH_SHORT).show()
|
Toast.makeText(activity, R.string.hint_edit_event, Toast.LENGTH_SHORT).show()
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
b.topic.text = event.topic
|
||||||
|
BetterLink.attach(b.topic) {
|
||||||
|
dialog.dismiss()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun showRemovingProgressDialog() {
|
||||||
|
if (progressDialog != null) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
progressDialog = MaterialAlertDialogBuilder(activity)
|
||||||
|
.setTitle(R.string.please_wait)
|
||||||
|
.setMessage(R.string.event_removing_text)
|
||||||
|
.setCancelable(false)
|
||||||
|
.show()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun showRemoveEventDialog() {
|
private fun showRemoveEventDialog() {
|
||||||
@ -186,11 +207,14 @@ class EventDetailsDialog(
|
|||||||
launch {
|
launch {
|
||||||
if (eventShared && eventOwn) {
|
if (eventShared && eventOwn) {
|
||||||
// unshare + remove own event
|
// unshare + remove own event
|
||||||
Toast.makeText(activity, R.string.event_manual_unshare_remove, Toast.LENGTH_SHORT).show()
|
showRemovingProgressDialog()
|
||||||
|
|
||||||
api.runCatching(activity) {
|
api.runCatching(activity) {
|
||||||
unshareEvent(event)
|
unshareEvent(event)
|
||||||
} ?: return@launch
|
} ?: run {
|
||||||
|
progressDialog?.dismiss()
|
||||||
|
return@launch
|
||||||
|
}
|
||||||
|
|
||||||
finishRemoving()
|
finishRemoving()
|
||||||
} else if (eventShared && !eventOwn) {
|
} else if (eventShared && !eventOwn) {
|
||||||
@ -202,6 +226,7 @@ class EventDetailsDialog(
|
|||||||
Toast.makeText(activity, R.string.event_manual_remove, Toast.LENGTH_SHORT).show()
|
Toast.makeText(activity, R.string.event_manual_remove, Toast.LENGTH_SHORT).show()
|
||||||
finishRemoving()
|
finishRemoving()
|
||||||
}
|
}
|
||||||
|
progressDialog?.dismiss()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -29,9 +29,11 @@ import pl.szczodrzynski.edziennik.data.api.szkolny.SzkolnyApi
|
|||||||
import pl.szczodrzynski.edziennik.data.db.entity.Event
|
import pl.szczodrzynski.edziennik.data.db.entity.Event
|
||||||
import pl.szczodrzynski.edziennik.data.db.entity.EventType
|
import pl.szczodrzynski.edziennik.data.db.entity.EventType
|
||||||
import pl.szczodrzynski.edziennik.data.db.entity.Metadata
|
import pl.szczodrzynski.edziennik.data.db.entity.Metadata
|
||||||
|
import pl.szczodrzynski.edziennik.data.db.entity.Profile
|
||||||
import pl.szczodrzynski.edziennik.data.db.full.EventFull
|
import pl.szczodrzynski.edziennik.data.db.full.EventFull
|
||||||
import pl.szczodrzynski.edziennik.data.db.full.LessonFull
|
import pl.szczodrzynski.edziennik.data.db.full.LessonFull
|
||||||
import pl.szczodrzynski.edziennik.databinding.DialogEventManualV2Binding
|
import pl.szczodrzynski.edziennik.databinding.DialogEventManualV2Binding
|
||||||
|
import pl.szczodrzynski.edziennik.ui.dialogs.sync.RegistrationEnableDialog
|
||||||
import pl.szczodrzynski.edziennik.ui.modules.views.TimeDropdown.Companion.DISPLAY_LESSONS
|
import pl.szczodrzynski.edziennik.ui.modules.views.TimeDropdown.Companion.DISPLAY_LESSONS
|
||||||
import pl.szczodrzynski.edziennik.utils.Anim
|
import pl.szczodrzynski.edziennik.utils.Anim
|
||||||
import pl.szczodrzynski.edziennik.utils.TextInputDropDown
|
import pl.szczodrzynski.edziennik.utils.TextInputDropDown
|
||||||
@ -62,12 +64,12 @@ class EventManualDialog(
|
|||||||
private val app by lazy { activity.application as App }
|
private val app by lazy { activity.application as App }
|
||||||
private lateinit var b: DialogEventManualV2Binding
|
private lateinit var b: DialogEventManualV2Binding
|
||||||
private lateinit var dialog: AlertDialog
|
private lateinit var dialog: AlertDialog
|
||||||
|
private var profile: Profile? = null
|
||||||
|
|
||||||
private var customColor: Int? = null
|
private var customColor: Int? = null
|
||||||
private val editingShared = editingEvent?.sharedBy != null
|
private val editingShared = editingEvent?.sharedBy != null
|
||||||
private val editingOwn = editingEvent?.sharedBy == "self"
|
private val editingOwn = editingEvent?.sharedBy == "self"
|
||||||
private var removeEventDialog: AlertDialog? = null
|
private var removeEventDialog: AlertDialog? = null
|
||||||
private var defaultLoaded = false
|
|
||||||
|
|
||||||
private val api by lazy {
|
private val api by lazy {
|
||||||
SzkolnyApi(app)
|
SzkolnyApi(app)
|
||||||
@ -76,6 +78,8 @@ class EventManualDialog(
|
|||||||
private var enqueuedWeekDialog: AlertDialog? = null
|
private var enqueuedWeekDialog: AlertDialog? = null
|
||||||
private var enqueuedWeekStart = Date.getToday()
|
private var enqueuedWeekStart = Date.getToday()
|
||||||
|
|
||||||
|
private var progressDialog: AlertDialog? = null
|
||||||
|
|
||||||
init { run {
|
init { run {
|
||||||
if (activity.isFinishing)
|
if (activity.isFinishing)
|
||||||
return@run
|
return@run
|
||||||
@ -95,6 +99,8 @@ class EventManualDialog(
|
|||||||
.setOnDismissListener {
|
.setOnDismissListener {
|
||||||
onDismissListener?.invoke(TAG)
|
onDismissListener?.invoke(TAG)
|
||||||
EventBus.getDefault().unregister(this@EventManualDialog)
|
EventBus.getDefault().unregister(this@EventManualDialog)
|
||||||
|
enqueuedWeekDialog?.dismiss()
|
||||||
|
progressDialog?.dismiss()
|
||||||
}
|
}
|
||||||
.setCancelable(false)
|
.setCancelable(false)
|
||||||
.create()
|
.create()
|
||||||
@ -168,7 +174,7 @@ class EventManualDialog(
|
|||||||
enqueuedWeekStart = weekStart
|
enqueuedWeekStart = weekStart
|
||||||
|
|
||||||
EdziennikTask.syncProfile(
|
EdziennikTask.syncProfile(
|
||||||
profileId = App.profileId,
|
profileId = profileId,
|
||||||
viewIds = listOf(
|
viewIds = listOf(
|
||||||
MainActivity.DRAWER_ITEM_TIMETABLE to 0
|
MainActivity.DRAWER_ITEM_TIMETABLE to 0
|
||||||
),
|
),
|
||||||
@ -178,13 +184,40 @@ class EventManualDialog(
|
|||||||
).enqueue(activity)
|
).enqueue(activity)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun showSharingProgressDialog() {
|
||||||
|
if (progressDialog != null) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
progressDialog = MaterialAlertDialogBuilder(activity)
|
||||||
|
.setTitle(R.string.please_wait)
|
||||||
|
.setMessage(R.string.event_sharing_text)
|
||||||
|
.setCancelable(false)
|
||||||
|
.show()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun showRemovingProgressDialog() {
|
||||||
|
if (progressDialog != null) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
progressDialog = MaterialAlertDialogBuilder(activity)
|
||||||
|
.setTitle(R.string.please_wait)
|
||||||
|
.setMessage(R.string.event_removing_text)
|
||||||
|
.setCancelable(false)
|
||||||
|
.show()
|
||||||
|
}
|
||||||
|
|
||||||
@Subscribe(threadMode = ThreadMode.MAIN)
|
@Subscribe(threadMode = ThreadMode.MAIN)
|
||||||
fun onApiTaskFinishedEvent(event: ApiTaskFinishedEvent) {
|
fun onApiTaskFinishedEvent(event: ApiTaskFinishedEvent) {
|
||||||
if (event.profileId == App.profileId) {
|
if (event.profileId == profileId) {
|
||||||
enqueuedWeekDialog?.dismiss()
|
enqueuedWeekDialog?.dismiss()
|
||||||
enqueuedWeekDialog = null
|
enqueuedWeekDialog = null
|
||||||
|
progressDialog?.dismiss()
|
||||||
launch {
|
launch {
|
||||||
b.timeDropdown.loadItems()
|
b.timeDropdown.loadItems()
|
||||||
|
b.timeDropdown.selectDefault(editingEvent?.startTime)
|
||||||
|
b.timeDropdown.selectDefault(defaultLesson?.displayStartTime ?: defaultTime)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -193,19 +226,22 @@ class EventManualDialog(
|
|||||||
fun onApiTaskAllFinishedEvent(event: ApiTaskAllFinishedEvent) {
|
fun onApiTaskAllFinishedEvent(event: ApiTaskAllFinishedEvent) {
|
||||||
enqueuedWeekDialog?.dismiss()
|
enqueuedWeekDialog?.dismiss()
|
||||||
enqueuedWeekDialog = null
|
enqueuedWeekDialog = null
|
||||||
|
progressDialog?.dismiss()
|
||||||
}
|
}
|
||||||
|
|
||||||
@Subscribe(threadMode = ThreadMode.MAIN)
|
@Subscribe(threadMode = ThreadMode.MAIN)
|
||||||
fun onApiTaskErrorEvent(event: ApiTaskErrorEvent) {
|
fun onApiTaskErrorEvent(event: ApiTaskErrorEvent) {
|
||||||
dialog.dismiss()
|
|
||||||
enqueuedWeekDialog?.dismiss()
|
enqueuedWeekDialog?.dismiss()
|
||||||
enqueuedWeekDialog = null
|
enqueuedWeekDialog = null
|
||||||
|
progressDialog?.dismiss()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun loadLists() { launch {
|
private fun loadLists() { launch {
|
||||||
|
profile = withContext(Dispatchers.Default) { app.db.profileDao().getByIdNow(profileId) }
|
||||||
|
|
||||||
with (b.dateDropdown) {
|
with (b.dateDropdown) {
|
||||||
db = app.db
|
db = app.db
|
||||||
profileId = App.profileId
|
profileId = profileId
|
||||||
showWeekDays = false
|
showWeekDays = false
|
||||||
showDays = true
|
showDays = true
|
||||||
showOtherDate = true
|
showOtherDate = true
|
||||||
@ -233,7 +269,7 @@ class EventManualDialog(
|
|||||||
|
|
||||||
with (b.timeDropdown) {
|
with (b.timeDropdown) {
|
||||||
db = app.db
|
db = app.db
|
||||||
profileId = App.profileId
|
profileId = profileId
|
||||||
showAllDay = true
|
showAllDay = true
|
||||||
showCustomTime = true
|
showCustomTime = true
|
||||||
lessonsDate = b.dateDropdown.getSelected() as? Date ?: Date.getToday()
|
lessonsDate = b.dateDropdown.getSelected() as? Date ?: Date.getToday()
|
||||||
@ -241,6 +277,8 @@ class EventManualDialog(
|
|||||||
if (!loadItems())
|
if (!loadItems())
|
||||||
syncTimetable(lessonsDate ?: Date.getToday())
|
syncTimetable(lessonsDate ?: Date.getToday())
|
||||||
selectDefault(editingEvent?.startTime)
|
selectDefault(editingEvent?.startTime)
|
||||||
|
if (editingEvent != null && editingEvent.startTime == null)
|
||||||
|
select(0L)
|
||||||
selectDefault(defaultLesson?.displayStartTime ?: defaultTime)
|
selectDefault(defaultLesson?.displayStartTime ?: defaultTime)
|
||||||
onLessonSelected = { lesson ->
|
onLessonSelected = { lesson ->
|
||||||
lesson.displaySubjectId?.let { b.subjectDropdown.selectSubject(it) } ?: b.subjectDropdown.deselect()
|
lesson.displaySubjectId?.let { b.subjectDropdown.selectSubject(it) } ?: b.subjectDropdown.deselect()
|
||||||
@ -251,7 +289,7 @@ class EventManualDialog(
|
|||||||
|
|
||||||
with (b.teamDropdown) {
|
with (b.teamDropdown) {
|
||||||
db = app.db
|
db = app.db
|
||||||
profileId = App.profileId
|
profileId = profileId
|
||||||
showNoTeam = true
|
showNoTeam = true
|
||||||
loadItems()
|
loadItems()
|
||||||
selectTeamClass()
|
selectTeamClass()
|
||||||
@ -261,7 +299,7 @@ class EventManualDialog(
|
|||||||
|
|
||||||
with (b.subjectDropdown) {
|
with (b.subjectDropdown) {
|
||||||
db = app.db
|
db = app.db
|
||||||
profileId = App.profileId
|
profileId = profileId
|
||||||
showNoSubject = true
|
showNoSubject = true
|
||||||
showCustomSubject = false
|
showCustomSubject = false
|
||||||
loadItems()
|
loadItems()
|
||||||
@ -271,7 +309,7 @@ class EventManualDialog(
|
|||||||
|
|
||||||
with (b.teacherDropdown) {
|
with (b.teacherDropdown) {
|
||||||
db = app.db
|
db = app.db
|
||||||
profileId = App.profileId
|
profileId = profileId
|
||||||
showNoTeacher = true
|
showNoTeacher = true
|
||||||
loadItems()
|
loadItems()
|
||||||
selectDefault(editingEvent?.teacherId)
|
selectDefault(editingEvent?.teacherId)
|
||||||
@ -323,7 +361,7 @@ class EventManualDialog(
|
|||||||
}
|
}
|
||||||
|
|
||||||
b.typeColor.onClick {
|
b.typeColor.onClick {
|
||||||
val currentColor = (b.typeDropdown?.selected?.tag as EventType?)?.color ?: Event.COLOR_DEFAULT
|
val currentColor = (b.typeDropdown.selected?.tag as EventType?)?.color ?: Event.COLOR_DEFAULT
|
||||||
val colorPickerDialog = ColorPickerDialog.newBuilder()
|
val colorPickerDialog = ColorPickerDialog.newBuilder()
|
||||||
.setColor(currentColor)
|
.setColor(currentColor)
|
||||||
.create()
|
.create()
|
||||||
@ -365,16 +403,24 @@ class EventManualDialog(
|
|||||||
|
|
||||||
private fun saveEvent() {
|
private fun saveEvent() {
|
||||||
val date = b.dateDropdown.getSelected() as? Date
|
val date = b.dateDropdown.getSelected() as? Date
|
||||||
val startTimePair = b.timeDropdown.getSelected() as? Pair<*, *>
|
val timeSelected = b.timeDropdown.getSelected()
|
||||||
val startTime = startTimePair?.first as? Time
|
|
||||||
val teamId = b.teamDropdown.getSelected() as? Long
|
val teamId = b.teamDropdown.getSelected() as? Long
|
||||||
val type = b.typeDropdown.selected?.id
|
val type = b.typeDropdown.selected?.id
|
||||||
val topic = b.topic.text?.toString()
|
val topic = b.topic.text?.toString()
|
||||||
val subjectId = b.subjectDropdown.getSelected() as? Long
|
val subjectId = b.subjectDropdown.getSelected() as? Long
|
||||||
val teacherId = b.teacherDropdown.getSelected() as? Long
|
val teacherId = b.teacherDropdown.getSelected()
|
||||||
|
|
||||||
val share = b.shareSwitch.isChecked
|
val share = b.shareSwitch.isChecked
|
||||||
|
|
||||||
|
if (share && profile?.registration != Profile.REGISTRATION_ENABLED) {
|
||||||
|
RegistrationEnableDialog(activity, profileId).showEventShareDialog {
|
||||||
|
if (it != null)
|
||||||
|
profile = it
|
||||||
|
saveEvent()
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
b.dateDropdown.error = null
|
b.dateDropdown.error = null
|
||||||
b.teamDropdown.error = null
|
b.teamDropdown.error = null
|
||||||
b.typeDropdown.error = null
|
b.typeDropdown.error = null
|
||||||
@ -384,24 +430,39 @@ class EventManualDialog(
|
|||||||
|
|
||||||
if (date == null) {
|
if (date == null) {
|
||||||
b.dateDropdown.error = app.getString(R.string.dialog_event_manual_date_choose)
|
b.dateDropdown.error = app.getString(R.string.dialog_event_manual_date_choose)
|
||||||
|
b.dateDropdown.requestFocus()
|
||||||
|
isError = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if (timeSelected !is Pair<*, *> && timeSelected != 0L) {
|
||||||
|
b.timeDropdown.error = app.getString(R.string.dialog_event_manual_time_choose)
|
||||||
|
if (!isError) b.timeDropdown.parent.requestChildFocus(b.timeDropdown, b.timeDropdown)
|
||||||
isError = true
|
isError = true
|
||||||
}
|
}
|
||||||
|
|
||||||
if (share && teamId == null) {
|
if (share && teamId == null) {
|
||||||
b.teamDropdown.error = app.getString(R.string.dialog_event_manual_team_choose)
|
b.teamDropdown.error = app.getString(R.string.dialog_event_manual_team_choose)
|
||||||
|
if (!isError) b.teamDropdown.parent.requestChildFocus(b.teamDropdown, b.teamDropdown)
|
||||||
isError = true
|
isError = true
|
||||||
}
|
}
|
||||||
|
|
||||||
if (type == null) {
|
if (type == null) {
|
||||||
b.typeDropdown.error = app.getString(R.string.dialog_event_manual_type_choose)
|
b.typeDropdown.error = app.getString(R.string.dialog_event_manual_type_choose)
|
||||||
|
if (!isError) b.typeDropdown.requestFocus()
|
||||||
isError = true
|
isError = true
|
||||||
}
|
}
|
||||||
|
|
||||||
if (topic.isNullOrBlank()) {
|
if (topic.isNullOrBlank()) {
|
||||||
b.topic.error = app.getString(R.string.dialog_event_manual_topic_choose)
|
b.topic.error = app.getString(R.string.dialog_event_manual_topic_choose)
|
||||||
|
if (!isError) b.topic.requestFocus()
|
||||||
isError = true
|
isError = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val startTime = if (timeSelected == 0L)
|
||||||
|
null
|
||||||
|
else
|
||||||
|
(timeSelected as? Pair<*, *>)?.first as? Time
|
||||||
|
|
||||||
if (isError) return
|
if (isError) return
|
||||||
|
|
||||||
val id = System.currentTimeMillis()
|
val id = System.currentTimeMillis()
|
||||||
@ -436,7 +497,7 @@ class EventManualDialog(
|
|||||||
val profile = app.db.profileDao().getByIdNow(profileId)
|
val profile = app.db.profileDao().getByIdNow(profileId)
|
||||||
|
|
||||||
if (!share && !editingShared) {
|
if (!share && !editingShared) {
|
||||||
Toast.makeText(activity, R.string.event_manual_saving, Toast.LENGTH_SHORT).show()
|
//Toast.makeText(activity, R.string.event_manual_saving, Toast.LENGTH_SHORT).show()
|
||||||
finishAdding(eventObject, metadataObject)
|
finishAdding(eventObject, metadataObject)
|
||||||
}
|
}
|
||||||
else if (editingShared && !editingOwn) {
|
else if (editingShared && !editingOwn) {
|
||||||
@ -444,7 +505,7 @@ class EventManualDialog(
|
|||||||
// TODO
|
// TODO
|
||||||
}
|
}
|
||||||
else if (!share && editingShared) {
|
else if (!share && editingShared) {
|
||||||
Toast.makeText(activity, R.string.event_manual_unshare, Toast.LENGTH_SHORT).show()
|
showSharingProgressDialog()
|
||||||
|
|
||||||
eventObject.apply {
|
eventObject.apply {
|
||||||
sharedBy = null
|
sharedBy = null
|
||||||
@ -453,13 +514,16 @@ class EventManualDialog(
|
|||||||
|
|
||||||
api.runCatching(activity) {
|
api.runCatching(activity) {
|
||||||
unshareEvent(eventObject)
|
unshareEvent(eventObject)
|
||||||
} ?: return@launch
|
} ?: run {
|
||||||
|
progressDialog?.dismiss()
|
||||||
|
return@launch
|
||||||
|
}
|
||||||
|
|
||||||
eventObject.sharedByName = null
|
eventObject.sharedByName = null
|
||||||
finishAdding(eventObject, metadataObject)
|
finishAdding(eventObject, metadataObject)
|
||||||
}
|
}
|
||||||
else if (share) {
|
else if (share) {
|
||||||
Toast.makeText(activity, R.string.event_manual_share, Toast.LENGTH_SHORT).show()
|
showSharingProgressDialog()
|
||||||
|
|
||||||
eventObject.apply {
|
eventObject.apply {
|
||||||
sharedBy = profile?.userCode
|
sharedBy = profile?.userCode
|
||||||
@ -470,7 +534,10 @@ class EventManualDialog(
|
|||||||
|
|
||||||
api.runCatching(activity) {
|
api.runCatching(activity) {
|
||||||
shareEvent(eventObject.withMetadata(metadataObject))
|
shareEvent(eventObject.withMetadata(metadataObject))
|
||||||
} ?: return@launch
|
} ?: run {
|
||||||
|
progressDialog?.dismiss()
|
||||||
|
return@launch
|
||||||
|
}
|
||||||
|
|
||||||
eventObject.sharedBy = "self"
|
eventObject.sharedBy = "self"
|
||||||
finishAdding(eventObject, metadataObject)
|
finishAdding(eventObject, metadataObject)
|
||||||
@ -478,6 +545,7 @@ class EventManualDialog(
|
|||||||
else {
|
else {
|
||||||
Toast.makeText(activity, "Unknown action :(", Toast.LENGTH_SHORT).show()
|
Toast.makeText(activity, "Unknown action :(", Toast.LENGTH_SHORT).show()
|
||||||
}
|
}
|
||||||
|
progressDialog?.dismiss()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -485,11 +553,14 @@ class EventManualDialog(
|
|||||||
launch {
|
launch {
|
||||||
if (editingShared && editingOwn) {
|
if (editingShared && editingOwn) {
|
||||||
// unshare + remove own event
|
// unshare + remove own event
|
||||||
Toast.makeText(activity, R.string.event_manual_unshare_remove, Toast.LENGTH_SHORT).show()
|
showRemovingProgressDialog()
|
||||||
|
|
||||||
api.runCatching(activity) {
|
api.runCatching(activity) {
|
||||||
unshareEvent(editingEvent!!)
|
unshareEvent(editingEvent!!)
|
||||||
} ?: return@launch
|
} ?: run {
|
||||||
|
progressDialog?.dismiss()
|
||||||
|
return@launch
|
||||||
|
}
|
||||||
|
|
||||||
finishRemoving()
|
finishRemoving()
|
||||||
} else if (editingShared && !editingOwn) {
|
} else if (editingShared && !editingOwn) {
|
||||||
@ -498,9 +569,10 @@ class EventManualDialog(
|
|||||||
// TODO
|
// TODO
|
||||||
} else {
|
} else {
|
||||||
// remove event
|
// remove event
|
||||||
Toast.makeText(activity, R.string.event_manual_remove, Toast.LENGTH_SHORT).show()
|
//Toast.makeText(activity, R.string.event_manual_remove, Toast.LENGTH_SHORT).show()
|
||||||
finishRemoving()
|
finishRemoving()
|
||||||
}
|
}
|
||||||
|
progressDialog?.dismiss()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -58,7 +58,7 @@ class GradeDetailsDialog(
|
|||||||
b.weightText = manager.getWeightString(app, grade)
|
b.weightText = manager.getWeightString(app, grade)
|
||||||
b.commentVisible = false
|
b.commentVisible = false
|
||||||
b.devMode = App.debugMode
|
b.devMode = App.debugMode
|
||||||
b.gradeName.setTextColor(if (ColorUtils.calculateLuminance(gradeColor) > 0.3) 0x99000000.toInt() else 0x99ffffff.toInt())
|
b.gradeName.setTextColor(if (ColorUtils.calculateLuminance(gradeColor) > 0.3) 0xaa000000.toInt() else 0xccffffff.toInt())
|
||||||
b.gradeName.background.setTintColor(gradeColor)
|
b.gradeName.background.setTintColor(gradeColor)
|
||||||
|
|
||||||
b.gradeValue = if (grade.weight == 0f || grade.value < 0f) -1f else manager.getGradeValue(grade)
|
b.gradeValue = if (grade.weight == 0f || grade.value < 0f) -1f else manager.getGradeValue(grade)
|
||||||
|
@ -20,6 +20,7 @@ import pl.szczodrzynski.edziennik.utils.managers.GradesManager.Companion.YEAR_1_
|
|||||||
import pl.szczodrzynski.edziennik.utils.managers.GradesManager.Companion.YEAR_1_SEM_2_AVG
|
import pl.szczodrzynski.edziennik.utils.managers.GradesManager.Companion.YEAR_1_SEM_2_AVG
|
||||||
import pl.szczodrzynski.edziennik.utils.managers.GradesManager.Companion.YEAR_1_SEM_2_SEM
|
import pl.szczodrzynski.edziennik.utils.managers.GradesManager.Companion.YEAR_1_SEM_2_SEM
|
||||||
import pl.szczodrzynski.edziennik.utils.managers.GradesManager.Companion.YEAR_ALL_GRADES
|
import pl.szczodrzynski.edziennik.utils.managers.GradesManager.Companion.YEAR_ALL_GRADES
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
class GradesConfigDialog(
|
class GradesConfigDialog(
|
||||||
val activity: AppCompatActivity,
|
val activity: AppCompatActivity,
|
||||||
@ -88,14 +89,34 @@ class GradesConfigDialog(
|
|||||||
else -> null
|
else -> null
|
||||||
}?.isChecked = true
|
}?.isChecked = true
|
||||||
|
|
||||||
b.dontCountZeroToAverage.isChecked = !profileConfig.countZeroToAvg
|
b.dontCountGrades.isChecked = profileConfig.dontCountEnabled && profileConfig.dontCountGrades.isNotEmpty()
|
||||||
b.hideImproved.isChecked = profileConfig.hideImproved
|
b.hideImproved.isChecked = profileConfig.hideImproved
|
||||||
b.averageWithoutWeight.isChecked = profileConfig.averageWithoutWeight
|
b.averageWithoutWeight.isChecked = profileConfig.averageWithoutWeight
|
||||||
|
|
||||||
|
if (profileConfig.dontCountGrades.isEmpty()) {
|
||||||
|
b.dontCountGradesText.setText("nb, 0, bz, bd")
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
b.dontCountGradesText.setText(profileConfig.dontCountGrades.join(", "))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun saveConfig() {
|
private fun saveConfig() {
|
||||||
profileConfig.plusValue = if (b.customPlusCheckBox.isChecked) b.customPlusValue.progress else null
|
profileConfig.plusValue = if (b.customPlusCheckBox.isChecked) b.customPlusValue.progress else null
|
||||||
profileConfig.minusValue = if (b.customMinusCheckBox.isChecked) b.customMinusValue.progress else null
|
profileConfig.minusValue = if (b.customMinusCheckBox.isChecked) b.customMinusValue.progress else null
|
||||||
|
|
||||||
|
b.dontCountGradesText.setText(
|
||||||
|
b.dontCountGradesText
|
||||||
|
.text
|
||||||
|
?.toString()
|
||||||
|
?.toLowerCase(Locale.getDefault())
|
||||||
|
?.replace(", ", ",")
|
||||||
|
)
|
||||||
|
profileConfig.dontCountEnabled = b.dontCountGrades.isChecked
|
||||||
|
profileConfig.dontCountGrades = b.dontCountGradesText.text
|
||||||
|
?.split(",")
|
||||||
|
?.map { it.trim() }
|
||||||
|
?: listOf()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun initView() {
|
private fun initView() {
|
||||||
@ -127,7 +148,6 @@ class GradesConfigDialog(
|
|||||||
b.gradeAverageMode2.setOnSelectedListener { profileConfig.yearAverageMode = YEAR_1_AVG_2_SEM }
|
b.gradeAverageMode2.setOnSelectedListener { profileConfig.yearAverageMode = YEAR_1_AVG_2_SEM }
|
||||||
b.gradeAverageMode3.setOnSelectedListener { profileConfig.yearAverageMode = YEAR_1_SEM_2_SEM }
|
b.gradeAverageMode3.setOnSelectedListener { profileConfig.yearAverageMode = YEAR_1_SEM_2_SEM }
|
||||||
|
|
||||||
b.dontCountZeroToAverage.onChange { _, isChecked -> profileConfig.countZeroToAvg = !isChecked }
|
|
||||||
b.hideImproved.onChange { _, isChecked -> profileConfig.hideImproved = isChecked }
|
b.hideImproved.onChange { _, isChecked -> profileConfig.hideImproved = isChecked }
|
||||||
b.averageWithoutWeight.onChange { _, isChecked -> profileConfig.averageWithoutWeight = isChecked }
|
b.averageWithoutWeight.onChange { _, isChecked -> profileConfig.averageWithoutWeight = isChecked }
|
||||||
|
|
||||||
|
@ -0,0 +1,89 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) Kuba Szczodrzyński 2020-3-15.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package pl.szczodrzynski.edziennik.ui.dialogs.sync
|
||||||
|
|
||||||
|
import android.text.Html
|
||||||
|
import androidx.appcompat.app.AlertDialog
|
||||||
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||||
|
import kotlinx.coroutines.*
|
||||||
|
import pl.szczodrzynski.edziennik.App
|
||||||
|
import pl.szczodrzynski.edziennik.R
|
||||||
|
import pl.szczodrzynski.edziennik.data.api.szkolny.SzkolnyApi
|
||||||
|
import pl.szczodrzynski.edziennik.data.api.task.AppSync
|
||||||
|
import pl.szczodrzynski.edziennik.data.db.entity.Profile
|
||||||
|
import kotlin.coroutines.CoroutineContext
|
||||||
|
|
||||||
|
class RegistrationEnableDialog(
|
||||||
|
val activity: AppCompatActivity,
|
||||||
|
val profileId: Int
|
||||||
|
) : CoroutineScope {
|
||||||
|
companion object {
|
||||||
|
private const val TAG = "RegistrationEnableDialog"
|
||||||
|
}
|
||||||
|
|
||||||
|
private lateinit var app: App
|
||||||
|
|
||||||
|
private val job = Job()
|
||||||
|
override val coroutineContext: CoroutineContext
|
||||||
|
get() = job + Dispatchers.Main
|
||||||
|
|
||||||
|
// local variables go here
|
||||||
|
private var progressDialog: AlertDialog? = null
|
||||||
|
|
||||||
|
init { run {
|
||||||
|
if (activity.isFinishing)
|
||||||
|
return@run
|
||||||
|
app = activity.applicationContext as App
|
||||||
|
}}
|
||||||
|
|
||||||
|
fun showEventShareDialog(onSuccess: (profile: Profile?) -> Unit) {
|
||||||
|
MaterialAlertDialogBuilder(activity)
|
||||||
|
.setTitle(R.string.event_manual_need_registration_title)
|
||||||
|
.setMessage(R.string.event_manual_need_registration_text)
|
||||||
|
.setPositiveButton(R.string.ok) { dialog, which ->
|
||||||
|
enableRegistration(onSuccess)
|
||||||
|
}
|
||||||
|
.setNegativeButton(R.string.cancel, null)
|
||||||
|
.show()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun showEnableDialog(onSuccess: (profile: Profile?) -> Unit) {
|
||||||
|
MaterialAlertDialogBuilder(activity)
|
||||||
|
.setTitle(R.string.registration_enable_dialog_title)
|
||||||
|
.setMessage(Html.fromHtml(app.getString(R.string.registration_enable_dialog_text)))
|
||||||
|
.setPositiveButton(R.string.ok) { dialog, which ->
|
||||||
|
enableRegistration(onSuccess)
|
||||||
|
}
|
||||||
|
.setNegativeButton(R.string.cancel, null)
|
||||||
|
.show()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun enableRegistration(onSuccess: (profile: Profile?) -> Unit) { launch {
|
||||||
|
progressDialog = MaterialAlertDialogBuilder(activity)
|
||||||
|
.setTitle(R.string.please_wait)
|
||||||
|
.setMessage(R.string.registration_enable_progress_text)
|
||||||
|
.setCancelable(false)
|
||||||
|
.show()
|
||||||
|
|
||||||
|
val profile = withContext(Dispatchers.Default) {
|
||||||
|
val profile = app.db.profileDao().getByIdNow(profileId) ?: return@withContext null
|
||||||
|
profile.registration = Profile.REGISTRATION_ENABLED
|
||||||
|
|
||||||
|
// force full registration of the user
|
||||||
|
App.config.getFor(profile.id).hash = ""
|
||||||
|
|
||||||
|
AppSync(app, mutableListOf(), listOf(profile), SzkolnyApi(app)).run(0L, markAsSeen = true)
|
||||||
|
app.db.profileDao().add(profile)
|
||||||
|
if (profile.id == App.profileId) {
|
||||||
|
App.profile.registration = profile.registration
|
||||||
|
}
|
||||||
|
return@withContext profile
|
||||||
|
}
|
||||||
|
|
||||||
|
progressDialog?.dismiss()
|
||||||
|
onSuccess(profile)
|
||||||
|
}}
|
||||||
|
}
|
@ -18,8 +18,8 @@ import androidx.appcompat.app.AlertDialog
|
|||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.cardview.widget.CardView
|
import androidx.cardview.widget.CardView
|
||||||
import androidx.core.content.FileProvider
|
import androidx.core.content.FileProvider
|
||||||
import com.google.android.material.datepicker.MaterialDatePicker
|
|
||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||||
|
import com.wdullaer.materialdatetimepicker.date.DatePickerDialog
|
||||||
import kotlinx.coroutines.*
|
import kotlinx.coroutines.*
|
||||||
import org.greenrobot.eventbus.EventBus
|
import org.greenrobot.eventbus.EventBus
|
||||||
import org.greenrobot.eventbus.Subscribe
|
import org.greenrobot.eventbus.Subscribe
|
||||||
@ -116,18 +116,16 @@ class GenerateBlockTimetableDialog(
|
|||||||
}}
|
}}
|
||||||
|
|
||||||
private fun selectDate() {
|
private fun selectDate() {
|
||||||
MaterialDatePicker.Builder
|
val date = Date.getToday()
|
||||||
.datePicker()
|
DatePickerDialog
|
||||||
.setSelection(Date.getToday().inMillis)
|
.newInstance({ _, year, monthOfYear, dayOfMonth ->
|
||||||
.build()
|
val dateSelected = Date(year, monthOfYear, dayOfMonth)
|
||||||
|
generateBlockTimetable(dateSelected.weekStart, dateSelected.weekEnd)
|
||||||
|
}, date.year, date.month, date.day)
|
||||||
.apply {
|
.apply {
|
||||||
addOnPositiveButtonClickListener { dateInMillis ->
|
accentColor = R.attr.colorPrimary.resolveAttr(this@GenerateBlockTimetableDialog.activity)
|
||||||
dismiss()
|
show(this@GenerateBlockTimetableDialog.activity.supportFragmentManager, "DatePickerDialog")
|
||||||
val selectedDate = Date.fromMillis(dateInMillis)
|
|
||||||
generateBlockTimetable(selectedDate.weekStart, selectedDate.weekEnd)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
.show(activity.supportFragmentManager, "MaterialDatePicker")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Subscribe(threadMode = ThreadMode.MAIN)
|
@Subscribe(threadMode = ThreadMode.MAIN)
|
||||||
|
@ -33,6 +33,7 @@ import pl.szczodrzynski.edziennik.utils.SimpleDividerItemDecoration;
|
|||||||
import pl.szczodrzynski.edziennik.utils.Themes;
|
import pl.szczodrzynski.edziennik.utils.Themes;
|
||||||
import pl.szczodrzynski.navlib.bottomsheet.items.BottomSheetPrimaryItem;
|
import pl.szczodrzynski.navlib.bottomsheet.items.BottomSheetPrimaryItem;
|
||||||
|
|
||||||
|
import static androidx.recyclerview.widget.RecyclerView.SCROLL_STATE_IDLE;
|
||||||
import static pl.szczodrzynski.edziennik.data.db.entity.LoginStore.LOGIN_TYPE_LIBRUS;
|
import static pl.szczodrzynski.edziennik.data.db.entity.LoginStore.LOGIN_TYPE_LIBRUS;
|
||||||
import static pl.szczodrzynski.edziennik.data.db.entity.Metadata.TYPE_ANNOUNCEMENT;
|
import static pl.szczodrzynski.edziennik.data.db.entity.Metadata.TYPE_ANNOUNCEMENT;
|
||||||
|
|
||||||
@ -90,6 +91,18 @@ public class AnnouncementsFragment extends Fragment {
|
|||||||
recyclerView.setLayoutManager(linearLayoutManager);
|
recyclerView.setLayoutManager(linearLayoutManager);
|
||||||
recyclerView.addItemDecoration(new SimpleDividerItemDecoration(view.getContext()));
|
recyclerView.addItemDecoration(new SimpleDividerItemDecoration(view.getContext()));
|
||||||
|
|
||||||
|
recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
|
||||||
|
@Override
|
||||||
|
public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) {
|
||||||
|
if (recyclerView.canScrollVertically(-1)) {
|
||||||
|
b.refreshLayout.setEnabled(false);
|
||||||
|
}
|
||||||
|
if (!recyclerView.canScrollVertically(-1) && newState == SCROLL_STATE_IDLE) {
|
||||||
|
b.refreshLayout.setEnabled(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
app.db.announcementDao().getAll(App.Companion.getProfileId()).observe(this, announcements -> {
|
app.db.announcementDao().getAll(App.Companion.getProfileId()).observe(this, announcements -> {
|
||||||
if (app == null || activity == null || b == null || !isAdded())
|
if (app == null || activity == null || b == null || !isAdded())
|
||||||
return;
|
return;
|
||||||
|
@ -21,6 +21,7 @@ import androidx.core.graphics.ColorUtils;
|
|||||||
import androidx.databinding.DataBindingUtil;
|
import androidx.databinding.DataBindingUtil;
|
||||||
import androidx.fragment.app.Fragment;
|
import androidx.fragment.app.Fragment;
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||||
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
|
||||||
import com.mikepenz.iconics.typeface.library.community.material.CommunityMaterial;
|
import com.mikepenz.iconics.typeface.library.community.material.CommunityMaterial;
|
||||||
|
|
||||||
@ -40,6 +41,7 @@ import pl.szczodrzynski.edziennik.utils.SimpleDividerItemDecoration;
|
|||||||
import pl.szczodrzynski.edziennik.utils.Themes;
|
import pl.szczodrzynski.edziennik.utils.Themes;
|
||||||
import pl.szczodrzynski.navlib.bottomsheet.items.BottomSheetPrimaryItem;
|
import pl.szczodrzynski.navlib.bottomsheet.items.BottomSheetPrimaryItem;
|
||||||
|
|
||||||
|
import static androidx.recyclerview.widget.RecyclerView.SCROLL_STATE_IDLE;
|
||||||
import static pl.szczodrzynski.edziennik.data.db.entity.Attendance.TYPE_ABSENT;
|
import static pl.szczodrzynski.edziennik.data.db.entity.Attendance.TYPE_ABSENT;
|
||||||
import static pl.szczodrzynski.edziennik.data.db.entity.Attendance.TYPE_ABSENT_EXCUSED;
|
import static pl.szczodrzynski.edziennik.data.db.entity.Attendance.TYPE_ABSENT_EXCUSED;
|
||||||
import static pl.szczodrzynski.edziennik.data.db.entity.Attendance.TYPE_BELATED;
|
import static pl.szczodrzynski.edziennik.data.db.entity.Attendance.TYPE_BELATED;
|
||||||
@ -181,6 +183,18 @@ public class AttendanceFragment extends Fragment {
|
|||||||
b.attendanceView.setLayoutManager(linearLayoutManager);
|
b.attendanceView.setLayoutManager(linearLayoutManager);
|
||||||
b.attendanceView.addItemDecoration(new SimpleDividerItemDecoration(getContext()));
|
b.attendanceView.addItemDecoration(new SimpleDividerItemDecoration(getContext()));
|
||||||
|
|
||||||
|
b.attendanceView.addOnScrollListener(new RecyclerView.OnScrollListener() {
|
||||||
|
@Override
|
||||||
|
public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) {
|
||||||
|
if (recyclerView.canScrollVertically(-1)) {
|
||||||
|
b.refreshLayout.setEnabled(false);
|
||||||
|
}
|
||||||
|
if (!recyclerView.canScrollVertically(-1) && newState == SCROLL_STATE_IDLE) {
|
||||||
|
b.refreshLayout.setEnabled(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
App.db.attendanceDao().getAll(App.Companion.getProfileId()).observe(this, attendance -> {
|
App.db.attendanceDao().getAll(App.Companion.getProfileId()).observe(this, attendance -> {
|
||||||
if (app == null || activity == null || b == null || !isAdded())
|
if (app == null || activity == null || b == null || !isAdded())
|
||||||
return;
|
return;
|
||||||
|
@ -40,6 +40,7 @@ class MainSnackbar(val activity: AppCompatActivity) {
|
|||||||
setAction(actionText) {
|
setAction(actionText) {
|
||||||
onClick?.invoke()
|
onClick?.invoke()
|
||||||
}
|
}
|
||||||
|
duration = 7000
|
||||||
show()
|
show()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -15,6 +15,7 @@ import androidx.appcompat.widget.PopupMenu;
|
|||||||
import androidx.databinding.DataBindingUtil;
|
import androidx.databinding.DataBindingUtil;
|
||||||
import androidx.fragment.app.Fragment;
|
import androidx.fragment.app.Fragment;
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||||
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
|
||||||
import com.mikepenz.iconics.typeface.library.community.material.CommunityMaterial;
|
import com.mikepenz.iconics.typeface.library.community.material.CommunityMaterial;
|
||||||
|
|
||||||
@ -31,6 +32,7 @@ import pl.szczodrzynski.edziennik.databinding.FragmentBehaviourBinding;
|
|||||||
import pl.szczodrzynski.edziennik.utils.Themes;
|
import pl.szczodrzynski.edziennik.utils.Themes;
|
||||||
import pl.szczodrzynski.navlib.bottomsheet.items.BottomSheetPrimaryItem;
|
import pl.szczodrzynski.navlib.bottomsheet.items.BottomSheetPrimaryItem;
|
||||||
|
|
||||||
|
import static androidx.recyclerview.widget.RecyclerView.SCROLL_STATE_IDLE;
|
||||||
import static pl.szczodrzynski.edziennik.data.db.entity.Metadata.TYPE_NOTICE;
|
import static pl.szczodrzynski.edziennik.data.db.entity.Metadata.TYPE_NOTICE;
|
||||||
|
|
||||||
public class BehaviourFragment extends Fragment {
|
public class BehaviourFragment extends Fragment {
|
||||||
@ -97,6 +99,18 @@ public class BehaviourFragment extends Fragment {
|
|||||||
b.noticesView.setHasFixedSize(true);
|
b.noticesView.setHasFixedSize(true);
|
||||||
b.noticesView.setLayoutManager(linearLayoutManager);
|
b.noticesView.setLayoutManager(linearLayoutManager);
|
||||||
|
|
||||||
|
b.noticesView.addOnScrollListener(new RecyclerView.OnScrollListener() {
|
||||||
|
@Override
|
||||||
|
public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) {
|
||||||
|
if (recyclerView.canScrollVertically(-1)) {
|
||||||
|
b.refreshLayout.setEnabled(false);
|
||||||
|
}
|
||||||
|
if (!recyclerView.canScrollVertically(-1) && newState == SCROLL_STATE_IDLE) {
|
||||||
|
b.refreshLayout.setEnabled(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
app.db.noticeDao().getAll(App.Companion.getProfileId()).observe(this, notices -> {
|
app.db.noticeDao().getAll(App.Companion.getProfileId()).observe(this, notices -> {
|
||||||
if (app == null || activity == null || b == null || !isAdded())
|
if (app == null || activity == null || b == null || !isAdded())
|
||||||
return;
|
return;
|
||||||
|
@ -1,54 +0,0 @@
|
|||||||
package pl.szczodrzynski.edziennik.ui.modules.feedback;
|
|
||||||
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.view.MenuItem;
|
|
||||||
|
|
||||||
import androidx.appcompat.app.AppCompatActivity;
|
|
||||||
import androidx.databinding.DataBindingUtil;
|
|
||||||
|
|
||||||
import pl.szczodrzynski.edziennik.App;
|
|
||||||
import pl.szczodrzynski.edziennik.R;
|
|
||||||
import pl.szczodrzynski.edziennik.databinding.ActivityFeedbackBinding;
|
|
||||||
import pl.szczodrzynski.edziennik.utils.Themes;
|
|
||||||
|
|
||||||
public class FeedbackActivity extends AppCompatActivity {
|
|
||||||
|
|
||||||
private static final String TAG = "FeedbackActivity";
|
|
||||||
private App app;
|
|
||||||
private ActivityFeedbackBinding b;
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
|
||||||
super.onCreate(savedInstanceState);
|
|
||||||
setTheme(Themes.INSTANCE.getAppTheme());
|
|
||||||
b = DataBindingUtil.inflate(getLayoutInflater(), R.layout.activity_feedback, null, false);
|
|
||||||
setContentView(b.getRoot());
|
|
||||||
app = (App) getApplication();
|
|
||||||
|
|
||||||
setSupportActionBar(b.toolbar);
|
|
||||||
if (getSupportActionBar() != null)
|
|
||||||
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean onOptionsItemSelected(MenuItem item) {
|
|
||||||
if (item.getItemId() == android.R.id.home) // Press Back Icon
|
|
||||||
{
|
|
||||||
finish();
|
|
||||||
}
|
|
||||||
|
|
||||||
return super.onOptionsItemSelected(item);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onResume() {
|
|
||||||
super.onResume();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onPause() {
|
|
||||||
super.onPause();
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,25 @@
|
|||||||
|
package pl.szczodrzynski.edziennik.ui.modules.feedback
|
||||||
|
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.view.MenuItem
|
||||||
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
|
import pl.szczodrzynski.edziennik.R
|
||||||
|
import pl.szczodrzynski.edziennik.utils.Themes.appTheme
|
||||||
|
|
||||||
|
class FeedbackActivity : AppCompatActivity() {
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
setTheme(appTheme)
|
||||||
|
setContentView(R.layout.activity_feedback)
|
||||||
|
|
||||||
|
val transaction = supportFragmentManager.beginTransaction()
|
||||||
|
transaction.replace(R.id.feedbackFragment, FeedbackFragment())
|
||||||
|
transaction.commitAllowingStateLoss()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||||
|
if (item.itemId == android.R.id.home)
|
||||||
|
finish()
|
||||||
|
return super.onOptionsItemSelected(item)
|
||||||
|
}
|
||||||
|
}
|
@ -12,6 +12,7 @@ import android.view.View
|
|||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.widget.PopupMenu
|
import android.widget.PopupMenu
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import coil.Coil
|
import coil.Coil
|
||||||
import coil.api.load
|
import coil.api.load
|
||||||
@ -22,15 +23,26 @@ import kotlinx.coroutines.*
|
|||||||
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.*
|
import pl.szczodrzynski.edziennik.App
|
||||||
|
import pl.szczodrzynski.edziennik.R
|
||||||
|
import pl.szczodrzynski.edziennik.crc16
|
||||||
import pl.szczodrzynski.edziennik.data.api.events.FeedbackMessageEvent
|
import pl.szczodrzynski.edziennik.data.api.events.FeedbackMessageEvent
|
||||||
import pl.szczodrzynski.edziennik.data.api.szkolny.SzkolnyApi
|
import pl.szczodrzynski.edziennik.data.api.szkolny.SzkolnyApi
|
||||||
import pl.szczodrzynski.edziennik.data.db.entity.FeedbackMessage
|
import pl.szczodrzynski.edziennik.data.db.entity.FeedbackMessage
|
||||||
import pl.szczodrzynski.edziennik.databinding.FragmentFeedbackBinding
|
import pl.szczodrzynski.edziennik.databinding.FragmentFeedbackBinding
|
||||||
import pl.szczodrzynski.edziennik.utils.Themes
|
import pl.szczodrzynski.edziennik.onClick
|
||||||
import pl.szczodrzynski.edziennik.utils.Utils
|
import pl.szczodrzynski.edziennik.utils.Utils
|
||||||
import pl.szczodrzynski.edziennik.utils.Utils.openUrl
|
import pl.szczodrzynski.edziennik.utils.Utils.openUrl
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
import kotlin.collections.List
|
||||||
|
import kotlin.collections.any
|
||||||
|
import kotlin.collections.filter
|
||||||
|
import kotlin.collections.firstOrNull
|
||||||
|
import kotlin.collections.forEach
|
||||||
|
import kotlin.collections.forEachIndexed
|
||||||
|
import kotlin.collections.isNotEmpty
|
||||||
|
import kotlin.collections.mutableMapOf
|
||||||
|
import kotlin.collections.set
|
||||||
import kotlin.coroutines.CoroutineContext
|
import kotlin.coroutines.CoroutineContext
|
||||||
|
|
||||||
class FeedbackFragment : Fragment(), CoroutineScope {
|
class FeedbackFragment : Fragment(), CoroutineScope {
|
||||||
@ -39,7 +51,7 @@ class FeedbackFragment : Fragment(), CoroutineScope {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private lateinit var app: App
|
private lateinit var app: App
|
||||||
private lateinit var activity: MainActivity
|
private lateinit var activity: AppCompatActivity
|
||||||
private lateinit var b: FragmentFeedbackBinding
|
private lateinit var b: FragmentFeedbackBinding
|
||||||
|
|
||||||
private val job: Job = Job()
|
private val job: Job = Job()
|
||||||
@ -54,11 +66,10 @@ class FeedbackFragment : Fragment(), CoroutineScope {
|
|||||||
private var receiver: BroadcastReceiver? = null
|
private var receiver: BroadcastReceiver? = null
|
||||||
|
|
||||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
||||||
activity = (getActivity() as MainActivity?) ?: return null
|
activity = (getActivity() as AppCompatActivity?) ?: return null
|
||||||
if (context == null)
|
if (context == null)
|
||||||
return null
|
return null
|
||||||
app = activity.application as App
|
app = activity.application as App
|
||||||
context!!.theme.applyStyle(Themes.appTheme, true)
|
|
||||||
// activity, context and profile is valid
|
// activity, context and profile is valid
|
||||||
b = FragmentFeedbackBinding.inflate(inflater)
|
b = FragmentFeedbackBinding.inflate(inflater)
|
||||||
// prevent doubled received messages on enter
|
// prevent doubled received messages on enter
|
||||||
@ -239,7 +250,7 @@ class FeedbackFragment : Fragment(), CoroutineScope {
|
|||||||
}
|
}
|
||||||
|
|
||||||
launch {
|
launch {
|
||||||
val message = api.runCatching(activity.errorSnackbar) {
|
val message = api.runCatching(activity) {
|
||||||
val message = api.sendFeedbackMessage(
|
val message = api.sendFeedbackMessage(
|
||||||
senderName = App.profile.accountName ?: App.profile.studentNameLong,
|
senderName = App.profile.accountName ?: App.profile.studentNameLong,
|
||||||
targetDeviceId = if (isDev) currentDeviceId else null,
|
targetDeviceId = if (isDev) currentDeviceId else null,
|
||||||
|
@ -77,9 +77,9 @@ class GradeView : AppCompatTextView {
|
|||||||
TYPE_SEMESTER2_PROPOSED,
|
TYPE_SEMESTER2_PROPOSED,
|
||||||
TYPE_YEAR_PROPOSED -> android.R.attr.textColorPrimary.resolveAttr(context)
|
TYPE_YEAR_PROPOSED -> android.R.attr.textColorPrimary.resolveAttr(context)
|
||||||
else -> if (ColorUtils.calculateLuminance(gradeColor) > 0.3)
|
else -> if (ColorUtils.calculateLuminance(gradeColor) > 0.3)
|
||||||
0x99000000.toInt()
|
0xaa000000.toInt()
|
||||||
else
|
else
|
||||||
0x99ffffff.toInt()
|
0xccffffff.toInt()
|
||||||
})
|
})
|
||||||
|
|
||||||
//typeface = Typeface.create("sans-serif-light", Typeface.NORMAL)
|
//typeface = Typeface.create("sans-serif-light", Typeface.NORMAL)
|
||||||
|
@ -13,6 +13,8 @@ import android.widget.Toast
|
|||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import androidx.lifecycle.Observer
|
import androidx.lifecycle.Observer
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import androidx.recyclerview.widget.RecyclerView.SCROLL_STATE_IDLE
|
||||||
import com.mikepenz.iconics.typeface.library.community.material.CommunityMaterial
|
import com.mikepenz.iconics.typeface.library.community.material.CommunityMaterial
|
||||||
import com.mikepenz.iconics.typeface.library.community.material.CommunityMaterial.Icon2
|
import com.mikepenz.iconics.typeface.library.community.material.CommunityMaterial.Icon2
|
||||||
import kotlinx.coroutines.*
|
import kotlinx.coroutines.*
|
||||||
@ -53,6 +55,7 @@ class GradesFragment : Fragment(), CoroutineScope {
|
|||||||
GradesAdapter(activity)
|
GradesAdapter(activity)
|
||||||
}
|
}
|
||||||
private val manager by lazy { app.gradesManager }
|
private val manager by lazy { app.gradesManager }
|
||||||
|
private val dontCountEnabled by lazy { manager.dontCountEnabled }
|
||||||
private val dontCountGrades by lazy { manager.dontCountGrades }
|
private val dontCountGrades by lazy { manager.dontCountGrades }
|
||||||
private var expandSubjectId = 0L
|
private var expandSubjectId = 0L
|
||||||
|
|
||||||
@ -80,6 +83,16 @@ class GradesFragment : Fragment(), CoroutineScope {
|
|||||||
setHasFixedSize(true)
|
setHasFixedSize(true)
|
||||||
layoutManager = LinearLayoutManager(context)
|
layoutManager = LinearLayoutManager(context)
|
||||||
//addItemDecoration(SimpleDividerItemDecoration(context))
|
//addItemDecoration(SimpleDividerItemDecoration(context))
|
||||||
|
addOnScrollListener(object : RecyclerView.OnScrollListener() {
|
||||||
|
override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
|
||||||
|
if (recyclerView.canScrollVertically(-1)) {
|
||||||
|
b.refreshLayout.isEnabled = false
|
||||||
|
}
|
||||||
|
if (!recyclerView.canScrollVertically(-1) && newState == SCROLL_STATE_IDLE) {
|
||||||
|
b.refreshLayout.isEnabled = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -299,7 +312,7 @@ class GradesFragment : Fragment(), CoroutineScope {
|
|||||||
|
|
||||||
private fun countGrade(grade: Grade, averages: GradesAverages) {
|
private fun countGrade(grade: Grade, averages: GradesAverages) {
|
||||||
val value = manager.getGradeValue(grade)
|
val value = manager.getGradeValue(grade)
|
||||||
val weight = manager.getGradeWeight(dontCountGrades, grade)
|
val weight = manager.getGradeWeight(dontCountEnabled, dontCountGrades, grade)
|
||||||
when (grade.type) {
|
when (grade.type) {
|
||||||
Grade.TYPE_NORMAL -> {
|
Grade.TYPE_NORMAL -> {
|
||||||
if (grade.value > 0f) {
|
if (grade.value > 0f) {
|
||||||
|
@ -3,7 +3,6 @@ package pl.szczodrzynski.edziennik.ui.modules.grades.editor
|
|||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.graphics.PorterDuff
|
import android.graphics.PorterDuff
|
||||||
import android.graphics.PorterDuffColorFilter
|
import android.graphics.PorterDuffColorFilter
|
||||||
import android.graphics.Typeface
|
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
@ -49,8 +48,7 @@ class GradesEditorAdapter(
|
|||||||
|
|
||||||
holder.gradesListName.text = editorGrade.name
|
holder.gradesListName.text = editorGrade.name
|
||||||
holder.gradesListName.isSelected = true
|
holder.gradesListName.isSelected = true
|
||||||
holder.gradesListName.setTypeface(null, Typeface.BOLD)
|
holder.gradesListName.setTextColor(if (ColorUtils.calculateLuminance(gradeColor) > 0.25) 0xaa000000.toInt() else 0xccffffff.toInt())
|
||||||
holder.gradesListName.setTextColor(if (ColorUtils.calculateLuminance(gradeColor) > 0.25) -0x1000000 else -0x1)
|
|
||||||
holder.gradesListName.background.colorFilter = PorterDuffColorFilter(gradeColor, PorterDuff.Mode.MULTIPLY)
|
holder.gradesListName.background.colorFilter = PorterDuffColorFilter(gradeColor, PorterDuff.Mode.MULTIPLY)
|
||||||
holder.gradesListCategory.text = editorGrade.category
|
holder.gradesListCategory.text = editorGrade.category
|
||||||
if (editorGrade.weight < 0) {
|
if (editorGrade.weight < 0) {
|
||||||
|
@ -16,7 +16,6 @@ import pl.szczodrzynski.edziennik.*
|
|||||||
import pl.szczodrzynski.edziennik.data.db.entity.Grade
|
import pl.szczodrzynski.edziennik.data.db.entity.Grade
|
||||||
import pl.szczodrzynski.edziennik.databinding.FragmentGradesEditorBinding
|
import pl.szczodrzynski.edziennik.databinding.FragmentGradesEditorBinding
|
||||||
import pl.szczodrzynski.edziennik.utils.Colors
|
import pl.szczodrzynski.edziennik.utils.Colors
|
||||||
import pl.szczodrzynski.edziennik.utils.Themes
|
|
||||||
import pl.szczodrzynski.edziennik.utils.managers.GradesManager.Companion.YEAR_1_AVG_2_AVG
|
import pl.szczodrzynski.edziennik.utils.managers.GradesManager.Companion.YEAR_1_AVG_2_AVG
|
||||||
import pl.szczodrzynski.edziennik.utils.managers.GradesManager.Companion.YEAR_1_AVG_2_SEM
|
import pl.szczodrzynski.edziennik.utils.managers.GradesManager.Companion.YEAR_1_AVG_2_SEM
|
||||||
import pl.szczodrzynski.edziennik.utils.managers.GradesManager.Companion.YEAR_1_SEM_2_AVG
|
import pl.szczodrzynski.edziennik.utils.managers.GradesManager.Companion.YEAR_1_SEM_2_AVG
|
||||||
@ -60,16 +59,13 @@ class GradesEditorFragment : Fragment() {
|
|||||||
if (context == null)
|
if (context == null)
|
||||||
return null
|
return null
|
||||||
app = activity.application as App
|
app = activity.application as App
|
||||||
context!!.theme.applyStyle(Themes.appTheme, true)
|
|
||||||
if (app.profile == null)
|
|
||||||
return inflater.inflate(R.layout.fragment_loading, container, false)
|
|
||||||
// activity, context and profile is valid
|
// activity, context and profile is valid
|
||||||
b = FragmentGradesEditorBinding.inflate(inflater)
|
b = FragmentGradesEditorBinding.inflate(inflater)
|
||||||
return b.root
|
return b.root
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
if (app.profile == null || !isAdded)
|
if (!isAdded)
|
||||||
return
|
return
|
||||||
|
|
||||||
subjectId = arguments.getLong("subjectId", -1)
|
subjectId = arguments.getLong("subjectId", -1)
|
||||||
@ -110,7 +106,7 @@ class GradesEditorFragment : Fragment() {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
var weight = editorGrade.weight
|
var weight = editorGrade.weight
|
||||||
if (!config.countZeroToAvg && editorGrade.name == "0") {
|
if (config.dontCountEnabled && config.dontCountGrades.contains(editorGrade.name.toLowerCase().trim())) {
|
||||||
weight = 0f
|
weight = 0f
|
||||||
}
|
}
|
||||||
val value = editorGrade.value * weight
|
val value = editorGrade.value * weight
|
||||||
@ -175,7 +171,7 @@ class GradesEditorFragment : Fragment() {
|
|||||||
averageSemester = 0f
|
averageSemester = 0f
|
||||||
for (editorGrade in editorGrades) {
|
for (editorGrade in editorGrades) {
|
||||||
var weight = editorGrade.weight
|
var weight = editorGrade.weight
|
||||||
if (!config.countZeroToAvg && editorGrade.name == "0") {
|
if (config.dontCountEnabled && config.dontCountGrades.contains(editorGrade.name.toLowerCase().trim())) {
|
||||||
weight = 0f
|
weight = 0f
|
||||||
}
|
}
|
||||||
val value = editorGrade.value * weight
|
val value = editorGrade.value * weight
|
||||||
@ -218,7 +214,7 @@ class GradesEditorFragment : Fragment() {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
var weight = grade.weight
|
var weight = grade.weight
|
||||||
if (!config.countZeroToAvg && grade.name == "0") {
|
if (config.dontCountEnabled && config.dontCountGrades.contains(grade.name.toLowerCase().trim())) {
|
||||||
weight = 0f
|
weight = 0f
|
||||||
}
|
}
|
||||||
val value = grade.value * weight
|
val value = grade.value * weight
|
||||||
|
@ -102,7 +102,7 @@ class StatsViewHolder(
|
|||||||
.show()
|
.show()
|
||||||
}
|
}
|
||||||
|
|
||||||
b.customValueDivider.isVisible = manager.plusValue != null || manager.minusValue != null
|
b.customValueDivider.isVisible = manager.dontCountEnabled || manager.plusValue != null || manager.minusValue != null
|
||||||
b.customValueLayout.isVisible = b.customValueDivider.isVisible
|
b.customValueLayout.isVisible = b.customValueDivider.isVisible
|
||||||
b.customValueButton.onClick {
|
b.customValueButton.onClick {
|
||||||
GradesConfigDialog(activity, reloadOnDismiss = true)
|
GradesConfigDialog(activity, reloadOnDismiss = true)
|
||||||
|
@ -16,11 +16,8 @@ import androidx.appcompat.view.ContextThemeWrapper
|
|||||||
import androidx.core.view.get
|
import androidx.core.view.get
|
||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import pl.szczodrzynski.edziennik.App
|
import pl.szczodrzynski.edziennik.*
|
||||||
import pl.szczodrzynski.edziennik.R
|
|
||||||
import pl.szczodrzynski.edziennik.databinding.GradesItemSubjectBinding
|
import pl.szczodrzynski.edziennik.databinding.GradesItemSubjectBinding
|
||||||
import pl.szczodrzynski.edziennik.dp
|
|
||||||
import pl.szczodrzynski.edziennik.setText
|
|
||||||
import pl.szczodrzynski.edziennik.ui.modules.grades.GradeView
|
import pl.szczodrzynski.edziennik.ui.modules.grades.GradeView
|
||||||
import pl.szczodrzynski.edziennik.ui.modules.grades.GradesAdapter
|
import pl.szczodrzynski.edziennik.ui.modules.grades.GradesAdapter
|
||||||
import pl.szczodrzynski.edziennik.ui.modules.grades.GradesAdapter.Companion.STATE_CLOSED
|
import pl.szczodrzynski.edziennik.ui.modules.grades.GradesAdapter.Companion.STATE_CLOSED
|
||||||
@ -38,7 +35,7 @@ class SubjectViewHolder(
|
|||||||
|
|
||||||
override fun onBind(activity: AppCompatActivity, app: App, item: GradesSubject, position: Int, adapter: GradesAdapter) {
|
override fun onBind(activity: AppCompatActivity, app: App, item: GradesSubject, position: Int, adapter: GradesAdapter) {
|
||||||
val manager = app.gradesManager
|
val manager = app.gradesManager
|
||||||
val contextWrapper = ContextThemeWrapper(activity, Themes.themeInt)
|
val contextWrapper = ContextThemeWrapper(activity, Themes.appTheme)
|
||||||
|
|
||||||
b.subjectName.text = item.subjectName
|
b.subjectName.text = item.subjectName
|
||||||
b.dropdownIcon.rotation = when (item.state) {
|
b.dropdownIcon.rotation = when (item.state) {
|
||||||
@ -62,6 +59,7 @@ class SubjectViewHolder(
|
|||||||
|
|
||||||
if (firstSemester.number != item.semester) {
|
if (firstSemester.number != item.semester) {
|
||||||
b.gradesContainer.addView(TextView(contextWrapper).apply {
|
b.gradesContainer.addView(TextView(contextWrapper).apply {
|
||||||
|
setTextColor(android.R.attr.textColorSecondary.resolveAttr(context))
|
||||||
setText(R.string.grades_preview_other_semester, firstSemester.number)
|
setText(R.string.grades_preview_other_semester, firstSemester.number)
|
||||||
setPadding(0, 0, 5.dp, 0)
|
setPadding(0, 0, 5.dp, 0)
|
||||||
maxLines = 1
|
maxLines = 1
|
||||||
@ -88,6 +86,7 @@ class SubjectViewHolder(
|
|||||||
}
|
}
|
||||||
|
|
||||||
b.previewContainer.addView(TextView(contextWrapper).apply {
|
b.previewContainer.addView(TextView(contextWrapper).apply {
|
||||||
|
setTextColor(android.R.attr.textColorSecondary.resolveAttr(context))
|
||||||
text = manager.getAverageString(app, firstSemester.averages, nameSemester = true, showSemester = firstSemester.number)
|
text = manager.getAverageString(app, firstSemester.averages, nameSemester = true, showSemester = firstSemester.number)
|
||||||
//gravity = Gravity.END
|
//gravity = Gravity.END
|
||||||
layoutParams = LinearLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT).apply {
|
layoutParams = LinearLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT).apply {
|
||||||
|
@ -5,16 +5,18 @@
|
|||||||
package pl.szczodrzynski.edziennik.ui.modules.home
|
package pl.szczodrzynski.edziennik.ui.modules.home
|
||||||
|
|
||||||
import androidx.recyclerview.widget.ItemTouchHelper
|
import androidx.recyclerview.widget.ItemTouchHelper
|
||||||
|
import androidx.recyclerview.widget.ItemTouchHelper.*
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import com.google.android.material.card.MaterialCardView
|
import com.google.android.material.card.MaterialCardView
|
||||||
|
import pl.szczodrzynski.edziennik.ui.modules.home.HomeFragment.Companion.removeCard
|
||||||
import pl.szczodrzynski.edziennik.ui.modules.home.HomeFragment.Companion.swapCards
|
import pl.szczodrzynski.edziennik.ui.modules.home.HomeFragment.Companion.swapCards
|
||||||
import pl.szczodrzynski.edziennik.utils.SwipeRefreshLayoutNoIndicator
|
import pl.szczodrzynski.edziennik.utils.SwipeRefreshLayoutNoIndicator
|
||||||
|
|
||||||
class CardItemTouchHelperCallback(private val cardAdapter: HomeCardAdapter, private val refreshLayout: SwipeRefreshLayoutNoIndicator?) : ItemTouchHelper.Callback() {
|
class CardItemTouchHelperCallback(private val cardAdapter: HomeCardAdapter, private val refreshLayout: SwipeRefreshLayoutNoIndicator?) : ItemTouchHelper.Callback() {
|
||||||
companion object {
|
companion object {
|
||||||
private const val TAG = "CardItemTouchHelperCallback"
|
private const val TAG = "CardItemTouchHelperCallback"
|
||||||
private const val DRAG_FLAGS = ItemTouchHelper.UP or ItemTouchHelper.DOWN
|
private const val DRAG_FLAGS = UP or DOWN
|
||||||
private const val SWIPE_FLAGS = 0
|
private const val SWIPE_FLAGS = LEFT
|
||||||
}
|
}
|
||||||
|
|
||||||
private var dragCardView: MaterialCardView? = null
|
private var dragCardView: MaterialCardView? = null
|
||||||
@ -27,20 +29,24 @@ class CardItemTouchHelperCallback(private val cardAdapter: HomeCardAdapter, priv
|
|||||||
val fromPosition = viewHolder.adapterPosition
|
val fromPosition = viewHolder.adapterPosition
|
||||||
val toPosition = target.adapterPosition
|
val toPosition = target.adapterPosition
|
||||||
|
|
||||||
swapCards(fromPosition, toPosition, cardAdapter)
|
return swapCards(fromPosition, toPosition, cardAdapter)
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) = Unit
|
override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {
|
||||||
|
removeCard(viewHolder.adapterPosition)
|
||||||
|
cardAdapter.items.removeAt(viewHolder.adapterPosition)
|
||||||
|
cardAdapter.notifyItemRemoved(viewHolder.adapterPosition)
|
||||||
|
}
|
||||||
|
|
||||||
override fun onSelectedChanged(viewHolder: RecyclerView.ViewHolder?, actionState: Int) {
|
override fun onSelectedChanged(viewHolder: RecyclerView.ViewHolder?, actionState: Int) {
|
||||||
super.onSelectedChanged(viewHolder, actionState)
|
super.onSelectedChanged(viewHolder, actionState)
|
||||||
|
|
||||||
if (actionState == ItemTouchHelper.ACTION_STATE_DRAG && viewHolder != null) {
|
if (viewHolder != null && (actionState == ACTION_STATE_DRAG || actionState == ACTION_STATE_SWIPE)) {
|
||||||
dragCardView = viewHolder.itemView as MaterialCardView
|
dragCardView = viewHolder.itemView as MaterialCardView
|
||||||
dragCardView?.isDragged = true
|
dragCardView?.isDragged = true
|
||||||
refreshLayout?.isEnabled = false
|
refreshLayout?.isEnabled = false
|
||||||
} else if (actionState == ItemTouchHelper.ACTION_STATE_IDLE && dragCardView != null) {
|
}
|
||||||
|
else if (actionState == ACTION_STATE_IDLE && dragCardView != null) {
|
||||||
refreshLayout?.isEnabled = true
|
refreshLayout?.isEnabled = true
|
||||||
dragCardView?.isDragged = false
|
dragCardView?.isDragged = false
|
||||||
dragCardView = null
|
dragCardView = null
|
||||||
|
@ -0,0 +1,80 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) Kuba Szczodrzyński 2020-3-11.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package pl.szczodrzynski.edziennik.ui.modules.home
|
||||||
|
|
||||||
|
import androidx.appcompat.app.AlertDialog
|
||||||
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||||
|
import pl.szczodrzynski.edziennik.App
|
||||||
|
import pl.szczodrzynski.edziennik.MainActivity
|
||||||
|
import pl.szczodrzynski.edziennik.R
|
||||||
|
import pl.szczodrzynski.edziennik.ui.modules.home.HomeCard.Companion.CARD_EVENTS
|
||||||
|
import pl.szczodrzynski.edziennik.ui.modules.home.HomeCard.Companion.CARD_GRADES
|
||||||
|
import pl.szczodrzynski.edziennik.ui.modules.home.HomeCard.Companion.CARD_LUCKY_NUMBER
|
||||||
|
import pl.szczodrzynski.edziennik.ui.modules.home.HomeCard.Companion.CARD_TIMETABLE
|
||||||
|
import kotlin.collections.set
|
||||||
|
|
||||||
|
class HomeConfigDialog(
|
||||||
|
val activity: AppCompatActivity,
|
||||||
|
private val reloadOnDismiss: Boolean = true,
|
||||||
|
val onShowListener: ((tag: String) -> Unit)? = null,
|
||||||
|
val onDismissListener: ((tag: String) -> Unit)? = null
|
||||||
|
) {
|
||||||
|
companion object {
|
||||||
|
const val TAG = "HomeConfigDialog"
|
||||||
|
}
|
||||||
|
|
||||||
|
private val app by lazy { activity.application as App }
|
||||||
|
private val profileConfig by lazy { app.config.getFor(app.profileId).ui }
|
||||||
|
|
||||||
|
private lateinit var dialog: AlertDialog
|
||||||
|
|
||||||
|
init { run {
|
||||||
|
if (activity.isFinishing)
|
||||||
|
return@run
|
||||||
|
onShowListener?.invoke(TAG)
|
||||||
|
|
||||||
|
val ids = listOf(
|
||||||
|
CARD_LUCKY_NUMBER,
|
||||||
|
CARD_TIMETABLE,
|
||||||
|
CARD_GRADES,
|
||||||
|
CARD_EVENTS
|
||||||
|
)
|
||||||
|
val items = listOf(
|
||||||
|
app.getString(R.string.card_type_lucky_number),
|
||||||
|
app.getString(R.string.card_type_timetable),
|
||||||
|
app.getString(R.string.card_type_grades),
|
||||||
|
app.getString(R.string.card_type_events)
|
||||||
|
)
|
||||||
|
val checkedItems = ids.map { it to false }.toMap().toMutableMap()
|
||||||
|
|
||||||
|
val profileId = App.profileId
|
||||||
|
val homeCards = profileConfig.homeCards
|
||||||
|
.filter { it.profileId == profileId }
|
||||||
|
.toMutableList()
|
||||||
|
|
||||||
|
homeCards.forEach {
|
||||||
|
checkedItems[it.cardId] = true
|
||||||
|
}
|
||||||
|
|
||||||
|
dialog = MaterialAlertDialogBuilder(activity)
|
||||||
|
.setTitle(R.string.home_configure_add_remove)
|
||||||
|
.setMultiChoiceItems(items.toTypedArray(), checkedItems.values.toBooleanArray()) { _, which, isChecked ->
|
||||||
|
if (isChecked) {
|
||||||
|
homeCards += HomeCardModel(profileId, ids[which])
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
homeCards.removeAll { it.profileId == profileId && it.cardId == ids[which] }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.setPositiveButton(R.string.ok) { dialog, _ -> dialog.dismiss() }
|
||||||
|
.setOnDismissListener {
|
||||||
|
profileConfig.homeCards = homeCards
|
||||||
|
onDismissListener?.invoke(TAG)
|
||||||
|
if (reloadOnDismiss) (activity as? MainActivity)?.reloadTarget()
|
||||||
|
}
|
||||||
|
.show()
|
||||||
|
}}
|
||||||
|
}
|
@ -13,6 +13,7 @@ import android.widget.Toast
|
|||||||
import androidx.core.view.AccessibilityDelegateCompat
|
import androidx.core.view.AccessibilityDelegateCompat
|
||||||
import androidx.core.view.accessibility.AccessibilityNodeInfoCompat
|
import androidx.core.view.accessibility.AccessibilityNodeInfoCompat
|
||||||
import androidx.core.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat
|
import androidx.core.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat
|
||||||
|
import androidx.core.widget.NestedScrollView
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import androidx.recyclerview.widget.ItemTouchHelper
|
import androidx.recyclerview.widget.ItemTouchHelper
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
@ -25,8 +26,12 @@ import pl.szczodrzynski.edziennik.MainActivity
|
|||||||
import pl.szczodrzynski.edziennik.R
|
import pl.szczodrzynski.edziennik.R
|
||||||
import pl.szczodrzynski.edziennik.data.db.entity.LoginStore
|
import pl.szczodrzynski.edziennik.data.db.entity.LoginStore
|
||||||
import pl.szczodrzynski.edziennik.databinding.FragmentHomeBinding
|
import pl.szczodrzynski.edziennik.databinding.FragmentHomeBinding
|
||||||
|
import pl.szczodrzynski.edziennik.onClick
|
||||||
import pl.szczodrzynski.edziennik.ui.dialogs.home.StudentNumberDialog
|
import pl.szczodrzynski.edziennik.ui.dialogs.home.StudentNumberDialog
|
||||||
import pl.szczodrzynski.edziennik.ui.modules.home.cards.*
|
import pl.szczodrzynski.edziennik.ui.modules.home.cards.HomeEventsCard
|
||||||
|
import pl.szczodrzynski.edziennik.ui.modules.home.cards.HomeGradesCard
|
||||||
|
import pl.szczodrzynski.edziennik.ui.modules.home.cards.HomeLuckyNumberCard
|
||||||
|
import pl.szczodrzynski.edziennik.ui.modules.home.cards.HomeTimetableCard
|
||||||
import pl.szczodrzynski.edziennik.utils.Themes
|
import pl.szczodrzynski.edziennik.utils.Themes
|
||||||
import pl.szczodrzynski.navlib.bottomsheet.items.BottomSheetPrimaryItem
|
import pl.szczodrzynski.navlib.bottomsheet.items.BottomSheetPrimaryItem
|
||||||
import pl.szczodrzynski.navlib.bottomsheet.items.BottomSheetSeparatorItem
|
import pl.szczodrzynski.navlib.bottomsheet.items.BottomSheetSeparatorItem
|
||||||
@ -36,12 +41,12 @@ class HomeFragment : Fragment(), CoroutineScope {
|
|||||||
companion object {
|
companion object {
|
||||||
private const val TAG = "HomeFragment"
|
private const val TAG = "HomeFragment"
|
||||||
|
|
||||||
fun swapCards(fromPosition: Int, toPosition: Int, cardAdapter: HomeCardAdapter) {
|
fun swapCards(fromPosition: Int, toPosition: Int, cardAdapter: HomeCardAdapter): Boolean {
|
||||||
val fromCard = cardAdapter.items[fromPosition]
|
val fromCard = cardAdapter.items[fromPosition]
|
||||||
val toCard = cardAdapter.items[toPosition]
|
val toCard = cardAdapter.items[toPosition]
|
||||||
if (fromCard.id == 100 || toCard.id == 100) {
|
if (fromCard.id == 100 || toCard.id == 100) {
|
||||||
// debug card is not swappable
|
// debug card is not swappable
|
||||||
return
|
return false
|
||||||
}
|
}
|
||||||
cardAdapter.items[fromPosition] = cardAdapter.items[toPosition]
|
cardAdapter.items[fromPosition] = cardAdapter.items[toPosition]
|
||||||
cardAdapter.items[toPosition] = fromCard
|
cardAdapter.items[toPosition] = fromCard
|
||||||
@ -52,6 +57,15 @@ class HomeFragment : Fragment(), CoroutineScope {
|
|||||||
homeCards[fromPosition] = homeCards[toPosition]
|
homeCards[fromPosition] = homeCards[toPosition]
|
||||||
homeCards[toPosition] = fromPair
|
homeCards[toPosition] = fromPair
|
||||||
App.config.forProfile().ui.homeCards = homeCards
|
App.config.forProfile().ui.homeCards = homeCards
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
fun removeCard(position: Int) {
|
||||||
|
val homeCards = App.config.forProfile().ui.homeCards.toMutableList()
|
||||||
|
if (position >= homeCards.size)
|
||||||
|
return
|
||||||
|
homeCards.removeAt(position)
|
||||||
|
App.config.forProfile().ui.homeCards = homeCards
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -76,10 +90,17 @@ class HomeFragment : Fragment(), CoroutineScope {
|
|||||||
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
// TODO check if app, activity, b can be null
|
// TODO check if app, activity, b can be null
|
||||||
if (app.profile == null || !isAdded)
|
if (!isAdded)
|
||||||
return
|
return
|
||||||
|
|
||||||
activity.bottomSheet.prependItems(
|
activity.bottomSheet.prependItems(
|
||||||
|
BottomSheetPrimaryItem(true)
|
||||||
|
.withTitle(R.string.menu_add_remove_cards)
|
||||||
|
.withIcon(Icon.cmd_card_bulleted_settings_outline)
|
||||||
|
.withOnClickListener(OnClickListener {
|
||||||
|
activity.bottomSheet.close()
|
||||||
|
HomeConfigDialog(activity, reloadOnDismiss = true)
|
||||||
|
}),
|
||||||
BottomSheetPrimaryItem(true)
|
BottomSheetPrimaryItem(true)
|
||||||
.withTitle(R.string.menu_set_student_number)
|
.withTitle(R.string.menu_set_student_number)
|
||||||
.withIcon(SzkolnyFont.Icon.szf_clipboard_list_outline)
|
.withIcon(SzkolnyFont.Icon.szf_clipboard_list_outline)
|
||||||
@ -106,6 +127,13 @@ class HomeFragment : Fragment(), CoroutineScope {
|
|||||||
Toast.makeText(activity, R.string.main_menu_mark_as_read_success, Toast.LENGTH_SHORT).show()
|
Toast.makeText(activity, R.string.main_menu_mark_as_read_success, Toast.LENGTH_SHORT).show()
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
b.configureCards.onClick {
|
||||||
|
HomeConfigDialog(activity, reloadOnDismiss = true)
|
||||||
|
}
|
||||||
|
|
||||||
|
b.scrollView.setOnScrollChangeListener { _: NestedScrollView?, _: Int, scrollY: Int, _: Int, _: Int ->
|
||||||
|
b.refreshLayout.isEnabled = scrollY == 0
|
||||||
|
}
|
||||||
|
|
||||||
val showUnified = false
|
val showUnified = false
|
||||||
|
|
||||||
@ -130,8 +158,8 @@ class HomeFragment : Fragment(), CoroutineScope {
|
|||||||
else -> null
|
else -> null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (App.devMode)
|
//if (App.devMode)
|
||||||
items += HomeDebugCard(100, app, activity, this, app.profile)
|
// items += HomeDebugCard(100, app, activity, this, app.profile)
|
||||||
|
|
||||||
val adapter = HomeCardAdapter(items)
|
val adapter = HomeCardAdapter(items)
|
||||||
val itemTouchHelper = ItemTouchHelper(CardItemTouchHelperCallback(adapter, b.refreshLayout))
|
val itemTouchHelper = ItemTouchHelper(CardItemTouchHelperCallback(adapter, b.refreshLayout))
|
||||||
|
@ -73,7 +73,7 @@ class HomeDebugCard(
|
|||||||
}
|
}
|
||||||
|
|
||||||
b.syncReceivers.onClick {
|
b.syncReceivers.onClick {
|
||||||
EdziennikTask.recipientListGet(App.profileId).enqueue(activity)
|
EdziennikTask.recipientListGet(profile.id).enqueue(activity)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -77,7 +77,7 @@ class HomeEventsCard(
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
app.db.eventDao().getAllNearest(App.profileId, Date.getToday(), 4).observe(activity, Observer { events ->
|
app.db.eventDao().getAllNearest(profile.id, Date.getToday(), 4).observe(activity, Observer { events ->
|
||||||
adapter.items = events
|
adapter.items = events
|
||||||
if (b.eventsView.adapter == null) {
|
if (b.eventsView.adapter == null) {
|
||||||
b.eventsView.adapter = adapter
|
b.eventsView.adapter = adapter
|
||||||
|
@ -60,7 +60,7 @@ class HomeLuckyNumberCard(
|
|||||||
R.string.home_lucky_number_details
|
R.string.home_lucky_number_details
|
||||||
b.subText.setText(subTextRes, profile.name ?: "", profile.studentNumber)
|
b.subText.setText(subTextRes, profile.name ?: "", profile.studentNumber)
|
||||||
|
|
||||||
app.db.luckyNumberDao().getNearestFuture(App.profileId, todayValue).observe(fragment, Observer { luckyNumber ->
|
app.db.luckyNumberDao().getNearestFuture(profile.id, todayValue).observe(fragment, Observer { luckyNumber ->
|
||||||
val isYours = luckyNumber?.number == profile.studentNumber
|
val isYours = luckyNumber?.number == profile.studentNumber
|
||||||
val res: Pair<Int, Array<out Any>> = when {
|
val res: Pair<Int, Array<out Any>> = when {
|
||||||
luckyNumber == null -> R.string.home_lucky_number_no_info to emptyArray()
|
luckyNumber == null -> R.string.home_lucky_number_no_info to emptyArray()
|
||||||
|
@ -137,13 +137,13 @@ class HomeTimetableCard(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun update() { launch {
|
private fun update() { launch {
|
||||||
|
var checkedDays = 0
|
||||||
val deferred = async(Dispatchers.Default) {
|
val deferred = async(Dispatchers.Default) {
|
||||||
// get the current bell-synced time
|
// get the current bell-synced time
|
||||||
val now = syncedNow
|
val now = syncedNow
|
||||||
|
|
||||||
// search for lessons to display
|
// search for lessons to display
|
||||||
val timetableDate = Date.getToday()
|
val timetableDate = Date.getToday()
|
||||||
var checkedDays = 0
|
|
||||||
lessons = allLessons.filter {
|
lessons = allLessons.filter {
|
||||||
it.profileId == profile.id
|
it.profileId == profile.id
|
||||||
&& it.displayDate == timetableDate
|
&& it.displayDate == timetableDate
|
||||||
@ -163,11 +163,14 @@ class HomeTimetableCard(
|
|||||||
lessons = allLessons.filter {
|
lessons = allLessons.filter {
|
||||||
it.profileId == profile.id
|
it.profileId == profile.id
|
||||||
&& it.displayDate == timetableDate
|
&& it.displayDate == timetableDate
|
||||||
&& !(it.isCancelled && ignoreCancelled)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//if (lessons.isEmpty() && timetableDate.weekDay <= 5)
|
if (lessons.isEmpty())
|
||||||
// break
|
break
|
||||||
|
|
||||||
|
/*lessons = lessons.filterNot {
|
||||||
|
it.isCancelled && ignoreCancelled
|
||||||
|
}*/
|
||||||
|
|
||||||
checkedDays++
|
checkedDays++
|
||||||
}
|
}
|
||||||
@ -176,7 +179,7 @@ class HomeTimetableCard(
|
|||||||
|
|
||||||
timetableDate = deferred.await()
|
timetableDate = deferred.await()
|
||||||
|
|
||||||
if (lessons.isEmpty()) {
|
if (lessons.isEmpty() && checkedDays < 7) {
|
||||||
// timetable is not downloaded yet
|
// timetable is not downloaded yet
|
||||||
b.timetableLayout.visibility = View.GONE
|
b.timetableLayout.visibility = View.GONE
|
||||||
b.noTimetableLayout.visibility = View.VISIBLE
|
b.noTimetableLayout.visibility = View.VISIBLE
|
||||||
@ -189,7 +192,7 @@ class HomeTimetableCard(
|
|||||||
b.noTimetableSync.onClick {
|
b.noTimetableSync.onClick {
|
||||||
it.isEnabled = false
|
it.isEnabled = false
|
||||||
EdziennikTask.syncProfile(
|
EdziennikTask.syncProfile(
|
||||||
profileId = App.profileId,
|
profileId = profile.id,
|
||||||
viewIds = listOf(
|
viewIds = listOf(
|
||||||
MainActivity.DRAWER_ITEM_TIMETABLE to 0
|
MainActivity.DRAWER_ITEM_TIMETABLE to 0
|
||||||
),
|
),
|
||||||
@ -198,14 +201,18 @@ class HomeTimetableCard(
|
|||||||
)
|
)
|
||||||
).enqueue(activity)
|
).enqueue(activity)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
timetableDate = Date.getToday()
|
||||||
return@launch
|
return@launch
|
||||||
}
|
}
|
||||||
if (lessons.size == 1 && lessons[0].type == Lesson.TYPE_NO_LESSONS) {
|
if (lessons.none { !it.isCancelled } || lessons.size == 1 && lessons[0].type == Lesson.TYPE_NO_LESSONS) {
|
||||||
// in next 7 days only NO_LESSONS is found
|
// in next 7 days only NO_LESSONS is found
|
||||||
b.timetableLayout.visibility = View.GONE
|
b.timetableLayout.visibility = View.GONE
|
||||||
b.noTimetableLayout.visibility = View.GONE
|
b.noTimetableLayout.visibility = View.GONE
|
||||||
b.noLessonsLayout.visibility = View.VISIBLE
|
b.noLessonsLayout.visibility = View.VISIBLE
|
||||||
timetableDate = timetableDate.weekStart
|
timetableDate = timetableDate.weekStart
|
||||||
|
|
||||||
|
timetableDate = Date.getToday()
|
||||||
return@launch
|
return@launch
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -85,9 +85,7 @@ class HomeworkFragment : Fragment() {
|
|||||||
b.viewPager.clearOnPageChangeListeners()
|
b.viewPager.clearOnPageChangeListeners()
|
||||||
b.viewPager.addOnPageChangeListener(object : ViewPager.OnPageChangeListener {
|
b.viewPager.addOnPageChangeListener(object : ViewPager.OnPageChangeListener {
|
||||||
override fun onPageScrollStateChanged(state: Int) {
|
override fun onPageScrollStateChanged(state: Int) {
|
||||||
if (b.refreshLayout != null) {
|
b.refreshLayout.isEnabled = state == ViewPager.SCROLL_STATE_IDLE
|
||||||
b.refreshLayout.isEnabled = state == ViewPager.SCROLL_STATE_IDLE
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) {}
|
override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) {}
|
||||||
override fun onPageSelected(position: Int) {
|
override fun onPageSelected(position: Int) {
|
||||||
|
@ -9,12 +9,10 @@ import androidx.lifecycle.Observer
|
|||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
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.databinding.HomeworkListBinding
|
|
||||||
import pl.szczodrzynski.edziennik.data.db.entity.Event
|
import pl.szczodrzynski.edziennik.data.db.entity.Event
|
||||||
|
import pl.szczodrzynski.edziennik.databinding.HomeworkListBinding
|
||||||
import pl.szczodrzynski.edziennik.getInt
|
import pl.szczodrzynski.edziennik.getInt
|
||||||
import pl.szczodrzynski.edziennik.utils.models.Date
|
import pl.szczodrzynski.edziennik.utils.models.Date
|
||||||
import pl.szczodrzynski.edziennik.utils.Themes
|
|
||||||
|
|
||||||
class HomeworkListFragment : Fragment() {
|
class HomeworkListFragment : Fragment() {
|
||||||
|
|
||||||
@ -26,20 +24,15 @@ class HomeworkListFragment : Fragment() {
|
|||||||
|
|
||||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
||||||
activity = (getActivity() as MainActivity?) ?: return null
|
activity = (getActivity() as MainActivity?) ?: return null
|
||||||
if (context == null)
|
context ?: return null
|
||||||
return null
|
|
||||||
app = activity.application as App
|
app = activity.application as App
|
||||||
context!!.theme.applyStyle(Themes.appTheme, true)
|
|
||||||
if (app.profile == null)
|
|
||||||
return inflater.inflate(R.layout.fragment_loading, container, false)
|
|
||||||
// activity, context and profile is valid
|
|
||||||
b = HomeworkListBinding.inflate(inflater)
|
b = HomeworkListBinding.inflate(inflater)
|
||||||
return b.root
|
return b.root
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
// TODO check if app, activity, b can be null
|
// TODO check if app, activity, b can be null
|
||||||
if (app.profile == null || !isAdded)
|
if (!isAdded)
|
||||||
return
|
return
|
||||||
|
|
||||||
if (arguments != null) {
|
if (arguments != null) {
|
||||||
@ -47,21 +40,21 @@ class HomeworkListFragment : Fragment() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
val layoutManager = LinearLayoutManager(context)
|
val layoutManager = LinearLayoutManager(context)
|
||||||
layoutManager.reverseLayout = true
|
layoutManager.reverseLayout = homeworkDate == HomeworkDate.PAST
|
||||||
layoutManager.stackFromEnd = true
|
layoutManager.stackFromEnd = homeworkDate == HomeworkDate.PAST
|
||||||
|
|
||||||
b.homeworkView.setHasFixedSize(true)
|
b.homeworkView.setHasFixedSize(true)
|
||||||
b.homeworkView.layoutManager = layoutManager
|
b.homeworkView.layoutManager = layoutManager
|
||||||
|
|
||||||
val filter = when(homeworkDate) {
|
val filter = when(homeworkDate) {
|
||||||
HomeworkDate.CURRENT -> "eventDate > '" + Date.getToday().stringY_m_d + "'"
|
HomeworkDate.CURRENT -> "eventDate >= '" + Date.getToday().stringY_m_d + "'"
|
||||||
else -> "eventDate <= '" + Date.getToday().stringY_m_d + "'"
|
else -> "eventDate < '" + Date.getToday().stringY_m_d + "'"
|
||||||
}
|
}
|
||||||
|
|
||||||
app.db.eventDao()
|
app.db.eventDao()
|
||||||
.getAllByType(App.profileId, Event.TYPE_HOMEWORK, filter)
|
.getAllByType(App.profileId, Event.TYPE_HOMEWORK, filter)
|
||||||
.observe(this, Observer { homeworkList ->
|
.observe(this, Observer { homeworkList ->
|
||||||
if (app.profile == null || !isAdded) return@Observer
|
if (!isAdded) return@Observer
|
||||||
|
|
||||||
if (homeworkList != null && homeworkList.size > 0) {
|
if (homeworkList != null && homeworkList.size > 0) {
|
||||||
val adapter = HomeworkAdapter(context, homeworkList)
|
val adapter = HomeworkAdapter(context, homeworkList)
|
||||||
|
@ -4,19 +4,27 @@
|
|||||||
|
|
||||||
package pl.szczodrzynski.edziennik.ui.modules.login
|
package pl.szczodrzynski.edziennik.ui.modules.login
|
||||||
|
|
||||||
|
import android.graphics.Color
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
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 androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
|
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 kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
import pl.szczodrzynski.edziennik.*
|
import pl.szczodrzynski.edziennik.*
|
||||||
import pl.szczodrzynski.edziennik.data.api.ERROR_LOGIN_LIBRUS_API_INVALID_LOGIN
|
import pl.szczodrzynski.edziennik.data.api.ERROR_LOGIN_LIBRUS_API_INVALID_LOGIN
|
||||||
|
import pl.szczodrzynski.edziennik.data.api.ERROR_LOGIN_LIBRUS_API_INVALID_REQUEST
|
||||||
import pl.szczodrzynski.edziennik.data.api.LOGIN_MODE_LIBRUS_JST
|
import pl.szczodrzynski.edziennik.data.api.LOGIN_MODE_LIBRUS_JST
|
||||||
import pl.szczodrzynski.edziennik.data.api.LOGIN_TYPE_LIBRUS
|
import pl.szczodrzynski.edziennik.data.api.LOGIN_TYPE_LIBRUS
|
||||||
import pl.szczodrzynski.edziennik.databinding.FragmentLoginLibrusJstBinding
|
import pl.szczodrzynski.edziennik.databinding.FragmentLoginLibrusJstBinding
|
||||||
|
import pl.szczodrzynski.edziennik.ui.dialogs.QrScannerDialog
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import kotlin.coroutines.CoroutineContext
|
import kotlin.coroutines.CoroutineContext
|
||||||
|
|
||||||
@ -47,12 +55,26 @@ class LoginLibrusJstFragment : Fragment(), CoroutineScope {
|
|||||||
activity.lastError = null
|
activity.lastError = null
|
||||||
startCoroutineTimer(delayMillis = 100) {
|
startCoroutineTimer(delayMillis = 100) {
|
||||||
when (error.errorCode) {
|
when (error.errorCode) {
|
||||||
ERROR_LOGIN_LIBRUS_API_INVALID_LOGIN ->
|
ERROR_LOGIN_LIBRUS_API_INVALID_LOGIN,
|
||||||
|
ERROR_LOGIN_LIBRUS_API_INVALID_REQUEST ->
|
||||||
b.loginCodeLayout.error = getString(R.string.login_error_incorrect_code_or_pin)
|
b.loginCodeLayout.error = getString(R.string.login_error_incorrect_code_or_pin)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
b.loginQrScan.setImageDrawable(IconicsDrawable(activity)
|
||||||
|
.icon(CommunityMaterial.Icon2.cmd_qrcode_scan)
|
||||||
|
.colorInt(Color.BLACK)
|
||||||
|
.sizeDp(72))
|
||||||
|
b.loginQrScan.onClick {
|
||||||
|
QrScannerDialog(activity, { code ->
|
||||||
|
b.loginCode.setText(code)
|
||||||
|
if (b.loginPin.requestFocus()) {
|
||||||
|
activity.window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
b.helpButton.onClick { nav.navigate(R.id.loginLibrusHelpFragment, null, LoginActivity.navOptions) }
|
b.helpButton.onClick { nav.navigate(R.id.loginLibrusHelpFragment, null, LoginActivity.navOptions) }
|
||||||
b.backButton.onClick { nav.navigateUp() }
|
b.backButton.onClick { nav.navigateUp() }
|
||||||
|
|
||||||
|
@ -4,17 +4,25 @@
|
|||||||
|
|
||||||
package pl.szczodrzynski.edziennik.ui.modules.login
|
package pl.szczodrzynski.edziennik.ui.modules.login
|
||||||
|
|
||||||
|
import android.graphics.Color
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
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 androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
|
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 kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
import pl.szczodrzynski.edziennik.*
|
import pl.szczodrzynski.edziennik.*
|
||||||
import pl.szczodrzynski.edziennik.data.api.*
|
import pl.szczodrzynski.edziennik.data.api.*
|
||||||
import pl.szczodrzynski.edziennik.databinding.FragmentLoginVulcanBinding
|
import pl.szczodrzynski.edziennik.databinding.FragmentLoginVulcanBinding
|
||||||
|
import pl.szczodrzynski.edziennik.ui.dialogs.QrScannerDialog
|
||||||
|
import pl.szczodrzynski.edziennik.utils.Utils
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import kotlin.coroutines.CoroutineContext
|
import kotlin.coroutines.CoroutineContext
|
||||||
|
|
||||||
@ -57,6 +65,26 @@ class LoginVulcanFragment : Fragment(), CoroutineScope {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
b.loginQrScan.setImageDrawable(IconicsDrawable(activity)
|
||||||
|
.icon(CommunityMaterial.Icon2.cmd_qrcode_scan)
|
||||||
|
.colorInt(Color.BLACK)
|
||||||
|
.sizeDp(72))
|
||||||
|
b.loginQrScan.onClick {
|
||||||
|
QrScannerDialog(activity, { code ->
|
||||||
|
try {
|
||||||
|
val data = Utils.VulcanQrEncryptionUtils.decode(code)
|
||||||
|
"CERT#https?://.+?/([A-z]+)/mobile-api#([A-z0-9]+)#ENDCERT".toRegex().find(data)?.let {
|
||||||
|
b.loginToken.setText(it[2])
|
||||||
|
b.loginSymbol.setText(it[1])
|
||||||
|
if (b.loginPin.requestFocus()) {
|
||||||
|
activity.window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (_: Exception) {}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
b.helpButton.onClick { nav.navigate(R.id.loginVulcanHelpFragment, null, LoginActivity.navOptions) }
|
b.helpButton.onClick { nav.navigate(R.id.loginVulcanHelpFragment, null, LoginActivity.navOptions) }
|
||||||
b.backButton.onClick { nav.navigateUp() }
|
b.backButton.onClick { nav.navigateUp() }
|
||||||
|
|
||||||
|
@ -41,6 +41,7 @@ import pl.szczodrzynski.edziennik.data.db.entity.Message.TYPE_SENT
|
|||||||
import pl.szczodrzynski.edziennik.data.db.full.MessageFull
|
import pl.szczodrzynski.edziennik.data.db.full.MessageFull
|
||||||
import pl.szczodrzynski.edziennik.databinding.MessageFragmentBinding
|
import pl.szczodrzynski.edziennik.databinding.MessageFragmentBinding
|
||||||
import pl.szczodrzynski.edziennik.utils.Anim
|
import pl.szczodrzynski.edziennik.utils.Anim
|
||||||
|
import pl.szczodrzynski.edziennik.utils.BetterLink
|
||||||
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 pl.szczodrzynski.edziennik.utils.Utils.getStringFromFile
|
import pl.szczodrzynski.edziennik.utils.Utils.getStringFromFile
|
||||||
@ -198,7 +199,7 @@ class MessageFragment : Fragment(), CoroutineScope {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun showMessage() {
|
private fun showMessage() {
|
||||||
b.body.text = MessagesUtils.htmlToSpannable(message.body ?: "")
|
b.body.text = MessagesUtils.htmlToSpannable(activity, message.body ?: "")
|
||||||
b.date.text = getString(R.string.messages_date_time_format, Date.fromMillis(message.addedDate).formattedStringShort, Time.fromMillis(message.addedDate).stringHM)
|
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)
|
val messageInfo = MessagesUtils.getMessageInfo(app, message, 40, 20, 14, 10)
|
||||||
@ -251,6 +252,9 @@ class MessageFragment : Fragment(), CoroutineScope {
|
|||||||
|
|
||||||
showAttachments()
|
showAttachments()
|
||||||
|
|
||||||
|
BetterLink.attach(b.subject)
|
||||||
|
BetterLink.attach(b.body)
|
||||||
|
|
||||||
b.progress.visibility = View.GONE
|
b.progress.visibility = View.GONE
|
||||||
Anim.fadeIn(b.content, 200, null)
|
Anim.fadeIn(b.content, 200, null)
|
||||||
MessagesFragment.pageSelection = min(message.type, 1)
|
MessagesFragment.pageSelection = min(message.type, 1)
|
||||||
|
@ -41,7 +41,7 @@ class MessagesAdapter(private val app: App, private val onItemClickListener: OnI
|
|||||||
b.messageAttachmentImage.visibility = if (message.hasAttachments()) View.VISIBLE else View.GONE
|
b.messageAttachmentImage.visibility = if (message.hasAttachments()) View.VISIBLE else View.GONE
|
||||||
|
|
||||||
val text = message.body?.substring(0, message.body!!.length.coerceAtMost(200)) ?: ""
|
val text = message.body?.substring(0, message.body!!.length.coerceAtMost(200)) ?: ""
|
||||||
b.messageBody.text = MessagesUtils.htmlToSpannable(text)
|
b.messageBody.text = MessagesUtils.htmlToSpannable(b.root.context, text)
|
||||||
|
|
||||||
if (message.type == Message.TYPE_SENT || message.type == Message.TYPE_DRAFT || message.seen) {
|
if (message.type == Message.TYPE_SENT || message.type == Message.TYPE_DRAFT || message.seen) {
|
||||||
b.messageSender.setTextAppearance(b.messageSender.context, R.style.NavView_TextView_Small)
|
b.messageSender.setTextAppearance(b.messageSender.context, R.style.NavView_TextView_Small)
|
||||||
|
@ -380,7 +380,7 @@ class MessagesComposeFragment : Fragment(), CoroutineScope {
|
|||||||
span.replace(0, 0, "\n\n")
|
span.replace(0, 0, "\n\n")
|
||||||
subject = "Fwd: ${msg.subject}"
|
subject = "Fwd: ${msg.subject}"
|
||||||
}
|
}
|
||||||
body = MessagesUtils.htmlToSpannable(msg.body ?: "Nie udało się wczytać oryginalnej wiadomości.")//Html.fromHtml(msg.body?.replace("<br\\s?/?>".toRegex(), "\n") ?: "Nie udało się wczytać oryginalnej wiadomości.")
|
body = MessagesUtils.htmlToSpannable(activity,msg.body ?: "Nie udało się wczytać oryginalnej wiadomości.")//Html.fromHtml(msg.body?.replace("<br\\s?/?>".toRegex(), "\n") ?: "Nie udało się wczytać oryginalnej wiadomości.")
|
||||||
}
|
}
|
||||||
|
|
||||||
b.recipients.addTextWithChips(chipList)
|
b.recipients.addTextWithChips(chipList)
|
||||||
|
@ -1,8 +1,10 @@
|
|||||||
package pl.szczodrzynski.edziennik.ui.modules.messages
|
package pl.szczodrzynski.edziennik.ui.modules.messages
|
||||||
|
|
||||||
import android.graphics.*
|
import android.content.Context
|
||||||
import android.os.Build
|
import android.graphics.Bitmap
|
||||||
import android.text.Html
|
import android.graphics.Canvas
|
||||||
|
import android.graphics.Paint
|
||||||
|
import android.graphics.RectF
|
||||||
import android.text.Spanned
|
import android.text.Spanned
|
||||||
import androidx.core.graphics.ColorUtils
|
import androidx.core.graphics.ColorUtils
|
||||||
import pl.szczodrzynski.edziennik.App
|
import pl.szczodrzynski.edziennik.App
|
||||||
@ -12,8 +14,8 @@ import pl.szczodrzynski.edziennik.data.db.full.MessageFull
|
|||||||
import pl.szczodrzynski.edziennik.fixName
|
import pl.szczodrzynski.edziennik.fixName
|
||||||
import pl.szczodrzynski.edziennik.getNameInitials
|
import pl.szczodrzynski.edziennik.getNameInitials
|
||||||
import pl.szczodrzynski.edziennik.utils.Colors
|
import pl.szczodrzynski.edziennik.utils.Colors
|
||||||
import pl.szczodrzynski.edziennik.utils.Themes
|
|
||||||
import pl.szczodrzynski.edziennik.utils.Utils
|
import pl.szczodrzynski.edziennik.utils.Utils
|
||||||
|
import pl.szczodrzynski.edziennik.utils.html.BetterHtml
|
||||||
import kotlin.math.roundToInt
|
import kotlin.math.roundToInt
|
||||||
|
|
||||||
object MessagesUtils {
|
object MessagesUtils {
|
||||||
@ -180,32 +182,7 @@ object MessagesUtils {
|
|||||||
class MessageInfo(var profileImage: Bitmap?, var profileName: String?)
|
class MessageInfo(var profileImage: Bitmap?, var profileName: String?)
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun htmlToSpannable(html: String): Spanned {
|
fun htmlToSpannable(context: Context, html: String): Spanned {
|
||||||
val hexPattern = "(#[a-fA-F0-9]{6})"
|
return BetterHtml.fromHtml(context, html)
|
||||||
val colorRegex = "(?:color=\"$hexPattern\")|(?:style=\"color: ?${hexPattern})"
|
|
||||||
.toRegex(RegexOption.IGNORE_CASE)
|
|
||||||
|
|
||||||
var text = html
|
|
||||||
.replace("\\[META:[A-z0-9]+;[0-9-]+]".toRegex(), "")
|
|
||||||
.replace("background-color: ?$hexPattern;".toRegex(), "")
|
|
||||||
|
|
||||||
colorRegex.findAll(text).forEach { result ->
|
|
||||||
val group = result.groups.drop(1).firstOrNull { it != null } ?: return@forEach
|
|
||||||
|
|
||||||
val color = Color.parseColor(group.value)
|
|
||||||
val luminance = ColorUtils.calculateLuminance(color)
|
|
||||||
|
|
||||||
if (Themes.isDark && luminance <= 0.5) {
|
|
||||||
text = text.replaceRange(group.range, "#FFFFFF")
|
|
||||||
} else if (!Themes.isDark && luminance > 0.5) {
|
|
||||||
text = text.replaceRange(group.range, "#000000")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
|
||||||
Html.fromHtml(text, Html.FROM_HTML_MODE_COMPACT)
|
|
||||||
} else {
|
|
||||||
Html.fromHtml(text)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1104,7 +1104,7 @@ public class SettingsNewFragment extends MaterialAboutFragment {
|
|||||||
.icon(SzkolnyFont.Icon.szf_discord_outline)
|
.icon(SzkolnyFont.Icon.szf_discord_outline)
|
||||||
.color(IconicsColor.colorInt(primaryTextOnPrimaryBg))
|
.color(IconicsColor.colorInt(primaryTextOnPrimaryBg))
|
||||||
.size(IconicsSize.dp(iconSizeDp)))
|
.size(IconicsSize.dp(iconSizeDp)))
|
||||||
.setOnClickAction(ConvenienceBuilder.createWebsiteOnClickAction(activity, Uri.parse("https://discord.gg/n9e8pWr")))
|
.setOnClickAction(ConvenienceBuilder.createWebsiteOnClickAction(activity, Uri.parse("https://szkolny.eu/discord")))
|
||||||
.build());
|
.build());
|
||||||
|
|
||||||
items.add(new MaterialAboutActionItem.Builder()
|
items.add(new MaterialAboutActionItem.Builder()
|
||||||
|
@ -16,16 +16,17 @@ import android.view.ViewGroup
|
|||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import androidx.viewpager.widget.ViewPager
|
import androidx.viewpager.widget.ViewPager
|
||||||
import com.google.android.material.datepicker.MaterialDatePicker
|
|
||||||
import com.mikepenz.iconics.typeface.library.community.material.CommunityMaterial
|
import com.mikepenz.iconics.typeface.library.community.material.CommunityMaterial
|
||||||
import com.mikepenz.iconics.typeface.library.community.material.CommunityMaterial.Icon2
|
import com.mikepenz.iconics.typeface.library.community.material.CommunityMaterial.Icon2
|
||||||
import com.mikepenz.iconics.typeface.library.szkolny.font.SzkolnyFont
|
import com.mikepenz.iconics.typeface.library.szkolny.font.SzkolnyFont
|
||||||
|
import com.wdullaer.materialdatetimepicker.date.DatePickerDialog
|
||||||
import kotlinx.coroutines.*
|
import kotlinx.coroutines.*
|
||||||
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.db.entity.Metadata
|
import pl.szczodrzynski.edziennik.data.db.entity.Metadata
|
||||||
import pl.szczodrzynski.edziennik.databinding.FragmentTimetableV2Binding
|
import pl.szczodrzynski.edziennik.databinding.FragmentTimetableV2Binding
|
||||||
|
import pl.szczodrzynski.edziennik.resolveAttr
|
||||||
import pl.szczodrzynski.edziennik.ui.dialogs.event.EventManualDialog
|
import pl.szczodrzynski.edziennik.ui.dialogs.event.EventManualDialog
|
||||||
import pl.szczodrzynski.edziennik.ui.dialogs.timetable.GenerateBlockTimetableDialog
|
import pl.szczodrzynski.edziennik.ui.dialogs.timetable.GenerateBlockTimetableDialog
|
||||||
import pl.szczodrzynski.edziennik.utils.models.Date
|
import pl.szczodrzynski.edziennik.utils.models.Date
|
||||||
@ -169,16 +170,15 @@ class TimetableFragment : Fragment(), CoroutineScope {
|
|||||||
.withIcon(SzkolnyFont.Icon.szf_calendar_today_outline)
|
.withIcon(SzkolnyFont.Icon.szf_calendar_today_outline)
|
||||||
.withOnClickListener(View.OnClickListener {
|
.withOnClickListener(View.OnClickListener {
|
||||||
activity.bottomSheet.close()
|
activity.bottomSheet.close()
|
||||||
MaterialDatePicker.Builder
|
val date = Date.getToday()
|
||||||
.datePicker()
|
DatePickerDialog
|
||||||
.setSelection(Date.getToday().inMillis)
|
.newInstance({ _, year, monthOfYear, dayOfMonth ->
|
||||||
.build()
|
val dateSelected = Date(year, monthOfYear, dayOfMonth)
|
||||||
|
b.tabLayout.setCurrentItem(items.indexOfFirst { it == dateSelected }, true)
|
||||||
|
}, date.year, date.month, date.day)
|
||||||
.apply {
|
.apply {
|
||||||
addOnPositiveButtonClickListener { dateInMillis ->
|
accentColor = R.attr.colorPrimary.resolveAttr(this@TimetableFragment.activity)
|
||||||
val dateSelected = Date.fromMillis(dateInMillis)
|
show(this@TimetableFragment.activity.supportFragmentManager, "DatePickerDialog")
|
||||||
b.tabLayout.setCurrentItem(items.indexOfFirst { it == dateSelected }, true)
|
|
||||||
}
|
|
||||||
show(this@TimetableFragment.activity.supportFragmentManager, "MaterialDatePicker")
|
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
BottomSheetPrimaryItem(true)
|
BottomSheetPrimaryItem(true)
|
||||||
|
@ -10,13 +10,14 @@ import android.util.AttributeSet
|
|||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.lifecycle.Observer
|
import androidx.lifecycle.Observer
|
||||||
import com.google.android.material.datepicker.MaterialDatePicker
|
import com.wdullaer.materialdatetimepicker.date.DatePickerDialog
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import pl.szczodrzynski.edziennik.R
|
import pl.szczodrzynski.edziennik.R
|
||||||
import pl.szczodrzynski.edziennik.data.db.AppDb
|
import pl.szczodrzynski.edziennik.data.db.AppDb
|
||||||
import pl.szczodrzynski.edziennik.data.db.full.LessonFull
|
import pl.szczodrzynski.edziennik.data.db.full.LessonFull
|
||||||
import pl.szczodrzynski.edziennik.observeOnce
|
import pl.szczodrzynski.edziennik.observeOnce
|
||||||
|
import pl.szczodrzynski.edziennik.resolveAttr
|
||||||
import pl.szczodrzynski.edziennik.utils.TextInputDropDown
|
import pl.szczodrzynski.edziennik.utils.TextInputDropDown
|
||||||
import pl.szczodrzynski.edziennik.utils.models.Date
|
import pl.szczodrzynski.edziennik.utils.models.Date
|
||||||
import pl.szczodrzynski.edziennik.utils.models.Week
|
import pl.szczodrzynski.edziennik.utils.models.Week
|
||||||
@ -176,23 +177,18 @@ class DateDropdown : TextInputDropDown {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun pickerDialog() {
|
fun pickerDialog() {
|
||||||
MaterialDatePicker.Builder
|
val date = getSelected() as? Date ?: Date.getToday()
|
||||||
.datePicker()
|
|
||||||
.setSelection(
|
DatePickerDialog
|
||||||
if (selected?.tag is Date)
|
.newInstance({ _, year, monthOfYear, dayOfMonth ->
|
||||||
(selected?.tag as Date).inMillis
|
val dateSelected = Date(year, monthOfYear+1, dayOfMonth)
|
||||||
else
|
selectDate(dateSelected)
|
||||||
Date.getToday().inMillis
|
onDateSelected?.invoke(dateSelected, null)
|
||||||
)
|
}, date.year, date.month-1, date.day)
|
||||||
.build()
|
|
||||||
.apply {
|
.apply {
|
||||||
addOnPositiveButtonClickListener {
|
|
||||||
val dateSelected = Date.fromMillis(it)
|
|
||||||
selectDate(dateSelected)
|
|
||||||
onDateSelected?.invoke(dateSelected, null)
|
|
||||||
}
|
|
||||||
this@DateDropdown.activity ?: return@apply
|
this@DateDropdown.activity ?: return@apply
|
||||||
show(this@DateDropdown.activity!!.supportFragmentManager, "MaterialDatePicker")
|
accentColor = R.attr.colorPrimary.resolveAttr(this@DateDropdown.activity)
|
||||||
|
show(this@DateDropdown.activity!!.supportFragmentManager, "DatePickerDialog")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -8,6 +8,7 @@ import android.content.Context
|
|||||||
import android.content.ContextWrapper
|
import android.content.ContextWrapper
|
||||||
import android.util.AttributeSet
|
import android.util.AttributeSet
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
|
import com.wdullaer.materialdatetimepicker.time.TimePickerDialog
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import pl.szczodrzynski.edziennik.*
|
import pl.szczodrzynski.edziennik.*
|
||||||
@ -174,19 +175,19 @@ class TimeDropdown : TextInputDropDown {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun pickerDialog() {
|
fun pickerDialog() {
|
||||||
/*MaterialDatePicker.Builder
|
val time = (getSelected() as? Pair<*, *>)?.first as? Time ?: Time.getNow()
|
||||||
.datePicker()
|
|
||||||
.setSelection((selectedId?.let { Date.fromValue(it.toInt()) }
|
TimePickerDialog
|
||||||
?: Date.getToday()).inMillis)
|
.newInstance({ _, hourOfDay, minute, second ->
|
||||||
.build()
|
val timeSelected = Time(hourOfDay, minute, second)
|
||||||
|
selectTime(timeSelected)
|
||||||
|
onTimeSelected?.invoke(timeSelected, null, null)
|
||||||
|
}, time.hour, time.minute, 0, true)
|
||||||
.apply {
|
.apply {
|
||||||
addOnPositiveButtonClickListener {
|
this@TimeDropdown.activity ?: return@apply
|
||||||
val dateSelected = Date.fromMillis(it)
|
accentColor = R.attr.colorPrimary.resolveAttr(this@TimeDropdown.activity)
|
||||||
selectDate(dateSelected)
|
show(this@TimeDropdown.activity!!.supportFragmentManager, "TimePickerDialog")
|
||||||
}
|
}
|
||||||
this@DateDropdown.activity ?: return@apply
|
|
||||||
show(this@DateDropdown.activity!!.supportFragmentManager, "MaterialDatePicker")
|
|
||||||
}*/
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun selectTime(time: Time) {
|
fun selectTime(time: Time) {
|
||||||
@ -208,12 +209,13 @@ class TimeDropdown : TextInputDropDown {
|
|||||||
* Get the currently selected time.
|
* Get the currently selected time.
|
||||||
* ### Returns:
|
* ### Returns:
|
||||||
* - null if no valid time is selected
|
* - null if no valid time is selected
|
||||||
|
* - 0L if 'all day' is selected
|
||||||
* - a [Pair] of [Time] and [Time]? - the selected time object, if [displayMode] == [DISPLAY_LESSONS] or [showCustomTime]
|
* - a [Pair] of [Time] and [Time]? - the selected time object, if [displayMode] == [DISPLAY_LESSONS] or [showCustomTime]
|
||||||
* - [LessonRange] - the selected lesson range object, if [displayMode] == [DISPLAY_LESSON_RANGES]
|
* - [LessonRange] - the selected lesson range object, if [displayMode] == [DISPLAY_LESSON_RANGES]
|
||||||
*/
|
*/
|
||||||
fun getSelected(): Any? {
|
fun getSelected(): Any? {
|
||||||
return when (val tag = selected?.tag) {
|
return when (val tag = selected?.tag) {
|
||||||
0L -> null
|
0L -> 0L
|
||||||
is LessonFull ->
|
is LessonFull ->
|
||||||
if (tag.displayStartTime != null)
|
if (tag.displayStartTime != null)
|
||||||
tag.displayStartTime!! to tag.displayEndTime
|
tag.displayStartTime!! to tag.displayEndTime
|
||||||
|
@ -116,6 +116,8 @@ class WebPushFragment : Fragment(), CoroutineScope {
|
|||||||
}
|
}
|
||||||
adapter.notifyDataSetChanged()
|
adapter.notifyDataSetChanged()
|
||||||
|
|
||||||
|
app.config.sync.webPushEnabled = browsers.isNotEmpty()
|
||||||
|
|
||||||
if (browsers.isNotEmpty()) {
|
if (browsers.isNotEmpty()) {
|
||||||
b.browsersView.visibility = View.VISIBLE
|
b.browsersView.visibility = View.VISIBLE
|
||||||
b.browsersNoData.visibility = View.GONE
|
b.browsersNoData.visibility = View.GONE
|
||||||
|
@ -226,11 +226,14 @@ class WidgetTimetableProvider : AppWidgetProvider() {
|
|||||||
lessons = lessonList.filter {
|
lessons = lessonList.filter {
|
||||||
it.profileId == profile.id
|
it.profileId == profile.id
|
||||||
&& it.displayDate == timetableDate
|
&& it.displayDate == timetableDate
|
||||||
&& !(it.isCancelled && ignoreCancelled)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//if (lessons.isEmpty() && timetableDate.weekDay <= 5)
|
if (lessons.isEmpty())
|
||||||
// break
|
break
|
||||||
|
|
||||||
|
/*lessons = lessons.filterNot {
|
||||||
|
it.isCancelled && ignoreCancelled
|
||||||
|
}*/
|
||||||
|
|
||||||
checkedDays++
|
checkedDays++
|
||||||
}
|
}
|
||||||
@ -248,35 +251,39 @@ class WidgetTimetableProvider : AppWidgetProvider() {
|
|||||||
models.add(separator)
|
models.add(separator)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
views.setViewVisibility(R.id.widgetTimetableListView, View.VISIBLE)
|
||||||
|
views.setViewVisibility(R.id.widgetTimetableNoTimetable, View.GONE)
|
||||||
|
views.setViewVisibility(R.id.widgetTimetableNoLessons, View.GONE)
|
||||||
|
|
||||||
// set the displayingDate to show in the header
|
// set the displayingDate to show in the header
|
||||||
if (!unified) {
|
if (!unified) {
|
||||||
if (lessons.isNotEmpty())
|
if (lessons.isNotEmpty())
|
||||||
displayingDate = timetableDate
|
displayingDate = timetableDate
|
||||||
profileId = profile.id
|
profileId = profile.id
|
||||||
if (lessons.isEmpty()) {
|
if (lessons.isEmpty() && checkedDays < 7) {
|
||||||
views.setViewVisibility(R.id.widgetTimetableListView, View.GONE)
|
views.setViewVisibility(R.id.widgetTimetableListView, View.GONE)
|
||||||
views.setViewVisibility(R.id.widgetTimetableNoTimetable, View.VISIBLE)
|
views.setViewVisibility(R.id.widgetTimetableNoTimetable, View.VISIBLE)
|
||||||
}
|
}
|
||||||
if (lessons.size == 1 && lessons[0].type == Lesson.TYPE_NO_LESSONS) {
|
else if (lessons.none { !it.isCancelled } || lessons.size == 1 && lessons[0].type == Lesson.TYPE_NO_LESSONS) {
|
||||||
views.setViewVisibility(R.id.widgetTimetableListView, View.GONE)
|
views.setViewVisibility(R.id.widgetTimetableListView, View.GONE)
|
||||||
views.setViewVisibility(R.id.widgetTimetableNoLessons, View.VISIBLE)
|
views.setViewVisibility(R.id.widgetTimetableNoLessons, View.VISIBLE)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
if (lessons.isEmpty()) {
|
if (lessons.isEmpty() && checkedDays < 7) {
|
||||||
val separator = ItemWidgetTimetableModel()
|
val separator = ItemWidgetTimetableModel()
|
||||||
separator.profileId = profile.id
|
separator.profileId = profile.id
|
||||||
separator.bigStyle = widgetConfig.bigStyle
|
separator.bigStyle = widgetConfig.bigStyle
|
||||||
separator.darkTheme = widgetConfig.darkTheme
|
separator.darkTheme = widgetConfig.darkTheme
|
||||||
separator.isNoTimetableItem = true;
|
separator.isNoTimetableItem = true
|
||||||
models.add(separator)
|
models.add(separator)
|
||||||
}
|
}
|
||||||
if (lessons.size == 1 && lessons[0].type == Lesson.TYPE_NO_LESSONS) {
|
if (lessons.none { !it.isCancelled } || lessons.size == 1 && lessons[0].type == Lesson.TYPE_NO_LESSONS) {
|
||||||
val separator = ItemWidgetTimetableModel()
|
val separator = ItemWidgetTimetableModel()
|
||||||
separator.profileId = profile.id
|
separator.profileId = profile.id
|
||||||
separator.bigStyle = widgetConfig.bigStyle
|
separator.bigStyle = widgetConfig.bigStyle
|
||||||
separator.darkTheme = widgetConfig.darkTheme
|
separator.darkTheme = widgetConfig.darkTheme
|
||||||
separator.isNoLessonsItem = true;
|
separator.isNoLessonsItem = true
|
||||||
models.add(separator)
|
models.add(separator)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
200
app/src/main/java/pl/szczodrzynski/edziennik/utils/BetterLink.kt
Normal file
200
app/src/main/java/pl/szczodrzynski/edziennik/utils/BetterLink.kt
Normal file
@ -0,0 +1,200 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) Kuba Szczodrzyński 2020-3-18.
|
||||||
|
*/
|
||||||
|
|
||||||
|
@file:Suppress("INACCESSIBLE_TYPE")
|
||||||
|
|
||||||
|
package pl.szczodrzynski.edziennik.utils
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
|
import android.graphics.Color
|
||||||
|
import android.text.Spannable
|
||||||
|
import android.text.SpannableString
|
||||||
|
import android.text.Spanned
|
||||||
|
import android.text.style.URLSpan
|
||||||
|
import android.text.util.Linkify
|
||||||
|
import android.view.MenuItem
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import android.widget.TextView
|
||||||
|
import androidx.appcompat.view.menu.MenuBuilder
|
||||||
|
import androidx.appcompat.view.menu.MenuPopupHelper
|
||||||
|
import pl.szczodrzynski.edziennik.Intent
|
||||||
|
import pl.szczodrzynski.edziennik.copyToClipboard
|
||||||
|
import pl.szczodrzynski.edziennik.data.api.Regexes
|
||||||
|
import pl.szczodrzynski.edziennik.get
|
||||||
|
import pl.szczodrzynski.edziennik.getTextPosition
|
||||||
|
import pl.szczodrzynski.edziennik.utils.models.Date
|
||||||
|
|
||||||
|
object BetterLink {
|
||||||
|
|
||||||
|
@SuppressLint("RestrictedApi")
|
||||||
|
fun attach(textView: TextView, onActionSelected: (() -> Unit)? = null) {
|
||||||
|
textView.autoLinkMask = Linkify.WEB_URLS or Linkify.EMAIL_ADDRESSES
|
||||||
|
BetterLinkMovementMethod.linkify(textView.autoLinkMask, textView).setOnLinkClickListener { v, span: BetterLinkMovementMethod.ClickableSpanWithText ->
|
||||||
|
val url = span.text()
|
||||||
|
val c = v.context
|
||||||
|
|
||||||
|
val s = v.text as Spanned
|
||||||
|
val start = s.getSpanStart(span.span())
|
||||||
|
val end = s.getSpanEnd(span.span())
|
||||||
|
|
||||||
|
val parent = v.rootView.findViewById<ViewGroup>(android.R.id.content)
|
||||||
|
val parentLocation = intArrayOf(0, 0)
|
||||||
|
parent.getLocationOnScreen(parentLocation)
|
||||||
|
|
||||||
|
val rect = textView.getTextPosition(start..end)
|
||||||
|
|
||||||
|
val view = View(c)
|
||||||
|
view.layoutParams = ViewGroup.LayoutParams(rect.width(), rect.height())
|
||||||
|
view.setBackgroundColor(Color.TRANSPARENT)
|
||||||
|
|
||||||
|
parent.addView(view)
|
||||||
|
|
||||||
|
view.x = rect.left.toFloat() - parentLocation[0]
|
||||||
|
view.y = rect.top.toFloat() - parentLocation[1]
|
||||||
|
|
||||||
|
val menu = MenuBuilder(c)
|
||||||
|
val helper = MenuPopupHelper(c, menu, view)
|
||||||
|
val popup = helper.popup
|
||||||
|
|
||||||
|
var menuTitle = url.substringAfter(":")
|
||||||
|
var date: Date? = null
|
||||||
|
|
||||||
|
var urlItem: MenuItem? = null
|
||||||
|
var createEventItem: MenuItem? = null
|
||||||
|
//var goToTimetableItem: MenuItem? = null // TODO 2020-03-19: implement this
|
||||||
|
var mailItem: MenuItem? = null
|
||||||
|
var copyItem: MenuItem? = null
|
||||||
|
|
||||||
|
when {
|
||||||
|
url.startsWith("mailto:") -> {
|
||||||
|
mailItem = menu.add(1, 20, 2, "Napisz e-mail")
|
||||||
|
}
|
||||||
|
url.startsWith("dateYmd:") -> {
|
||||||
|
createEventItem = menu.add(1, 10, 2, "Utwórz wydarzenie")
|
||||||
|
//goToTimetableItem = menu.add(1, 11, 3, "Idź do planu lekcji")
|
||||||
|
date = parseDateYmd(menuTitle)
|
||||||
|
}
|
||||||
|
url.startsWith("dateDmy:") -> {
|
||||||
|
createEventItem = menu.add(1, 10, 2, "Utwórz wydarzenie")
|
||||||
|
//goToTimetableItem = menu.add(1, 11, 3, "Idź do planu lekcji")
|
||||||
|
date = parseDateDmy(menuTitle)
|
||||||
|
}
|
||||||
|
url.startsWith("dateAbs:") -> {
|
||||||
|
createEventItem = menu.add(1, 10, 2, "Utwórz wydarzenie")
|
||||||
|
//goToTimetableItem = menu.add(1, 11, 3, "Idź do planu lekcji")
|
||||||
|
date = parseDateAbs(menuTitle)
|
||||||
|
}
|
||||||
|
url.startsWith("dateRel:") -> {
|
||||||
|
createEventItem = menu.add(1, 10, 2, "Utwórz wydarzenie")
|
||||||
|
//goToTimetableItem = menu.add(1, 11, 3, "Idź do planu lekcji")
|
||||||
|
date = parseDateRel(menuTitle)
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
urlItem = menu.add(1, 1, 2, "Otwórz w przeglądarce")
|
||||||
|
menuTitle = url
|
||||||
|
}
|
||||||
|
}
|
||||||
|
copyItem = menu.add(1, 1000, 1000, "Kopiuj tekst")
|
||||||
|
|
||||||
|
helper.setOnDismissListener { parent.removeView(view) }
|
||||||
|
|
||||||
|
urlItem?.setOnMenuItemClickListener { Utils.openUrl(c, url); true }
|
||||||
|
mailItem?.setOnMenuItemClickListener { Utils.openUrl(c, url); true }
|
||||||
|
copyItem?.setOnMenuItemClickListener { menuTitle.copyToClipboard(c); true }
|
||||||
|
createEventItem?.setOnMenuItemClickListener {
|
||||||
|
onActionSelected?.invoke()
|
||||||
|
val intent = Intent(
|
||||||
|
android.content.Intent.ACTION_MAIN,
|
||||||
|
"action" to "createManualEvent",
|
||||||
|
"eventDate" to date?.stringY_m_d
|
||||||
|
)
|
||||||
|
c.sendBroadcast(intent)
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
menu::class.java.getDeclaredMethod("setHeaderTitleInt", CharSequence::class.java).let {
|
||||||
|
it.isAccessible = true
|
||||||
|
it.invoke(menu, menuTitle)
|
||||||
|
}
|
||||||
|
popup::class.java.getDeclaredField("mShowTitle").let {
|
||||||
|
it.isAccessible = true
|
||||||
|
it.set(popup, true)
|
||||||
|
}
|
||||||
|
helper::class.java.getDeclaredMethod("showPopup", Int::class.java, Int::class.java, Boolean::class.java, Boolean::class.java).let {
|
||||||
|
it.isAccessible = true
|
||||||
|
it.invoke(helper, 0, 0, false, true)
|
||||||
|
}
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
val spanned = textView.text as? Spannable ?: {
|
||||||
|
SpannableString(textView.text)
|
||||||
|
}()
|
||||||
|
|
||||||
|
Regexes.LINKIFY_DATE_YMD.findAll(textView.text).forEach { match ->
|
||||||
|
val span = URLSpan("dateYmd:" + match.value)
|
||||||
|
spanned.setSpan(span, match.range.first, match.range.last + 1, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
|
||||||
|
}
|
||||||
|
Regexes.LINKIFY_DATE_DMY.findAll(textView.text).forEach { match ->
|
||||||
|
val span = URLSpan("dateDmy:" + match.value)
|
||||||
|
spanned.setSpan(span, match.range.first, match.range.last + 1, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
|
||||||
|
}
|
||||||
|
Regexes.LINKIFY_DATE_ABSOLUTE.findAll(textView.text).forEach { match ->
|
||||||
|
val span = URLSpan("dateAbs:" + match.value)
|
||||||
|
spanned.setSpan(span, match.range.first, match.range.last + 1, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
|
||||||
|
}
|
||||||
|
Regexes.LINKIFY_DATE_RELATIVE.findAll(textView.text).forEach { match ->
|
||||||
|
val span = URLSpan("dateRel:" + match.value)
|
||||||
|
spanned.setSpan(span, match.range.first, match.range.last + 1, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
|
||||||
|
}
|
||||||
|
//Linkify.addLinks(textView, LINKIFY_DATE_ABSOLUTE.toPattern(), "dateAbs:")
|
||||||
|
//Linkify.addLinks(textView, LINKIFY_DATE_RELATIVE.toPattern(), "dateRel:")
|
||||||
|
}
|
||||||
|
|
||||||
|
private val monthNames = listOf("sty", "lut", "mar", "kwi", "maj", "cze", "lip", "sie", "wrz", "paź", "lis", "gru")
|
||||||
|
|
||||||
|
private fun parseDateYmd(text: String): Date? {
|
||||||
|
return Regexes.LINKIFY_DATE_YMD.find(text)?.let {
|
||||||
|
val year = it[1].toIntOrNull() ?: Date.getToday().year
|
||||||
|
val month = it[2].toIntOrNull() ?: 1
|
||||||
|
val day = it[3].toIntOrNull() ?: 1
|
||||||
|
Date(year, month, day)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private fun parseDateDmy(text: String): Date? {
|
||||||
|
return Regexes.LINKIFY_DATE_DMY.find(text)?.let {
|
||||||
|
val day = it[1].toIntOrNull() ?: 1
|
||||||
|
val month = it[2].toIntOrNull() ?: 1
|
||||||
|
var year = it[3].toIntOrNull() ?: Date.getToday().year
|
||||||
|
if (year < 50)
|
||||||
|
year += 2000
|
||||||
|
Date(year, month, day)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun parseDateAbs(text: String): Date? {
|
||||||
|
return Regexes.LINKIFY_DATE_ABSOLUTE.find(text)?.let {
|
||||||
|
val year = it[3].toIntOrNull() ?: Date.getToday().year
|
||||||
|
val month = monthNames.indexOf(it[2]) + 1
|
||||||
|
val day = it[1].toIntOrNull() ?: 1
|
||||||
|
Date(year, month.coerceAtLeast(1), day)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun parseDateRel(text: String): Date? {
|
||||||
|
return Regexes.LINKIFY_DATE_RELATIVE.find(text)?.let {
|
||||||
|
val date = Date.getToday()
|
||||||
|
|
||||||
|
val amount = it[1].toIntOrNull() ?: 1
|
||||||
|
val unitInDays = when (it[2]) {
|
||||||
|
"dni", "dzień" -> 1
|
||||||
|
"tydzień", "tygodnie" -> 7
|
||||||
|
else -> 1
|
||||||
|
}
|
||||||
|
|
||||||
|
date.stepForward(0, 0, amount*unitInDays)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,458 @@
|
|||||||
|
package pl.szczodrzynski.edziennik.utils;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.graphics.RectF;
|
||||||
|
import android.text.Layout;
|
||||||
|
import android.text.Selection;
|
||||||
|
import android.text.Spannable;
|
||||||
|
import android.text.Spanned;
|
||||||
|
import android.text.method.LinkMovementMethod;
|
||||||
|
import android.text.style.BackgroundColorSpan;
|
||||||
|
import android.text.style.ClickableSpan;
|
||||||
|
import android.text.style.URLSpan;
|
||||||
|
import android.text.util.Linkify;
|
||||||
|
import android.view.HapticFeedbackConstants;
|
||||||
|
import android.view.MotionEvent;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewConfiguration;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.view.Window;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import pl.szczodrzynski.edziennik.R;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles URL clicks on TextViews. Unlike the default implementation, this:
|
||||||
|
* <p>
|
||||||
|
* <ul>
|
||||||
|
* <li>Reliably applies a highlight color on links when they're touched.</li>
|
||||||
|
* <li>Let's you handle single and long clicks on URLs</li>
|
||||||
|
* <li>Correctly identifies focused URLs (Unlike the default implementation where a click is registered even if it's
|
||||||
|
* made outside of the URL's bounds if there is no more text in that direction.)</li>
|
||||||
|
* </ul>
|
||||||
|
*/
|
||||||
|
public class BetterLinkMovementMethod extends LinkMovementMethod {
|
||||||
|
|
||||||
|
private static BetterLinkMovementMethod singleInstance;
|
||||||
|
private static final int LINKIFY_NONE = -2;
|
||||||
|
|
||||||
|
private OnLinkClickListener onLinkClickListener;
|
||||||
|
private OnLinkLongClickListener onLinkLongClickListener;
|
||||||
|
private final RectF touchedLineBounds = new RectF();
|
||||||
|
private boolean isUrlHighlighted;
|
||||||
|
private ClickableSpan clickableSpanUnderTouchOnActionDown;
|
||||||
|
private int activeTextViewHashcode;
|
||||||
|
private LongPressTimer ongoingLongPressTimer;
|
||||||
|
private boolean wasLongPressRegistered;
|
||||||
|
|
||||||
|
public interface OnLinkClickListener {
|
||||||
|
/**
|
||||||
|
* @param textView The TextView on which a click was registered.
|
||||||
|
* @param span The clicked URL span.
|
||||||
|
* @return True if this click was handled. False to let Android handle the URL.
|
||||||
|
*/
|
||||||
|
boolean onClick(TextView textView, ClickableSpanWithText span);
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface OnLinkLongClickListener {
|
||||||
|
/**
|
||||||
|
* @param textView The TextView on which a long-click was registered.
|
||||||
|
* @param span The long-clicked URL span.
|
||||||
|
* @return True if this long-click was handled. False to let Android handle the URL (as a short-click).
|
||||||
|
*/
|
||||||
|
boolean onLongClick(TextView textView, ClickableSpanWithText span);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return a new instance of BetterLinkMovementMethod.
|
||||||
|
*/
|
||||||
|
public static BetterLinkMovementMethod newInstance() {
|
||||||
|
return new BetterLinkMovementMethod();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param linkifyMask One of {@link Linkify#ALL}, {@link Linkify#PHONE_NUMBERS}, {@link Linkify#MAP_ADDRESSES},
|
||||||
|
* {@link Linkify#WEB_URLS} and {@link Linkify#EMAIL_ADDRESSES}.
|
||||||
|
* @param textViews The TextViews on which a {@link BetterLinkMovementMethod} should be registered.
|
||||||
|
* @return The registered {@link BetterLinkMovementMethod} on the TextViews.
|
||||||
|
*/
|
||||||
|
public static BetterLinkMovementMethod linkify(int linkifyMask, TextView... textViews) {
|
||||||
|
BetterLinkMovementMethod movementMethod = newInstance();
|
||||||
|
for (TextView textView : textViews) {
|
||||||
|
addLinks(linkifyMask, movementMethod, textView);
|
||||||
|
}
|
||||||
|
return movementMethod;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Like {@link #linkify(int, TextView...)}, but can be used for TextViews with HTML links.
|
||||||
|
*
|
||||||
|
* @param textViews The TextViews on which a {@link BetterLinkMovementMethod} should be registered.
|
||||||
|
* @return The registered {@link BetterLinkMovementMethod} on the TextViews.
|
||||||
|
*/
|
||||||
|
public static BetterLinkMovementMethod linkifyHtml(TextView... textViews) {
|
||||||
|
return linkify(LINKIFY_NONE, textViews);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Recursively register a {@link BetterLinkMovementMethod} on every TextView inside a layout.
|
||||||
|
*
|
||||||
|
* @param linkifyMask One of {@link Linkify#ALL}, {@link Linkify#PHONE_NUMBERS}, {@link Linkify#MAP_ADDRESSES},
|
||||||
|
* {@link Linkify#WEB_URLS} and {@link Linkify#EMAIL_ADDRESSES}.
|
||||||
|
* @return The registered {@link BetterLinkMovementMethod} on the TextViews.
|
||||||
|
*/
|
||||||
|
public static BetterLinkMovementMethod linkify(int linkifyMask, ViewGroup viewGroup) {
|
||||||
|
BetterLinkMovementMethod movementMethod = newInstance();
|
||||||
|
rAddLinks(linkifyMask, viewGroup, movementMethod);
|
||||||
|
return movementMethod;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Like {@link #linkify(int, TextView...)}, but can be used for TextViews with HTML links.
|
||||||
|
*
|
||||||
|
* @return The registered {@link BetterLinkMovementMethod} on the TextViews.
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
public static BetterLinkMovementMethod linkifyHtml(ViewGroup viewGroup) {
|
||||||
|
return linkify(LINKIFY_NONE, viewGroup);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Recursively register a {@link BetterLinkMovementMethod} on every TextView inside a layout.
|
||||||
|
*
|
||||||
|
* @param linkifyMask One of {@link Linkify#ALL}, {@link Linkify#PHONE_NUMBERS}, {@link Linkify#MAP_ADDRESSES},
|
||||||
|
* {@link Linkify#WEB_URLS} and {@link Linkify#EMAIL_ADDRESSES}.
|
||||||
|
* @return The registered {@link BetterLinkMovementMethod} on the TextViews.
|
||||||
|
*/
|
||||||
|
public static BetterLinkMovementMethod linkify(int linkifyMask, Activity activity) {
|
||||||
|
// Find the layout passed to setContentView().
|
||||||
|
ViewGroup activityLayout = ((ViewGroup) ((ViewGroup) activity.findViewById(Window.ID_ANDROID_CONTENT)).getChildAt(0));
|
||||||
|
|
||||||
|
BetterLinkMovementMethod movementMethod = newInstance();
|
||||||
|
rAddLinks(linkifyMask, activityLayout, movementMethod);
|
||||||
|
return movementMethod;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Like {@link #linkify(int, TextView...)}, but can be used for TextViews with HTML links.
|
||||||
|
*
|
||||||
|
* @return The registered {@link BetterLinkMovementMethod} on the TextViews.
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
public static BetterLinkMovementMethod linkifyHtml(Activity activity) {
|
||||||
|
return linkify(LINKIFY_NONE, activity);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a static instance of BetterLinkMovementMethod. Do note that registering a click listener on the returned
|
||||||
|
* instance is not supported because it will potentially be shared on multiple TextViews.
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
public static BetterLinkMovementMethod getInstance() {
|
||||||
|
if (singleInstance == null) {
|
||||||
|
singleInstance = new BetterLinkMovementMethod();
|
||||||
|
}
|
||||||
|
return singleInstance;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected BetterLinkMovementMethod() {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set a listener that will get called whenever any link is clicked on the TextView.
|
||||||
|
*/
|
||||||
|
public BetterLinkMovementMethod setOnLinkClickListener(OnLinkClickListener clickListener) {
|
||||||
|
if (this == singleInstance) {
|
||||||
|
throw new UnsupportedOperationException("Setting a click listener on the instance returned by getInstance() is not supported to avoid memory " +
|
||||||
|
"leaks. Please use newInstance() or any of the linkify() methods instead.");
|
||||||
|
}
|
||||||
|
|
||||||
|
this.onLinkClickListener = clickListener;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set a listener that will get called whenever any link is clicked on the TextView.
|
||||||
|
*/
|
||||||
|
public BetterLinkMovementMethod setOnLinkLongClickListener(OnLinkLongClickListener longClickListener) {
|
||||||
|
if (this == singleInstance) {
|
||||||
|
throw new UnsupportedOperationException("Setting a long-click listener on the instance returned by getInstance() is not supported to avoid " +
|
||||||
|
"memory leaks. Please use newInstance() or any of the linkify() methods instead.");
|
||||||
|
}
|
||||||
|
|
||||||
|
this.onLinkLongClickListener = longClickListener;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ======== PUBLIC APIs END ======== //
|
||||||
|
|
||||||
|
private static void rAddLinks(int linkifyMask, ViewGroup viewGroup, BetterLinkMovementMethod movementMethod) {
|
||||||
|
for (int i = 0; i < viewGroup.getChildCount(); i++) {
|
||||||
|
View child = viewGroup.getChildAt(i);
|
||||||
|
|
||||||
|
if (child instanceof ViewGroup) {
|
||||||
|
// Recursively find child TextViews.
|
||||||
|
rAddLinks(linkifyMask, ((ViewGroup) child), movementMethod);
|
||||||
|
|
||||||
|
} else if (child instanceof TextView) {
|
||||||
|
TextView textView = (TextView) child;
|
||||||
|
addLinks(linkifyMask, movementMethod, textView);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void addLinks(int linkifyMask, BetterLinkMovementMethod movementMethod, TextView textView) {
|
||||||
|
textView.setMovementMethod(movementMethod);
|
||||||
|
if (linkifyMask != LINKIFY_NONE) {
|
||||||
|
Linkify.addLinks(textView, linkifyMask);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onTouchEvent(final TextView textView, Spannable text, MotionEvent event) {
|
||||||
|
if (activeTextViewHashcode != textView.hashCode()) {
|
||||||
|
// Bug workaround: TextView stops calling onTouchEvent() once any URL is highlighted.
|
||||||
|
// A hacky solution is to reset any "autoLink" property set in XML. But we also want
|
||||||
|
// to do this once per TextView.
|
||||||
|
activeTextViewHashcode = textView.hashCode();
|
||||||
|
textView.setAutoLinkMask(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
final ClickableSpan clickableSpanUnderTouch = findClickableSpanUnderTouch(textView, text, event);
|
||||||
|
if (event.getAction() == MotionEvent.ACTION_DOWN) {
|
||||||
|
clickableSpanUnderTouchOnActionDown = clickableSpanUnderTouch;
|
||||||
|
}
|
||||||
|
final boolean touchStartedOverAClickableSpan = clickableSpanUnderTouchOnActionDown != null;
|
||||||
|
|
||||||
|
switch (event.getAction()) {
|
||||||
|
case MotionEvent.ACTION_DOWN:
|
||||||
|
if (clickableSpanUnderTouch != null) {
|
||||||
|
highlightUrl(textView, clickableSpanUnderTouch, text);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (touchStartedOverAClickableSpan && onLinkLongClickListener != null) {
|
||||||
|
LongPressTimer.OnTimerReachedListener longClickListener = new LongPressTimer.OnTimerReachedListener() {
|
||||||
|
@Override
|
||||||
|
public void onTimerReached() {
|
||||||
|
wasLongPressRegistered = true;
|
||||||
|
textView.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
|
||||||
|
removeUrlHighlightColor(textView);
|
||||||
|
dispatchUrlLongClick(textView, clickableSpanUnderTouch);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
startTimerForRegisteringLongClick(textView, longClickListener);
|
||||||
|
}
|
||||||
|
return touchStartedOverAClickableSpan;
|
||||||
|
|
||||||
|
case MotionEvent.ACTION_UP:
|
||||||
|
// Register a click only if the touch started and ended on the same URL.
|
||||||
|
if (!wasLongPressRegistered && touchStartedOverAClickableSpan && clickableSpanUnderTouch == clickableSpanUnderTouchOnActionDown) {
|
||||||
|
dispatchUrlClick(textView, clickableSpanUnderTouch);
|
||||||
|
}
|
||||||
|
cleanupOnTouchUp(textView);
|
||||||
|
|
||||||
|
// Consume this event even if we could not find any spans to avoid letting Android handle this event.
|
||||||
|
// Android's TextView implementation has a bug where links get clicked even when there is no more text
|
||||||
|
// next to the link and the touch lies outside its bounds in the same direction.
|
||||||
|
return touchStartedOverAClickableSpan;
|
||||||
|
|
||||||
|
case MotionEvent.ACTION_CANCEL:
|
||||||
|
cleanupOnTouchUp(textView);
|
||||||
|
return false;
|
||||||
|
|
||||||
|
case MotionEvent.ACTION_MOVE:
|
||||||
|
// Stop listening for a long-press as soon as the user wanders off to unknown lands.
|
||||||
|
if (clickableSpanUnderTouch != clickableSpanUnderTouchOnActionDown) {
|
||||||
|
removeLongPressCallback(textView);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!wasLongPressRegistered) {
|
||||||
|
// Toggle highlight.
|
||||||
|
if (clickableSpanUnderTouch != null) {
|
||||||
|
highlightUrl(textView, clickableSpanUnderTouch, text);
|
||||||
|
} else {
|
||||||
|
removeUrlHighlightColor(textView);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return touchStartedOverAClickableSpan;
|
||||||
|
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void cleanupOnTouchUp(TextView textView) {
|
||||||
|
wasLongPressRegistered = false;
|
||||||
|
clickableSpanUnderTouchOnActionDown = null;
|
||||||
|
removeUrlHighlightColor(textView);
|
||||||
|
removeLongPressCallback(textView);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determines the touched location inside the TextView's text and returns the ClickableSpan found under it (if any).
|
||||||
|
*
|
||||||
|
* @return The touched ClickableSpan or null.
|
||||||
|
*/
|
||||||
|
protected ClickableSpan findClickableSpanUnderTouch(TextView textView, Spannable text, MotionEvent event) {
|
||||||
|
// So we need to find the location in text where touch was made, regardless of whether the TextView
|
||||||
|
// has scrollable text. That is, not the entire text is currently visible.
|
||||||
|
int touchX = (int) event.getX();
|
||||||
|
int touchY = (int) event.getY();
|
||||||
|
|
||||||
|
// Ignore padding.
|
||||||
|
touchX -= textView.getTotalPaddingLeft();
|
||||||
|
touchY -= textView.getTotalPaddingTop();
|
||||||
|
|
||||||
|
// Account for scrollable text.
|
||||||
|
touchX += textView.getScrollX();
|
||||||
|
touchY += textView.getScrollY();
|
||||||
|
|
||||||
|
final Layout layout = textView.getLayout();
|
||||||
|
final int touchedLine = layout.getLineForVertical(touchY);
|
||||||
|
final int touchOffset = layout.getOffsetForHorizontal(touchedLine, touchX);
|
||||||
|
|
||||||
|
touchedLineBounds.left = layout.getLineLeft(touchedLine);
|
||||||
|
touchedLineBounds.top = layout.getLineTop(touchedLine);
|
||||||
|
touchedLineBounds.right = layout.getLineWidth(touchedLine) + touchedLineBounds.left;
|
||||||
|
touchedLineBounds.bottom = layout.getLineBottom(touchedLine);
|
||||||
|
|
||||||
|
if (touchedLineBounds.contains(touchX, touchY)) {
|
||||||
|
// Find a ClickableSpan that lies under the touched area.
|
||||||
|
final Object[] spans = text.getSpans(touchOffset, touchOffset, ClickableSpan.class);
|
||||||
|
for (final Object span : spans) {
|
||||||
|
if (span instanceof ClickableSpan) {
|
||||||
|
return (ClickableSpan) span;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// No ClickableSpan found under the touched location.
|
||||||
|
return null;
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// Touch lies outside the line's horizontal bounds where no spans should exist.
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a background color span at <var>clickableSpan</var>'s location.
|
||||||
|
*/
|
||||||
|
protected void highlightUrl(TextView textView, ClickableSpan clickableSpan, Spannable text) {
|
||||||
|
if (isUrlHighlighted) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
isUrlHighlighted = true;
|
||||||
|
|
||||||
|
int spanStart = text.getSpanStart(clickableSpan);
|
||||||
|
int spanEnd = text.getSpanEnd(clickableSpan);
|
||||||
|
BackgroundColorSpan highlightSpan = new BackgroundColorSpan(textView.getHighlightColor());
|
||||||
|
text.setSpan(highlightSpan, spanStart, spanEnd, Spannable.SPAN_INCLUSIVE_INCLUSIVE);
|
||||||
|
|
||||||
|
textView.setTag(R.id.bettermovementmethod_highlight_background_span, highlightSpan);
|
||||||
|
|
||||||
|
Selection.setSelection(text, spanStart, spanEnd);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes the highlight color under the Url.
|
||||||
|
*/
|
||||||
|
protected void removeUrlHighlightColor(TextView textView) {
|
||||||
|
if (!isUrlHighlighted) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
isUrlHighlighted = false;
|
||||||
|
|
||||||
|
Spannable text = (Spannable) textView.getText();
|
||||||
|
BackgroundColorSpan highlightSpan = (BackgroundColorSpan) textView.getTag(R.id.bettermovementmethod_highlight_background_span);
|
||||||
|
text.removeSpan(highlightSpan);
|
||||||
|
|
||||||
|
Selection.removeSelection(text);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void startTimerForRegisteringLongClick(TextView textView, LongPressTimer.OnTimerReachedListener longClickListener) {
|
||||||
|
ongoingLongPressTimer = new LongPressTimer();
|
||||||
|
ongoingLongPressTimer.setOnTimerReachedListener(longClickListener);
|
||||||
|
textView.postDelayed(ongoingLongPressTimer, ViewConfiguration.getLongPressTimeout());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove the long-press detection timer.
|
||||||
|
*/
|
||||||
|
protected void removeLongPressCallback(TextView textView) {
|
||||||
|
if (ongoingLongPressTimer != null) {
|
||||||
|
textView.removeCallbacks(ongoingLongPressTimer);
|
||||||
|
ongoingLongPressTimer = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void dispatchUrlClick(TextView textView, ClickableSpan clickableSpan) {
|
||||||
|
ClickableSpanWithText clickableSpanWithText = ClickableSpanWithText.ofSpan(textView, clickableSpan);
|
||||||
|
boolean handled = onLinkClickListener != null && onLinkClickListener.onClick(textView, clickableSpanWithText);
|
||||||
|
|
||||||
|
if (!handled) {
|
||||||
|
// Let Android handle this click.
|
||||||
|
clickableSpanWithText.span().onClick(textView);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void dispatchUrlLongClick(TextView textView, ClickableSpan clickableSpan) {
|
||||||
|
ClickableSpanWithText clickableSpanWithText = ClickableSpanWithText.ofSpan(textView, clickableSpan);
|
||||||
|
boolean handled = onLinkLongClickListener != null && onLinkLongClickListener.onLongClick(textView, clickableSpanWithText);
|
||||||
|
|
||||||
|
if (!handled) {
|
||||||
|
// Let Android handle this long click as a short-click.
|
||||||
|
clickableSpanWithText.span().onClick(textView);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static final class LongPressTimer implements Runnable {
|
||||||
|
private OnTimerReachedListener onTimerReachedListener;
|
||||||
|
|
||||||
|
protected interface OnTimerReachedListener {
|
||||||
|
void onTimerReached();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
onTimerReachedListener.onTimerReached();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setOnTimerReachedListener(OnTimerReachedListener listener) {
|
||||||
|
onTimerReachedListener = listener;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A wrapper to support all {@link ClickableSpan}s that may or may not provide URLs.
|
||||||
|
*/
|
||||||
|
protected static class ClickableSpanWithText {
|
||||||
|
private ClickableSpan span;
|
||||||
|
private String text;
|
||||||
|
|
||||||
|
protected static ClickableSpanWithText ofSpan(TextView textView, ClickableSpan span) {
|
||||||
|
Spanned s = (Spanned) textView.getText();
|
||||||
|
String text;
|
||||||
|
if (span instanceof URLSpan) {
|
||||||
|
text = ((URLSpan) span).getURL();
|
||||||
|
} else {
|
||||||
|
int start = s.getSpanStart(span);
|
||||||
|
int end = s.getSpanEnd(span);
|
||||||
|
text = s.subSequence(start, end).toString();
|
||||||
|
}
|
||||||
|
return new ClickableSpanWithText(span, text);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected ClickableSpanWithText(ClickableSpan span, String text) {
|
||||||
|
this.span = span;
|
||||||
|
this.text = text;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected ClickableSpan span() {
|
||||||
|
return span;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected String text() {
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,14 +1,9 @@
|
|||||||
package pl.szczodrzynski.edziennik.utils
|
package pl.szczodrzynski.edziennik.utils
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.graphics.Color
|
|
||||||
|
|
||||||
import androidx.annotation.StringRes
|
import androidx.annotation.StringRes
|
||||||
import androidx.annotation.StyleRes
|
import androidx.annotation.StyleRes
|
||||||
import androidx.core.graphics.ColorUtils
|
|
||||||
|
|
||||||
import pl.szczodrzynski.edziennik.R
|
import pl.szczodrzynski.edziennik.R
|
||||||
|
|
||||||
import pl.szczodrzynski.navlib.getColorFromAttr
|
import pl.szczodrzynski.navlib.getColorFromAttr
|
||||||
|
|
||||||
object Themes {
|
object Themes {
|
||||||
@ -49,7 +44,7 @@ object Themes {
|
|||||||
|
|
||||||
|
|
||||||
val appThemeNoDisplay: Int
|
val appThemeNoDisplay: Int
|
||||||
get() = if (theme.isDark) R.style.AppThemeDark_NoDisplay else R.style.AppTheme_NoDisplay
|
get() = if (theme.isDark) R.style.AppTheme_Dark_NoDisplay else R.style.AppTheme_Light_NoDisplay
|
||||||
|
|
||||||
|
|
||||||
val appTheme: Int
|
val appTheme: Int
|
||||||
|
@ -0,0 +1,89 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) Kuba Szczodrzyński 2020-3-17.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package pl.szczodrzynski.edziennik.utils.html
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.graphics.Color
|
||||||
|
import android.os.Build
|
||||||
|
import android.text.Html
|
||||||
|
import android.text.SpannableStringBuilder
|
||||||
|
import android.text.Spanned
|
||||||
|
import android.text.style.BulletSpan
|
||||||
|
import androidx.core.graphics.ColorUtils
|
||||||
|
import pl.szczodrzynski.edziennik.dp
|
||||||
|
import pl.szczodrzynski.edziennik.resolveAttr
|
||||||
|
import pl.szczodrzynski.navlib.blendColors
|
||||||
|
|
||||||
|
object BetterHtml {
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
fun fromHtml(context: Context, html: String): Spanned {
|
||||||
|
val hexPattern = "(#[a-fA-F0-9]{6})"
|
||||||
|
val colorRegex = "(?:color=\"$hexPattern\")|(?:style=\"color: ?${hexPattern})"
|
||||||
|
.toRegex(RegexOption.IGNORE_CASE)
|
||||||
|
|
||||||
|
var text = html
|
||||||
|
.replace("\\[META:[A-z0-9]+;[0-9-]+]".toRegex(), "")
|
||||||
|
.replace("background-color: ?$hexPattern;".toRegex(), "")
|
||||||
|
|
||||||
|
val colorBackground = android.R.attr.colorBackground.resolveAttr(context)
|
||||||
|
val textColorPrimary = android.R.attr.textColorPrimary.resolveAttr(context) and 0xffffff
|
||||||
|
|
||||||
|
colorRegex.findAll(text).forEach { result ->
|
||||||
|
val group = result.groups.drop(1).firstOrNull { it != null } ?: return@forEach
|
||||||
|
|
||||||
|
val color = Color.parseColor(group.value)
|
||||||
|
var newColor = 0xff000000.toInt() or color
|
||||||
|
|
||||||
|
var blendAmount = 1
|
||||||
|
var numIterations = 0
|
||||||
|
|
||||||
|
while (numIterations < 100 && ColorUtils.calculateContrast(colorBackground, newColor) < 4.5f) {
|
||||||
|
blendAmount += 2
|
||||||
|
newColor = blendColors(color, blendAmount shl 24 or textColorPrimary)
|
||||||
|
numIterations++
|
||||||
|
}
|
||||||
|
|
||||||
|
text = text.replaceRange(group.range, "#" + (newColor and 0xffffff).toString(16))
|
||||||
|
}
|
||||||
|
|
||||||
|
/*val olRegex = """<ol>(.+?)</\s*?ol>"""
|
||||||
|
.toRegex(setOf(RegexOption.DOT_MATCHES_ALL, RegexOption.IGNORE_CASE))
|
||||||
|
olRegex.findAll(text).forEach {
|
||||||
|
text.replaceRange(
|
||||||
|
it.range,
|
||||||
|
text.slice(it.range).replace("li>", "_li>")
|
||||||
|
)
|
||||||
|
}*/
|
||||||
|
|
||||||
|
@Suppress("DEPRECATION")
|
||||||
|
val htmlSpannable = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
||||||
|
Html.fromHtml(
|
||||||
|
text,
|
||||||
|
Html.FROM_HTML_SEPARATOR_LINE_BREAK_LIST_ITEM or Html.FROM_HTML_SEPARATOR_LINE_BREAK_LIST or Html.FROM_HTML_SEPARATOR_LINE_BREAK_DIV,
|
||||||
|
null,
|
||||||
|
LiTagHandler()
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
Html.fromHtml(text, null, LiTagHandler())
|
||||||
|
}
|
||||||
|
|
||||||
|
val spannableBuilder = SpannableStringBuilder(htmlSpannable)
|
||||||
|
val bulletSpans = spannableBuilder.getSpans(0, spannableBuilder.length, BulletSpan::class.java)
|
||||||
|
bulletSpans.forEach {
|
||||||
|
val start = spannableBuilder.getSpanStart(it)
|
||||||
|
val end = spannableBuilder.getSpanEnd(it)
|
||||||
|
spannableBuilder.removeSpan(it)
|
||||||
|
spannableBuilder.setSpan(
|
||||||
|
ImprovedBulletSpan(bulletRadius = 3.dp, startWidth = 24.dp, gapWidth = 8.dp),
|
||||||
|
start,
|
||||||
|
end,
|
||||||
|
Spanned.SPAN_INCLUSIVE_EXCLUSIVE
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return spannableBuilder
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,79 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) Kuba Szczodrzyński 2020-3-17.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* https://github.com/davidbilik/bullet-span-sample/blob/master/app/src/main/java/cz/davidbilik/bulletsample/ImprovedBulletSpan.kt
|
||||||
|
*/
|
||||||
|
|
||||||
|
package pl.szczodrzynski.edziennik.utils.html
|
||||||
|
|
||||||
|
import android.graphics.Canvas
|
||||||
|
import android.graphics.Paint
|
||||||
|
import android.graphics.Path
|
||||||
|
import android.graphics.Path.Direction
|
||||||
|
import android.text.Layout
|
||||||
|
import android.text.Spanned
|
||||||
|
import android.text.style.LeadingMarginSpan
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Copy of [android.text.style.BulletSpan] from android SDK 28 with removed internal code
|
||||||
|
*/
|
||||||
|
class ImprovedBulletSpan(
|
||||||
|
val bulletRadius: Int = STANDARD_BULLET_RADIUS,
|
||||||
|
val startWidth: Int = STANDARD_GAP_WIDTH,
|
||||||
|
val gapWidth: Int = STANDARD_GAP_WIDTH,
|
||||||
|
val color: Int = STANDARD_COLOR
|
||||||
|
) : LeadingMarginSpan {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
// Bullet is slightly bigger to avoid aliasing artifacts on mdpi devices.
|
||||||
|
private const val STANDARD_BULLET_RADIUS = 4
|
||||||
|
private const val STANDARD_GAP_WIDTH = 2
|
||||||
|
private const val STANDARD_COLOR = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
private var mBulletPath: Path? = null
|
||||||
|
|
||||||
|
override fun getLeadingMargin(first: Boolean): Int {
|
||||||
|
return startWidth + 2 * bulletRadius + gapWidth
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun drawLeadingMargin(
|
||||||
|
canvas: Canvas, paint: Paint, x: Int, dir: Int,
|
||||||
|
top: Int, baseline: Int, bottom: Int,
|
||||||
|
text: CharSequence, start: Int, end: Int,
|
||||||
|
first: Boolean,
|
||||||
|
layout: Layout?
|
||||||
|
) {
|
||||||
|
if ((text as Spanned).getSpanStart(this) == start) {
|
||||||
|
val style = paint.style
|
||||||
|
paint.style = Paint.Style.FILL
|
||||||
|
|
||||||
|
val yPosition = if (layout != null) {
|
||||||
|
val line = layout.getLineForOffset(start)
|
||||||
|
layout.getLineBaseline(line).toFloat() - bulletRadius * 2f
|
||||||
|
} else {
|
||||||
|
(top + bottom) / 2f
|
||||||
|
}
|
||||||
|
|
||||||
|
val xPosition = startWidth + (x + dir * bulletRadius).toFloat()
|
||||||
|
|
||||||
|
if (canvas.isHardwareAccelerated) {
|
||||||
|
if (mBulletPath == null) {
|
||||||
|
mBulletPath = Path()
|
||||||
|
mBulletPath!!.addCircle(0.0f, 0.0f, bulletRadius.toFloat(), Direction.CW)
|
||||||
|
}
|
||||||
|
|
||||||
|
canvas.save()
|
||||||
|
canvas.translate(xPosition, yPosition)
|
||||||
|
canvas.drawPath(mBulletPath!!, paint)
|
||||||
|
canvas.restore()
|
||||||
|
} else {
|
||||||
|
canvas.drawCircle(xPosition, yPosition, bulletRadius.toFloat(), paint)
|
||||||
|
}
|
||||||
|
|
||||||
|
paint.style = style
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,45 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) Kuba Szczodrzyński 2020-3-17.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* https://github.com/davidbilik/bullet-span-sample/blob/master/app/src/main/java/cz/davidbilik/bulletsample/LiTagHandler.kt
|
||||||
|
*/
|
||||||
|
|
||||||
|
package pl.szczodrzynski.edziennik.utils.html
|
||||||
|
|
||||||
|
import android.text.Editable
|
||||||
|
import android.text.Html
|
||||||
|
import android.text.Spannable
|
||||||
|
import android.text.Spanned
|
||||||
|
import android.text.style.BulletSpan
|
||||||
|
import org.xml.sax.XMLReader
|
||||||
|
|
||||||
|
/**
|
||||||
|
* [Html.TagHandler] implementation that processes <ul> and <li> tags and creates bullets.
|
||||||
|
*
|
||||||
|
* Note: This class is only applied on SDK < 25 and processes only one-level list, nested lists do not work.
|
||||||
|
*/
|
||||||
|
class LiTagHandler : Html.TagHandler {
|
||||||
|
/**
|
||||||
|
* Helper marker class. Idea stolen from [Html.fromHtml] implementation
|
||||||
|
*/
|
||||||
|
class Bullet
|
||||||
|
|
||||||
|
override fun handleTag(opening: Boolean, tag: String, output: Editable, xmlReader: XMLReader) {
|
||||||
|
if (tag == "li" && opening) {
|
||||||
|
output.setSpan(Bullet(), output.length, output.length, Spannable.SPAN_INCLUSIVE_EXCLUSIVE)
|
||||||
|
}
|
||||||
|
if (tag == "li" && !opening) {
|
||||||
|
output.append("\n")
|
||||||
|
val lastMark = output.getSpans(0, output.length, Bullet::class.java).lastOrNull()
|
||||||
|
lastMark?.let {
|
||||||
|
val start = output.getSpanStart(it)
|
||||||
|
output.removeSpan(it)
|
||||||
|
if (start != output.length) {
|
||||||
|
output.setSpan(BulletSpan(), start, output.length, Spanned.SPAN_INCLUSIVE_EXCLUSIVE)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -53,6 +53,8 @@ class GradesManager(val app: App) : CoroutineScope {
|
|||||||
get() = app.config.forProfile().grades.plusValue
|
get() = app.config.forProfile().grades.plusValue
|
||||||
val minusValue
|
val minusValue
|
||||||
get() = app.config.forProfile().grades.minusValue
|
get() = app.config.forProfile().grades.minusValue
|
||||||
|
val dontCountEnabled
|
||||||
|
get() = app.config.forProfile().grades.dontCountEnabled
|
||||||
val dontCountGrades
|
val dontCountGrades
|
||||||
get() = app.config.forProfile().grades.dontCountGrades
|
get() = app.config.forProfile().grades.dontCountGrades
|
||||||
val hideImproved
|
val hideImproved
|
||||||
@ -100,8 +102,10 @@ class GradesManager(val app: App) : CoroutineScope {
|
|||||||
return grade.value
|
return grade.value
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getGradeWeight(dontCountGrades: List<String>, grade: Grade): Float {
|
fun getGradeWeight(dontCountEnabled: Boolean, dontCountGrades: List<String>, grade: Grade): Float {
|
||||||
if (grade.name.toLowerCase() in dontCountGrades)
|
if (!dontCountEnabled)
|
||||||
|
return grade.weight
|
||||||
|
if (grade.name.toLowerCase().trim() in dontCountGrades)
|
||||||
return 0f
|
return 0f
|
||||||
return grade.weight
|
return grade.weight
|
||||||
}
|
}
|
||||||
|
@ -1,150 +1,5 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<layout xmlns:android="http://schemas.android.com/apk/res/android"
|
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
android:id="@+id/feedbackFragment"
|
||||||
xmlns:tools="http://schemas.android.com/tools">
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent" />
|
||||||
<LinearLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:orientation="vertical"
|
|
||||||
tools:context=".ui.modules.feedback.FeedbackActivity">
|
|
||||||
|
|
||||||
<androidx.appcompat.widget.Toolbar
|
|
||||||
android:id="@+id/toolbar"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:background="?attr/colorSurface"
|
|
||||||
android:minHeight="?attr/actionBarSize" />
|
|
||||||
|
|
||||||
<com.google.android.material.textfield.TextInputLayout
|
|
||||||
android:id="@+id/targetDeviceLayout"
|
|
||||||
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_margin="8dp"
|
|
||||||
android:visibility="gone">
|
|
||||||
|
|
||||||
<pl.szczodrzynski.edziennik.utils.TextInputDropDown
|
|
||||||
android:id="@+id/targetDeviceDropDown"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:hint=""
|
|
||||||
android:paddingEnd="6.0dip"
|
|
||||||
android:paddingRight="6.0dip" />
|
|
||||||
</com.google.android.material.textfield.TextInputLayout>
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:orientation="vertical">
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/faqText"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginStart="8dp"
|
|
||||||
android:layout_marginLeft="8dp"
|
|
||||||
android:layout_marginTop="8dp"
|
|
||||||
android:layout_marginEnd="8dp"
|
|
||||||
android:layout_marginRight="8dp"
|
|
||||||
android:autoLink="all"
|
|
||||||
android:background="?selectableItemBackground"
|
|
||||||
android:text="@string/feedback_faq"
|
|
||||||
android:textSize="20sp" />
|
|
||||||
|
|
||||||
<com.google.android.material.button.MaterialButton
|
|
||||||
android:id="@+id/faqButton"
|
|
||||||
style="@style/Widget.MaterialComponents.Button"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_gravity="end"
|
|
||||||
android:layout_marginLeft="8dp"
|
|
||||||
android:layout_marginTop="8dp"
|
|
||||||
android:layout_marginRight="8dp"
|
|
||||||
android:text="@string/feedback_faq_button"
|
|
||||||
android:visibility="gone" />
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:id="@+id/inputLayout"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginTop="8dp"
|
|
||||||
android:orientation="vertical">
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginStart="8dp"
|
|
||||||
android:layout_marginLeft="8dp"
|
|
||||||
android:layout_marginTop="8dp"
|
|
||||||
android:layout_marginEnd="8dp"
|
|
||||||
android:layout_marginRight="8dp"
|
|
||||||
android:text="@string/feedback_title"
|
|
||||||
android:textSize="20sp" />
|
|
||||||
|
|
||||||
<com.google.android.material.textfield.TextInputLayout
|
|
||||||
style="@style/Widget.MaterialComponents.TextInputLayout.FilledBox"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginStart="8dp"
|
|
||||||
android:layout_marginLeft="8dp"
|
|
||||||
android:layout_marginTop="8dp"
|
|
||||||
android:layout_marginEnd="8dp"
|
|
||||||
android:layout_marginRight="8dp"
|
|
||||||
android:hint="@string/feedback_ask_a_question"
|
|
||||||
app:errorEnabled="true"
|
|
||||||
app:hintAnimationEnabled="true"
|
|
||||||
app:hintEnabled="true">
|
|
||||||
|
|
||||||
<com.google.android.material.textfield.TextInputEditText
|
|
||||||
android:id="@+id/textInput"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:ems="10"
|
|
||||||
android:inputType="textShortMessage|textMultiLine|textPersonName|textCapSentences|textAutoComplete"
|
|
||||||
android:maxLines="5"
|
|
||||||
android:singleLine="false" />
|
|
||||||
</com.google.android.material.textfield.TextInputLayout>
|
|
||||||
|
|
||||||
<com.google.android.material.button.MaterialButton
|
|
||||||
android:id="@+id/sendButton"
|
|
||||||
style="@style/Widget.MaterialComponents.Button"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_gravity="end"
|
|
||||||
android:layout_marginTop="8dp"
|
|
||||||
android:layout_marginEnd="8dp"
|
|
||||||
android:layout_marginRight="8dp"
|
|
||||||
android:text="@string/feedback_send" />
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:id="@+id/chatLayout"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:layout_marginTop="8dp"
|
|
||||||
android:layout_marginBottom="8dp"
|
|
||||||
android:orientation="vertical"
|
|
||||||
android:visibility="visible">
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginLeft="8dp"
|
|
||||||
android:layout_marginRight="8dp"
|
|
||||||
android:text="@string/feedback_history"
|
|
||||||
android:textSize="20sp" />
|
|
||||||
|
|
||||||
<com.github.bassaer.chatmessageview.view.ChatView
|
|
||||||
android:id="@+id/chat_view"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:layout_marginTop="8dp"
|
|
||||||
android:layout_marginBottom="8dp" />
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
</layout>
|
|
||||||
|
@ -84,16 +84,32 @@
|
|||||||
android:background="@drawable/divider"/>
|
android:background="@drawable/divider"/>
|
||||||
|
|
||||||
<CheckBox
|
<CheckBox
|
||||||
android:id="@+id/dontCountZeroToAverage"
|
android:id="@+id/dontCountGrades"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:minHeight="0dp"
|
android:minHeight="0dp"
|
||||||
android:text="@string/settings_register_dont_count_zero_text"/>
|
android:text="@string/grades_config_dont_count_grades"/>
|
||||||
|
|
||||||
|
<com.google.android.material.textfield.TextInputLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox.Dense"
|
||||||
|
android:hint="@string/grades_config_dont_count_hint"
|
||||||
|
app:placeholderText="@string/grades_config_dont_count_placeholder"
|
||||||
|
android:enabled="@{dontCountGrades.checked}">
|
||||||
|
|
||||||
|
<pl.szczodrzynski.edziennik.utils.TextInputKeyboardEdit
|
||||||
|
android:id="@+id/dontCountGradesText"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
tools:text="nb, 0, +, -, bz"/>
|
||||||
|
</com.google.android.material.textfield.TextInputLayout>
|
||||||
|
|
||||||
<CheckBox
|
<CheckBox
|
||||||
android:id="@+id/hideImproved"
|
android:id="@+id/hideImproved"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
android:minHeight="32dp"
|
android:minHeight="32dp"
|
||||||
android:text="@string/grades_config_dont_show_improved"/>
|
android:text="@string/grades_config_dont_show_improved"/>
|
||||||
|
|
||||||
|
@ -141,12 +141,14 @@
|
|||||||
android:layout_marginTop="4dp"
|
android:layout_marginTop="4dp"
|
||||||
android:textAppearance="@style/NavView.TextView.Helper"
|
android:textAppearance="@style/NavView.TextView.Helper"
|
||||||
android:text="@string/dialog_event_details_topic"/>
|
android:text="@string/dialog_event_details_topic"/>
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
|
android:id="@+id/topic"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:text="@{event.topic}"
|
android:text="@{event.topic}"
|
||||||
android:textIsSelectable="true"
|
|
||||||
android:textAppearance="@style/NavView.TextView.Medium"
|
android:textAppearance="@style/NavView.TextView.Medium"
|
||||||
|
android:textIsSelectable="true"
|
||||||
tools:text="Rozdział II: Panowanie Piastów i Jagiellonów.Przeniesiony z 11 grudnia." />
|
tools:text="Rozdział II: Panowanie Piastów i Jagiellonów.Przeniesiony z 11 grudnia." />
|
||||||
|
|
||||||
<View
|
<View
|
||||||
|
@ -8,6 +8,7 @@
|
|||||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||||
|
|
||||||
<ScrollView
|
<ScrollView
|
||||||
|
android:id="@+id/scrollView"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent">
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
@ -43,6 +44,8 @@
|
|||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:enabled="false"
|
android:enabled="false"
|
||||||
|
android:focusable="true"
|
||||||
|
android:focusableInTouchMode="true"
|
||||||
tools:text="8:10 - język polski"/>
|
tools:text="8:10 - język polski"/>
|
||||||
</com.google.android.material.textfield.TextInputLayout>
|
</com.google.android.material.textfield.TextInputLayout>
|
||||||
|
|
||||||
@ -58,6 +61,8 @@
|
|||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:enabled="false"
|
android:enabled="false"
|
||||||
|
android:focusable="true"
|
||||||
|
android:focusableInTouchMode="true"
|
||||||
tools:text="2b3T" />
|
tools:text="2b3T" />
|
||||||
</com.google.android.material.textfield.TextInputLayout>
|
</com.google.android.material.textfield.TextInputLayout>
|
||||||
|
|
||||||
@ -94,6 +99,8 @@
|
|||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:enabled="false"
|
android:enabled="false"
|
||||||
|
android:focusable="true"
|
||||||
|
android:focusableInTouchMode="true"
|
||||||
tools:text="2b3T" />
|
tools:text="2b3T" />
|
||||||
</com.google.android.material.textfield.TextInputLayout>
|
</com.google.android.material.textfield.TextInputLayout>
|
||||||
|
|
||||||
@ -123,6 +130,8 @@
|
|||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:inputType="textLongMessage|textMultiLine|textImeMultiLine"
|
android:inputType="textLongMessage|textMultiLine|textImeMultiLine"
|
||||||
android:minLines="2"
|
android:minLines="2"
|
||||||
|
android:focusable="true"
|
||||||
|
android:focusableInTouchMode="true"
|
||||||
tools:text="2b3T" />
|
tools:text="2b3T" />
|
||||||
</com.google.android.material.textfield.TextInputLayout>
|
</com.google.android.material.textfield.TextInputLayout>
|
||||||
|
|
||||||
|
@ -3,18 +3,70 @@
|
|||||||
~ Copyright (c) Kuba Szczodrzyński 2019-11-23.
|
~ Copyright (c) Kuba Szczodrzyński 2019-11-23.
|
||||||
-->
|
-->
|
||||||
|
|
||||||
<layout xmlns:tools="http://schemas.android.com/tools"
|
<layout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android">
|
xmlns:tools="http://schemas.android.com/tools">
|
||||||
|
|
||||||
<pl.szczodrzynski.edziennik.utils.SwipeRefreshLayoutNoIndicator
|
<pl.szczodrzynski.edziennik.utils.SwipeRefreshLayoutNoIndicator
|
||||||
android:id="@+id/refreshLayout"
|
android:id="@+id/refreshLayout"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent">
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
<androidx.recyclerview.widget.RecyclerView
|
<androidx.core.widget.NestedScrollView
|
||||||
android:id="@+id/list"
|
android:id="@+id/scrollView"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent">
|
||||||
tools:listitem="@layout/card_home" />
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
|
android:id="@+id/list"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
tools:itemCount="1"
|
||||||
|
tools:listitem="@layout/card_home_timetable" />
|
||||||
|
|
||||||
|
<View
|
||||||
|
android:id="@+id/configHintDivider"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="1dp"
|
||||||
|
android:layout_marginVertical="8dp"
|
||||||
|
android:background="@drawable/divider" />
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/configHint"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:layout_marginBottom="16dp"
|
||||||
|
android:orientation="horizontal">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginHorizontal="8dp"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:text="@string/home_configure_notice"
|
||||||
|
android:textAppearance="@style/NavView.TextView.Helper"
|
||||||
|
android:textSize="12sp"
|
||||||
|
android:textStyle="italic" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/configureCards"
|
||||||
|
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginEnd="8dp"
|
||||||
|
android:layout_marginRight="8dp"
|
||||||
|
android:minHeight="0dp"
|
||||||
|
android:text="@string/home_configure_add_remove" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</androidx.core.widget.NestedScrollView>
|
||||||
|
|
||||||
</pl.szczodrzynski.edziennik.utils.SwipeRefreshLayoutNoIndicator>
|
</pl.szczodrzynski.edziennik.utils.SwipeRefreshLayoutNoIndicator>
|
||||||
</layout>
|
</layout>
|
||||||
|
@ -67,24 +67,48 @@
|
|||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="16dp" />
|
android:layout_height="16dp" />
|
||||||
|
|
||||||
<com.google.android.material.textfield.TextInputLayout
|
<LinearLayout
|
||||||
android:id="@+id/login_code_layout"
|
|
||||||
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
|
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="match_parent"
|
||||||
android:layout_marginLeft="16dp"
|
android:orientation="horizontal">
|
||||||
android:layout_marginRight="16dp"
|
|
||||||
android:hint="@string/login_hint_token"
|
|
||||||
app:errorEnabled="true"
|
|
||||||
app:hintAnimationEnabled="true"
|
|
||||||
app:hintEnabled="true">
|
|
||||||
|
|
||||||
<com.google.android.material.textfield.TextInputEditText
|
<com.google.android.material.textfield.TextInputLayout
|
||||||
android:id="@+id/login_code"
|
android:id="@+id/login_code_layout"
|
||||||
|
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:inputType="textNoSuggestions|textEmailAddress" />
|
android:layout_marginLeft="16dp"
|
||||||
</com.google.android.material.textfield.TextInputLayout>
|
android:layout_weight="1"
|
||||||
|
android:hint="@string/login_hint_token"
|
||||||
|
app:errorEnabled="true"
|
||||||
|
app:hintAnimationEnabled="true"
|
||||||
|
app:hintEnabled="true">
|
||||||
|
|
||||||
|
<com.google.android.material.textfield.TextInputEditText
|
||||||
|
android:id="@+id/login_code"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:inputType="textNoSuggestions|textEmailAddress" />
|
||||||
|
</com.google.android.material.textfield.TextInputLayout>
|
||||||
|
|
||||||
|
<ImageButton
|
||||||
|
android:id="@+id/loginQrScan"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_marginTop="4dp"
|
||||||
|
android:layout_marginBottom="16dp"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:background="@android:color/transparent"
|
||||||
|
android:padding="4dp"
|
||||||
|
android:layout_marginRight="16dp"
|
||||||
|
android:layout_marginLeft="8dp"
|
||||||
|
android:scaleType="centerInside"
|
||||||
|
tools:srcCompat="@tools:sample/avatars"
|
||||||
|
android:layout_marginStart="8dp"
|
||||||
|
android:layout_marginEnd="16dp"
|
||||||
|
android:contentDescription="@string/login_vulcan_qr" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
<com.google.android.material.textfield.TextInputLayout
|
<com.google.android.material.textfield.TextInputLayout
|
||||||
android:id="@+id/loginPinLayout"
|
android:id="@+id/loginPinLayout"
|
||||||
@ -167,4 +191,4 @@
|
|||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||||
</layout>
|
</layout>
|
||||||
|
@ -315,7 +315,7 @@
|
|||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_margin="8dp"
|
android:layout_margin="8dp"
|
||||||
android:layout_weight="1"
|
android:layout_weight="1"
|
||||||
android:text="@string/grades_stats_custom_value_notice"
|
android:text="@string/grades_stats_custom_config_notice"
|
||||||
android:textAppearance="@style/NavView.TextView.Helper"
|
android:textAppearance="@style/NavView.TextView.Helper"
|
||||||
android:textSize="12sp"
|
android:textSize="12sp"
|
||||||
android:textStyle="italic" />
|
android:textStyle="italic" />
|
||||||
|
@ -141,7 +141,6 @@
|
|||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginTop="8dp"
|
android:layout_marginTop="8dp"
|
||||||
android:layout_marginBottom="8dp"
|
android:layout_marginBottom="8dp"
|
||||||
android:autoLink="all"
|
|
||||||
android:minHeight="250dp"
|
android:minHeight="250dp"
|
||||||
android:paddingLeft="16dp"
|
android:paddingLeft="16dp"
|
||||||
android:paddingRight="16dp"
|
android:paddingRight="16dp"
|
||||||
@ -359,4 +358,4 @@
|
|||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
</layout>
|
</layout>
|
||||||
|
@ -14,11 +14,9 @@
|
|||||||
android:layout_marginStart="8dp"
|
android:layout_marginStart="8dp"
|
||||||
android:layout_marginLeft="8dp"
|
android:layout_marginLeft="8dp"
|
||||||
android:background="@drawable/bg_rounded_8dp"
|
android:background="@drawable/bg_rounded_8dp"
|
||||||
android:fontFamily="serif-monospace"
|
|
||||||
android:gravity="center"
|
android:gravity="center"
|
||||||
android:textColor="@color/black"
|
android:textColor="@color/black"
|
||||||
android:textSize="24sp"
|
android:textSize="24sp"
|
||||||
android:textStyle="bold"
|
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toTopOf="parent"
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
@ -551,7 +551,7 @@
|
|||||||
<string name="settings_about_licenses_text">Open-source licenses</string>
|
<string name="settings_about_licenses_text">Open-source licenses</string>
|
||||||
<string name="settings_about_privacy_policy_text">Privacy policy</string>
|
<string name="settings_about_privacy_policy_text">Privacy policy</string>
|
||||||
<string name="settings_about_register_title_text">E-register</string>
|
<string name="settings_about_register_title_text">E-register</string>
|
||||||
<string name="settings_about_title_subtext">© Kuba Szczodrzyński && Kacper Ziubryniewicz\nSeptember 2018 - January 2020</string>
|
<string name="settings_about_title_subtext">© Kuba Szczodrzyński && Kacper Ziubryniewicz\nSeptember 2018 - March 2020</string>
|
||||||
<string name="settings_about_update_subtext">Click to check for updates</string>
|
<string name="settings_about_update_subtext">Click to check for updates</string>
|
||||||
<string name="settings_about_update_text">Update</string>
|
<string name="settings_about_update_text">Update</string>
|
||||||
<string name="settings_about_version_text">Version</string>
|
<string name="settings_about_version_text">Version</string>
|
||||||
@ -960,4 +960,6 @@
|
|||||||
<string name="other">Other</string>
|
<string name="other">Other</string>
|
||||||
<string name="menu_grades_config">Grades settings</string>
|
<string name="menu_grades_config">Grades settings</string>
|
||||||
<string name="dialog_day_lessons_info">%s - %s (%s lessons - %s hours %s minutes)</string>
|
<string name="dialog_day_lessons_info">%s - %s (%s lessons - %s hours %s minutes)</string>
|
||||||
|
<string name="event_sharing_text">Sharing the event…</string>
|
||||||
|
<string name="event_removing_text">Removing shared event…</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
@ -13,6 +13,9 @@
|
|||||||
|
|
||||||
<color name="dividerColor">#3e7f7f7f</color>
|
<color name="dividerColor">#3e7f7f7f</color>
|
||||||
|
|
||||||
|
<color name="mdtp_accent_color">#2196f3</color>
|
||||||
|
<color name="mdtp_accent_color_dark">#64b5f6</color>
|
||||||
|
|
||||||
|
|
||||||
<!-- LIGHT THEME -->
|
<!-- LIGHT THEME -->
|
||||||
<color name="windowBackgroundLight">#ffffffff</color>
|
<color name="windowBackgroundLight">#ffffffff</color>
|
||||||
|
@ -96,6 +96,8 @@
|
|||||||
<string name="error_183" translatable="false">ERROR_LIBRUS_API_NOTICEBOARD_PROBLEM</string>
|
<string name="error_183" translatable="false">ERROR_LIBRUS_API_NOTICEBOARD_PROBLEM</string>
|
||||||
<string name="error_184" translatable="false">ERROR_LOGIN_LIBRUS_PORTAL_CSRF_EXPIRED</string>
|
<string name="error_184" translatable="false">ERROR_LOGIN_LIBRUS_PORTAL_CSRF_EXPIRED</string>
|
||||||
<string name="error_185" translatable="false">ERROR_LIBRUS_API_DEVICE_REGISTERED</string>
|
<string name="error_185" translatable="false">ERROR_LIBRUS_API_DEVICE_REGISTERED</string>
|
||||||
|
<string name="error_186" translatable="false">ERROR_LIBRUS_MESSAGES_NOT_FOUND</string>
|
||||||
|
<string name="error_187" translatable="false">ERROR_LOGIN_LIBRUS_API_INVALID_REQUEST</string>
|
||||||
|
|
||||||
<string name="error_201" translatable="false">ERROR_LOGIN_MOBIDZIENNIK_WEB_INVALID_LOGIN</string>
|
<string name="error_201" translatable="false">ERROR_LOGIN_MOBIDZIENNIK_WEB_INVALID_LOGIN</string>
|
||||||
<string name="error_202" translatable="false">ERROR_LOGIN_MOBIDZIENNIK_WEB_OLD_PASSWORD</string>
|
<string name="error_202" translatable="false">ERROR_LOGIN_MOBIDZIENNIK_WEB_OLD_PASSWORD</string>
|
||||||
@ -110,6 +112,7 @@
|
|||||||
<string name="error_213" translatable="false">ERROR_MOBIDZIENNIK_WEB_NO_SERVER_ID</string>
|
<string name="error_213" translatable="false">ERROR_MOBIDZIENNIK_WEB_NO_SERVER_ID</string>
|
||||||
<string name="error_214" translatable="false">ERROR_MOBIDZIENNIK_WEB_INVALID_RESPONSE</string>
|
<string name="error_214" translatable="false">ERROR_MOBIDZIENNIK_WEB_INVALID_RESPONSE</string>
|
||||||
<string name="error_215" translatable="false">ERROR_LOGIN_MOBIDZIENNIK_WEB_NO_SESSION_ID</string>
|
<string name="error_215" translatable="false">ERROR_LOGIN_MOBIDZIENNIK_WEB_NO_SESSION_ID</string>
|
||||||
|
<string name="error_218" translatable="false">ERROR_MOBIDZIENNIK_WEB_SERVER_PROBLEM</string>
|
||||||
|
|
||||||
<string name="error_301" translatable="false">ERROR_LOGIN_VULCAN_INVALID_SYMBOL</string>
|
<string name="error_301" translatable="false">ERROR_LOGIN_VULCAN_INVALID_SYMBOL</string>
|
||||||
<string name="error_302" translatable="false">ERROR_LOGIN_VULCAN_INVALID_TOKEN</string>
|
<string name="error_302" translatable="false">ERROR_LOGIN_VULCAN_INVALID_TOKEN</string>
|
||||||
@ -180,20 +183,20 @@
|
|||||||
<string name="error_10_reason">Nie udało się wysłać wiadomości: nowa wiadomość nie została odnaleziona na liście wiadomości wysłanych</string>
|
<string name="error_10_reason">Nie udało się wysłać wiadomości: nowa wiadomość nie została odnaleziona na liście wiadomości wysłanych</string>
|
||||||
|
|
||||||
<string name="error_50_reason">Błąd odpowiedzi serwera</string>
|
<string name="error_50_reason">Błąd odpowiedzi serwera</string>
|
||||||
<string name="error_51_reason">Błąd serwera: nieprawidłowe zapytanie</string>
|
<string name="error_51_reason">Błąd serwera: nieprawidłowe zapytanie. Dziennik może być przeciążony lub mieć przerwę techniczną.</string>
|
||||||
<string name="error_52_reason">Błąd serwera: odmowa dostępu</string>
|
<string name="error_52_reason">Błąd serwera: odmowa dostępu</string>
|
||||||
<string name="error_53_reason">Błąd serwera: dostęp zabroniony</string>
|
<string name="error_53_reason">Błąd serwera: dostęp zabroniony</string>
|
||||||
<string name="error_54_reason">Błąd serwera: plik nie znaleziony</string>
|
<string name="error_54_reason">Błąd serwera: plik nie znaleziony</string>
|
||||||
<string name="error_55_reason">Błąd serwera: nieprawidłowa metoda zapytania</string>
|
<string name="error_55_reason">Błąd serwera: nieprawidłowa metoda zapytania. Dziennik może być przeciążony lub mieć przerwę techniczną.</string>
|
||||||
<string name="error_56_reason">Błąd serwera: odpowiedź niedostępna</string>
|
<string name="error_56_reason">Błąd serwera: odpowiedź niedostępna. Dziennik może być przeciążony lub mieć przerwę techniczną.</string>
|
||||||
<string name="error_57_reason">Błąd serwera: niespełnione zależności</string>
|
<string name="error_57_reason">Błąd serwera: niespełnione zależności. Dziennik może być przeciążony lub mieć przerwę techniczną.</string>
|
||||||
<string name="error_58_reason">Wewnętrzny błąd serwera</string>
|
<string name="error_58_reason">Wewnętrzny błąd serwera. Dziennik może być przeciążony lub mieć przerwę techniczną.</string>
|
||||||
<string name="error_59_reason">Dziennik jest tymczasowo niedostępny</string>
|
<string name="error_59_reason">Dziennik jest tymczasowo niedostępny</string>
|
||||||
<string name="error_60_reason">Brak internetu: nie znaleziono adresu serwera</string>
|
<string name="error_60_reason">Brak internetu: nie znaleziono adresu serwera</string>
|
||||||
<string name="error_61_reason">Brak internetu: przekroczono czas oczekiwania</string>
|
<string name="error_61_reason">Brak internetu: przekroczono czas oczekiwania. Dziennik może być przeciążony lub mieć przerwę techniczną.</string>
|
||||||
<string name="error_62_reason">Brak internetu</string>
|
<string name="error_62_reason">Brak internetu</string>
|
||||||
<string name="error_63_reason">Brak internetu: połączenie SSL nie powiodło się</string>
|
<string name="error_63_reason">Brak internetu: połączenie SSL nie powiodło się</string>
|
||||||
<string name="error_100_reason">Brak odpowiedzi serwera</string>
|
<string name="error_100_reason">Brak odpowiedzi serwera. Dziennik może być przeciążony lub mieć przerwę techniczną.</string>
|
||||||
<string name="error_101_reason">Dane logowania niekompletne</string>
|
<string name="error_101_reason">Dane logowania niekompletne</string>
|
||||||
<string name="error_102_reason">Nieprawidłowe dane logowania</string>
|
<string name="error_102_reason">Nieprawidłowe dane logowania</string>
|
||||||
<string name="error_105_reason">Profil nie został ustawiony</string>
|
<string name="error_105_reason">Profil nie został ustawiony</string>
|
||||||
@ -244,7 +247,7 @@
|
|||||||
<string name="error_159_reason">API Portalu Librus wyłączone</string>
|
<string name="error_159_reason">API Portalu Librus wyłączone</string>
|
||||||
<string name="error_160_reason">Konto Synergia zostało rozłączone</string>
|
<string name="error_160_reason">Konto Synergia zostało rozłączone</string>
|
||||||
<string name="error_161_reason">Inny błąd Portalu Librus</string>
|
<string name="error_161_reason">Inny błąd Portalu Librus</string>
|
||||||
<string name="error_162_reason">Nie znaleziono konta Synergia</string>
|
<string name="error_162_reason">Nie znaleziono konta Synergia. Zaloguj się na stronie portal.librus.pl, a następnie powiąż swoje konto Synergia do konta Librus Portal.</string>
|
||||||
<string name="error_163_reason">Inny błąd logowania do Portalu Librus</string>
|
<string name="error_163_reason">Inny błąd logowania do Portalu Librus</string>
|
||||||
<string name="error_164_reason">ERROR_LOGIN_LIBRUS_PORTAL_CODE_EXPIRED</string>
|
<string name="error_164_reason">ERROR_LOGIN_LIBRUS_PORTAL_CODE_EXPIRED</string>
|
||||||
<string name="error_165_reason">ERROR_LOGIN_LIBRUS_PORTAL_CODE_REVOKED</string>
|
<string name="error_165_reason">ERROR_LOGIN_LIBRUS_PORTAL_CODE_REVOKED</string>
|
||||||
@ -259,8 +262,8 @@
|
|||||||
<string name="error_174_reason">ERROR_LIBRUS_SYNERGIA_OTHER</string>
|
<string name="error_174_reason">ERROR_LIBRUS_SYNERGIA_OTHER</string>
|
||||||
<string name="error_175_reason">Librus Synergia: przerwa techniczna</string>
|
<string name="error_175_reason">Librus Synergia: przerwa techniczna</string>
|
||||||
<string name="error_176_reason">Librus Wiadomości: przerwa techniczna</string>
|
<string name="error_176_reason">Librus Wiadomości: przerwa techniczna</string>
|
||||||
<string name="error_177_reason">ERROR_LIBRUS_MESSAGES_ERROR</string>
|
<string name="error_177_reason">Librus Wiadomości: serwer zwrócił błąd. Prześlij zgłoszenie błędu.</string>
|
||||||
<string name="error_178_reason">ERROR_LIBRUS_MESSAGES_OTHER</string>
|
<string name="error_178_reason">Librus Wiadomości: serwer zwrócił nieznany błąd. Prześlij zgłoszenie błędu.</string>
|
||||||
<string name="error_179_reason">Librus Wiadomości: nieprawidłowe dane logowania</string>
|
<string name="error_179_reason">Librus Wiadomości: nieprawidłowe dane logowania</string>
|
||||||
<string name="error_180_reason">Librus Portal: nieprawidłowe dane logowania</string>
|
<string name="error_180_reason">Librus Portal: nieprawidłowe dane logowania</string>
|
||||||
<string name="error_181_reason">Librus API: przerwa techniczna</string>
|
<string name="error_181_reason">Librus API: przerwa techniczna</string>
|
||||||
@ -268,6 +271,8 @@
|
|||||||
<string name="error_183_reason">Wystąpił problem z tablicą ogłoszeń</string>
|
<string name="error_183_reason">Wystąpił problem z tablicą ogłoszeń</string>
|
||||||
<string name="error_184_reason">Librus: Sesja logowania wygasła. Zaloguj się ponownie.</string>
|
<string name="error_184_reason">Librus: Sesja logowania wygasła. Zaloguj się ponownie.</string>
|
||||||
<string name="error_185_reason">Urządzenie jest już zarejestrowane</string>
|
<string name="error_185_reason">Urządzenie jest już zarejestrowane</string>
|
||||||
|
<string name="error_186_reason">Nie znaleziono wiadomości. Mogła zostać usunięta.</string>
|
||||||
|
<string name="error_187_reason">Nieprawidłowe dane dostępu. Sprawdź poprawność wprowadzonych danych.</string>
|
||||||
|
|
||||||
<string name="error_201_reason">Nieprawidłowy login lub hasło</string>
|
<string name="error_201_reason">Nieprawidłowy login lub hasło</string>
|
||||||
<string name="error_202_reason">Podano stare hasło</string>
|
<string name="error_202_reason">Podano stare hasło</string>
|
||||||
@ -282,6 +287,7 @@
|
|||||||
<string name="error_213_reason">MobiDziennik: brak identyfikatora serwera</string>
|
<string name="error_213_reason">MobiDziennik: brak identyfikatora serwera</string>
|
||||||
<string name="error_214_reason">MobiDziennik: błąd odpowiedzi serwera</string>
|
<string name="error_214_reason">MobiDziennik: błąd odpowiedzi serwera</string>
|
||||||
<string name="error_215_reason">Brak identyfikatora sesji przy logowaniu</string>
|
<string name="error_215_reason">Brak identyfikatora sesji przy logowaniu</string>
|
||||||
|
<string name="error_218_reason">MobiDziennik: problemy z wydajnością serwerów. Spróbuj ponownie później.</string>
|
||||||
|
|
||||||
<string name="error_301_reason">Nieprawidłowy symbol</string>
|
<string name="error_301_reason">Nieprawidłowy symbol</string>
|
||||||
<string name="error_302_reason">Nieprawidłowy token</string>
|
<string name="error_302_reason">Nieprawidłowy token</string>
|
||||||
@ -332,7 +338,7 @@
|
|||||||
<string name="error_901_reason">EXCEPTION_LOGIN_LIBRUS_API_TOKEN</string>
|
<string name="error_901_reason">EXCEPTION_LOGIN_LIBRUS_API_TOKEN</string>
|
||||||
<string name="error_902_reason">EXCEPTION_LOGIN_LIBRUS_PORTAL_TOKEN</string>
|
<string name="error_902_reason">EXCEPTION_LOGIN_LIBRUS_PORTAL_TOKEN</string>
|
||||||
<string name="error_903_reason">EXCEPTION_LIBRUS_PORTAL_SYNERGIA_TOKEN</string>
|
<string name="error_903_reason">EXCEPTION_LIBRUS_PORTAL_SYNERGIA_TOKEN</string>
|
||||||
<string name="error_904_reason">EXCEPTION_LIBRUS_API_REQUEST</string>
|
<string name="error_904_reason">Zgłoś błąd: wyjątek w API Librus</string>
|
||||||
<string name="error_905_reason">EXCEPTION_LIBRUS_SYNERGIA_REQUEST</string>
|
<string name="error_905_reason">EXCEPTION_LIBRUS_SYNERGIA_REQUEST</string>
|
||||||
<string name="error_906_reason">EXCEPTION_MOBIDZIENNIK_WEB_REQUEST</string>
|
<string name="error_906_reason">EXCEPTION_MOBIDZIENNIK_WEB_REQUEST</string>
|
||||||
<string name="error_907_reason">EXCEPTION_VULCAN_API_REQUEST</string>
|
<string name="error_907_reason">EXCEPTION_VULCAN_API_REQUEST</string>
|
||||||
|
@ -6,4 +6,5 @@
|
|||||||
<resources>
|
<resources>
|
||||||
<item name="move_card_down_action" type="id"/>
|
<item name="move_card_down_action" type="id"/>
|
||||||
<item name="move_card_up_action" type="id"/>
|
<item name="move_card_up_action" type="id"/>
|
||||||
|
<item name="bettermovementmethod_highlight_background_span" type="id" />
|
||||||
</resources>
|
</resources>
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user