Compare commits

...

39 Commits

Author SHA1 Message Date
f79b7eaf83 [3.9.11-dev] 2019-11-24 21:47:05 +01:00
ae13bf946f [Home] Remove useless dummy cards. 2019-11-24 21:21:37 +01:00
f116c4f1f4 [Home] Implement basic timetable card. 2019-11-24 21:15:01 +01:00
867c8920a8 [APIv2/Messages] Add downloading attachments. 2019-11-24 20:55:04 +01:00
6e6dd34872 [UI] Add new Home fragment. Add Lucky number card and number selection dialog. 2019-11-24 19:41:17 +01:00
0759468fa7 [Sync] Add syncing all to manual sync dialog. 2019-11-24 16:47:10 +01:00
1b1fb09211 [APIv2/Vulcan] Fix problems with week start in timetable. 2019-11-24 16:31:51 +01:00
de414c912c [Sync] Fix error when user selects no features. 2019-11-24 13:20:42 +01:00
d274a2fed1 [Timetable] Change date receiver argument to timetableDate. 2019-11-24 13:20:22 +01:00
285b7e9b9e [Timetable] Make going to the specified date on the notification click. 2019-11-24 12:57:00 +01:00
875efcff7e [APIv2/Timetable] Fix ID in lessons. 2019-11-24 12:11:39 +01:00
07ae37167d [Notifications/Timetable] Make notifications for timetable changes 2019-11-24 11:09:45 +01:00
f689f4d427 [APIv2/Timetable] Fix lesson changes metadata. 2019-11-24 10:36:20 +01:00
19bc2b8b37 [3.9.10-dev] New UI + stability fixes 2019-11-23 23:26:19 +01:00
673116e27e [Settings/About] Add a new developer to about! 2019-11-23 23:09:03 +01:00
59fcb0a050 [APIv2/Timetable] Add lesson change metadata only when the lesson is today or in the future 2019-11-23 22:40:20 +01:00
cd76f99bbf [APIv2/Timetable] Add showing unread lesson changes 2019-11-23 22:26:21 +01:00
6a4994b9c2 [APIv2/Timetable] Make swipe refresh download timetable for the selected week 2019-11-23 21:57:30 +01:00
63960c5e05 [APIv2/Timetable] Add selecting date, marking as read and fix stepForward in Date 2019-11-23 21:27:52 +01:00
540afb6a28 [Home] Start making new home timetable card in Kotlin 2019-11-23 19:41:55 +01:00
ae10b8abbd [APIv2/Idziennik] Add new timetable getting and fix week start 2019-11-23 19:40:32 +01:00
db2ebab879 [APIv2/Vulcan] Add missing Lublin endpoints 2019-11-23 19:39:13 +01:00
6ec3d062df [UI] Update header image. Fix fragment bundle passing. 2019-11-23 19:37:00 +01:00
86b6060a09 [UI] Migrate to outlined icons. 2019-11-23 18:32:18 +01:00
83d123e341 [UI] New notifications view. 2019-11-22 22:41:40 +01:00
34061695f9 [UI] Update colored placeholder icons. 2019-11-22 19:23:49 +01:00
de68476442 [UI/Event] Add Sync text to manual event dialog. 2019-11-22 18:42:45 +01:00
678a81a44b [APIv2/Vulcan] Improve Vulcan login when migrating from APIv1. 2019-11-22 18:42:11 +01:00
cfb3096d53 [Sync] Add better task cancelling and better frozen task detection. 2019-11-22 18:41:15 +01:00
9b7aca745a [3.9.9-dev] 2019-11-21 19:18:04 +01:00
82852389fa [UI] Update indentation, again. Fix manual event color selecting. 2019-11-21 18:43:01 +01:00
ce06084e6f [Timetable] Fix lessons removing, again. 2019-11-21 18:24:02 +01:00
3ca051983f [APIv2/Timetable] Add searching for the next lesson by team id 2019-11-20 22:43:48 +01:00
cd379e4175 [APIv2/Librus] Add getting grade comments 2019-11-20 21:16:18 +01:00
62fdfa2b6f [UI] Update manual event dialog. Fix timetable errors. 2019-11-20 21:13:43 +01:00
9866017f7e [APIv2/Vulcan] Fix timetable teams issue. Fix missing login data error. 2019-11-20 19:51:50 +01:00
67f98b08c6 [Update] Update update code to allow update from direct download. 2019-11-20 17:11:08 +01:00
fdb5f7ec02 [APIv2/Mobidziennik] Implement getting message details. 2019-11-18 22:57:23 +01:00
04c3c7ca6e [APIv2] Handle Librus Portal maintenance. 2019-11-18 18:55:52 +01:00
115 changed files with 3260 additions and 1244 deletions

2
.gitignore vendored
View File

@ -81,3 +81,5 @@ lint/generated/
lint/outputs/ lint/outputs/
lint/tmp/ lint/tmp/
# lint/reports/ # lint/reports/
app/schemas/

View File

