mirror of
https://github.com/szkolny-eu/szkolny-android.git
synced 2025-06-17 15:52:43 +02:00
Compare commits
33 Commits
v4.0-beta.
...
v4.0-rc.1
Author | SHA1 | Date | |
---|---|---|---|
a983af6c28 | |||
114c841f0c | |||
e271048577 | |||
b8c5925e82 | |||
9bda6c8869 | |||
d1608d308c | |||
b8e1e1d33a | |||
8099a037e7 | |||
af23c932a6 | |||
4edabbb186 | |||
37a5bea79b | |||
40cdc7d713 | |||
49825aca48 | |||
1d57c4e705 | |||
87ae5787ee | |||
20f16c25a3 | |||
6f1ec79d9b | |||
18c7eea89c | |||
f73060aeb6 | |||
2f653b83b6 | |||
445dec907d | |||
927316d24b | |||
3957453ed6 | |||
0296c704cb | |||
1e7fe972de | |||
c95bc656ea | |||
a082d95b04 | |||
6866dd4801 | |||
2186da416e | |||
22d859fcde | |||
39514b69b3 | |||
c384736840 | |||
507657f273 |
@ -128,6 +128,7 @@ dependencies {
|
|||||||
implementation "com.mikepenz:iconics-core:${versions.iconics}"
|
implementation "com.mikepenz:iconics-core:${versions.iconics}"
|
||||||
implementation "com.mikepenz:iconics-views:${versions.iconics}"
|
implementation "com.mikepenz:iconics-views:${versions.iconics}"
|
||||||
implementation "com.mikepenz:community-material-typeface:${versions.font_cmd}@aar"
|
implementation "com.mikepenz:community-material-typeface:${versions.font_cmd}@aar"
|
||||||
|
implementation "com.mikepenz:materialize:1.2.1"
|
||||||
|
|
||||||
implementation "com.github.kuba2k2:NavLib:${versions.navlib}"
|
implementation "com.github.kuba2k2:NavLib:${versions.navlib}"
|
||||||
|
|
||||||
|
@ -31,6 +31,12 @@
|
|||||||
-keepnames class pl.szczodrzynski.edziennik.ui.widgets.notifications.WidgetNotificationsProvider
|
-keepnames class pl.szczodrzynski.edziennik.ui.widgets.notifications.WidgetNotificationsProvider
|
||||||
-keepnames class pl.szczodrzynski.edziennik.ui.widgets.luckynumber.WidgetLuckyNumberProvider
|
-keepnames class pl.szczodrzynski.edziennik.ui.widgets.luckynumber.WidgetLuckyNumberProvider
|
||||||
|
|
||||||
|
-keepnames class androidx.appcompat.view.menu.MenuBuilder { setHeaderTitleInt(java.lang.CharSequence); }
|
||||||
|
-keepclassmembernames class androidx.appcompat.view.menu.StandardMenuPopup { private *; }
|
||||||
|
-keepnames class androidx.appcompat.view.menu.MenuPopupHelper { showPopup(int, int, boolean, boolean); }
|
||||||
|
|
||||||
|
-keepclassmembernames class com.mikepenz.materialdrawer.widget.MiniDrawerSliderView { private *; }
|
||||||
|
|
||||||
-keep class .R
|
-keep class .R
|
||||||
-keep class **.R$* {
|
-keep class **.R$* {
|
||||||
<fields>;
|
<fields>;
|
||||||
|
@ -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,12 +1,13 @@
|
|||||||
<h3>Wersja 4.0-beta.13, 2020-03-15</h3>
|
<h3>Wersja 4.0-rc.1, 2020-03-26</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><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>
|
||||||
|
<li><b>Przebudowaliśmy cały moduł synchronizacji</b>, co oznacza większą stabilność aplikacji, szybkość oraz poprawność pobieranych danych</li>
|
||||||
<li>Udoskonalony wygląd Szkolnego - sprawi, że korzystanie z aplikacji będzie jeszcze przyjemniejsze</li>
|
<li>Udoskonalony wygląd Szkolnego - sprawi, że korzystanie z aplikacji będzie jeszcze przyjemniejsze</li>
|
||||||
<li>Nowa <b>Strona główna</b> - ładniejszy wygląd oraz możliwość przestawiania kart na każdym profilu</li>
|
<li>Nowa <b>Strona główna</b> - ładniejszy wygląd oraz możliwość przestawiania kart na każdym profilu</li>
|
||||||
<li>Nowy <b>Plan lekcji</b> - z doskonałą obsługą lekcji przesuniętych oraz dwóch lekcji o tej samej godzinie</li>
|
<li>Nowy <b>Plan lekcji</b> - z doskonałą obsługą lekcji przesuniętych oraz dwóch lekcji o tej samej godzinie</li>
|
||||||
<li>Nowe <b>Oceny</b> - z możliwością zmiany wartości plusów oraz minusów oraz wyłączenia niektórych ocen ze średniej</li>
|
<li>Nowe <b>Oceny</b> - z możliwością zmiany wartości plusów oraz minusów oraz wyłączenia niektórych ocen ze średniej</li>
|
||||||
<li>Opcja wyłączenia wybranych powiadomień z aplikacji</li>
|
<li>Opcja wyłączenia wybranych powiadomień z aplikacji</li>
|
||||||
|
<li>Znaczki nieprzeczytanych informacji na obrazkach profili.</li>
|
||||||
<br>
|
<br>
|
||||||
<br>
|
<br>
|
||||||
<li>Nowe okienka informacji o wydarzeniach oraz lekcjach</li>
|
<li>Nowe okienka informacji o wydarzeniach oraz lekcjach</li>
|
||||||
@ -21,18 +22,8 @@
|
|||||||
<li>Poprawiliśmy synchronizację w tle na niektórych telefonach</li>
|
<li>Poprawiliśmy synchronizację w tle na niektórych telefonach</li>
|
||||||
<li>Usunąłem denerwujący brak zaznaczenia w lewym menu</li>
|
<li>Usunąłem denerwujący brak zaznaczenia w lewym menu</li>
|
||||||
<li>Znaczna ilość błędów z poprzednich wersji już nie występuje</li>
|
<li>Znaczna ilość błędów z poprzednich wersji już nie występuje</li>
|
||||||
<li><strike>Występują natomiast nowe błędy, dlatego proszę o ich zgłaszanie :)</strike></li>
|
|
||||||
</ul>
|
</ul>
|
||||||
<br>
|
<br>
|
||||||
<br>
|
<br>
|
||||||
<br>
|
|
||||||
<b>Uwaga.</b> Ponieważ to wersja <i>beta</i>, niektóre funkcje mogą nie działać prawidłowo.<br>
|
|
||||||
Staramy się usuwać takie przypadki, jednak na chwilę obecną mogą występować błędy w:
|
|
||||||
<ul>
|
|
||||||
<li>Wysyłanie wiadomości może nie działać w pełni prawidłowo - proszę o zgłaszanie wszystkich błędów na naszym serwerze Discord</li>
|
|
||||||
</ul>
|
|
||||||
<br>
|
|
||||||
<br>
|
|
||||||
<br>
|
|
||||||
Dzięki za korzystanie ze Szkolnego!<br>
|
Dzięki za korzystanie ze Szkolnego!<br>
|
||||||
<i>© Kuba Szczodrzyński, Kacper Ziubryniewicz 2020</i>
|
<i>© Kuba Szczodrzyński, Kacper Ziubryniewicz 2020</i>
|
||||||
|
@ -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] = {
|
||||||
0xc1, 0x4d, 0xcc, 0x2e, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff };
|
0x53, 0xfb, 0x18, 0x06, 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);
|
||||||
|
|
||||||
|
@ -28,9 +28,6 @@ import com.hypertrack.hyperlog.HyperLog
|
|||||||
import com.mikepenz.iconics.Iconics
|
import com.mikepenz.iconics.Iconics
|
||||||
import com.mikepenz.iconics.typeface.library.szkolny.font.SzkolnyFont
|
import com.mikepenz.iconics.typeface.library.szkolny.font.SzkolnyFont
|
||||||
import im.wangchao.mhttp.MHttp
|
import im.wangchao.mhttp.MHttp
|
||||||
import im.wangchao.mhttp.internal.cookie.PersistentCookieJar
|
|
||||||
import im.wangchao.mhttp.internal.cookie.cache.SetCookieCache
|
|
||||||
import im.wangchao.mhttp.internal.cookie.persistence.SharedPrefsCookiePersistor
|
|
||||||
import kotlinx.coroutines.*
|
import kotlinx.coroutines.*
|
||||||
import me.leolin.shortcutbadger.ShortcutBadger
|
import me.leolin.shortcutbadger.ShortcutBadger
|
||||||
import okhttp3.OkHttpClient
|
import okhttp3.OkHttpClient
|
||||||
@ -41,6 +38,7 @@ import pl.szczodrzynski.edziennik.data.api.szkolny.interceptor.Signing
|
|||||||
import pl.szczodrzynski.edziennik.data.db.AppDb
|
import pl.szczodrzynski.edziennik.data.db.AppDb
|
||||||
import pl.szczodrzynski.edziennik.data.db.entity.Profile
|
import pl.szczodrzynski.edziennik.data.db.entity.Profile
|
||||||
import pl.szczodrzynski.edziennik.network.NetworkUtils
|
import pl.szczodrzynski.edziennik.network.NetworkUtils
|
||||||
|
import pl.szczodrzynski.edziennik.network.cookie.DumbCookieJar
|
||||||
import pl.szczodrzynski.edziennik.sync.SyncWorker
|
import pl.szczodrzynski.edziennik.sync.SyncWorker
|
||||||
import pl.szczodrzynski.edziennik.sync.UpdateWorker
|
import pl.szczodrzynski.edziennik.sync.UpdateWorker
|
||||||
import pl.szczodrzynski.edziennik.ui.modules.base.CrashActivity
|
import pl.szczodrzynski.edziennik.ui.modules.base.CrashActivity
|
||||||
@ -125,7 +123,7 @@ class App : MultiDexApplication(), Configuration.Provider, CoroutineScope {
|
|||||||
.followSslRedirects(false)
|
.followSslRedirects(false)
|
||||||
.build()
|
.build()
|
||||||
}
|
}
|
||||||
val cookieJar by lazy { PersistentCookieJar(SetCookieCache(), SharedPrefsCookiePersistor(this)) }
|
val cookieJar by lazy { DumbCookieJar(this) }
|
||||||
|
|
||||||
/* _____ _ _
|
/* _____ _ _
|
||||||
/ ____(_) | |
|
/ ____(_) | |
|
||||||
|
@ -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
|
||||||
@ -1094,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
|
||||||
|
}
|
||||||
|
@ -31,8 +31,8 @@ import com.mikepenz.iconics.typeface.library.szkolny.font.SzkolnyFont
|
|||||||
import com.mikepenz.materialdrawer.model.DividerDrawerItem
|
import com.mikepenz.materialdrawer.model.DividerDrawerItem
|
||||||
import com.mikepenz.materialdrawer.model.ProfileDrawerItem
|
import com.mikepenz.materialdrawer.model.ProfileDrawerItem
|
||||||
import com.mikepenz.materialdrawer.model.ProfileSettingDrawerItem
|
import com.mikepenz.materialdrawer.model.ProfileSettingDrawerItem
|
||||||
import com.mikepenz.materialdrawer.model.interfaces.IDrawerItem
|
import com.mikepenz.materialdrawer.model.interfaces.*
|
||||||
import com.mikepenz.materialdrawer.model.interfaces.IProfile
|
import com.mikepenz.materialdrawer.model.utils.withIsHiddenInMiniDrawer
|
||||||
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
|
||||||
@ -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
|
||||||
@ -89,7 +90,6 @@ import pl.szczodrzynski.navlib.bottomsheet.items.BottomSheetPrimaryItem
|
|||||||
import pl.szczodrzynski.navlib.bottomsheet.items.BottomSheetSeparatorItem
|
import pl.szczodrzynski.navlib.bottomsheet.items.BottomSheetSeparatorItem
|
||||||
import pl.szczodrzynski.navlib.drawer.NavDrawer
|
import pl.szczodrzynski.navlib.drawer.NavDrawer
|
||||||
import pl.szczodrzynski.navlib.drawer.items.DrawerPrimaryItem
|
import pl.szczodrzynski.navlib.drawer.items.DrawerPrimaryItem
|
||||||
import pl.szczodrzynski.navlib.drawer.items.withAppTitle
|
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import java.util.*
|
import java.util.*
|
||||||
@ -360,7 +360,7 @@ class MainActivity : AppCompatActivity(), CoroutineScope {
|
|||||||
false
|
false
|
||||||
}
|
}
|
||||||
drawerProfileLongClickListener = { _, profile, _, view ->
|
drawerProfileLongClickListener = { _, profile, _, view ->
|
||||||
if (profile is ProfileDrawerItem) {
|
if (view != null && profile is ProfileDrawerItem) {
|
||||||
showProfileContextMenu(profile, view)
|
showProfileContextMenu(profile, view)
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
@ -723,6 +723,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) {
|
||||||
@ -905,7 +914,8 @@ class MainActivity : AppCompatActivity(), CoroutineScope {
|
|||||||
bottomSheet.removeAllContextual()
|
bottomSheet.removeAllContextual()
|
||||||
bottomSheet.toggleGroupEnabled = false
|
bottomSheet.toggleGroupEnabled = false
|
||||||
drawer.close()
|
drawer.close()
|
||||||
drawer.setSelection(target.id, fireOnClick = false)
|
if (drawer.getSelection() != target.id)
|
||||||
|
drawer.setSelection(target.id, fireOnClick = false)
|
||||||
navView.toolbar.setTitle(target.title ?: target.name)
|
navView.toolbar.setTitle(target.title ?: target.name)
|
||||||
navView.bottomBar.fabEnable = false
|
navView.bottomBar.fabEnable = false
|
||||||
navView.bottomBar.fabExtended = false
|
navView.bottomBar.fabExtended = false
|
||||||
@ -1053,7 +1063,7 @@ class MainActivity : AppCompatActivity(), CoroutineScope {
|
|||||||
val item = DrawerPrimaryItem()
|
val item = DrawerPrimaryItem()
|
||||||
.withIdentifier(target.id.toLong())
|
.withIdentifier(target.id.toLong())
|
||||||
.withName(target.name)
|
.withName(target.name)
|
||||||
.withHiddenInMiniDrawer(!app.config.ui.miniMenuButtons.contains(target.id))
|
.withIsHiddenInMiniDrawer(!app.config.ui.miniMenuButtons.contains(target.id))
|
||||||
.also { if (target.description != null) it.withDescription(target.description!!) }
|
.also { if (target.description != null) it.withDescription(target.description!!) }
|
||||||
.also { if (target.icon != null) it.withIcon(target.icon!!) }
|
.also { if (target.icon != null) it.withIcon(target.icon!!) }
|
||||||
.also { if (target.title != null) it.withAppTitle(getString(target.title!!)) }
|
.also { if (target.title != null) it.withAppTitle(getString(target.title!!)) }
|
||||||
@ -1117,7 +1127,7 @@ class MainActivity : AppCompatActivity(), CoroutineScope {
|
|||||||
drawer.addProfileSettings(*drawerProfiles.toTypedArray())
|
drawer.addProfileSettings(*drawerProfiles.toTypedArray())
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun showProfileContextMenu(profile: IProfile<*>, view: View) {
|
private fun showProfileContextMenu(profile: IProfile, view: View) {
|
||||||
val profileId = profile.identifier.toInt()
|
val profileId = profile.identifier.toInt()
|
||||||
val popupMenu = PopupMenu(this, view)
|
val popupMenu = PopupMenu(this, view)
|
||||||
popupMenu.menu.add(0, 1, 1, R.string.profile_menu_open_settings)
|
popupMenu.menu.add(0, 1, 1, R.string.profile_menu_open_settings)
|
||||||
@ -1130,7 +1140,7 @@ class MainActivity : AppCompatActivity(), CoroutineScope {
|
|||||||
}
|
}
|
||||||
loadTarget(DRAWER_ITEM_SETTINGS, null)
|
loadTarget(DRAWER_ITEM_SETTINGS, null)
|
||||||
} else if (item.itemId == 2) {
|
} else if (item.itemId == 2) {
|
||||||
ProfileRemoveDialog(this, profileId, profile.name?.getText(this)?.toString() ?: "?")
|
ProfileRemoveDialog(this, profileId, profile.name?.getText(this) ?: "?")
|
||||||
}
|
}
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
@ -1141,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()
|
||||||
|
@ -142,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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,7 +7,6 @@ package pl.szczodrzynski.edziennik.data.api.edziennik.edudziennik.data
|
|||||||
import im.wangchao.mhttp.Request
|
import im.wangchao.mhttp.Request
|
||||||
import im.wangchao.mhttp.Response
|
import im.wangchao.mhttp.Response
|
||||||
import im.wangchao.mhttp.callback.TextCallbackHandler
|
import im.wangchao.mhttp.callback.TextCallbackHandler
|
||||||
import okhttp3.Cookie
|
|
||||||
import pl.szczodrzynski.edziennik.data.api.*
|
import pl.szczodrzynski.edziennik.data.api.*
|
||||||
import pl.szczodrzynski.edziennik.data.api.edziennik.edudziennik.DataEdudziennik
|
import pl.szczodrzynski.edziennik.data.api.edziennik.edudziennik.DataEdudziennik
|
||||||
import pl.szczodrzynski.edziennik.data.api.models.ApiError
|
import pl.szczodrzynski.edziennik.data.api.models.ApiError
|
||||||
@ -43,8 +42,8 @@ open class EdudziennikWeb(open val data: DataEdudziennik, open val lastSync: Lon
|
|||||||
|
|
||||||
if (semester == null && url.contains("start")) {
|
if (semester == null && url.contains("start")) {
|
||||||
profile?.also { profile ->
|
profile?.also { profile ->
|
||||||
val cookies = data.app.cookieJar.getForDomain("dziennikel.appspot.com")
|
val cookies = data.app.cookieJar.getAll("dziennikel.appspot.com")
|
||||||
val semesterCookie = cookies.firstOrNull { it.name() == "semester" }?.value()?.toIntOrNull()
|
val semesterCookie = cookies["semester"]?.toIntOrNull()
|
||||||
|
|
||||||
semesterCookie?.let { data.currentSemester = it }
|
semesterCookie?.let { data.currentSemester = it }
|
||||||
|
|
||||||
@ -75,13 +74,7 @@ open class EdudziennikWeb(open val data: DataEdudziennik, open val lastSync: Lon
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
data.app.cookieJar.saveFromResponse(null, listOf(
|
data.app.cookieJar.set("dziennikel.appspot.com", "sessionid", data.webSessionId)
|
||||||
Cookie.Builder()
|
|
||||||
.name("sessionid")
|
|
||||||
.value(data.webSessionId!!)
|
|
||||||
.domain("dziennikel.appspot.com")
|
|
||||||
.secure().httpOnly().build()
|
|
||||||
))
|
|
||||||
|
|
||||||
Request.builder()
|
Request.builder()
|
||||||
.url(url)
|
.url(url)
|
||||||
|
@ -24,7 +24,7 @@ class EdudziennikLoginWeb(val data: DataEdudziennik, val onSuccess: () -> Unit)
|
|||||||
onSuccess()
|
onSuccess()
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
data.app.cookieJar.clearForDomain("dziennikel.appspot.com")
|
data.app.cookieJar.clear("dziennikel.appspot.com")
|
||||||
if (data.loginEmail.isNotNullNorEmpty() && data.loginPassword.isNotNullNorEmpty()) {
|
if (data.loginEmail.isNotNullNorEmpty() && data.loginPassword.isNotNullNorEmpty()) {
|
||||||
loginWithCredentials()
|
loginWithCredentials()
|
||||||
}
|
}
|
||||||
@ -59,8 +59,8 @@ class EdudziennikLoginWeb(val data: DataEdudziennik, val onSuccess: () -> Unit)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val cookies = data.app.cookieJar.getForDomain("dziennikel.appspot.com")
|
val cookies = data.app.cookieJar.getAll("dziennikel.appspot.com")
|
||||||
val sessionId = cookies.firstOrNull { it.name() == "sessionid" }?.value()
|
val sessionId = cookies["sessionid"]
|
||||||
|
|
||||||
if (sessionId == null) {
|
if (sessionId == null) {
|
||||||
data.error(ApiError(TAG, ERROR_LOGIN_EDUDZIENNIK_WEB_NO_SESSION_ID)
|
data.error(ApiError(TAG, ERROR_LOGIN_EDUDZIENNIK_WEB_NO_SESSION_ID)
|
||||||
|
@ -5,7 +5,6 @@
|
|||||||
package pl.szczodrzynski.edziennik.data.api.edziennik.idziennik
|
package pl.szczodrzynski.edziennik.data.api.edziennik.idziennik
|
||||||
|
|
||||||
import androidx.core.util.set
|
import androidx.core.util.set
|
||||||
import okhttp3.Cookie
|
|
||||||
import pl.szczodrzynski.edziennik.*
|
import pl.szczodrzynski.edziennik.*
|
||||||
import pl.szczodrzynski.edziennik.data.api.LOGIN_METHOD_IDZIENNIK_API
|
import pl.szczodrzynski.edziennik.data.api.LOGIN_METHOD_IDZIENNIK_API
|
||||||
import pl.szczodrzynski.edziennik.data.api.LOGIN_METHOD_IDZIENNIK_WEB
|
import pl.szczodrzynski.edziennik.data.api.LOGIN_METHOD_IDZIENNIK_WEB
|
||||||
@ -24,18 +23,8 @@ class DataIdziennik(app: App, profile: Profile?, loginStore: LoginStore) : Data(
|
|||||||
loginMethods.clear()
|
loginMethods.clear()
|
||||||
if (isWebLoginValid()) {
|
if (isWebLoginValid()) {
|
||||||
loginMethods += LOGIN_METHOD_IDZIENNIK_WEB
|
loginMethods += LOGIN_METHOD_IDZIENNIK_WEB
|
||||||
app.cookieJar.saveFromResponse(null, listOf(
|
app.cookieJar.set("iuczniowie.progman.pl", "ASP.NET_SessionId_iDziennik", webSessionId)
|
||||||
Cookie.Builder()
|
app.cookieJar.set("iuczniowie.progman.pl", ".ASPXAUTH", webAuth)
|
||||||
.name("ASP.NET_SessionId_iDziennik")
|
|
||||||
.value(webSessionId!!)
|
|
||||||
.domain("iuczniowie.progman.pl")
|
|
||||||
.secure().httpOnly().build(),
|
|
||||||
Cookie.Builder()
|
|
||||||
.name(".ASPXAUTH")
|
|
||||||
.value(webAuth!!)
|
|
||||||
.domain("iuczniowie.progman.pl")
|
|
||||||
.secure().httpOnly().build()
|
|
||||||
))
|
|
||||||
}
|
}
|
||||||
if (isApiLoginValid())
|
if (isApiLoginValid())
|
||||||
loginMethods += LOGIN_METHOD_IDZIENNIK_API
|
loginMethods += LOGIN_METHOD_IDZIENNIK_API
|
||||||
|
@ -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,
|
||||||
|
@ -7,7 +7,6 @@ package pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.login
|
|||||||
import im.wangchao.mhttp.Request
|
import im.wangchao.mhttp.Request
|
||||||
import im.wangchao.mhttp.Response
|
import im.wangchao.mhttp.Response
|
||||||
import im.wangchao.mhttp.callback.TextCallbackHandler
|
import im.wangchao.mhttp.callback.TextCallbackHandler
|
||||||
import okhttp3.Cookie
|
|
||||||
import pl.szczodrzynski.edziennik.*
|
import pl.szczodrzynski.edziennik.*
|
||||||
import pl.szczodrzynski.edziennik.data.api.*
|
import pl.szczodrzynski.edziennik.data.api.*
|
||||||
import pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.DataIdziennik
|
import pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.DataIdziennik
|
||||||
@ -24,22 +23,12 @@ class IdziennikLoginWeb(val data: DataIdziennik, val onSuccess: () -> Unit) {
|
|||||||
|
|
||||||
init { run {
|
init { run {
|
||||||
if (data.isWebLoginValid()) {
|
if (data.isWebLoginValid()) {
|
||||||
data.app.cookieJar.saveFromResponse(null, listOf(
|
data.app.cookieJar.set("iuczniowie.progman.pl", "ASP.NET_SessionId_iDziennik", data.webSessionId)
|
||||||
Cookie.Builder()
|
data.app.cookieJar.set("iuczniowie.progman.pl", ".ASPXAUTH", data.webAuth)
|
||||||
.name("ASP.NET_SessionId_iDziennik")
|
|
||||||
.value(data.webSessionId!!)
|
|
||||||
.domain("iuczniowie.progman.pl")
|
|
||||||
.secure().httpOnly().build(),
|
|
||||||
Cookie.Builder()
|
|
||||||
.name(".ASPXAUTH")
|
|
||||||
.value(data.webAuth!!)
|
|
||||||
.domain("iuczniowie.progman.pl")
|
|
||||||
.secure().httpOnly().build()
|
|
||||||
))
|
|
||||||
onSuccess()
|
onSuccess()
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
data.app.cookieJar.clearForDomain("iuczniowie.progman.pl")
|
data.app.cookieJar.clear("iuczniowie.progman.pl")
|
||||||
if (data.webSchoolName != null && data.webUsername != null && data.webPassword != null) {
|
if (data.webSchoolName != null && data.webUsername != null && data.webPassword != null) {
|
||||||
loginWithCredentials()
|
loginWithCredentials()
|
||||||
}
|
}
|
||||||
@ -62,11 +51,11 @@ class IdziennikLoginWeb(val data: DataIdziennik, val onSuccess: () -> Unit) {
|
|||||||
|
|
||||||
// login succeeded: there is a start page
|
// login succeeded: there is a start page
|
||||||
if (text.contains("czyWyswietlicDostepMobilny")) {
|
if (text.contains("czyWyswietlicDostepMobilny")) {
|
||||||
val cookies = data.app.cookieJar.getForDomain("iuczniowie.progman.pl")
|
val cookies = data.app.cookieJar.getAll("iuczniowie.progman.pl")
|
||||||
run {
|
run {
|
||||||
data.webSessionId = cookies.singleOrNull { it.name() == "ASP.NET_SessionId_iDziennik" }?.value() ?: return@run ERROR_LOGIN_IDZIENNIK_WEB_NO_SESSION
|
data.webSessionId = cookies["ASP.NET_SessionId_iDziennik"] ?: return@run ERROR_LOGIN_IDZIENNIK_WEB_NO_SESSION
|
||||||
data.webAuth = cookies.singleOrNull { it.name() == ".ASPXAUTH" }?.value() ?: return@run ERROR_LOGIN_IDZIENNIK_WEB_NO_AUTH
|
data.webAuth = cookies[".ASPXAUTH"] ?: return@run ERROR_LOGIN_IDZIENNIK_WEB_NO_AUTH
|
||||||
data.apiBearer = cookies.singleOrNull { it.name() == "Bearer" }?.value() ?: return@run ERROR_LOGIN_IDZIENNIK_WEB_NO_BEARER
|
data.apiBearer = cookies["Bearer"]?: return@run ERROR_LOGIN_IDZIENNIK_WEB_NO_BEARER
|
||||||
data.loginExpiryTime = response.getUnixDate() + 30 * MINUTE /* after about 40 minutes the login didn't work already */
|
data.loginExpiryTime = response.getUnixDate() + 30 * MINUTE /* after about 40 minutes the login didn't work already */
|
||||||
data.apiExpiryTime = response.getUnixDate() + 12 * HOUR /* actually it expires after 24 hours but I'm not sure when does the token refresh. */
|
data.apiExpiryTime = response.getUnixDate() + 12 * HOUR /* actually it expires after 24 hours but I'm not sure when does the token refresh. */
|
||||||
|
|
||||||
|
@ -4,7 +4,6 @@
|
|||||||
|
|
||||||
package pl.szczodrzynski.edziennik.data.api.edziennik.librus
|
package pl.szczodrzynski.edziennik.data.api.edziennik.librus
|
||||||
|
|
||||||
import okhttp3.Cookie
|
|
||||||
import pl.szczodrzynski.edziennik.App
|
import pl.szczodrzynski.edziennik.App
|
||||||
import pl.szczodrzynski.edziennik.currentTimeUnix
|
import pl.szczodrzynski.edziennik.currentTimeUnix
|
||||||
import pl.szczodrzynski.edziennik.data.api.LOGIN_METHOD_LIBRUS_API
|
import pl.szczodrzynski.edziennik.data.api.LOGIN_METHOD_LIBRUS_API
|
||||||
@ -31,23 +30,11 @@ class DataLibrus(app: App, profile: Profile?, loginStore: LoginStore) : Data(app
|
|||||||
loginMethods += LOGIN_METHOD_LIBRUS_API
|
loginMethods += LOGIN_METHOD_LIBRUS_API
|
||||||
if (isSynergiaLoginValid()) {
|
if (isSynergiaLoginValid()) {
|
||||||
loginMethods += LOGIN_METHOD_LIBRUS_SYNERGIA
|
loginMethods += LOGIN_METHOD_LIBRUS_SYNERGIA
|
||||||
app.cookieJar.saveFromResponse(null, listOf(
|
app.cookieJar.set("synergia.librus.pl", "DZIENNIKSID", synergiaSessionId)
|
||||||
Cookie.Builder()
|
|
||||||
.name("DZIENNIKSID")
|
|
||||||
.value(synergiaSessionId!!)
|
|
||||||
.domain("synergia.librus.pl")
|
|
||||||
.secure().httpOnly().build()
|
|
||||||
))
|
|
||||||
}
|
}
|
||||||
if (isMessagesLoginValid()) {
|
if (isMessagesLoginValid()) {
|
||||||
loginMethods += LOGIN_METHOD_LIBRUS_MESSAGES
|
loginMethods += LOGIN_METHOD_LIBRUS_MESSAGES
|
||||||
app.cookieJar.saveFromResponse(null, listOf(
|
app.cookieJar.set("wiadomosci.librus.pl", "DZIENNIKSID", messagesSessionId)
|
||||||
Cookie.Builder()
|
|
||||||
.name("DZIENNIKSID")
|
|
||||||
.value(messagesSessionId!!)
|
|
||||||
.domain("wiadomosci.librus.pl")
|
|
||||||
.secure().httpOnly().build()
|
|
||||||
))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -64,7 +64,7 @@ val LibrusFeatures = listOf(
|
|||||||
Feature(LOGIN_TYPE_LIBRUS, FEATURE_PUSH_CONFIG, listOf(
|
Feature(LOGIN_TYPE_LIBRUS, FEATURE_PUSH_CONFIG, listOf(
|
||||||
ENDPOINT_LIBRUS_API_PUSH_CONFIG to LOGIN_METHOD_LIBRUS_API
|
ENDPOINT_LIBRUS_API_PUSH_CONFIG to LOGIN_METHOD_LIBRUS_API
|
||||||
), listOf(LOGIN_METHOD_LIBRUS_API)).withShouldSync { data ->
|
), listOf(LOGIN_METHOD_LIBRUS_API)).withShouldSync { data ->
|
||||||
!data.app.config.sync.tokenLibrusList.contains(data.profileId)
|
(data as DataLibrus).isPremium && !data.app.config.sync.tokenLibrusList.contains(data.profileId)
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
@ -116,11 +116,11 @@ val LibrusFeatures = listOf(
|
|||||||
* Homework - using API.
|
* Homework - using API.
|
||||||
* Sync only if account has premium access.
|
* Sync only if account has premium access.
|
||||||
*/
|
*/
|
||||||
Feature(LOGIN_TYPE_LIBRUS, FEATURE_HOMEWORK, listOf(
|
/*Feature(LOGIN_TYPE_LIBRUS, FEATURE_HOMEWORK, listOf(
|
||||||
ENDPOINT_LIBRUS_API_HOMEWORK to LOGIN_METHOD_LIBRUS_API
|
ENDPOINT_LIBRUS_API_HOMEWORK to LOGIN_METHOD_LIBRUS_API
|
||||||
), listOf(LOGIN_METHOD_LIBRUS_API)).withShouldSync { data ->
|
), listOf(LOGIN_METHOD_LIBRUS_API)).withShouldSync { data ->
|
||||||
(data as DataLibrus).isPremium
|
(data as DataLibrus).isPremium
|
||||||
},
|
},*/
|
||||||
/**
|
/**
|
||||||
* Behaviour - using API.
|
* Behaviour - using API.
|
||||||
*/
|
*/
|
||||||
@ -227,9 +227,9 @@ val LibrusFeatures = listOf(
|
|||||||
*/
|
*/
|
||||||
Feature(LOGIN_TYPE_LIBRUS, FEATURE_HOMEWORK, listOf(
|
Feature(LOGIN_TYPE_LIBRUS, FEATURE_HOMEWORK, listOf(
|
||||||
ENDPOINT_LIBRUS_SYNERGIA_HOMEWORK to LOGIN_METHOD_LIBRUS_SYNERGIA
|
ENDPOINT_LIBRUS_SYNERGIA_HOMEWORK to LOGIN_METHOD_LIBRUS_SYNERGIA
|
||||||
), listOf(LOGIN_METHOD_LIBRUS_SYNERGIA)).withShouldSync { data ->
|
), listOf(LOGIN_METHOD_LIBRUS_SYNERGIA))/*.withShouldSync { data ->
|
||||||
!(data as DataLibrus).isPremium
|
!(data as DataLibrus).isPremium
|
||||||
},
|
}*/,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Messages inbox - using messages website.
|
* Messages inbox - using messages website.
|
||||||
|
@ -12,7 +12,6 @@ import im.wangchao.mhttp.body.MediaTypeUtils
|
|||||||
import im.wangchao.mhttp.callback.FileCallbackHandler
|
import im.wangchao.mhttp.callback.FileCallbackHandler
|
||||||
import im.wangchao.mhttp.callback.JsonCallbackHandler
|
import im.wangchao.mhttp.callback.JsonCallbackHandler
|
||||||
import im.wangchao.mhttp.callback.TextCallbackHandler
|
import im.wangchao.mhttp.callback.TextCallbackHandler
|
||||||
import okhttp3.Cookie
|
|
||||||
import org.json.JSONObject
|
import org.json.JSONObject
|
||||||
import org.json.XML
|
import org.json.XML
|
||||||
import org.jsoup.Jsoup
|
import org.jsoup.Jsoup
|
||||||
@ -89,14 +88,7 @@ open class LibrusMessages(open val data: DataLibrus, open val lastSync: Long?) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
data.app.cookieJar.saveFromResponse(null, listOf(
|
data.app.cookieJar.set("wiadomosci.librus.pl", "DZIENNIKSID", data.messagesSessionId)
|
||||||
Cookie.Builder()
|
|
||||||
.name("DZIENNIKSID")
|
|
||||||
.value(data.messagesSessionId!!)
|
|
||||||
.domain("wiadomosci.librus.pl")
|
|
||||||
.secure().httpOnly().build()
|
|
||||||
))
|
|
||||||
|
|
||||||
|
|
||||||
val docBuilder = DocumentBuilderFactory.newInstance().newDocumentBuilder()
|
val docBuilder = DocumentBuilderFactory.newInstance().newDocumentBuilder()
|
||||||
val doc = docBuilder.newDocument()
|
val doc = docBuilder.newDocument()
|
||||||
@ -180,14 +172,7 @@ open class LibrusMessages(open val data: DataLibrus, open val lastSync: Long?) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
data.app.cookieJar.saveFromResponse(null, listOf(
|
data.app.cookieJar.set("wiadomosci.librus.pl", "DZIENNIKSID", data.messagesSessionId)
|
||||||
Cookie.Builder()
|
|
||||||
.name("DZIENNIKSID")
|
|
||||||
.value(data.messagesSessionId!!)
|
|
||||||
.domain("wiadomosci.librus.pl")
|
|
||||||
.secure().httpOnly().build()
|
|
||||||
))
|
|
||||||
|
|
||||||
|
|
||||||
val docBuilder = DocumentBuilderFactory.newInstance().newDocumentBuilder()
|
val docBuilder = DocumentBuilderFactory.newInstance().newDocumentBuilder()
|
||||||
val doc = docBuilder.newDocument()
|
val doc = docBuilder.newDocument()
|
||||||
|
@ -13,6 +13,7 @@ import pl.szczodrzynski.edziennik.data.db.entity.Attendance
|
|||||||
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.utils.models.Date
|
import pl.szczodrzynski.edziennik.utils.models.Date
|
||||||
|
import pl.szczodrzynski.edziennik.utils.models.Time
|
||||||
|
|
||||||
class LibrusApiAttendances(override val data: DataLibrus,
|
class LibrusApiAttendances(override val data: DataLibrus,
|
||||||
override val lastSync: Long?,
|
override val lastSync: Long?,
|
||||||
@ -45,7 +46,7 @@ class LibrusApiAttendances(override val data: DataLibrus,
|
|||||||
val typeObject = data.attendanceTypes[type] ?: null
|
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
|
||||||
|
|
||||||
val lesson = if (lessonId != -1L)
|
val lesson = if (lessonId != -1L)
|
||||||
data.librusLessons.singleOrNull { it.lessonId == lessonId }
|
data.librusLessons.singleOrNull { it.lessonId == lessonId }
|
||||||
@ -59,7 +60,7 @@ class LibrusApiAttendances(override val data: DataLibrus,
|
|||||||
semester,
|
semester,
|
||||||
topic,
|
topic,
|
||||||
lessonDate,
|
lessonDate,
|
||||||
startTime,
|
startTime ?: Time(0, 0, 0),
|
||||||
typeObject?.type ?: Attendance.TYPE_CUSTOM
|
typeObject?.type ?: Attendance.TYPE_CUSTOM
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -4,15 +4,12 @@
|
|||||||
|
|
||||||
package pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.api
|
package pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.api
|
||||||
|
|
||||||
import pl.szczodrzynski.edziennik.DAY
|
import pl.szczodrzynski.edziennik.*
|
||||||
import pl.szczodrzynski.edziennik.data.api.edziennik.librus.DataLibrus
|
import pl.szczodrzynski.edziennik.data.api.edziennik.librus.DataLibrus
|
||||||
import pl.szczodrzynski.edziennik.data.api.edziennik.librus.ENDPOINT_LIBRUS_API_LUCKY_NUMBER
|
import pl.szczodrzynski.edziennik.data.api.edziennik.librus.ENDPOINT_LIBRUS_API_LUCKY_NUMBER
|
||||||
import pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.LibrusApi
|
import pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.LibrusApi
|
||||||
import pl.szczodrzynski.edziennik.data.db.entity.LuckyNumber
|
import pl.szczodrzynski.edziennik.data.db.entity.LuckyNumber
|
||||||
import pl.szczodrzynski.edziennik.data.db.entity.Metadata
|
import pl.szczodrzynski.edziennik.data.db.entity.Metadata
|
||||||
import pl.szczodrzynski.edziennik.getInt
|
|
||||||
import pl.szczodrzynski.edziennik.getJsonObject
|
|
||||||
import pl.szczodrzynski.edziennik.getString
|
|
||||||
import pl.szczodrzynski.edziennik.utils.models.Date
|
import pl.szczodrzynski.edziennik.utils.models.Date
|
||||||
import pl.szczodrzynski.edziennik.utils.models.Time
|
import pl.szczodrzynski.edziennik.utils.models.Time
|
||||||
|
|
||||||
@ -41,9 +38,10 @@ class LibrusApiLuckyNumber(override val data: DataLibrus,
|
|||||||
luckyNumber
|
luckyNumber
|
||||||
)
|
)
|
||||||
|
|
||||||
//if (luckyNumberDate > Date.getToday()) {
|
if (luckyNumberDate >= Date.getToday())
|
||||||
nextSync = luckyNumberDate.combineWith(Time(15, 0, 0))
|
nextSync = luckyNumberDate.combineWith(Time(15, 0, 0))
|
||||||
//}
|
else
|
||||||
|
nextSync = System.currentTimeMillis() + 6*HOUR*1000
|
||||||
|
|
||||||
data.luckyNumberList.add(luckyNumberObject)
|
data.luckyNumberList.add(luckyNumberObject)
|
||||||
data.metadataList.add(
|
data.metadataList.add(
|
||||||
|
@ -21,6 +21,14 @@ class LibrusApiPushConfig(override val data: DataLibrus,
|
|||||||
}
|
}
|
||||||
|
|
||||||
init { data.app.config.sync.tokenLibrus?.also { tokenLibrus ->
|
init { data.app.config.sync.tokenLibrus?.also { tokenLibrus ->
|
||||||
|
if(tokenLibrus.isEmpty()) {
|
||||||
|
data.setSyncNext(ENDPOINT_LIBRUS_API_PUSH_CONFIG, SYNC_ALWAYS)
|
||||||
|
data.app.config.sync.tokenLibrusList =
|
||||||
|
data.app.config.sync.tokenLibrusList + profileId
|
||||||
|
onSuccess(ENDPOINT_LIBRUS_API_PUSH_CONFIG)
|
||||||
|
return@also
|
||||||
|
}
|
||||||
|
|
||||||
apiGet(TAG, "ChangeRegister", payload = JsonObject(
|
apiGet(TAG, "ChangeRegister", payload = JsonObject(
|
||||||
"provider" to "FCM",
|
"provider" to "FCM",
|
||||||
"device" to tokenLibrus,
|
"device" to tokenLibrus,
|
||||||
|
@ -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
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,7 +8,6 @@ import im.wangchao.mhttp.Request
|
|||||||
import im.wangchao.mhttp.Response
|
import im.wangchao.mhttp.Response
|
||||||
import im.wangchao.mhttp.body.MediaTypeUtils
|
import im.wangchao.mhttp.body.MediaTypeUtils
|
||||||
import im.wangchao.mhttp.callback.TextCallbackHandler
|
import im.wangchao.mhttp.callback.TextCallbackHandler
|
||||||
import okhttp3.Cookie
|
|
||||||
import pl.szczodrzynski.edziennik.data.api.*
|
import pl.szczodrzynski.edziennik.data.api.*
|
||||||
import pl.szczodrzynski.edziennik.data.api.edziennik.librus.DataLibrus
|
import pl.szczodrzynski.edziennik.data.api.edziennik.librus.DataLibrus
|
||||||
import pl.szczodrzynski.edziennik.data.api.models.ApiError
|
import pl.szczodrzynski.edziennik.data.api.models.ApiError
|
||||||
@ -64,21 +63,15 @@ class LibrusLoginMessages(val data: DataLibrus, val onSuccess: () -> Unit) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (data.isMessagesLoginValid()) {
|
if (data.isMessagesLoginValid()) {
|
||||||
data.app.cookieJar.saveFromResponse(null, listOf(
|
data.app.cookieJar.set("wiadomosci.librus.pl", "DZIENNIKSID", data.messagesSessionId)
|
||||||
Cookie.Builder()
|
|
||||||
.name("DZIENNIKSID")
|
|
||||||
.value(data.messagesSessionId!!)
|
|
||||||
.domain("wiadomosci.librus.pl")
|
|
||||||
.secure().httpOnly().build()
|
|
||||||
))
|
|
||||||
onSuccess()
|
onSuccess()
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
data.app.cookieJar.clearForDomain("wiadomosci.librus.pl")
|
data.app.cookieJar.clear("wiadomosci.librus.pl")
|
||||||
if (data.loginMethods.contains(LOGIN_METHOD_LIBRUS_SYNERGIA)) {
|
if (data.loginMethods.contains(LOGIN_METHOD_LIBRUS_SYNERGIA)) {
|
||||||
loginWithSynergia()
|
loginWithSynergia()
|
||||||
}
|
}
|
||||||
else if (data.apiLogin != null && data.apiPassword != null) {
|
else if (data.apiLogin != null && data.apiPassword != null && false) {
|
||||||
loginWithCredentials()
|
loginWithCredentials()
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
@ -148,7 +141,7 @@ class LibrusLoginMessages(val data: DataLibrus, val onSuccess: () -> Unit) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun saveSessionId(response: Response?, text: String?) {
|
private fun saveSessionId(response: Response?, text: String?) {
|
||||||
var sessionId = data.app.cookieJar.getCookie("wiadomosci.librus.pl", "DZIENNIKSID")
|
var sessionId = data.app.cookieJar.get("wiadomosci.librus.pl", "DZIENNIKSID")
|
||||||
sessionId = sessionId?.replace("-MAINT", "") // dunno what's this
|
sessionId = sessionId?.replace("-MAINT", "") // dunno what's this
|
||||||
sessionId = sessionId?.replace("MAINT", "") // dunno what's this
|
sessionId = sessionId?.replace("MAINT", "") // dunno what's this
|
||||||
if (sessionId == null) {
|
if (sessionId == null) {
|
||||||
|
@ -37,19 +37,19 @@ class LibrusLoginPortal(val data: DataLibrus, val onSuccess: () -> Unit) {
|
|||||||
}
|
}
|
||||||
else if (data.portalRefreshToken != null) {
|
else if (data.portalRefreshToken != null) {
|
||||||
if (data.fakeLogin) {
|
if (data.fakeLogin) {
|
||||||
data.app.cookieJar.clearForDomain("librus.szkolny.eu")
|
data.app.cookieJar.clear("librus.szkolny.eu")
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
data.app.cookieJar.clearForDomain("portal.librus.pl")
|
data.app.cookieJar.clear("portal.librus.pl")
|
||||||
}
|
}
|
||||||
accessToken(null, data.portalRefreshToken)
|
accessToken(null, data.portalRefreshToken)
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
if (data.fakeLogin) {
|
if (data.fakeLogin) {
|
||||||
data.app.cookieJar.clearForDomain("librus.szkolny.eu")
|
data.app.cookieJar.clear("librus.szkolny.eu")
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
data.app.cookieJar.clearForDomain("portal.librus.pl")
|
data.app.cookieJar.clear("portal.librus.pl")
|
||||||
}
|
}
|
||||||
authorize(if (data.fakeLogin) FAKE_LIBRUS_AUTHORIZE else LIBRUS_AUTHORIZE_URL)
|
authorize(if (data.fakeLogin) FAKE_LIBRUS_AUTHORIZE else LIBRUS_AUTHORIZE_URL)
|
||||||
}
|
}
|
||||||
|
@ -8,7 +8,6 @@ import com.google.gson.JsonObject
|
|||||||
import im.wangchao.mhttp.Request
|
import im.wangchao.mhttp.Request
|
||||||
import im.wangchao.mhttp.Response
|
import im.wangchao.mhttp.Response
|
||||||
import im.wangchao.mhttp.callback.TextCallbackHandler
|
import im.wangchao.mhttp.callback.TextCallbackHandler
|
||||||
import okhttp3.Cookie
|
|
||||||
import pl.szczodrzynski.edziennik.data.api.*
|
import pl.szczodrzynski.edziennik.data.api.*
|
||||||
import pl.szczodrzynski.edziennik.data.api.edziennik.librus.DataLibrus
|
import pl.szczodrzynski.edziennik.data.api.edziennik.librus.DataLibrus
|
||||||
import pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.LibrusApi
|
import pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.LibrusApi
|
||||||
@ -30,17 +29,11 @@ class LibrusLoginSynergia(override val data: DataLibrus, val onSuccess: () -> Un
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (data.isSynergiaLoginValid()) {
|
if (data.isSynergiaLoginValid()) {
|
||||||
data.app.cookieJar.saveFromResponse(null, listOf(
|
data.app.cookieJar.set("synergia.librus.pl", "DZIENNIKSID", data.synergiaSessionId)
|
||||||
Cookie.Builder()
|
|
||||||
.name("DZIENNIKSID")
|
|
||||||
.value(data.synergiaSessionId!!)
|
|
||||||
.domain("synergia.librus.pl")
|
|
||||||
.secure().httpOnly().build()
|
|
||||||
))
|
|
||||||
onSuccess()
|
onSuccess()
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
data.app.cookieJar.clearForDomain("synergia.librus.pl")
|
data.app.cookieJar.clear("synergia.librus.pl")
|
||||||
if (data.loginMethods.contains(LOGIN_METHOD_LIBRUS_API)) {
|
if (data.loginMethods.contains(LOGIN_METHOD_LIBRUS_API)) {
|
||||||
loginWithApi()
|
loginWithApi()
|
||||||
}
|
}
|
||||||
@ -92,7 +85,7 @@ class LibrusLoginSynergia(override val data: DataLibrus, val onSuccess: () -> Un
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (location?.endsWith("centrum_powiadomien") == true) {
|
if (location?.endsWith("centrum_powiadomien") == true) {
|
||||||
val sessionId = data.app.cookieJar.getCookie("synergia.librus.pl", "DZIENNIKSID")
|
val sessionId = data.app.cookieJar.get("synergia.librus.pl", "DZIENNIKSID")
|
||||||
if (sessionId == null) {
|
if (sessionId == null) {
|
||||||
data.error(ApiError(TAG, ERROR_LOGIN_LIBRUS_SYNERGIA_NO_SESSION_ID)
|
data.error(ApiError(TAG, ERROR_LOGIN_LIBRUS_SYNERGIA_NO_SESSION_ID)
|
||||||
.withResponse(response)
|
.withResponse(response)
|
||||||
@ -117,7 +110,7 @@ class LibrusLoginSynergia(override val data: DataLibrus, val onSuccess: () -> Un
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
data.app.cookieJar.clearForDomain("synergia.librus.pl")
|
data.app.cookieJar.clear("synergia.librus.pl")
|
||||||
Request.builder()
|
Request.builder()
|
||||||
.url(LIBRUS_SYNERGIA_TOKEN_LOGIN_URL.replace("TOKEN", token) + "/uczen/widok/centrum_powiadomien")
|
.url(LIBRUS_SYNERGIA_TOKEN_LOGIN_URL.replace("TOKEN", token) + "/uczen/widok/centrum_powiadomien")
|
||||||
.userAgent(LIBRUS_USER_AGENT)
|
.userAgent(LIBRUS_USER_AGENT)
|
||||||
|
@ -8,7 +8,6 @@ import im.wangchao.mhttp.Request
|
|||||||
import im.wangchao.mhttp.Response
|
import im.wangchao.mhttp.Response
|
||||||
import im.wangchao.mhttp.callback.FileCallbackHandler
|
import im.wangchao.mhttp.callback.FileCallbackHandler
|
||||||
import im.wangchao.mhttp.callback.TextCallbackHandler
|
import im.wangchao.mhttp.callback.TextCallbackHandler
|
||||||
import okhttp3.Cookie
|
|
||||||
import pl.szczodrzynski.edziennik.data.api.*
|
import pl.szczodrzynski.edziennik.data.api.*
|
||||||
import pl.szczodrzynski.edziennik.data.api.edziennik.mobidziennik.DataMobidziennik
|
import pl.szczodrzynski.edziennik.data.api.edziennik.mobidziennik.DataMobidziennik
|
||||||
import pl.szczodrzynski.edziennik.data.api.models.ApiError
|
import pl.szczodrzynski.edziennik.data.api.models.ApiError
|
||||||
@ -26,6 +25,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 +81,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) {
|
||||||
@ -82,18 +104,8 @@ open class MobidziennikWeb(open val data: DataMobidziennik, open val lastSync: L
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
data.app.cookieJar.saveFromResponse(null, listOf(
|
data.app.cookieJar.set("${data.loginServerName}.mobidziennik.pl", data.webSessionKey, data.webSessionValue)
|
||||||
Cookie.Builder()
|
data.app.cookieJar.set("${data.loginServerName}.mobidziennik.pl", "SERVERID", data.webServerId)
|
||||||
.name(data.webSessionKey!!)
|
|
||||||
.value(data.webSessionValue!!)
|
|
||||||
.domain("${data.loginServerName}.mobidziennik.pl")
|
|
||||||
.secure().httpOnly().build(),
|
|
||||||
Cookie.Builder()
|
|
||||||
.name("SERVERID")
|
|
||||||
.value(data.webServerId!!)
|
|
||||||
.domain("${data.loginServerName}.mobidziennik.pl")
|
|
||||||
.secure().httpOnly().build()
|
|
||||||
))
|
|
||||||
|
|
||||||
Request.builder()
|
Request.builder()
|
||||||
.url(url)
|
.url(url)
|
||||||
@ -164,18 +176,8 @@ open class MobidziennikWeb(open val data: DataMobidziennik, open val lastSync: L
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
data.app.cookieJar.saveFromResponse(null, listOf(
|
data.app.cookieJar.set("${data.loginServerName}.mobidziennik.pl", data.webSessionKey, data.webSessionValue)
|
||||||
Cookie.Builder()
|
data.app.cookieJar.set("${data.loginServerName}.mobidziennik.pl", "SERVERID", data.webServerId)
|
||||||
.name(data.webSessionKey!!)
|
|
||||||
.value(data.webSessionValue!!)
|
|
||||||
.domain("${data.loginServerName}.mobidziennik.pl")
|
|
||||||
.secure().httpOnly().build(),
|
|
||||||
Cookie.Builder()
|
|
||||||
.name("SERVERID")
|
|
||||||
.value(data.webServerId!!)
|
|
||||||
.domain("${data.loginServerName}.mobidziennik.pl")
|
|
||||||
.secure().httpOnly().build()
|
|
||||||
))
|
|
||||||
|
|
||||||
Request.builder()
|
Request.builder()
|
||||||
.url(url)
|
.url(url)
|
||||||
|
@ -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
|
||||||
|
@ -26,7 +26,7 @@ class MobidziennikLoginWeb(val data: DataMobidziennik, val onSuccess: () -> Unit
|
|||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
if (data.loginServerName.isNotNullNorEmpty() && data.loginUsername.isNotNullNorEmpty() && data.loginPassword.isNotNullNorEmpty()) {
|
if (data.loginServerName.isNotNullNorEmpty() && data.loginUsername.isNotNullNorEmpty() && data.loginPassword.isNotNullNorEmpty()) {
|
||||||
data.app.cookieJar.clearForDomain(data.loginServerName + ".mobidziennik.pl")
|
data.app.cookieJar.clear("${data.loginServerName}.mobidziennik.pl")
|
||||||
loginWithCredentials()
|
loginWithCredentials()
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
@ -58,10 +58,10 @@ class MobidziennikLoginWeb(val data: DataMobidziennik, val onSuccess: () -> Unit
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val cookies = data.app.cookieJar.getForDomain("${data.loginServerName}.mobidziennik.pl")
|
val cookies = data.app.cookieJar.getAll("${data.loginServerName}.mobidziennik.pl")
|
||||||
val cookie = cookies.singleOrNull { it.name().length > 32 }
|
val cookie = cookies.entries.firstOrNull { it.key.length > 32 }
|
||||||
val sessionKey = cookie?.name()
|
val sessionKey = cookie?.key
|
||||||
val sessionId = cookie?.value()
|
val sessionId = cookie?.value
|
||||||
if (sessionId == null) {
|
if (sessionId == null) {
|
||||||
data.error(ApiError(TAG, ERROR_LOGIN_MOBIDZIENNIK_WEB_NO_SESSION_ID)
|
data.error(ApiError(TAG, ERROR_LOGIN_MOBIDZIENNIK_WEB_NO_SESSION_ID)
|
||||||
.withResponse(response)
|
.withResponse(response)
|
||||||
@ -71,7 +71,7 @@ class MobidziennikLoginWeb(val data: DataMobidziennik, val onSuccess: () -> Unit
|
|||||||
|
|
||||||
data.webSessionKey = sessionKey
|
data.webSessionKey = sessionKey
|
||||||
data.webSessionValue = sessionId
|
data.webSessionValue = sessionId
|
||||||
data.webServerId = data.app.cookieJar.getCookie("${data.loginServerName}.mobidziennik.pl", "SERVERID")
|
data.webServerId = data.app.cookieJar.get("${data.loginServerName}.mobidziennik.pl", "SERVERID")
|
||||||
data.webSessionIdExpiryTime = response.getUnixDate() + 45 * 60 /* 45min */
|
data.webSessionIdExpiryTime = response.getUnixDate() + 45 * 60 /* 45min */
|
||||||
onSuccess()
|
onSuccess()
|
||||||
}
|
}
|
||||||
|
@ -4,7 +4,6 @@
|
|||||||
|
|
||||||
package pl.szczodrzynski.edziennik.data.api.edziennik.template
|
package pl.szczodrzynski.edziennik.data.api.edziennik.template
|
||||||
|
|
||||||
import okhttp3.Cookie
|
|
||||||
import pl.szczodrzynski.edziennik.App
|
import pl.szczodrzynski.edziennik.App
|
||||||
import pl.szczodrzynski.edziennik.currentTimeUnix
|
import pl.szczodrzynski.edziennik.currentTimeUnix
|
||||||
import pl.szczodrzynski.edziennik.data.api.LOGIN_METHOD_TEMPLATE_API
|
import pl.szczodrzynski.edziennik.data.api.LOGIN_METHOD_TEMPLATE_API
|
||||||
@ -28,13 +27,7 @@ class DataTemplate(app: App, profile: Profile?, loginStore: LoginStore) : Data(a
|
|||||||
loginMethods.clear()
|
loginMethods.clear()
|
||||||
if (isWebLoginValid()) {
|
if (isWebLoginValid()) {
|
||||||
loginMethods += LOGIN_METHOD_TEMPLATE_WEB
|
loginMethods += LOGIN_METHOD_TEMPLATE_WEB
|
||||||
app.cookieJar.saveFromResponse(null, listOf(
|
app.cookieJar.set("eregister.example.com", "AuthCookie", webCookie)
|
||||||
Cookie.Builder()
|
|
||||||
.name("AuthCookie")
|
|
||||||
.value(webCookie!!)
|
|
||||||
.domain("eregister.example.com")
|
|
||||||
.secure().httpOnly().build()
|
|
||||||
))
|
|
||||||
}
|
}
|
||||||
if (isApiLoginValid())
|
if (isApiLoginValid())
|
||||||
loginMethods += LOGIN_METHOD_TEMPLATE_API
|
loginMethods += LOGIN_METHOD_TEMPLATE_API
|
||||||
|
@ -4,12 +4,11 @@
|
|||||||
|
|
||||||
package pl.szczodrzynski.edziennik.data.api.edziennik.template.login
|
package pl.szczodrzynski.edziennik.data.api.edziennik.template.login
|
||||||
|
|
||||||
import okhttp3.Cookie
|
import pl.szczodrzynski.edziennik.currentTimeUnix
|
||||||
import pl.szczodrzynski.edziennik.data.api.ERROR_LOGIN_DATA_MISSING
|
import pl.szczodrzynski.edziennik.data.api.ERROR_LOGIN_DATA_MISSING
|
||||||
import pl.szczodrzynski.edziennik.data.api.ERROR_PROFILE_MISSING
|
import pl.szczodrzynski.edziennik.data.api.ERROR_PROFILE_MISSING
|
||||||
import pl.szczodrzynski.edziennik.data.api.edziennik.template.DataTemplate
|
import pl.szczodrzynski.edziennik.data.api.edziennik.template.DataTemplate
|
||||||
import pl.szczodrzynski.edziennik.data.api.models.ApiError
|
import pl.szczodrzynski.edziennik.data.api.models.ApiError
|
||||||
import pl.szczodrzynski.edziennik.currentTimeUnix
|
|
||||||
|
|
||||||
class TemplateLoginWeb(val data: DataTemplate, val onSuccess: () -> Unit) {
|
class TemplateLoginWeb(val data: DataTemplate, val onSuccess: () -> Unit) {
|
||||||
companion object {
|
companion object {
|
||||||
@ -23,17 +22,11 @@ class TemplateLoginWeb(val data: DataTemplate, val onSuccess: () -> Unit) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (data.isWebLoginValid()) {
|
if (data.isWebLoginValid()) {
|
||||||
data.app.cookieJar.saveFromResponse(null, listOf(
|
data.app.cookieJar.set("eregister.example.com", "AuthCookie", data.webCookie)
|
||||||
Cookie.Builder()
|
|
||||||
.name("AuthCookie")
|
|
||||||
.value(data.webCookie!!)
|
|
||||||
.domain("eregister.example.com")
|
|
||||||
.secure().httpOnly().build()
|
|
||||||
))
|
|
||||||
onSuccess()
|
onSuccess()
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
data.app.cookieJar.clearForDomain("eregister.example.com")
|
data.app.cookieJar.clear("eregister.example.com")
|
||||||
if (/*data.webLogin != null && data.webPassword != null && */true) {
|
if (/*data.webLogin != null && data.webPassword != null && */true) {
|
||||||
loginWithCredentials()
|
loginWithCredentials()
|
||||||
}
|
}
|
||||||
|
@ -210,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/"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -36,7 +36,7 @@ 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")
|
||||||
|
|
||||||
|
@ -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)
|
||||||
|
@ -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.MTIzNDU2Nzg5MDoIuBbQeJ===.$param2".sha256()
|
return "$param1.MTIzNDU2Nzg5MD86J6EdtN===.$param2".sha256()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -40,7 +40,7 @@ interface ProfileDao {
|
|||||||
@Query("SELECT profileId FROM profiles WHERE loginStoreId = :loginStoreId ORDER BY profileId")
|
@Query("SELECT profileId FROM profiles WHERE loginStoreId = :loginStoreId ORDER BY profileId")
|
||||||
fun getIdsByLoginStoreIdNow(loginStoreId: Int): List<Int>
|
fun getIdsByLoginStoreIdNow(loginStoreId: Int): List<Int>
|
||||||
|
|
||||||
@get:Query("SELECT * FROM profiles WHERE 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 profilesForFirebaseNow: 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")
|
||||||
|
@ -9,8 +9,8 @@ import android.app.PendingIntent
|
|||||||
import android.app.PendingIntent.CanceledException
|
import android.app.PendingIntent.CanceledException
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import com.google.firebase.iid.zzaq
|
import com.google.firebase.iid.zzad
|
||||||
import com.google.firebase.iid.zzv
|
import com.google.firebase.iid.zzaz
|
||||||
import com.google.firebase.messaging.zzc
|
import com.google.firebase.messaging.zzc
|
||||||
import com.google.gson.JsonObject
|
import com.google.gson.JsonObject
|
||||||
import pl.szczodrzynski.edziennik.*
|
import pl.szczodrzynski.edziennik.*
|
||||||
@ -36,7 +36,7 @@ open class FirebaseService : zzc() {
|
|||||||
|
|
||||||
// apparently this gets the correct intent from some
|
// apparently this gets the correct intent from some
|
||||||
// kind of queue inside Firebase's InstanceID Receiver
|
// kind of queue inside Firebase's InstanceID Receiver
|
||||||
final override fun zza(intent: Intent?) = zzaq.zza()?.zzb()
|
final override fun zza(intent: Intent?) = zzaz.zza()?.zzb()
|
||||||
final override fun zzb(intent: Intent?): Boolean {
|
final override fun zzb(intent: Intent?): Boolean {
|
||||||
val action = intent?.action
|
val action = intent?.action
|
||||||
if (action == "com.google.firebase.messaging.NOTIFICATION_OPEN") {
|
if (action == "com.google.firebase.messaging.NOTIFICATION_OPEN") {
|
||||||
@ -80,7 +80,7 @@ open class FirebaseService : zzc() {
|
|||||||
val ackBundle = Bundle(
|
val ackBundle = Bundle(
|
||||||
"google.message_id" to messageId
|
"google.message_id" to messageId
|
||||||
)
|
)
|
||||||
zzv.zza(this).zza(2, ackBundle)
|
zzad.zza(this).zza(2, ackBundle)
|
||||||
}
|
}
|
||||||
// check for duplicate message
|
// check for duplicate message
|
||||||
// and add it to queue
|
// and add it to queue
|
||||||
|
@ -0,0 +1,48 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) Kuba Szczodrzyński 2020-3-24.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package pl.szczodrzynski.edziennik.network.cookie
|
||||||
|
|
||||||
|
import okhttp3.Cookie
|
||||||
|
|
||||||
|
class DumbCookie(var cookie: Cookie) {
|
||||||
|
|
||||||
|
constructor(domain: String, name: String, value: String, expiresAt: Long? = null) : this(
|
||||||
|
Cookie.Builder()
|
||||||
|
.name(name)
|
||||||
|
.value(value)
|
||||||
|
.also { if (expiresAt != null) it.expiresAt(expiresAt) }
|
||||||
|
.domain(domain)
|
||||||
|
.build()
|
||||||
|
)
|
||||||
|
|
||||||
|
init {
|
||||||
|
cookie = Cookie.Builder()
|
||||||
|
.name(cookie.name())
|
||||||
|
.value(cookie.value())
|
||||||
|
.expiresAt(cookie.expiresAt())
|
||||||
|
.domain(cookie.domain())
|
||||||
|
.build()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun domainMatches(host: String): Boolean {
|
||||||
|
val domain = cookie.domain()
|
||||||
|
return host == domain || host.endsWith(".$domain")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun equals(other: Any?): Boolean {
|
||||||
|
if (other !is DumbCookie) return false
|
||||||
|
if (this.cookie === other.cookie) return true
|
||||||
|
|
||||||
|
return cookie.name() == other.cookie.name()
|
||||||
|
&& cookie.domain() == other.cookie.domain()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun hashCode(): Int {
|
||||||
|
var hash = 17
|
||||||
|
hash = 31 * hash + cookie.name().hashCode()
|
||||||
|
hash = 31 * hash + cookie.domain().hashCode()
|
||||||
|
return hash
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,103 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) Kuba Szczodrzyński 2020-3-24.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package pl.szczodrzynski.edziennik.network.cookie
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import okhttp3.Cookie
|
||||||
|
import okhttp3.CookieJar
|
||||||
|
import okhttp3.HttpUrl
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A simple cookie jar that does not care about the [Cookie.secure], [Cookie.hostOnly],
|
||||||
|
* [Cookie.httpOnly] and [Cookie.path] attributes.
|
||||||
|
*/
|
||||||
|
class DumbCookieJar(
|
||||||
|
/**
|
||||||
|
* A context to create the shared prefs file.
|
||||||
|
*/
|
||||||
|
context: Context,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether to persist session cookies as well, when [Cookie.persistent] is false.
|
||||||
|
*/
|
||||||
|
private val persistAll: Boolean = false
|
||||||
|
) : CookieJar {
|
||||||
|
|
||||||
|
private val prefs = context.getSharedPreferences("cookies", Context.MODE_PRIVATE)
|
||||||
|
private val sessionCookies = mutableSetOf<DumbCookie>()
|
||||||
|
private val savedCookies = mutableSetOf<DumbCookie>()
|
||||||
|
private fun save(dc: DumbCookie) {
|
||||||
|
sessionCookies.remove(dc)
|
||||||
|
sessionCookies.add(dc)
|
||||||
|
if (dc.cookie.persistent() || persistAll) {
|
||||||
|
savedCookies.remove(dc)
|
||||||
|
savedCookies.add(dc)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private fun delete(vararg toRemove: DumbCookie) {
|
||||||
|
sessionCookies.removeAll(toRemove)
|
||||||
|
savedCookies.removeAll(toRemove)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun saveFromResponse(url: HttpUrl?, cookies: List<Cookie>) {
|
||||||
|
for (cookie in cookies) {
|
||||||
|
val dc = DumbCookie(cookie)
|
||||||
|
save(dc)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun loadForRequest(url: HttpUrl): List<Cookie> {
|
||||||
|
return sessionCookies.filter {
|
||||||
|
it.cookie.matches(url)
|
||||||
|
}.map { it.cookie }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun get(domain: String, name: String): String? {
|
||||||
|
return sessionCookies.firstOrNull {
|
||||||
|
it.domainMatches(domain) && it.cookie.name() == name
|
||||||
|
}?.cookie?.value()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun set(domain: String, name: String, value: String?, isSession: Boolean) = set(
|
||||||
|
domain, name, value,
|
||||||
|
if (isSession) null
|
||||||
|
else System.currentTimeMillis() + 365 * 24 * 60 * 60 * 1000L
|
||||||
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a cookie to the cache.
|
||||||
|
* By default a session cookie is added. If [expiresAt] is set, the cookie is
|
||||||
|
* additionally persisted.
|
||||||
|
*/
|
||||||
|
fun set(domain: String, name: String?, value: String?, expiresAt: Long? = null) {
|
||||||
|
name ?: return
|
||||||
|
if (value == null) {
|
||||||
|
remove(domain, name)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
val dc = DumbCookie(domain, name, value, expiresAt)
|
||||||
|
save(dc)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getAll(domain: String): Map<String, String> {
|
||||||
|
return sessionCookies.filter {
|
||||||
|
it.domainMatches(domain)
|
||||||
|
}.map { it.cookie.name() to it.cookie.value() }.toMap()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun remove(domain: String, name: String) {
|
||||||
|
val toRemove = sessionCookies.filter {
|
||||||
|
it.domainMatches(domain) && it.cookie.name() == name
|
||||||
|
}
|
||||||
|
delete(*toRemove.toTypedArray())
|
||||||
|
}
|
||||||
|
|
||||||
|
fun clear(domain: String) {
|
||||||
|
val toRemove = sessionCookies.filter {
|
||||||
|
it.domainMatches(domain)
|
||||||
|
}
|
||||||
|
delete(*toRemove.toTypedArray())
|
||||||
|
}
|
||||||
|
}
|
@ -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
|
||||||
|
|
||||||
@ -159,6 +160,11 @@ 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() {
|
private fun showRemovingProgressDialog() {
|
||||||
|
@ -174,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
|
||||||
),
|
),
|
||||||
@ -210,7 +210,7 @@ class EventManualDialog(
|
|||||||
|
|
||||||
@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()
|
progressDialog?.dismiss()
|
||||||
@ -241,7 +241,7 @@ class EventManualDialog(
|
|||||||
|
|
||||||
with (b.dateDropdown) {
|
with (b.dateDropdown) {
|
||||||
db = app.db
|
db = app.db
|
||||||
profileId = App.profileId
|
profileId = this@EventManualDialog.profileId
|
||||||
showWeekDays = false
|
showWeekDays = false
|
||||||
showDays = true
|
showDays = true
|
||||||
showOtherDate = true
|
showOtherDate = true
|
||||||
@ -269,7 +269,7 @@ class EventManualDialog(
|
|||||||
|
|
||||||
with (b.timeDropdown) {
|
with (b.timeDropdown) {
|
||||||
db = app.db
|
db = app.db
|
||||||
profileId = App.profileId
|
profileId = this@EventManualDialog.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()
|
||||||
@ -289,7 +289,7 @@ class EventManualDialog(
|
|||||||
|
|
||||||
with (b.teamDropdown) {
|
with (b.teamDropdown) {
|
||||||
db = app.db
|
db = app.db
|
||||||
profileId = App.profileId
|
profileId = this@EventManualDialog.profileId
|
||||||
showNoTeam = true
|
showNoTeam = true
|
||||||
loadItems()
|
loadItems()
|
||||||
selectTeamClass()
|
selectTeamClass()
|
||||||
@ -299,7 +299,7 @@ class EventManualDialog(
|
|||||||
|
|
||||||
with (b.subjectDropdown) {
|
with (b.subjectDropdown) {
|
||||||
db = app.db
|
db = app.db
|
||||||
profileId = App.profileId
|
profileId = this@EventManualDialog.profileId
|
||||||
showNoSubject = true
|
showNoSubject = true
|
||||||
showCustomSubject = false
|
showCustomSubject = false
|
||||||
loadItems()
|
loadItems()
|
||||||
@ -309,7 +309,7 @@ class EventManualDialog(
|
|||||||
|
|
||||||
with (b.teacherDropdown) {
|
with (b.teacherDropdown) {
|
||||||
db = app.db
|
db = app.db
|
||||||
profileId = App.profileId
|
profileId = this@EventManualDialog.profileId
|
||||||
showNoTeacher = true
|
showNoTeacher = true
|
||||||
loadItems()
|
loadItems()
|
||||||
selectDefault(editingEvent?.teacherId)
|
selectDefault(editingEvent?.teacherId)
|
||||||
@ -497,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) {
|
||||||
@ -569,7 +569,7 @@ 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()
|
progressDialog?.dismiss()
|
||||||
|
@ -40,6 +40,7 @@ class MainSnackbar(val activity: AppCompatActivity) {
|
|||||||
setAction(actionText) {
|
setAction(actionText) {
|
||||||
onClick?.invoke()
|
onClick?.invoke()
|
||||||
}
|
}
|
||||||
|
duration = 7000
|
||||||
show()
|
show()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -313,8 +313,6 @@ 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(dontCountEnabled, dontCountGrades, grade)
|
val weight = manager.getGradeWeight(dontCountEnabled, dontCountGrades, grade)
|
||||||
if (weight == 0f)
|
|
||||||
return
|
|
||||||
when (grade.type) {
|
when (grade.type) {
|
||||||
Grade.TYPE_NORMAL -> {
|
Grade.TYPE_NORMAL -> {
|
||||||
if (grade.value > 0f) {
|
if (grade.value > 0f) {
|
||||||
|
@ -16,7 +16,7 @@ class CardItemTouchHelperCallback(private val cardAdapter: HomeCardAdapter, priv
|
|||||||
companion object {
|
companion object {
|
||||||
private const val TAG = "CardItemTouchHelperCallback"
|
private const val TAG = "CardItemTouchHelperCallback"
|
||||||
private const val DRAG_FLAGS = UP or DOWN
|
private const val DRAG_FLAGS = UP or DOWN
|
||||||
private const val SWIPE_FLAGS = RIGHT
|
private const val SWIPE_FLAGS = LEFT
|
||||||
}
|
}
|
||||||
|
|
||||||
private var dragCardView: MaterialCardView? = null
|
private var dragCardView: MaterialCardView? = null
|
||||||
|
@ -94,6 +94,13 @@ class HomeFragment : Fragment(), CoroutineScope {
|
|||||||
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)
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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
|
||||||
@ -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)
|
||||||
|
@ -438,7 +438,7 @@ class MessagesComposeFragment : Fragment(), CoroutineScope {
|
|||||||
if (b.textLayout.counterMaxLength != -1 && b.text.length() > b.textLayout.counterMaxLength)
|
if (b.textLayout.counterMaxLength != -1 && b.text.length() > b.textLayout.counterMaxLength)
|
||||||
return
|
return
|
||||||
|
|
||||||
var textHtml = if (app.profile.loginStoreType != LoginStore.LOGIN_TYPE_VULCAN && app.profile.loginStoreType != LoginStore.LOGIN_TYPE_IDZIENNIK) {
|
var textHtml = if (app.profile.loginStoreType != LoginStore.LOGIN_TYPE_VULCAN) {
|
||||||
HtmlCompat.toHtml(SpannableString(text), HtmlCompat.TO_HTML_PARAGRAPH_LINES_INDIVIDUAL)
|
HtmlCompat.toHtml(SpannableString(text), HtmlCompat.TO_HTML_PARAGRAPH_LINES_INDIVIDUAL)
|
||||||
.replace("\n", "")
|
.replace("\n", "")
|
||||||
.replace(" dir=\"ltr\"", "")
|
.replace(" dir=\"ltr\"", "")
|
||||||
|
@ -1,17 +1,21 @@
|
|||||||
package pl.szczodrzynski.edziennik.ui.modules.messages
|
package pl.szczodrzynski.edziennik.ui.modules.messages
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.graphics.*
|
import android.graphics.Bitmap
|
||||||
import android.os.Build
|
import android.graphics.Canvas
|
||||||
import android.text.Html
|
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.*
|
import pl.szczodrzynski.edziennik.App
|
||||||
|
import pl.szczodrzynski.edziennik.R
|
||||||
import pl.szczodrzynski.edziennik.data.db.entity.Message
|
import pl.szczodrzynski.edziennik.data.db.entity.Message
|
||||||
import pl.szczodrzynski.edziennik.data.db.full.MessageFull
|
import pl.szczodrzynski.edziennik.data.db.full.MessageFull
|
||||||
|
import pl.szczodrzynski.edziennik.fixName
|
||||||
|
import pl.szczodrzynski.edziennik.getNameInitials
|
||||||
import pl.szczodrzynski.edziennik.utils.Colors
|
import pl.szczodrzynski.edziennik.utils.Colors
|
||||||
import pl.szczodrzynski.edziennik.utils.Utils
|
import pl.szczodrzynski.edziennik.utils.Utils
|
||||||
import pl.szczodrzynski.navlib.blendColors
|
import pl.szczodrzynski.edziennik.utils.html.BetterHtml
|
||||||
import kotlin.math.roundToInt
|
import kotlin.math.roundToInt
|
||||||
|
|
||||||
object MessagesUtils {
|
object MessagesUtils {
|
||||||
@ -179,39 +183,6 @@ object MessagesUtils {
|
|||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun htmlToSpannable(context: Context, 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(), "")
|
|
||||||
|
|
||||||
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))
|
|
||||||
}
|
|
||||||
|
|
||||||
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
|
||||||
Html.fromHtml(text, Html.FROM_HTML_MODE_LEGACY)
|
|
||||||
} 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()
|
||||||
|
@ -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,84 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) Kuba Szczodrzyński 2020-3-17.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package pl.szczodrzynski.edziennik.utils.html
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.graphics.Color
|
||||||
|
import android.text.SpannableStringBuilder
|
||||||
|
import android.text.Spanned
|
||||||
|
import android.text.style.BulletSpan
|
||||||
|
import androidx.core.graphics.ColorUtils
|
||||||
|
import androidx.core.text.HtmlCompat
|
||||||
|
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 = HtmlCompat.fromHtml(
|
||||||
|
text,
|
||||||
|
HtmlCompat.FROM_HTML_SEPARATOR_LINE_BREAK_LIST_ITEM or HtmlCompat.FROM_HTML_SEPARATOR_LINE_BREAK_LIST or HtmlCompat.FROM_HTML_SEPARATOR_LINE_BREAK_DIV,
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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
|
||||||
|
@ -29,12 +29,14 @@
|
|||||||
tools:listitem="@layout/card_home_timetable" />
|
tools:listitem="@layout/card_home_timetable" />
|
||||||
|
|
||||||
<View
|
<View
|
||||||
|
android:id="@+id/configHintDivider"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="1dp"
|
android:layout_height="1dp"
|
||||||
android:layout_marginVertical="8dp"
|
android:layout_marginVertical="8dp"
|
||||||
android:background="@drawable/divider" />
|
android:background="@drawable/divider" />
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
|
android:id="@+id/configHint"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:gravity="center_vertical"
|
android:gravity="center_vertical"
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
<androidx.constraintlayout.widget.ConstraintLayout
|
<merge xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:id="@+id/material_drawer_account_header"
|
android:id="@+id/material_drawer_account_header"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="@dimen/material_drawer_account_header_height"
|
android:layout_height="@dimen/material_drawer_account_header_height"
|
||||||
@ -35,10 +35,27 @@
|
|||||||
android:elevation="2dp"
|
android:elevation="2dp"
|
||||||
android:focusable="true"
|
android:focusable="true"
|
||||||
android:scaleType="centerCrop"
|
android:scaleType="centerCrop"
|
||||||
app:biv_selectorOnPress="#80ffffff"
|
app:materialDrawerSelectorOnPress="#80ffffff"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toBottomOf="@+id/material_drawer_statusbar_guideline" />
|
app:layout_constraintTop_toBottomOf="@+id/material_drawer_statusbar_guideline" />
|
||||||
|
|
||||||
|
<androidx.appcompat.widget.AppCompatTextView
|
||||||
|
android:id="@+id/material_drawer_account_header_current_badge"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:elevation="4dp"
|
||||||
|
android:fontFamily="sans-serif"
|
||||||
|
android:gravity="center"
|
||||||
|
android:lines="1"
|
||||||
|
android:minWidth="20dp"
|
||||||
|
android:paddingLeft="1dp"
|
||||||
|
android:paddingRight="1dp"
|
||||||
|
android:singleLine="true"
|
||||||
|
android:textSize="@dimen/material_drawer_item_badge_text"
|
||||||
|
app:layout_constraintBottom_toBottomOf="@id/material_drawer_account_header_current"
|
||||||
|
app:layout_constraintStart_toStartOf="@id/material_drawer_account_header_current"
|
||||||
|
tools:text="99" />
|
||||||
|
|
||||||
<com.mikepenz.materialdrawer.view.BezelImageView
|
<com.mikepenz.materialdrawer.view.BezelImageView
|
||||||
android:id="@+id/material_drawer_account_header_small_first"
|
android:id="@+id/material_drawer_account_header_small_first"
|
||||||
style="@style/BezelImageView"
|
style="@style/BezelImageView"
|
||||||
@ -50,12 +67,29 @@
|
|||||||
android:clickable="true"
|
android:clickable="true"
|
||||||
android:elevation="2dp"
|
android:elevation="2dp"
|
||||||
android:focusable="true"
|
android:focusable="true"
|
||||||
|
android:scaleType="fitCenter"
|
||||||
android:visibility="visible"
|
android:visibility="visible"
|
||||||
android:scaleType="centerCrop"
|
app:materialDrawerSelectorOnPress="#80ffffff"
|
||||||
app:biv_selectorOnPress="#80ffffff"
|
|
||||||
app:layout_constraintEnd_toStartOf="@id/material_drawer_account_header_small_second"
|
app:layout_constraintEnd_toStartOf="@id/material_drawer_account_header_small_second"
|
||||||
app:layout_constraintTop_toBottomOf="@+id/material_drawer_statusbar_guideline" />
|
app:layout_constraintTop_toBottomOf="@+id/material_drawer_statusbar_guideline" />
|
||||||
|
|
||||||
|
<androidx.appcompat.widget.AppCompatTextView
|
||||||
|
android:id="@+id/material_drawer_account_header_small_first_badge"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:elevation="4dp"
|
||||||
|
android:fontFamily="sans-serif"
|
||||||
|
android:gravity="center"
|
||||||
|
android:lines="1"
|
||||||
|
android:minWidth="20dp"
|
||||||
|
android:paddingLeft="1dp"
|
||||||
|
android:paddingRight="1dp"
|
||||||
|
android:singleLine="true"
|
||||||
|
android:textSize="@dimen/material_drawer_item_badge_small_text"
|
||||||
|
app:layout_constraintBottom_toBottomOf="@id/material_drawer_account_header_small_first"
|
||||||
|
app:layout_constraintStart_toStartOf="@id/material_drawer_account_header_small_first"
|
||||||
|
tools:text="99" />
|
||||||
|
|
||||||
<com.mikepenz.materialdrawer.view.BezelImageView
|
<com.mikepenz.materialdrawer.view.BezelImageView
|
||||||
android:id="@+id/material_drawer_account_header_small_second"
|
android:id="@+id/material_drawer_account_header_small_second"
|
||||||
style="@style/BezelImageView"
|
style="@style/BezelImageView"
|
||||||
@ -67,12 +101,29 @@
|
|||||||
android:clickable="true"
|
android:clickable="true"
|
||||||
android:elevation="2dp"
|
android:elevation="2dp"
|
||||||
android:focusable="true"
|
android:focusable="true"
|
||||||
|
android:scaleType="fitCenter"
|
||||||
android:visibility="visible"
|
android:visibility="visible"
|
||||||
android:scaleType="centerCrop"
|
app:materialDrawerSelectorOnPress="#80ffffff"
|
||||||
app:biv_selectorOnPress="#80ffffff"
|
|
||||||
app:layout_constraintEnd_toStartOf="@id/material_drawer_account_header_small_third"
|
app:layout_constraintEnd_toStartOf="@id/material_drawer_account_header_small_third"
|
||||||
app:layout_constraintTop_toBottomOf="@+id/material_drawer_statusbar_guideline" />
|
app:layout_constraintTop_toBottomOf="@+id/material_drawer_statusbar_guideline" />
|
||||||
|
|
||||||
|
<androidx.appcompat.widget.AppCompatTextView
|
||||||
|
android:id="@+id/material_drawer_account_header_small_second_badge"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:elevation="4dp"
|
||||||
|
android:fontFamily="sans-serif"
|
||||||
|
android:gravity="center"
|
||||||
|
android:lines="1"
|
||||||
|
android:minWidth="20dp"
|
||||||
|
android:paddingLeft="1dp"
|
||||||
|
android:paddingRight="1dp"
|
||||||
|
android:singleLine="true"
|
||||||
|
android:textSize="@dimen/material_drawer_item_badge_small_text"
|
||||||
|
app:layout_constraintBottom_toBottomOf="@id/material_drawer_account_header_small_second"
|
||||||
|
app:layout_constraintStart_toStartOf="@id/material_drawer_account_header_small_second"
|
||||||
|
tools:text="99" />
|
||||||
|
|
||||||
<com.mikepenz.materialdrawer.view.BezelImageView
|
<com.mikepenz.materialdrawer.view.BezelImageView
|
||||||
android:id="@+id/material_drawer_account_header_small_third"
|
android:id="@+id/material_drawer_account_header_small_third"
|
||||||
style="@style/BezelImageView"
|
style="@style/BezelImageView"
|
||||||
@ -84,12 +135,29 @@
|
|||||||
android:clickable="true"
|
android:clickable="true"
|
||||||
android:elevation="2dp"
|
android:elevation="2dp"
|
||||||
android:focusable="true"
|
android:focusable="true"
|
||||||
|
android:scaleType="fitCenter"
|
||||||
android:visibility="visible"
|
android:visibility="visible"
|
||||||
android:scaleType="centerCrop"
|
app:materialDrawerSelectorOnPress="#80ffffff"
|
||||||
app:biv_selectorOnPress="#80ffffff"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintTop_toBottomOf="@+id/material_drawer_statusbar_guideline" />
|
app:layout_constraintTop_toBottomOf="@+id/material_drawer_statusbar_guideline" />
|
||||||
|
|
||||||
|
<androidx.appcompat.widget.AppCompatTextView
|
||||||
|
android:id="@+id/material_drawer_account_header_small_third_badge"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:elevation="4dp"
|
||||||
|
android:fontFamily="sans-serif"
|
||||||
|
android:gravity="center"
|
||||||
|
android:lines="1"
|
||||||
|
android:minWidth="20dp"
|
||||||
|
android:paddingLeft="1dp"
|
||||||
|
android:paddingRight="1dp"
|
||||||
|
android:singleLine="true"
|
||||||
|
android:textSize="@dimen/material_drawer_item_badge_small_text"
|
||||||
|
app:layout_constraintBottom_toBottomOf="@id/material_drawer_account_header_small_third"
|
||||||
|
app:layout_constraintStart_toStartOf="@id/material_drawer_account_header_small_third"
|
||||||
|
tools:text="99" />
|
||||||
|
|
||||||
<androidx.constraintlayout.widget.Guideline
|
<androidx.constraintlayout.widget.Guideline
|
||||||
android:id="@+id/material_drawer_text_guideline"
|
android:id="@+id/material_drawer_text_guideline"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
@ -138,4 +206,4 @@
|
|||||||
android:layout_marginBottom="@dimen/material_drawer_account_header_dropdown_margin_bottom"
|
android:layout_marginBottom="@dimen/material_drawer_account_header_dropdown_margin_bottom"
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
app:layout_constraintEnd_toEndOf="parent" />
|
app:layout_constraintEnd_toEndOf="parent" />
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
</merge>
|
||||||
|
@ -4,6 +4,8 @@
|
|||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="@dimen/material_drawer_item_profile"
|
android:layout_height="@dimen/material_drawer_item_profile"
|
||||||
|
android:clipChildren="false"
|
||||||
|
android:clipToPadding="false"
|
||||||
android:orientation="horizontal"
|
android:orientation="horizontal"
|
||||||
android:paddingStart="@dimen/material_drawer_vertical_padding"
|
android:paddingStart="@dimen/material_drawer_vertical_padding"
|
||||||
android:paddingLeft="@dimen/material_drawer_vertical_padding"
|
android:paddingLeft="@dimen/material_drawer_vertical_padding"
|
||||||
@ -44,7 +46,7 @@
|
|||||||
android:textDirection="anyRtl"
|
android:textDirection="anyRtl"
|
||||||
android:textSize="@dimen/material_drawer_item_profile_text"
|
android:textSize="@dimen/material_drawer_item_profile_text"
|
||||||
app:layout_constraintBottom_toTopOf="@id/material_drawer_email"
|
app:layout_constraintBottom_toTopOf="@id/material_drawer_email"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toStartOf="@+id/material_drawer_badge_container"
|
||||||
app:layout_constraintStart_toEndOf="@id/material_drawer_profileIcon"
|
app:layout_constraintStart_toEndOf="@id/material_drawer_profileIcon"
|
||||||
app:layout_constraintTop_toTopOf="parent"
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
app:layout_constraintVertical_chainStyle="packed"
|
app:layout_constraintVertical_chainStyle="packed"
|
||||||
@ -69,9 +71,37 @@
|
|||||||
android:textDirection="anyRtl"
|
android:textDirection="anyRtl"
|
||||||
android:textSize="@dimen/material_drawer_item_profile_description"
|
android:textSize="@dimen/material_drawer_item_profile_description"
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toStartOf="@+id/material_drawer_badge_container"
|
||||||
app:layout_constraintStart_toEndOf="@id/material_drawer_profileIcon"
|
app:layout_constraintStart_toEndOf="@id/material_drawer_profileIcon"
|
||||||
app:layout_constraintTop_toBottomOf="@id/material_drawer_name"
|
app:layout_constraintTop_toBottomOf="@id/material_drawer_name"
|
||||||
tools:text="Some drawer text" />
|
tools:text="Some drawer text" />
|
||||||
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
<LinearLayout
|
||||||
|
android:id="@+id/material_drawer_badge_container"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:clipToPadding="false"
|
||||||
|
android:gravity="center"
|
||||||
|
android:paddingStart="@dimen/material_drawer_padding"
|
||||||
|
android:paddingLeft="@dimen/material_drawer_padding"
|
||||||
|
android:paddingEnd="0dp"
|
||||||
|
android:paddingRight="0dp"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent">
|
||||||
|
|
||||||
|
<androidx.appcompat.widget.AppCompatTextView
|
||||||
|
android:id="@+id/material_drawer_badge"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:fontFamily="sans-serif"
|
||||||
|
android:gravity="center"
|
||||||
|
android:lines="1"
|
||||||
|
android:minWidth="20dp"
|
||||||
|
android:paddingLeft="1dp"
|
||||||
|
android:paddingRight="1dp"
|
||||||
|
android:singleLine="true"
|
||||||
|
android:textSize="@dimen/material_drawer_item_primary_text"
|
||||||
|
tools:text="99" />
|
||||||
|
</LinearLayout>
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
@ -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>
|
||||||
|
@ -112,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>
|
||||||
@ -182,28 +183,28 @@
|
|||||||
<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. Skontaktuj się z twórcą aplikacji.</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>
|
||||||
<string name="error_106_reason">Profil jest archiwalny - synchronizacja profilu z poprzedniego roku szkolnego nie jest możliwa</string>
|
<string name="error_106_reason">Profil jest archiwalny - synchronizacja profilu z poprzedniego roku szkolnego nie jest możliwa</string>
|
||||||
<string name="error_110_reason">Nieprawidłowy sposób logowania</string>
|
<string name="error_110_reason">Nieprawidłowy sposób logowania. Skontaktuj się z twórcą aplikacji.</string>
|
||||||
<string name="error_111_reason">Nie można wywołać metody logowania</string>
|
<string name="error_111_reason">Nie można wywołać metody logowania. Skontaktuj się z twórcą aplikacji.</string>
|
||||||
<string name="error_112_reason">Nie zaimplementowano</string>
|
<string name="error_112_reason">Nie zaimplementowano</string>
|
||||||
<string name="error_113_reason">Wystąpił błąd podczas pobierania pliku</string>
|
<string name="error_113_reason">Wystąpił błąd podczas pobierania pliku. Dziennik może być przeciążony lub mieć przerwę techniczną.</string>
|
||||||
|
|
||||||
<string name="error_115_reason">Brak uczniów przypisanych do konta</string>
|
<string name="error_115_reason">Brak uczniów przypisanych do konta</string>
|
||||||
|
|
||||||
@ -286,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>
|
||||||
|
@ -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>
|
@ -1261,7 +1261,7 @@
|
|||||||
<string name="grades_config_dont_count_grades">Wyklucz wybrane oceny ze średniej</string>
|
<string name="grades_config_dont_count_grades">Wyklucz wybrane oceny ze średniej</string>
|
||||||
<string name="grades_config_dont_count_hint">Oceny oddziel przecinkiem</string>
|
<string name="grades_config_dont_count_hint">Oceny oddziel przecinkiem</string>
|
||||||
<string name="grades_config_dont_count_placeholder">Podaj oceny...</string>
|
<string name="grades_config_dont_count_placeholder">Podaj oceny...</string>
|
||||||
<string name="home_configure_notice">Możesz usunąć karty przesuwając w prawo lub zmienić ich kolejność, przytrzymując na kartę.</string>
|
<string name="home_configure_notice">Możesz usunąć karty przesuwając w lewo lub zmienić ich kolejność, przytrzymując na kartę.</string>
|
||||||
<string name="home_configure_add_remove">Dodaj/usuń karty</string>
|
<string name="home_configure_add_remove">Dodaj/usuń karty</string>
|
||||||
<string name="card_type_lucky_number">Szczęśliwy numerek</string>
|
<string name="card_type_lucky_number">Szczęśliwy numerek</string>
|
||||||
<string name="card_type_timetable">Plan lekcji</string>
|
<string name="card_type_timetable">Plan lekcji</string>
|
||||||
@ -1275,4 +1275,5 @@
|
|||||||
<string name="registration_enable_progress_text">Pobieranie udostępnionych wydarzeń...</string>
|
<string name="registration_enable_progress_text">Pobieranie udostępnionych wydarzeń...</string>
|
||||||
<string name="registration_enable_dialog_title">Rejestracja na serwerze</string>
|
<string name="registration_enable_dialog_title">Rejestracja na serwerze</string>
|
||||||
<string name="registration_enable_dialog_text">Rejestracja jest automatyczna, jeśli ta opcja jest włączona. Pozwala na tworzenie i odbieranie wydarzeń udostępnionych innym uczniom z Twojej klasy. Dzięki temu, można dodawać do dziennika pozycje nie zapisane przez nauczyciela.\n\nUpewnij się, że zapoznałeś się z warunkami <a href="http://szkolny.eu/privacy-policy">Polityki prywatności</a> i akceptujesz jej postanowienia.</string>
|
<string name="registration_enable_dialog_text">Rejestracja jest automatyczna, jeśli ta opcja jest włączona. Pozwala na tworzenie i odbieranie wydarzeń udostępnionych innym uczniom z Twojej klasy. Dzięki temu, można dodawać do dziennika pozycje nie zapisane przez nauczyciela.\n\nUpewnij się, że zapoznałeś się z warunkami <a href="http://szkolny.eu/privacy-policy">Polityki prywatności</a> i akceptujesz jej postanowienia.</string>
|
||||||
|
<string name="menu_add_remove_cards">Dodaj lub usuń karty</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
@ -10,63 +10,65 @@
|
|||||||
<item name="android:windowBackground">@drawable/dead_background</item>
|
<item name="android:windowBackground">@drawable/dead_background</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<style name="AppTheme.NoDisplay" parent="Theme.MaterialComponents.Light.Dialog">
|
<style name="AppTheme.Light.NoDisplay" parent="AppTheme.Light">
|
||||||
<item name="colorPrimary">@color/colorPrimary</item>
|
<item name="android:colorBackground">?attr/colorBackgroundFloating</item>
|
||||||
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
|
<item name="android:colorBackgroundCacheHint">@null</item>
|
||||||
<item name="colorAccent">@color/colorAccent</item>
|
|
||||||
<item name="colorSection">@color/colorSection</item>
|
|
||||||
|
|
||||||
<item name="android:textColor">@color/primaryTextLight</item>
|
<item name="android:windowFrame">@null</item>
|
||||||
<item name="android:textColorPrimary">@color/primaryTextLight</item>
|
<item name="android:windowTitleStyle">@style/RtlOverlay.DialogWindowTitle.AppCompat</item>
|
||||||
<item name="material_drawer_primary_text">@color/primaryTextLight</item>
|
<item name="android:windowTitleBackgroundStyle">@style/Base.DialogWindowTitleBackground.AppCompat</item>
|
||||||
<item name="android:textColorSecondary">@color/secondaryTextLight</item>
|
<item name="android:windowBackground">@android:color/transparent</item>
|
||||||
<item name="material_drawer_secondary_text">@color/secondaryTextLight</item>
|
<item name="android:windowIsFloating">true</item>
|
||||||
<item name="android:textColorTertiary">@color/secondaryTextLight</item>
|
<item name="android:backgroundDimEnabled">true</item>
|
||||||
|
<item name="android:windowContentOverlay">@null</item>
|
||||||
|
<item name="android:windowAnimationStyle">@android:style/Animation</item>
|
||||||
|
<item name="android:windowSoftInputMode">stateUnspecified|adjustPan</item>
|
||||||
|
<item name="android:windowIsTranslucent">true</item>
|
||||||
|
<item name="android:windowDisablePreview">true</item>
|
||||||
|
<item name="android:windowNoTitle">true</item>
|
||||||
|
|
||||||
<item name="md_dark_theme">false</item>
|
<item name="windowActionBar">false</item>
|
||||||
<item name="md_title_color">?android:textColorPrimary</item>
|
<item name="windowActionModeOverlay">true</item>
|
||||||
<item name="md_content_color">?android:textColorPrimary</item>
|
|
||||||
<item name="md_link_color">?colorAccent</item>
|
|
||||||
<item name="md_item_color">?android:textColorPrimary</item>
|
|
||||||
|
|
||||||
|
<item name="listPreferredItemPaddingLeft">24dip</item>
|
||||||
|
<item name="listPreferredItemPaddingRight">24dip</item>
|
||||||
|
|
||||||
|
<item name="android:listDivider">@null</item>
|
||||||
|
|
||||||
|
<item name="android:buttonBarStyle">@style/Widget.AppCompat.ButtonBar.AlertDialog</item>
|
||||||
|
<item name="android:borderlessButtonStyle">@style/Widget.AppCompat.Button.Borderless</item>
|
||||||
|
<item name="android:windowCloseOnTouchOutside">true</item>
|
||||||
<item name="android:popupBackground">@color/windowBackgroundLight</item>
|
<item name="android:popupBackground">@color/windowBackgroundLight</item>
|
||||||
|
|
||||||
|
|
||||||
<item name="android:windowBackground">@android:color/transparent</item>
|
|
||||||
<item name="android:windowContentOverlay">@null</item>
|
|
||||||
<item name="android:windowIsTranslucent">true</item>
|
|
||||||
<item name="android:windowAnimationStyle">@android:style/Animation</item>
|
|
||||||
<item name="android:windowDisablePreview">true</item>
|
|
||||||
<item name="android:windowNoTitle">true</item>
|
|
||||||
</style>
|
</style>
|
||||||
<style name="AppThemeDark.NoDisplay" parent="Theme.MaterialComponents.Dialog">
|
<style name="AppTheme.Dark.NoDisplay" parent="AppTheme.Dark">
|
||||||
<item name="colorPrimary">@color/colorPrimary</item>
|
<item name="android:colorBackground">?attr/colorBackgroundFloating</item>
|
||||||
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
|
<item name="android:colorBackgroundCacheHint">@null</item>
|
||||||
<item name="colorAccent">@color/colorAccent</item>
|
|
||||||
<item name="colorSection">@color/colorSection</item>
|
|
||||||
|
|
||||||
|
|
||||||
<item name="android:textColor">@color/primaryTextDark</item>
|
|
||||||
<item name="android:textColorPrimary">@color/primaryTextDark</item>
|
|
||||||
<item name="material_drawer_primary_text">@color/primaryTextDark</item>
|
|
||||||
<item name="android:textColorSecondary">@color/secondaryTextDark</item>
|
|
||||||
<item name="material_drawer_secondary_text">@color/secondaryTextDark</item>
|
|
||||||
<item name="android:textColorTertiary">@color/secondaryTextDark</item>
|
|
||||||
|
|
||||||
<item name="md_dark_theme">true</item>
|
|
||||||
<item name="md_title_color">?android:textColorPrimary</item>
|
|
||||||
<item name="md_content_color">?android:textColorPrimary</item>
|
|
||||||
<item name="md_link_color">?colorAccent</item>
|
|
||||||
<item name="md_item_color">?android:textColorPrimary</item>
|
|
||||||
|
|
||||||
<item name="android:popupBackground">@color/windowBackgroundDark</item>
|
|
||||||
|
|
||||||
|
<item name="android:windowFrame">@null</item>
|
||||||
|
<item name="android:windowTitleStyle">@style/RtlOverlay.DialogWindowTitle.AppCompat</item>
|
||||||
|
<item name="android:windowTitleBackgroundStyle">@style/Base.DialogWindowTitleBackground.AppCompat</item>
|
||||||
<item name="android:windowBackground">@android:color/transparent</item>
|
<item name="android:windowBackground">@android:color/transparent</item>
|
||||||
|
<item name="android:windowIsFloating">true</item>
|
||||||
|
<item name="android:backgroundDimEnabled">true</item>
|
||||||
<item name="android:windowContentOverlay">@null</item>
|
<item name="android:windowContentOverlay">@null</item>
|
||||||
<item name="android:windowIsTranslucent">true</item>
|
|
||||||
<item name="android:windowAnimationStyle">@android:style/Animation</item>
|
<item name="android:windowAnimationStyle">@android:style/Animation</item>
|
||||||
|
<item name="android:windowSoftInputMode">stateUnspecified|adjustPan</item>
|
||||||
|
<item name="android:windowIsTranslucent">true</item>
|
||||||
<item name="android:windowDisablePreview">true</item>
|
<item name="android:windowDisablePreview">true</item>
|
||||||
<item name="android:windowNoTitle">true</item>
|
<item name="android:windowNoTitle">true</item>
|
||||||
|
|
||||||
|
<item name="windowActionBar">false</item>
|
||||||
|
<item name="windowActionModeOverlay">true</item>
|
||||||
|
|
||||||
|
<item name="listPreferredItemPaddingLeft">24dip</item>
|
||||||
|
<item name="listPreferredItemPaddingRight">24dip</item>
|
||||||
|
|
||||||
|
<item name="android:listDivider">@null</item>
|
||||||
|
|
||||||
|
<item name="android:buttonBarStyle">@style/Widget.AppCompat.ButtonBar.AlertDialog</item>
|
||||||
|
<item name="android:borderlessButtonStyle">@style/Widget.AppCompat.Button.Borderless</item>
|
||||||
|
<item name="android:windowCloseOnTouchOutside">true</item>
|
||||||
|
<item name="android:popupBackground">@color/windowBackgroundDark</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<style name="AppTheme.MaterialAlertDialog" parent="ThemeOverlay.MaterialComponents.MaterialAlertDialog">
|
<style name="AppTheme.MaterialAlertDialog" parent="ThemeOverlay.MaterialComponents.MaterialAlertDialog">
|
||||||
|
22
build.gradle
22
build.gradle
@ -5,8 +5,8 @@ buildscript {
|
|||||||
kotlin_version = '1.3.61'
|
kotlin_version = '1.3.61'
|
||||||
|
|
||||||
release = [
|
release = [
|
||||||
versionName: "4.0-beta.13",
|
versionName: "4.0-rc.1",
|
||||||
versionCode: 4000013
|
versionCode: 4000019
|
||||||
]
|
]
|
||||||
|
|
||||||
setup = [
|
setup = [
|
||||||
@ -17,6 +17,8 @@ buildscript {
|
|||||||
]
|
]
|
||||||
|
|
||||||
versions = [
|
versions = [
|
||||||
|
gradleAndroid : "4.0.0-beta03",
|
||||||
|
|
||||||
kotlin : ext.kotlin_version,
|
kotlin : ext.kotlin_version,
|
||||||
ktx : "1.2.0",
|
ktx : "1.2.0",
|
||||||
|
|
||||||
@ -32,24 +34,24 @@ buildscript {
|
|||||||
navigationFragment: "1.0.0",
|
navigationFragment: "1.0.0",
|
||||||
legacy : "1.0.0",
|
legacy : "1.0.0",
|
||||||
|
|
||||||
room : "2.2.4",
|
room : "2.2.5",
|
||||||
lifecycle : "2.2.0",
|
lifecycle : "2.2.0",
|
||||||
work : "2.3.2",
|
work : "2.3.4",
|
||||||
|
|
||||||
firebase : '17.2.2',
|
firebase : '17.2.2',
|
||||||
firebasemessaging: "20.1.0",
|
firebasemessaging: "20.1.3",
|
||||||
play_services : "17.0.0",
|
play_services : "17.0.0",
|
||||||
|
|
||||||
materialdialogs : "0.9.6.0",
|
materialdialogs : "0.9.6.0",
|
||||||
materialdrawer : "cad66092a6",
|
materialdrawer : "817e45765c367034b03046aaea6e95eeabcb40e9",
|
||||||
iconics : "4.0.1",
|
iconics : "4.0.1",
|
||||||
font_cmd : "3.5.95.1-kotlin",
|
font_cmd : "3.5.95.1-kotlin",
|
||||||
|
|
||||||
navlib : "5c8b13c0d9db0d9e822fdae82c8afca6c01ab41e",
|
navlib : "28cdab341470dffa5f331379fe9702482681d7de",
|
||||||
|
|
||||||
gifdrawable : "1.2.15",
|
gifdrawable : "1.2.15",
|
||||||
|
|
||||||
retrofit : '2.6.2'
|
retrofit : "2.6.4"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -61,11 +63,11 @@ buildscript {
|
|||||||
jcenter()
|
jcenter()
|
||||||
}
|
}
|
||||||
dependencies {
|
dependencies {
|
||||||
classpath 'com.android.tools.build:gradle:4.0.0-beta01'
|
classpath "com.android.tools.build:gradle:${versions.gradleAndroid}"
|
||||||
|
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:${versions.kotlin}"
|
||||||
classpath 'me.tatarka:gradle-retrolambda:3.7.0'
|
classpath 'me.tatarka:gradle-retrolambda:3.7.0'
|
||||||
classpath 'com.google.gms:google-services:4.3.3'
|
classpath 'com.google.gms:google-services:4.3.3'
|
||||||
classpath 'io.fabric.tools:gradle:1.28.1'
|
classpath 'io.fabric.tools:gradle:1.28.1'
|
||||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
|
||||||
|
|
||||||
// NOTE: Do not place your application dependencies here; they belong
|
// NOTE: Do not place your application dependencies here; they belong
|
||||||
// in the individual module build.gradle files
|
// in the individual module build.gradle files
|
||||||
|
2
gradle/wrapper/gradle-wrapper.properties
vendored
2
gradle/wrapper/gradle-wrapper.properties
vendored
@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME
|
|||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-6.2-all.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-6.2.1-all.zip
|
||||||
|
Reference in New Issue
Block a user