@ -17,6 +17,7 @@ import android.util.LongSparseArray
import android.util.SparseArray import android.util.SparseArray
import android.util.TypedValue import android.util.TypedValue
import android.view.View import android.view.View
import android.widget.CompoundButton
import android.widget.TextView import android.widget.TextView
import androidx.annotation.* import androidx.annotation.*
import androidx.core.app.ActivityCompat import androidx.core.app.ActivityCompat
@ -28,9 +29,13 @@ import com.google.gson.JsonArray
import com.google.gson.JsonElement import com.google.gson.JsonElement
import com.google.gson.JsonObject import com.google.gson.JsonObject
import im.wangchao.mhttp.Response import im.wangchao.mhttp.Response
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import pl.szczodrzynski.edziennik.data.db.modules.profiles.Profile import pl.szczodrzynski.edziennik.data.db.modules.profiles.Profile
import pl.szczodrzynski.edziennik.data.db.modules.teachers.Teacher import pl.szczodrzynski.edziennik.data.db.modules.teachers.Teacher
import pl.szczodrzynski.edziennik.data.db.modules.teams.Team import pl.szczodrzynski.edziennik.data.db.modules.teams.Team
import pl.szczodrzynski.edziennik.utils.models.Time
import pl.szczodrzynski.navlib.R import pl.szczodrzynski.navlib.R
import pl.szczodrzynski.navlib.getColorFromRes import pl.szczodrzynski.navlib.getColorFromRes
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
@ -417,15 +422,15 @@ fun TextView.setText(@StringRes resid: Int, vararg formatArgs: Any) {
text = context.getString(resid, *formatArgs) text = context.getString(resid, *formatArgs)
} }
fun JsonObject(vararg properties: Pair<String, Any>): JsonObject { fun JsonObject(vararg properties: Pair<String, Any?>): JsonObject {
return JsonObject().apply { return JsonObject().apply {
for (property in properties) { for (property in properties) {
when (property.second) { when (property.second) {
is JsonElement -> add(property.first, property.second as JsonElement) is JsonElement -> add(property.first, property.second as JsonElement?)
is String -> addProperty(property.first, property.second as String) is String -> addProperty(property.first, property.second as String?)
is Char -> addProperty(property.first, property.second as Char) is Char -> addProperty(property.first, property.second as Char?)
is Number -> addProperty(property.first, property.second as Number) is Number -> addProperty(property.first, property.second as Number?)
is Boolean -> addProperty(property.first, property.second as Boolean) is Boolean -> addProperty(property.first, property.second as Boolean?)
} }
} }
} }
@ -441,6 +446,13 @@ inline fun <T : View> T.onClick(crossinline onClickListener: (v: T) -> Unit) {
} }
} }
@Suppress("UNCHECKED_CAST")
inline fun <T : CompoundButton> T.onChange(crossinline onChangeListener: (v: T, isChecked: Boolean) -> Unit) {
setOnCheckedChangeListener { buttonView, isChecked ->
onChangeListener(buttonView as T, isChecked)
}
}
fun <T> LiveData<T>.observeOnce(lifecycleOwner: LifecycleOwner, observer: Observer<T>) { fun <T> LiveData<T>.observeOnce(lifecycleOwner: LifecycleOwner, observer: Observer<T>) {
observe(lifecycleOwner, object : Observer<T> { observe(lifecycleOwner, object : Observer<T> {
override fun onChanged(t: T?) { override fun onChanged(t: T?) {
@ -494,4 +506,30 @@ fun View.findParentById(targetId: Int): View? {
return viewParent.findParentById(targetId) return viewParent.findParentById(targetId)
} }
return null return null
} }
fun CoroutineScope.startCoroutineTimer(delayMillis: Long = 0, repeatMillis: Long = 0, action: () -> Unit) = launch {
delay(delayMillis)
if (repeatMillis > 0) {
while (true) {
action()
delay(repeatMillis)
}
} else {
action()
}
}
operator fun Time?.compareTo(other: Time?): Int {
if (this == null && other == null)
return 0
if (this == null)
return -1
if (other == null)
return 1
return this.compareTo(other)
}
operator fun StringBuilder.plusAssign(str: String?) {
this.append(str)
}

View File

@ -56,7 +56,7 @@ import pl.szczodrzynski.edziennik.ui.modules.feedback.FeedbackFragment
import pl.szczodrzynski.edziennik.ui.modules.feedback.HelpFragment import pl.szczodrzynski.edziennik.ui.modules.feedback.HelpFragment
import pl.szczodrzynski.edziennik.ui.modules.grades.GradesFragment import pl.szczodrzynski.edziennik.ui.modules.grades.GradesFragment
import pl.szczodrzynski.edziennik.ui.modules.grades.editor.GradesEditorFragment import pl.szczodrzynski.edziennik.ui.modules.grades.editor.GradesEditorFragment
import pl.szczodrzynski.edziennik.ui.modules.home.HomeFragment import pl.szczodrzynski.edziennik.ui.modules.home.HomeFragmentV2
import pl.szczodrzynski.edziennik.ui.modules.homework.HomeworkFragment import pl.szczodrzynski.edziennik.ui.modules.homework.HomeworkFragment
import pl.szczodrzynski.edziennik.ui.modules.login.LoginActivity import pl.szczodrzynski.edziennik.ui.modules.login.LoginActivity
import pl.szczodrzynski.edziennik.ui.modules.messages.MessageFragment import pl.szczodrzynski.edziennik.ui.modules.messages.MessageFragment
@ -122,9 +122,9 @@ class MainActivity : AppCompatActivity() {
val list: MutableList<NavTarget> = mutableListOf() val list: MutableList<NavTarget> = mutableListOf()
// home item // home item
list += NavTarget(DRAWER_ITEM_HOME, R.string.menu_home_page, HomeFragment::class) list += NavTarget(DRAWER_ITEM_HOME, R.string.menu_home_page, HomeFragmentV2::class)
.withTitle(R.string.app_name) .withTitle(R.string.app_name)
.withIcon(CommunityMaterial.Icon2.cmd_home) .withIcon(CommunityMaterial.Icon2.cmd_home_outline)
.isInDrawer(true) .isInDrawer(true)
.isStatic(true) .isStatic(true)
.withPopToHome(false) .withPopToHome(false)
@ -135,50 +135,50 @@ class MainActivity : AppCompatActivity() {
.isInDrawer(true) .isInDrawer(true)
list += NavTarget(DRAWER_ITEM_AGENDA, R.string.menu_agenda, AgendaFragment::class) list += NavTarget(DRAWER_ITEM_AGENDA, R.string.menu_agenda, AgendaFragment::class)
.withIcon(CommunityMaterial.Icon.cmd_calendar) .withIcon(CommunityMaterial.Icon.cmd_calendar_outline)
.withBadgeTypeId(TYPE_EVENT) .withBadgeTypeId(TYPE_EVENT)
.isInDrawer(true) .isInDrawer(true)
list += NavTarget(DRAWER_ITEM_GRADES, R.string.menu_grades, GradesFragment::class) list += NavTarget(DRAWER_ITEM_GRADES, R.string.menu_grades, GradesFragment::class)
.withIcon(CommunityMaterial.Icon2.cmd_numeric_5_box) .withIcon(CommunityMaterial.Icon2.cmd_numeric_5_box_outline)
.withBadgeTypeId(TYPE_GRADE) .withBadgeTypeId(TYPE_GRADE)
.isInDrawer(true) .isInDrawer(true)
list += NavTarget(DRAWER_ITEM_MESSAGES, R.string.menu_messages, MessagesFragment::class) list += NavTarget(DRAWER_ITEM_MESSAGES, R.string.menu_messages, MessagesFragment::class)
.withIcon(CommunityMaterial.Icon.cmd_email) .withIcon(CommunityMaterial.Icon.cmd_email_outline)
.withBadgeTypeId(TYPE_MESSAGE) .withBadgeTypeId(TYPE_MESSAGE)
.isInDrawer(true) .isInDrawer(true)
list += NavTarget(DRAWER_ITEM_HOMEWORK, R.string.menu_homework, HomeworkFragment::class) list += NavTarget(DRAWER_ITEM_HOMEWORK, R.string.menu_homework, HomeworkFragment::class)
.withIcon(SzkolnyFont.Icon.szf_file_document_edit) .withIcon(SzkolnyFont.Icon.szf_notebook_outline)
.withBadgeTypeId(TYPE_HOMEWORK) .withBadgeTypeId(TYPE_HOMEWORK)
.isInDrawer(true) .isInDrawer(true)
list += NavTarget(DRAWER_ITEM_BEHAVIOUR, R.string.menu_notices, BehaviourFragment::class) list += NavTarget(DRAWER_ITEM_BEHAVIOUR, R.string.menu_notices, BehaviourFragment::class)
.withIcon(CommunityMaterial.Icon2.cmd_message_alert) .withIcon(CommunityMaterial.Icon.cmd_emoticon_outline)
.withBadgeTypeId(TYPE_NOTICE) .withBadgeTypeId(TYPE_NOTICE)
.isInDrawer(true) .isInDrawer(true)
list += NavTarget(DRAWER_ITEM_ATTENDANCE, R.string.menu_attendance, AttendanceFragment::class) list += NavTarget(DRAWER_ITEM_ATTENDANCE, R.string.menu_attendance, AttendanceFragment::class)
.withIcon(CommunityMaterial.Icon.cmd_calendar_remove) .withIcon(CommunityMaterial.Icon.cmd_calendar_remove_outline)
.withBadgeTypeId(TYPE_ATTENDANCE) .withBadgeTypeId(TYPE_ATTENDANCE)
.isInDrawer(true) .isInDrawer(true)
list += NavTarget(DRAWER_ITEM_ANNOUNCEMENTS, R.string.menu_announcements, AnnouncementsFragment::class) list += NavTarget(DRAWER_ITEM_ANNOUNCEMENTS, R.string.menu_announcements, AnnouncementsFragment::class)
.withIcon(CommunityMaterial.Icon.cmd_bulletin_board) .withIcon(CommunityMaterial.Icon.cmd_bullhorn_outline)
.withBadgeTypeId(TYPE_ANNOUNCEMENT) .withBadgeTypeId(TYPE_ANNOUNCEMENT)
.isInDrawer(true) .isInDrawer(true)
// static drawer items // static drawer items
list += NavTarget(DRAWER_ITEM_NOTIFICATIONS, R.string.menu_notifications, NotificationsFragment::class) list += NavTarget(DRAWER_ITEM_NOTIFICATIONS, R.string.menu_notifications, NotificationsFragment::class)
.withIcon(CommunityMaterial.Icon.cmd_bell_ring) .withIcon(CommunityMaterial.Icon.cmd_bell_ring_outline)
.isInDrawer(true) .isInDrawer(true)
.isStatic(true) .isStatic(true)
.isBelowSeparator(true) .isBelowSeparator(true)
list += NavTarget(DRAWER_ITEM_SETTINGS, R.string.menu_settings, SettingsNewFragment::class) list += NavTarget(DRAWER_ITEM_SETTINGS, R.string.menu_settings, SettingsNewFragment::class)
.withIcon(CommunityMaterial.Icon2.cmd_settings) .withIcon(CommunityMaterial.Icon2.cmd_settings_outline)
.isInDrawer(true) .isInDrawer(true)
.isStatic(true) .isStatic(true)
.isBelowSeparator(true) .isBelowSeparator(true)
@ -197,7 +197,7 @@ class MainActivity : AppCompatActivity() {
.isInProfileList(false) .isInProfileList(false)
list += NavTarget(DRAWER_PROFILE_SYNC_ALL, R.string.menu_sync_all, null) list += NavTarget(DRAWER_PROFILE_SYNC_ALL, R.string.menu_sync_all, null)
.withIcon(CommunityMaterial.Icon2.cmd_sync) .withIcon(CommunityMaterial.Icon.cmd_download_outline)
.isInProfileList(true) .isInProfileList(true)
@ -434,7 +434,7 @@ class MainActivity : AppCompatActivity() {
navView.coordinator.postDelayed({ navView.coordinator.postDelayed({
CafeBar.builder(this) CafeBar.builder(this)
.content(R.string.rate_snackbar_text) .content(R.string.rate_snackbar_text)
.icon(IconicsDrawable(this).icon(CommunityMaterial.Icon2.cmd_star).size(IconicsSize.dp(20)).color(IconicsColor.colorInt(Themes.getPrimaryTextColor(this)))) .icon(IconicsDrawable(this).icon(CommunityMaterial.Icon2.cmd_star_outline).size(IconicsSize.dp(20)).color(IconicsColor.colorInt(Themes.getPrimaryTextColor(this))))
.positiveText(R.string.rate_snackbar_positive) .positiveText(R.string.rate_snackbar_positive)
.positiveColor(-0xb350b0) .positiveColor(-0xb350b0)
.negativeText(R.string.rate_snackbar_negative) .negativeText(R.string.rate_snackbar_negative)
@ -471,7 +471,7 @@ class MainActivity : AppCompatActivity() {
bottomSheet.appendItems( bottomSheet.appendItems(
BottomSheetPrimaryItem(false) BottomSheetPrimaryItem(false)
.withTitle(R.string.menu_sync) .withTitle(R.string.menu_sync)
.withIcon(CommunityMaterial.Icon2.cmd_sync) .withIcon(CommunityMaterial.Icon.cmd_download_outline)
.withOnClickListener(View.OnClickListener { .withOnClickListener(View.OnClickListener {
bottomSheet.close() bottomSheet.close()
SyncViewListDialog(this, navTargetId) SyncViewListDialog(this, navTargetId)
@ -479,17 +479,17 @@ class MainActivity : AppCompatActivity() {
BottomSheetSeparatorItem(false), BottomSheetSeparatorItem(false),
BottomSheetPrimaryItem(false) BottomSheetPrimaryItem(false)
.withTitle(R.string.menu_settings) .withTitle(R.string.menu_settings)
.withIcon(CommunityMaterial.Icon2.cmd_settings) .withIcon(CommunityMaterial.Icon2.cmd_settings_outline)
.withOnClickListener(View.OnClickListener { loadTarget(DRAWER_ITEM_SETTINGS) }), .withOnClickListener(View.OnClickListener { loadTarget(DRAWER_ITEM_SETTINGS) }),
BottomSheetPrimaryItem(false) BottomSheetPrimaryItem(false)
.withTitle(R.string.menu_feedback) .withTitle(R.string.menu_feedback)
.withIcon(CommunityMaterial.Icon2.cmd_help_circle) .withIcon(CommunityMaterial.Icon2.cmd_help_circle_outline)
.withOnClickListener(View.OnClickListener { loadTarget(TARGET_FEEDBACK) }) .withOnClickListener(View.OnClickListener { loadTarget(TARGET_FEEDBACK) })
) )
if (App.devMode) { if (App.devMode) {
bottomSheet += BottomSheetPrimaryItem(false) bottomSheet += BottomSheetPrimaryItem(false)
.withTitle(R.string.menu_debug) .withTitle(R.string.menu_debug)
.withIcon(CommunityMaterial.Icon.cmd_android_debug_bridge) .withIcon(CommunityMaterial.Icon.cmd_android_studio)
.withOnClickListener(View.OnClickListener { loadTarget(DRAWER_ITEM_DEBUG) }) .withOnClickListener(View.OnClickListener { loadTarget(DRAWER_ITEM_DEBUG) })
} }
@ -537,9 +537,14 @@ class MainActivity : AppCompatActivity() {
DRAWER_ITEM_MESSAGES -> MessagesFragment.pageSelection DRAWER_ITEM_MESSAGES -> MessagesFragment.pageSelection
else -> 0 else -> 0
} }
val arguments = when (navTargetId) {
DRAWER_ITEM_TIMETABLE -> JsonObject("weekStart" to TimetableFragment.pageSelection?.weekStart?.stringY_m_d)
else -> null
}
EdziennikTask.syncProfile( EdziennikTask.syncProfile(
App.profileId, App.profileId,
listOf(navTargetId to fragmentParam) listOf(navTargetId to fragmentParam),
arguments
).enqueue(this) ).enqueue(this)
} }
@Subscribe(threadMode = ThreadMode.MAIN) @Subscribe(threadMode = ThreadMode.MAIN)
@ -694,10 +699,13 @@ class MainActivity : AppCompatActivity() {
app.profile == null -> { app.profile == null -> {
if (intentProfileId == -1) if (intentProfileId == -1)
intentProfileId = app.appSharedPrefs.getInt("current_profile_id", 1) intentProfileId = app.appSharedPrefs.getInt("current_profile_id", 1)
loadProfile(intentProfileId, intentTargetId) loadProfile(intentProfileId, intentTargetId, extras)
} }
intentProfileId != -1 -> { intentProfileId != -1 -> {
loadProfile(intentProfileId, intentTargetId) if (app.profile.id != intentProfileId)
loadProfile(intentProfileId, intentTargetId, extras)
else
loadTarget(intentTargetId, extras)
} }
intentTargetId != -1 -> { intentTargetId != -1 -> {
drawer.currentProfile = app.profile.id drawer.currentProfile = app.profile.id

View File

@ -311,13 +311,14 @@ public class Notifier {
\____/| .__/ \__,_|\__,_|\__\___||___/ \____/| .__/ \__,_|\__,_|\__\___||___/
| | | |
|*/ |*/
public void notificationUpdatesShow(String updateVersion, String updateUrl, String updateFilename) { public void notificationUpdatesShow(String updateVersion, String updateUrl, String updateFilename, boolean updateDirect) {
if (!app.appConfig.notifyAboutUpdates) if (!app.appConfig.notifyAboutUpdates)
return; return;
Intent notificationIntent = new Intent(app.getContext(), BootReceiver.NotificationActionService.class) Intent notificationIntent = new Intent(app.getContext(), BootReceiver.NotificationActionService.class)
.putExtra("update_version", updateVersion) .putExtra("update_version", updateVersion)
.putExtra("update_url", updateUrl) .putExtra("update_url", updateUrl)
.putExtra("update_filename", updateFilename); .putExtra("update_filename", updateFilename)
.putExtra("update_direct", updateDirect);
PendingIntent pendingIntent = PendingIntent.getService(app.getContext(), 0, PendingIntent pendingIntent = PendingIntent.getService(app.getContext(), 0,
notificationIntent, PendingIntent.FLAG_CANCEL_CURRENT); notificationIntent, PendingIntent.FLAG_CANCEL_CURRENT);

View File

@ -87,7 +87,7 @@ class WidgetTimetable : AppWidgetProvider() {
.colorInt(Color.WHITE) .colorInt(Color.WHITE)
.sizeDp(if (widgetConfig.bigStyle) 24 else 16).toBitmap()) .sizeDp(if (widgetConfig.bigStyle) 24 else 16).toBitmap())
views.setImageViewBitmap(R.id.widgetTimetableSync, IconicsDrawable(context, CommunityMaterial.Icon2.cmd_sync) views.setImageViewBitmap(R.id.widgetTimetableSync, IconicsDrawable(context, CommunityMaterial.Icon.cmd_download_outline)
.colorInt(Color.WHITE) .colorInt(Color.WHITE)
.sizeDp(if (widgetConfig.bigStyle) 24 else 16).toBitmap()) .sizeDp(if (widgetConfig.bigStyle) 24 else 16).toBitmap())

View File

@ -60,6 +60,9 @@ class ApiService : Service() {
private val notification by lazy { EdziennikNotification(this) } private val notification by lazy { EdziennikNotification(this) }
private var lastEventTime = System.currentTimeMillis()
private var taskCancelTries = 0
/* ______ _ _ _ _ _____ _ _ _ _ /* ______ _ _ _ _ _____ _ _ _ _
| ____| | | (_) (_) | / ____| | | | | | | | ____| | | (_) (_) | / ____| | | | | | |
| |__ __| |_____ ___ _ __ _ __ _| | __ | | __ _| | | |__ __ _ ___| | __ | |__ __| |_____ ___ _ __ _ __ _| | __ | | __ _| | | |__ __ _ ___| | __
@ -68,22 +71,17 @@ class ApiService : Service() {
|______\__,_/___|_|\___|_| |_|_| |_|_|_|\_\ \_____\__,_|_|_|_.__/ \__,_|\___|_|\*/ |______\__,_/___|_|\___|_| |_|_| |_|_|_|\_\ \_____\__,_|_|_|_.__/ \__,_|\___|_|\*/
private val taskCallback = object : EdziennikCallback { private val taskCallback = object : EdziennikCallback {
override fun onCompleted() { override fun onCompleted() {
lastEventTime = System.currentTimeMillis()
d(TAG, "Task $taskRunningId (profile $taskProfileId) - $taskProgressText - finished") d(TAG, "Task $taskRunningId (profile $taskProfileId) - $taskProgressText - finished")
//if (!taskCancelled) { EventBus.getDefault().post(ApiTaskFinishedEvent(taskProfileId))
EventBus.getDefault().post(ApiTaskFinishedEvent(taskProfileId)) clearTask()
//}
taskIsRunning = false
taskRunningId = -1
taskRunning = null
taskProfileId = -1
taskProgress = -1f
taskProgressText = null
notification.setIdle().post() notification.setIdle().post()
runTask() runTask()
} }
override fun onError(apiError: ApiError) { override fun onError(apiError: ApiError) {
lastEventTime = System.currentTimeMillis()
d(TAG, "Task $taskRunningId threw an error - $apiError") d(TAG, "Task $taskRunningId threw an error - $apiError")
apiError.profileId = taskProfileId apiError.profileId = taskProfileId
EventBus.getDefault().post(ApiTaskErrorEvent(apiError)) EventBus.getDefault().post(ApiTaskErrorEvent(apiError))
@ -92,9 +90,7 @@ class ApiService : Service() {
if (apiError.isCritical) { if (apiError.isCritical) {
taskRunning?.cancel() taskRunning?.cancel()
notification.setCriticalError().post() notification.setCriticalError().post()
taskRunning = null clearTask()
taskIsRunning = false
taskRunningId = -1
runTask() runTask()
} }
else { else {
@ -103,6 +99,7 @@ class ApiService : Service() {
} }
override fun onProgress(step: Float) { override fun onProgress(step: Float) {
lastEventTime = System.currentTimeMillis()
if (step <= 0) if (step <= 0)
return return
if (taskProgress < 0) if (taskProgress < 0)
@ -115,6 +112,7 @@ class ApiService : Service() {
} }
override fun onStartProgress(stringRes: Int) { override fun onStartProgress(stringRes: Int) {
lastEventTime = System.currentTimeMillis()
taskProgressText = getString(stringRes) taskProgressText = getString(stringRes)
d(TAG, "Task $taskRunningId progress: $taskProgressText") d(TAG, "Task $taskRunningId progress: $taskProgressText")
EventBus.getDefault().post(ApiTaskProgressEvent(taskProfileId, taskProgress, taskProgressText)) EventBus.getDefault().post(ApiTaskProgressEvent(taskProfileId, taskProgress, taskProgressText))
@ -129,6 +127,7 @@ class ApiService : Service() {
| | (_| \__ \ < | __/> < __/ (__| |_| | |_| | (_) | | | | | | (_| \__ \ < | __/> < __/ (__| |_| | |_| | (_) | | | |
|_|\__,_|___/_|\_\ \___/_/\_\___|\___|\__,_|\__|_|\___/|_| |*/ |_|\__,_|___/_|\_\ \___/_/\_\___|\___|\__,_|\__|_|\___/|_| |*/
private fun runTask() { private fun runTask() {
checkIfTaskFrozen()
if (taskIsRunning) if (taskIsRunning)
return return
if (taskCancelled || serviceClosed || (taskQueue.isEmpty() && finishingTaskQueue.isEmpty())) { if (taskCancelled || serviceClosed || (taskQueue.isEmpty() && finishingTaskQueue.isEmpty())) {
@ -137,6 +136,8 @@ class ApiService : Service() {
return return
} }
lastEventTime = System.currentTimeMillis()
val task = if (taskQueue.isEmpty()) finishingTaskQueue.removeAt(0) else taskQueue.removeAt(0) val task = if (taskQueue.isEmpty()) finishingTaskQueue.removeAt(0) else taskQueue.removeAt(0)
task.taskId = ++taskMaximumId task.taskId = ++taskMaximumId
task.prepare(app) task.prepare(app)
@ -166,6 +167,48 @@ class ApiService : Service() {
} }
} }
/**
* Check if a task is inactive for more than 30 seconds.
* If the user tries to cancel a task with no success at least three times,
* consider it frozen as well.
*
* This usually means it is broken and won't become active again.
* This method cancels the task and removes any pointers to it.
*/
private fun checkIfTaskFrozen(): Boolean {
if (System.currentTimeMillis() - lastEventTime > 30*1000
|| taskCancelTries >= 3) {
val time = System.currentTimeMillis() - lastEventTime
d(TAG, "!!! Task $taskRunningId froze for $time ms. $taskRunning")
clearTask()
return true
}
return false
}
/**
* Stops the service if the current task is frozen/broken.
*/
private fun stopIfTaskFrozen() {
if (checkIfTaskFrozen()) {
stopSelf()
}
}
/**
* Remove any task descriptors or pointers from the service.
*/
private fun clearTask() {
taskIsRunning = false
taskRunningId = -1
taskRunning = null
taskProfileId = -1
taskProgress = -1f
taskProgressText = null
taskCancelled = false
taskCancelTries = 0
}
private fun allCompleted() { private fun allCompleted() {
EventBus.getDefault().post(ApiTaskAllFinishedEvent()) EventBus.getDefault().post(ApiTaskAllFinishedEvent())
stopSelf() stopSelf()
@ -211,8 +254,10 @@ class ApiService : Service() {
EventBus.getDefault().removeStickyEvent(request) EventBus.getDefault().removeStickyEvent(request)
d(TAG, request.toString()) d(TAG, request.toString())
taskCancelTries++
taskCancelled = true taskCancelled = true
taskRunning?.cancel() taskRunning?.cancel()
stopIfTaskFrozen()
} }
@Subscribe(sticky = true, threadMode = ThreadMode.ASYNC) @Subscribe(sticky = true, threadMode = ThreadMode.ASYNC)
fun onServiceCloseRequest(request: ServiceCloseRequest) { fun onServiceCloseRequest(request: ServiceCloseRequest) {

View File

@ -45,8 +45,8 @@ class DataNotifications(val data: Data) {
return@run return@run
} }
for (change in app.db.lessonChangeDao().getNotNotifiedNow(profileId)) { for (lesson in app.db.timetableDao().getNotNotifiedNow(profileId)) {
val text = app.getString(R.string.notification_lesson_change_format, change.changeTypeStr(app), if (change.lessonDate == null) "" else change.lessonDate!!.formattedString, change.subjectLongName) val text = app.getString(R.string.notification_lesson_change_format, lesson.getDisplayChangeType(app), if (lesson.displayDate == null) "" else lesson.displayDate!!.formattedString, lesson.changeSubjectName)
data.notifications += Notification( data.notifications += Notification(
title = app.getNotificationTitle(TYPE_TIMETABLE_LESSON_CHANGE), title = app.getNotificationTitle(TYPE_TIMETABLE_LESSON_CHANGE),
text = text, text = text,
@ -54,8 +54,8 @@ class DataNotifications(val data: Data) {
profileId = profileId, profileId = profileId,
profileName = profileName, profileName = profileName,
viewId = DRAWER_ITEM_TIMETABLE, viewId = DRAWER_ITEM_TIMETABLE,
addedDate = change.addedDate addedDate = lesson.addedDate
).addExtra("timetableDate", change.lessonDate?.value?.toLong()) ).addExtra("timetableDate", lesson.displayDate?.stringY_m_d ?: "")
} }
for (event in app.db.eventDao().getNotNotifiedNow(profileId)) { for (event in app.db.eventDao().getNotNotifiedNow(profileId)) {
@ -186,10 +186,10 @@ class DataNotifications(val data: Data) {
val luckyNumbers = app.db.luckyNumberDao().getNotNotifiedNow(profileId) val luckyNumbers = app.db.luckyNumberDao().getNotNotifiedNow(profileId)
luckyNumbers?.removeAll { it.date < today } luckyNumbers?.removeAll { it.date < today }
luckyNumbers?.forEach { luckyNumber -> luckyNumbers?.forEach { luckyNumber ->
val text = when { val text = when (luckyNumber.date.value) {
luckyNumber.date.value == todayValue -> // LN for today todayValue -> // LN for today
app.getString(if (profile.studentNumber != -1 && profile.studentNumber == luckyNumber.number) R.string.notification_lucky_number_yours_format else R.string.notification_lucky_number_format, luckyNumber.number) app.getString(if (profile.studentNumber != -1 && profile.studentNumber == luckyNumber.number) R.string.notification_lucky_number_yours_format else R.string.notification_lucky_number_format, luckyNumber.number)
luckyNumber.date.value == todayValue + 1 -> // LN for tomorrow todayValue + 1 -> // LN for tomorrow
app.getString(if (profile.studentNumber != -1 && profile.studentNumber == luckyNumber.number) R.string.notification_lucky_number_yours_tomorrow_format else R.string.notification_lucky_number_tomorrow_format, luckyNumber.number) app.getString(if (profile.studentNumber != -1 && profile.studentNumber == luckyNumber.number) R.string.notification_lucky_number_yours_tomorrow_format else R.string.notification_lucky_number_tomorrow_format, luckyNumber.number)
else -> // LN for later else -> // LN for later
app.getString(if (profile.studentNumber != -1 && profile.studentNumber == luckyNumber.number) R.string.notification_lucky_number_yours_later_format else R.string.notification_lucky_number_later_format, luckyNumber.date.formattedString, luckyNumber.number) app.getString(if (profile.studentNumber != -1 && profile.studentNumber == luckyNumber.number) R.string.notification_lucky_number_yours_later_format else R.string.notification_lucky_number_later_format, luckyNumber.date.formattedString, luckyNumber.number)
@ -207,4 +207,4 @@ class DataNotifications(val data: Data) {
data.db.metadataDao().setAllNotified(profileId, true) data.db.metadataDao().setAllNotified(profileId, true)
}} }}
} }

View File

@ -39,12 +39,16 @@ const val ERROR_REQUEST_HTTP_404 = 54
const val ERROR_REQUEST_HTTP_405 = 55 const val ERROR_REQUEST_HTTP_405 = 55
const val ERROR_REQUEST_HTTP_410 = 56 const val ERROR_REQUEST_HTTP_410 = 56
const val ERROR_REQUEST_HTTP_500 = 57 const val ERROR_REQUEST_HTTP_500 = 57
const val ERROR_REQUEST_FAILURE_HOSTNAME_NOT_FOUND = 60
const val ERROR_REQUEST_FAILURE_TIMEOUT = 61
const val ERROR_REQUEST_FAILURE_NO_INTERNET = 62
const val ERROR_RESPONSE_EMPTY = 100 const val ERROR_RESPONSE_EMPTY = 100
const val ERROR_LOGIN_DATA_MISSING = 101 const val ERROR_LOGIN_DATA_MISSING = 101
const val ERROR_PROFILE_MISSING = 105 const val ERROR_PROFILE_MISSING = 105
const val ERROR_INVALID_LOGIN_MODE = 110 const val ERROR_INVALID_LOGIN_MODE = 110
const val ERROR_LOGIN_METHOD_NOT_SATISFIED = 111 const val ERROR_LOGIN_METHOD_NOT_SATISFIED = 111
const val ERROR_NOT_IMPLEMENTED = 112 const val ERROR_NOT_IMPLEMENTED = 112
const val ERROR_FILE_DOWNLOAD = 113
const val ERROR_NO_STUDENTS_IN_ACCOUNT = 115 const val ERROR_NO_STUDENTS_IN_ACCOUNT = 115
@ -104,6 +108,7 @@ const val ERROR_LIBRUS_MESSAGES_OTHER = 178
const val ERROR_LOGIN_LIBRUS_MESSAGES_INVALID_LOGIN = 179 const val ERROR_LOGIN_LIBRUS_MESSAGES_INVALID_LOGIN = 179
const val ERROR_LOGIN_LIBRUS_PORTAL_INVALID_LOGIN = 180 const val ERROR_LOGIN_LIBRUS_PORTAL_INVALID_LOGIN = 180
const val ERROR_LIBRUS_API_MAINTENANCE = 181 const val ERROR_LIBRUS_API_MAINTENANCE = 181
const val ERROR_LIBRUS_PORTAL_MAINTENANCE = 182
const val ERROR_LOGIN_MOBIDZIENNIK_WEB_INVALID_LOGIN = 201 const val ERROR_LOGIN_MOBIDZIENNIK_WEB_INVALID_LOGIN = 201
const val ERROR_LOGIN_MOBIDZIENNIK_WEB_OLD_PASSWORD = 202 const val ERROR_LOGIN_MOBIDZIENNIK_WEB_OLD_PASSWORD = 202

View File

@ -37,6 +37,16 @@ object Regexes {
"""events: (.+),$""".toRegex(RegexOption.MULTILINE) """events: (.+),$""".toRegex(RegexOption.MULTILINE)
} }
val MOBIDZIENNIK_MESSAGE_READ_DATE by lazy {
"""czas przeczytania:.+?,\s([0-9]+)\s(.+?)\s([0-9]{4}),\sgodzina\s([0-9:]+)""".toRegex(RegexOption.DOT_MATCHES_ALL)
}
val MOBIDZIENNIK_MESSAGE_SENT_READ_DATE by lazy {
""".+?,\s([0-9]+)\s(.+?)\s([0-9]{4}),\sgodzina\s([0-9:]+)""".toRegex(RegexOption.DOT_MATCHES_ALL)
}
val MOBIDZIENNIK_MESSAGE_ATTACHMENT by lazy {
"""href="https://.+?\.mobidziennik.pl/.+?&(?:amp;)?zalacznik=([0-9]+)"(?:.+?<small.+?\(([0-9.]+)\s(M|K|G|)B\))*""".toRegex(RegexOption.DOT_MATCHES_ALL)
}
val IDZIENNIK_LOGIN_HIDDEN_FIELDS by lazy { val IDZIENNIK_LOGIN_HIDDEN_FIELDS by lazy {
@ -61,7 +71,15 @@ object Regexes {
"""<option.*?value="([0-9]+)"\sdata-id-ucznia="([A-z0-9]+?)".*?>(.+?)\s(.+?)\s*\((.+?),\s*(.+?)\)</option>""".toRegex(RegexOption.DOT_MATCHES_ALL) """<option.*?value="([0-9]+)"\sdata-id-ucznia="([A-z0-9]+?)".*?>(.+?)\s(.+?)\s*\((.+?),\s*(.+?)\)</option>""".toRegex(RegexOption.DOT_MATCHES_ALL)
} }
val VULCAN_SHITFT_ANNOTATION by lazy { val VULCAN_SHITFT_ANNOTATION by lazy {
"""\(przeniesiona (z|na) lekcj[ię] ([0-9]+), (.+)\)""".toRegex() """\(przeniesiona (z|na) lekcj[ię] ([0-9]+), (.+)\)""".toRegex()
} }
val LIBRUS_ATTACHMENT_KEY by lazy {
"""singleUseKey=([0-9A-f_]+)""".toRegex()
}
} }

View File

@ -0,0 +1,14 @@
/*
* Copyright (c) Kacper Ziubryniewicz 2019-11-24
*/
package pl.szczodrzynski.edziennik.api.v2.events
data class AttachmentGetEvent(val profileId: Int, val messageId: Long, val attachmentId: Long,
var eventType: Int = TYPE_PROGRESS, val fileName: String? = null,
val bytesWritten: Long = 0) {
companion object {
const val TYPE_PROGRESS = 0
const val TYPE_FINISHED = 1
}
}

View File

@ -24,6 +24,7 @@ open class EdziennikTask(override val profileId: Int, val request: Any) : IApiTa
fun syncProfileList(profileList: List<Int>) = EdziennikTask(-1, SyncProfileListRequest(profileList)) fun syncProfileList(profileList: List<Int>) = EdziennikTask(-1, SyncProfileListRequest(profileList))
fun messageGet(profileId: Int, message: MessageFull) = EdziennikTask(profileId, MessageGetRequest(message)) fun messageGet(profileId: Int, message: MessageFull) = EdziennikTask(profileId, MessageGetRequest(message))
fun announcementsRead(profileId: Int) = EdziennikTask(profileId, AnnouncementsReadRequest()) fun announcementsRead(profileId: Int) = EdziennikTask(profileId, AnnouncementsReadRequest())
fun attachmentGet(profileId: Int, messageId: Long, attachmentId: Long, attachmentName: String) = EdziennikTask(profileId, AttachmentGetRequest(messageId, attachmentId, attachmentName))
} }
private lateinit var loginStore: LoginStore private lateinit var loginStore: LoginStore
@ -35,8 +36,7 @@ open class EdziennikTask(override val profileId: Int, val request: Any) : IApiTa
loginStore = request.loginStore loginStore = request.loginStore
// save the profile ID and name as the current task's // save the profile ID and name as the current task's
taskName = app.getString(R.string.edziennik_notification_api_first_login_title) taskName = app.getString(R.string.edziennik_notification_api_first_login_title)
} } else {
else {
// get the requested profile and login store // get the requested profile and login store
val profile = app.db.profileDao().getByIdNow(profileId) val profile = app.db.profileDao().getByIdNow(profileId)
this.profile = profile this.profile = profile
@ -67,12 +67,14 @@ open class EdziennikTask(override val profileId: Int, val request: Any) : IApiTa
when (request) { when (request) {
is SyncProfileRequest -> edziennikInterface?.sync( is SyncProfileRequest -> edziennikInterface?.sync(
featureIds = request.viewIds?.flatMap { Features.getIdsByView(it.first, it.second) } ?: Features.getAllIds(), featureIds = request.viewIds?.flatMap { Features.getIdsByView(it.first, it.second) }
?: Features.getAllIds(),
viewId = request.viewIds?.get(0)?.first, viewId = request.viewIds?.get(0)?.first,
arguments = request.arguments) arguments = request.arguments)
is MessageGetRequest -> edziennikInterface?.getMessage(request.message) is MessageGetRequest -> edziennikInterface?.getMessage(request.message)
is FirstLoginRequest -> edziennikInterface?.firstLogin() is FirstLoginRequest -> edziennikInterface?.firstLogin()
is AnnouncementsReadRequest -> edziennikInterface?.markAllAnnouncementsAsRead() is AnnouncementsReadRequest -> edziennikInterface?.markAllAnnouncementsAsRead()
is AttachmentGetRequest -> edziennikInterface?.getAttachment(request.messageId, request.attachmentId, request.attachmentName)
} }
} }
@ -90,4 +92,5 @@ open class EdziennikTask(override val profileId: Int, val request: Any) : IApiTa
data class SyncProfileListRequest(val profileList: List<Int>) data class SyncProfileListRequest(val profileList: List<Int>)
data class MessageGetRequest(val message: MessageFull) data class MessageGetRequest(val message: MessageFull)
class AnnouncementsReadRequest class AnnouncementsReadRequest
data class AttachmentGetRequest(val messageId: Long, val attachmentId: Long, val attachmentName: String)
} }

View File

@ -9,6 +9,7 @@ import pl.szczodrzynski.edziennik.MainActivity
import pl.szczodrzynski.edziennik.Notifier.ID_NOTIFICATIONS import pl.szczodrzynski.edziennik.Notifier.ID_NOTIFICATIONS
import pl.szczodrzynski.edziennik.R import pl.szczodrzynski.edziennik.R
import pl.szczodrzynski.edziennik.api.v2.interfaces.EdziennikCallback import pl.szczodrzynski.edziennik.api.v2.interfaces.EdziennikCallback
import pl.szczodrzynski.edziennik.data.db.modules.notification.getNotificationTitle
import pl.szczodrzynski.edziennik.utils.models.Notification import pl.szczodrzynski.edziennik.utils.models.Notification
import kotlin.math.min import kotlin.math.min
@ -33,9 +34,9 @@ class NotifyTask : IApiTask(-1) {
val pendingIntent = PendingIntent.getActivity(app, notification.id, intent, 0) val pendingIntent = PendingIntent.getActivity(app, notification.id, intent, 0)
val notificationBuilder = NotificationCompat.Builder(app, app.notifier.notificationGroup) val notificationBuilder = NotificationCompat.Builder(app, app.notifier.notificationGroup)
// title, text, type, date // title, text, type, date
.setContentTitle(notification.title) .setContentTitle(notification.profileName)
.setContentText(notification.text) .setContentText(notification.text)
.setSubText(Notification.stringType(app, notification.type)) .setSubText(app.getNotificationTitle(notification.type))
.setWhen(notification.addedDate) .setWhen(notification.addedDate)
.setTicker(app.getString(R.string.notification_ticker_format, Notification.stringType(app, notification.type))) .setTicker(app.getString(R.string.notification_ticker_format, Notification.stringType(app, notification.type)))
// icon, color, lights, priority // icon, color, lights, priority

View File

@ -138,10 +138,12 @@ class DataIdziennik(app: App, profile: Profile?, loginStore: LoginStore) : Data(
val teacher = teacherList.singleOrNull { it.fullName == "$firstName $lastName" } val teacher = teacherList.singleOrNull { it.fullName == "$firstName $lastName" }
return validateTeacher(teacher, firstName, lastName) return validateTeacher(teacher, firstName, lastName)
} }
fun getTeacher(firstNameChar: Char, lastName: String): Teacher { fun getTeacher(firstNameChar: Char, lastName: String): Teacher {
val teacher = teacherList.singleOrNull { it.shortName == "$firstNameChar.$lastName" } val teacher = teacherList.singleOrNull { it.shortName == "$firstNameChar.$lastName" }
return validateTeacher(teacher, firstNameChar.toString(), lastName) return validateTeacher(teacher, firstNameChar.toString(), lastName)
} }
fun getTeacherByLastFirst(nameLastFirst: String): Teacher { fun getTeacherByLastFirst(nameLastFirst: String): Teacher {
val nameParts = nameLastFirst.split(" ") val nameParts = nameLastFirst.split(" ")
return if (nameParts.size == 1) getTeacher(nameParts[0], "") else getTeacher(nameParts[1], nameParts[0]) return if (nameParts.size == 1) getTeacher(nameParts[0], "") else getTeacher(nameParts[1], nameParts[0])

View File

@ -70,6 +70,10 @@ class Idziennik(val app: App, val profile: Profile?, val loginStore: LoginStore,
} }
override fun getAttachment(messageId: Long, attachmentId: Long, attachmentName: String) {
}
override fun firstLogin() { override fun firstLogin() {
IdziennikFirstLogin(data) { IdziennikFirstLogin(data) {
completed() completed()

View File

@ -1,5 +1,5 @@
/* /*
* Copyright (c) Kuba Szczodrzyński 2019-10-27. * Copyright (c) Kacper Ziubryniewicz 2019-11-22
*/ */
package pl.szczodrzynski.edziennik.api.v2.idziennik.data.web package pl.szczodrzynski.edziennik.api.v2.idziennik.data.web
@ -12,33 +12,38 @@ import pl.szczodrzynski.edziennik.api.v2.idziennik.DataIdziennik
import pl.szczodrzynski.edziennik.api.v2.idziennik.ENDPOINT_IDZIENNIK_WEB_TIMETABLE import pl.szczodrzynski.edziennik.api.v2.idziennik.ENDPOINT_IDZIENNIK_WEB_TIMETABLE
import pl.szczodrzynski.edziennik.api.v2.idziennik.data.IdziennikWeb import pl.szczodrzynski.edziennik.api.v2.idziennik.data.IdziennikWeb
import pl.szczodrzynski.edziennik.api.v2.models.ApiError import pl.szczodrzynski.edziennik.api.v2.models.ApiError
import pl.szczodrzynski.edziennik.api.v2.models.DataRemoveModel
import pl.szczodrzynski.edziennik.data.db.modules.api.SYNC_ALWAYS import pl.szczodrzynski.edziennik.data.db.modules.api.SYNC_ALWAYS
import pl.szczodrzynski.edziennik.data.db.modules.lessons.Lesson
import pl.szczodrzynski.edziennik.data.db.modules.lessons.LessonChange
import pl.szczodrzynski.edziennik.data.db.modules.lessons.LessonChange.TYPE_CANCELLED
import pl.szczodrzynski.edziennik.data.db.modules.lessons.LessonChange.TYPE_CHANGE
import pl.szczodrzynski.edziennik.data.db.modules.lessons.LessonRange import pl.szczodrzynski.edziennik.data.db.modules.lessons.LessonRange
import pl.szczodrzynski.edziennik.data.db.modules.metadata.Metadata import pl.szczodrzynski.edziennik.data.db.modules.metadata.Metadata
import pl.szczodrzynski.edziennik.data.db.modules.timetable.Lesson
import pl.szczodrzynski.edziennik.utils.Utils.d
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
import pl.szczodrzynski.edziennik.utils.models.Week import pl.szczodrzynski.edziennik.utils.models.Week
class IdziennikWebTimetable(override val data: DataIdziennik, class IdziennikWebTimetable(override val data: DataIdziennik,
val onSuccess: () -> Unit) : IdziennikWeb(data) { val onSuccess: () -> Unit) : IdziennikWeb(data) {
companion object { companion object {
private const val TAG = "IdziennikWebTimetable" private const val TAG = "IdziennikWebTimetable"
} }
init { init { data.profile?.also { profile ->
val weekStart = Week.getWeekStart() val currentWeekStart = Week.getWeekStart()
if (Date.getToday().weekDay > 4) { if (Date.getToday().weekDay > 4) {
weekStart.stepForward(0, 0, 7) currentWeekStart.stepForward(0, 0, 7)
} }
val getDate = data.arguments?.getString("weekStart") ?: currentWeekStart.stringY_m_d
val weekStart = Date.fromY_m_d(getDate)
val weekEnd = weekStart.clone().stepForward(0, 0, 6)
webApiGet(TAG, IDZIENNIK_WEB_TIMETABLE, mapOf( webApiGet(TAG, IDZIENNIK_WEB_TIMETABLE, mapOf(
"idPozDziennika" to data.registerId, "idPozDziennika" to data.registerId,
"pidRokSzkolny" to data.schoolYearId, "pidRokSzkolny" to data.schoolYearId,
"data" to weekStart.stringY_m_d+"T10:00:00.000Z" "data" to "${weekStart.stringY_m_d}T10:00:00.000Z"
)) { result -> )) { result ->
val json = result.getJsonObject("d") ?: run { val json = result.getJsonObject("d") ?: run {
data.error(ApiError(TAG, ERROR_IDZIENNIK_WEB_REQUEST_NO_DATA) data.error(ApiError(TAG, ERROR_IDZIENNIK_WEB_REQUEST_NO_DATA)
@ -56,73 +61,132 @@ class IdziennikWebTimetable(override val data: DataIdziennik,
data.lessonRanges[lessonRange.lessonNumber] = lessonRange data.lessonRanges[lessonRange.lessonNumber] = lessonRange
} }
val dates = mutableSetOf<Int>()
val lessons = mutableListOf<Lesson>()
json.getJsonArray("Przedmioty")?.asJsonObjectList()?.forEach { lesson -> json.getJsonArray("Przedmioty")?.asJsonObjectList()?.forEach { lesson ->
val subject = data.getSubject( val subject = data.getSubject(
lesson.getString("Nazwa") ?: return@forEach, lesson.getString("Nazwa") ?: return@forEach,
lesson.getLong("Id"), lesson.getLong("Id"),
lesson.getString("Skrot") ?: "" lesson.getString("Skrot") ?: ""
) )
val teacher = data.getTeacherByFDotLast(lesson.getString("Nauczyciel") ?: return@forEach) val teacher = data.getTeacherByFDotLast(lesson.getString("Nauczyciel")
val weekDay = lesson.getInt("DzienTygodnia")?.minus(1) ?: return@forEach ?: return@forEach)
val lessonRange = data.lessonRanges[lesson.getInt("Godzina")?.plus(1) ?: return@forEach]
val lessonObject = Lesson( val newSubjectName = lesson.getString("PrzedmiotZastepujacy")
profileId, val newSubject = when (newSubjectName.isNullOrBlank()) {
weekDay, true -> null
lessonRange.startTime, else -> data.getSubject(newSubjectName, null, newSubjectName)
lessonRange.endTime
).apply {
subjectId = subject.id
teacherId = teacher.id
teamId = data.teamClass?.id ?: -1
classroomName = lesson.getString("NazwaSali") ?: ""
} }
data.lessonList.add(lessonObject) val newTeacherName = lesson.getString("NauZastepujacy")
val newTeacher = when (newTeacherName.isNullOrBlank()) {
true -> null
else -> data.getTeacherByFDotLast(newTeacherName)
}
val weekDay = lesson.getInt("DzienTygodnia")?.minus(1) ?: return@forEach
val lessonRange = data.lessonRanges[lesson.getInt("Godzina")?.plus(1)
?: return@forEach]
val lessonDate = weekStart.clone().stepForward(0, 0, weekDay)
val classroom = lesson.getString("NazwaSali")
val type = lesson.getInt("TypZastepstwa") ?: -1 val type = lesson.getInt("TypZastepstwa") ?: -1
if (type != -1) {
// we have a lesson change to process
val lessonChangeObject = LessonChange(
profileId,
weekStart.clone().stepForward(0, 0, weekDay),
lessonObject.startTime,
lessonObject.endTime
)
lessonChangeObject.teamId = lessonObject.teamId val lessonObject = Lesson(profileId, -1)
lessonChangeObject.teacherId = lessonObject.teacherId
lessonChangeObject.subjectId = lessonObject.subjectId when (type) {
lessonChangeObject.classroomName = lessonObject.classroomName 1, 2, 3, 4, 5 -> {
when (type) { lessonObject.apply {
0 -> lessonChangeObject.type = TYPE_CANCELLED this.type = Lesson.TYPE_CHANGE
1, 2, 3, 4, 5 -> {
lessonChangeObject.type = TYPE_CHANGE this.date = lessonDate
val newTeacher = lesson.getString("NauZastepujacy") this.lessonNumber = lessonRange.lessonNumber
val newSubject = lesson.getString("PrzedmiotZastepujacy") this.startTime = lessonRange.startTime
if (newTeacher != null) { this.endTime = lessonRange.endTime
lessonChangeObject.teacherId = data.getTeacherByFDotLast(newTeacher).id this.subjectId = newSubject?.id
} this.teacherId = newTeacher?.id
if (newSubject != null) { this.teamId = data.teamClass?.id
lessonChangeObject.subjectId = data.getSubject(newSubject, null, "").id this.classroom = classroom
}
this.oldDate = lessonDate
this.oldLessonNumber = lessonRange.lessonNumber
this.oldStartTime = lessonRange.startTime
this.oldEndTime = lessonRange.endTime
this.oldSubjectId = subject.id
this.oldTeacherId = teacher.id
this.oldTeamId = data.teamClass?.id
this.oldClassroom = classroom
} }
} }
0 -> {
lessonObject.apply {
this.type = Lesson.TYPE_CANCELLED
data.lessonChangeList.add(lessonChangeObject) this.oldDate = lessonDate
this.oldLessonNumber = lessonRange.lessonNumber
this.oldStartTime = lessonRange.startTime
this.oldEndTime = lessonRange.endTime
this.oldSubjectId = subject.id
this.oldTeacherId = teacher.id
this.oldTeamId = data.teamClass?.id
this.oldClassroom = classroom
}
}
else -> {
lessonObject.apply {
this.type = Lesson.TYPE_NORMAL
this.date = lessonDate
this.lessonNumber = lessonRange.lessonNumber
this.startTime = lessonRange.startTime
this.endTime = lessonRange.endTime
this.subjectId = subject.id
this.teacherId = teacher.id
this.teamId = data.teamClass?.id
this.classroom = classroom
}
}
}
lessonObject.id = lessonObject.buildId()
dates.add(lessonDate.value)
lessons.add(lessonObject)
val seen = profile.empty || lessonDate < Date.getToday()
if (lessonObject.type != Lesson.TYPE_NORMAL && lessonDate >= Date.getToday()) {
data.metadataList.add(Metadata( data.metadataList.add(Metadata(
profileId, profileId,
Metadata.TYPE_LESSON_CHANGE, Metadata.TYPE_LESSON_CHANGE,
lessonChangeObject.id, lessonObject.id,
profile?.empty ?: false, seen,
profile?.empty ?: false, seen,
System.currentTimeMillis() System.currentTimeMillis()
)) ))
} }
} }
val date: Date = weekStart.clone()
while (date <= weekEnd) {
if (!dates.contains(date.value)) {
lessons.add(Lesson(profileId, date.value.toLong()).apply {
this.type = Lesson.TYPE_NO_LESSONS
this.date = date.clone()
})
}
date.stepForward(0, 0, 1)
}
d(TAG, "Clearing lessons between ${weekStart.stringY_m_d} and ${weekEnd.stringY_m_d} - timetable downloaded for $getDate")
data.lessonNewList.addAll(lessons)
data.toRemove.add(DataRemoveModel.Timetable.between(weekStart, weekEnd))
data.setSyncNext(ENDPOINT_IDZIENNIK_WEB_TIMETABLE, SYNC_ALWAYS) data.setSyncNext(ENDPOINT_IDZIENNIK_WEB_TIMETABLE, SYNC_ALWAYS)
onSuccess() onSuccess()
} }
} }}
} }

View File

@ -11,6 +11,7 @@ interface EdziennikInterface {
fun sync(featureIds: List<Int>, viewId: Int? = null, arguments: JsonObject? = null) fun sync(featureIds: List<Int>, viewId: Int? = null, arguments: JsonObject? = null)
fun getMessage(message: MessageFull) fun getMessage(message: MessageFull)
fun markAllAnnouncementsAsRead() fun markAllAnnouncementsAsRead()
fun getAttachment(messageId: Long, attachmentId: Long, attachmentName: String)
fun firstLogin() fun firstLogin()
fun cancel() fun cancel()
} }

View File

@ -10,13 +10,11 @@ import pl.szczodrzynski.edziennik.api.v2.*
import pl.szczodrzynski.edziennik.api.v2.interfaces.EdziennikCallback import pl.szczodrzynski.edziennik.api.v2.interfaces.EdziennikCallback
import pl.szczodrzynski.edziennik.api.v2.interfaces.EdziennikInterface import pl.szczodrzynski.edziennik.api.v2.interfaces.EdziennikInterface
import pl.szczodrzynski.edziennik.api.v2.librus.data.LibrusData import pl.szczodrzynski.edziennik.api.v2.librus.data.LibrusData
import pl.szczodrzynski.edziennik.api.v2.librus.data.messages.LibrusMessagesGetAttachment
import pl.szczodrzynski.edziennik.api.v2.librus.data.messages.LibrusMessagesGetMessage import pl.szczodrzynski.edziennik.api.v2.librus.data.messages.LibrusMessagesGetMessage
import pl.szczodrzynski.edziennik.api.v2.librus.data.synergia.LibrusSynergiaMarkAllAnnouncementsAsRead import pl.szczodrzynski.edziennik.api.v2.librus.data.synergia.LibrusSynergiaMarkAllAnnouncementsAsRead
import pl.szczodrzynski.edziennik.api.v2.librus.firstlogin.LibrusFirstLogin import pl.szczodrzynski.edziennik.api.v2.librus.firstlogin.LibrusFirstLogin
import pl.szczodrzynski.edziennik.api.v2.librus.login.LibrusLogin import pl.szczodrzynski.edziennik.api.v2.librus.login.*
import pl.szczodrzynski.edziennik.api.v2.librus.login.LibrusLoginApi
import pl.szczodrzynski.edziennik.api.v2.librus.login.LibrusLoginMessages
import pl.szczodrzynski.edziennik.api.v2.librus.login.LibrusLoginSynergia
import pl.szczodrzynski.edziennik.api.v2.models.ApiError import pl.szczodrzynski.edziennik.api.v2.models.ApiError
import pl.szczodrzynski.edziennik.data.db.modules.login.LoginStore import pl.szczodrzynski.edziennik.data.db.modules.login.LoginStore
import pl.szczodrzynski.edziennik.data.db.modules.messages.MessageFull import pl.szczodrzynski.edziennik.data.db.modules.messages.MessageFull
@ -82,11 +80,13 @@ class Librus(val app: App, val profile: Profile?, val loginStore: LoginStore, va
} }
override fun getMessage(message: MessageFull) { override fun getMessage(message: MessageFull) {
LibrusLoginApi(data) { LibrusLoginPortal(data) {
LibrusLoginSynergia(data) { LibrusLoginApi(data) {
LibrusLoginMessages(data) { LibrusLoginSynergia(data) {
LibrusMessagesGetMessage(data, message) { LibrusLoginMessages(data) {
completed() LibrusMessagesGetMessage(data, message) {
completed()
}
} }
} }
} }
@ -94,10 +94,26 @@ class Librus(val app: App, val profile: Profile?, val loginStore: LoginStore, va
} }
override fun markAllAnnouncementsAsRead() { override fun markAllAnnouncementsAsRead() {
LibrusLoginApi(data) { LibrusLoginPortal(data) {
LibrusLoginSynergia(data) { LibrusLoginApi(data) {
LibrusSynergiaMarkAllAnnouncementsAsRead(data) { LibrusLoginSynergia(data) {
completed() LibrusSynergiaMarkAllAnnouncementsAsRead(data) {
completed()
}
}
}
}
}
override fun getAttachment(messageId: Long, attachmentId: Long, attachmentName: String) {
LibrusLoginPortal(data) {
LibrusLoginApi(data) {
LibrusLoginSynergia(data) {
LibrusLoginMessages(data) {
LibrusMessagesGetAttachment(data, messageId, attachmentId, attachmentName) {
completed()
}
}
} }
} }
} }

View File

@ -24,6 +24,7 @@ const val ENDPOINT_LIBRUS_API_DESCRIPTIVE_GC = 1023
const val ENDPOINT_LIBRUS_API_TEXT_GC = 1024 const val ENDPOINT_LIBRUS_API_TEXT_GC = 1024
const val ENDPOINT_LIBRUS_API_DESCRIPTIVE_TEXT_GC = 1025 const val ENDPOINT_LIBRUS_API_DESCRIPTIVE_TEXT_GC = 1025
const val ENDPOINT_LIBRUS_API_BEHAVIOUR_GC = 1026 const val ENDPOINT_LIBRUS_API_BEHAVIOUR_GC = 1026
const val ENDPOINT_LIBRUS_API_NORMAL_GRADE_COMMENTS = 1030
const val ENDPOINT_LIBRUS_API_NORMAL_GRADES = 1031 const val ENDPOINT_LIBRUS_API_NORMAL_GRADES = 1031
const val ENDPOINT_LIBRUS_API_POINT_GRADES = 1032 const val ENDPOINT_LIBRUS_API_POINT_GRADES = 1032
const val ENDPOINT_LIBRUS_API_DESCRIPTIVE_GRADES = 1033 const val ENDPOINT_LIBRUS_API_DESCRIPTIVE_GRADES = 1033
@ -97,6 +98,7 @@ val LibrusFeatures = listOf(
ENDPOINT_LIBRUS_API_TEXT_GC to LOGIN_METHOD_LIBRUS_API, ENDPOINT_LIBRUS_API_TEXT_GC to LOGIN_METHOD_LIBRUS_API,
ENDPOINT_LIBRUS_API_DESCRIPTIVE_TEXT_GC to LOGIN_METHOD_LIBRUS_API, ENDPOINT_LIBRUS_API_DESCRIPTIVE_TEXT_GC to LOGIN_METHOD_LIBRUS_API,
ENDPOINT_LIBRUS_API_BEHAVIOUR_GC to LOGIN_METHOD_LIBRUS_API, ENDPOINT_LIBRUS_API_BEHAVIOUR_GC to LOGIN_METHOD_LIBRUS_API,
ENDPOINT_LIBRUS_API_NORMAL_GRADE_COMMENTS to LOGIN_METHOD_LIBRUS_API,
ENDPOINT_LIBRUS_API_NORMAL_GRADES to LOGIN_METHOD_LIBRUS_API, ENDPOINT_LIBRUS_API_NORMAL_GRADES to LOGIN_METHOD_LIBRUS_API,
ENDPOINT_LIBRUS_API_POINT_GRADES to LOGIN_METHOD_LIBRUS_API, ENDPOINT_LIBRUS_API_POINT_GRADES to LOGIN_METHOD_LIBRUS_API,
ENDPOINT_LIBRUS_API_DESCRIPTIVE_GRADES to LOGIN_METHOD_LIBRUS_API, ENDPOINT_LIBRUS_API_DESCRIPTIVE_GRADES to LOGIN_METHOD_LIBRUS_API,

View File

@ -85,6 +85,10 @@ class LibrusData(val data: DataLibrus, val onSuccess: () -> Unit) {
data.startProgress(R.string.edziennik_progress_endpoint_grades) data.startProgress(R.string.edziennik_progress_endpoint_grades)
LibrusApiGrades(data, onSuccess) LibrusApiGrades(data, onSuccess)
} }
ENDPOINT_LIBRUS_API_NORMAL_GRADE_COMMENTS -> {
data.startProgress(R.string.edziennik_progress_endpoint_grade_comments)
LibrusApiGradeComments(data, onSuccess)
}
ENDPOINT_LIBRUS_API_NORMAL_GC -> { ENDPOINT_LIBRUS_API_NORMAL_GC -> {
data.startProgress(R.string.edziennik_progress_endpoint_grade_categories) data.startProgress(R.string.edziennik_progress_endpoint_grade_categories)
LibrusApiGradeCategories(data, onSuccess) LibrusApiGradeCategories(data, onSuccess)

View File

@ -4,9 +4,12 @@
package pl.szczodrzynski.edziennik.api.v2.librus.data package pl.szczodrzynski.edziennik.api.v2.librus.data
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.body.MediaTypeUtils import im.wangchao.mhttp.body.MediaTypeUtils
import im.wangchao.mhttp.callback.FileCallbackHandler
import im.wangchao.mhttp.callback.JsonCallbackHandler
import im.wangchao.mhttp.callback.TextCallbackHandler import im.wangchao.mhttp.callback.TextCallbackHandler
import okhttp3.Cookie import okhttp3.Cookie
import org.jsoup.Jsoup import org.jsoup.Jsoup
@ -16,6 +19,7 @@ import pl.szczodrzynski.edziennik.api.v2.*
import pl.szczodrzynski.edziennik.api.v2.librus.DataLibrus import pl.szczodrzynski.edziennik.api.v2.librus.DataLibrus
import pl.szczodrzynski.edziennik.api.v2.models.ApiError import pl.szczodrzynski.edziennik.api.v2.models.ApiError
import pl.szczodrzynski.edziennik.utils.Utils.d import pl.szczodrzynski.edziennik.utils.Utils.d
import java.io.File
import java.io.StringWriter import java.io.StringWriter
import javax.xml.parsers.DocumentBuilderFactory import javax.xml.parsers.DocumentBuilderFactory
import javax.xml.transform.OutputKeys import javax.xml.transform.OutputKeys
@ -131,4 +135,95 @@ open class LibrusMessages(open val data: DataLibrus) {
.build() .build()
.enqueue() .enqueue()
} }
fun sandboxGet(tag: String, action: String, parameters: Map<String, Any>? = null,
onSuccess: (json: JsonObject) -> Unit) {
d(tag, "Request: Librus/Messages - $LIBRUS_SANDBOX_URL$action")
val callback = object : JsonCallbackHandler() {
override fun onSuccess(json: JsonObject?, response: Response?) {
if (json == null) {
data.error(ApiError(TAG, ERROR_RESPONSE_EMPTY)
.withResponse(response))
return
}
try {
onSuccess(json)
} catch (e: Exception) {
data.error(ApiError(tag, EXCEPTION_LIBRUS_MESSAGES_REQUEST)
.withResponse(response)
.withThrowable(e)
.withApiResponse(json))
}
}
override fun onFailure(response: Response?, throwable: Throwable?) {
data.error(ApiError(tag, ERROR_REQUEST_FAILURE)
.withResponse(response)
.withThrowable(throwable))
}
}
Request.builder()
.url("$LIBRUS_SANDBOX_URL$action")
.userAgent(SYNERGIA_USER_AGENT)
.apply {
parameters?.forEach { (k, v) ->
addParameter(k, v)
}
}
.post()
.callback(callback)
.build()
.enqueue()
}
fun sandboxGetFile(tag: String, action: String, targetFile: File, onSuccess: (file: File) -> Unit,
onProgress: (written: Long, total: Long) -> Unit) {
d(tag, "Request: Librus/Messages - $LIBRUS_SANDBOX_URL$action")
val callback = object : FileCallbackHandler(targetFile) {
override fun onSuccess(file: File?, response: Response?) {
if (file == null) {
data.error(ApiError(TAG, ERROR_FILE_DOWNLOAD)
.withResponse(response))
return
}
try {
onSuccess(file)
} catch (e: Exception) {
data.error(ApiError(tag, EXCEPTION_LIBRUS_MESSAGES_REQUEST)
.withResponse(response)
.withThrowable(e))
}
}
override fun onProgress(bytesWritten: Long, bytesTotal: Long) {
try {
onProgress(bytesWritten, bytesTotal)
} catch (e: Exception) {
data.error(ApiError(tag, EXCEPTION_LIBRUS_MESSAGES_REQUEST)
.withThrowable(e))
}
}
override fun onFailure(response: Response?, throwable: Throwable?) {
data.error(ApiError(tag, ERROR_REQUEST_FAILURE)
.withResponse(response)
.withThrowable(throwable))
}
}
Request.builder()
.url("$LIBRUS_SANDBOX_URL$action")
.userAgent(SYNERGIA_USER_AGENT)
.post()
.callback(callback)
.build()
.enqueue()
}
} }

View File

@ -29,15 +29,6 @@ open class LibrusSynergia(open val data: DataLibrus) {
val callback = object : TextCallbackHandler() { val callback = object : TextCallbackHandler() {
override fun onSuccess(text: String?, response: Response?) { override fun onSuccess(text: String?, response: Response?) {
val location = response?.headers()?.get("Location")
if (location?.endsWith("przerwa_techniczna") == true) {
// double checking for maintenance?
data.error(ApiError(TAG, ERROR_LIBRUS_SYNERGIA_MAINTENANCE)
.withApiResponse(text)
.withResponse(response))
return
}
if (text.isNullOrEmpty()) { if (text.isNullOrEmpty()) {
data.error(ApiError(TAG, ERROR_RESPONSE_EMPTY) data.error(ApiError(TAG, ERROR_RESPONSE_EMPTY)
.withResponse(response)) .withResponse(response))

View File

@ -0,0 +1,47 @@
/*
* Copyright (c) Kacper Ziubryniewicz 2019-11-20
*/
package pl.szczodrzynski.edziennik.api.v2.librus.data.api
import pl.szczodrzynski.edziennik.api.v2.librus.DataLibrus
import pl.szczodrzynski.edziennik.api.v2.librus.ENDPOINT_LIBRUS_API_NORMAL_GRADE_COMMENTS
import pl.szczodrzynski.edziennik.api.v2.librus.data.LibrusApi
import pl.szczodrzynski.edziennik.asJsonObjectList
import pl.szczodrzynski.edziennik.data.db.modules.api.SYNC_ALWAYS
import pl.szczodrzynski.edziennik.data.db.modules.grades.GradeCategory
import pl.szczodrzynski.edziennik.getJsonArray
import pl.szczodrzynski.edziennik.getLong
import pl.szczodrzynski.edziennik.getString
class LibrusApiGradeComments(override val data: DataLibrus,
val onSuccess: () -> Unit) : LibrusApi(data) {
companion object {
const val TAG = "LibrusApiGradeComments"
}
init {
apiGet(TAG, "Grades/Comments") { json ->
json.getJsonArray("Comments")?.asJsonObjectList()?.forEach { comment ->
val id = comment.getLong("Id") ?: return@forEach
val text = comment.getString("Text")
val gradeCategoryObject = GradeCategory(
profileId,
id,
-1f,
-1,
text
).apply {
type = GradeCategory.TYPE_COMMENT
}
data.gradeCategories.put(id, gradeCategoryObject)
}
data.setSyncNext(ENDPOINT_LIBRUS_API_NORMAL_GRADE_COMMENTS, SYNC_ALWAYS)
onSuccess()
}
}
}

View File

@ -6,6 +6,7 @@ import pl.szczodrzynski.edziennik.api.v2.librus.ENDPOINT_LIBRUS_API_NORMAL_GRADE
import pl.szczodrzynski.edziennik.api.v2.librus.data.LibrusApi import pl.szczodrzynski.edziennik.api.v2.librus.data.LibrusApi
import pl.szczodrzynski.edziennik.data.db.modules.api.SYNC_ALWAYS import pl.szczodrzynski.edziennik.data.db.modules.api.SYNC_ALWAYS
import pl.szczodrzynski.edziennik.data.db.modules.grades.Grade import pl.szczodrzynski.edziennik.data.db.modules.grades.Grade
import pl.szczodrzynski.edziennik.data.db.modules.grades.GradeCategory
import pl.szczodrzynski.edziennik.data.db.modules.metadata.Metadata import pl.szczodrzynski.edziennik.data.db.modules.metadata.Metadata
import pl.szczodrzynski.edziennik.utils.Utils import pl.szczodrzynski.edziennik.utils.Utils
import pl.szczodrzynski.edziennik.utils.models.Date import pl.szczodrzynski.edziennik.utils.models.Date
@ -42,12 +43,21 @@ class LibrusApiGrades(override val data: DataLibrus,
weight = 0f weight = 0f
} }
val description = grade.getJsonArray("Comments")?.asJsonObjectList()?.let { comments ->
if (comments.isNotEmpty()) {
data.gradeCategories.singleOrNull {
it.type == GradeCategory.TYPE_COMMENT
&& it.categoryId == comments[0].asJsonObject.getLong("Id")
}?.text
} else null
} ?: ""
val gradeObject = Grade( val gradeObject = Grade(
profileId, profileId,
id, id,
categoryName, categoryName,
color, color,
"", description,
name, name,
value, value,
weight, weight,

View File

@ -17,6 +17,7 @@ import pl.szczodrzynski.edziennik.data.db.modules.timetable.Lesson
import pl.szczodrzynski.edziennik.utils.Utils.d import pl.szczodrzynski.edziennik.utils.Utils.d
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
import pl.szczodrzynski.edziennik.utils.models.Week
class LibrusApiTimetables(override val data: DataLibrus, class LibrusApiTimetables(override val data: DataLibrus,
val onSuccess: () -> Unit) : LibrusApi(data) { val onSuccess: () -> Unit) : LibrusApi(data) {
@ -29,9 +30,18 @@ class LibrusApiTimetables(override val data: DataLibrus,
data.db.classroomDao().getAllNow(profileId).toSparseArray(data.classrooms) { it.id } data.db.classroomDao().getAllNow(profileId).toSparseArray(data.classrooms) { it.id }
} }
val currentWeekStart = Date.getToday().let { it.stepForward(0, 0, -it.weekDay) } val currentWeekStart = Week.getWeekStart()
if (Date.getToday().weekDay > 4) {
currentWeekStart.stepForward(0, 0, 7)
}
val getDate = data.arguments?.getString("weekStart") ?: currentWeekStart.stringY_m_d val getDate = data.arguments?.getString("weekStart") ?: currentWeekStart.stringY_m_d
apiGet(TAG, "Timetables?weekStart=$getDate") { json ->
val weekStart = Date.fromY_m_d(getDate)
val weekEnd = weekStart.clone().stepForward(0, 0, 6)
apiGet(TAG, "Timetables?weekStart=${weekStart.stringY_m_d}") { json ->
val days = json.getJsonObject("Timetable") val days = json.getJsonObject("Timetable")
days?.entrySet()?.forEach { (dateString, dayEl) -> days?.entrySet()?.forEach { (dateString, dayEl) ->
@ -57,8 +67,6 @@ class LibrusApiTimetables(override val data: DataLibrus,
} }
} }
val weekStart = Date.fromY_m_d(getDate)
val weekEnd = weekStart.clone().stepForward(0, 0, 6)
d(TAG, "Clearing lessons between ${weekStart.stringY_m_d} and ${weekEnd.stringY_m_d} - timetable downloaded for $getDate") d(TAG, "Clearing lessons between ${weekStart.stringY_m_d} and ${weekEnd.stringY_m_d} - timetable downloaded for $getDate")
data.toRemove.add(DataRemoveModel.Timetable.between(weekStart, weekEnd)) data.toRemove.add(DataRemoveModel.Timetable.between(weekStart, weekEnd))
@ -67,7 +75,7 @@ class LibrusApiTimetables(override val data: DataLibrus,
} }
} }
private fun parseLesson(lessonDate: Date, lesson: JsonObject) { private fun parseLesson(lessonDate: Date, lesson: JsonObject) { data.profile?.also { profile ->
val isSubstitution = lesson.getBoolean("IsSubstitutionClass") ?: false val isSubstitution = lesson.getBoolean("IsSubstitutionClass") ?: false
val isCancelled = lesson.getBoolean("IsCanceled") ?: false val isCancelled = lesson.getBoolean("IsCanceled") ?: false
@ -80,8 +88,7 @@ class LibrusApiTimetables(override val data: DataLibrus,
val virtualClassId = lesson.getJsonObject("VirtualClass")?.getLong("Id") val virtualClassId = lesson.getJsonObject("VirtualClass")?.getLong("Id")
val teamId = lesson.getJsonObject("Class")?.getLong("Id") ?: virtualClassId val teamId = lesson.getJsonObject("Class")?.getLong("Id") ?: virtualClassId
val id = lessonDate.combineWith(startTime) / 6L * 10L + (lesson.hashCode() and 0xFFFF) val lessonObject = Lesson(profileId, -1)
val lessonObject = Lesson(profileId, id)
if (isSubstitution && isCancelled) { if (isSubstitution && isCancelled) {
// shifted lesson - source // shifted lesson - source
@ -176,17 +183,21 @@ class LibrusApiTimetables(override val data: DataLibrus,
} }
} }
lessonObject.id = lessonObject.buildId()
val seen = profile.empty || lessonDate < Date.getToday()
if (lessonObject.type != Lesson.TYPE_NORMAL) { if (lessonObject.type != Lesson.TYPE_NORMAL) {
data.metadataList.add( data.metadataList.add(
Metadata( Metadata(
data.profileId, profileId,
Metadata.TYPE_LESSON_CHANGE, Metadata.TYPE_LESSON_CHANGE,
lessonObject.id, lessonObject.id,
data.profile?.empty ?: false, seen,
data.profile?.empty ?: false, seen,
System.currentTimeMillis() System.currentTimeMillis()
)) ))
} }
data.lessonNewList.add(lessonObject) data.lessonNewList.add(lessonObject)
} }}
} }

View File

@ -0,0 +1,123 @@
/*
* Copyright (c) Kacper Ziubryniewicz 2019-11-24
*/
package pl.szczodrzynski.edziennik.api.v2.librus.data.messages
import kotlinx.coroutines.*
import org.greenrobot.eventbus.EventBus
import pl.szczodrzynski.edziennik.api.v2.ERROR_FILE_DOWNLOAD
import pl.szczodrzynski.edziennik.api.v2.EXCEPTION_LIBRUS_MESSAGES_REQUEST
import pl.szczodrzynski.edziennik.api.v2.Regexes
import pl.szczodrzynski.edziennik.api.v2.events.AttachmentGetEvent
import pl.szczodrzynski.edziennik.api.v2.events.AttachmentGetEvent.Companion.TYPE_FINISHED
import pl.szczodrzynski.edziennik.api.v2.events.AttachmentGetEvent.Companion.TYPE_PROGRESS
import pl.szczodrzynski.edziennik.api.v2.librus.DataLibrus
import pl.szczodrzynski.edziennik.api.v2.librus.data.LibrusMessages
import pl.szczodrzynski.edziennik.api.v2.models.ApiError
import pl.szczodrzynski.edziennik.get
import pl.szczodrzynski.edziennik.getString
import pl.szczodrzynski.edziennik.utils.Utils
import java.io.File
import kotlin.coroutines.CoroutineContext
class LibrusMessagesGetAttachment(override val data: DataLibrus, val messageId: Long, val attachmentId: Long,
val attachmentName: String, val onSuccess: () -> Unit) : LibrusMessages(data), CoroutineScope {
companion object {
const val TAG = "LibrusMessagesGetAttachment"
}
private var job = Job()
override val coroutineContext: CoroutineContext
get() = job + Dispatchers.Default
private var getAttachmentCheckKeyTries = 0
init {
messagesGet(TAG, "GetFileDownloadLink", parameters = mapOf(
"fileId" to attachmentId,
"msgId" to messageId,
"archive" to 0
)) { doc ->
val downloadLink = doc.select("response GetFileDownloadLink downloadLink").text()
val keyMatcher = Regexes.LIBRUS_ATTACHMENT_KEY.find(downloadLink)
if (keyMatcher != null) {
getAttachmentCheckKeyTries = 0
val attachmentKey = keyMatcher[1]
getAttachmentCheckKey(attachmentKey) {
downloadAttachment(attachmentKey)
}
} else {
data.error(ApiError(TAG, ERROR_FILE_DOWNLOAD)
.withApiResponse(doc.toString()))
}
onSuccess()
}
}
private fun getAttachmentCheckKey(attachmentKey: String, callback: () -> Unit) {
sandboxGet(TAG, "CSCheckKey",
parameters = mapOf("singleUseKey" to attachmentKey)) { json ->
when (json.getString("status")) {
"not_downloaded_yet" -> {
if (getAttachmentCheckKeyTries++ > 5) {
data.error(ApiError(TAG, ERROR_FILE_DOWNLOAD)
.withApiResponse(json))
return@sandboxGet
}
launch {
delay(2000)
getAttachmentCheckKey(attachmentKey, callback)
}
}
"ready" -> {
launch { callback() }
}
else -> {
data.error(ApiError(TAG, EXCEPTION_LIBRUS_MESSAGES_REQUEST)
.withApiResponse(json))
}
}
}
}
private fun downloadAttachment(attachmentKey: String) {
val targetFile = File(Utils.getStorageDir(), attachmentName)
sandboxGetFile(TAG, "CSDownload&singleUseKey=$attachmentKey",
targetFile, { file ->
val event = AttachmentGetEvent(
profileId,
messageId,
attachmentId,
TYPE_FINISHED,
file.absolutePath
)
val attachmentDataFile = File(Utils.getStorageDir(), ".${profileId}_${event.messageId}_${event.attachmentId}")
Utils.writeStringToFile(attachmentDataFile, event.fileName)
EventBus.getDefault().post(event)
}) { written, _ ->
val event = AttachmentGetEvent(
profileId,
messageId,
attachmentId,
TYPE_PROGRESS,
bytesWritten = written
)
EventBus.getDefault().post(event)
}
}
}

View File

@ -99,6 +99,14 @@ class LibrusLoginPortal(val data: DataLibrus, val onSuccess: () -> Unit) {
.post() .post()
.callback(object : JsonCallbackHandler() { .callback(object : JsonCallbackHandler() {
override fun onSuccess(json: JsonObject?, response: Response) { override fun onSuccess(json: JsonObject?, response: Response) {
val location = response.headers()?.get("Location")
if (location == "http://localhost/bar?command=close") {
data.error(ApiError(TAG, ERROR_LIBRUS_PORTAL_MAINTENANCE)
.withApiResponse(json)
.withResponse(response))
return
}
if (json == null) { if (json == null) {
if (response.parserErrorBody?.contains("wciąż nieaktywne") == true) { if (response.parserErrorBody?.contains("wciąż nieaktywne") == true) {
data.error(ApiError(TAG, ERROR_LOGIN_LIBRUS_PORTAL_NOT_ACTIVATED) data.error(ApiError(TAG, ERROR_LOGIN_LIBRUS_PORTAL_NOT_ACTIVATED)

View File

@ -10,8 +10,10 @@ import pl.szczodrzynski.edziennik.api.v2.CODE_INTERNAL_LIBRUS_ACCOUNT_410
import pl.szczodrzynski.edziennik.api.v2.interfaces.EdziennikCallback import pl.szczodrzynski.edziennik.api.v2.interfaces.EdziennikCallback
import pl.szczodrzynski.edziennik.api.v2.interfaces.EdziennikInterface import pl.szczodrzynski.edziennik.api.v2.interfaces.EdziennikInterface
import pl.szczodrzynski.edziennik.api.v2.mobidziennik.data.MobidziennikData import pl.szczodrzynski.edziennik.api.v2.mobidziennik.data.MobidziennikData
import pl.szczodrzynski.edziennik.api.v2.mobidziennik.data.web.MobidziennikWebGetMessage
import pl.szczodrzynski.edziennik.api.v2.mobidziennik.firstlogin.MobidziennikFirstLogin import pl.szczodrzynski.edziennik.api.v2.mobidziennik.firstlogin.MobidziennikFirstLogin
import pl.szczodrzynski.edziennik.api.v2.mobidziennik.login.MobidziennikLogin import pl.szczodrzynski.edziennik.api.v2.mobidziennik.login.MobidziennikLogin
import pl.szczodrzynski.edziennik.api.v2.mobidziennik.login.MobidziennikLoginWeb
import pl.szczodrzynski.edziennik.api.v2.mobidziennikLoginMethods import pl.szczodrzynski.edziennik.api.v2.mobidziennikLoginMethods
import pl.szczodrzynski.edziennik.api.v2.models.ApiError import pl.szczodrzynski.edziennik.api.v2.models.ApiError
import pl.szczodrzynski.edziennik.api.v2.prepare import pl.szczodrzynski.edziennik.api.v2.prepare
@ -63,13 +65,21 @@ class Mobidziennik(val app: App, val profile: Profile?, val loginStore: LoginSto
} }
override fun getMessage(message: MessageFull) { override fun getMessage(message: MessageFull) {
MobidziennikLoginWeb(data) {
MobidziennikWebGetMessage(data, message) {
completed()
}
}
} }
override fun markAllAnnouncementsAsRead() { override fun markAllAnnouncementsAsRead() {
} }
override fun getAttachment(messageId: Long, attachmentId: Long, attachmentName: String) {
}
override fun firstLogin() { override fun firstLogin() {
MobidziennikFirstLogin(data) { MobidziennikFirstLogin(data) {
completed() completed()

View File

@ -14,7 +14,7 @@ import pl.szczodrzynski.edziennik.utils.models.Date
import pl.szczodrzynski.edziennik.utils.models.Time import pl.szczodrzynski.edziennik.utils.models.Time
class MobidziennikApiTimetable(val data: DataMobidziennik, rows: List<String>) { class MobidziennikApiTimetable(val data: DataMobidziennik, rows: List<String>) {
init { init { data.profile?.also { profile ->
val lessons = rows.filterNot { it.isEmpty() }.map { it.split("|") } val lessons = rows.filterNot { it.isEmpty() }.map { it.split("|") }
val dataStart = Date.getToday() val dataStart = Date.getToday()
@ -32,7 +32,6 @@ class MobidziennikApiTimetable(val data: DataMobidziennik, rows: List<String>) {
val date = Date.fromYmd(lesson[2]) val date = Date.fromYmd(lesson[2])
val startTime = Time.fromYmdHm(lesson[3]) val startTime = Time.fromYmdHm(lesson[3])
val endTime = Time.fromYmdHm(lesson[4]) val endTime = Time.fromYmdHm(lesson[4])
val id = date.combineWith(startTime) / 6L * 10L + (lesson.joinToString("|").hashCode() and 0xFFFF)
dataDays.remove(date.value) dataDays.remove(date.value)
@ -41,7 +40,7 @@ class MobidziennikApiTimetable(val data: DataMobidziennik, rows: List<String>) {
val teamId = data.teamList.singleOrNull { it.name == lesson[8]+lesson[9] }?.id ?: -1 val teamId = data.teamList.singleOrNull { it.name == lesson[8]+lesson[9] }?.id ?: -1
val classroom = lesson[11] val classroom = lesson[11]
Lesson(data.profileId, id).also { Lesson(data.profileId, -1).also {
when (lesson[1]) { when (lesson[1]) {
"plan_lekcji", "lekcja" -> { "plan_lekcji", "lekcja" -> {
it.type = Lesson.TYPE_NORMAL it.type = Lesson.TYPE_NORMAL
@ -75,14 +74,18 @@ class MobidziennikApiTimetable(val data: DataMobidziennik, rows: List<String>) {
} }
} }
it.id = it.buildId()
val seen = profile.empty || date < Date.getToday()
if (it.type != Lesson.TYPE_NORMAL) { if (it.type != Lesson.TYPE_NORMAL) {
data.metadataList.add( data.metadataList.add(
Metadata( Metadata(
data.profileId, data.profileId,
Metadata.TYPE_LESSON_CHANGE, Metadata.TYPE_LESSON_CHANGE,
it.id, it.id,
data.profile?.empty ?: false, seen,
data.profile?.empty ?: false, seen,
System.currentTimeMillis() System.currentTimeMillis()
)) ))
} }
@ -194,5 +197,5 @@ class MobidziennikApiTimetable(val data: DataMobidziennik, rows: List<String>) {
} }
} }
}*/ }*/
} }}
} }

View File

@ -0,0 +1,157 @@
/*
* Copyright (c) Kuba Szczodrzyński 2019-11-18.
*/
package pl.szczodrzynski.edziennik.api.v2.mobidziennik.data.web
import org.greenrobot.eventbus.EventBus
import org.jsoup.Jsoup
import pl.szczodrzynski.edziennik.api.v2.Regexes
import pl.szczodrzynski.edziennik.api.v2.events.MessageGetEvent
import pl.szczodrzynski.edziennik.api.v2.mobidziennik.DataMobidziennik
import pl.szczodrzynski.edziennik.api.v2.mobidziennik.data.MobidziennikWeb
import pl.szczodrzynski.edziennik.data.db.modules.messages.Message
import pl.szczodrzynski.edziennik.data.db.modules.messages.Message.TYPE_RECEIVED
import pl.szczodrzynski.edziennik.data.db.modules.messages.MessageFull
import pl.szczodrzynski.edziennik.data.db.modules.messages.MessageRecipientFull
import pl.szczodrzynski.edziennik.data.db.modules.metadata.Metadata
import pl.szczodrzynski.edziennik.get
import pl.szczodrzynski.edziennik.singleOrNull
import pl.szczodrzynski.edziennik.utils.Utils.monthFromName
import pl.szczodrzynski.edziennik.utils.models.Date
import pl.szczodrzynski.edziennik.utils.models.Time
class MobidziennikWebGetMessage(
override val data: DataMobidziennik,
private val message: MessageFull,
val onSuccess: () -> Unit) : MobidziennikWeb(data) {
companion object {
private const val TAG = "MobidziennikWebGetMessage"
}
init {
val typeUrl = if (message.type == Message.TYPE_SENT)
"wiadwyslana"
else
"wiadodebrana"
webGet(TAG, "/dziennik/$typeUrl/?id=${message.id}") { text ->
MobidziennikLuckyNumberExtractor(data, text)
val messageRecipientList = mutableListOf<MessageRecipientFull>()
val doc = Jsoup.parse(text)
val content = doc.select("#content").first()
val body = content.select(".wiadomosc_tresc").first()
if (message.type == TYPE_RECEIVED) {
var readDate = System.currentTimeMillis()
Regexes.MOBIDZIENNIK_MESSAGE_READ_DATE.find(body.html())?.let {
val date = Date(
it[3].toIntOrNull() ?: 2019,
monthFromName(it[2]),
it[1].toIntOrNull() ?: 1
)
val time = Time.fromH_m_s(
it[4] // TODO blank string safety
)
readDate = date.combineWith(time)
}
val recipient = MessageRecipientFull(
profileId,
-1,
-1,
readDate,
message.id
)
recipient.fullName = profile?.accountNameLong ?: profile?.studentNameLong
messageRecipientList.add(recipient)
} else {
message.senderId = -1
message.senderReplyId = -1
content.select("table.spis tr:has(td)")?.forEach { recipientEl ->
val senderEl = recipientEl.select("td:eq(0)").first()
val senderName = senderEl.text()
val teacher = data.teacherList.singleOrNull { it.fullNameLastFirst == senderName }
val receiverId = teacher?.id ?: -1
var readDate = 0L
val isReadEl = recipientEl.select("td:eq(2)").first()
if (isReadEl.ownText() != "NIE") {
val readDateEl = recipientEl.select("td:eq(3) small").first()
Regexes.MOBIDZIENNIK_MESSAGE_SENT_READ_DATE.find(readDateEl.ownText())?.let {
val date = Date(
it[3].toIntOrNull() ?: 2019,
monthFromName(it[2]),
it[1].toIntOrNull() ?: 1
)
val time = Time.fromH_m_s(
it[4] // TODO blank string safety
)
readDate = date.combineWith(time)
}
}
val recipient = MessageRecipientFull(
profileId,
receiverId,
-1,
readDate,
message.id
)
recipient.fullName = teacher?.fullName ?: "?"
messageRecipientList.add(recipient)
}
}
// this line removes the sender and read date details
body.select("div").remove()
// this needs to be at the end
message.apply {
this.body = body.html()
clearAttachments()
content.select("ul li").map { it.select("a").first() }.forEach {
val attachmentName = it.ownText()
Regexes.MOBIDZIENNIK_MESSAGE_ATTACHMENT.find(it.outerHtml())?.let { match ->
val attachmentId = match[1].toLong()
var size = match[2].toFloatOrNull() ?: 0f
when (match[3]) {
"K" -> size *= 1024f
"M" -> size *= 1024f * 1024f
"G" -> size *= 1024f * 1024f * 1024f
}
message.addAttachment(attachmentId, attachmentName, size.toLong())
}
}
}
if (!message.seen) { // TODO discover why this monstrosity instead of MetadataDao.setSeen
data.messageMetadataList.add(Metadata(
message.profileId,
Metadata.TYPE_MESSAGE,
message.id,
true,
true,
message.addedDate
))
}
message.recipients = messageRecipientList
data.messageRecipientList.addAll(messageRecipientList)
data.messageList.add(message)
EventBus.getDefault().postSticky(MessageGetEvent(message))
onSuccess()
}
}
}

View File

@ -375,7 +375,7 @@ open class Data(val app: App, val profile: Profile?, val loginStore: LoginStore)
} }
fun shouldSyncLuckyNumber(): Boolean { fun shouldSyncLuckyNumber(): Boolean {
return (db.luckyNumberDao().getNearestFutureNow(profileId, Date.getToday()) ?: -1) == -1 return (db.luckyNumberDao().getNearestFutureNow(profileId, Date.getToday().value) ?: -1) == -1
} }
fun error(tag: String, errorCode: Int, response: Response? = null, throwable: Throwable? = null, apiResponse: JsonObject? = null) { fun error(tag: String, errorCode: Int, response: Response? = null, throwable: Throwable? = null, apiResponse: JsonObject? = null) {

View File

@ -70,6 +70,10 @@ class Template(val app: App, val profile: Profile?, val loginStore: LoginStore,
} }
override fun getAttachment(messageId: Long, attachmentId: Long, attachmentName: String) {
}
override fun firstLogin() { override fun firstLogin() {
TemplateFirstLogin(data) { TemplateFirstLogin(data) {
completed() completed()

View File

@ -7,15 +7,14 @@ package pl.szczodrzynski.edziennik.api.v2.vulcan
import pl.szczodrzynski.edziennik.App import pl.szczodrzynski.edziennik.App
import pl.szczodrzynski.edziennik.api.v2.LOGIN_METHOD_VULCAN_API import pl.szczodrzynski.edziennik.api.v2.LOGIN_METHOD_VULCAN_API
import pl.szczodrzynski.edziennik.api.v2.models.Data import pl.szczodrzynski.edziennik.api.v2.models.Data
import pl.szczodrzynski.edziennik.currentTimeUnix
import pl.szczodrzynski.edziennik.data.db.modules.login.LoginStore import pl.szczodrzynski.edziennik.data.db.modules.login.LoginStore
import pl.szczodrzynski.edziennik.data.db.modules.profiles.Profile import pl.szczodrzynski.edziennik.data.db.modules.profiles.Profile
import pl.szczodrzynski.edziennik.isNotNullNorEmpty import pl.szczodrzynski.edziennik.isNotNullNorEmpty
class DataVulcan(app: App, profile: Profile?, loginStore: LoginStore) : Data(app, profile, loginStore) { class DataVulcan(app: App, profile: Profile?, loginStore: LoginStore) : Data(app, profile, loginStore) {
fun isApiLoginValid() = apiCertificateExpiryTime-30 > currentTimeUnix() fun isApiLoginValid() = /*apiCertificateExpiryTime-30 > currentTimeUnix()
&& apiCertificateKey.isNotNullNorEmpty() &&*/ apiCertificateKey.isNotNullNorEmpty()
&& apiCertificatePrivate.isNotNullNorEmpty() && apiCertificatePrivate.isNotNullNorEmpty()
&& symbol.isNotNullNorEmpty() && symbol.isNotNullNorEmpty()
@ -165,6 +164,8 @@ class DataVulcan(app: App, profile: Profile?, loginStore: LoginStore) : Data(app
"GD1" -> "https://uonetplus-komunikacja.edu.gdansk.pl" "GD1" -> "https://uonetplus-komunikacja.edu.gdansk.pl"
"KA1" -> "https://uonetplus-komunikacja.mcuw.katowice.eu" "KA1" -> "https://uonetplus-komunikacja.mcuw.katowice.eu"
"KA2" -> "https://uonetplus-komunikacja-test.mcuw.katowice.eu" "KA2" -> "https://uonetplus-komunikacja-test.mcuw.katowice.eu"
"LU1" -> "https://uonetplus-komunikacja.edu.lublin.eu"
"LU2" -> "https://test-uonetplus-komunikacja.edu.lublin.eu"
"P03" -> "https://efeb-komunikacja-pro-efebmobile.pro.vulcan.pl" "P03" -> "https://efeb-komunikacja-pro-efebmobile.pro.vulcan.pl"
"P01" -> "http://efeb-komunikacja.pro-hudson.win.vulcan.pl" "P01" -> "http://efeb-komunikacja.pro-hudson.win.vulcan.pl"
"P02" -> "http://efeb-komunikacja.pro-hudsonrc.win.vulcan.pl" "P02" -> "http://efeb-komunikacja.pro-hudsonrc.win.vulcan.pl"
@ -173,7 +174,7 @@ class DataVulcan(app: App, profile: Profile?, loginStore: LoginStore) : Data(app
"SZ9" -> "http://vulcan.szkolny.eu" "SZ9" -> "http://vulcan.szkolny.eu"
else -> null else -> null
} }
return if (url != null) "$url/$symbol" else null return if (url != null) "$url/$symbol" else loginStore.getLoginData("apiUrl", null)
} }
val fullApiUrl: String? val fullApiUrl: String?

View File

@ -76,6 +76,10 @@ class Vulcan(val app: App, val profile: Profile?, val loginStore: LoginStore, va
} }
override fun getAttachment(messageId: Long, attachmentId: Long, attachmentName: String) {
}
override fun firstLogin() { override fun firstLogin() {
VulcanFirstLogin(data) { VulcanFirstLogin(data) {
completed() completed()

View File

@ -4,6 +4,7 @@
package pl.szczodrzynski.edziennik.api.v2.vulcan.data.api package pl.szczodrzynski.edziennik.api.v2.vulcan.data.api
import androidx.core.util.set
import pl.szczodrzynski.edziennik.* import pl.szczodrzynski.edziennik.*
import pl.szczodrzynski.edziennik.api.v2.Regexes import pl.szczodrzynski.edziennik.api.v2.Regexes
import pl.szczodrzynski.edziennik.api.v2.VULCAN_API_ENDPOINT_TIMETABLE import pl.szczodrzynski.edziennik.api.v2.VULCAN_API_ENDPOINT_TIMETABLE
@ -14,173 +15,199 @@ import pl.szczodrzynski.edziennik.api.v2.vulcan.data.VulcanApi
import pl.szczodrzynski.edziennik.data.db.modules.api.SYNC_ALWAYS import pl.szczodrzynski.edziennik.data.db.modules.api.SYNC_ALWAYS
import pl.szczodrzynski.edziennik.data.db.modules.metadata.Metadata import pl.szczodrzynski.edziennik.data.db.modules.metadata.Metadata
import pl.szczodrzynski.edziennik.data.db.modules.subjects.Subject import pl.szczodrzynski.edziennik.data.db.modules.subjects.Subject
import pl.szczodrzynski.edziennik.data.db.modules.teams.Team
import pl.szczodrzynski.edziennik.data.db.modules.timetable.Lesson import pl.szczodrzynski.edziennik.data.db.modules.timetable.Lesson
import pl.szczodrzynski.edziennik.utils.Utils.crc16 import pl.szczodrzynski.edziennik.utils.Utils.crc16
import pl.szczodrzynski.edziennik.utils.Utils.d import pl.szczodrzynski.edziennik.utils.Utils.d
import pl.szczodrzynski.edziennik.utils.models.Date import pl.szczodrzynski.edziennik.utils.models.Date
import pl.szczodrzynski.edziennik.utils.models.Week
class VulcanApiTimetable(override val data: DataVulcan, val onSuccess: () -> Unit) : VulcanApi(data) { class VulcanApiTimetable(override val data: DataVulcan, val onSuccess: () -> Unit) : VulcanApi(data) {
companion object { companion object {
const val TAG = "VulcanApiTimetable" const val TAG = "VulcanApiTimetable"
} }
init { init { data.profile?.also { profile ->
data.profile?.also { profile -> val currentWeekStart = Week.getWeekStart()
val currentWeekStart = Date.getToday().let { it.stepForward(0, 0, -it.weekDay) }
val getDate = data.arguments?.getString("weekStart") ?: currentWeekStart.stringY_m_d
val weekStart = Date.fromY_m_d(getDate) if (Date.getToday().weekDay > 4) {
val weekEnd = weekStart.clone().stepForward(0, 0, 6) currentWeekStart.stepForward(0, 0, 7)
}
apiGet(TAG, VULCAN_API_ENDPOINT_TIMETABLE, parameters = mapOf( val getDate = data.arguments?.getString("weekStart") ?: currentWeekStart.stringY_m_d
"DataPoczatkowa" to weekStart.stringY_m_d,
"DataKoncowa" to weekEnd.stringY_m_d,
"IdUczen" to data.studentId,
"IdOddzial" to data.studentClassId,
"IdOkresKlasyfikacyjny" to data.studentSemesterId
)) { json, _ ->
val dates: MutableSet<Int> = mutableSetOf()
val lessons: MutableList<Lesson> = mutableListOf()
json.getJsonArray("Data")?.asJsonObjectList()?.forEach { lesson -> val weekStart = Date.fromY_m_d(getDate)
val lessonDate = Date.fromY_m_d(lesson.getString("DzienTekst")) val weekEnd = weekStart.clone().stepForward(0, 0, 6)
val lessonNumber = lesson.getInt("NumerLekcji")
val lessonRange = data.lessonRanges.singleOrNull { it.lessonNumber == lessonNumber }
val startTime = lessonRange?.startTime
val endTime = lessonRange?.endTime
val teacherId = lesson.getLong("IdPracownik")
val teamId = data.studentClassId.toLong()
val classroom = lesson.getString("Classroom")
val oldTeacherId = lesson.getLong("IdPracownikOld") apiGet(TAG, VULCAN_API_ENDPOINT_TIMETABLE, parameters = mapOf(
"DataPoczatkowa" to weekStart.stringY_m_d,
"DataKoncowa" to weekEnd.stringY_m_d,
"IdUczen" to data.studentId,
"IdOddzial" to data.studentClassId,
"IdOkresKlasyfikacyjny" to data.studentSemesterId
)) { json, _ ->
val dates = mutableSetOf<Int>()
val lessons = mutableListOf<Lesson>()
val changeAnnotation = lesson.getString("AdnotacjaOZmianie") ?: "" json.getJsonArray("Data")?.asJsonObjectList()?.forEach { lesson ->
val type = when { if (lesson.getBoolean("PlanUcznia") != true)
changeAnnotation.startsWith("(przeniesiona z") -> Lesson.TYPE_SHIFTED_TARGET return@forEach
changeAnnotation.startsWith("(przeniesiona na") -> Lesson.TYPE_SHIFTED_SOURCE val lessonDate = Date.fromY_m_d(lesson.getString("DzienTekst"))
changeAnnotation.startsWith("(zastępstwo") -> Lesson.TYPE_CHANGE val lessonNumber = lesson.getInt("NumerLekcji")
lesson.getBoolean("PrzekreslonaNazwa") == true -> Lesson.TYPE_CANCELLED val lessonRange = data.lessonRanges.singleOrNull { it.lessonNumber == lessonNumber }
else -> Lesson.TYPE_NORMAL val startTime = lessonRange?.startTime
val endTime = lessonRange?.endTime
val teacherId = lesson.getLong("IdPracownik")
val classroom = lesson.getString("Sala")
val oldTeacherId = lesson.getLong("IdPracownikOld")
val changeAnnotation = lesson.getString("AdnotacjaOZmianie") ?: ""
val type = when {
changeAnnotation.startsWith("(przeniesiona z") -> Lesson.TYPE_SHIFTED_TARGET
changeAnnotation.startsWith("(przeniesiona na") -> Lesson.TYPE_SHIFTED_SOURCE
changeAnnotation.startsWith("(zastępstwo") -> Lesson.TYPE_CHANGE
lesson.getBoolean("PrzekreslonaNazwa") == true -> Lesson.TYPE_CANCELLED
else -> Lesson.TYPE_NORMAL
}
val teamId = lesson.getString("PodzialSkrot")?.let { teamName ->
val name = "${data.teamClass?.name} $teamName"
val id = name.crc16().toLong()
var team = data.teamList.singleOrNull { it.name == name }
if (team == null) {
team = Team(
profileId,
id,
name,
Team.TYPE_VIRTUAL,
"${data.schoolName}:$name",
teacherId ?: oldTeacherId ?: -1
)
data.teamList[id] = team
} }
team.id
} ?: data.studentClassId.toLong()
val subjectId = lesson.getLong("IdPrzedmiot")?.let { val subjectId = lesson.getLong("IdPrzedmiot")?.let {
when (it) { when (it) {
0L -> { 0L -> {
val subjectName = lesson.getString("PrzedmiotNazwa") ?: "" val subjectName = lesson.getString("PrzedmiotNazwa") ?: ""
data.subjectList.singleOrNull { subject -> subject.longName == subjectName }?.id data.subjectList.singleOrNull { subject -> subject.longName == subjectName }?.id
?: { ?: {
/** /**
* CREATE A NEW SUBJECT IF IT DOESN'T EXIST * CREATE A NEW SUBJECT IF IT DOESN'T EXIST
*/ */
val subjectObject = Subject( val subjectObject = Subject(
profileId, profileId,
-1 * crc16(subjectName.toByteArray()).toLong(), -1 * crc16(subjectName.toByteArray()).toLong(),
subjectName, subjectName,
subjectName subjectName
) )
data.subjectList.put(subjectObject.id, subjectObject) data.subjectList.put(subjectObject.id, subjectObject)
subjectObject.id subjectObject.id
}.invoke() }.invoke()
} }
else -> it else -> it
}
}
val lessonObject = Lesson(profileId, -1).apply {
this.type = type
when (type) {
Lesson.TYPE_NORMAL, Lesson.TYPE_CHANGE, Lesson.TYPE_SHIFTED_TARGET -> {
this.date = lessonDate
this.lessonNumber = lessonNumber
this.startTime = startTime
this.endTime = endTime
this.subjectId = subjectId
this.teacherId = teacherId
this.teamId = teamId
this.classroom = classroom
this.oldTeacherId = oldTeacherId
}
Lesson.TYPE_CANCELLED, Lesson.TYPE_SHIFTED_SOURCE -> {
this.oldDate = lessonDate
this.oldLessonNumber = lessonNumber
this.oldStartTime = startTime
this.oldEndTime = endTime
this.oldSubjectId = subjectId
this.oldTeacherId = teacherId
this.oldTeamId = teamId
this.oldClassroom = classroom
} }
} }
val id = lessonDate.combineWith(startTime) / 6L * 10L + (lesson.hashCode() and 0xFFFF) if (type == Lesson.TYPE_SHIFTED_SOURCE || type == Lesson.TYPE_SHIFTED_TARGET) {
val shift = Regexes.VULCAN_SHITFT_ANNOTATION.find(changeAnnotation)
val oldLessonNumber = shift?.get(2)?.toInt()
val oldLessonDate = shift?.get(3)?.let { Date.fromd_m_Y(it) }
val lessonObject = Lesson(profileId, id).apply { val oldLessonRange = data.lessonRanges.singleOrNull { it.lessonNumber == oldLessonNumber }
this.type = type val oldStartTime = oldLessonRange?.startTime
val oldEndTime = oldLessonRange?.endTime
when (type) { when (type) {
Lesson.TYPE_NORMAL, Lesson.TYPE_CHANGE, Lesson.TYPE_SHIFTED_TARGET -> { Lesson.TYPE_SHIFTED_SOURCE -> {
this.date = lessonDate this.lessonNumber = oldLessonNumber
this.lessonNumber = lessonNumber this.date = oldLessonDate
this.startTime = startTime this.startTime = oldStartTime
this.endTime = endTime this.endTime = oldEndTime
this.subjectId = subjectId
this.teacherId = teacherId
this.teamId = teamId
this.classroom = classroom
this.oldTeacherId = oldTeacherId
} }
Lesson.TYPE_CANCELLED, Lesson.TYPE_SHIFTED_SOURCE -> { Lesson.TYPE_SHIFTED_TARGET -> {
this.oldDate = lessonDate this.oldLessonNumber = oldLessonNumber
this.oldLessonNumber = lessonNumber this.oldDate = oldLessonDate
this.oldStartTime = startTime this.oldStartTime = oldStartTime
this.oldEndTime = endTime this.oldEndTime = oldEndTime
this.oldSubjectId = subjectId
this.oldTeacherId = teacherId
this.oldTeamId = teamId
this.oldClassroom = classroom
}
}
if (type == Lesson.TYPE_SHIFTED_SOURCE || type == Lesson.TYPE_SHIFTED_TARGET) {
val shift = Regexes.VULCAN_SHITFT_ANNOTATION.find(changeAnnotation)
val oldLessonNumber = shift?.get(2)?.toInt()
val oldLessonDate = shift?.get(3)?.let { Date.fromd_m_Y(it) }
val oldLessonRange = data.lessonRanges.singleOrNull { it.lessonNumber == oldLessonNumber }
val oldStartTime = oldLessonRange?.startTime
val oldEndTime = oldLessonRange?.endTime
when (type) {
Lesson.TYPE_SHIFTED_SOURCE -> {
this.lessonNumber = oldLessonNumber
this.date = oldLessonDate
this.startTime = oldStartTime
this.endTime = oldEndTime
}
Lesson.TYPE_SHIFTED_TARGET -> {
this.oldLessonNumber = oldLessonNumber
this.oldDate = oldLessonDate
this.oldStartTime = oldStartTime
this.oldEndTime = oldEndTime
}
} }
} }
} }
if (type != Lesson.TYPE_NORMAL) { this.id = buildId()
data.metadataList.add(Metadata(
profileId,
Metadata.TYPE_LESSON_CHANGE,
id,
profile.empty,
profile.empty,
System.currentTimeMillis()
))
}
dates.add(lessonDate.value)
lessons.add(lessonObject)
} }
val date: Date = weekStart.clone() val seen = profile.empty || lessonDate < Date.getToday()
while (date <= weekEnd) {
if (!dates.contains(date.value)) {
lessons.add(Lesson(profileId, date.value.toLong()).apply {
this.type = Lesson.TYPE_NO_LESSONS
this.date = date.clone()
})
}
date.stepForward(0, 0, 1) if (type != Lesson.TYPE_NORMAL) {
data.metadataList.add(Metadata(
profileId,
Metadata.TYPE_LESSON_CHANGE,
lessonObject.id,
seen,
seen,
System.currentTimeMillis()
))
} }
d(TAG, "Clearing lessons between ${weekStart.stringY_m_d} and ${weekEnd.stringY_m_d} - timetable downloaded for $getDate") dates.add(lessonDate.value)
lessons.add(lessonObject)
data.lessonNewList.addAll(lessons)
data.toRemove.add(DataRemoveModel.Timetable.between(weekStart, weekEnd))
data.setSyncNext(ENDPOINT_VULCAN_API_TIMETABLE, SYNC_ALWAYS)
onSuccess()
} }
val date: Date = weekStart.clone()
while (date <= weekEnd) {
if (!dates.contains(date.value)) {
lessons.add(Lesson(profileId, date.value.toLong()).apply {
this.type = Lesson.TYPE_NO_LESSONS
this.date = date.clone()
})
}
date.stepForward(0, 0, 1)
}
d(TAG, "Clearing lessons between ${weekStart.stringY_m_d} and ${weekEnd.stringY_m_d} - timetable downloaded for $getDate")
data.lessonNewList.addAll(lessons)
data.toRemove.add(DataRemoveModel.Timetable.between(weekStart, weekEnd))
data.setSyncNext(ENDPOINT_VULCAN_API_TIMETABLE, SYNC_ALWAYS)
onSuccess()
} }
} }}
} }

View File

@ -38,10 +38,12 @@ class VulcanLoginApi(val data: DataVulcan, val onSuccess: () -> Unit) {
if (data.apiToken?.get(0) == 'F') VULCAN_API_PASSWORD_FAKELOG else VULCAN_API_PASSWORD, if (data.apiToken?.get(0) == 'F') VULCAN_API_PASSWORD_FAKELOG else VULCAN_API_PASSWORD,
data.apiCertificatePfx ?: "" data.apiCertificatePfx ?: ""
) )
onSuccess() data.loginStore.removeLoginData("certificatePfx")
return@run
} catch (e: Throwable) { } catch (e: Throwable) {
e.printStackTrace() e.printStackTrace()
} finally {
onSuccess()
return@run
} }
} }
if (data.symbol.isNotNullNorEmpty() && data.apiToken.isNotNullNorEmpty() && data.apiPin.isNotNullNorEmpty()) { if (data.symbol.isNotNullNorEmpty() && data.apiToken.isNotNullNorEmpty() && data.apiPin.isNotNullNorEmpty()) {

View File

@ -1,10 +1,10 @@
package pl.szczodrzynski.edziennik.data.db.modules.grades; package pl.szczodrzynski.edziennik.data.db.modules.grades;
import androidx.room.Entity;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import androidx.room.Entity;
@Entity(tableName = "gradeCategories", @Entity(tableName = "gradeCategories",
primaryKeys = {"profileId", "categoryId"}) primaryKeys = {"profileId", "categoryId"})
public class GradeCategory { public class GradeCategory {
@ -25,6 +25,9 @@ public class GradeCategory {
*/ */
public int type = 0; public int type = 0;
public static final int TYPE_NORMAL = 0;
public static final int TYPE_COMMENT = 1;
public GradeCategory(int profileId, long categoryId, float weight, int color, String text) { public GradeCategory(int profileId, long categoryId, float weight, int color, String text) {
this.profileId = profileId; this.profileId = profileId;
this.categoryId = categoryId; this.categoryId = categoryId;

View File

@ -37,7 +37,10 @@ public abstract class LuckyNumberDao {
@Nullable @Nullable
@Query("SELECT * FROM luckyNumbers WHERE profileId = :profileId AND luckyNumberDate >= :date ORDER BY luckyNumberDate DESC LIMIT 1") @Query("SELECT * FROM luckyNumbers WHERE profileId = :profileId AND luckyNumberDate >= :date ORDER BY luckyNumberDate DESC LIMIT 1")
public abstract LuckyNumber getNearestFutureNow(int profileId, Date date); public abstract LuckyNumber getNearestFutureNow(int profileId, int date);
@Query("SELECT * FROM luckyNumbers WHERE profileId = :profileId AND luckyNumberDate >= :date ORDER BY luckyNumberDate DESC LIMIT 1")
public abstract LiveData<LuckyNumber> getNearestFuture(int profileId, int date);
@RawQuery(observedEntities = {LuckyNumber.class}) @RawQuery(observedEntities = {LuckyNumber.class})
abstract LiveData<List<LuckyNumberFull>> getAll(SupportSQLiteQuery query); abstract LiveData<List<LuckyNumberFull>> getAll(SupportSQLiteQuery query);

View File

@ -21,7 +21,10 @@ interface NotificationDao {
@Query("DELETE FROM notifications WHERE profileId = :profileId") @Query("DELETE FROM notifications WHERE profileId = :profileId")
fun clear(profileId: Int) fun clear(profileId: Int)
@Query("SELECT * FROM notifications") @Query("DELETE FROM notifications")
fun clearAll()
@Query("SELECT * FROM notifications ORDER BY addedDate DESC")
fun getAll(): LiveData<List<Notification>> fun getAll(): LiveData<List<Notification>>
@Query("SELECT * FROM notifications") @Query("SELECT * FROM notifications")

View File

@ -15,7 +15,7 @@ import pl.szczodrzynski.edziennik.utils.models.Time
Index(value = ["profileId", "type", "date"]), Index(value = ["profileId", "type", "date"]),
Index(value = ["profileId", "type", "oldDate"]) Index(value = ["profileId", "type", "oldDate"])
]) ])
open class Lesson(val profileId: Int, @PrimaryKey val id: Long) { open class Lesson(val profileId: Int, @PrimaryKey var id: Long) {
companion object { companion object {
const val TYPE_NO_LESSONS = -1 const val TYPE_NO_LESSONS = -1
const val TYPE_NORMAL = 0 const val TYPE_NORMAL = 0
@ -45,6 +45,22 @@ open class Lesson(val profileId: Int, @PrimaryKey val id: Long) {
var oldTeamId: Long? = null var oldTeamId: Long? = null
var oldClassroom: String? = null var oldClassroom: String? = null
val displayDate: Date?
get() {
if (type == TYPE_SHIFTED_SOURCE)
return oldDate
return date ?: oldDate
}
val displayStartTime: Time?
get() {
if (type == TYPE_SHIFTED_SOURCE)
return oldStartTime
return startTime ?: oldStartTime
}
fun buildId(): Long = (displayDate?.combineWith(displayStartTime) ?: 0L) / 6L * 10L + (hashCode() and 0xFFFF)
override fun toString(): String { override fun toString(): String {
return "Lesson(profileId=$profileId, " + return "Lesson(profileId=$profileId, " +
"id=$id, " + "id=$id, " +
@ -66,6 +82,55 @@ open class Lesson(val profileId: Int, @PrimaryKey val id: Long) {
"oldTeamId=$oldTeamId, " + "oldTeamId=$oldTeamId, " +
"oldClassroom=$oldClassroom)" "oldClassroom=$oldClassroom)"
} }
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other !is Lesson) return false
if (profileId != other.profileId) return false
if (id != other.id) return false
if (type != other.type) return false
if (date != other.date) return false
if (lessonNumber != other.lessonNumber) return false
if (startTime != other.startTime) return false
if (endTime != other.endTime) return false
if (subjectId != other.subjectId) return false
if (teacherId != other.teacherId) return false
if (teamId != other.teamId) return false
if (classroom != other.classroom) return false
if (oldDate != other.oldDate) return false
if (oldLessonNumber != other.oldLessonNumber) return false
if (oldStartTime != other.oldStartTime) return false
if (oldEndTime != other.oldEndTime) return false
if (oldSubjectId != other.oldSubjectId) return false
if (oldTeacherId != other.oldTeacherId) return false
if (oldTeamId != other.oldTeamId) return false
if (oldClassroom != other.oldClassroom) return false
return true
}
override fun hashCode(): Int {
var result = profileId
result = 31 * result + type
result = 31 * result + (date?.hashCode() ?: 0)
result = 31 * result + (lessonNumber ?: 0)
result = 31 * result + (startTime?.hashCode() ?: 0)
result = 31 * result + (endTime?.hashCode() ?: 0)
result = 31 * result + (subjectId?.hashCode() ?: 0)
result = 31 * result + (teacherId?.hashCode() ?: 0)
result = 31 * result + (teamId?.hashCode() ?: 0)
result = 31 * result + (classroom?.hashCode() ?: 0)
result = 31 * result + (oldDate?.hashCode() ?: 0)
result = 31 * result + (oldLessonNumber ?: 0)
result = 31 * result + (oldStartTime?.hashCode() ?: 0)
result = 31 * result + (oldEndTime?.hashCode() ?: 0)
result = 31 * result + (oldSubjectId?.hashCode() ?: 0)
result = 31 * result + (oldTeacherId?.hashCode() ?: 0)
result = 31 * result + (oldTeamId?.hashCode() ?: 0)
result = 31 * result + (oldClassroom?.hashCode() ?: 0)
return result
}
} }
/* /*
DROP TABLE lessons; DROP TABLE lessons;
@ -94,4 +159,4 @@ CREATE TABLE lessons (
PRIMARY KEY(profileId) PRIMARY KEY(profileId)
); );
*/ */

View File

@ -1,6 +1,7 @@
package pl.szczodrzynski.edziennik.data.db.modules.timetable package pl.szczodrzynski.edziennik.data.db.modules.timetable
import pl.szczodrzynski.edziennik.utils.models.Date import android.content.Context
import pl.szczodrzynski.edziennik.R
import pl.szczodrzynski.edziennik.utils.models.Time import pl.szczodrzynski.edziennik.utils.models.Time
class LessonFull(profileId: Int, id: Long) : Lesson(profileId, id) { class LessonFull(profileId: Int, id: Long) : Lesson(profileId, id) {
@ -11,24 +12,13 @@ class LessonFull(profileId: Int, id: Long) : Lesson(profileId, id) {
var oldTeacherName: String? = null var oldTeacherName: String? = null
var oldTeamName: String? = null var oldTeamName: String? = null
val displayDate: Date?
get() {
if (type == TYPE_SHIFTED_SOURCE)
return oldDate
return date ?: oldDate
}
val displayLessonNumber: Int? val displayLessonNumber: Int?
get() { get() {
if (type == TYPE_SHIFTED_SOURCE) if (type == TYPE_SHIFTED_SOURCE)
return oldLessonNumber return oldLessonNumber
return lessonNumber ?: oldLessonNumber return lessonNumber ?: oldLessonNumber
} }
val displayStartTime: Time?
get() {
if (type == TYPE_SHIFTED_SOURCE)
return oldStartTime
return startTime ?: oldStartTime
}
val displayEndTime: Time? val displayEndTime: Time?
get() { get() {
if (type == TYPE_SHIFTED_SOURCE) if (type == TYPE_SHIFTED_SOURCE)
@ -42,12 +32,14 @@ class LessonFull(profileId: Int, id: Long) : Lesson(profileId, id) {
return oldSubjectName return oldSubjectName
return subjectName ?: oldSubjectName return subjectName ?: oldSubjectName
} }
val displayTeacherName: String? val displayTeacherName: String?
get() { get() {
if (type == TYPE_SHIFTED_SOURCE) if (type == TYPE_SHIFTED_SOURCE)
return oldTeacherName return oldTeacherName
return teacherName ?: oldTeacherName return teacherName ?: oldTeacherName
} }
val displayTeamName: String? val displayTeamName: String?
get() { get() {
if (type == TYPE_SHIFTED_SOURCE) if (type == TYPE_SHIFTED_SOURCE)
@ -68,12 +60,14 @@ class LessonFull(profileId: Int, id: Long) : Lesson(profileId, id) {
return oldTeamId return oldTeamId
return teamId ?: oldTeamId return teamId ?: oldTeamId
} }
val displaySubjectId: Long? val displaySubjectId: Long?
get() { get() {
if (type == TYPE_SHIFTED_SOURCE) if (type == TYPE_SHIFTED_SOURCE)
return oldSubjectId return oldSubjectId
return subjectId ?: oldSubjectId return subjectId ?: oldSubjectId
} }
val displayTeacherId: Long? val displayTeacherId: Long?
get() { get() {
if (type == TYPE_SHIFTED_SOURCE) if (type == TYPE_SHIFTED_SOURCE)
@ -81,8 +75,35 @@ class LessonFull(profileId: Int, id: Long) : Lesson(profileId, id) {
return teacherId ?: oldTeacherId return teacherId ?: oldTeacherId
} }
fun getDisplayChangeType(context: Context): String {
return context.getString(when (type) {
TYPE_CHANGE -> R.string.lesson_change
TYPE_CANCELLED -> R.string.lesson_cancelled
TYPE_SHIFTED_TARGET, TYPE_SHIFTED_SOURCE -> R.string.lesson_shifted
else -> R.string.lesson_timetable_change
})
}
val changeSubjectName: String
get() {
val first = when (type) {
TYPE_CHANGE, TYPE_CANCELLED, TYPE_SHIFTED_SOURCE -> oldSubjectName
else -> subjectName
}
val second = when (type) {
TYPE_CHANGE -> subjectName
else -> null
}
return when (second) {
null -> first ?: ""
else -> "$first -> $second"
}
}
// metadata // metadata
var seen: Boolean = false var seen: Boolean = false
var notified: Boolean = false var notified: Boolean = false
var addedDate: Long = 0 var addedDate: Long = 0
} }

View File

@ -38,16 +38,16 @@ interface TimetableDao {
@Query("DELETE FROM timetable WHERE profileId = :profileId") @Query("DELETE FROM timetable WHERE profileId = :profileId")
fun clear(profileId: Int) fun clear(profileId: Int)
@Query("DELETE FROM timetable WHERE profileId = :profileId AND (type != 3 AND date >= :dateFrom) OR ((type = 3 OR type = 1) AND oldDate >= :dateFrom)") @Query("DELETE FROM timetable WHERE profileId = :profileId AND ((type != 3 AND date >= :dateFrom) OR ((type = 3 OR type = 1) AND oldDate >= :dateFrom))")
fun clearFromDate(profileId: Int, dateFrom: Date) fun clearFromDate(profileId: Int, dateFrom: Date)
@Query("DELETE FROM timetable WHERE profileId = :profileId AND (type != 3 AND date <= :dateTo) OR ((type = 3 OR type = 1) AND oldDate <= :dateTo)") @Query("DELETE FROM timetable WHERE profileId = :profileId AND ((type != 3 AND date <= :dateTo) OR ((type = 3 OR type = 1) AND oldDate <= :dateTo))")
fun clearToDate(profileId: Int, dateTo: Date) fun clearToDate(profileId: Int, dateTo: Date)
@Query("DELETE FROM timetable WHERE profileId = :profileId AND (type != 3 AND date >= :dateFrom AND date <= :dateTo) OR ((type = 3 OR type = 1) AND oldDate >= :dateFrom AND oldDate <= :dateTo)") @Query("DELETE FROM timetable WHERE profileId = :profileId AND ((type != 3 AND date >= :dateFrom AND date <= :dateTo) OR ((type = 3 OR type = 1) AND oldDate >= :dateFrom AND oldDate <= :dateTo))")
fun clearBetweenDates(profileId: Int, dateFrom: Date, dateTo: Date) fun clearBetweenDates(profileId: Int, dateFrom: Date, dateTo: Date)
@Query(""" @Query("""
$QUERY $QUERY
WHERE timetable.profileId = :profileId AND (type != 3 AND date = :date) OR ((type = 3 OR type = 1) AND oldDate = :date) WHERE timetable.profileId = :profileId AND ((type != 3 AND date = :date) OR ((type = 3 OR type = 1) AND oldDate = :date))
ORDER BY id, type ORDER BY id, type
""") """)
fun getForDate(profileId: Int, date: Date) : LiveData<List<LessonFull>> fun getForDate(profileId: Int, date: Date) : LiveData<List<LessonFull>>
@ -58,7 +58,15 @@ interface TimetableDao {
ORDER BY id, type ORDER BY id, type
LIMIT 1 LIMIT 1
""") """)
fun getNextWithSubject(profileId: Int, today: Date, subjectId: Long) : LiveData<LessonFull> fun getNextWithSubject(profileId: Int, today: Date, subjectId: Long) : LiveData<LessonFull?>
@Query("""
$QUERY
WHERE timetable.profileId = :profileId AND ((type != 3 AND date > :today) OR ((type = 3 OR type = 1) AND oldDate > :today)) AND timetable.subjectId = :subjectId AND timetable.teamId = :teamId
ORDER BY id, type
LIMIT 1
""")
fun getNextWithSubjectAndTeam(profileId: Int, today: Date, subjectId: Long, teamId: Long): LiveData<LessonFull?>
@Query(""" @Query("""
$QUERY $QUERY
@ -67,10 +75,23 @@ interface TimetableDao {
""") """)
fun getBetweenDatesNow(dateFrom: Date, dateTo: Date) : List<LessonFull> fun getBetweenDatesNow(dateFrom: Date, dateTo: Date) : List<LessonFull>
@Query("""
$QUERY
WHERE (type != 3 AND date >= :dateFrom AND date <= :dateTo) OR ((type = 3 OR type = 1) AND oldDate >= :dateFrom AND oldDate <= :dateTo)
ORDER BY profileId, id, type
""")
fun getBetweenDates(dateFrom: Date, dateTo: Date) : LiveData<List<LessonFull>>
@Query(""" @Query("""
$QUERY $QUERY
WHERE timetable.profileId = :profileId AND timetable.id = :lessonId WHERE timetable.profileId = :profileId AND timetable.id = :lessonId
ORDER BY id, type ORDER BY id, type
""") """)
fun getByIdNow(profileId: Int, lessonId: Long) : LessonFull? fun getByIdNow(profileId: Int, lessonId: Long) : LessonFull?
@Query("""
$QUERY
WHERE timetable.profileId = :profileId AND timetable.type NOT IN (${Lesson.TYPE_NORMAL}, ${Lesson.TYPE_NO_LESSONS}, ${Lesson.TYPE_SHIFTED_SOURCE}) AND metadata.notified = 0
""")
fun getNotNotifiedNow(profileId: Int): List<LessonFull>
} }

View File

@ -125,19 +125,22 @@ public class BootReceiver extends BroadcastReceiver {
String updateUrl = result.get("update_url").getAsString(); String updateUrl = result.get("update_url").getAsString();
String updateFilename = result.get("update_filename").getAsString(); String updateFilename = result.get("update_filename").getAsString();
boolean updateMandatory = result.get("update_mandatory").getAsBoolean(); boolean updateMandatory = result.get("update_mandatory").getAsBoolean();
boolean updateDirect = result.get("update_direct").getAsBoolean();
if (app.appConfig.updateVersion == null || !app.appConfig.updateVersion.equals(updateVersion)) { if (app.appConfig.updateVersion == null || !app.appConfig.updateVersion.equals(updateVersion)) {
app.appConfig.updateVersion = updateVersion; app.appConfig.updateVersion = updateVersion;
app.appConfig.updateUrl = updateUrl; app.appConfig.updateUrl = updateUrl;
app.appConfig.updateFilename = updateFilename; app.appConfig.updateFilename = updateFilename;
app.appConfig.updateMandatory = updateMandatory; app.appConfig.updateMandatory = updateMandatory;
app.appConfig.updateDirect = updateDirect;
app.saveConfig(); app.saveConfig();
} }
if (!UPDATES_ON_PLAY_STORE || intent.getBooleanExtra("UserChecked", false)) { if (!UPDATES_ON_PLAY_STORE || intent.getBooleanExtra("UserChecked", false)) {
app.notifier.notificationUpdatesShow( app.notifier.notificationUpdatesShow(
updateVersion, updateVersion,
updateUrl, updateUrl,
updateFilename); updateFilename,
updateDirect);
} }
} else { } else {
if (app.appConfig.updateVersion == null || !app.appConfig.updateVersion.equals("")) { if (app.appConfig.updateVersion == null || !app.appConfig.updateVersion.equals("")) {
@ -259,7 +262,7 @@ public class BootReceiver extends BroadcastReceiver {
@Override @Override
protected void onHandleIntent(Intent intent) { protected void onHandleIntent(Intent intent) {
if (UPDATES_ON_PLAY_STORE) { if (UPDATES_ON_PLAY_STORE && !intent.getBooleanExtra("update_direct", false)) {
Utils.openGooglePlay(this, "pl.szczodrzynski.edziennik"); Utils.openGooglePlay(this, "pl.szczodrzynski.edziennik");
return; return;
} }
@ -270,7 +273,8 @@ public class BootReceiver extends BroadcastReceiver {
app.notifier.notificationUpdatesShow( app.notifier.notificationUpdatesShow(
intent.getStringExtra("update_version"), intent.getStringExtra("update_version"),
intent.getStringExtra("update_url"), intent.getStringExtra("update_url"),
intent.getStringExtra("update_filename")); intent.getStringExtra("update_filename"),
intent.getBooleanExtra("update_direct", false));
return; return;
} }
} }

View File

@ -154,19 +154,22 @@ public class MyFirebaseMessagingService extends FirebaseMessagingService {
String updateUrl = remoteMessage.getData().get("update_url"); String updateUrl = remoteMessage.getData().get("update_url");
String updateFilename = remoteMessage.getData().get("update_filename"); String updateFilename = remoteMessage.getData().get("update_filename");
boolean updateMandatory = Boolean.parseBoolean(remoteMessage.getData().get("update_mandatory")); boolean updateMandatory = Boolean.parseBoolean(remoteMessage.getData().get("update_mandatory"));
boolean updateDirect = Boolean.parseBoolean(remoteMessage.getData().get("update_direct"));
if (app.appConfig.updateVersion == null || !app.appConfig.updateVersion.equals(updateVersion)) { if (app.appConfig.updateVersion == null || !app.appConfig.updateVersion.equals(updateVersion)) {
app.appConfig.updateVersion = updateVersion; app.appConfig.updateVersion = updateVersion;
app.appConfig.updateUrl = updateUrl; app.appConfig.updateUrl = updateUrl;
app.appConfig.updateFilename = updateFilename; app.appConfig.updateFilename = updateFilename;
app.appConfig.updateMandatory = updateMandatory; app.appConfig.updateMandatory = updateMandatory;
app.appConfig.updateDirect = updateDirect;
app.saveConfig("updateVersion", "updateUrl", "updateFilename", "updateMandatory"); app.saveConfig("updateVersion", "updateUrl", "updateFilename", "updateMandatory");
} }
if (!remoteMessage.getData().containsKey("update_silent")) { if (!remoteMessage.getData().containsKey("update_silent")) {
app.notifier.notificationUpdatesShow( app.notifier.notificationUpdatesShow(
updateVersion, updateVersion,
updateUrl, updateUrl,
updateFilename); updateFilename,
updateDirect);
} }
} else { } else {
if (app.appConfig.updateVersion == null || !app.appConfig.updateVersion.equals("")) { if (app.appConfig.updateVersion == null || !app.appConfig.updateVersion.equals("")) {

View File

@ -517,7 +517,7 @@ public class EventManualDialog {
registerEventManualDateLayout = dialogView.findViewById(R.id.registerEventManualDateLayout); registerEventManualDateLayout = dialogView.findViewById(R.id.registerEventManualDateLayout);
registerEventManualDate = dialogView.findViewById(R.id.registerEventManualDate); registerEventManualDate = dialogView.findViewById(R.id.registerEventManualDate);
registerEventManualDate.setCompoundDrawablesWithIntrinsicBounds(null, null, new IconicsDrawable(context, CommunityMaterial.Icon.cmd_calendar).size(IconicsSize.dp(16)).color(IconicsColor.colorInt(primaryTextColor)), null); registerEventManualDate.setCompoundDrawablesWithIntrinsicBounds(null, null, new IconicsDrawable(context, CommunityMaterial.Icon.cmd_calendar_outline).size(IconicsSize.dp(16)).color(IconicsColor.colorInt(primaryTextColor)), null);
//registerEventManualDate.setCompoundDrawablePadding(Utils.dpToPx(6)); //registerEventManualDate.setCompoundDrawablePadding(Utils.dpToPx(6));
registerEventManualLessonLayout = dialogView.findViewById(R.id.registerEventManualLessonLayout); registerEventManualLessonLayout = dialogView.findViewById(R.id.registerEventManualLessonLayout);
registerEventManualLesson = dialogView.findViewById(R.id.registerEventManualLesson); registerEventManualLesson = dialogView.findViewById(R.id.registerEventManualLesson);

View File

@ -4,19 +4,26 @@
package pl.szczodrzynski.edziennik.ui.dialogs.event package pl.szczodrzynski.edziennik.ui.dialogs.event
import android.graphics.PorterDuff
import android.graphics.PorterDuffColorFilter
import android.view.View
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.Observer import androidx.lifecycle.Observer
import com.google.android.material.datepicker.MaterialDatePicker import com.google.android.material.datepicker.MaterialDatePicker
import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.jaredrummler.android.colorpicker.ColorPickerDialog
import com.jaredrummler.android.colorpicker.ColorPickerDialogListener
import kotlinx.coroutines.* import kotlinx.coroutines.*
import pl.szczodrzynski.edziennik.* import pl.szczodrzynski.edziennik.*
import pl.szczodrzynski.edziennik.data.db.modules.events.Event import pl.szczodrzynski.edziennik.data.db.modules.events.Event
import pl.szczodrzynski.edziennik.data.db.modules.events.EventType
import pl.szczodrzynski.edziennik.data.db.modules.subjects.Subject import pl.szczodrzynski.edziennik.data.db.modules.subjects.Subject
import pl.szczodrzynski.edziennik.data.db.modules.teams.Team import pl.szczodrzynski.edziennik.data.db.modules.teams.Team
import pl.szczodrzynski.edziennik.data.db.modules.timetable.Lesson import pl.szczodrzynski.edziennik.data.db.modules.timetable.Lesson
import pl.szczodrzynski.edziennik.data.db.modules.timetable.LessonFull import pl.szczodrzynski.edziennik.data.db.modules.timetable.LessonFull
import pl.szczodrzynski.edziennik.databinding.DialogEventManualV2Binding import pl.szczodrzynski.edziennik.databinding.DialogEventManualV2Binding
import pl.szczodrzynski.edziennik.utils.Anim
import pl.szczodrzynski.edziennik.utils.TextInputDropDown import pl.szczodrzynski.edziennik.utils.TextInputDropDown
import pl.szczodrzynski.edziennik.utils.models.Date import pl.szczodrzynski.edziennik.utils.models.Date
import pl.szczodrzynski.edziennik.utils.models.Time import pl.szczodrzynski.edziennik.utils.models.Time
@ -46,9 +53,11 @@ class EventManualV2Dialog(
private val app by lazy { activity.application as App } private val app by lazy { activity.application as App }
private lateinit var b: DialogEventManualV2Binding private lateinit var b: DialogEventManualV2Binding
private lateinit var dialog: AlertDialog private lateinit var dialog: AlertDialog
private lateinit var event: Event
private var defaultLoaded = false private var defaultLoaded = false
private lateinit var event: Event
private var customColor: Int? = null
init { run { init { run {
if (activity.isFinishing) if (activity.isFinishing)
return@run return@run
@ -78,11 +87,46 @@ class EventManualV2Dialog(
defaultType?.let { defaultType?.let {
event.type = it event.type = it
}*/ }*/
b.shareSwitch.isChecked = event.sharedBy != null
}
b.showMore.onClick { // TODO iconics is broken
it.apply {
refreshDrawableState()
if (isChecked)
Anim.expand(b.moreLayout, 200, null)
else
Anim.collapse(b.moreLayout, 200, null)
}
}
updateShareText()
b.shareSwitch.onChange { _, isChecked ->
updateShareText(isChecked)
} }
loadLists() loadLists()
}} }}
private fun updateShareText(checked: Boolean = b.shareSwitch.isChecked) {
val editingShared = editingEvent?.sharedBy != null
val editingOwn = editingEvent?.sharedBy == "self"
b.shareDetails.visibility = if (checked || editingShared)
View.VISIBLE
else View.GONE
val text = when {
checked && editingShared && editingOwn -> R.string.dialog_event_manual_share_will_change
checked && editingShared -> R.string.dialog_event_manual_share_will_request
!checked && editingShared -> R.string.dialog_event_manual_share_will_remove
else -> R.string.dialog_event_manual_share_first_notice
}
b.shareDetails.setText(text)
}
private fun loadLists() { launch { private fun loadLists() { launch {
val deferred = async(Dispatchers.Default) { val deferred = async(Dispatchers.Default) {
// get the team list // get the team list
@ -114,18 +158,33 @@ class EventManualV2Dialog(
"" ""
) )
b.teacherDropdown += teachers.map { TextInputDropDown.Item(it.id, it.fullName, tag = it) } b.teacherDropdown += teachers.map { TextInputDropDown.Item(it.id, it.fullName, tag = it) }
// get the event type list
val eventTypes = app.db.eventTypeDao().getAllNow(profileId)
b.typeDropdown.clear()
b.typeDropdown += eventTypes.map { TextInputDropDown.Item(it.id, it.name, tag = it) }
} }
deferred.await() deferred.await()
b.teamDropdown.isEnabled = true b.teamDropdown.isEnabled = true
b.subjectDropdown.isEnabled = true b.subjectDropdown.isEnabled = true
b.teacherDropdown.isEnabled = true b.teacherDropdown.isEnabled = true
b.typeDropdown.isEnabled = true
b.typeDropdown.selected?.let { item ->
customColor = (item.tag as EventType).color
}
// copy IDs from event being edited // copy IDs from event being edited
editingEvent?.let { editingEvent?.let {
b.teamDropdown.select(it.teamId) b.teamDropdown.select(it.teamId)
b.subjectDropdown.select(it.subjectId) b.subjectDropdown.select(it.subjectId)
b.teacherDropdown.select(it.teacherId) b.teacherDropdown.select(it.teacherId)
b.typeDropdown.select(it.type)?.let { item ->
customColor = (item.tag as EventType).color
}
if (it.color != -1)
customColor = it.color
} }
// copy IDs from the LessonFull // copy IDs from the LessonFull
@ -135,6 +194,30 @@ class EventManualV2Dialog(
b.teacherDropdown.select(it.displayTeacherId) b.teacherDropdown.select(it.displayTeacherId)
} }
b.typeDropdown.setOnChangeListener {
b.typeColor.background.colorFilter = PorterDuffColorFilter((it.tag as EventType).color, PorterDuff.Mode.SRC_ATOP)
customColor = null
return@setOnChangeListener true
}
(customColor ?: Event.COLOR_DEFAULT).let {
b.typeColor.background.colorFilter = PorterDuffColorFilter(it, PorterDuff.Mode.SRC_ATOP)
}
b.typeColor.onClick {
val currentColor = (b.typeDropdown?.selected?.tag as EventType?)?.color ?: Event.COLOR_DEFAULT
val colorPickerDialog = ColorPickerDialog.newBuilder()
.setColor(currentColor)
.create()
colorPickerDialog.setColorPickerDialogListener(
object : ColorPickerDialogListener {
override fun onDialogDismissed(dialogId: Int) {}
override fun onColorSelected(dialogId: Int, color: Int) {
b.typeColor.background.colorFilter = PorterDuffColorFilter(color, PorterDuff.Mode.SRC_ATOP)
customColor = color
}
})
colorPickerDialog.show(activity.fragmentManager, "color-picker-dialog")
}
loadDates() loadDates()
}} }}
@ -205,12 +288,20 @@ class EventManualV2Dialog(
val dates = deferred.await() val dates = deferred.await()
b.dateDropdown.clear().append(dates) b.dateDropdown.clear().append(dates)
editingEvent?.let { editingEvent?.eventDate?.let {
b.dateDropdown.select(it.eventDate.value.toLong()) b.dateDropdown.select(TextInputDropDown.Item(
it.value.toLong(),
it.formattedString,
tag = it
))
} }
defaultLesson?.let { defaultLesson?.displayDate?.let {
b.dateDropdown.select(it.displayDate?.value?.toLong()) b.dateDropdown.select(TextInputDropDown.Item(
it.value.toLong(),
it.formattedString,
tag = it
))
} }
if (b.dateDropdown.selected == null) { if (b.dateDropdown.selected == null) {
@ -223,22 +314,24 @@ class EventManualV2Dialog(
when { when {
// next lesson with specified subject // next lesson with specified subject
item.id < -1 -> { item.id < -1 -> {
app.db.timetableDao().getNextWithSubject(profileId, Date.getToday(), -item.id).observeOnce(activity, Observer { val teamId = defaultLesson?.teamId ?: -1
val selectedLessonDate = defaultLesson?.date ?: Date.getToday()
when (teamId) {
-1L -> app.db.timetableDao().getNextWithSubject(profileId, selectedLessonDate, -item.id)
else -> app.db.timetableDao().getNextWithSubjectAndTeam(profileId, selectedLessonDate, -item.id, teamId)
}.observeOnce(activity, Observer {
val lessonDate = it?.displayDate ?: return@Observer val lessonDate = it?.displayDate ?: return@Observer
b.dateDropdown.selected = TextInputDropDown.Item( b.dateDropdown.select(TextInputDropDown.Item(
lessonDate.value.toLong(), lessonDate.value.toLong(),
lessonDate.formattedString, lessonDate.formattedString,
tag = lessonDate tag = lessonDate
) ))
// TODO load correct hour when selecting next lesson b.teamDropdown.select(it.displayTeamId)
b.dateDropdown.updateText() b.subjectDropdown.select(it.displaySubjectId)
it.let { b.teacherDropdown.select(it.displayTeacherId)
b.teamDropdown.select(it.displayTeamId)
b.subjectDropdown.select(it.displaySubjectId)
b.teacherDropdown.select(it.displayTeacherId)
}
defaultLoaded = false defaultLoaded = false
loadHours() loadHours(it.displayStartTime)
}) })
return@setOnChangeListener false return@setOnChangeListener false
} }
@ -246,17 +339,17 @@ class EventManualV2Dialog(
item.id == -1L -> { item.id == -1L -> {
MaterialDatePicker.Builder MaterialDatePicker.Builder
.datePicker() .datePicker()
.setSelection((b.dateDropdown.selectedId?.let { Date.fromValue(it.toInt()) } ?: Date.getToday()).inMillis) .setSelection((b.dateDropdown.selectedId?.let { Date.fromValue(it.toInt()) }
?: Date.getToday()).inMillis)
.build() .build()
.apply { .apply {
addOnPositiveButtonClickListener { addOnPositiveButtonClickListener {
val dateSelected = Date.fromMillis(it) val dateSelected = Date.fromMillis(it)
b.dateDropdown.selected = TextInputDropDown.Item( b.dateDropdown.select(TextInputDropDown.Item(
dateSelected.value.toLong(), dateSelected.value.toLong(),
dateSelected.formattedString, dateSelected.formattedString,
tag = dateSelected tag = dateSelected
) ))
b.dateDropdown.updateText()
loadHours() loadHours()
} }
show(this@EventManualV2Dialog.activity.supportFragmentManager, "MaterialDatePicker") show(this@EventManualV2Dialog.activity.supportFragmentManager, "MaterialDatePicker")
@ -276,7 +369,7 @@ class EventManualV2Dialog(
loadHours() loadHours()
}} }}
private fun loadHours() { private fun loadHours(defaultHour: Time? = null) {
b.timeDropdown.isEnabled = false b.timeDropdown.isEnabled = false
// get the selected date // get the selected date
val date = b.dateDropdown.selectedId?.let { Date.fromValue(it.toInt()) } ?: return val date = b.dateDropdown.selectedId?.let { Date.fromValue(it.toInt()) } ?: return
@ -302,7 +395,8 @@ class EventManualV2Dialog(
lesson.displayStartTime?.stringHM ?: "", lesson.displayStartTime?.stringHM ?: "",
lesson.displaySubjectName?.let { lesson.displaySubjectName?.let {
when { when {
lesson.type == Lesson.TYPE_CANCELLED -> it.asStrikethroughSpannable() lesson.type == Lesson.TYPE_CANCELLED
|| lesson.type == Lesson.TYPE_SHIFTED_SOURCE -> it.asStrikethroughSpannable()
lesson.type != Lesson.TYPE_NORMAL -> it.asItalicSpannable() lesson.type != Lesson.TYPE_NORMAL -> it.asItalicSpannable()
else -> it else -> it
} }
@ -338,6 +432,10 @@ class EventManualV2Dialog(
defaultLesson?.let { defaultLesson?.let {
b.timeDropdown.select(it.displayStartTime?.value?.toLong()) b.timeDropdown.select(it.displayStartTime?.value?.toLong())
} }
defaultHour?.let {
b.timeDropdown.select(it.value.toLong())
}
} }
defaultLoaded = true defaultLoaded = true
b.timeDropdown.isEnabled = true b.timeDropdown.isEnabled = true
@ -345,16 +443,16 @@ class EventManualV2Dialog(
// attach a listener to time dropdown // attach a listener to time dropdown
b.timeDropdown.setOnChangeListener { item -> b.timeDropdown.setOnChangeListener { item ->
when { when {
// custom start hour
item.id == -1L -> {
return@setOnChangeListener false
}
// no lessons this day // no lessons this day
item.id == -2L -> { item.id == -2L -> {
b.timeDropdown.deselect() b.timeDropdown.deselect()
return@setOnChangeListener false return@setOnChangeListener false
} }
// custom start hour
item.id == -1L -> {
return@setOnChangeListener false
}
// selected a specific lesson // selected a specific lesson
else -> { else -> {
if (item.tag is LessonFull) { if (item.tag is LessonFull) {
@ -383,4 +481,4 @@ class EventManualV2Dialog(
private fun saveEvent() { private fun saveEvent() {
} }
} }

View File

@ -0,0 +1,46 @@
/*
* Copyright (c) Kuba Szczodrzyński 2019-11-24.
*/
package pl.szczodrzynski.edziennik.ui.dialogs.home
import android.text.InputType
import android.widget.Toast
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import com.afollestad.materialdialogs.MaterialDialog
import pl.szczodrzynski.edziennik.R
import pl.szczodrzynski.edziennik.data.db.modules.profiles.Profile
class StudentNumberDialog(
val activity: AppCompatActivity,
val profile: Profile,
val onShowListener: ((tag: String) -> Unit)? = null,
val onDismissListener: ((tag: String) -> Unit)? = null
) {
companion object {
private const val TAG = "StudentNumberDialog"
}
private lateinit var dialog: AlertDialog
init { run {
if (activity.isFinishing)
return@run
onShowListener?.invoke(TAG)
MaterialDialog.Builder(activity)
.title(R.string.card_lucky_number_set_title)
.content(R.string.card_lucky_number_set_text)
.inputType(InputType.TYPE_CLASS_NUMBER)
.input(null, if (profile.studentNumber == -1) "" else profile.studentNumber.toString()) { _: MaterialDialog?, input: CharSequence ->
try {
profile.studentNumber = input.toString().toInt()
} catch (e: Exception) {
Toast.makeText(activity, R.string.incorrect_format, Toast.LENGTH_SHORT).show()
}
}
.dismissListener {
onDismissListener?.invoke(TAG)
}.show()
}}
}

View File

@ -94,15 +94,23 @@ class SyncViewListDialog(
listOfNotNull(*it.toTypedArray()) listOfNotNull(*it.toTypedArray())
} }
if (selectedViewIds.isNotEmpty()) {
activity.swipeRefreshLayout.isRefreshing = true
EdziennikTask.syncProfile(
App.profileId,
selectedViewIds
).enqueue(activity)
}
}
.setNeutralButton(R.string.sync_feature_all) { _, _ ->
dialog.dismiss()
activity.swipeRefreshLayout.isRefreshing = true activity.swipeRefreshLayout.isRefreshing = true
EdziennikTask.syncProfile( EdziennikTask.syncProfile(App.profileId).enqueue(activity)
App.profileId,
selectedViewIds
).enqueue(activity)
} }
.setNegativeButton(R.string.cancel) { _, _ -> .setNegativeButton(R.string.cancel) { _, _ ->
dialog.dismiss() dialog.dismiss()
} }
.show() .show()
}} }}
} }

View File

@ -120,7 +120,7 @@ class LessonDetailsDialog(
dialog.dismiss() dialog.dismiss()
val dateStr = otherLessonDate?.stringY_m_d ?: return@setOnClickListener val dateStr = otherLessonDate?.stringY_m_d ?: return@setOnClickListener
val intent = Intent(TimetableFragment.ACTION_SCROLL_TO_DATE).apply { val intent = Intent(TimetableFragment.ACTION_SCROLL_TO_DATE).apply {
putExtra("date", dateStr) putExtra("timetableDate", dateStr)
} }
activity.sendBroadcast(intent) activity.sendBroadcast(intent)
} }
@ -132,29 +132,29 @@ class LessonDetailsDialog(
if (lesson.type < Lesson.TYPE_SHIFTED_SOURCE && lesson.oldSubjectId != null && lesson.subjectId != lesson.oldSubjectId) { if (lesson.type < Lesson.TYPE_SHIFTED_SOURCE && lesson.oldSubjectId != null && lesson.subjectId != lesson.oldSubjectId) {
b.oldSubjectName = lesson.oldSubjectName b.oldSubjectName = lesson.oldSubjectName
} }
if (lesson.type != Lesson.TYPE_CANCELLED && lesson.subjectId != null) { if (lesson.type != Lesson.TYPE_CANCELLED && lesson.displaySubjectId != null) {
b.subjectName = lesson.subjectName b.subjectName = lesson.subjectName
} }
if (lesson.type < Lesson.TYPE_SHIFTED_SOURCE && lesson.oldTeacherId != null && lesson.teacherId != lesson.oldTeacherId) { if (lesson.type < Lesson.TYPE_SHIFTED_SOURCE && lesson.oldTeacherId != null && lesson.teacherId != lesson.oldTeacherId) {
b.oldTeacherName = lesson.oldTeacherName b.oldTeacherName = lesson.oldTeacherName
} }
if (lesson.type != Lesson.TYPE_CANCELLED && lesson.teacherId != null) { if (lesson.type != Lesson.TYPE_CANCELLED && lesson.displayTeacherId != null) {
b.teacherName = lesson.teacherName b.teacherName = lesson.teacherName
} }
if (lesson.oldClassroom != null && lesson.classroom != lesson.oldClassroom) { if (lesson.oldClassroom != null && lesson.classroom != lesson.oldClassroom) {
b.oldClassroom = lesson.oldClassroom b.oldClassroom = lesson.oldClassroom
} }
if (lesson.type != Lesson.TYPE_CANCELLED && lesson.classroom != null) { if (lesson.type != Lesson.TYPE_CANCELLED && lesson.displayClassroom != null) {
b.classroom = lesson.classroom b.classroom = lesson.classroom
} }
if (lesson.type < Lesson.TYPE_SHIFTED_SOURCE && lesson.oldTeamId != null && lesson.teamId != lesson.oldTeamId) { if (lesson.type < Lesson.TYPE_SHIFTED_SOURCE && lesson.oldTeamId != null && lesson.teamId != lesson.oldTeamId) {
b.oldTeamName = lesson.oldTeamName b.oldTeamName = lesson.oldTeamName
} }
if (lesson.type != Lesson.TYPE_CANCELLED && lesson.teamId != null) { if (lesson.type != Lesson.TYPE_CANCELLED && lesson.displayTeamId != null) {
b.teamName = lesson.teamName b.teamName = lesson.teamName
} }
} }
} }

View File

@ -6,15 +6,16 @@ import android.graphics.drawable.Drawable;
import android.os.AsyncTask; import android.os.AsyncTask;
import android.os.Bundle; import android.os.Bundle;
import android.os.Handler; import android.os.Handler;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.databinding.DataBindingUtil;
import androidx.fragment.app.Fragment;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.Toast; import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.databinding.DataBindingUtil;
import androidx.fragment.app.Fragment;
import com.afollestad.materialdialogs.MaterialDialog; import com.afollestad.materialdialogs.MaterialDialog;
import com.applandeo.materialcalendarview.CalendarView; import com.applandeo.materialcalendarview.CalendarView;
import com.applandeo.materialcalendarview.EventDay; import com.applandeo.materialcalendarview.EventDay;
@ -27,6 +28,7 @@ import com.mikepenz.iconics.IconicsColor;
import com.mikepenz.iconics.IconicsDrawable; import com.mikepenz.iconics.IconicsDrawable;
import com.mikepenz.iconics.IconicsSize; import com.mikepenz.iconics.IconicsSize;
import com.mikepenz.iconics.typeface.library.community.material.CommunityMaterial; import com.mikepenz.iconics.typeface.library.community.material.CommunityMaterial;
import com.mikepenz.iconics.typeface.library.szkolny.font.SzkolnyFont;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Calendar; import java.util.Calendar;
@ -34,13 +36,13 @@ import java.util.List;
import java.util.Locale; import java.util.Locale;
import pl.szczodrzynski.edziennik.App; import pl.szczodrzynski.edziennik.App;
import pl.szczodrzynski.edziennik.R;
import pl.szczodrzynski.edziennik.MainActivity; import pl.szczodrzynski.edziennik.MainActivity;
import pl.szczodrzynski.edziennik.R;
import pl.szczodrzynski.edziennik.data.db.modules.events.EventFull;
import pl.szczodrzynski.edziennik.data.db.modules.lessons.LessonFull;
import pl.szczodrzynski.edziennik.data.db.modules.teachers.TeacherAbsenceFull; import pl.szczodrzynski.edziennik.data.db.modules.teachers.TeacherAbsenceFull;
import pl.szczodrzynski.edziennik.databinding.FragmentAgendaCalendarBinding; import pl.szczodrzynski.edziennik.databinding.FragmentAgendaCalendarBinding;
import pl.szczodrzynski.edziennik.databinding.FragmentAgendaDefaultBinding; import pl.szczodrzynski.edziennik.databinding.FragmentAgendaDefaultBinding;
import pl.szczodrzynski.edziennik.data.db.modules.events.EventFull;
import pl.szczodrzynski.edziennik.data.db.modules.lessons.LessonFull;
import pl.szczodrzynski.edziennik.ui.dialogs.event.EventListDialog; import pl.szczodrzynski.edziennik.ui.dialogs.event.EventListDialog;
import pl.szczodrzynski.edziennik.ui.dialogs.event.EventManualDialog; import pl.szczodrzynski.edziennik.ui.dialogs.event.EventManualDialog;
import pl.szczodrzynski.edziennik.ui.dialogs.lessonchange.LessonChangeDialog; import pl.szczodrzynski.edziennik.ui.dialogs.lessonchange.LessonChangeDialog;
@ -51,11 +53,11 @@ import pl.szczodrzynski.edziennik.ui.modules.agenda.lessonchange.LessonChangeEve
import pl.szczodrzynski.edziennik.ui.modules.agenda.teacherabsence.TeacherAbsenceCounter; import pl.szczodrzynski.edziennik.ui.modules.agenda.teacherabsence.TeacherAbsenceCounter;
import pl.szczodrzynski.edziennik.ui.modules.agenda.teacherabsence.TeacherAbsenceEvent; import pl.szczodrzynski.edziennik.ui.modules.agenda.teacherabsence.TeacherAbsenceEvent;
import pl.szczodrzynski.edziennik.ui.modules.agenda.teacherabsence.TeacherAbsenceEventRenderer; import pl.szczodrzynski.edziennik.ui.modules.agenda.teacherabsence.TeacherAbsenceEventRenderer;
import pl.szczodrzynski.edziennik.utils.models.Date;
import pl.szczodrzynski.edziennik.utils.models.Time;
import pl.szczodrzynski.edziennik.utils.Colors; import pl.szczodrzynski.edziennik.utils.Colors;
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.models.Date;
import pl.szczodrzynski.edziennik.utils.models.Time;
import pl.szczodrzynski.navlib.bottomsheet.items.BottomSheetPrimaryItem; import pl.szczodrzynski.navlib.bottomsheet.items.BottomSheetPrimaryItem;
import pl.szczodrzynski.navlib.bottomsheet.items.BottomSheetSeparatorItem; import pl.szczodrzynski.navlib.bottomsheet.items.BottomSheetSeparatorItem;
@ -102,7 +104,7 @@ public class AgendaFragment extends Fragment {
new BottomSheetPrimaryItem(true) new BottomSheetPrimaryItem(true)
.withTitle(R.string.menu_add_event) .withTitle(R.string.menu_add_event)
.withDescription(R.string.menu_add_event_desc) .withDescription(R.string.menu_add_event_desc)
.withIcon(CommunityMaterial.Icon.cmd_calendar_plus) .withIcon(SzkolnyFont.Icon.szf_calendar_plus_outline)
.withOnClickListener(v3 -> { .withOnClickListener(v3 -> {
activity.getBottomSheet().close(); activity.getBottomSheet().close();
new MaterialDialog.Builder(activity) new MaterialDialog.Builder(activity)
@ -122,7 +124,7 @@ public class AgendaFragment extends Fragment {
}), }),
new BottomSheetPrimaryItem(true) new BottomSheetPrimaryItem(true)
.withTitle(R.string.menu_agenda_change_view) .withTitle(R.string.menu_agenda_change_view)
.withIcon(viewType == AGENDA_DEFAULT ? CommunityMaterial.Icon.cmd_calendar : CommunityMaterial.Icon2.cmd_view_list) .withIcon(viewType == AGENDA_DEFAULT ? CommunityMaterial.Icon.cmd_calendar_outline : CommunityMaterial.Icon.cmd_format_list_bulleted_square)
.withOnClickListener(v3 -> { .withOnClickListener(v3 -> {
activity.getBottomSheet().close(); activity.getBottomSheet().close();
viewType = viewType == AGENDA_DEFAULT ? AGENDA_CALENDAR : AGENDA_DEFAULT; viewType = viewType == AGENDA_DEFAULT ? AGENDA_CALENDAR : AGENDA_DEFAULT;
@ -133,7 +135,7 @@ public class AgendaFragment extends Fragment {
new BottomSheetSeparatorItem(true), new BottomSheetSeparatorItem(true),
new BottomSheetPrimaryItem(true) new BottomSheetPrimaryItem(true)
.withTitle(R.string.menu_mark_as_read) .withTitle(R.string.menu_mark_as_read)
.withIcon(CommunityMaterial.Icon.cmd_eye_check) .withIcon(CommunityMaterial.Icon.cmd_eye_check_outline)
.withOnClickListener(v3 -> { .withOnClickListener(v3 -> {
activity.getBottomSheet().close(); activity.getBottomSheet().close();
AsyncTask.execute(() -> app.db.metadataDao().setAllSeen(App.profileId, TYPE_EVENT, true)); AsyncTask.execute(() -> app.db.metadataDao().setAllSeen(App.profileId, TYPE_EVENT, true));

View File

@ -58,7 +58,7 @@ public class AnnouncementsFragment extends Fragment {
activity.getBottomSheet().prependItems( activity.getBottomSheet().prependItems(
new BottomSheetPrimaryItem(true) new BottomSheetPrimaryItem(true)
.withTitle(R.string.menu_mark_as_read) .withTitle(R.string.menu_mark_as_read)
.withIcon(CommunityMaterial.Icon.cmd_eye_check) .withIcon(CommunityMaterial.Icon.cmd_eye_check_outline)
.withOnClickListener(v3 -> { .withOnClickListener(v3 -> {
activity.getBottomSheet().close(); activity.getBottomSheet().close();
AsyncTask.execute(() -> app.db.metadataDao().setAllSeen(App.profileId, TYPE_ANNOUNCEMENT, true)); AsyncTask.execute(() -> app.db.metadataDao().setAllSeen(App.profileId, TYPE_ANNOUNCEMENT, true));

View File

@ -95,7 +95,7 @@ public class AttendanceFragment extends Fragment {
activity.getBottomSheet().prependItems( activity.getBottomSheet().prependItems(
new BottomSheetPrimaryItem(true) new BottomSheetPrimaryItem(true)
.withTitle(R.string.menu_mark_as_read) .withTitle(R.string.menu_mark_as_read)
.withIcon(CommunityMaterial.Icon.cmd_eye_check) .withIcon(CommunityMaterial.Icon.cmd_eye_check_outline)
.withOnClickListener(v3 -> { .withOnClickListener(v3 -> {
activity.getBottomSheet().close(); activity.getBottomSheet().close();
AsyncTask.execute(() -> app.db.metadataDao().setAllSeen(App.profileId, TYPE_ATTENDANCE, true)); AsyncTask.execute(() -> app.db.metadataDao().setAllSeen(App.profileId, TYPE_ATTENDANCE, true));
@ -133,7 +133,7 @@ public class AttendanceFragment extends Fragment {
CafeBar.builder(activity) CafeBar.builder(activity)
.to(activity.getNavView().getCoordinator()) .to(activity.getNavView().getCoordinator())
.content(R.string.sync_old_data_info) .content(R.string.sync_old_data_info)
.icon(new IconicsDrawable(activity).icon(CommunityMaterial.Icon2.cmd_sync).size(IconicsSize.dp(20)).color(IconicsColor.colorInt(Themes.INSTANCE.getPrimaryTextColor(activity)))) .icon(new IconicsDrawable(activity).icon(CommunityMaterial.Icon.cmd_download_outline).size(IconicsSize.dp(20)).color(IconicsColor.colorInt(Themes.INSTANCE.getPrimaryTextColor(activity))))
.positiveText(R.string.refresh) .positiveText(R.string.refresh)
.positiveColor(0xff4caf50) .positiveColor(0xff4caf50)
.negativeText(R.string.ok) .negativeText(R.string.ok)

View File

@ -69,7 +69,7 @@ public class BehaviourFragment extends Fragment {
activity.getBottomSheet().prependItems( activity.getBottomSheet().prependItems(
new BottomSheetPrimaryItem(true) new BottomSheetPrimaryItem(true)
.withTitle(R.string.menu_mark_as_read) .withTitle(R.string.menu_mark_as_read)
.withIcon(CommunityMaterial.Icon.cmd_eye_check) .withIcon(CommunityMaterial.Icon.cmd_eye_check_outline)
.withOnClickListener(v3 -> { .withOnClickListener(v3 -> {
activity.getBottomSheet().close(); activity.getBottomSheet().close();
AsyncTask.execute(() -> app.db.metadataDao().setAllSeen(App.profileId, TYPE_NOTICE, true)); AsyncTask.execute(() -> app.db.metadataDao().setAllSeen(App.profileId, TYPE_NOTICE, true));

View File

@ -9,21 +9,20 @@ import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.ImageView import android.widget.ImageView
import android.widget.TextView import android.widget.TextView
import com.mikepenz.iconics.IconicsDrawable
import androidx.cardview.widget.CardView import androidx.cardview.widget.CardView
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import com.mikepenz.iconics.IconicsDrawable
import com.mikepenz.iconics.typeface.library.community.material.CommunityMaterial import com.mikepenz.iconics.typeface.library.community.material.CommunityMaterial
import com.mikepenz.iconics.typeface.library.szkolny.font.SzkolnyFont
import com.mikepenz.iconics.utils.colorRes import com.mikepenz.iconics.utils.colorRes
import com.mikepenz.iconics.utils.sizeDp import com.mikepenz.iconics.utils.sizeDp
import pl.szczodrzynski.edziennik.App import pl.szczodrzynski.edziennik.App
import pl.szczodrzynski.edziennik.R import pl.szczodrzynski.edziennik.R
import pl.szczodrzynski.edziennik.data.db.modules.login.LoginStore.LOGIN_TYPE_MOBIDZIENNIK
import pl.szczodrzynski.edziennik.data.db.modules.notices.Notice import pl.szczodrzynski.edziennik.data.db.modules.notices.Notice
import pl.szczodrzynski.edziennik.data.db.modules.notices.NoticeFull import pl.szczodrzynski.edziennik.data.db.modules.notices.NoticeFull
import pl.szczodrzynski.edziennik.utils.models.Date
import pl.szczodrzynski.edziennik.data.db.modules.login.LoginStore.LOGIN_TYPE_MOBIDZIENNIK
import pl.szczodrzynski.edziennik.utils.Utils.bs import pl.szczodrzynski.edziennik.utils.Utils.bs
import pl.szczodrzynski.edziennik.utils.models.Date
class NoticesAdapter//getting the context and product list with constructor class NoticesAdapter//getting the context and product list with constructor
(private val context: Context, var noticeList: List<NoticeFull>) : RecyclerView.Adapter<NoticesAdapter.ViewHolder>() { (private val context: Context, var noticeList: List<NoticeFull>) : RecyclerView.Adapter<NoticesAdapter.ViewHolder>() {
@ -50,15 +49,15 @@ class NoticesAdapter//getting the context and product list with constructor
holder.noticesItemAddedDate.text = Date.fromMillis(notice.addedDate).formattedString holder.noticesItemAddedDate.text = Date.fromMillis(notice.addedDate).formattedString
if (notice.type == Notice.TYPE_POSITIVE) { if (notice.type == Notice.TYPE_POSITIVE) {
holder.noticesItemType.setImageDrawable(IconicsDrawable(context, CommunityMaterial.Icon2.cmd_plus_circle) holder.noticesItemType.setImageDrawable(IconicsDrawable(context, CommunityMaterial.Icon2.cmd_plus_circle_outline)
.colorRes(R.color.md_green_600) .colorRes(R.color.md_green_600)
.sizeDp(36)) .sizeDp(36))
} else if (notice.type == Notice.TYPE_NEGATIVE) { } else if (notice.type == Notice.TYPE_NEGATIVE) {
holder.noticesItemType.setImageDrawable(IconicsDrawable(context, CommunityMaterial.Icon.cmd_alert_decagram) holder.noticesItemType.setImageDrawable(IconicsDrawable(context, CommunityMaterial.Icon.cmd_alert_decagram_outline)
.colorRes(R.color.md_red_600) .colorRes(R.color.md_red_600)
.sizeDp(36)) .sizeDp(36))
} else { } else {
holder.noticesItemType.setImageDrawable(IconicsDrawable(context, CommunityMaterial.Icon2.cmd_message_outline) holder.noticesItemType.setImageDrawable(IconicsDrawable(context, SzkolnyFont.Icon.szf_message_processing_outline)
.colorRes(R.color.md_blue_500) .colorRes(R.color.md_blue_500)
.sizeDp(36)) .sizeDp(36))
} }

View File

@ -132,7 +132,7 @@ public class GradesFragment extends Fragment {
}), }),
new BottomSheetPrimaryItem(true) new BottomSheetPrimaryItem(true)
.withTitle(R.string.menu_grades_color_mode) .withTitle(R.string.menu_grades_color_mode)
.withIcon(CommunityMaterial.Icon2.cmd_palette) .withIcon(CommunityMaterial.Icon2.cmd_palette_outline)
.withOnClickListener(v3 -> { .withOnClickListener(v3 -> {
activity.getBottomSheet().close(); activity.getBottomSheet().close();
new MaterialDialog.Builder(activity) new MaterialDialog.Builder(activity)
@ -195,7 +195,7 @@ public class GradesFragment extends Fragment {
new BottomSheetSeparatorItem(true), new BottomSheetSeparatorItem(true),
new BottomSheetPrimaryItem(true) new BottomSheetPrimaryItem(true)
.withTitle(R.string.menu_mark_as_read) .withTitle(R.string.menu_mark_as_read)
.withIcon(CommunityMaterial.Icon.cmd_eye_check) .withIcon(CommunityMaterial.Icon.cmd_eye_check_outline)
.withOnClickListener(v3 -> { .withOnClickListener(v3 -> {
activity.getBottomSheet().close(); activity.getBottomSheet().close();
AsyncTask.execute(() -> app.db.metadataDao().setAllSeen(App.profileId, TYPE_GRADE, true)); AsyncTask.execute(() -> app.db.metadataDao().setAllSeen(App.profileId, TYPE_GRADE, true));

View File

@ -0,0 +1,49 @@
/*
* Copyright (c) Kuba Szczodrzyński 2019-11-23.
*/
package pl.szczodrzynski.edziennik.ui.modules.home
import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.RecyclerView
import com.google.android.material.card.MaterialCardView
import pl.szczodrzynski.edziennik.ui.modules.home.HomeFragmentV2.Companion.swapCards
import pl.szczodrzynski.edziennik.utils.SwipeRefreshLayoutNoIndicator
class CardItemTouchHelperCallback(private val cardAdapter: HomeCardAdapter, private val refreshLayout: SwipeRefreshLayoutNoIndicator?) : ItemTouchHelper.Callback() {
companion object {
private const val TAG = "CardItemTouchHelperCallback"
private const val DRAG_FLAGS = ItemTouchHelper.UP or ItemTouchHelper.DOWN
private const val SWIPE_FLAGS = 0
}
private var dragCardView: MaterialCardView? = null
override fun getMovementFlags(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder): Int {
return makeMovementFlags(DRAG_FLAGS, SWIPE_FLAGS)
}
override fun onMove(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder, target: RecyclerView.ViewHolder): Boolean {
val fromPosition = viewHolder.adapterPosition
val toPosition = target.adapterPosition
swapCards(fromPosition, toPosition, cardAdapter)
return true
}
override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) = Unit
override fun onSelectedChanged(viewHolder: RecyclerView.ViewHolder?, actionState: Int) {
super.onSelectedChanged(viewHolder, actionState)
if (actionState == ItemTouchHelper.ACTION_STATE_DRAG && viewHolder != null) {
dragCardView = viewHolder.itemView as MaterialCardView
dragCardView?.isDragged = true
refreshLayout?.isEnabled = false
} else if (actionState == ItemTouchHelper.ACTION_STATE_IDLE && dragCardView != null) {
refreshLayout?.isEnabled = true
dragCardView?.isDragged = false
dragCardView = null
}
}
}

View File

@ -1,11 +1,12 @@
package pl.szczodrzynski.edziennik.ui.modules.home; package pl.szczodrzynski.edziennik.ui.modules.home;
import androidx.databinding.DataBindingUtil;
import android.os.AsyncTask; import android.os.AsyncTask;
import android.os.Bundle; import android.os.Bundle;
import android.util.Log;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity; import androidx.appcompat.app.AppCompatActivity;
import android.util.Log; import androidx.databinding.DataBindingUtil;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
@ -14,8 +15,8 @@ import java.util.TimerTask;
import pl.szczodrzynski.edziennik.App; import pl.szczodrzynski.edziennik.App;
import pl.szczodrzynski.edziennik.R; import pl.szczodrzynski.edziennik.R;
import pl.szczodrzynski.edziennik.databinding.ActivityCounterBinding;
import pl.szczodrzynski.edziennik.data.db.modules.lessons.LessonFull; import pl.szczodrzynski.edziennik.data.db.modules.lessons.LessonFull;
import pl.szczodrzynski.edziennik.databinding.ActivityCounterBinding;
import pl.szczodrzynski.edziennik.utils.models.Date; import pl.szczodrzynski.edziennik.utils.models.Date;
import pl.szczodrzynski.edziennik.utils.models.Time; import pl.szczodrzynski.edziennik.utils.models.Time;
@ -88,7 +89,7 @@ public class CounterActivity extends AppCompatActivity {
private void findLessons(Time syncedNow) { private void findLessons(Time syncedNow) {
AsyncTask.execute(() -> { AsyncTask.execute(() -> {
Date today = Date.getToday(); Date today = Date.getToday();
lessons = app.db.lessonDao().getAllNearestNow(App.profileId, today.clone().stepForward(0, 0, -today.getWeekDay()), today, syncedNow); lessons = app.db.lessonDao().getAllNearestNow(App.profileId, today.getWeekStart(), today, syncedNow);
if (lessons != null && lessons.size() != 0) { if (lessons != null && lessons.size() != 0) {
Date displayingDate = lessons.get(0).lessonDate; Date displayingDate = lessons.get(0).lessonDate;

View File

@ -0,0 +1,10 @@
/*
* Copyright (c) Kuba Szczodrzyński 2019-11-23.
*/
package pl.szczodrzynski.edziennik.ui.modules.home
interface HomeCard {
fun bind(position: Int, holder: HomeCardAdapter.ViewHolder)
fun unbind(position: Int, holder: HomeCardAdapter.ViewHolder)
}

View File

@ -0,0 +1,51 @@
/*
* Copyright (c) Kuba Szczodrzyński 2019-11-23.
*/
package pl.szczodrzynski.edziennik.ui.modules.home
import android.annotation.SuppressLint
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.RecyclerView
import com.google.android.material.card.MaterialCardView
import pl.szczodrzynski.edziennik.R
class HomeCardAdapter(var items: MutableList<HomeCard>) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
companion object {
private const val TAG = "HomeCardAdapter"
}
var itemTouchHelper: ItemTouchHelper? = null
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
(holder as ViewHolder).bind(itemTouchHelper)
items[position].bind(position, holder)
}
override fun onViewRecycled(holder: RecyclerView.ViewHolder) {
items.getOrNull(holder.adapterPosition)?.unbind(holder.adapterPosition, holder as ViewHolder)
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
return ViewHolder(
LayoutInflater.from(parent.context).inflate(R.layout.card_home, parent, false) as MaterialCardView
)
}
override fun getItemCount(): Int = items.size
class ViewHolder(val root: MaterialCardView) : RecyclerView.ViewHolder(root) {
@SuppressLint("ClickableViewAccessibility")
fun bind(itemTouchHelper: ItemTouchHelper?) {
/*root.setOnTouchListener { _: View?, event: MotionEvent ->
if (event.action == MotionEvent.ACTION_DOWN) {
itemTouchHelper?.startDrag(this)
return@setOnTouchListener true
}
false
}*/
}
}
}

View File

@ -0,0 +1,60 @@
/*
* Copyright (c) Kuba Szczodrzyński 2019-11-23.
*/
package pl.szczodrzynski.edziennik.ui.modules.home
import android.widget.TextView
import androidx.core.view.plusAssign
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
import pl.szczodrzynski.edziennik.App
import pl.szczodrzynski.edziennik.MainActivity
import pl.szczodrzynski.edziennik.startCoroutineTimer
import kotlin.coroutines.CoroutineContext
class HomeDummyCard(val id: Int) : HomeCard, CoroutineScope {
companion object {
private const val TAG = "HomeDummyCard"
}
private lateinit var app: App
private lateinit var activity: MainActivity
private var job: Job = Job()
override val coroutineContext: CoroutineContext
get() = job + Dispatchers.Main
var timer: Job? = null
var time = 0
override fun bind(position: Int, holder: HomeCardAdapter.ViewHolder) { launch {
holder.root.removeAllViews()
//holder.setIsRecyclable(false)
val text = TextView(holder.root.context).apply {
text = "This is a card #$id"
}
holder.root += text
timer = startCoroutineTimer(repeatMillis = 1000) {
time++
text.text = "Coroutine timer at #$id! $time seconds"
}
/*val button = MaterialButton(holder.root.context).apply {
setText("Cancel")
onClick {
timer.cancel()
}
}
holder.root += button*/
}}
override fun unbind(position: Int, holder: HomeCardAdapter.ViewHolder) {
timer?.cancel()
timer = null
}
}

View File

@ -37,6 +37,7 @@ import com.mikepenz.iconics.IconicsColor;
import com.mikepenz.iconics.IconicsDrawable; import com.mikepenz.iconics.IconicsDrawable;
import com.mikepenz.iconics.IconicsSize; import com.mikepenz.iconics.IconicsSize;
import com.mikepenz.iconics.typeface.library.community.material.CommunityMaterial; import com.mikepenz.iconics.typeface.library.community.material.CommunityMaterial;
import com.mikepenz.iconics.typeface.library.szkolny.font.SzkolnyFont;
import java.io.File; import java.io.File;
import java.util.ArrayList; import java.util.ArrayList;
@ -268,7 +269,7 @@ public class HomeFragment extends Fragment {
activity.getBottomSheet().prependItems( activity.getBottomSheet().prependItems(
new BottomSheetPrimaryItem(true) new BottomSheetPrimaryItem(true)
.withTitle(R.string.menu_set_student_number) .withTitle(R.string.menu_set_student_number)
.withIcon(CommunityMaterial.Icon.cmd_counter) .withIcon(SzkolnyFont.Icon.szf_clipboard_list_outline)
.withOnClickListener(v3 -> { .withOnClickListener(v3 -> {
activity.getBottomSheet().close(); activity.getBottomSheet().close();
setNumberDialog(); setNumberDialog();
@ -276,7 +277,7 @@ public class HomeFragment extends Fragment {
new BottomSheetSeparatorItem(true), new BottomSheetSeparatorItem(true),
new BottomSheetPrimaryItem(true) new BottomSheetPrimaryItem(true)
.withTitle(R.string.menu_mark_everything_as_read) .withTitle(R.string.menu_mark_everything_as_read)
.withIcon(CommunityMaterial.Icon.cmd_eye_check) .withIcon(CommunityMaterial.Icon.cmd_eye_check_outline)
.withOnClickListener(v3 -> { .withOnClickListener(v3 -> {
activity.getBottomSheet().close(); activity.getBottomSheet().close();
AsyncTask.execute(() -> app.db.metadataDao().setAllSeen(App.profileId, true)); AsyncTask.execute(() -> app.db.metadataDao().setAllSeen(App.profileId, true));

View File

@ -0,0 +1,141 @@
/*
* Copyright (c) Kuba Szczodrzyński 2019-11-23.
*/
package pl.szczodrzynski.edziennik.ui.modules.home
import android.os.AsyncTask
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.View.OnClickListener
import android.view.ViewGroup
import android.widget.Toast
import androidx.core.view.AccessibilityDelegateCompat
import androidx.core.view.accessibility.AccessibilityNodeInfoCompat
import androidx.core.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat
import androidx.fragment.app.Fragment
import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerViewAccessibilityDelegate
import com.mikepenz.iconics.typeface.library.community.material.CommunityMaterial.Icon
import com.mikepenz.iconics.typeface.library.szkolny.font.SzkolnyFont
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import pl.szczodrzynski.edziennik.App
import pl.szczodrzynski.edziennik.MainActivity
import pl.szczodrzynski.edziennik.R
import pl.szczodrzynski.edziennik.databinding.FragmentHomeV2Binding
import pl.szczodrzynski.edziennik.ui.dialogs.home.StudentNumberDialog
import pl.szczodrzynski.edziennik.ui.modules.home.cards.HomeLuckyNumberCard
import pl.szczodrzynski.edziennik.ui.modules.home.cards.HomeTimetableCard
import pl.szczodrzynski.edziennik.utils.Themes
import pl.szczodrzynski.navlib.bottomsheet.items.BottomSheetPrimaryItem
import pl.szczodrzynski.navlib.bottomsheet.items.BottomSheetSeparatorItem
import kotlin.coroutines.CoroutineContext
class HomeFragmentV2 : Fragment(), CoroutineScope {
companion object {
private const val TAG = "HomeFragment"
fun swapCards(fromPosition: Int, toPosition: Int, cardAdapter: HomeCardAdapter) {
val fromCard = cardAdapter.items[fromPosition]
cardAdapter.items[fromPosition] = cardAdapter.items[toPosition]
cardAdapter.items[toPosition] = fromCard
cardAdapter.notifyItemMoved(fromPosition, toPosition)
}
}
private lateinit var app: App
private lateinit var activity: MainActivity
private lateinit var b: FragmentHomeV2Binding
private lateinit var job: Job
override val coroutineContext: CoroutineContext
get() = job + Dispatchers.Main
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
activity = (getActivity() as MainActivity?) ?: return null
context ?: return null
app = activity.application as App
context!!.theme.applyStyle(Themes.appTheme, true)
b = FragmentHomeV2Binding.inflate(inflater)
b.refreshLayout.setParent(activity.swipeRefreshLayout)
job = Job()
return b.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
// TODO check if app, activity, b can be null
if (app.profile == null || !isAdded)
return
activity.bottomSheet.prependItems(
BottomSheetPrimaryItem(true)
.withTitle(R.string.menu_set_student_number)
.withIcon(SzkolnyFont.Icon.szf_clipboard_list_outline)
.withOnClickListener(OnClickListener {
activity.bottomSheet.close()
StudentNumberDialog(activity, app.profile) {
app.profileSaveAsync()
}
}),
BottomSheetSeparatorItem(true),
BottomSheetPrimaryItem(true)
.withTitle(R.string.menu_mark_everything_as_read)
.withIcon(Icon.cmd_eye_check_outline)
.withOnClickListener(OnClickListener {
activity.bottomSheet.close()
AsyncTask.execute { app.db.metadataDao().setAllSeen(App.profileId, true) }
Toast.makeText(activity, R.string.main_menu_mark_as_read_success, Toast.LENGTH_SHORT).show()
})
)
val items = mutableListOf<HomeCard>(
HomeLuckyNumberCard(0, app, activity, this, app.profile),
HomeTimetableCard(1, app, activity, this, app.profile)
)
val adapter = HomeCardAdapter(items)
val itemTouchHelper = ItemTouchHelper(CardItemTouchHelperCallback(adapter, b.refreshLayout))
adapter.itemTouchHelper = itemTouchHelper
b.list.layoutManager = LinearLayoutManager(activity)
b.list.adapter = adapter
b.list.setAccessibilityDelegateCompat(object : RecyclerViewAccessibilityDelegate(b.list) {
override fun getItemDelegate(): AccessibilityDelegateCompat {
return object : ItemDelegate(this) {
override fun onInitializeAccessibilityNodeInfo(host: View, info: AccessibilityNodeInfoCompat) {
super.onInitializeAccessibilityNodeInfo(host, info)
val position: Int = b.list.getChildLayoutPosition(host)
if (position != 0) {
info.addAction(AccessibilityActionCompat(
R.id.move_card_up_action,
host.resources.getString(R.string.card_action_move_up)
))
}
if (position != adapter.itemCount - 1) {
info.addAction(AccessibilityActionCompat(
R.id.move_card_down_action,
host.resources.getString(R.string.card_action_move_down)
))
}
}
override fun performAccessibilityAction(host: View, action: Int, args: Bundle): Boolean {
val fromPosition: Int = b.list.getChildLayoutPosition(host)
if (action == R.id.move_card_down_action) {
swapCards(fromPosition, fromPosition + 1, adapter)
return true
} else if (action == R.id.move_card_up_action) {
swapCards(fromPosition, fromPosition - 1, adapter)
return true
}
return super.performAccessibilityAction(host, action, args)
}
}
}
})
itemTouchHelper.attachToRecyclerView(b.list)
}
}

View File

@ -0,0 +1,178 @@
/*
* Copyright (c) Kacper Ziubryniewicz 2019-11-22
*/
package pl.szczodrzynski.edziennik.ui.modules.home
import android.content.Intent
import android.os.AsyncTask
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.view.ViewGroup.LayoutParams.MATCH_PARENT
import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
import androidx.databinding.DataBindingUtil
import com.afollestad.materialdialogs.DialogAction
import com.afollestad.materialdialogs.MaterialDialog
import com.mikepenz.iconics.typeface.library.community.material.CommunityMaterial
import pl.szczodrzynski.edziennik.App
import pl.szczodrzynski.edziennik.MainActivity
import pl.szczodrzynski.edziennik.R
import pl.szczodrzynski.edziennik.data.db.modules.events.Event
import pl.szczodrzynski.edziennik.data.db.modules.timetable.Lesson
import pl.szczodrzynski.edziennik.databinding.CardTimetableBinding
import pl.szczodrzynski.edziennik.ui.modules.home.HomeFragment.updateInterval
import pl.szczodrzynski.edziennik.utils.models.Date
import pl.szczodrzynski.edziennik.utils.models.Time
import java.util.*
class HomeTimetableCard(
private val app: App,
private val activity: MainActivity,
private val homeFragment: HomeFragment,
private val layoutInflater: LayoutInflater,
private val insertPoint: ViewGroup
) {
companion object {
private const val TAG = "HomeTimetableCard"
const val TIME_TILL = 0
const val TIME_LEFT = 1
}
private lateinit var timetableTimer: Timer
private lateinit var b: CardTimetableBinding
private var bellSyncTime: Time? = null
private var counterType = TIME_TILL
private val counterTarget = Time(0, 0, 0)
private val lessons = mutableListOf<Lesson>()
private val events = mutableListOf<Event>()
fun run() {
timetableTimer = Timer()
b = DataBindingUtil.inflate(layoutInflater, R.layout.card_timetable, null, false)
update()
insertPoint.addView(b.root, ViewGroup.LayoutParams(MATCH_PARENT, WRAP_CONTENT))
b.cardTimetableFullscreenCounter.setOnClickListener {
activity.startActivity(Intent(activity, CounterActivity::class.java))
}
b.cardTimetableBellSync.setOnClickListener {
if (bellSyncTime == null) {
MaterialDialog.Builder(activity)
.title(R.string.bell_sync_title)
.content(R.string.bell_sync_cannot_now)
.positiveText(R.string.ok)
.show()
} else {
MaterialDialog.Builder(activity)
.title(R.string.bell_sync_title)
.content(app.getString(R.string.bell_sync_howto, bellSyncTime!!.stringHM).toString() +
when {
app.appConfig.bellSyncDiff != null -> app.getString(R.string.bell_sync_current_dialog,
(if (app.appConfig.bellSyncMultiplier == -1) "-" else "+") + app.appConfig.bellSyncDiff.stringHMS)
else -> ""
})
.positiveText(R.string.ok)
.negativeText(R.string.cancel)
.neutralText(R.string.reset)
.onPositive { _, _: DialogAction? ->
val bellDiff = Time.diff(Time.getNow(), bellSyncTime)
app.appConfig.bellSyncDiff = bellDiff
app.appConfig.bellSyncMultiplier = if (bellSyncTime!!.value > Time.getNow().value) -1 else 1
app.saveConfig("bellSyncDiff", "bellSyncMultiplier")
MaterialDialog.Builder(activity)
.title(R.string.bell_sync_title)
.content(app.getString(R.string.bell_sync_results, if (bellSyncTime!!.value > Time.getNow().value) "-" else "+", bellDiff.stringHMS))
.positiveText(R.string.ok)
.show()
}
.onNeutral { _, _ ->
MaterialDialog.Builder(activity)
.title(R.string.bell_sync_title)
.content(R.string.bell_sync_reset_confirm)
.positiveText(R.string.yes)
.negativeText(R.string.no)
.onPositive { _, _ ->
app.appConfig.bellSyncDiff = null
app.appConfig.bellSyncMultiplier = 0
app.saveConfig("bellSyncDiff", "bellSyncMultiplier")
}
.show()
}
.show()
}
}
HomeFragment.buttonAddDrawable(activity, b.cardTimetableButton, CommunityMaterial.Icon.cmd_arrow_right)
}
fun destroy() {
try {
timetableTimer.apply {
cancel()
purge()
}
} catch (e: Exception) {
e.printStackTrace()
}
}
private fun update() {
if (!homeFragment.isAdded) return
val now = Time.getNow()
val syncedNow: Time = when (app.appConfig.bellSyncDiff != null) {
true -> when {
app.appConfig.bellSyncMultiplier < 0 -> Time.sum(now, app.appConfig.bellSyncDiff)
app.appConfig.bellSyncMultiplier > 0 -> Time.diff(now, app.appConfig.bellSyncDiff)
else -> now
}
else -> now
}
if (lessons.size == 0 || syncedNow.value > counterTarget.value) {
findLessons(syncedNow)
} else {
scheduleUpdate(updateCounter(syncedNow))
}
}
private fun updateCounter(syncedNow: Time): Long {
val diff = Time.diff(counterTarget, syncedNow)
b.cardTimetableTimeLeft.text = when (counterType) {
TIME_TILL -> HomeFragment.timeTill(app, diff, app.appConfig.countInSeconds)
else -> HomeFragment.timeLeft(app, diff, app.appConfig.countInSeconds)
}
bellSyncTime = counterTarget.clone()
b.cardTimetableFullscreenCounter.visibility = View.VISIBLE
return updateInterval(app, diff)
}
private fun scheduleUpdate(newRefreshInterval: Long) {
timetableTimer.schedule(object : TimerTask() {
override fun run() {
activity.runOnUiThread { update() }
}
}, newRefreshInterval)
}
private fun findLessons(syncedNow: Time) {
AsyncTask.execute {
val today = Date.getToday()
val searchEnd = Date.getToday().stepForward(0, 0, -today.weekDay)
lessons.apply {
clear()
addAll(app.db.timetableDao().getBetweenDatesNow(today, searchEnd))
}
}
}
}

View File

@ -21,11 +21,11 @@ import java.util.Timer;
import java.util.TimerTask; import java.util.TimerTask;
import pl.szczodrzynski.edziennik.App; import pl.szczodrzynski.edziennik.App;
import pl.szczodrzynski.edziennik.R;
import pl.szczodrzynski.edziennik.MainActivity; import pl.szczodrzynski.edziennik.MainActivity;
import pl.szczodrzynski.edziennik.databinding.CardTimetableBinding; import pl.szczodrzynski.edziennik.R;
import pl.szczodrzynski.edziennik.data.db.modules.events.EventFull; import pl.szczodrzynski.edziennik.data.db.modules.events.EventFull;
import pl.szczodrzynski.edziennik.data.db.modules.lessons.LessonFull; import pl.szczodrzynski.edziennik.data.db.modules.lessons.LessonFull;
import pl.szczodrzynski.edziennik.databinding.CardTimetableBinding;
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;
import pl.szczodrzynski.edziennik.utils.models.Week; import pl.szczodrzynski.edziennik.utils.models.Week;
@ -35,8 +35,8 @@ import static pl.szczodrzynski.edziennik.data.db.modules.lessons.LessonChange.TY
import static pl.szczodrzynski.edziennik.ui.modules.home.HomeFragment.updateInterval; import static pl.szczodrzynski.edziennik.ui.modules.home.HomeFragment.updateInterval;
import static pl.szczodrzynski.edziennik.utils.Utils.bs; import static pl.szczodrzynski.edziennik.utils.Utils.bs;
public class HomeTimetableCard { public class HomeTimetableCardOld {
private static final String TAG = "HomeTimetableCard"; private static final String TAG = "HomeTimetableCardOld";
private App app; private App app;
private MainActivity a; private MainActivity a;
private HomeFragment f; private HomeFragment f;
@ -46,7 +46,7 @@ public class HomeTimetableCard {
private Timer timetableTimer; private Timer timetableTimer;
private Time bellSyncTime = null; private Time bellSyncTime = null;
public HomeTimetableCard(App app, MainActivity a, HomeFragment f, LayoutInflater layoutInflater, ViewGroup insertPoint) { public HomeTimetableCardOld(App app, MainActivity a, HomeFragment f, LayoutInflater layoutInflater, ViewGroup insertPoint) {
this.app = app; this.app = app;
this.a = a; this.a = a;
this.f = f; this.f = f;

View File

@ -0,0 +1,107 @@
/*
* Copyright (c) Kuba Szczodrzyński 2019-11-24.
*/
package pl.szczodrzynski.edziennik.ui.modules.home.cards
import android.view.LayoutInflater
import android.view.ViewGroup.LayoutParams.MATCH_PARENT
import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
import android.widget.FrameLayout
import androidx.core.view.plusAssign
import androidx.core.view.setMargins
import androidx.lifecycle.Observer
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
import pl.szczodrzynski.edziennik.*
import pl.szczodrzynski.edziennik.data.db.modules.profiles.Profile
import pl.szczodrzynski.edziennik.databinding.CardHomeLuckyNumberBinding
import pl.szczodrzynski.edziennik.ui.dialogs.home.StudentNumberDialog
import pl.szczodrzynski.edziennik.ui.modules.home.HomeCard
import pl.szczodrzynski.edziennik.ui.modules.home.HomeCardAdapter
import pl.szczodrzynski.edziennik.ui.modules.home.HomeFragmentV2
import pl.szczodrzynski.edziennik.utils.models.Date
import kotlin.coroutines.CoroutineContext
class HomeLuckyNumberCard(
val id: Int,
val app: App,
val activity: MainActivity,
val fragment: HomeFragmentV2,
val profile: Profile
) : HomeCard, CoroutineScope {
companion object {
private const val TAG = "HomeLuckyNumberCard"
}
private var job: Job = Job()
override val coroutineContext: CoroutineContext
get() = job + Dispatchers.Main
override fun bind(position: Int, holder: HomeCardAdapter.ViewHolder) { launch {
holder.root.removeAllViews()
val b = CardHomeLuckyNumberBinding.inflate(LayoutInflater.from(holder.root.context))
b.root.layoutParams = FrameLayout.LayoutParams(MATCH_PARENT, WRAP_CONTENT).apply {
setMargins(8.dp)
}
holder.root += b.root
val today = Date.getToday()
val todayValue = today.value
val subTextRes = if (profile.studentNumber == -1)
R.string.home_lucky_number_details_click_to_set
else
R.string.home_lucky_number_details
b.subText.setText(subTextRes, profile.name ?: "", profile.studentNumber)
app.db.luckyNumberDao().getNearestFuture(App.profileId, todayValue).observe(fragment, Observer { luckyNumber ->
val isYours = luckyNumber?.number == profile.studentNumber
val titleRes = when {
luckyNumber == null -> R.string.home_lucky_number_no_info
luckyNumber.number == -1 -> R.string.home_lucky_number_no_number
else -> when (isYours) {
true -> when (luckyNumber.date.value) {
todayValue -> R.string.home_lucky_number_yours_today
todayValue + 1 -> R.string.home_lucky_number_yours_tomorrow
else -> R.string.home_lucky_number_yours_later
}
false -> when (luckyNumber.date.value) {
todayValue -> R.string.home_lucky_number_today
todayValue + 1 -> R.string.home_lucky_number_tomorrow
else -> R.string.home_lucky_number_later
}
}
}
b.title.setText(
titleRes,
luckyNumber?.number ?: 0,
luckyNumber?.date?.formattedString ?: ""
)
val drawableRes = when {
luckyNumber == null || luckyNumber.number == -1 -> R.drawable.emoji_sad
isYours -> R.drawable.emoji_glasses
!isYours -> R.drawable.emoji_smiling
else -> R.drawable.emoji_no_face
}
b.image.setImageResource(drawableRes)
})
holder.root.onClick {
StudentNumberDialog(activity, profile, onDismissListener = {
app.profileSaveAsync(profile)
val newSubTextRes = if (profile.studentNumber == -1)
R.string.home_lucky_number_details_click_to_set
else
R.string.home_lucky_number_details
b.subText.setText(newSubTextRes, profile.name ?: "", profile.studentNumber)
})
}
}}
override fun unbind(position: Int, holder: HomeCardAdapter.ViewHolder) = Unit
}

View File

@ -0,0 +1,100 @@
/*
* Copyright (c) Kuba Szczodrzyński 2019-11-24.
*/
package pl.szczodrzynski.edziennik.ui.modules.home.cards
import android.view.LayoutInflater
import android.view.ViewGroup
import android.widget.FrameLayout
import androidx.core.view.plusAssign
import androidx.core.view.setMargins
import androidx.lifecycle.Observer
import kotlinx.coroutines.*
import pl.szczodrzynski.edziennik.*
import pl.szczodrzynski.edziennik.data.db.modules.events.Event
import pl.szczodrzynski.edziennik.data.db.modules.profiles.Profile
import pl.szczodrzynski.edziennik.data.db.modules.timetable.Lesson
import pl.szczodrzynski.edziennik.data.db.modules.timetable.LessonFull
import pl.szczodrzynski.edziennik.databinding.CardHomeTimetableBinding
import pl.szczodrzynski.edziennik.ui.modules.home.HomeCard
import pl.szczodrzynski.edziennik.ui.modules.home.HomeCardAdapter
import pl.szczodrzynski.edziennik.ui.modules.home.HomeFragmentV2
import pl.szczodrzynski.edziennik.utils.models.Date
import pl.szczodrzynski.edziennik.utils.models.Time
import kotlin.coroutines.CoroutineContext
class HomeTimetableCard(
val id: Int,
val app: App,
val activity: MainActivity,
val fragment: HomeFragmentV2,
val profile: Profile
) : HomeCard, CoroutineScope {
companion object {
private const val TAG = "HomeTimetableCard"
}
private var job: Job = Job()
override val coroutineContext: CoroutineContext
get() = job + Dispatchers.Main
private lateinit var b: CardHomeTimetableBinding
private val today = Date.getToday()
private val searchEnd = today.clone().stepForward(0, 0, 7)
private var allLessons = listOf<LessonFull>()
private var lessons = listOf<LessonFull>()
private var events = listOf<Event>()
override fun bind(position: Int, holder: HomeCardAdapter.ViewHolder) {
holder.root.removeAllViews()
b = CardHomeTimetableBinding.inflate(LayoutInflater.from(holder.root.context))
b.root.layoutParams = FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT).apply {
setMargins(8.dp)
}
holder.root += b.root
// get all lessons within the search bounds
app.db.timetableDao().getBetweenDates(today, searchEnd).observe(fragment, Observer {
allLessons = it
update()
})
}
private fun update() { launch {
val deferred = async(Dispatchers.Default) {
// get current bell-sync params
var bellSyncDiffMillis: Long = 0
if (app.appConfig.bellSyncDiff != null) {
bellSyncDiffMillis = (app.appConfig.bellSyncDiff.hour * 60 * 60 * 1000 + app.appConfig.bellSyncDiff.minute * 60 * 1000 + app.appConfig.bellSyncDiff.second * 1000).toLong()
bellSyncDiffMillis *= app.appConfig.bellSyncMultiplier.toLong()
bellSyncDiffMillis *= -1
}
// get the current bell-synced time
val now = Time.fromMillis(Time.getNow().inMillis + bellSyncDiffMillis)
// search for lessons to display
val timetableDate = Date.getToday()
var checkedDays = 0
lessons = allLessons.filter { it.profileId == profile.id && it.displayDate == timetableDate && it.displayEndTime > now && it.type != Lesson.TYPE_NO_LESSONS }
while ((lessons.isEmpty() || lessons.none {
it.displayDate != today || (it.displayDate == today && it.displayEndTime != null && it.displayEndTime!! >= now)
}) && checkedDays < 7) {
timetableDate.stepForward(0, 0, 1)
lessons = allLessons.filter { it.profileId == profile.id && it.displayDate == timetableDate && it.type != Lesson.TYPE_NO_LESSONS }
checkedDays++
}
}
deferred.await()
val text = StringBuilder()
for (lesson in lessons) {
text += lesson.displayStartTime?.stringHM+" "+lesson.displaySubjectName+"\n"
}
b.text.text = text.toString()
}}
override fun unbind(position: Int, holder: HomeCardAdapter.ViewHolder) = Unit
}

View File

@ -9,6 +9,7 @@ import android.widget.Toast
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.viewpager.widget.ViewPager import androidx.viewpager.widget.ViewPager
import com.mikepenz.iconics.typeface.library.community.material.CommunityMaterial import com.mikepenz.iconics.typeface.library.community.material.CommunityMaterial
import com.mikepenz.iconics.typeface.library.szkolny.font.SzkolnyFont
import pl.szczodrzynski.edziennik.App import pl.szczodrzynski.edziennik.App
import pl.szczodrzynski.edziennik.MainActivity import pl.szczodrzynski.edziennik.MainActivity
import pl.szczodrzynski.edziennik.R import pl.szczodrzynski.edziennik.R
@ -52,7 +53,7 @@ class HomeworkFragment : Fragment() {
BottomSheetPrimaryItem(true) BottomSheetPrimaryItem(true)
.withTitle(R.string.menu_add_event) .withTitle(R.string.menu_add_event)
.withDescription(R.string.menu_add_event_desc) .withDescription(R.string.menu_add_event_desc)
.withIcon(CommunityMaterial.Icon.cmd_calendar_plus) .withIcon(SzkolnyFont.Icon.szf_calendar_plus_outline)
.withOnClickListener(View.OnClickListener { .withOnClickListener(View.OnClickListener {
activity.bottomSheet.close() activity.bottomSheet.close()
EventManualDialog(activity).show(app, null, null, null, EventManualDialog.DIALOG_HOMEWORK) EventManualDialog(activity).show(app, null, null, null, EventManualDialog.DIALOG_HOMEWORK)
@ -60,7 +61,7 @@ class HomeworkFragment : Fragment() {
BottomSheetSeparatorItem(true), BottomSheetSeparatorItem(true),
BottomSheetPrimaryItem(true) BottomSheetPrimaryItem(true)
.withTitle(R.string.menu_mark_as_read) .withTitle(R.string.menu_mark_as_read)
.withIcon(CommunityMaterial.Icon.cmd_eye_check) .withIcon(CommunityMaterial.Icon.cmd_eye_check_outline)
.withOnClickListener(View.OnClickListener { .withOnClickListener(View.OnClickListener {
activity.bottomSheet.close() activity.bottomSheet.close()
AsyncTask.execute { app.db.metadataDao().setAllSeen(App.profileId, Metadata.TYPE_HOMEWORK, true) } AsyncTask.execute { app.db.metadataDao().setAllSeen(App.profileId, Metadata.TYPE_HOMEWORK, true) }

View File

@ -104,7 +104,7 @@ public class LoginVulcanFragment extends Fragment {
b.helpButton.setOnClickListener((v) -> nav.navigate(R.id.loginVulcanHelpFragment, null, LoginActivity.navOptions)); b.helpButton.setOnClickListener((v) -> nav.navigate(R.id.loginVulcanHelpFragment, null, LoginActivity.navOptions));
b.backButton.setOnClickListener((v) -> nav.navigateUp()); b.backButton.setOnClickListener((v) -> nav.navigateUp());
b.loginQrScan.setImageDrawable(new IconicsDrawable(getActivity()).icon(CommunityMaterial.Icon2.cmd_qrcode).color(IconicsColor.colorInt(Color.BLACK)).size(IconicsSize.dp(72))); b.loginQrScan.setImageDrawable(new IconicsDrawable(getActivity()).icon(CommunityMaterial.Icon2.cmd_qrcode_scan).color(IconicsColor.colorInt(Color.BLACK)).size(IconicsSize.dp(72)));
b.loginQrScan.setOnClickListener((v -> { b.loginQrScan.setOnClickListener((v -> {
QrScannerActivity.resultHandler = result -> { QrScannerActivity.resultHandler = result -> {
try { try {

View File

@ -22,6 +22,7 @@ import com.mikepenz.iconics.IconicsDrawable
import com.mikepenz.iconics.IconicsSize import com.mikepenz.iconics.IconicsSize
import com.mikepenz.iconics.typeface.IIcon import com.mikepenz.iconics.typeface.IIcon
import com.mikepenz.iconics.typeface.library.community.material.CommunityMaterial import com.mikepenz.iconics.typeface.library.community.material.CommunityMaterial
import com.mikepenz.iconics.typeface.library.szkolny.font.SzkolnyFont
import com.mikepenz.iconics.utils.sizeDp import com.mikepenz.iconics.utils.sizeDp
import kotlinx.coroutines.* import kotlinx.coroutines.*
import org.greenrobot.eventbus.EventBus import org.greenrobot.eventbus.EventBus
@ -30,6 +31,9 @@ import org.greenrobot.eventbus.ThreadMode
import pl.szczodrzynski.edziennik.App import pl.szczodrzynski.edziennik.App
import pl.szczodrzynski.edziennik.MainActivity import pl.szczodrzynski.edziennik.MainActivity
import pl.szczodrzynski.edziennik.R import pl.szczodrzynski.edziennik.R
import pl.szczodrzynski.edziennik.api.v2.events.AttachmentGetEvent
import pl.szczodrzynski.edziennik.api.v2.events.AttachmentGetEvent.Companion.TYPE_FINISHED
import pl.szczodrzynski.edziennik.api.v2.events.AttachmentGetEvent.Companion.TYPE_PROGRESS
import pl.szczodrzynski.edziennik.api.v2.events.MessageGetEvent import pl.szczodrzynski.edziennik.api.v2.events.MessageGetEvent
import pl.szczodrzynski.edziennik.api.v2.events.task.EdziennikTask import pl.szczodrzynski.edziennik.api.v2.events.task.EdziennikTask
import pl.szczodrzynski.edziennik.data.db.modules.messages.Message.TYPE_SENT import pl.szczodrzynski.edziennik.data.db.modules.messages.Message.TYPE_SENT
@ -224,17 +228,17 @@ class MessageFragment : Fragment(), CoroutineScope {
attachmentChip.ellipsize = TextUtils.TruncateAt.MIDDLE attachmentChip.ellipsize = TextUtils.TruncateAt.MIDDLE
// create an icon for the attachment // create an icon for the attachment
var icon: IIcon = CommunityMaterial.Icon.cmd_file val icon: IIcon = when (Utils.getExtensionFromFileName(name)) {
when (Utils.getExtensionFromFileName(name)) { "doc", "docx", "odt", "rtf" -> SzkolnyFont.Icon.szf_file_word_outline
"txt" -> icon = CommunityMaterial.Icon.cmd_file_document "xls", "xlsx", "ods" -> SzkolnyFont.Icon.szf_file_excel_outline
"doc", "docx", "odt", "rtf" -> icon = CommunityMaterial.Icon.cmd_file_word "ppt", "pptx", "odp" -> SzkolnyFont.Icon.szf_file_powerpoint_outline
"xls", "xlsx", "ods" -> icon = CommunityMaterial.Icon.cmd_file_excel "pdf" -> SzkolnyFont.Icon.szf_file_pdf_outline
"ppt", "pptx", "odp" -> icon = CommunityMaterial.Icon.cmd_file_powerpoint "mp3", "wav", "aac" -> SzkolnyFont.Icon.szf_file_music_outline
"pdf" -> icon = CommunityMaterial.Icon.cmd_file_pdf "mp4", "avi", "3gp", "mkv", "flv" -> SzkolnyFont.Icon.szf_file_video_outline
"mp3", "wav", "aac" -> icon = CommunityMaterial.Icon.cmd_file_music "jpg", "jpeg", "png", "bmp", "gif" -> SzkolnyFont.Icon.szf_file_image_outline
"mp4", "avi", "3gp", "mkv", "flv" -> icon = CommunityMaterial.Icon.cmd_file_video "zip", "rar", "tar", "7z" -> SzkolnyFont.Icon.szf_zip_box_outline
"jpg", "jpeg", "png", "bmp", "gif" -> icon = CommunityMaterial.Icon.cmd_file_image "html", "cpp", "c", "h", "css", "java", "py" -> SzkolnyFont.Icon.szf_file_code_outline
"zip", "rar", "tar", "7z" -> icon = CommunityMaterial.Icon.cmd_file_lock else -> CommunityMaterial.Icon.cmd_file_document_outline
} }
attachmentChip.chipIcon = IconicsDrawable(activity).color(IconicsColor.colorRes(R.color.colorPrimary)).icon(icon).size(IconicsSize.dp(26)) attachmentChip.chipIcon = IconicsDrawable(activity).color(IconicsColor.colorRes(R.color.colorPrimary)).icon(icon).size(IconicsSize.dp(26))
attachmentChip.closeIcon = IconicsDrawable(activity).icon(CommunityMaterial.Icon.cmd_check).size(IconicsSize.dp(18)).color(IconicsColor.colorInt(Utils.getAttr(activity, android.R.attr.textColorPrimary))) attachmentChip.closeIcon = IconicsDrawable(activity).icon(CommunityMaterial.Icon.cmd_check).size(IconicsSize.dp(18)).color(IconicsColor.colorInt(Utils.getAttr(activity, android.R.attr.textColorPrimary)))
@ -243,7 +247,7 @@ class MessageFragment : Fragment(), CoroutineScope {
attachmentChip.tag = index attachmentChip.tag = index
attachmentChip.setOnClickListener { v -> attachmentChip.setOnClickListener { v ->
if (v.tag is Int) { if (v.tag is Int) {
// TODO downloadAttachment(v.tag as Int) downloadAttachment(v.tag as Int)
} }
} }
attachmentLayout.addView(attachmentChip) attachmentLayout.addView(attachmentChip)
@ -267,6 +271,60 @@ class MessageFragment : Fragment(), CoroutineScope {
} }
} }
private fun downloadAttachment(index: Int) {
val attachment = attachmentList[index]
if (attachment.downloaded != null) {
Utils.openFile(activity, File(attachment.downloaded))
return
}
attachment.chip.isEnabled = false
attachment.chip.setTextColor(Themes.getSecondaryTextColor(activity))
attachment.progressBar.visibility = View.VISIBLE
EdziennikTask.attachmentGet(
App.profileId,
attachment.messageId,
attachment.attachmentId,
attachment.attachmentName
).enqueue(activity)
}
@Subscribe(threadMode = ThreadMode.MAIN)
fun onAttachmentGetEvent(event: AttachmentGetEvent) {
attachmentList.firstOrNull { it.profileId == event.profileId
&& it.messageId == event.messageId
&& it.attachmentId == event.attachmentId }?.let { attachment ->
when (event.eventType) {
TYPE_FINISHED -> {
// save the downloaded file name
attachment.downloaded = event.fileName
// set the correct name (and size)
if (attachment.attachmentSize == -1L)
attachment.chip.text = getString(R.string.messages_attachment_no_size_format, attachment.attachmentName)
else
attachment.chip.text = getString(R.string.messages_attachment_format, attachment.attachmentName, readableFileSize(attachment.attachmentSize))
// hide the progress bar and show a tick icon
attachment.progressBar.visibility = View.GONE
attachment.chip.isEnabled = true
attachment.chip.setTextColor(Themes.getPrimaryTextColor(activity))
attachment.chip.isCloseIconVisible = true
// open the file
Utils.openFile(activity, File(attachment.downloaded))
}
TYPE_PROGRESS -> {
attachment.chip.text = getString(R.string.messages_attachment_downloading_format, attachment.attachmentName, event.bytesWritten.toFloat() / 1000000)
}
}
}
}
private fun checkAttachment(attachment: Attachment) { private fun checkAttachment(attachment: Attachment) {
val storageDir = Environment.getExternalStoragePublicDirectory("Szkolny.eu") val storageDir = Environment.getExternalStoragePublicDirectory("Szkolny.eu")
storageDir.mkdirs() storageDir.mkdirs()
@ -284,7 +342,6 @@ class MessageFragment : Fragment(), CoroutineScope {
e.printStackTrace() e.printStackTrace()
//app.apiEdziennik.guiReportException(activity, 355, e) //app.apiEdziennik.guiReportException(activity, 355, e)
} }
} }
} }

View File

@ -1,104 +0,0 @@
package pl.szczodrzynski.edziennik.ui.modules.notifications;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffColorFilter;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.cardview.widget.CardView;
import androidx.recyclerview.widget.RecyclerView;
import java.util.List;
import pl.szczodrzynski.edziennik.App;
import pl.szczodrzynski.edziennik.R;
import pl.szczodrzynski.edziennik.utils.models.Date;
import pl.szczodrzynski.edziennik.utils.models.Notification;
import static pl.szczodrzynski.edziennik.utils.Utils.d;
public class NotificationsAdapter extends RecyclerView.Adapter<NotificationsAdapter.ViewHolder> {
private static final String TAG = "NotificationsAdapter";
private Context context;
private List<Notification> notificationList;
//getting the context and product list with constructor
public NotificationsAdapter(Context mCtx, List<Notification> notificationList) {
this.context = mCtx;
this.notificationList = notificationList;
}
@NonNull
@Override
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
//inflating and returning our view holder
LayoutInflater inflater = LayoutInflater.from(context);
View view = inflater.inflate(R.layout.row_notifications_item, parent, false);
return new ViewHolder(view);
}
@Override
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
App app = (App) context.getApplicationContext();
Notification notification = notificationList.get(position);
holder.notificationsItemDate.setText(Date.fromMillis(notification.addedDate).getFormattedString());
holder.notificationsItemText.setText(notification.text);
holder.notificationsItemTitle.setText(notification.title);
holder.notificationsItemType.setText(Notification.stringType(context, notification.type));
holder.notificationsItemCard.setOnClickListener((v -> {
Intent intent = new Intent("android.intent.action.MAIN");
notification.fillIntent(intent);
d(TAG, "notification with item "+notification.redirectFragmentId+" extras "+(intent.getExtras() == null ? "null" : intent.getExtras().toString()));
//Log.d(TAG, "Got date "+intent.getLongExtra("timetableDate", 0));
if (notification.profileId != -1 && notification.profileId != app.profile.getId() && context instanceof Activity) {
Toast.makeText(app, app.getString(R.string.toast_changing_profile), Toast.LENGTH_LONG).show();
}
app.sendBroadcast(intent);
}));
if (!notification.seen) {
holder.notificationsItemText.setBackground(context.getResources().getDrawable(R.drawable.bg_rounded_8dp));
holder.notificationsItemText.getBackground().setColorFilter(new PorterDuffColorFilter(0x692196f3, PorterDuff.Mode.MULTIPLY));
}
else {
holder.notificationsItemText.setBackground(null);
}
}
@Override
public int getItemCount() {
return notificationList.size();
}
class ViewHolder extends RecyclerView.ViewHolder {
CardView notificationsItemCard;
TextView notificationsItemDate;
TextView notificationsItemText;
TextView notificationsItemTitle;
TextView notificationsItemType;
ViewHolder(View itemView) {
super(itemView);
notificationsItemCard = itemView.findViewById(R.id.notificationsItemCard);
notificationsItemDate = itemView.findViewById(R.id.notificationsItemDate);
notificationsItemText = itemView.findViewById(R.id.notificationsItemText);
notificationsItemTitle = itemView.findViewById(R.id.notificationsItemTitle);
notificationsItemType = itemView.findViewById(R.id.notificationsItemType);
}
}
}

View File

@ -0,0 +1,72 @@
package pl.szczodrzynski.edziennik.ui.modules.notifications
import android.app.Activity
import android.content.Context
import android.content.Intent
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import android.widget.Toast
import androidx.recyclerview.widget.RecyclerView
import pl.szczodrzynski.edziennik.*
import pl.szczodrzynski.edziennik.data.db.modules.notification.Notification
import pl.szczodrzynski.edziennik.data.db.modules.notification.getNotificationTitle
import pl.szczodrzynski.edziennik.utils.Utils.d
import pl.szczodrzynski.edziennik.utils.models.Date
class NotificationsAdapter(
private val context: Context
) : RecyclerView.Adapter<NotificationsAdapter.ViewHolder>() {
companion object {
private const val TAG = "NotificationsAdapter"
}
var items = listOf<Notification>()
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val inflater = LayoutInflater.from(context)
val view = inflater.inflate(R.layout.row_notifications_item, parent, false)
return ViewHolder(view)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val app = context.applicationContext as App
val notification = items[position]
val date = Date.fromMillis(notification.addedDate).formattedString
val colorSecondary = android.R.attr.textColorSecondary.resolveAttr(context)
holder.title.text = notification.text
holder.profileDate.text = listOf(
notification.profileName ?: "",
"",
date.asColoredSpannable(colorSecondary)
).concat()
holder.type.text = context.getNotificationTitle(notification.type)
holder.root.onClick {
val intent = Intent("android.intent.action.MAIN")
notification.fillIntent(intent)
d(TAG, "notification with item " + notification.viewId + " extras " + if (intent.extras == null) "null" else intent.extras!!.toString())
//Log.d(TAG, "Got date "+intent.getLongExtra("timetableDate", 0));
if (notification.profileId != -1 && notification.profileId != app.profile.id && context is Activity) {
Toast.makeText(app, app.getString(R.string.toast_changing_profile), Toast.LENGTH_LONG).show()
}
app.sendBroadcast(intent)
}
}
override fun getItemCount() = items.size
class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
var root = itemView
var title: TextView = itemView.findViewById(R.id.title)
var profileDate: TextView = itemView.findViewById(R.id.profileDate)
var type: TextView = itemView.findViewById(R.id.type)
}
}

View File

@ -1,65 +0,0 @@
package pl.szczodrzynski.edziennik.ui.modules.notifications;
import android.app.Activity;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.databinding.DataBindingUtil;
import androidx.fragment.app.Fragment;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import pl.szczodrzynski.edziennik.App;
import pl.szczodrzynski.edziennik.R;
import pl.szczodrzynski.edziennik.databinding.FragmentNotificationsBinding;
import pl.szczodrzynski.edziennik.utils.Themes;
public class NotificationsFragment extends Fragment {
private App app = null;
private Activity activity = null;
private FragmentNotificationsBinding b = null;
@Override
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
activity = getActivity();
if (getActivity() == null || getContext() == null)
return null;
app = (App) activity.getApplication();
getContext().getTheme().applyStyle(Themes.INSTANCE.getAppTheme(), true);
if (app.profile == null)
return inflater.inflate(R.layout.fragment_loading, container, false);
// activity, context and profile is valid
b = DataBindingUtil.inflate(inflater, R.layout.fragment_notifications, container, false);
return b.getRoot();
}
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
if (app == null || app.profile == null || activity == null || b == null || !isAdded())
return;
RecyclerView recyclerView = b.notificationsView;
recyclerView.setHasFixedSize(true);
recyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
if (app.appConfig.notifications.size() > 0) {
NotificationsAdapter adapter = new NotificationsAdapter(getContext(), app.appConfig.notifications);
recyclerView.setAdapter(adapter);
recyclerView.setVisibility(View.VISIBLE);
LinearLayoutManager linearLayoutManager = new LinearLayoutManager(getContext());
//linearLayoutManager.setReverseLayout(true);
//linearLayoutManager.setStackFromEnd(true);
recyclerView.setLayoutManager(linearLayoutManager);
b.notificationsNoData.setVisibility(View.GONE);
}
else {
recyclerView.setVisibility(View.GONE);
b.notificationsNoData.setVisibility(View.VISIBLE);
}
}
}

View File

@ -0,0 +1,90 @@
/*
* Copyright (c) Kuba Szczodrzyński 2019-11-22.
*/
package pl.szczodrzynski.edziennik.ui.modules.notifications
import android.os.AsyncTask
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Toast
import androidx.fragment.app.Fragment
import androidx.lifecycle.Observer
import androidx.recyclerview.widget.DividerItemDecoration.HORIZONTAL
import androidx.recyclerview.widget.LinearLayoutManager
import com.mikepenz.iconics.typeface.library.community.material.CommunityMaterial
import pl.szczodrzynski.edziennik.App
import pl.szczodrzynski.edziennik.MainActivity
import pl.szczodrzynski.edziennik.R
import pl.szczodrzynski.edziennik.databinding.FragmentNotificationsBinding
import pl.szczodrzynski.edziennik.utils.SimpleDividerItemDecoration
import pl.szczodrzynski.edziennik.utils.Themes
import pl.szczodrzynski.navlib.bottomsheet.items.BottomSheetPrimaryItem
class NotificationsFragment : Fragment() {
private lateinit var app: App
private lateinit var activity: MainActivity
private lateinit var b: FragmentNotificationsBinding
private val adapter by lazy {
NotificationsAdapter(activity)
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
activity = (getActivity() as MainActivity?) ?: return null
if (context == null)
return null
app = activity.application as App
context!!.theme.applyStyle(Themes.appTheme, true)
if (app.profile == null)
return inflater.inflate(R.layout.fragment_loading, container, false)
// activity, context and profile is valid
b = FragmentNotificationsBinding.inflate(inflater)
return b.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
// TODO check if app, activity, b can be null
if (app.profile == null || !isAdded)
return
activity.bottomSheet.prependItems(
BottomSheetPrimaryItem(true)
.withTitle(R.string.menu_remove_notifications)
.withIcon(CommunityMaterial.Icon.cmd_delete_sweep_outline)
.withOnClickListener(View.OnClickListener {
activity.bottomSheet.close()
AsyncTask.execute { app.db.notificationDao().clearAll() }
Toast.makeText(activity, R.string.menu_remove_notifications_success, Toast.LENGTH_SHORT).show()
}))
app.db.notificationDao()
.getAll()
.observe(this, Observer { notifications ->
if (app.profile == null || !isAdded) return@Observer
adapter.items = notifications
if (b.notificationsView.adapter == null) {
b.notificationsView.adapter = adapter
b.notificationsView.apply {
setHasFixedSize(true)
layoutManager = LinearLayoutManager(context).apply {
addItemDecoration(SimpleDividerItemDecoration(context))
}
}
}
adapter.notifyDataSetChanged()
if (notifications != null && notifications.isNotEmpty()) {
b.notificationsView.visibility = View.VISIBLE
b.notificationsNoData.visibility = View.GONE
} else {
b.notificationsView.visibility = View.GONE
b.notificationsNoData.visibility = View.VISIBLE
}
})
}
}

View File

@ -39,7 +39,7 @@ class SettingsLicenseActivity : MaterialAboutActivity() {
libraryUrl: String): MaterialAboutCard { libraryUrl: String): MaterialAboutCard {
val licenseItem = MaterialAboutActionItem.Builder() val licenseItem = MaterialAboutActionItem.Builder()
.icon(IconicsDrawable(this) .icon(IconicsDrawable(this)
.icon(CommunityMaterial.Icon.cmd_book) .icon(CommunityMaterial.Icon.cmd_book_outline)
.colorInt(foregroundColor) .colorInt(foregroundColor)
.sizeDp(18)) .sizeDp(18))
.setIconGravity(MaterialAboutActionItem.GRAVITY_TOP) .setIconGravity(MaterialAboutActionItem.GRAVITY_TOP)

View File

@ -29,6 +29,7 @@ import com.mikepenz.iconics.IconicsColor;
import com.mikepenz.iconics.IconicsDrawable; import com.mikepenz.iconics.IconicsDrawable;
import com.mikepenz.iconics.IconicsSize; import com.mikepenz.iconics.IconicsSize;
import com.mikepenz.iconics.typeface.library.community.material.CommunityMaterial; import com.mikepenz.iconics.typeface.library.community.material.CommunityMaterial;
import com.mikepenz.iconics.typeface.library.szkolny.font.SzkolnyFont;
import com.theartofdev.edmodo.cropper.CropImage; import com.theartofdev.edmodo.cropper.CropImage;
import com.theartofdev.edmodo.cropper.CropImageView; import com.theartofdev.edmodo.cropper.CropImageView;
import com.wdullaer.materialdatetimepicker.time.TimePickerDialog; import com.wdullaer.materialdatetimepicker.time.TimePickerDialog;
@ -259,7 +260,7 @@ public class SettingsNewFragment extends MaterialAboutFragment {
getString(R.string.settings_profile_remove_text), getString(R.string.settings_profile_remove_text),
getString(R.string.settings_profile_remove_subtext), getString(R.string.settings_profile_remove_subtext),
new IconicsDrawable(activity) new IconicsDrawable(activity)
.icon(CommunityMaterial.Icon.cmd_delete_empty) .icon(SzkolnyFont.Icon.szf_delete_empty_outline)
.size(IconicsSize.dp(iconSizeDp)) .size(IconicsSize.dp(iconSizeDp))
.color(IconicsColor.colorInt(iconColor)) .color(IconicsColor.colorInt(iconColor))
) )
@ -287,7 +288,7 @@ public class SettingsNewFragment extends MaterialAboutFragment {
getString(R.string.settings_theme_theme_text), getString(R.string.settings_theme_theme_text),
Themes.INSTANCE.getThemeName(activity), Themes.INSTANCE.getThemeName(activity),
new IconicsDrawable(activity) new IconicsDrawable(activity)
.icon(CommunityMaterial.Icon2.cmd_palette) .icon(CommunityMaterial.Icon2.cmd_palette_outline)
.size(IconicsSize.dp(iconSizeDp)) .size(IconicsSize.dp(iconSizeDp))
.color(IconicsColor.colorInt(iconColor)) .color(IconicsColor.colorInt(iconColor))
) )
@ -313,7 +314,7 @@ public class SettingsNewFragment extends MaterialAboutFragment {
getString(R.string.settings_theme_mini_drawer_text), getString(R.string.settings_theme_mini_drawer_text),
getString(R.string.settings_theme_mini_drawer_subtext), getString(R.string.settings_theme_mini_drawer_subtext),
new IconicsDrawable(activity) new IconicsDrawable(activity)
.icon(CommunityMaterial.Icon.cmd_chevron_left) .icon(CommunityMaterial.Icon.cmd_dots_vertical)
.size(IconicsSize.dp(iconSizeDp)) .size(IconicsSize.dp(iconSizeDp))
.color(IconicsColor.colorInt(iconColor)) .color(IconicsColor.colorInt(iconColor))
) )
@ -339,7 +340,7 @@ public class SettingsNewFragment extends MaterialAboutFragment {
getString(R.string.settings_theme_mini_drawer_buttons_text), getString(R.string.settings_theme_mini_drawer_buttons_text),
null, null,
new IconicsDrawable(activity) new IconicsDrawable(activity)
.icon(CommunityMaterial.Icon2.cmd_menu) .icon(CommunityMaterial.Icon.cmd_format_list_checks)
.size(IconicsSize.dp(iconSizeDp)) .size(IconicsSize.dp(iconSizeDp))
.color(IconicsColor.colorInt(iconColor)) .color(IconicsColor.colorInt(iconColor))
) )
@ -403,7 +404,7 @@ public class SettingsNewFragment extends MaterialAboutFragment {
getString(R.string.settings_theme_drawer_header_text), getString(R.string.settings_theme_drawer_header_text),
null, null,
new IconicsDrawable(activity) new IconicsDrawable(activity)
.icon(CommunityMaterial.Icon2.cmd_image) .icon(CommunityMaterial.Icon2.cmd_image_outline)
.size(IconicsSize.dp(iconSizeDp)) .size(IconicsSize.dp(iconSizeDp))
.color(IconicsColor.colorInt(iconColor)) .color(IconicsColor.colorInt(iconColor))
) )
@ -504,7 +505,7 @@ public class SettingsNewFragment extends MaterialAboutFragment {
getString(R.string.settings_sync_wifi_text), getString(R.string.settings_sync_wifi_text),
getString(R.string.settings_sync_wifi_subtext), getString(R.string.settings_sync_wifi_subtext),
new IconicsDrawable(activity) new IconicsDrawable(activity)
.icon(CommunityMaterial.Icon2.cmd_wifi_strength_4) .icon(CommunityMaterial.Icon2.cmd_wifi_strength_2)
.size(IconicsSize.dp(iconSizeDp)) .size(IconicsSize.dp(iconSizeDp))
.color(IconicsColor.colorInt(iconColor)) .color(IconicsColor.colorInt(iconColor))
) )
@ -526,7 +527,7 @@ public class SettingsNewFragment extends MaterialAboutFragment {
getString(R.string.settings_sync_sync_interval_text), getString(R.string.settings_sync_sync_interval_text),
getString(R.string.settings_sync_sync_interval_subtext_disabled), getString(R.string.settings_sync_sync_interval_subtext_disabled),
new IconicsDrawable(activity) new IconicsDrawable(activity)
.icon(CommunityMaterial.Icon2.cmd_sync) .icon(CommunityMaterial.Icon.cmd_download_outline)
.size(IconicsSize.dp(iconSizeDp)) .size(IconicsSize.dp(iconSizeDp))
.color(IconicsColor.colorInt(iconColor)) .color(IconicsColor.colorInt(iconColor))
); );
@ -619,7 +620,7 @@ public class SettingsNewFragment extends MaterialAboutFragment {
getString(R.string.settings_sync_quiet_hours_text), getString(R.string.settings_sync_quiet_hours_text),
getString(R.string.settings_sync_quiet_hours_subtext_disabled), getString(R.string.settings_sync_quiet_hours_subtext_disabled),
new IconicsDrawable(activity) new IconicsDrawable(activity)
.icon(CommunityMaterial.Icon.cmd_bell_sleep) .icon(CommunityMaterial.Icon.cmd_bell_sleep_outline)
.size(IconicsSize.dp(iconSizeDp)) .size(IconicsSize.dp(iconSizeDp))
.color(IconicsColor.colorInt(iconColor)) .color(IconicsColor.colorInt(iconColor))
); );
@ -740,7 +741,7 @@ public class SettingsNewFragment extends MaterialAboutFragment {
getString(R.string.settings_sync_notifications_settings_text), getString(R.string.settings_sync_notifications_settings_text),
getString(R.string.settings_sync_notifications_settings_subtext), getString(R.string.settings_sync_notifications_settings_subtext),
new IconicsDrawable(activity) new IconicsDrawable(activity)
.icon(CommunityMaterial.Icon2.cmd_settings) .icon(CommunityMaterial.Icon2.cmd_settings_outline)
.size(IconicsSize.dp(iconSizeDp)) .size(IconicsSize.dp(iconSizeDp))
.color(IconicsColor.colorInt(iconColor)) .color(IconicsColor.colorInt(iconColor))
) )
@ -820,7 +821,7 @@ public class SettingsNewFragment extends MaterialAboutFragment {
getString(R.string.settings_register_shared_events_text), getString(R.string.settings_register_shared_events_text),
getString(R.string.settings_register_shared_events_subtext), getString(R.string.settings_register_shared_events_subtext),
new IconicsDrawable(activity) new IconicsDrawable(activity)
.icon(CommunityMaterial.Icon2.cmd_share_variant) .icon(CommunityMaterial.Icon2.cmd_share_outline)
.size(IconicsSize.dp(iconSizeDp)) .size(IconicsSize.dp(iconSizeDp))
.color(IconicsColor.colorInt(iconColor)) .color(IconicsColor.colorInt(iconColor))
) )
@ -888,7 +889,7 @@ public class SettingsNewFragment extends MaterialAboutFragment {
getString(R.string.settings_register_allow_registration_text), getString(R.string.settings_register_allow_registration_text),
getString(R.string.settings_register_allow_registration_subtext), getString(R.string.settings_register_allow_registration_subtext),
new IconicsDrawable(activity) new IconicsDrawable(activity)
.icon(CommunityMaterial.Icon.cmd_account_circle) .icon(CommunityMaterial.Icon.cmd_account_circle_outline)
.size(IconicsSize.dp(iconSizeDp)) .size(IconicsSize.dp(iconSizeDp))
.color(IconicsColor.colorInt(iconColor)) .color(IconicsColor.colorInt(iconColor))
); );
@ -958,7 +959,7 @@ public class SettingsNewFragment extends MaterialAboutFragment {
getString(R.string.settings_register_bell_sync_text), getString(R.string.settings_register_bell_sync_text),
getRegisterCardBellSyncSubText(), getRegisterCardBellSyncSubText(),
new IconicsDrawable(activity) new IconicsDrawable(activity)
.icon(CommunityMaterial.Icon.cmd_alarm_bell) .icon(SzkolnyFont.Icon.szf_alarm_bell_outline)
.size(IconicsSize.dp(iconSizeDp)) .size(IconicsSize.dp(iconSizeDp))
.color(IconicsColor.colorInt(iconColor)) .color(IconicsColor.colorInt(iconColor))
); );
@ -1030,7 +1031,7 @@ public class SettingsNewFragment extends MaterialAboutFragment {
getString(R.string.settings_register_dont_count_zero_text), getString(R.string.settings_register_dont_count_zero_text),
null, null,
new IconicsDrawable(activity) new IconicsDrawable(activity)
.icon(CommunityMaterial.Icon2.cmd_numeric_0_box) .icon(CommunityMaterial.Icon2.cmd_numeric_0_box_outline)
.size(IconicsSize.dp(iconSizeDp)) .size(IconicsSize.dp(iconSizeDp))
.color(IconicsColor.colorInt(iconColor)) .color(IconicsColor.colorInt(iconColor))
) )
@ -1065,7 +1066,7 @@ public class SettingsNewFragment extends MaterialAboutFragment {
getString(R.string.settings_register_show_teacher_absences_text), getString(R.string.settings_register_show_teacher_absences_text),
null, null,
new IconicsDrawable(activity) new IconicsDrawable(activity)
.icon(CommunityMaterial.Icon.cmd_account_arrow_right) .icon(CommunityMaterial.Icon.cmd_account_arrow_right_outline)
.size(IconicsSize.dp(iconSizeDp)) .size(IconicsSize.dp(iconSizeDp))
.color(IconicsColor.colorInt(iconColor)) .color(IconicsColor.colorInt(iconColor))
) )
@ -1109,7 +1110,7 @@ public class SettingsNewFragment extends MaterialAboutFragment {
.subTextColor(secondaryTextOnPrimaryBg) .subTextColor(secondaryTextOnPrimaryBg)
.subText(BuildConfig.VERSION_NAME + ", " + BuildConfig.BUILD_TYPE) .subText(BuildConfig.VERSION_NAME + ", " + BuildConfig.BUILD_TYPE)
.icon(new IconicsDrawable(activity) .icon(new IconicsDrawable(activity)
.icon(CommunityMaterial.Icon2.cmd_information) .icon(CommunityMaterial.Icon2.cmd_information_outline)
.color(IconicsColor.colorInt(primaryTextOnPrimaryBg)) .color(IconicsColor.colorInt(primaryTextOnPrimaryBg))
.size(IconicsSize.dp(iconSizeDp))) .size(IconicsSize.dp(iconSizeDp)))
.build(); .build();
@ -1133,7 +1134,7 @@ public class SettingsNewFragment extends MaterialAboutFragment {
.textColor(primaryTextOnPrimaryBg) .textColor(primaryTextOnPrimaryBg)
.subTextColor(secondaryTextOnPrimaryBg) .subTextColor(secondaryTextOnPrimaryBg)
.icon(new IconicsDrawable(activity) .icon(new IconicsDrawable(activity)
.icon(CommunityMaterial.Icon2.cmd_shield_half_full) .icon(CommunityMaterial.Icon2.cmd_shield_outline)
.color(IconicsColor.colorInt(primaryTextOnPrimaryBg)) .color(IconicsColor.colorInt(primaryTextOnPrimaryBg))
.size(IconicsSize.dp(iconSizeDp))) .size(IconicsSize.dp(iconSizeDp)))
.setOnClickAction(ConvenienceBuilder.createWebsiteOnClickAction(activity, Uri.parse("https://szkolny.eu/privacy-policy"))) .setOnClickAction(ConvenienceBuilder.createWebsiteOnClickAction(activity, Uri.parse("https://szkolny.eu/privacy-policy")))
@ -1145,7 +1146,7 @@ public class SettingsNewFragment extends MaterialAboutFragment {
.subTextColor(secondaryTextOnPrimaryBg) .subTextColor(secondaryTextOnPrimaryBg)
.subText(R.string.settings_about_discord_subtext) .subText(R.string.settings_about_discord_subtext)
.icon(new IconicsDrawable(activity) .icon(new IconicsDrawable(activity)
.icon(CommunityMaterial.Icon.cmd_discord) .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://discord.gg/n9e8pWr")))
@ -1270,7 +1271,7 @@ public class SettingsNewFragment extends MaterialAboutFragment {
.textColor(primaryTextOnPrimaryBg) .textColor(primaryTextOnPrimaryBg)
.subTextColor(secondaryTextOnPrimaryBg) .subTextColor(secondaryTextOnPrimaryBg)
.icon(new IconicsDrawable(activity) .icon(new IconicsDrawable(activity)
.icon(CommunityMaterial.Icon.cmd_bug) .icon(CommunityMaterial.Icon.cmd_bug_outline)
.color(IconicsColor.colorInt(primaryTextOnPrimaryBg)) .color(IconicsColor.colorInt(primaryTextOnPrimaryBg))
.size(IconicsSize.dp(iconSizeDp))) .size(IconicsSize.dp(iconSizeDp)))
.setOnClickAction(() -> { .setOnClickAction(() -> {

View File

@ -37,6 +37,7 @@ import androidx.viewpager.widget.ViewPager;
import com.afollestad.materialdialogs.MaterialDialog; import com.afollestad.materialdialogs.MaterialDialog;
import com.google.android.material.tabs.TabLayout; import com.google.android.material.tabs.TabLayout;
import com.mikepenz.iconics.typeface.library.community.material.CommunityMaterial; import com.mikepenz.iconics.typeface.library.community.material.CommunityMaterial;
import com.mikepenz.iconics.typeface.library.szkolny.font.SzkolnyFont;
import java.io.File; import java.io.File;
import java.io.FileOutputStream; import java.io.FileOutputStream;
@ -103,7 +104,7 @@ public class TimetableFragment extends Fragment {
new BottomSheetPrimaryItem(true) new BottomSheetPrimaryItem(true)
.withTitle(R.string.menu_add_event) .withTitle(R.string.menu_add_event)
.withDescription(R.string.menu_add_event_desc) .withDescription(R.string.menu_add_event_desc)
.withIcon(CommunityMaterial.Icon.cmd_calendar_plus) .withIcon(SzkolnyFont.Icon.szf_calendar_plus_outline)
.withOnClickListener(v3 -> { .withOnClickListener(v3 -> {
activity.getBottomSheet().close(); activity.getBottomSheet().close();
new MaterialDialog.Builder(activity) new MaterialDialog.Builder(activity)
@ -132,7 +133,7 @@ public class TimetableFragment extends Fragment {
new BottomSheetSeparatorItem(true), new BottomSheetSeparatorItem(true),
new BottomSheetPrimaryItem(true) new BottomSheetPrimaryItem(true)
.withTitle(R.string.menu_mark_as_read) .withTitle(R.string.menu_mark_as_read)
.withIcon(CommunityMaterial.Icon.cmd_eye_check) .withIcon(CommunityMaterial.Icon.cmd_eye_check_outline)
.withOnClickListener(v3 -> { .withOnClickListener(v3 -> {
activity.getBottomSheet().close(); activity.getBottomSheet().close();
AsyncTask.execute(() -> app.db.metadataDao().setAllSeen(App.profileId, TYPE_LESSON_CHANGE, true)); AsyncTask.execute(() -> app.db.metadataDao().setAllSeen(App.profileId, TYPE_LESSON_CHANGE, true));
@ -183,7 +184,7 @@ public class TimetableFragment extends Fragment {
if (app == null || app.profile == null || activity == null || b == null || !isAdded()) if (app == null || app.profile == null || activity == null || b == null || !isAdded())
return; return;
List<LessonFull> lessons = app.db.lessonDao().getAllWeekNow(App.profileId, today.clone().stepForward(0, 0, -today.getWeekDay()), today); List<LessonFull> lessons = app.db.lessonDao().getAllWeekNow(App.profileId, today.getWeekStart(), today);
displayingDate = HomeFragment.findDateWithLessons(App.profileId, lessons); displayingDate = HomeFragment.findDateWithLessons(App.profileId, lessons);
pageSelection = app.appConfig.timetableDisplayDaysBackward + Date.diffDays(displayingDate, today); // DEFAULT HERE pageSelection = app.appConfig.timetableDisplayDaysBackward + Date.diffDays(displayingDate, today); // DEFAULT HERE

View File

@ -4,21 +4,28 @@ import android.content.BroadcastReceiver
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.content.IntentFilter import android.content.IntentFilter
import android.os.AsyncTask
import android.os.Bundle import android.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.Toast
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.viewpager.widget.ViewPager import androidx.viewpager.widget.ViewPager
import com.google.android.material.datepicker.MaterialDatePicker
import com.mikepenz.iconics.typeface.library.community.material.CommunityMaterial import com.mikepenz.iconics.typeface.library.community.material.CommunityMaterial
import com.mikepenz.iconics.typeface.library.szkolny.font.SzkolnyFont
import kotlinx.coroutines.* import kotlinx.coroutines.*
import pl.szczodrzynski.edziennik.App import pl.szczodrzynski.edziennik.App
import pl.szczodrzynski.edziennik.MainActivity import pl.szczodrzynski.edziennik.MainActivity
import pl.szczodrzynski.edziennik.R import pl.szczodrzynski.edziennik.R
import pl.szczodrzynski.edziennik.api.v2.LOGIN_TYPE_LIBRUS import pl.szczodrzynski.edziennik.api.v2.LOGIN_TYPE_LIBRUS
import pl.szczodrzynski.edziennik.data.db.modules.metadata.Metadata
import pl.szczodrzynski.edziennik.databinding.FragmentTimetableV2Binding import pl.szczodrzynski.edziennik.databinding.FragmentTimetableV2Binding
import pl.szczodrzynski.edziennik.utils.Themes import pl.szczodrzynski.edziennik.utils.Themes
import pl.szczodrzynski.edziennik.utils.models.Date import pl.szczodrzynski.edziennik.utils.models.Date
import pl.szczodrzynski.navlib.bottomsheet.items.BottomSheetPrimaryItem
import pl.szczodrzynski.navlib.bottomsheet.items.BottomSheetSeparatorItem
import kotlin.coroutines.CoroutineContext import kotlin.coroutines.CoroutineContext
class TimetableFragment : Fragment(), CoroutineScope { class TimetableFragment : Fragment(), CoroutineScope {
@ -60,7 +67,7 @@ class TimetableFragment : Fragment(), CoroutineScope {
override fun onReceive(context: Context, i: Intent) { override fun onReceive(context: Context, i: Intent) {
if (!isAdded) if (!isAdded)
return return
val dateStr = i.extras?.getString("date", null) ?: return val dateStr = i.extras?.getString("timetableDate", null) ?: return
val date = Date.fromY_m_d(dateStr) val date = Date.fromY_m_d(dateStr)
b.viewPager.setCurrentItem(items.indexOf(date), true) b.viewPager.setCurrentItem(items.indexOf(date), true)
} }
@ -148,14 +155,45 @@ class TimetableFragment : Fragment(), CoroutineScope {
} }
}) })
val selectedDate = arguments?.getString("timetableDate", "")?.let { if (it.isBlank()) null else Date.fromY_m_d(it) }
b.tabLayout.setUpWithViewPager(b.viewPager) b.tabLayout.setUpWithViewPager(b.viewPager)
b.tabLayout.setCurrentItem(items.indexOfFirst { it.value == today }, false) b.tabLayout.setCurrentItem(items.indexOfFirst { it.value == selectedDate?.value ?: today }, false)
activity.navView.bottomSheet.prependItems(
BottomSheetPrimaryItem(true)
.withTitle(R.string.timetable_select_day)
.withIcon(SzkolnyFont.Icon.szf_calendar_today_outline)
.withOnClickListener(View.OnClickListener {
activity.bottomSheet.close()
MaterialDatePicker.Builder
.datePicker()
.setSelection(Date.getToday().inMillis)
.build()
.apply {
addOnPositiveButtonClickListener { dateInMillis ->
val dateSelected = Date.fromMillis(dateInMillis)
b.tabLayout.setCurrentItem(items.indexOfFirst { it == dateSelected }, true)
}
show(this@TimetableFragment.activity.supportFragmentManager, "MaterialDatePicker")
}
}),
BottomSheetSeparatorItem(true),
BottomSheetPrimaryItem(true)
.withTitle(R.string.menu_mark_as_read)
.withIcon(CommunityMaterial.Icon.cmd_eye_check_outline)
.withOnClickListener(View.OnClickListener {
activity.bottomSheet.close()
AsyncTask.execute { app.db.metadataDao().setAllSeen(App.profileId, Metadata.TYPE_LESSON_CHANGE, true) }
Toast.makeText(activity, R.string.main_menu_mark_as_read_success, Toast.LENGTH_SHORT).show()
})
)
//activity.navView.bottomBar.fabEnable = true //activity.navView.bottomBar.fabEnable = true
activity.navView.bottomBar.fabExtendedText = getString(R.string.timetable_today) activity.navView.bottomBar.fabExtendedText = getString(R.string.timetable_today)
activity.navView.bottomBar.fabIcon = CommunityMaterial.Icon.cmd_calendar_today activity.navView.bottomBar.fabIcon = SzkolnyFont.Icon.szf_calendar_today_outline
activity.navView.setFabOnClickListener(View.OnClickListener { activity.navView.setFabOnClickListener(View.OnClickListener {
b.tabLayout.setCurrentItem(items.indexOfFirst { it.value == today }, true) b.tabLayout.setCurrentItem(items.indexOfFirst { it.value == today }, true)
}) })
}} }}
} }

View File

@ -19,7 +19,7 @@ class TimetablePagerAdapter(
} }
private val today by lazy { Date.getToday() } private val today by lazy { Date.getToday() }
private val weekStart by lazy { today.clone().stepForward(0, 0, -today.weekDay) } private val weekStart by lazy { today.weekStart }
private val weekEnd by lazy { weekStart.clone().stepForward(0, 0, 6) } private val weekEnd by lazy { weekStart.clone().stepForward(0, 0, 6) }
override fun getItem(position: Int): Fragment { override fun getItem(position: Int): Fragment {
@ -49,4 +49,4 @@ class TimetablePagerAdapter(
} }
return pageTitle return pageTitle
} }
} }

View File

@ -120,7 +120,7 @@ class TimetableDayFragment : Fragment(), CoroutineScope {
parent?.removeAllViews() parent?.removeAllViews()
parent?.addView(view) parent?.addView(view)
val b = TimetableNoTimetableBinding.bind(view) val b = TimetableNoTimetableBinding.bind(view)
val weekStart = date.clone().stepForward(0, 0, -date.weekDay).stringY_m_d val weekStart = date.weekStart.stringY_m_d
b.noTimetableSync.onClick { b.noTimetableSync.onClick {
it.isEnabled = false it.isEnabled = false
EdziennikTask.syncProfile( EdziennikTask.syncProfile(
@ -247,6 +247,8 @@ class TimetableDayFragment : Fragment(), CoroutineScope {
lb.detailsFirst.text = listOfNotEmpty(timeRange, classroomInfo).concat(bullet) lb.detailsFirst.text = listOfNotEmpty(timeRange, classroomInfo).concat(bullet)
lb.detailsSecond.text = listOfNotEmpty(teacherInfo, teamInfo).concat(bullet) lb.detailsSecond.text = listOfNotEmpty(teacherInfo, teamInfo).concat(bullet)
lb.unread = lesson.type != Lesson.TYPE_NORMAL && !lesson.seen
//lb.subjectName.typeface = Typeface.create("sans-serif-light", Typeface.BOLD) //lb.subjectName.typeface = Typeface.create("sans-serif-light", Typeface.BOLD)
when (lesson.type) { when (lesson.type) {
Lesson.TYPE_NORMAL -> { Lesson.TYPE_NORMAL -> {

View File

@ -0,0 +1,41 @@
/*
* Copyright (c) Kuba Szczodrzyński 2019-11-22.
*/
package pl.szczodrzynski.edziennik.utils;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.drawable.Drawable;
import android.view.View;
import androidx.recyclerview.widget.RecyclerView;
import pl.szczodrzynski.edziennik.R;
public class SimpleDividerItemDecoration extends RecyclerView.ItemDecoration {
private Drawable mDivider;
public SimpleDividerItemDecoration(Context context) {
mDivider = context.getResources().getDrawable(R.drawable.divider);
}
@Override
public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) {
int left = parent.getPaddingLeft();
int right = parent.getWidth() - parent.getPaddingRight();
int childCount = parent.getChildCount();
for (int i = 0; i < childCount; i++) {
View child = parent.getChildAt(i);
RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child.getLayoutParams();
int top = child.getBottom() + params.bottomMargin;
int bottom = top + mDivider.getIntrinsicHeight();
mDivider.setBounds(left, top, right, bottom);
mDivider.draw(c);
}
}
}

View File

@ -75,21 +75,22 @@ class TextInputDropDown : TextInputEditText {
} }
} }
fun select(item: Item) { fun select(item: Item): Item? {
selected = item selected = item
updateText() updateText()
return item
} }
fun select(id: Long?) { fun select(id: Long?): Item? {
items.singleOrNull { it.id == id }?.let { select(it) } return items.singleOrNull { it.id == id }?.let { select(it) }
} }
fun select(tag: Any?) { fun select(tag: Any?): Item? {
items.singleOrNull { it.tag == tag }?.let { select(it) } return items.singleOrNull { it.tag == tag }?.let { select(it) }
} }
fun select(index: Int) { fun select(index: Int): Item? {
items.getOrNull(index)?.let { select(it) } return items.getOrNull(index)?.let { select(it) }
} }
fun deselect(): TextInputDropDown { fun deselect(): TextInputDropDown {

View File

@ -0,0 +1,56 @@
/*
* Copyright (c) Kuba Szczodrzyński 2019-11-22.
*/
package pl.szczodrzynski.edziennik.utils
import android.content.Context
import android.graphics.Rect
import android.util.AttributeSet
import android.view.KeyEvent
import android.view.KeyEvent.KEYCODE_BACK
import androidx.annotation.NonNull
import com.google.android.material.textfield.TextInputEditText
class TextInputKeyboardEdit : TextInputEditText {
/**
* Keyboard Listener
*/
internal var listener: KeyboardListener? = null
constructor(context: Context) : super(context)
constructor(context: Context, attrs: AttributeSet) : super(context, attrs)
constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr)
override fun onFocusChanged(focused: Boolean, direction: Int, previouslyFocusedRect: Rect?) {
super.onFocusChanged(focused, direction, previouslyFocusedRect)
if (listener != null)
listener!!.onStateChanged(this, true)
}
override fun onKeyPreIme(keyCode: Int, @NonNull event: KeyEvent): Boolean {
if (event.keyCode == KEYCODE_BACK && event.action == KeyEvent.ACTION_UP) {
if (listener != null)
listener!!.onStateChanged(this, false)
// Hide cursor
isFocusable = false
// Set EditText to be focusable again
isFocusable = true
isFocusableInTouchMode = true
}
return super.onKeyPreIme(keyCode, event)
}
fun setOnKeyboardListener(listener: KeyboardListener) {
this.listener = listener
}
interface KeyboardListener {
fun onStateChanged(keyboardEditText: TextInputKeyboardEdit, showing: Boolean)
}
}

View File

@ -95,6 +95,7 @@ public class AppConfig {
public String updateUrl = ""; public String updateUrl = "";
public String updateFilename = ""; public String updateFilename = "";
public boolean updateMandatory = false; public boolean updateMandatory = false;
public boolean updateDirect = false;
public boolean webPushEnabled = false; public boolean webPushEnabled = false;

View File

@ -41,6 +41,10 @@ public class Date implements Comparable<Date> {
return new Date(this.year, this.month, this.day); return new Date(this.year, this.month, this.day);
} }
public Date getWeekStart() {
return clone().stepForward(0, 0, -getWeekDay());
}
public static Date fromYmd(String dateTime) { public static Date fromYmd(String dateTime) {
return new Date(Integer.parseInt(dateTime.substring(0, 4)), Integer.parseInt(dateTime.substring(4, 6)), Integer.parseInt(dateTime.substring(6, 8))); return new Date(Integer.parseInt(dateTime.substring(0, 4)), Integer.parseInt(dateTime.substring(4, 6)), Integer.parseInt(dateTime.substring(6, 8)));
} }
@ -134,11 +138,23 @@ public class Date implements Comparable<Date> {
public Date stepForward(int years, int months, int days) { public Date stepForward(int years, int months, int days) {
this.day += days; this.day += days;
if (day <= 0) {
month--;
if(month <= 0) {
month += 12;
year--;
}
day += daysInMonth();
}
if (day > daysInMonth()) { if (day > daysInMonth()) {
day -= daysInMonth(); day -= daysInMonth();
month++; month++;
} }
this.month += months; this.month += months;
if(month <= 0) {
month += 12;
year--;
}
if (month > 12) { if (month > 12) {
month -= 12; month -= 12;
year++; year++;
@ -176,6 +192,7 @@ public class Date implements Comparable<Date> {
public boolean isLeap() { public boolean isLeap() {
return ((year & 3) == 0) && ((year % 100) != 0 || (year % 400) == 0); return ((year & 3) == 0) && ((year % 100) != 0 || (year % 400) == 0);
} }
public int daysInMonth() { public int daysInMonth() {
switch (month) { switch (month) {
case 1: case 1:
@ -255,4 +272,12 @@ public class Date implements Comparable<Date> {
", day=" + day + ", day=" + day +
'}'; '}';
} }
@Override
public int hashCode() {
int result = year;
result = 31 * result + month;
result = 31 * result + day;
return result;
}
} }

View File

@ -121,7 +121,7 @@ public class Time implements Comparable<Time> {
public String getStringValue() public String getStringValue()
{ {
return (hour < 10 ? "0" : "")+Integer.toString(hour)+(minute < 10 ? "0" : "")+Integer.toString(minute)+(second < 10 ? "0" : "")+Integer.toString(second); return (hour < 10 ? "0" : "")+ hour +(minute < 10 ? "0" : "")+ minute +(second < 10 ? "0" : "")+ second;
} }
public String getStringHM() public String getStringHM()
@ -129,18 +129,18 @@ public class Time implements Comparable<Time> {
if (hour < 0) { if (hour < 0) {
return ""; return "";
} }
return Integer.toString(hour)+":"+(minute < 10 ? "0" : "")+Integer.toString(minute); return hour +":"+(minute < 10 ? "0" : "")+ minute;
} }
public String getStringH_M() public String getStringH_M()
{ {
if (hour < 0) { if (hour < 0) {
return ""; return "";
} }
return Integer.toString(hour)+"-"+(minute < 10 ? "0" : "")+Integer.toString(minute); return hour +"-"+(minute < 10 ? "0" : "")+ minute;
} }
public String getStringHMS() public String getStringHMS()
{ {
return Integer.toString(hour)+":"+(minute < 10 ? "0" : "")+Integer.toString(minute)+":"+(second < 10 ? "0" : "")+Integer.toString(second); return hour +":"+(minute < 10 ? "0" : "")+ minute +":"+(second < 10 ? "0" : "")+ second;
} }
public static Time getNow() public static Time getNow()
@ -194,4 +194,12 @@ public class Time implements Comparable<Time> {
", second=" + second + ", second=" + second +
'}'; '}';
} }
@Override
public int hashCode() {
int result = hour;
result = 31 * result + minute;
result = 31 * result + second;
return result;
}
} }

View File

@ -117,7 +117,7 @@ public class WidgetNotifications extends AppWidgetProvider {
.color(IconicsColor.colorInt(Color.WHITE)) .color(IconicsColor.colorInt(Color.WHITE))
.size(IconicsSize.dp(widgetConfig.bigStyle ? 24 : 16)).toBitmap()); .size(IconicsSize.dp(widgetConfig.bigStyle ? 24 : 16)).toBitmap());
views.setImageViewBitmap(R.id.widgetNotificationsSync, new IconicsDrawable(context, CommunityMaterial.Icon2.cmd_sync) views.setImageViewBitmap(R.id.widgetNotificationsSync, new IconicsDrawable(context, CommunityMaterial.Icon.cmd_download_outline)
.color(IconicsColor.colorInt(Color.WHITE)) .color(IconicsColor.colorInt(Color.WHITE))
.size(IconicsSize.dp(widgetConfig.bigStyle ? 24 : 16)).toBitmap()); .size(IconicsSize.dp(widgetConfig.bigStyle ? 24 : 16)).toBitmap());

View File

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright (c) Kuba Szczodrzyński 2019-11-22.
-->
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<size
android:width="1dp"
android:height="1dp" />
<solid android:color="@color/dividerColor" />
</shape>

View File

@ -0,0 +1,28 @@
<!--
~ Copyright (c) Kuba Szczodrzyński 2019-11-24.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="128dp"
android:height="128dp"
android:viewportWidth="128"
android:viewportHeight="128">
<path
android:pathData="M63.79,8.64C1.48,8.64 0,78.5 0,92.33c0,13.83 28.56,25.03 63.79,25.03c35.24,0 63.79,-11.21 63.79,-25.03C127.58,78.5 126.11,8.64 63.79,8.64z"
android:fillColor="#FCC21B"/>
<path
android:pathData="M63.91,104.82c-3.43,0 -6.87,-0.43 -10.25,-1.31c-1.6,-0.42 -2.56,-2.06 -2.15,-3.66c0.42,-1.6 2.06,-2.56 3.66,-2.14c11.65,3.04 24.21,-0.21 32.78,-8.48c1.19,-1.15 3.09,-1.12 4.24,0.08c1.15,1.19 1.12,3.09 -0.08,4.24C84.54,100.85 74.32,104.82 63.91,104.82z"
android:fillColor="#2F2F2F"/>
<path
android:pathData="M55.53,67.26c-0.01,0.01 -0.02,0.02 -0.02,0.02C55.51,67.27 55.52,67.26 55.53,67.26z"
android:fillColor="#2F2F2F"/>
<path
android:pathData="M98.21,41.34c-13.36,0 -15.15,2.03 -21.4,3.36C70.56,46.02 64,46.02 64,46.02s-6.56,0 -12.81,-1.33c-6.25,-1.33 -8.05,-3.36 -21.4,-3.36c-13.36,0 -29.37,2.89 -29.37,2.89v8.51c0,0 3.59,0.47 3.91,3.75c0.16,1.33 -3.12,28.35 23.51,28.35c18.9,0 26.87,-11.33 29.45,-20.54c1.17,-4.37 2.19,-9.37 6.72,-9.37c4.53,0 5.55,5 6.72,9.37c2.58,9.22 10.54,20.54 29.45,20.54c26.63,0 23.35,-27.03 23.51,-28.35c0.31,-3.28 3.91,-3.75 3.91,-3.75v-8.51C127.58,44.23 111.57,41.34 98.21,41.34z"
android:fillColor="#2F2F2F"/>
<path
android:pathData="M95.94,45.05c-6.62,0.23 -11.65,1.31 -11.65,1.31c-9.84,2.06 -10.55,8.14 -9.93,12.97c0.8,6.07 3.29,13.75 10.04,18.49c0.53,0.38 1.76,0.79 2.35,-0.77c0,0 -0.02,0.11 0,0c2.22,-10.48 5.52,-20.14 10.78,-29.89l0,0C98.14,45.37 96.71,45.02 95.94,45.05z"
android:fillColor="#FFFFFF"/>
<path
android:pathData="M31.06,45.02c-4.27,-0.09 -9.11,0.19 -13.65,1.34c-5.1,1.28 -7.07,3.85 -7.6,9.39c-0.53,5.43 -1.13,19.27 8.73,24.46c0.57,0.3 1.83,0.5 2.44,-0.91l0,0C24,66.21 25.61,60.13 32.54,47.22l0,0C33.11,45.49 31.83,45.03 31.06,45.02z"
android:fillColor="#FFFFFF"/>
</vector>

View File

@ -0,0 +1,22 @@
<!--
~ Copyright (c) Kuba Szczodrzyński 2019-11-24.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="128dp"
android:height="128dp"
android:viewportWidth="128"
android:viewportHeight="128">
<path
android:pathData="M64,9.56c-62.41,0 -63.88,69.96 -63.88,83.8c0,13.86 28.59,25.08 63.88,25.08c35.28,0 63.88,-11.22 63.88,-25.08C127.88,79.52 126.4,9.56 64,9.56z"
android:fillColor="#FCC21B"/>
<path
android:pathData="M42.21,62.3c-4.49,0.04 -8.17,-4.27 -8.22,-9.62c-0.05,-5.37 3.55,-9.75 8.04,-9.79c4.48,-0.04 8.17,4.27 8.22,9.64C50.3,57.88 46.7,62.25 42.21,62.3z"
android:fillColor="#2F2F2F"/>
<path
android:pathData="M86.32,62.3c4.48,-0.01 8.11,-4.36 8.1,-9.71c-0.01,-5.37 -3.66,-9.7 -8.14,-9.69c-4.49,0.01 -8.13,4.36 -8.12,9.73C78.18,57.98 81.83,62.31 86.32,62.3z"
android:fillColor="#2F2F2F"/>
<path
android:pathData="M89.69,84.75H38.31c-1.66,0 -3,-1.34 -3,-3s1.34,-3 3,-3h51.39c1.66,0 3,1.34 3,3S91.35,84.75 89.69,84.75z"
android:fillColor="#2F2F2F"/>
</vector>

View File

@ -0,0 +1,19 @@
<!--
~ Copyright (c) Kuba Szczodrzyński 2019-11-24.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="128dp"
android:height="128dp"
android:viewportWidth="128"
android:viewportHeight="128">
<path
android:pathData="M64,9.56c-62.41,0 -63.88,69.96 -63.88,83.8c0,13.86 28.59,25.08 63.88,25.08c35.28,0 63.88,-11.22 63.88,-25.08C127.88,79.52 126.4,9.56 64,9.56z"
android:fillColor="#FCC21B"/>
<path
android:pathData="M42.21,65.3c-4.49,0.04 -8.17,-4.27 -8.22,-9.62c-0.05,-5.37 3.55,-9.75 8.04,-9.79c4.48,-0.04 8.17,4.27 8.22,9.64C50.3,60.88 46.7,65.25 42.21,65.3z"
android:fillColor="#2F2F2F"/>
<path
android:pathData="M86.32,65.3c4.48,-0.01 8.11,-4.36 8.1,-9.71c-0.01,-5.37 -3.66,-9.7 -8.14,-9.69c-4.49,0.01 -8.13,4.36 -8.12,9.73C78.18,60.98 81.83,65.31 86.32,65.3z"
android:fillColor="#2F2F2F"/>
</vector>

View File

@ -0,0 +1,22 @@
<!--
~ Copyright (c) Kuba Szczodrzyński 2019-11-24.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="128dp"
android:height="128dp"
android:viewportWidth="128"
android:viewportHeight="128">
<path
android:pathData="M64,9.62c-62.41,0 -63.88,69.96 -63.88,83.8c0,13.86 28.59,25.08 63.88,25.08c35.28,0 63.88,-11.22 63.88,-25.08C127.88,79.58 126.4,9.62 64,9.62z"
android:fillColor="#FCC21B"/>
<path
android:pathData="M41.99,65.5c-4.49,0.04 -8.17,-4.27 -8.22,-9.62c-0.05,-5.37 3.55,-9.75 8.04,-9.79c4.48,-0.04 8.17,4.27 8.22,9.64C50.08,61.09 46.47,65.46 41.99,65.5z"
android:fillColor="#2F2F2F"/>
<path
android:pathData="M86.1,65.5c4.48,-0.01 8.11,-4.36 8.1,-9.71c-0.01,-5.37 -3.66,-9.7 -8.14,-9.69c-4.49,0.01 -8.13,4.36 -8.12,9.73C77.95,61.18 81.61,65.51 86.1,65.5z"
android:fillColor="#2F2F2F"/>
<path
android:pathData="M43.08,97.67c1.99,1.34 4.5,0.46 6.71,0c6.18,-1.28 11.6,-1.33 14.2,-1.33s8.03,0.05 14.2,1.33c2.21,0.46 4.72,1.34 6.71,0c2.52,-1.71 0.66,-7.83 -3.31,-11.97c-2.4,-2.5 -8.13,-7.35 -17.61,-7.35c-9.48,0 -15.2,4.85 -17.61,7.35C42.42,89.85 40.56,95.97 43.08,97.67z"
android:fillColor="#ED6C30"/>
</vector>

View File

@ -0,0 +1,22 @@
<!--
~ Copyright (c) Kuba Szczodrzyński 2019-11-24.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="128dp"
android:height="128dp"
android:viewportWidth="128"
android:viewportHeight="128">
<path
android:pathData="M127.94,93.75c0,14.02 -28.61,25.39 -63.93,25.39S0.06,107.77 0.06,93.75c0,-14.03 1.48,-84.89 63.95,-84.89C126.47,8.86 127.94,79.72 127.94,93.75"
android:fillColor="#FCC21B"/>
<path
android:pathData="M48.14,57.33c0,5.47 -3.66,9.9 -8.19,9.9c-4.53,0 -8.21,-4.43 -8.21,-9.9c0,-5.48 3.68,-9.91 8.21,-9.91C44.48,47.42 48.14,51.85 48.14,57.33"
android:fillColor="#2F2F2F"/>
<path
android:pathData="M80.14,57.33c0,5.47 3.66,9.9 8.21,9.9c4.53,0 8.21,-4.43 8.21,-9.9c0,-5.48 -3.68,-9.91 -8.21,-9.91C83.8,47.42 80.14,51.85 80.14,57.33"
android:fillColor="#2F2F2F"/>
<path
android:pathData="M66.8,93.74c-0.72,0 -1.48,-0.03 -2.25,-0.07C46.53,92.59 39.69,82.82 39.41,82.4c-1,-1.48 -0.62,-3.48 0.85,-4.48c1.46,-1 3.45,-0.62 4.46,0.83c0.25,0.37 5.61,7.61 20.22,8.47c14.57,0.84 20.91,-8.67 20.99,-8.77c0.95,-1.49 2.97,-1.92 4.45,-0.96c1.49,0.97 1.93,2.96 0.95,4.45C91.01,82.46 83.52,93.74 66.8,93.74z"
android:fillColor="#2F2F2F"/>
</vector>

Binary file not shown.

After

Width:  |  Height:  |  Size: 62 KiB

View File

@ -0,0 +1,37 @@
<!--
~ Copyright (c) Kuba Szczodrzyński 2019-11-22.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="128dp"
android:height="128dp"
android:viewportWidth="128"
android:viewportHeight="128">
<path
android:pathData="m104,18h-80c-5.523,0 -10,4.477 -10,10v84.699c0,2.93 2.371,5.301 5.301,5.301h0.012c1.691,0 3.277,-0.805 4.277,-2.164l10.082,-13.75c1.883,-2.566 4.875,-4.086 8.063,-4.086h62.266c5.523,0 10,-4.477 10,-10v-60c0,-5.523 -4.477,-10 -10,-10z"
android:fillColor="#2babee"/>
<path
android:pathData="m128,104c0,13.254 -10.746,24 -24,24 -13.254,0 -24,-10.746 -24,-24s10.746,-24 24,-24c13.254,0 24,10.746 24,24z"
android:fillColor="#fc556c"/>
<path
android:pathData="m114.84,98.824 l-16.012,16.012c-1.555,1.555 -4.102,1.555 -5.656,0 -1.555,-1.559 -1.555,-4.106 0,-5.656l16.012,-16.012c1.555,-1.555 4.102,-1.555 5.656,0 1.551,1.555 1.551,4.102 0,5.656z"
android:fillColor="#fff"/>
<path
android:pathData="m93.164,98.824 l16.012,16.012c1.555,1.555 4.102,1.555 5.656,0 1.555,-1.559 1.555,-4.106 0,-5.656l-16.012,-16.012c-1.555,-1.555 -4.102,-1.555 -5.656,0 -1.551,1.555 -1.551,4.102 0,5.656z"
android:fillColor="#fff"/>
<path
android:pathData="m74,78h-36c-2.211,0 -4,-1.789 -4,-4 0,-2.211 1.789,-4 4,-4h36c2.211,0 4,1.789 4,4 0,2.211 -1.789,4 -4,4z"
android:fillColor="#f1fcff"/>
<path
android:pathData="m72,54h18c2.211,0 4,1.789 4,4 0,2.211 -1.789,4 -4,4h-18c-2.211,0 -4,-1.789 -4,-4 0,-2.211 1.789,-4 4,-4z"
android:fillColor="#f1fcff"/>
<path
android:pathData="m38,54h20c2.211,0 4,1.789 4,4 0,2.211 -1.789,4 -4,4h-20c-2.211,0 -4,-1.789 -4,-4 0,-2.211 1.789,-4 4,-4z"
android:fillColor="#f1fcff"/>
<path
android:pathData="m38,38h34c2.211,0 4,1.789 4,4 0,2.211 -1.789,4 -4,4h-34c-2.211,0 -4,-1.789 -4,-4 0,-2.211 1.789,-4 4,-4z"
android:fillColor="#f1fcff"/>
<path
android:pathData="m86,38h4c2.211,0 4,1.789 4,4 0,2.211 -1.789,4 -4,4h-4c-2.211,0 -4,-1.789 -4,-4 0,-2.211 1.789,-4 4,-4z"
android:fillColor="#f1fcff"/>
</vector>

View File

@ -1,61 +1,12 @@
<!--
~ Copyright (c) Kuba Szczodrzyński 2019-11-11.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android" <vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt"
android:width="128dp" android:width="128dp"
android:height="128dp" android:height="128dp"
android:viewportWidth="128" android:viewportWidth="128"
android:viewportHeight="128"> android:viewportHeight="128">
<path <path
android:pathData="m117,76h4c3.852,0 7,-3.148 7,-7 0,-3.852 -3.148,-7 -7,-7h-13c-2.199,0 -4,-1.801 -4,-4s1.801,-4 4,-4h11c3.852,0 7,-3.148 7,-7 0,-3.852 -3.148,-7 -7,-7h-1c-2.211,0 -4,-1.789 -4,-4 0,-2.211 1.789,-4 4,-4h4c3.695,0 6.637,-3.387 5.879,-7.211 -0.566,-2.844 -3.238,-4.789 -6.141,-4.789h-15.738c-1.656,0 -3,-1.344 -3,-3s1.344,-3 3,-3h6.824c2.277,0 4.402,-1.441 4.992,-3.641 0.895,-3.328 -1.625,-6.359 -4.816,-6.359h-96c-3.852,0 -7,3.148 -7,7 0,3.852 3.148,7 7,7h3c2.211,0 4,1.789 4,4 0,2.211 -1.789,4 -4,4h-10.77c-3.34,0 -6.391,2.242 -7.074,5.516 -0.941,4.488 2.508,8.484 6.844,8.484h15l-6,24h-10.77c-3.34,0 -6.391,2.242 -7.074,5.516 -0.941,4.488 2.508,8.484 6.844,8.484h1c2.211,0 4,1.789 4,4 0,2.211 -1.789,4 -4,4h-1c-4.336,0 -7.785,3.996 -6.844,8.484 0.684,3.273 3.734,5.516 7.074,5.516h10.77c2.211,0 4,1.789 4,4s-1.789,4 -4,4h-2.77c-3.34,0 -6.391,2.242 -7.074,5.516 -0.941,4.488 2.508,8.484 6.844,8.484h97c3.313,0 6,-2.688 6,-6s-2.688,-6 -6,-6h-1c-2.762,0 -5,-2.238 -5,-5s2.238,-5 5,-5h6c3.852,0 7,-3.148 7,-7 0,-3.852 -3.148,-7 -7,-7 -2.75,0 -5,-2.25 -5,-5s2.25,-5 5,-5z"> android:pathData="m103.24,15.168 l-7.883,7.871c-8.746,-7.063 -19.719,-11.039 -31.355,-11.039 -27.57,0 -50,22.43 -50,50s22.43,50 50,50c17.758,0 34.348,-9.367 43.301,-24.449 1.41,-2.375 0.625,-5.441 -1.75,-6.852 -2.367,-1.41 -5.438,-0.629 -6.852,1.746 -7.156,12.062 -20.453,19.555 -34.699,19.555 -22.055,0 -40,-17.945 -40,-40s17.945,-40 40,-40c8.934,0 17.371,2.938 24.223,8.16l-9.063,9.047c-2.48,2.5 -0.719,6.762 2.801,6.762h24.039c2.199,0 4,-1.801 4,-4v-24c0,-3.52 -4.262,-5.301 -6.762,-2.801z"
<aapt:attr name="android:fillColor"> android:fillColor="#ffcf48"/>
<gradient
android:gradientRadius="100.522"
android:centerX="63.746"
android:centerY="71.138"
android:type="radial"
android:tileMode="mirror">
<item android:offset="0" android:color="#1F0FCCFF"/>
<item android:offset="0.1927" android:color="#1F0FCEFF"/>
<item android:offset="0.7025" android:color="#1F0FD5FF"/>
<item android:offset="1" android:color="#1F0FD7FF"/>
</gradient>
</aapt:attr>
</path>
<path <path
android:pathData="m103.24,15.168 l-7.883,7.871c-8.746,-7.063 -19.719,-11.039 -31.355,-11.039 -27.57,0 -50,22.43 -50,50 0,27.57 22.43,50 50,50 17.758,0 34.348,-9.367 43.301,-24.449 1.41,-2.375 0.625,-5.441 -1.75,-6.852 -2.367,-1.41 -5.438,-0.629 -6.852,1.746 -7.156,12.062 -20.453,19.555 -34.699,19.555 -22.055,0 -40,-17.945 -40,-40 0,-22.055 17.945,-40 40,-40 8.934,0 17.371,2.938 24.223,8.16l-9.063,9.047c-2.48,2.5 -0.719,6.762 2.801,6.762h24.039c2.199,0 4,-1.801 4,-4v-24c0,-3.52 -4.262,-5.301 -6.762,-2.801z"> android:pathData="m68,85c0,2.762 -2.238,5 -5,5s-5,-2.238 -5,-5 2.238,-5 5,-5 5,2.238 5,5zM70,41c0,-3.867 -3.133,-7 -7,-7s-7,3.133 -7,7c0,0.047 0.016,0.094 0.016,0.141h-0.016l2.438,28.59c0.203,2.414 2.188,4.269 4.563,4.269s4.359,-1.855 4.563,-4.269l2.438,-28.59h-0.016c0,-0.047 0.016,-0.094 0.016,-0.141z"
<aapt:attr name="android:fillColor"> android:fillColor="#fd657a"/>
<gradient
android:startY="125.452"
android:startX="62"
android:endY="6.4762"
android:endX="62"
android:type="linear"
android:tileMode="mirror">
<item android:offset="0" android:color="#FFFEAA53"/>
<item android:offset="0.6124" android:color="#FFFFCD49"/>
<item android:offset="1" android:color="#FFFFDE44"/>
</gradient>
</aapt:attr>
</path>
<path
android:pathData="m68,85c0,2.762 -2.238,5 -5,5s-5,-2.238 -5,-5 2.238,-5 5,-5 5,2.238 5,5zM70,41c0,-3.867 -3.133,-7 -7,-7 -3.867,0 -7,3.133 -7,7 0,0.047 0.016,0.094 0.016,0.141h-0.016l2.438,28.59c0.203,2.414 2.188,4.269 4.563,4.269s4.359,-1.855 4.563,-4.269l2.438,-28.59h-0.016c0,-0.047 0.016,-0.094 0.016,-0.141z">
<aapt:attr name="android:fillColor">
<gradient
android:startY="90"
android:startX="63"
android:endY="11.7176"
android:endX="63"
android:type="linear"
android:tileMode="mirror">
<item android:offset="0" android:color="#FFFF634D"/>
<item android:offset="0.2043" android:color="#FFFE6464"/>
<item android:offset="0.5209" android:color="#FFFC6581"/>
<item android:offset="0.7936" android:color="#FFFA6694"/>
<item android:offset="0.9892" android:color="#FFFA669A"/>
</gradient>
</aapt:attr>
</path>
</vector> </vector>

View File

@ -1,45 +1,5 @@
<!--
~ Copyright (c) Kuba Szczodrzyński 2019-11-11.
-->
<vector android:height="128dp" android:viewportHeight="64" <vector android:height="128dp" android:viewportHeight="64"
android:viewportWidth="64" android:width="128dp" android:viewportWidth="64" android:width="128dp" xmlns:android="http://schemas.android.com/apk/res/android">
xmlns:aapt="http://schemas.android.com/aapt" xmlns:android="http://schemas.android.com/apk/res/android"> <path android:fillColor="#fd646f" android:pathData="m51.483,15.326 l3.937,-3.93c1.24,-1.25 0.36,-3.38 -1.4,-3.38h-12.02c-1.1,0 -2,0.9 -2,2v12c0,1.76 2.13,2.65 3.38,1.4l4.537,-4.529c2.615,3.426 4.083,7.646 4.083,12.113 0,11.215 -8.565,20 -19.5,20 -1.381,0 -2.5,1.119 -2.5,2.5s1.119,2.5 2.5,2.5c13.738,0 24.5,-10.981 24.5,-25 0,-5.817 -1.988,-11.301 -5.517,-15.674z"/>
<path android:pathData="m59,16h2c1.848,0 3.319,-1.693 2.94,-3.605 -0.283,-1.423 -1.62,-2.395 -3.071,-2.395h-10.369c-0.828,0 -1.5,-0.672 -1.5,-1.5s0.672,-1.5 1.5,-1.5h5.912c1.139,0 2.202,-0.721 2.497,-1.821 0.446,-1.663 -0.813,-3.179 -2.409,-3.179h-48c-1.925,0 -3.5,1.575 -3.5,3.5s1.575,3.5 3.5,3.5h1.5c1.105,0 2,0.895 2,2s-0.895,2 -2,2h-5.385c-1.67,0 -3.195,1.122 -3.537,2.757 -0.47,2.245 1.254,4.243 3.422,4.243h7.5l-3,12h-5.385c-1.67,0 -3.195,1.122 -3.537,2.757 -0.47,2.245 1.254,4.243 3.422,4.243h0.5c1.105,0 2,0.895 2,2s-0.895,2 -2,2h-0.5c-2.168,0 -3.892,1.998 -3.422,4.243 0.342,1.635 1.867,2.757 3.537,2.757h5.385c1.105,0 2,0.895 2,2s-0.895,2 -2,2h-1.385c-1.67,0 -3.195,1.122 -3.537,2.757 -0.47,2.245 1.254,4.243 3.422,4.243h48.5c1.657,0 3,-1.343 3,-3s-1.343,-3 -3,-3h-0.5c-1.381,0 -2.5,-1.119 -2.5,-2.5s1.119,-2.5 2.5,-2.5h4.5c1.657,0 3,-1.343 3,-3s-1.343,-3 -3,-3h-8.377c2.141,-3.494 3.377,-7.602 3.377,-12 0,-2.441 -0.384,-4.792 -1.088,-7h6.501c1.139,0 2.202,-0.721 2.497,-1.821 0.445,-1.663 -0.814,-3.179 -2.41,-3.179h-1.5c-1.105,0 -2,-0.895 -2,-2s0.895,-2 2,-2z"> <path android:fillColor="#fd646f" android:pathData="m20.604,38.58 l-4.523,4.53c-2.613,-3.427 -4.081,-7.647 -4.081,-12.11 0,-11.215 8.565,-20 19.5,-20 1.381,0 2.5,-1.119 2.5,-2.5s-1.119,-2.5 -2.5,-2.5c-13.738,0 -24.5,10.981 -24.5,25 0,5.815 1.989,11.303 5.52,15.677l-3.936,3.943c-1.25,1.25 -0.36,3.38 1.4,3.38h12c1.1,0 2,-0.9 2,-2v-12.02c0,-1.76 -2.13,-2.64 -3.38,-1.4z"/>
<aapt:attr name="android:fillColor">
<gradient android:centerX="32" android:centerY="31.500021"
android:gradientRadius="32" android:tileMode="mirror" android:type="radial">
<item android:color="#1F0FCCFF" android:offset="0"/>
<item android:color="#1F0FCEFF" android:offset="0.193"/>
<item android:color="#1F0FD5FF" android:offset="0.703"/>
<item android:color="#1F0ECEFF" android:offset="1"/>
</gradient>
</aapt:attr>
</path>
<path android:pathData="M51.483,15.326l3.937,-3.93c1.24,-1.25 0.36,-3.38 -1.4,-3.38H42c-1.1,0 -2,0.9 -2,2v12c0,1.76 2.13,2.65 3.38,1.4l4.537,-4.529C50.532,22.313 52,26.533 52,31c0,11.215 -8.565,20 -19.5,20c-1.381,0 -2.5,1.119 -2.5,2.5s1.119,2.5 2.5,2.5C46.238,56 57,45.019 57,31C57,25.183 55.012,19.699 51.483,15.326z">
<aapt:attr name="android:fillColor">
<gradient android:endX="43.5" android:endY="-21.15"
android:startX="43.5" android:startY="59.034"
android:tileMode="mirror" android:type="linear">
<item android:color="#FFFF634D" android:offset="0"/>
<item android:color="#FFFE6464" android:offset="0.204"/>
<item android:color="#FFFC6581" android:offset="0.521"/>
<item android:color="#FFFA6694" android:offset="0.794"/>
<item android:color="#FFFA669A" android:offset="0.989"/>
</gradient>
</aapt:attr>
</path>
<path android:pathData="M20.604,38.58l-4.523,4.53C13.468,39.683 12,35.463 12,31c0,-11.215 8.565,-20 19.5,-20c1.381,0 2.5,-1.119 2.5,-2.5S32.881,6 31.5,6C17.762,6 7,16.981 7,31c0,5.815 1.989,11.303 5.52,15.677L8.584,50.62c-1.25,1.25 -0.36,3.38 1.4,3.38h12c1.1,0 2,-0.9 2,-2V39.98C23.984,38.22 21.854,37.34 20.604,38.58z">
<aapt:attr name="android:fillColor">
<gradient android:endX="20.5" android:endY="-24.844"
android:startX="20.5" android:startY="55.227"
android:tileMode="mirror" android:type="linear">
<item android:color="#FFFF634D" android:offset="0"/>
<item android:color="#FFFE6464" android:offset="0.204"/>
<item android:color="#FFFC6581" android:offset="0.521"/>
<item android:color="#FFFA6694" android:offset="0.794"/>
<item android:color="#FFFA669A" android:offset="0.989"/>
</gradient>
</aapt:attr>
</path>
</vector> </vector>

View File

@ -1,202 +1,30 @@
<!--
~ Copyright (c) Kuba Szczodrzyński 2019-11-11.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android" <vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt"
android:width="128dp" android:width="128dp"
android:height="128dp" android:height="128dp"
android:viewportWidth="128" android:viewportWidth="128"
android:viewportHeight="128"> android:viewportHeight="128">
<path <path
android:pathData="m118,62.129h2.605c3.789,0 7.188,-2.84 7.383,-6.625 0.211,-4.035 -3,-7.375 -6.988,-7.375h-4.805c-1.965,0 -3.785,-1.328 -4.129,-3.262 -0.043,-0.25 -0.066,-0.5 -0.066,-0.746 0.004,-2.168 1.734,-3.93 3.887,-3.988 3.023,-0.082 5.731,-2.293 6.07,-5.297 0.027,-0.242 0.039,-0.477 0.039,-0.711 0,-3.313 -2.688,-5.996 -6,-5.996h-4.602c-0.434,0 -4.863,-0.035 -5.281,-0.105 -0.035,-0.008 -0.074,-0.016 -0.113,-0.023v18h-42v-18h25.715c0.438,-1.688 0.539,-3.512 -0.945,-5.719 -1.836,-2.742 -5.023,-4.281 -8.324,-4.281h-9.445c-1.656,0 -3,-1.344 -3,-3s1.344,-3 3,-3h0.66c3.25,0 6.16,-2.434 6.332,-5.68 0.18,-3.461 -2.57,-6.32 -5.992,-6.32h-57.66c-3.25,0 -6.16,2.434 -6.332,5.68 -0.18,3.457 2.57,6.32 5.992,6.32h15c1.656,0 3,1.344 3,3s-1.344,3 -3,3h-17c-4.418,0 -8,3.582 -8,8s3.582,8 8,8h26v12h-14l2,21.109c-1.07,0.801 -1.836,1.98 -1.973,3.375 -0.184,1.84 0.633,3.5 1.973,4.504v5.047c-0.027,0.148 -0.043,0.297 -0.027,0.449 0.301,2.992 -2.039,5.516 -4.973,5.516h-14.66c-2.984,0 -5.762,2.023 -6.25,4.965 -0.246,1.465 0.043,2.848 0.684,4.012 1.074,1.938 3.234,3.023 5.449,3.023h3.164c2.375,0 4.207,1.328 4.551,3.27 0.039,0.246 0.063,0.488 0.063,0.723 0.004,2.215 -1.789,4.008 -3.996,4.008h-0.004c-2.27,0 -4.473,1.203 -5.398,3.277 -1.98,4.426 1.207,8.723 5.398,8.723h62c3.313,0 6,-2.688 6,-6s-2.688,-6 -6,-6h-10v-20l46,0.129h9.66c3.141,0 6.168,-2.539 6.328,-5.676 0.184,-3.461 -2.57,-6.324 -5.992,-6.324h-0.023,-0.035c-1.555,0 -3.078,-0.508 -4.156,-1.535 -0.34,-0.324 -0.637,-0.699 -0.875,-1.129 -2.574,-4.637 0.711,-9.336 5.094,-9.336z"> android:pathData="m18,100v-60h92v60c0,5.523 -4.477,10 -10,10h-72c-5.523,0 -10,-4.477 -10,-10z"
<aapt:attr name="android:fillColor"> android:fillColor="#ffc662"/>
<gradient
android:gradientRadius="80.322"
android:centerX="60.334"
android:centerY="65.146"
android:type="radial"
android:tileMode="mirror">
<item android:offset="0" android:color="#1F0FCCFF"/>
<item android:offset="0.1927" android:color="#1F0FCEFF"/>
<item android:offset="0.7025" android:color="#1F0FD5FF"/>
<item android:offset="1" android:color="#1F0FD7FF"/>
</gradient>
</aapt:attr>
</path>
<path <path
android:pathData="m18,100v-60h92v60c0,5.523 -4.477,10 -10,10h-72c-5.523,0 -10,-4.477 -10,-10z"> android:pathData="m110,29.602v16.398h-92v-16.398c0,-5.309 4.332,-9.602 9.684,-9.602h72.633c5.352,0 9.684,4.293 9.684,9.602"
<aapt:attr name="android:fillColor"> android:fillColor="#ff634f"/>
<gradient
android:startY="110"
android:startX="64"
android:endY="40"
android:endX="64"
android:type="linear"
android:tileMode="mirror">
<item android:offset="0" android:color="#FFFFC662"/>
<item android:offset="0.0036" android:color="#FFFFC662"/>
<item android:offset="0.6085" android:color="#FFFFC582"/>
<item android:offset="1" android:color="#FFFFC491"/>
</gradient>
</aapt:attr>
</path>
<path <path
android:pathData="m110,29.602v16.398h-92v-16.398c0,-5.309 4.332,-9.602 9.684,-9.602h72.633c5.352,0 9.684,4.293 9.684,9.602"> android:pathData="m43,34c-2.75,0 -5,-2.25 -5,-5v-12c0,-2.75 2.25,-5 5,-5s5,2.25 5,5v12c0,2.75 -2.25,5 -5,5z"
<aapt:attr name="android:fillColor"> android:fillColor="#888"/>
<gradient
android:startY="46"
android:startX="64"
android:endY="20"
android:endX="64"
android:type="linear"
android:tileMode="mirror">
<item android:offset="0" android:color="#FFFF634D"/>
<item android:offset="0.2083" android:color="#FFFD6464"/>
<item android:offset="0.5223" android:color="#FFFC6582"/>
<item android:offset="0.7935" android:color="#FFFA6694"/>
<item android:offset="0.9892" android:color="#FFFA669A"/>
<item android:offset="1" android:color="#FFFA669A"/>
</gradient>
</aapt:attr>
</path>
<path <path
android:pathData="m49.309,20h-12.621c-2.832,1.988 -4.688,5.277 -4.688,9 0,6.07 4.93,11 11,11 6.07,0 11,-4.93 11,-11 0,-3.723 -1.855,-7.012 -4.691,-9z"> android:pathData="m85,34c-2.75,0 -5,-2.25 -5,-5v-12c0,-2.75 2.25,-5 5,-5s5,2.25 5,5v12c0,2.75 -2.25,5 -5,5z"
<aapt:attr name="android:fillColor"> android:fillColor="#888"/>
<gradient
android:startY="40"
android:startX="43"
android:endY="20"
android:endX="43"
android:type="linear"
android:tileMode="mirror">
<item android:offset="0" android:color="#FFFF5840"/>
<item android:offset="0.0072" android:color="#FFFF5840"/>
<item android:offset="0.9892" android:color="#FFFA528C"/>
<item android:offset="1" android:color="#FFFA528C"/>
</gradient>
</aapt:attr>
</path>
<path <path
android:pathData="m43,34c-2.75,0 -5,-2.25 -5,-5v-12c0,-2.75 2.25,-5 5,-5s5,2.25 5,5v12c0,2.75 -2.25,5 -5,5z"> android:pathData="m48,74h-8c-2.211,0 -4,-1.789 -4,-4v-8c0,-2.211 1.789,-4 4,-4h8c2.211,0 4,1.789 4,4v8c0,2.211 -1.789,4 -4,4zM72,70v-8c0,-2.211 -1.789,-4 -4,-4h-8c-2.211,0 -4,1.789 -4,4v8c0,2.211 1.789,4 4,4h8c2.211,0 4,-1.789 4,-4zM92,70v-8c0,-2.211 -1.789,-4 -4,-4h-8c-2.211,0 -4,1.789 -4,4v8c0,2.211 1.789,4 4,4h8c2.211,0 4,-1.789 4,-4zM52,90v-8c0,-2.211 -1.789,-4 -4,-4h-8c-2.211,0 -4,1.789 -4,4v8c0,2.211 1.789,4 4,4h8c2.211,0 4,-1.789 4,-4zM72,90v-8c0,-2.211 -1.789,-4 -4,-4h-8c-2.211,0 -4,1.789 -4,4v8c0,2.211 1.789,4 4,4h8c2.211,0 4,-1.789 4,-4zM92,90v-8c0,-2.211 -1.789,-4 -4,-4h-8c-2.211,0 -4,1.789 -4,4v8c0,2.211 1.789,4 4,4h8c2.211,0 4,-1.789 4,-4z"
<aapt:attr name="android:fillColor"> android:fillColor="#ffe79f"/>
<gradient
android:startY="12"
android:startX="43"
android:endY="34"
android:endX="43"
android:type="linear"
android:tileMode="mirror">
<item android:offset="0" android:color="#FFA4A4A4"/>
<item android:offset="0.6301" android:color="#FF7F7F7F"/>
<item android:offset="1" android:color="#FF6F6F6F"/>
</gradient>
</aapt:attr>
</path>
<path
android:pathData="m91.309,20h-12.621c-2.832,1.988 -4.688,5.277 -4.688,9 0,6.07 4.93,11 11,11 6.07,0 11,-4.93 11,-11 0,-3.723 -1.855,-7.012 -4.691,-9z">
<aapt:attr name="android:fillColor">
<gradient
android:startY="40"
android:startX="85"
android:endY="20"
android:endX="85"
android:type="linear"
android:tileMode="mirror">
<item android:offset="0" android:color="#FFFF5840"/>
<item android:offset="0.0072" android:color="#FFFF5840"/>
<item android:offset="0.9892" android:color="#FFFA528C"/>
<item android:offset="1" android:color="#FFFA528C"/>
</gradient>
</aapt:attr>
</path>
<path
android:pathData="m85,34c-2.75,0 -5,-2.25 -5,-5v-12c0,-2.75 2.25,-5 5,-5s5,2.25 5,5v12c0,2.75 -2.25,5 -5,5z">
<aapt:attr name="android:fillColor">
<gradient
android:startY="12"
android:startX="85"
android:endY="34"
android:endX="85"
android:type="linear"
android:tileMode="mirror">
<item android:offset="0" android:color="#FFA4A4A4"/>
<item android:offset="0.6301" android:color="#FF7F7F7F"/>
<item android:offset="1" android:color="#FF6F6F6F"/>
</gradient>
</aapt:attr>
</path>
<path
android:pathData="m78,81.008c0,2.762 2.238,5 5,4.996h2.992c3.316,-0.008 6.008,2.684 6.008,5.996s-2.688,6 -6,6h-8c-3.313,0 -6,2.688 -6,6s2.688,6 6,6h22c5.523,0 10,-4.477 10,-10v-24l-27.008,0.012c-2.758,-0 -4.992,2.234 -4.992,4.996z"
android:fillColor="#ffb86a"/>
<path
android:pathData="m18,68h17.785c1.992,0 3.84,-1.363 4.16,-3.328 0.406,-2.508 -1.516,-4.672 -3.945,-4.672h-3c-1.656,0 -3,-1.344 -3,-3s1.344,-3 3,-3h14.785c1.992,0 3.84,-1.363 4.16,-3.328 0.406,-2.508 -1.516,-4.672 -3.945,-4.672h-30z">
<aapt:attr name="android:fillColor">
<gradient
android:startY="108.25"
android:startX="35"
android:endY="43.742"
android:endX="35"
android:type="linear"
android:tileMode="mirror">
<item android:offset="0" android:color="#FFFFCE76"/>
<item android:offset="0.0036" android:color="#FFFFCE76"/>
<item android:offset="0.6054" android:color="#FFFFCD92"/>
<item android:offset="1" android:color="#FFFFCCA0"/>
</gradient>
</aapt:attr>
</path>
<path
android:pathData="m98,62c0,3.313 -2.688,6 -6,6s-6,-2.688 -6,-6 2.688,-6 6,-6 6,2.688 6,6z"
android:fillColor="#ffb977"/>
<path
android:pathData="m48,74h-8c-2.211,0 -4,-1.789 -4,-4v-8c0,-2.211 1.789,-4 4,-4h8c2.211,0 4,1.789 4,4v8c0,2.211 -1.789,4 -4,4zM72,70v-8c0,-2.211 -1.789,-4 -4,-4h-8c-2.211,0 -4,1.789 -4,4v8c0,2.211 1.789,4 4,4h8c2.211,0 4,-1.789 4,-4zM92,70v-8c0,-2.211 -1.789,-4 -4,-4h-8c-2.211,0 -4,1.789 -4,4v8c0,2.211 1.789,4 4,4h8c2.211,0 4,-1.789 4,-4zM52,90v-8c0,-2.211 -1.789,-4 -4,-4h-8c-2.211,0 -4,1.789 -4,4v8c0,2.211 1.789,4 4,4h8c2.211,0 4,-1.789 4,-4zM72,90v-8c0,-2.211 -1.789,-4 -4,-4h-8c-2.211,0 -4,1.789 -4,4v8c0,2.211 1.789,4 4,4h8c2.211,0 4,-1.789 4,-4zM92,90v-8c0,-2.211 -1.789,-4 -4,-4h-8c-2.211,0 -4,1.789 -4,4v8c0,2.211 1.789,4 4,4h8c2.211,0 4,-1.789 4,-4z">
<aapt:attr name="android:fillColor">
<gradient
android:startY="94"
android:startX="64"
android:endY="58"
android:endX="64"
android:type="linear"
android:tileMode="mirror">
<item android:offset="0" android:color="#FFFFE79F"/>
<item android:offset="0.1186" android:color="#FFFFE9A6"/>
<item android:offset="1" android:color="#FFFFF5D5"/>
</gradient>
</aapt:attr>
</path>
<path <path
android:pathData="m124,104c0,11.047 -8.953,20 -20,20 -11.047,0 -20,-8.953 -20,-20 0,-11.047 8.953,-20 20,-20 11.047,0 20,8.953 20,20z" android:pathData="m124,104c0,11.047 -8.953,20 -20,20 -11.047,0 -20,-8.953 -20,-20 0,-11.047 8.953,-20 20,-20 11.047,0 20,8.953 20,20z"
android:fillColor="#fff"/> android:fillColor="#fff"/>
<path <path
android:pathData="m104,80c-13.254,0 -24,10.746 -24,24 0,13.254 10.746,24 24,24 13.254,0 24,-10.746 24,-24 0,-13.254 -10.746,-24 -24,-24zM104,120c-8.836,0 -16,-7.164 -16,-16 0,-8.836 7.164,-16 16,-16 8.836,0 16,7.164 16,16 0,8.836 -7.164,16 -16,16z"> android:pathData="m104,80c-13.254,0 -24,10.746 -24,24 0,13.254 10.746,24 24,24 13.254,0 24,-10.746 24,-24 0,-13.254 -10.746,-24 -24,-24zM104,120c-8.836,0 -16,-7.164 -16,-16 0,-8.836 7.164,-16 16,-16 8.836,0 16,7.164 16,16 0,8.836 -7.164,16 -16,16z"
<aapt:attr name="android:fillColor"> android:fillColor="#1f80e5"/>
<gradient
android:startY="128"
android:startX="104"
android:endY="80"
android:endX="104"
android:type="linear"
android:tileMode="mirror">
<item android:offset="0" android:color="#FF155CDE"/>
<item android:offset="0.6248" android:color="#FF2289E7"/>
<item android:offset="1" android:color="#FF289FEC"/>
</gradient>
</aapt:attr>
</path>
<path <path
android:pathData="m113.41,110.59 l-5.563,-5.563c0.086,-0.328 0.148,-0.668 0.148,-1.023 0,-1.477 -0.809,-2.754 -2,-3.445v-6.555c0,-1.105 -0.895,-2 -2,-2s-2,0.895 -2,2v6.555c-1.191,0.691 -2,1.969 -2,3.445 0,2.211 1.789,4 4,4 0.355,0 0.695,-0.063 1.023,-0.148l5.563,5.563c0.391,0.391 0.902,0.586 1.414,0.586s1.023,-0.195 1.414,-0.586c0.781,-0.781 0.781,-2.047 0,-2.828z"> android:pathData="m113.41,110.59 l-5.563,-5.563c0.086,-0.328 0.148,-0.668 0.148,-1.023 0,-1.477 -0.809,-2.754 -2,-3.445v-6.555c0,-1.105 -0.895,-2 -2,-2s-2,0.895 -2,2v6.555c-1.191,0.691 -2,1.969 -2,3.445 0,2.211 1.789,4 4,4 0.355,0 0.695,-0.063 1.023,-0.148l5.563,5.563c0.391,0.391 0.902,0.586 1.414,0.586s1.023,-0.195 1.414,-0.586c0.781,-0.781 0.781,-2.047 0,-2.828z"
<aapt:attr name="android:fillColor"> android:fillColor="#919191"/>
<gradient
android:startY="92"
android:startX="107"
android:endY="114"
android:endX="107"
android:type="linear"
android:tileMode="mirror">
<item android:offset="0" android:color="#FF919191"/>
<item android:offset="1" android:color="#FF6F6F6F"/>
</gradient>
</aapt:attr>
</path>
</vector> </vector>

View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright (c) Kacper Ziubryniewicz 2019-11-23
-->
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval">
<solid android:color="@color/red500"/>
<size android:width="10dp" android:height="10dp"/>
</shape>

View File

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright (c) Kuba Szczodrzyński 2019-11-23.
-->
<com.google.android.material.card.MaterialCardView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="16dp"
android:layout_marginTop="8dp"
android:layout_marginRight="16dp"
android:layout_marginBottom="8dp"
android:clickable="true"
android:focusable="true" />

View File

@ -0,0 +1,47 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright (c) Kuba Szczodrzyński 2019-11-24.
-->
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
tools:layout_margin="8dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<ImageView
android:id="@+id/image"
android:layout_width="40dp"
android:layout_height="40dp"
app:srcCompat="@drawable/emoji_sad" />
<TextView
android:id="@+id/title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginStart="8dp"
android:layout_marginLeft="8dp"
android:text="@string/home_lucky_number_no_info"
android:textAppearance="@style/NavView.TextView.Title" />
</LinearLayout>
<TextView
android:id="@+id/subText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textAppearance="@style/NavView.TextView.Helper"
tools:text="Oranż Metylowy • Numer w dzienniku to 23" />
</LinearLayout>
</layout>

View File

@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright (c) Kuba Szczodrzyński 2019-11-24.
-->
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
tools:layout_margin="8dp">
<TextView
android:id="@+id/text"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
</layout>

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